Watchdog 會使用核心在 `/proc/uid_io/stats` 位置公開的每個 UID 磁碟 I/O 統計資料,追蹤所有應用程式和服務所做的磁碟 I/O 寫入作業總量,藉此監控閃存記憶體用量。如果應用程式或服務超過磁碟 I/O 過度使用門檻,Watchdog 就會對應用程式或服務採取行動。磁碟 I/O 過度使用設定中已預先定義磁碟 I/O 超額用量門檻和應採取的行動。
過度使用門檻
- 磁碟 I/O 過度使用門檻會每天強制執行,也就是說,自當前世界標準時間日曆日開始起,應用程式/服務所做的所有寫入作業都會匯總,並與過度使用設定中定義的門檻進行檢查。
- 如果車輛在特定一天啟動多次,監視器模組會將磁碟 I/O 使用率統計資料儲存在快閃記憶體中,並自目前世界標準時間日曆日開始匯總這些資料。
過度使用動作
如果應用程式一再超出定義的磁碟 I/O 過度使用門檻,Watchdog 就會採取過度使用設定中定義的動作。
- 所有供應商應用程式和服務都會視為整體系統穩定性的關鍵,因此不會因磁碟 I/O 過度使用而終止。不過,過度使用設定可定義可安全終止的供應商應用程式和服務清單。
- 所有第三方應用程式都可以安全終止。
當應用程式或服務可安全終止時,Watchdog 會使用應用程式元件狀態
PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
停用應用程式或服務。
過度使用設定
過度使用設定包含磁碟 I/O 過度使用門檻和動作。系統和供應商映像檔會定義預設的過度使用設定,並隨建構作業一併提供。供應商可選擇在供應商映像檔中加入供應商設定。如果未提供供應商設定,系統會為供應商應用程式和服務使用系統設定。
Watchdog 會透過 CarWatchdogManager
公開系統 API,可讓供應商應用程式或服務隨時更新供應商設定。
過度使用設定定義
過度使用設定會依元件類型 (例如系統、供應商和第三方) 劃分。原始設備製造商 (OEM) 只能更新供應商元件設定。
供應商設定
供應商設定會為所有供應商應用程式和服務,以及所有地圖和媒體應用程式定義磁碟 I/O 過度使用門檻和動作。設定包含下列設定欄位。
- 供應商套件前置字串。所有在供應商分區中安裝的套件都視為供應商套件。除了這些套件外,供應商也可以將預先安裝的套件歸類為供應商套件,方法是將套件前置字串新增至供應商套件前置字串設定。這項設定不接受規則運算式。
- 可安全終止的套件。供應商可以在safe-to-terminate 套件設定中新增完整的套件名稱,指定哪些供應商套件可以安全終止。
- 應用程式類別對應。供應商可以將任何套件 (包括第三方套件) 對應至兩個支援的應用程式類別之一:地圖和媒體應用程式。這項對應作業是為了讓地圖和媒體應用程式擁有較高的磁碟 I/O 過度使用門檻,因為這類應用程式傾向於下載並寫入更多資料至磁碟,而非其他應用程式類型。
- 元件層級門檻:為所有供應商套件定義通用門檻 (也就是未納入套件專屬門檻或應用程式類別專屬門檻的套件)。定義磁碟 I/O 過度使用設定時,供應商必須定義非零的元件層級門檻。
- 套裝方案專屬門檻。供應商可以為特定供應商套件定義特殊門檻。對應項目應包含完整的套件名稱。此設定檔中定義的門檻優先於特定套件中其他設定檔中定義的門檻。
- 應用程式類別專屬門檻。供應商可以為特定應用程式類別指定特殊門檻。應用程式類別必須是支援的類別之一,也就是地圖和媒體應用程式。這個設定檔中定義的門檻會使用應用程式類別對應對應至特定套件。
- 整個系統的門檻。供應商不得指定此設定。
供應商套件前置字串、可安全終止的套件、元件層級門檻和套件專屬門檻設定,只能由供應商應用程式和服務的供應商設定更新。只有供應商設定可以更新應用程式類別專屬門檻設定,適用於所有地圖和媒體應用程式。
超出使用量門檻的內容包括在以下期間可寫入的位元組數:
- 應用程式或服務的前景模式與背景模式
- 系統車庫模式
這種分類機制讓面向使用者的前景應用程式和服務寫入的資料量比背景應用程式和服務多。在「車庫模式」中,應用程式和服務通常會下載更新,因此每個應用程式和服務都需要比在其他模式下執行的應用程式和服務更高的閾值。
系統和第三方設定
原始設備製造商 (OEM) 不應更新系統和第三方設定。
- 系統設定會定義系統應用程式和服務的 I/O 過度使用門檻和動作。
- 這項設定也可以更新應用程式類別對應。因此,這個設定欄位會在系統和供應商設定之間共用。
- 第三方設定會為所有第三方應用程式定義門檻。所有未預先安裝在系統中的應用程式都是第三方應用程式。
- 除了地圖和媒體應用程式 (其閾值由供應商設定定義) 以外,所有第三方應用程式都會收到相同的閾值 (例如,沒有第三方應用程式會收到特殊閾值)。
- 下列磁碟 I/O 超額用量門檻是第三方應用程式的預設門檻。這些門檻會隨系統映像檔一併提供。
- 在應用程式前景模式下寫入 3 GB。
- 在應用程式背景模式下寫入 2 GiB。
- 在系統車庫模式下寫入 4 GiB。
- 這些是基本門檻值。隨著我們對磁碟 I/O 用量的瞭解更加深入,這些門檻也會隨之更新。
過度使用設定 XML 格式
您可以將預設供應商設定 (選用) 放在建構映像檔中的 /vendor/etc/automotive/watchdog/resource_overuse_configuration.xml
位置。如果未指定這項設定,系統也會將系統定義的設定套用至供應商應用程式和服務。
在 XML 檔案中,每個設定欄位只能包含一個標記。您必須在 XML 檔案中定義 I/O 過度使用設定。所有門檻值應在 MiB 單位中指定。
以下提供 XML 設定範例:
<resourceOveruseConfiguration version="1.0"> <componentType> VENDOR </componentType> <!-- List of safe to kill vendor packages. --> <safeToKillPackages> <package> com.vendor.package.A </package> <package> com.vendor.package.B </package> </safeToKillPackages> <!-- List of vendor package prefixes. --> <vendorPackagePrefixes> <packagePrefix> com.vendor.package </packagePrefix> </vendorPackagePrefixes> <!-- List of unique package names to app category mappings. --> <packagesToAppCategoryTypes> <packageAppCategory type="MEDIA"> com.vendor.package.A </packageAppCategory> <packageAppCategory type="MAPS"> com.google.package.B </packageAppCategory> <packageAppCategory type="MEDIA"> com.third.party.package.C </packageAppCategory> </packagesToAppCategoryTypes> <ioOveruseConfiguration> <!-- Thresholds in MiB for all vendor packages that don't have package specific thresholds. --> <componentLevelThresholds> <state id="foreground_mode"> 1024 </state> <state id="background_mode"> 512 </state> <state id="garage_mode"> 3072 </state> </componentLevelThresholds> <packageSpecificThresholds> <!-- IDs must be unique --> <perStateThreshold id="com.vendor.package.C"> <state id="foreground_mode"> 400 </state> <state id="background_mode"> 100 </state> <state id="garage_mode"> 200 </state> </perStateThreshold> <perStateThreshold id="com.vendor.package.D"> <state id="foreground_mode"> 1024 </state> <state id="background_mode"> 500 </state> <state id="garage_mode"> 2048 </state> </perStateThreshold> </packageSpecificThresholds> <!-- Application category specific thresholds. --> <appCategorySpecificThresholds> <!-- One entry per supported application category --> <perStateThreshold id="MEDIA"> <state id="foreground_mode"> 600 </state> <state id="background_mode"> 700 </state> <state id="garage_mode"> 1024 </state> </perStateThreshold> <perStateThreshold id="MAPS"> <state id="foreground_mode"> 800 </state> <state id="background_mode"> 900 </state> <state id="garage_mode"> 2048 </state> </perStateThreshold> </appCategorySpecificThresholds> </ioOveruseConfiguration> </resourceOveruseConfiguration>
透過 CarWatchdogManager 系統 API 更新過度使用設定
上述 XML 設定只能在建構映像檔中提供。如果原始設備製造商選擇在版本發布後更新裝置端設定,可以使用下列 API 變更裝置端設定。
- 將
Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG
權限授予呼叫端。 - 請務必使用現有設定來更新及設置新設定。使用 API
CarWatchdogManager.getResourceOveruseConfigurations
取得現有設定。如果未使用現有設定,系統會覆寫所有設定 (包括系統和第三方設定),但不建議這麼做。 - 使用差異變更更新現有設定,並設定新的設定。請勿更新系統和第三方元件設定。
- 請使用 API
CarWatchdogManager.setResourceOveruseConfigurations
進行新設定。 - 如要取得及設定磁碟 I/O 過度使用設定,請使用
CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO
標記。
以下是更新資源過度使用設定的範例實作方式:
void updateResourceOveruseConfigurations() { CarWatchdogManager manager = (CarWatchdogManager) car.getCarManager(Car.CAR_WATCHDOG_SERVICE); List<ResourceOveruseConfiguration> resourceOveruseConfigurations = manager.getResourceOveruseConfigurations( CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO); List<ResourceOveruseConfiguration> newResourceOveruseConfigurations = new List<>(); ResourceOveruseConfiguration vendorConfiguration; for(ResourceOveruseConfiguration config : resourceOveruseConfigurations) { // Do not update the configurations of the system and third-party component types. if (config.getComponentType() != ResourceOveruseConfiguration.COMPONENT_TYPE_VENDOR) { newResourceOveruseConfigurations.add(config); continue; } vendorConfiguration = config; } if (vendorConfiguration == null) { ResourceOveruseConfiguration.Builder vendorConfigBuilder = new ResourceOveruseConfiguration.Builder(); initializeConfig(vendorConfigBuilder); newResourceOveruseConfigurations.add(vendorConfigBuilder.build()); } else { ResourceOveruseConfiguration newVendorConfig = updateConfig(vendorConfiguration); newResourceOveruseConfigurations.add(newVendorConfig); } int result = manager.setResourceOveruseConfigurations( newResourceOveruseConfigurations, if (result != CarWatchdogManager.RETURN_CODE_SUCCESS) { // Failed to set the resource overuse configurations. } } /** Sets the delta between the old configuration and the new configuration. */ ResourceOveruseConfiguration updateConfig( ResourceOveruseConfiguration oldConfiguration) { // Replace com.vendor.package.A with com.vendor.package.B in the safe-to-kill list. List<String> safeToKillPackages = oldConfiguration.getSafeToKillPackages(); safeToKillPackages.remove("com.vendor.package.A"); safeToKillPackages.add("com.vendor.package.B"); ResourceOveruseConfiguration.Builder configBuilder = new ResourceOveruseConfiguration.Builder( oldConfiguration.getComponentType(), safeToKillPackages, oldConfiguration.getVendorPackagePrefixes(), oldConfiguration.getPackagesToAppCategoryTypes()); configBuilder.addVendorPackagePrefixes("com.vendor."); configBuilder.addPackagesToAppCategoryTypes("com.vendor.package.B", ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MAPS); IoOveruseConfiguration oldIoConfiguration = oldConfiguration.getIoOveruseConfiguration(); IoOveruseConfiguration.Builder ioConfigBuilder = new IoOveruseConfiguration.Builder( oldIoConfiguration.getComponentLevelThresholds(), oldIoConfiguration.getPackageSpecificThresholds(), oldIoConfiguration.getAppCategorySpecificThresholds(), oldIoConfiguration.getSystemWideThresholds()); // Define the amount of bytes based on the flash memory specification, expected lifetime, // and estimated average amount of bytes written by a package during different modes. ioConfigBuilder.addPackageSpecificThresholds("com.vendor.package.B", new PerStateBytes(/* foregroundModeBytes= */ 2 * 1024 * 1024 * 1024, /* backgroundModeBytes= */ 500 * 1024 * 1024, /* garageModeBytes= */ 3 * 1024 * 1024 * 1024)); return configBuilder.setIoOveruseConfiguration(ioConfigBuilder.build()).build(); }
應用程式監控資源過度使用
供應商和第三方應用程式可以監聽 Watchdog 的應用程式專屬資源過度使用通知,或輪詢 CarWatchdogManager
以取得過去 30 天內的應用程式專屬資源過度使用統計資料。
接聽資源過度使用通知
應用程式可以實作資源過度使用監聽器,並使用 CarWatchdogManager
註冊監聽器,以便在磁碟 I/O 過度使用門檻超過 80% 或 100% 時,接收應用程式特定通知。應用程式可以使用這些通知來執行以下操作:
- 記錄磁碟 I/O 過度使用的統計資料,以利離線分析。應用程式開發人員可以使用這項記錄功能來偵錯磁碟 I/O 過度使用的問題。
- 減少磁碟 I/O 寫入作業,直到過度使用計數器重設為止。
Java 用戶端
- 透過繼承
CarWatchdogManager.ResourceOveruseListener
實作監聽器:class ResourceOveruseListenerImpl implements CarWatchdogManager.ResourceOveruseListener { @Override public void onOveruse( @NonNull ResourceOveruseStats resourceOveruseStats) { // 1. Log/Upload resource overuse metrics. // 2. Reduce writes until the counters reset. IoOveruseStats ioOveruseStats = resourceOveruseStats.getIoOveruseStats(); // Stats period - [ioOveruseStats.getStartTime(), ioOveruseStats.getStartTime() // + ioOveruseStats.getDurationInSeconds()] // Total I/O overuses - ioOveruseStats.getTotalOveruses() // Total bytes written - ioOveruseStats.getTotalBytesWritten() // Remaining write bytes for the current UTC calendar day - // ioOveruseStats.getRemainingWriteBytes() } } }
- 呼叫
CarWatchdogManager.addResourceOveruseListener
註冊事件監聽器例項private void addResourceOveruseListener() { CarWatchdogManager manager = (CarWatchdogManager) car.getCarManager(Car.CAR_WATCHDOG_SERVICE); // Choose a proper executor to handle resource overuse notifications. Executor executor = mContext.getMainExecutor(); manager.addResourceOveruseListener( executor, CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, mListenerImpl); }
- 在應用程式完成監聽以下事件後,取消註冊事件監聽器:
private void removeResourceOveruseListener() { CarWatchdogManager manager = (CarWatchdogManager) car.getCarManager(Car.CAR_WATCHDOG_SERVICE); mCarWatchdogManager.removeResourceOveruseListener( mListenerImpl); }
原生用戶端
- 在建構規則的
shared_libs
依附元件中加入carwatchdog_aidl_interface-ndk_platform
。Android.bp
cc_binary { name: "sample_native_client", srcs: [ "src/*.cpp" ], shared_libs: [ "carwatchdog_aidl_interface-ndk_platform", "libbinder_ndk", ], vendor: true, }
- 新增 SELinux 政策,允許供應商服務網域使用 Binder (
binder_user
巨集),並將供應商服務網域新增至carwatchdog
用戶端網域(carwatchdog_client_domain macro)
。請參閱下方的sample_client.te
和file_contexts
程式碼。sample_client.te
type sample_client, domain; type sample_client_exec, exec_type, file_type, vendor_file_type; carwatchdog_client_domain(sample_client) init_daemon_domain(sample_client) binder_use(sample_client)
file_contexts
/vendor/bin/sample_native_client u:object_r:sample_client_exec:s0
- 繼承
BnResourceOveruseListener
來實作資源過度使用監聽器。覆寫BnResourceOveruseListener::onOveruse
以處理資源過度使用的通知。ResourceOveruseListenerImpl.h
class ResourceOveruseListenerImpl : public BnResourceOveruseListener { public: ndk::ScopedAStatus onOveruse( ResourceOveruseStats resourceOveruseStats) override; private: void initialize(); void terminate(); std::shared_ptr<ICarWatchdog> mWatchdogServer; std::shared_ptr<IResourceOveruseListener> mListener; }
ResourceOveruseListenerImpl.cpp
ndk::ScopedAStatus ResourceOveruseListenerImpl::onOveruse( ResourceOveruseStats resourceOveruseStats) { // 1. Log/Upload resource overuse metrics. // 2. Reduce writes until the counters reset. if (stats.getTag() != ResourceOveruseStats::ioOveruseStats) { // Received resourceOveruseStats doesn't contain I/O overuse stats. } const IoOveruseStats& ioOveruseStats = stats.get(); // Stats period - [ioOveruseStats.startTime, // ioOveruseStats.startTime + ioOveruseStats.durationInSeconds] // Total I/O overuses - ioOveruseStats.totalOveruses // Total bytes written - ioOveruseStats.writtenBytes // Remaining write bytes for the current UTC calendar day - // ioOveruseStats.remainingWriteBytes return ndk::ScopedAStatus::ok(); }
- 啟動繫結器執行緒集區,並將資源過度使用監聽器註冊至監視器伺服器。監視器伺服器會在服務名稱
android.automotive.watchdog.ICarWatchdog/default
下註冊。main.cpp
int main(int argc, char** argv) { ABinderProcess_setThreadPoolMaxThreadCount(1); ABinderProcess_startThreadPool(); std::shared_ptr<ResourceOveruseListenerImpl> listener = ndk::SharedRefBase::make<ResourceOveruseListenerImpl>(); // The listener is added in initialize(). listener->initialize(); ... Run service ... // The listener is removed in terminate(). listener->terminate(); }
ResourceOveruseListenerImpl.cpp
void ResourceOveruseListener::initialize() { ndk::SpAIBinder binder(AServiceManager_getService( "android.automotive.watchdog.ICarWatchdog/default")); std::shared_ptr<ICarWatchdog> server = ICarWatchdog::fromBinder(binder); mWatchdogServer = server; std::shared_ptr<IResourceOveruseListener> listener = IResourceOveruseListener::fromBinder(this->asBinder()); mWatchdogServer->addResourceOveruseListener( std::vector<int>{ResourceType.IO}, listener); mListener = listener; } void ResourceOveruseListener::terminate() { mWatchdogServer->removeResourceOveruseListener(mListener); }
輪詢資源過度使用統計資料
應用程式可以向 CarWatchdogManager 輪詢應用程式特定的 I/O 過度使用統計資料 ATS,瞭解過去 30 天的情況。
Java 用戶端
使用 CarWatchdogManager.getResourceOveruseStats
取得資源過度使用的統計資料。傳遞 CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO
旗標,以取得磁碟 I/O 過度使用統計資料。
private void getResourceOveruseStats() { CarWatchdogManager manager = (CarWatchdogManager) car.getCarManager(Car.CAR_WATCHDOG_SERVICE); // Returns resource overuse stats with I/O overuse stats for the past // 7 days. Stats are available for up to the past 30 days. ResourceOveruseStats resourceOveruseStats = mCarWatchdogManager.getResourceOveruseStats( CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, CarWatchdogManager.STATS_PERIOD_PAST_7_DAYS); IoOveruseStats ioOveruseStats = resourceOveruseStats.getIoOveruseStats(); // Stats period - [ioOveruseStats.getStartTime(), ioOveruseStats.getStartTime() // + ioOveruseStats.getDurationInSeconds()] // Total I/O overuses - ioOveruseStats.getTotalOveruses() // Total bytes written - ioOveruseStats.getTotalBytesWritten() // Remaining write bytes for the UTC calendar day - // ioOveruseStats.getRemainingWriteBytes() }
原生用戶端
使用 CarWatchdogServer.getResourceOveruseStats
取得資源過度使用的統計資料。傳遞 ResourceType.IO
列舉,擷取磁碟 I/O 過度使用的統計資料。
void getResourceOveruseStats() { ndk::SpAIBinder binder(AServiceManager_getService( "android.automotive.watchdog.ICarWatchdog/default")); std::shared_ptr<ICarWatchdog> server = ICarWatchdog::fromBinder(binder); // Returns the stats only for the current UTC calendar day. const std::vector<ResourceOveruseStats> resourceOveruseStats; ndk::ScopedAStatus status = server.getResourceOveruseStats( std::vector<int>{ResourceType.IO}, &resourceOveruseStats); if (!status.isOk()) { // Failed to get the resource overuse stats. return; } for (const auto& stats : resourceOveruseStats) { if (stats.getTag() != ResourceOveruseStats::ioOveruseStats) { continue; } const IoOveruseStats& ioOveruseStats = stats.get(); // Stats period - [ioOveruseStats.startTime, // ioOveruseStats.startTime + ioOveruseStats.durationInSeconds] // Total I/O overuses - ioOveruseStats.totalOveruses // Total bytes written - ioOveruseStats.writtenBytes // Remaining write bytes for the current UTC calendar day - // ioOveruseStats.remainingWriteBytes } }
資源過度使用的使用者體驗
以下各節將說明資源過度使用的使用者體驗。
應用程式效能優先設定
應用程式「設定」頁面包含 Prioritize app performance
的設定 (請參閱下方圖片),可讓使用者將應用程式效能優先於系統和長期硬體效能。這項設定僅適用於可在資源用量過高時安全終止的應用程式。否則這項設定就會停用。如果為應用程式關閉這項設定 (預設設定),應用程式就會在資源過度使用時終止。否則,系統不會因資源過度使用而終止應用程式。
當使用者切換這項設定時,系統會顯示以下確認對話方塊,說明切換這項設定的影響:
90 天後,這項設定會自動重設為預設值。您可以使用 watchdogUserPackageSettingsResetDays
搭配 RRO 疊加應用程式,修改每日限制,上限為 180 天。詳情請參閱「在執行階段變更應用程式的資源值」。以下範例疊加標記可納入 AndroidManifest.xml
:
<overlay android:priority="<insert-value>" android:targetPackage="com.android.car.updatable" android:targetName="CarServiceCustomization" android:resourcesMap="@xml/overlays" />
在 res/values/config.xml
中:
<resources> <integer name="watchdogUserPackageSettingsResetDays">value</integer> </resources>
在 res/xml/overlays.xml
中:
<overlay> <item target="integer/watchdogUserPackageSettingsResetDays" value="@integer/watchdogUserPackageSettingsResetDays" /> </overlay>
影響效能的應用程式設定
「設定」應用程式包含「影響效能的應用程式」部分 (請見圖 1)。輕觸後,系統會顯示因快閃記憶體用量過高而受限,並對系統效能造成負面影響的應用程式清單。這項規定符合 CDD 3.5.1 規定 [C-1-1]。
圖 1. 影響效能的應用程式。
這裡列出因資源使用過多而終止的應用程式 (請見圖 2)。您可以為列出的應用程式設定優先順序。詳情請參閱「優先考量應用程式效能設定」。
圖 2. 因資源使用過多而終止的應用程式清單。
使用者通知
如果應用程式或服務在特定期間內重複過度使用磁碟 I/O (例如,將資料寫入磁碟的次數超過定義的門檻),且可安全地因資源過度使用而終止,則車輛進入允許駕駛人分心的狀態後,系統會通知使用者。
第一則使用者通知 (行車期間) 會以抬頭通知的形式發布,其他通知則會發布在通知中心。
舉例來說,如果應用程式屢次過度使用磁碟 I/O,使用者就會收到以下通知:
- 使用者點選「優先處理應用程式」按鈕後,應用程式的設定頁面就會啟動,使用者可以在此切換「優先處理應用程式效能」設定。
- 使用者點選「Disable app」按鈕後,應用程式會停用,直到使用者在應用程式設定頁面中啟動或啟用應用程式為止。
- 如果是可解除安裝的應用程式,系統會將「停用應用程式」按鈕替換為「解除安裝應用程式」按鈕。使用者點選「Uninstall app」按鈕時,應用程式的「Settings」頁面就會啟動,使用者可透過該頁面解除安裝應用程式。
建議導入啟動器
當應用程式因資源過度使用而停用時,CarService 會將應用程式的啟用狀態更新為 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
,因此應用程式會從預設啟動器應用程式中消失。OEM 必須更新內建啟動器實作項目,將這些應用程式顯示為異常,以便使用者在需要時使用。請參閱下列依版本發布版本提供的最佳化建議。
Android SC V2 版本
- 擷取要在啟動器中顯示的套件清單時,啟動器實作應使用
MATCH_DISABLED_UNTIL_USED_COMPONENTS
標記。 - 當使用者點選處於
PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
狀態的應用程式時,啟動器應用程式必須將啟用狀態設為以下狀態,才能啟用該應用程式: