Lyrasis fork of the NYPL's Library Simplified Android client.
The contents of this repository provide the The Palace Project's Android client application, Palace.
Application | Module | Description |
---|---|---|
Palace | simplified-app-palace | The DRM-enabled application |
Make sure you clone this repository with git clone --recursive
.
If you forgot to use --recursive
, then execute:
$ git submodule update --init
Install the Android SDK and Android Studio. We don't support the use of any other IDE at the moment.
Install a reasonably modern JDK, at least JDK 17. We don't recommend building on anything newer than the current LTS JDK for everyday usage.
Any of the following JDKs should work:
The JAVA_HOME
environment variable must be set correctly. You can check what it is set to in
most shells with echo $JAVA_HOME
. If that command does not show anything, adding the following
line to $HOME/.profile
and then executing source $HOME/.profile
or opening a new shell
should suffice:
# Replace NNN with your particular version of 17.
export JAVA_HOME=/path/to/jdk-17+NNN
You can verify that everything is set up correctly by inspecting the results of both
java -version
and javac -version
:
$ java -version
openjdk version "17.0.8" 2023-07-18
OpenJDK Runtime Environment (build 17.0.8+7)
OpenJDK 64-Bit Server VM (build 17.0.8+7, mixed mode)
Our application can use packages that are only available from our S3 bucket. If you wish to use these packages, you need to obtain S3 credentials and then tell Gradle to use them.
S3 credentials can be obtained by emailing [email protected]
or by asking in the #palace-project-tech
channel of
lyrasis.slack.com.
Once you have your credentials, the following lines must be added to
$HOME/.gradle/gradle.properties
:
# Replace ACCESS_KEY and SECRET_ACCESS_KEY appropriately.
# Do NOT use quotes around either value.
org.thepalaceproject.aws.access_key_id=ACCESS_KEY
org.thepalaceproject.aws.secret_access_key=SECRET_ACCESS_KEY
org.thepalaceproject.s3.depend=true
If you wish to generate a signed APK for publishing the application, you will need to copy
a keystore to release.jks
and set the following values correctly in
$HOME/.gradle/gradle.properties
:
# Replace KEYALIAS, KEYPASSWORD, and STOREPASSWORD appropriately.
# Do NOT use quotes around values.
org.thepalaceproject.keyAlias=KEYALIAS
org.thepalaceproject.keyPassword=KEYPASSWORD
org.thepalaceproject.storePassword=STOREPASSWORD
Note that APK files are only signed if the code is built in release mode. In other words, you need to use either of these commands to produce signed APK files:
$ ./gradlew clean assembleRelease test
$ ./gradlew clean assemble test
The application contains optional support for various DRM systems. These are all disabled by default, and must be enabled explicitly in order to build a version of the Palace application equivalent to the Play Store version.
Firstly, make sure you have your S3 credentials
correctly configured. Then, add the following property to your
$HOME/.gradle/gradle.properties
file:
org.thepalaceproject.adobeDRM.enabled=true
This will instruct the build system that you want to build with DRM enabled.
If you were to attempt to build the code right now, you would encounter a
build failure: When DRM is enabled, the build system will check that you have
provided various configuration files containing secrets that the DRM systems
require, and will refuse to build the app if you've failed to do this. The
build system can copy in the correct secrets for you if tell it the location
of directories containing those secrets. There are two directories to configure:
A credentials directory, containing secrets needed to build the app, and an
assets directory, containing secrets needed by the app at runtime (which will be
installed as assets in the app). For example, assuming that you have
Palace's credentials in '/path/to/palace/credentials',
and assets in /path/to/palace/assets
, you can add the following properties to
your $HOME/.gradle/gradle.properties
file and the build system will copy in
the required secrets at build time:
org.thepalaceproject.app.credentials.palace=/path/to/palace/credentials
org.thepalaceproject.app.assets.palace=/path/to/palace/assets
The project currently makes calls to the Palace Adobe DRM API. The API is structured in a manner that means that enabling actual support for Adobe DRM simply entails adding a dependency on the Palace Adobe DRM implementation. This implementation is only available to DRM licensees. Please get in touch with us if you have a DRM license and want to produce a DRM-enabled build!
The project currently uses the Palace AudioBook API to provide support for playing audio books. The API is structured such that adding support for new types of audiobooks and playback engines only involves adding those modules to the classpath. The Palace app depends on a Findaway module to play Findaway audio books. Please get in touch with us if you have a Findaway license and want to produce a Findaway-enabled build.
The project uses Readium's liblcp module to provide support for LCP
content protection. This module must be available on the classpath
when the org.thepalaceproject.lcp.enabled
property is true. Otherwise,
the project will not compile. Please get in touch with us if you have
an LCP license and want to produce a DRM-enabled build.
All new features should be created on feature branches and merged to main
once
completed.
The project, as a whole, roughly follows an MVC architecture distributed over the application modules. The controller in the application is task-based and executes all tasks on a background thread to avoid any possibility of blocking the Android UI thread.
Newer application modules, roughly follow an MVVM architecture. The View Model in the application exposes reactive properties and executes all tasks on a background thread. The View observes those properties and updates on the Android UI thread.
The project makes various references to APIs and SPIs. API stands for application programming interface and SPI stands for service provider interface.
An API module defines a user-visible contract (or specification) for a module; it defines the data types and abstract interfaces via which the user is expected to make calls in order to make use of a module. An API module is typically paired with an implementation module that provides concrete implementations of the API interface types. A good example of this is the accounts database: The Accounts database API declares a set of data types and interfaces that describe how an accounts database should behave. The Accounts database implementation module provides an implementation of the described API. Keeping the API specification strictly separated from the implementation in this manner has a number of benefits:
-
Substitutability: When an API has a sufficiently detailed specification, it's possible to replace an implementation module with a superior implementation without having to modify code that makes calls to the API.
-
Testability: Keeping API types strictly separated from implementation types tends to lead to interfaces that are easy to mock.
-
Understandability: Users of modules can go straight to the API specifications to find out how to use them. This cuts down on the amount of archaeological work necessary to learn how to use the application's internal interfaces.
An SPI module is similar to an API in that it provides a specification, however the defined interfaces are expected to be implemented by users rather than called by users directly. An implementor of an SPI is known as a service provider.
A good example of an SPI is the Account provider source SPI; the SPI defines an interface that is expected to be implemented by account provider sources. The file-based source module is capable of delivering account provider descriptions from a bundled asset file. The registry source implementation is capable of fetching account provider descriptions from the NYPL's registry server. Neither the SPI or the implementation modules are expected to be used by application programmers directly: Instead, implementation modules are loaded using ServiceLoader by the Account provider registry, and users interact with the registry via a published registry API. This same design pattern is used by the NYPL AudioBook API to provide a common API into which new audio book players and parsers can be introduced without needing to modify application code at all.
Modules should make every attempt not to specify explicit dependencies on implementation modules.
API and implementation modules should typically only depend on other API modules, leaving the choice
of implementation modules to the final application assembly. In other words, a module should say
"I can work with any module that provides this API" rather than "I depend on implementation M
of a particular API". Following this convention allows us to replace module implementation without
having to modify lots of different parts of the application; it allows us to avoid
strong coupling between modules.
Most of the modularity concepts described here were pioneered by the OSGi module system and so, although the Library Simplified application is not an OSGi application, much of the design and architecture conforms to conventions followed by OSGi applications. Further reading can be found on the OSGi web site.
The build is driven by the build.gradle file in the root of the project,
with the build.gradle
files in each module typically only listing dependencies (the actual
dependency definitions are defined in the root build.gradle
file to avoid duplicating version
numbers over the whole project). Metadata used to publish builds (such as Maven group IDs, version
numbers, etc) is defined in the gradle.properties
file in each module. The gradle.properties
file in the root of the project defines default values that are overridden as necessary by each
module.
We aggregate all unit tests in the simplified-tests module. Tests should be written using the JUnit 5 library, although at the time of writing we have one test that still requires JUnit 4 due to the use of Robolectric.
The project is heavily modularized in order to keep the separate application components as loosely coupled as possible. New features should typically be implemented as new modules.
The above table is generated with ReadMe.java.
Binaries for every commit are built and published in the android-binaries repository. Note that these binaries are not to be considered production ready and may have undergone little or no testing. Use at your own risk!
The codebase uses ktlint to enforce a consistent
code style. It's possible to ensure that any changes you've made to the code
continue to pass ktlint
checks by running the ktlintFormat
task to reformat
source code:
$ ./gradlew ktlintFormat
Please see RELEASING.md for documentation on our release process.
Copyright 2015 The New York Public Library, Astor, Lenox, and Tilden Foundations
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.