APK Caching

This document describes design of an APK caching solution for rapid installation of preloaded apps on a device that supports A/B partitions.

OEMs can place preloads and popular apps in the APK cache stored in the mostly empty B partition on new A/B-partitioned devices without impacting any user-facing data space. By having an APK cache available on the device, new or recently factory reset devices are ready for use almost immediately, without needing to download APK files from Google Play.

Use cases

  • Store preloaded apps in B partition for faster setup
  • Store popular apps in B partition for faster restoration

Prerequisites

To use this feature, the device needs:

  • Android 8.1 (O MR1) release installed
  • A/B partition implemented

Preloaded content can be copied only during first boot. This is because on devices supporting A/B system updates, the B partition doesn't actually store system image files, but instead preloaded content like retail demo resources, OAT files and the APK cache. After resources have been copied to the /data partition (this happens on first boot), the B partition will be used by over-the-air (OTA) updates for downloading updated versions of the system image.

Therefore, the APK cache cannot be updated through OTA; it can be preloaded only at a factory. Factory reset affects only the /data partition. The system B partition still has the preloaded content until the OTA image is downloaded. After factory reset, the system will go through first boot again. This means APK caching is not available if the OTA image is downloaded to the B partition, and then the device is factory reset.

Implementation

Approach 1. Content on system_other partition

Pro: Preloaded content is not lost after factory reset - it will be copied from the B partition after a reboot.

Con: Requires space on B partition. Boot after factory reset requires additional time to copy preloaded content.

In order for preloads to be copied during first boot, the system calls a script in /system/bin/preloads_copy.sh. The script is called with a single argument (path to the read-only mount point for system_b partition):

To implement this feature, make these device-specific changes. Here is an example from Marlin:

  1. Add the script that does the copying to the device-common.mk file (in this case, device/google/marlin/device-common.mk), like so:
    # Script that copies preloads directory from system_other to data partition
    PRODUCT_COPY_FILES += \
        device/google/marlin/preloads_copy.sh:system/bin/preloads_copy.sh
    
    Find example script source at: device/google/marlin/preloads_copy.sh
  2. Edit the init.common.rc file to have it create the necessary /data/preloads directory and subdirectories:
    mkdir /data/preloads 0775 system system
    mkdir /data/preloads/media 0775 system system
    mkdir /data/preloads/demo 0775 system system
    
    Find example init file source at: device/google/marlin/init.common.rc
  3. Define a new SELinux domain in the file preloads_copy.te:
    type preloads_copy, domain, coredomain;
    type preloads_copy_exec, exec_type, vendor_file_type, file_type;
    
    init_daemon_domain(preloads_copy)
    
    allow preloads_copy shell_exec:file rx_file_perms;
    allow preloads_copy toolbox_exec:file rx_file_perms;
    allow preloads_copy preloads_data_file:dir create_dir_perms;
    allow preloads_copy preloads_data_file:file create_file_perms;
    allow preloads_copy preloads_media_file:dir create_dir_perms;
    allow preloads_copy preloads_media_file:file create_file_perms;
    
    # Allow to copy from /postinstall
    allow preloads_copy system_file:dir r_dir_perms;
    
    Find an example SELinux domain file at: /device/google/marlin/+/main/sepolicy/preloads_copy.te
  4. Register the domain in a new /sepolicy/file_contexts file:
    /system/bin/preloads_copy\.sh     u:object_r:preloads_copy_exec:s0
    
    Find an example SELinux contexts file at: device/google/marlin/sepolicy/preloads_copy.te
  5. At build time, the directory with preloaded content must be copied to the system_other partition:
    # Copy contents of preloads directory to system_other partition
    PRODUCT_COPY_FILES += \
        $(call find-copy-subdir-files,*,vendor/google_devices/marlin/preloads,system_other/preloads)
    
    This is an example of a change in a Makefile that allows copying APK cache resources from vendor's Git repository (in our case it was vendor/google_devices/marlin/preloads) to the location on system_other partition that will later be copied to /data/preloads when device boots for the first time. This script runs at build time to prepare system_other image. It expects preloaded content to be available in vendor/google_devices/marlin/preloads. OEM is free to choose the actual repository name/path.
  6. The APK cache is located in /data/preloads/file_cache and has the following layout:
    /data/preloads/file_cache/
        app.package.name.1/
              file1
              fileN
        app.package.name.N/
    
    This is the final directory structure on the devices. OEMs are free to choose any implementation approach as long as the final file structure replicates the one described above.

Approach 2. Content on user data image flashed at factory

This alternative approach assumes that preloaded content is already included in the /data/preloads directory on the /data partition.

Pro: Works out of the box - no need to make device customizations to copy files on first boot. Content is already on the /data partition.

Con: Preloaded content is lost after a factory reset. While this may be acceptable for some, it may not always work for OEMs who factory reset devices after doing quality control inspections.

A new @SystemApi method, getPreloadsFileCache(), was added to android.content.Context. It returns an absolute path to an application-specific directory in the preloaded cache.

A new method, IPackageManager.deletePreloadsFileCache, was added that allows deleting the preloads directory to reclaim all space. The method can be called only by apps with SYSTEM_UID, i.e. system server or Settings.

App preparation

Only privileged applications can access the preloads cache directory. For that access, apps must be installed in the /system/priv-app directory.

Validation

  • After first boot, the device should have content in the /data/preloads/file_cache directory.
  • The content in the file_cache/ directory must be deleted if the device runs low on storage.

Use the example ApkCacheTest app for testing APK cache.

  1. Build the app by running this command from the root directory:
    make ApkCacheTest
    
  2. Install the app as a privileged application. (Remember, only privileged apps can access the APK cache.) This requires a rooted device:
    adb root && adb remount
    adb shell mkdir /system/priv-app/ApkCacheTest
    adb push $ANDROID_PRODUCT_OUT/data/app/ApkCacheTest/ApkCacheTest.apk /system/priv-app/ApkCacheTest/
    adb shell stop && adb shell start
    
  3. Simulate the file cache directory and its content if needed (also requiring root privileges):
    adb shell mkdir -p /data/preloads/file_cache/com.android.apkcachetest
    adb shell restorecon -r /data/preloads
    adb shell "echo "Test File" > /data/preloads/file_cache/com.android.apkcachetest/test.txt"
    
  4. Test the app. After installing the app and creating test file_cache directory, open the ApkCacheTest app. It should show one file test.txt and its contents. See this screenshot to see how these results appear in the user interface.

    Figure 1. ApkCacheTest results