調整受管理的設定

如果您是為企業市場開發應用程式,可能需要滿足機構政策設定的特定規定。受管理設定 (舊稱「應用程式限制」) 可讓機構的 IT 管理員從遠端指定應用程式設定。如果機構核准的應用程式已部署至工作資料夾,這項功能就特別有用。

舉例來說,機構可能會要求核准的應用程式允許 IT 管理員執行以下操作:

  • 允許或封鎖網頁瀏覽器的網址
  • 設定應用程式是否允許透過行動網路同步內容,或是僅透過 Wi-Fi 同步
  • 設定應用程式的電子郵件設定

本指南說明如何在應用程式中實作受管理的設定。如要查看含有受管理設定的應用程式範例,請參閱「ManagedConfigurations」。如果您是企業行動管理服務 (EMM) 開發人員,請參閱 Android Management API 指南

注意:基於歷史原因,這些設定檔稱為限制,並使用使用此字詞的檔案和類別 (例如 RestrictionsManager) 實作。不過,這些限制實際上可實作多種設定選項,而非僅限於應用程式功能。

遠端設定總覽

應用程式會定義可由 IT 管理員從遠端設定的受管理設定選項。這些是任意設定,可由受管理的設定提供者變更。如果應用程式是在工作資料夾中執行,IT 管理員可以變更應用程式的受管理設定。

受管理設定供應器是另一個在同一部裝置上執行的應用程式。這個應用程式通常由 IT 管理員控制。IT 管理員會將設定變更通知給受管理的設定提供者應用程式,該應用程式隨後會變更應用程式上的設定。

如何提供外部管理設定:

  • 在應用程式資訊清單中宣告受管理的設定。這樣一來,IT 管理員就能透過 Google Play API 讀取應用程式的設定。
  • 每當應用程式重新啟動時,請使用 RestrictionsManager 物件檢查目前受管理的設定,並變更應用程式的 UI 和行為,以符合這些設定。
  • 請聆聽 ACTION_APPLICATION_RESTRICTIONS_CHANGED 意圖。收到這項廣播時,請檢查 RestrictionsManager,瞭解目前的受管理設定,並視需要調整應用程式的行為。

定義受管理的設定

您的應用程式可以支援您想要定義的任何受管理設定。您可以在受管理的設定檔案中宣告應用程式的受管理設定,並在資訊清單中宣告設定檔案。建立設定檔可讓其他應用程式檢查應用程式提供的受管理設定。EMM 合作夥伴可使用 Google Play API 讀取應用程式的設定。

如要定義應用程式的遠端設定選項,請在資訊清單的 <application> 元素中放入下列元素:

<meta-data android:name="android.content.APP_RESTRICTIONS"
    android:resource="@xml/app_restrictions" />

在應用程式的 res/xml 目錄中建立名為 app_restrictions.xml 的檔案。如需該檔案的結構說明,請參閱 RestrictionsManager 的參考資料。檔案包含一個頂層 <restrictions> 元素,其中包含應用程式具有的每個設定選項的一個 <restriction> 子項。

注意:請勿建立受管理設定檔的本地化版本。您的應用程式只能有單一受管理的設定檔,因此所有語言代碼的應用程式設定都會一致。

在企業環境中,EMM 通常會使用受管理的設定結構定義,為 IT 管理員產生遠端控制台,方便管理員從遠端設定應用程式。

受管理的設定提供者可以查詢應用程式,找出應用程式的可用設定詳細資料,包括說明文字。設定供應器和 IT 管理員隨時可以變更應用程式的受管理設定,即使應用程式未執行也一樣。

舉例來說,假設您的應用程式可透過遠端設定,允許或禁止透過行動網路連線下載資料。應用程式可能會有如下所示的 <restriction> 元素:

<?xml version="1.0" encoding="utf-8"?>
<restrictions xmlns:android="http://schemas.android.com/apk/res/android">

  <restriction
    android:key="downloadOnCellular"
    android:title="@string/download_on_cell_title"
    android:restrictionType="bool"
    android:description="@string/download_on_cell_description"
    android:defaultValue="true" />

</restrictions>

您可以使用各個設定的 android:key 屬性,從受管理的設定套件讀取其值。因此,每個設定都必須有專屬的鍵字串,且字串「無法」本地化。必須使用字串常值指定。

注意:在正式版應用程式中,android:titleandroid:description 應從本地化資源檔案繪製,如「使用資源進行本地化」一文所述。

應用程式會使用 bundle_array 中的套件定義限制。舉例來說,如果應用程式提供多種 VPN 連線選項,則可在 bundle 中定義每個 VPN 伺服器設定,並將多個套件在套件陣列中分組:

<?xml version="1.0" encoding="utf-8"?>
<restrictions xmlns:android="http://schemas.android.com/apk/res/android" >

  <restriction
    android:key="vpn_configuration_list"
    android:restrictionType="bundle_array">
    <restriction
      android:key="vpn_configuration"
      android:restrictionType="bundle">
      <restriction
        android:key="vpn_server"
        android:restrictionType="string"/>
      <restriction
        android:key="vpn_username"
        android:restrictionType="string"/>
      <restriction
        android:key="vpn_password"
        android:restrictionType="string"/>
    </restriction>
  </restriction>

