調整受管理的設定

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

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

  • 允許或封鎖網路瀏覽器的網址
  • 設定應用程式是否可透過行動數據或 Wi-Fi 同步處理內容
  • 調整應用程式的電子郵件設定

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

注意:基於歷史因素,這些設定稱為「限制」,導入的檔案和類別會使用此字詞,例如 RestrictionsManager。不過,這些限制實際上可以實作多種設定選項,而不是只限制應用程式功能。

遠端設定總覽

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

受管理設定供應商是同一部裝置上執行的其他應用程式。這個應用程式通常是由 IT 管理員控管。IT 管理員會將設定變更傳送至代管設定提供者應用程式。該應用程式便會據此變更應用程式設定。

如要提供外部代管的設定:

  • 在應用程式資訊清單中宣告受管理的設定。這樣 IT 管理員就能透過 Google Play API 讀取應用程式設定。
  • 每當應用程式重新啟用時,請使用 RestrictionsManager 物件檢查目前的受管理設定,並變更應用程式的使用者介面和行為,使其符合這些設定。
  • 監聽 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。