blob: 4523afab4a630784be3ffaac2e247e0dd373d8e2 [file] [log] [blame]
<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>