</restrictions>

表 1 列出了 android:restrictionType 元素支援的類型,並在 RestrictionsManagerRestrictionEntry 的參考資料中記錄。

表 1. 限制項目類型和用法。

類型 android:restrictionType 常見用途
TYPE_BOOLEAN "bool" 布林值,true 或 false。
TYPE_STRING "string" 字串值,例如名稱。
TYPE_INTEGER "integer" 整數值介於 MIN_VALUEMAX_VALUE 之間。
TYPE_CHOICE "choice" android:entryValues 選取的字串值,通常會以單選清單的形式顯示。
TYPE_MULTI_SELECT "multi-select" 字串陣列,其中包含從 android:entryValues 選取的值。用於呈現可選取多個項目的多重選擇清單,例如選擇要加入許可清單的特定書籍。
TYPE_NULL "hidden" 隱藏的限制類型。請將這類型用於需要轉移,但不應在 UI 中向使用者顯示的資訊。儲存單一字串值。
TYPE_BUNDLE_ARRAY "bundle_array" 用於儲存限制 bundles 的陣列。適用於 Android 6.0 (API 級別 23)。

注意: android:entryValues 是機器可讀的值,無法本地化。使用 android:entries 呈現可本地化的可讀值。每個項目都必須在 android:entryValues 中對應至索引。

檢查受管理的設定

其他應用程式變更設定時,您的應用程式不會自動收到通知。相反地,您需要在應用程式啟動或繼續執行時檢查受管理的設定,並監聽系統意圖,找出應用程式執行期間是否有設定變更。

如要查看目前的設定,應用程式會使用 RestrictionsManager 物件。應用程式應在下列時間檢查目前的受管理設定:

如要取得 RestrictionsManager 物件,請使用 getActivity() 取得目前的活動,然後呼叫該活動的 Activity.getSystemService() 方法:

Kotlin

var myRestrictionsMgr =
        activity?.getSystemService(Context.RESTRICTIONS_SERVICE) as RestrictionsManager

Java

RestrictionsManager myRestrictionsMgr =
    (RestrictionsManager) getActivity()
        .getSystemService(Context.RESTRICTIONS_SERVICE);

取得 RestrictionsManager 後,您可以呼叫其 getApplicationRestrictions() 方法,取得目前的設定:

Kotlin

var appRestrictions: Bundle = myRestrictionsMgr.applicationRestrictions

Java

Bundle appRestrictions = myRestrictionsMgr.getApplicationRestrictions();

注意:為了方便起見,您也可以透過呼叫 UserManager.getApplicationRestrictions(),使用 UserManager 擷取目前的設定。這個方法的行為與 RestrictionsManager.getApplicationRestrictions() 完全相同。

getApplicationRestrictions() 方法需要從資料儲存空間讀取資料,因此應謹慎使用。請勿在每次需要瞭解目前設定時都呼叫此方法。相反地,您應在應用程式啟動或重新啟動時呼叫此方法一次,並將擷取的受管理設定套件快取。接著,請聆聽 ACTION_APPLICATION_RESTRICTIONS_CHANGED 意圖,找出應用程式處於活動狀態時是否有設定變更,如「聆聽受管理的設定變更」一文所述。

讀取及套用受管理的設定

getApplicationRestrictions() 方法會傳回 Bundle,其中包含每個已設定的設定的鍵/值組合。這些值的類型皆為 BooleanintStringString[]。有了受管理的設定 Bundle 後,您可以使用標準 Bundle 方法檢查目前的設定,例如 getBoolean()getString() 的資料類型。

注意:受管理設定 Bundle 會針對受管理設定供應器明確設定的每個設定,包含一個項目。不過,您不可以假設只要在受管理的設定 XML 檔案中定義預設值,設定就會出現在套件中。

應用程式會根據目前的受管理設定採取適當行動。舉例來說,如果應用程式有設定可指定是否可透過行動網路連線下載資料,而您發現設定已設為 false,則必須停用資料下載功能,除非裝置已連上 Wi-Fi,如以下程式碼範例所示:

Kotlin

val appCanUseCellular: Boolean =
        if (appRestrictions.containsKey("downloadOnCellular")) {
            appRestrictions.getBoolean("downloadOnCellular")
        } else {
            // cellularDefault is a boolean using the restriction's default value
            cellularDefault
        }

if (!appCanUseCellular) {
    // ...turn off app's cellular-download functionality
    // ...show appropriate notices to user
}

Java

boolean appCanUseCellular;

if (appRestrictions.containsKey("downloadOnCellular")) {
    appCanUseCellular = appRestrictions.getBoolean("downloadOnCellular");
} else {
    // cellularDefault is a boolean using the restriction's default value
    appCanUseCellular = cellularDefault;
}

if (!appCanUseCellular) {
    // ...turn off app's cellular-download functionality
    // ...show appropriate notices to user
}

