| <html devsite> |
| <head> |
| <title>APK Caching</title> |
| <meta name="project_path" value="/_project.yaml" /> |
| <meta name="book_path" value="/_book.yaml" /> |
| </head> |
| <body> |
| <!-- |
| Copyright 2017 The Android Open Source Project |
| |
| 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. |
| --> |
| |
| |
| |
| <p> |
| This document describes design of an APK caching solution for rapid installation |
| of preloaded apps on a device that supports A/B partitions. |
| </p> |
| |
| <p> |
| OEMs can place preloads and popular apps in the APK cache stored in the mostly |
| empty B partition on new <a |
| href="/devices/tech/ota/ab_updates">A/B-partitioned</a> 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. |
| </p> |
| |
| <h2 id="use-cases">Use cases</h2> |
| |
| <ul> |
| <li>Store preloaded apps in B partition for faster setup |
| <li>Store popular apps in B partition for faster restoration |
| </ul> |
| |
| <h2 id="prerequisites">Prerequisites</h2> |
| |
| <p> |
| To use this feature, the device needs: |
| </p> |
| |
| <ul> |
| <li>Android 8.1 (O MR1) release installed |
| <li>A/B partition implemented</li> |
| </ul> |
| |
| <p> |
| 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 <a |
| href="/devices/tech/ota/">over-the-air (OTA) |
| updates</a> for downloading updated versions of the system image. |
| </p> |
| |
| <p> |
| 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. |
| </p> |
| |
| <h2 id="implementation">Implementation</h2> |
| |
| <h3 id="approach-1-content-on-system_other-partition">Approach 1. Content on |
| system_other partition</h3> |
| |
| <p> |
| <strong>Pro</strong>: Preloaded content is not lost after factory reset - it |
| will be copied from the B partition after a reboot. |
| </p> |
| |
| <p> |
| <strong>Con</strong>: Requires space on B partition. Boot after factory reset |
| requires additional time to copy preloaded content. |
| </p> |
| |
| <p> |
| In order for preloads to be copied during first boot, the system calls a script |
| in <code>/system/bin/preloads_copy.sh</code>. The script is called with a single |
| argument (path to the read-only mount point for <code>system_b</code> |
| partition): |
| </p> |
| |
| <p> |
| To implement this feature, make these device-specific changes. Here is an |
| example from Marlin: |
| </p> |
| |
| <ol> |
| <li>Add the script that does the copying to the <code>device-common.mk</code> |
| file (in this case, <code>device/google/marlin/device-common.mk</code>), like so: |
| |
| <pre class="devsite-click-to-copy"> |
| # 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 |
| </pre> |
| |
| Find example script source at: <a |
| href="https://android.googlesource.com/device/google/marlin/+/master/preloads_copy.sh">device/google/marlin/preloads_copy.sh</a> |
| </li> |
| |
| <li> Edit the <code>init.common.rc</code> file to have it create the |
| necessary<code> /data/preloads</code> directory and subdirectories: |
| |
| <pre class="devsite-click-to-copy"> |
| <code class="devsite-terminal">mkdir /data/preloads 0775 system system</code> |
| <code class="devsite-terminal">mkdir /data/preloads/media 0775 system system</code> |
| <code class="devsite-terminal">mkdir /data/preloads/demo 0775 system system</code> |
| </pre> |
| |
| Find example <code>init</code> file source at: <a |
| href="https://android.googlesource.com/device/google/marlin/+/master/init.common.rc">device/google/marlin/init.common.rc</a> |
| </li> |
| |
| <li>Define a new SELinux domain in the file <code>preloads_copy.te</code>: |
| |
| <pre class="devsite-click-to-copy"> |
| 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; |
| </pre> |
| |
| Find an example SELinux domain file at: <a |
| href="https://android.googlesource.com/device/google/marlin/+/master/sepolicy/preloads_copy.te">/device/google/marlin/+/master/sepolicy/preloads_copy.te</a> |
| </li> |
| |
| <li>Register the domain in a new <code><device>/sepolicy/file_contexts</code> |
| file: |
| |
| <pre class="devsite-click-to-copy"> |
| /system/bin/preloads_copy\.sh u:object_r:preloads_copy_exec:s0 |
| </pre> |
| |
| Find an example SELinux contexts file at: <a |
| href="https://android.googlesource.com/device/google/marlin/+/master/sepolicy/preloads_copy.te">device/google/marlin/sepolicy/preloads_copy.te</a> |
| </li> |
| |
| <li>At build time, the directory with preloaded content must be copied to the |
| <code>system_other</code> partition: |
| |
| <pre class="devsite-click-to-copy"> |
| # 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) |
| </pre> |
| |
| 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. |
| </li> |
| |
| <li>The APK cache is located in <code>/data/preloads/file_cache</code> and has |
| the following layout: |
| |
| <pre> |
| /data/preloads/file_cache/ |
| app.package.name.1/ |
| file1 |
| fileN |
| app.package.name.N/ |
| </pre> |
| |
| 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. |
| </li> |
| </ol> |
| |
| <h3 id="approach-2-content-on-user-data-image">Approach 2. Content on user data |
| image flashed at factory</h3> |
| |
| <p> |
| This alternative approach assumes that preloaded content is already included in |
| the <code>/data/preloads</code> directory on the <code>/data</code> partition. |
| </p> |
| |
| <p> |
| <strong>Pro</strong>: Works out of the box - no need to make device |
| customizations to copy files on first boot. Content is already on the |
| <code>/data</code> partition. |
| </p> |
| |
| <p> |
| <strong>Con</strong>: 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. |
| </p> |
| |
| <p> |
| A new @SystemApi method, <code>getPreloadsFileCache()</code>, was added to |
| <code>android.content.Context</code>. It returns an absolute path to an |
| application-specific directory in the preloaded cache. |
| </p> |
| |
| <p> |
| A new method, <code>IPackageManager.deletePreloadsFileCache</code>, 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. |
| </p> |
| |
| <h2 id="app-preparation">App preparation</h2> |
| |
| <p> |
| Only privileged applications can access the preloads cache directory. For that |
| access, apps must be installed in the<code> /system/priv-app</code> directory. |
| </p> |
| |
| <h2 id="validation">Validation</h2> |
| |
| <ul> |
| <li>After first boot, the device should have content in the |
| <code>/data/preloads/file_cache</code> directory. |
| <li>The content in the <code>file_cache/</code> directory must be deleted if the |
| device runs low on storage.</li> |
| </ul> |
| |
| <p>Use the example <a |
| href="https://android.googlesource.com/platform/development/+/master/samples/apkcachetest/">ApkCacheTest</a> |
| app for testing APK cache.</p> |
| |
| <ol> |
| <li>Build the app by running this command from the root directory: |
| <pre class="devsite-click-to-copy"> |
| <code class="devsite-terminal">make ApkCacheTest</code> |
| </pre> |
| </li> |
| |
| <li>Install the app as a privileged application. (Remember, only privileged apps can access the APK cache.) This requires a rooted device: |
| <pre class="devsite-click-to-copy"> |
| <code class="devsite-terminal">adb root && adb remount</code> |
| <code class="devsite-terminal">adb shell mkdir /system/priv-app/ApkCacheTest</code> |
| <code class="devsite-terminal">adb push $ANDROID_PRODUCT_OUT/data/app/ApkCacheTest/ApkCacheTest.apk /system/priv-app/ApkCacheTest/</code> |
| <code class="devsite-terminal">adb shell stop && adb shell start</code> |
| </pre> |
| </li> |
| |
| <li>Simulate the file cache directory and its content if neeeded (also requiring root privileges): |
| <pre class="devsite-click-to-copy"> |
| <code class="devsite-terminal">adb shell mkdir -p /data/preloads/file_cache/com.android.apkcachetest</code> |
| <code class="devsite-terminal">adb shell restorecon -r /data/preloads</code> |
| <code class="devsite-terminal">adb shell "echo "Test File" > /data/preloads/file_cache/com.android.apkcachetest/test.txt"</code> |
| </pre> |
| </li> |
| |
| <li>Test the app. After installing the app and creating test <code>file_cache</code> directory, open the ApkCacheTest app. It should show one file <code>test.txt</code> and its contents. See this screenshot to see how these results appear in the user interface. |
| |
| <p><img src="/devices/tech/perf/images/apk_cache_test_results.png"></p> |
| <figcaption><strong>Figure 1.</strong> ApkCacheTest results</figcaption> |
| </li> |
| </ol> |
| |
| </body> |
| </html> |