システム ユーザーのパッケージを削除する

この記事では、SYSTEM ユーザーにとって不要なパッケージを特定して削除することで、パフォーマンスを改善する方法について説明します。

不要なパッケージを無効にする

Automotive では、SYSTEM ユーザーはヘッドレスです。つまり、SYSTEM ユーザーは、人間が使用したり直接アクセスすることを意図したものではありません。そのため、多くのアプリやサービスは SYSTEM ユーザーで実行する必要はなく、無効にすることでパフォーマンスを向上させることができます。そのため、SYSTEM ユーザー(ユーザー 0)にとって不要なアプリを削除するオプションが用意されています。

この記事では、次の 2 種類のユーザーについて説明します。

  • SYSTEM。常にユーザー 0
  • FULL。人間による使用が想定されているユーザー(SYSTEM 以外のユーザー)、ユーザー 10 以上

Android 11

Android 11 では、構成 config_userTypePackageWhitelistMode を変更します。フラグは組み合わせることができます。この場合、514(フラグ 14 の組み合わせ)に相当します。

  0  - disable whitelist (install all system packages; no logging)
  1  - enforce (only install system packages if they are whitelisted)
  2  - log (log non-whitelisted packages)
  4  - any package not mentioned in the whitelist file is implicitly whitelisted on all users
  8  - same as 4, but just for the SYSTEM user
  16 - ignore OTAs (don't install system packages during OTAs)
  Common scenarios:
  - to enable feature (fully enforced) for a complete allowlist: 1
  - to enable feature for an incomplete allowlist (so use implicit allowlist mode): 5
  - to enable feature but implicitly allowlist for SYSTEM user to ease local development: 9
  - to disable feature completely if it had never been enabled: 16
  - to henceforth disable feature and try to undo its previous effects: 0

デバイスの sysconfig ディレクトリに XML ファイルを必ずインストールします(これはデバイスのシステム イメージのビルドに使用する makefile(.mk)が含まれているのと同じディレクトリです)。XML ファイルに名前を付ける場合は、ビルド時にパッケージが定義されている場所を含めます。(例: preinstalled-packages-product-car-CAR_PRODUCT_NAME.xml)。

<!- this package will be installed for both FULL and SYSTEM user -->
    <install-in-user-type package="com.android.bluetooth"->
        <install-in user-type="FULL" /->
        <install-in user-type="SYSTEM" /->
    </install-in-user-type->

<!- this package will only be installed for both FULL user -->
    <install-in-user-type package="com.android.car.calendar"->
        <install-in user-type="FULL" >
    </install-in-user-type->

Android 9 および Android 10

Android 9 と Android 10 でこの機能を設定するには、以下を行います。

  1. frameworks/base/core/res/res/values/config.xml から config_systemUserPackagesBlacklistSupported 構成をオーバーレイし、true に設定します。この機能をオンにすると、デフォルトでは、SYSTEM ユーザーと FULL ユーザーの両方にすべてのパッケージがインストールされます。
  2. SYSTEM ユーザーに対して無効にする必要があるパッケージをリストする config.xml ファイルを作成します。次に例を示します。
    <config>
        <!-- This package will be uninstalled for the system user -->
        <system-user-blacklisted-app package="com.google.car.calendar" />
    </config>
    
  3. device.mk に行を追加して、ファイルをデバイスのターゲット フォルダ system/etc/sysconfig/ にコピーします。次に例を示します。
    PRODUCT_COPY_FILES += <full path to the config file>:system/etc/sysconfig/<new denylist config file>.xml
    

結果を確認する

結果を確認するには、次のコマンドを実行します。

$ adb shell dumpsys user | grep PACKAGE_SUBSTRING
$ adb shell pm list packages --user USER_ID PACKAGE_SUBSTRING
$ adb shell cmd user report-system-user-package-whitelist-problems

前提

パッケージを SYSTEM ユーザーにインストールする必要があるかどうかを判断するには、プロジェクトのソース(アプリの属性やアプリのコンポーネントを含む)のルートにあるパッケージの AndroidManifest.xml ファイルを調べます。これには、アプリの属性と、すべてのアクティビティ、サービス、ブロードキャスト レシーバ、コンテンツ プロバイダを含むアプリのコンポーネントが含まれます。詳細については、アプリ マニフェストの概要をご覧ください。

パッケージ無効化のワークフロー

図 1. パッケージ無効化のワークフロー

レベル 1、アプリケーション レベル

1. アプリ(またはアプリ コンポーネント)がシングルトンとして宣言されているかどうかを確認する

アプリケーションがシングルトンである場合、システムは SYSTEM ユーザーのみでアプリをインスタンス化します。アプリがマルチユーザーを認識するアプリを想定していた可能性が高いと考えられます。マルチユーザーを認識するアプリの詳細については、複数のユーザーを認識するアプリを構築するをご覧ください。

  1. Android マニフェストで android:singleUser="true" を確認します。
  2. true の場合は、許可リストに含めます。SYSTEM ユーザーに必要です。
  3. false の場合は、続行します。削除する前に、他の条件を確認します。

2. そのアプリに保護されたストレージ アクセスが必要かどうかを確認する

システム ブート サービスは、多くの場合、認証情報暗号化(CE)ストレージの代わりにデバイス暗号化(DE)ストレージを使用します。また、ダイレクト ブートに対応したシステムアプリも、デバイス暗号化ストレージを使用します。ダイレクト ブート対応アプリについて詳しくは、システムアプリでのダイレクト ブートのサポートをご覧ください。

  1. Android マニフェストで、多数のシステム ブート サービスで必要となる android:defaultToDeviceProtectedStorage="true" を確認します。
  2. true の場合は、許可リストに含めます。
  3. false の場合は、続行します。

レベル 2、アプリケーション コンポーネント

アクティビティ

アクティビティの詳細については、アクティビティの概要をご覧ください。

a. そのアプリに含まれているのがアクティビティのみであるかどうかを確認する

アクティビティはユーザー インターフェース指向です。Automotive では、SYSTEM ユーザーはヘッドレスであるため、人間は SYSTEM ユーザーを操作する必要がありません。そのため、アプリにアクティビティのみが含まれている場合、そのアプリは SYSTEM ユーザーとの関連しない可能性が高いと考えられます。

優先度と特別な権限を確認します。

  1. アクティビティのみである場合、SYSTEM ユーザーで必要になる可能性があります。
  2. アクティビティのみでない場合、SYSTEM ユーザーの許可リストに含めません。

たとえば、互換性テストスイート(CTS)(com.android.cts.priv.ctsshim)にはアクティビティのみが含まれ、アクティビティはインテント フィルタをテストするように定義されています。ただし、権限が高いため、テスト目的で SYSTEM ユーザー用にインストールする必要があります。

サービス

サービスの詳細については、サービスの概要をご覧ください。

b. サービスが非公開であると宣言され、他のアプリからアクセスできないかどうかを確認する

サービスを非公開として宣言されている場合、他のパッケージはそのサービスを使用できません。android:exported="false" を確認します。サービスが非公開として宣言されている、あるいは他のアプリからアクセスできないと宣言されている場合、そのサービスは他のアプリにバインドできません。したがって、以下のステップ C とステップ D とは関連性がありません。そのため、このコンポーネントでは、SYSTEM ユーザーにそのサービスが必要かどうかに関して、これ以上ヒントが提供されません。

  1. 宣言されている場合は、次のコンポーネントに進みます。
  2. 宣言されていない場合は、引き続きこのコンポーネントを確認します。

c. SYSTEM にインストールされているアプリがこのサービスにバインドされているかどうかを確認する

レベル 1 で許可リストに含まれているパッケージを確認して、バインドされるサービスを特定します。このサービスのインテント フィルタと、他のパッケージ内の startService からトレースします。

このサービスが SYSTEM ユーザーにインストールされているアプリにバインドされている場合(たとえば、com.android.car.companiondevicesupport が許可リストに含まれ、SYSTEM ユーザーで実行されている)、そのサービスを許可リストに含めます。

  1. バインドされている場合は、許可リストに含めます。
  2. バインドされていない場合は、引き続きこのコンポーネントを確認します。

d. サービスが他のアプリからバインドされ、フォアグラウンドで実行されるように宣言されているかどうかを確認する

startForeground を確認します。これは、ユーザーがフォアグラウンドでアプリを操作することを意味します。ほとんどの場合、このサービスは SYSTEM ユーザーに不要で、許可リストに含める必要はありません。

  1. 宣言されている場合、許可リストに含めません。
  2. 宣言されていない場合は、引き続き次のコンポーネントを確認します。

e. サービスがシステム プロセスで実行されるように定義されているかどうかを確認する

AndroidManifest で、android:process="system" を確認します。
サービスがシステム プロセスで実行されるように意図的に定義されている場合、そのサービスはシステム サービスと同じプロセスで明示的に実行されることを意味するため、SYSTEM ユーザーで実行されるように許可リストに含める必要があります。Android のメモリ割り当て設計の一部として、システム サービスは強制終了される最終のプロセスです。これは、そうした属性で定義されたサービスが重要であることを示します。Android のメモリ割り当て設計の詳細については、ローメモリ キラーをご覧ください。

  1. 定義されている場合、許可リストに含めません。
  2. 定義されていない場合は、他のコンポーネントを確認します。

たとえば、パッケージ com.android.networkstack.inprocess には、android:process="system" タグが付与された RegularMaintenanceJobService が含まれるため、許可リストに含める必要があります。

コンテンツ プロバイダ

コンテンツ プロバイダの詳細については、コンテンツ プロバイダをご覧ください。

f. SYSTEM ユーザーにインストールされたアプリがこのプロバイダに依存しているかどうかを確認する

レベル 1 で許可リストに含まれたパッケージを確認して、依存するプロバイダを確認します。SYSTEM ユーザーで実行されているアプリ(例: com.android.car.companiondevicesupport が SYSTEM ユーザーで実行されるように許可リストに含まれている)が、このコンテンツ プロバイダに依存している場合は、このコンテンツ プロバイダも許可リストに含めてください。

  1. 依存している場合は、許可リストに含めます。
  2. 依存していない場合は、許可リストに含めません。

たとえば、com.android.car.EXAMPLE にシングルトン プロバイダ(SystemActionsContentProviderManagedProvisioningActionsContentProvider)が含まれている場合、SYSTEM ユーザーの許可リストに含める必要があります。次に、com.android.car.EXAMPLEWebViewFactoryProviderandroid.webkit に依存している場合は、android.webkit を読み込むため、com.android.webview は SYSTEM ユーザーの許可リストに含める必要があります。

サンプル パッケージのチュートリアル

次の例は、パッケージの AndroidManifest.xml を評価する方法を示しています。

<?xml version="1.0" encoding="utf-8"?>
<!-- 1. Search in the entire manifest for singleUser attribute.
No. Move to step 2 -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.android.providers.calendar"
        android:sharedUserId="android.uid.calendar">
    We can ignore the entire permission section
    <uses-permission android:name="android.permission.READ_CALENDAR" />
    ...
    <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
<!-- 2. Look for defaultToDeviceProtectedStorage in application's attribute.
No. Continue evaluating app components. -->
    <application android:label="@string/calendar_storage"
                 android:allowBackup="false"
                 android:icon="@drawable/app_icon"
                 android:usesCleartextTraffic="false">
<!-- a. Contain only activities?
No. Continue to evaluate components other than activities. -->
        <provider android:name="CalendarProvider2" android:authorities="com.android.calendar"
                <!-- b. Is this component exported?
                Yes. Continue evaluating this component.
                f. App on u0 might depend on this? Search for CalendarProvider2 in dumpsys, shows ContentProviderRecord{b710923 u0 com.android.providers.calendar/.CalendarProvider2}
                Yes. Whitelist for system user. -->
                android:label="@string/provider_label"
                android:multiprocess="false"
                android:exported="true"
                android:readPermission="android.permission.READ_CALENDAR"
                android:writePermission="android.permission.WRITE_CALENDAR" />

<activity android:name="CalendarContentProviderTests" android:label="Calendar Content Provider" android:exported="false"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.UNIT_TEST" /> </intent-filter> </activity> <!-- Not service/content provider. Ignore. --> <receiver android:name="CalendarProviderBroadcastReceiver" android:exported="false"> <intent-filter> <action android:name="com.android.providers.calendar.intent.CalendarProvider2"/> <category android:name="com.android.providers.calendar"/> </intent-filter> <intent-filter> <action android:name="android.intent.action.EVENT_REMINDER"/> <data android:scheme="content" /> </intent-filter> </receiver> <service android:name="CalendarProviderIntentService"/> </application> </manifest>