如要套用多個巢狀限制,請將 bundle_array 限制條件項目讀取為 Parcelable 物件的集合,並轉換為 Bundle。在這個範例中,系統會剖析每個 VPN 的設定資料,並用於建立伺服器連線選項清單:

Kotlin

// VpnConfig is a sample class used store config data, not defined
val vpnConfigs = mutableListOf<VpnConfig>()

val parcelables: Array<out Parcelable>? =
        appRestrictions.getParcelableArray("vpn_configuration_list")

if (parcelables?.isNotEmpty() == true) {
    // iterate parcelables and cast as bundle
    parcelables.map { it as Bundle }.forEach { vpnConfigBundle ->
        // parse bundle data and store in VpnConfig array
        vpnConfigs.add(VpnConfig()
                .setServer(vpnConfigBundle.getString("vpn_server"))
                .setUsername(vpnConfigBundle.getString("vpn_username"))
                .setPassword(vpnConfigBundle.getString("vpn_password")))
    }
}

if (vpnConfigs.isNotEmpty()) {
    // ...choose a VPN configuration or prompt user to select from list
}

Java

// VpnConfig is a sample class used store config data, not defined
List<VpnConfig> vpnConfigs = new ArrayList<>();

Parcelable[] parcelables =
    appRestrictions.getParcelableArray("vpn_configuration_list");

if (parcelables != null && parcelables.length > 0) {
    // iterate parcelables and cast as bundle
    for (int i = 0; i < parcelables.length; i++) {
        Bundle vpnConfigBundle = (Bundle) parcelables[i];
        // parse bundle data and store in VpnConfig array
        vpnConfigs.add(new VpnConfig()
            .setServer(vpnConfigBundle.getString("vpn_server"))
            .setUsername(vpnConfigBundle.getString("vpn_username"))
            .setPassword(vpnConfigBundle.getString("vpn_password")));
    }
}

if (!vpnConfigs.isEmpty()) {
    // ...choose a VPN configuration or prompt user to select from list
}

監聽受管理設定的變更

每當應用程式的受管理設定有所變更時,系統就會觸發 ACTION_APPLICATION_RESTRICTIONS_CHANGED 意圖。應用程式必須聆聽此意圖,才能在設定變更時變更應用程式行為。

注意:ACTION_APPLICATION_RESTRICTIONS_CHANGED 意圖只會傳送給動態註冊的事件監聽器,而非傳送給在應用程式資訊清單中宣告的事件監聽器。

下列程式碼示範如何為此意圖動態註冊廣播接收器:

Kotlin

val restrictionsFilter = IntentFilter(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED)

val restrictionsReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {

        // Get the current configuration bundle
        val appRestrictions = myRestrictionsMgr.applicationRestrictions

        // Check current configuration settings, change your app's UI and
        // functionality as necessary.
    }
}

registerReceiver(restrictionsReceiver, restrictionsFilter)

Java

IntentFilter restrictionsFilter =
    new IntentFilter(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);

BroadcastReceiver restrictionsReceiver = new BroadcastReceiver() {
  @Override public void onReceive(Context context, Intent intent) {

    // Get the current configuration bundle
    Bundle appRestrictions = myRestrictionsMgr.getApplicationRestrictions();

    // Check current configuration settings, change your app's UI and
    // functionality as necessary.
  }
};

registerReceiver(restrictionsReceiver, restrictionsFilter);

注意:一般來說,應用程式在暫停時,不需要收到設定變更的通知。相反地,您應在應用程式暫停時取消註冊廣播接收器。應用程式重新啟動時,您應先檢查目前的受管理設定 (如「檢查受管理設定」一文所述),然後註冊廣播接收器,確保在應用程式處於活動狀態時,您會收到設定變更通知。

將受管理設定意見回饋傳送給 EMM

在應用程式套用受管理設定變更後,建議您通知 EMM 變更狀態。Android 支援「鍵控應用程式狀態」功能,您可以利用這項功能,在應用程式嘗試套用受管理設定變更時傳送意見回饋。這項意見回饋可用於確認應用程式是否已成功設定受管理的設定,或在應用程式無法套用指定變更時,提供錯誤訊息。

EMM 供應商可以擷取這項意見回饋,並在控制台中顯示,供 IT 管理員查看。如要進一步瞭解這個主題,請參閱「傳送應用程式意見回饋給 EMM」,其中包含如何在應用程式中新增意見回饋支援功能的詳細指南。

其他程式碼範例

ManagedConfigurations 範例進一步示範如何使用本頁涵蓋的 API。

在個人資料夾中將應用程式加入許可清單/封鎖清單

第三方應用程式商店可能會想使用受管理的設定,以可靠的方式將應用程式封鎖清單或許可清單套用至個人設定檔和私人空間消費者功能,這是使用者可用來保留私密應用程式的額外個人空間。如果您開發的是企業用途的應用程式商店,且想使用這項功能,請提交 這份表單表達您的意願,並在表單中選取「有意加入第三方應用程式商店的許可清單」做為「回覆原因」