管理多位使用者

本開發人員指南將說明裝置政策控制器 (DPC) 如何 在專用裝置上管理多位 Android 使用者。

總覽

DPC 可以協助多人共用一部專用裝置。您的裝置政策控制器 (DPC) 在全代管裝置上執行的應用程式可以建立及管理兩種使用者:

  • 次要使用者是 Android 使用者,且已儲存獨立的應用程式和資料 。您將透過管理員元件管理使用者。這些使用者 適用於於轉換開始時取貨的情況,例如 外送司機或資安工作者。
  • 暫時使用者是次要使用者,系統會在使用者進行操作時將其刪除 停止、切換或裝置重新啟動。這類使用者 可在工作階段結束後刪除資料,例如公開存取 網際網路資訊站
,瞭解如何調查及移除這項存取權。

透過現有的 DPC 管理專用裝置和次要裝置 使用者。DPC 中的管理員元件會將自己設為新次要次要執行個體的管理員 建立使用者

主要使用者和兩位次要使用者。
圖 1. 管理員透過 相同的裝置政策控制器 (DPC)

次要使用者的管理員必須與 全代管裝置。如要簡化開發作業,建議您與管理員聯絡 讓裝置與次要使用者之間保持適當平衡

如要在專用裝置上管理多位使用者,通常需要 Android 9.0、 然而,這份開發人員指南中使用的部分方法已列於 也就是較舊版本的 Android 系統

次要使用者

次要使用者可以連線至 Wi-Fi 並設定新網路。但 也無法編輯或刪除網路,包括已建立的網路。

建立使用者

DPC 可以在背景建立其他使用者,而且使用者可以切換 移到前景次要和次要執行個體的程序大致相同 以及臨時使用者請在完全採用 Google Cloud 控制台的 受管理的裝置和次要使用者:

  1. 呼叫 DevicePolicyManager.createAndManageUser()。 如要建立臨時使用者,請加入 MAKE_USER_EPHEMERAL
  2. 致電 DevicePolicyManager.startUserInBackground() 到 並在背景啟動使用者使用者已開始運轉,但您也希望 完成設定,然後再將使用者導向前景 操作者。
  3. 在次要使用者的管理員中,呼叫 DevicePolicyManager.setAffiliationIds() 到 讓新使用者與主要使用者建立聯盟關係。詳情請見 DPC 協調
  4. 返回全代管裝置的管理員,發起通話 DevicePolicyManager.switchUser():將使用者切換為 前景

以下範例說明如何在 DPC 中新增步驟 1:

Kotlin

val dpm = getContext().getSystemService(Context.DEVICE_POLICY_SERVICE)
        as DevicePolicyManager

// If possible, reuse an existing affiliation ID across the
// primary user and (later) the ephemeral user.
val identifiers = dpm.getAffiliationIds(adminName)
if (identifiers.isEmpty()) {
    identifiers.add(UUID.randomUUID().toString())
    dpm.setAffiliationIds(adminName, identifiers)
}

// Pass an affiliation ID to the ephemeral user in the admin extras.
val adminExtras = PersistableBundle()
adminExtras.putString(AFFILIATION_ID_KEY, identifiers.first())
// Include any other config for the new user here ...

// Create the ephemeral user, using this component as the admin.
try {
    val ephemeralUser = dpm.createAndManageUser(
            adminName,
            "tmp_user",
            adminName,
            adminExtras,
            DevicePolicyManager.MAKE_USER_EPHEMERAL or
                    DevicePolicyManager.SKIP_SETUP_WIZARD)

} catch (e: UserManager.UserOperationException) {
    if (e.userOperationResult ==
            UserManager.USER_OPERATION_ERROR_MAX_USERS) {
        // Find a way to free up users...
    }
}

Java

DevicePolicyManager dpm = (DevicePolicyManager)
    getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);

// If possible, reuse an existing affiliation ID across the
// primary user and (later) the ephemeral user.
Set<String> identifiers = dpm.getAffiliationIds(adminName);
if (identifiers.isEmpty()) {
  identifiers.add(UUID.randomUUID().toString());
  dpm.setAffiliationIds(adminName, identifiers);
}

// Pass an affiliation ID to the ephemeral user in the admin extras.
PersistableBundle adminExtras = new PersistableBundle();
adminExtras.putString(AFFILIATION_ID_KEY, identifiers.iterator().next());
// Include any other config for the new user here ...

// Create the ephemeral user, using this component as the admin.
try {
  UserHandle ephemeralUser = dpm.createAndManageUser(
      adminName,
      "tmp_user",
      adminName,
      adminExtras,
      DevicePolicyManager.MAKE_USER_EPHEMERAL |
          DevicePolicyManager.SKIP_SETUP_WIZARD);

} catch (UserManager.UserOperationException e) {
  if (e.getUserOperationResult() ==
      UserManager.USER_OPERATION_ERROR_MAX_USERS) {
    // Find a way to free up users...
  }
}

建立或建立新使用者時,您可以查看失敗的原因 擷取 UserOperationException 例外狀況並呼叫 getUserOperationResult()。超出使用者數量 導致失敗的常見原因包括:

,瞭解如何調查及移除這項存取權。

建立使用者可能需要一段時間。如果你經常建立使用者 引導使用者立即在背景執行,提升使用者體驗。 您可能會需要在可開始使用的使用者之優勢和最高 單一裝置允許的使用者人數。

身分認同

建立新使用者後,請使用永久序列參照使用者 號碼。不要保留 UserHandle,因為系統會在您回收這些物件時進行回收 建立及刪除使用者透過撥打電話取得序號 UserManager.getSerialNumberForUser():

Kotlin

// After calling createAndManageUser() use a device-unique serial number
// (that isn’t recycled) to identify the new user.
secondaryUser?.let {
    val userManager = getContext().getSystemService(UserManager::class.java)
    val ephemeralUserId = userManager!!.getSerialNumberForUser(it)
    // Save the serial number to storage  ...
}

Java

// After calling createAndManageUser() use a device-unique serial number
// (that isn’t recycled) to identify the new user.
if (secondaryUser != null) {
  UserManager userManager = getContext().getSystemService(UserManager.class);
  long ephemeralUserId = userManager.getSerialNumberForUser(secondaryUser);
  // Save the serial number to storage  ...
}

使用者設定

您可以根據使用者需求,自訂 使用者。您可以在呼叫 createAndManageUser() 時加入下列旗標:

SKIP_SETUP_WIZARD
略過執行新的使用者設定精靈,用於檢查及安裝更新, 提示使用者新增 Google 帳戶與 Google 服務, 就會啟動螢幕鎖定功能這項作業可能需要一些時間才能完成,而且不一定適用於所有使用者 使用者的資料,例如公開網際網路資訊站。
LEAVE_ALL_SYSTEM_APPS_ENABLED
讓新使用者啟用所有系統應用程式。如未設定這個標記 新使用者僅包含手機所需的最少應用程式 通常是檔案瀏覽器、電話撥號程式、聯絡人和簡訊。

追蹤使用者生命週期

DPC (如果是全代管裝置的管理員) 可能會想 也能得知次要使用者的變更時間如要在變更後執行後續工作,請覆寫 您 DPC DeviceAdminReceiver 子類別中的下列回呼方法:

onUserStarted()
系統啟動使用者後呼叫。這位使用者可能仍在設定,或 會在背景執行您可以從 startedUser 取得使用者 引數
onUserSwitched()
系統切換至其他使用者後呼叫。您也能獲得新使用者 目前是透過 switchedUser 引數在前景執行。
onUserStopped()
系統因使用者登出而停止使用者後呼叫,改為 (如果是臨時使用者),或 DPC 停止該名使用者。您可以 透過 stoppedUser 引數傳回使用者。
onUserAdded()
在新增使用者時呼叫。次要使用者 DPC 收到回呼時完整設定。您可以從 newUser 引數。
onUserRemoved()
系統刪除使用者後呼叫。由於使用者已遭刪除 您無法存取 removedUser 引數代表的使用者。

得知系統何時將使用者導向前景或將使用者導向至前景 應用程式便可註冊接收端 ACTION_USER_FOREGROUNDACTION_USER_BACKGROUND 廣播。

探索使用者

如要取得所有次要使用者,全代管裝置管理員可以呼叫 DevicePolicyManager.getSecondaryUsers()。成果 包含管理員建立的任何次要或臨時使用者。結果也 納入使用裝置的任何次要使用者 (或訪客使用者) 搜尋結果未包含工作資料夾,因為這不是工作資料夾 次要使用者以下範例說明如何使用這個方法:

Kotlin

// The device is stored for the night. Stop all running secondary users.
dpm.getSecondaryUsers(adminName).forEach {
    dpm.stopUser(adminName, it)
}

Java

// The device is stored for the night. Stop all running secondary users.
for (UserHandle user : dpm.getSecondaryUsers(adminName)) {
  dpm.stopUser(adminName, user);
}

您也可以使用以下方法找出次要使用者的狀態:

DevicePolicyManager.isEphemeralUser()
從次要使用者的管理員呼叫這個方法,瞭解帳戶是否 。
DevicePolicyManager.isAffiliatedUser()
從次要使用者的管理員呼叫這個方法,確認該使用者 與主要使用者相關聯如要進一步瞭解關聯,請參閱 DPC 協調方式

使用者管理

如要完全管理使用者生命週期,您可以針對應用程式的生命週期 精細控管裝置變更使用者的時間和方式。舉例來說, 可以在裝置閒置一段時間後刪除使用者,或者,您也可以 在消費者的輪班完成前,將未送出的訂單傳送至伺服器。

登出

Android 9.0 在螢幕鎖定畫面上新增登出按鈕,讓使用 裝置才能結束工作階段。只要輕觸按鈕,系統就會停止 次要使用者,如果是暫時使用者,則刪除該使用者,然後主要使用者返回 移到前景主要使用者位於以下位置時,Android 會隱藏按鈕 否則主要使用者無法登出

根據預設,Android 不會顯示結束工作階段按鈕,而是您的管理員 ( 全代管裝置) 呼叫 DevicePolicyManager.setLogoutEnabled()。如果需要 確認按鈕目前狀態、呼叫 DevicePolicyManager.isLogoutEnabled()

次要使用者的管理員能夠透過程式,將使用者登出並返回 提供給主要使用者首先,請確認次要使用者和主要使用者 ,然後呼叫 DevicePolicyManager.logoutUser()。如果 系統會將登出的使用者視為臨時使用者,則會停止,然後刪除 內容。

切換使用者

如要切換到其他次要使用者,全代管裝置的管理員可以 呼叫 DevicePolicyManager.switchUser()。為了方便起見 可以傳遞 null,切換至主要使用者。

停止使用者

如要阻止次要使用者,擁有全代管裝置的 DPC 可以呼叫 DevicePolicyManager.stopUser()。如果停止的使用者為 臨時使用者,系統會停止該使用者,然後刪除該使用者。

建議您盡可能停止使用者,避免將數量維持在裝置的容量以下 執行使用者人數上限。

刪除使用者

如要永久刪除次要使用者,DPC 可呼叫下列其中一項 DevicePolicyManager 方法:

  • 全代管裝置的管理員可以呼叫 removeUser()
  • 次要使用者的管理員可以呼叫 wipeData()

系統會在臨時使用者登出、停止或切換後刪除使用者

停用預設 UI

如果您的 DPC 提供管理使用者的 UI,您可以停用 Android 內建的 多使用者介面方法是呼叫 DevicePolicyManager.setLogoutEnabled(),然後新增 DISALLOW_USER_SWITCH 限制,如 範例:

Kotlin

// Explicitly disallow logging out using Android UI (disabled by default).
dpm.setLogoutEnabled(adminName, false)

// Disallow switching users in Android's UI. This DPC can still
// call switchUser() to manage users.
dpm.addUserRestriction(adminName, UserManager.DISALLOW_USER_SWITCH)

Java

// Explicitly disallow logging out using Android UI (disabled by default).
dpm.setLogoutEnabled(adminName, false);

// Disallow switching users in Android's UI. This DPC can still
// call switchUser() to manage users.
dpm.addUserRestriction(adminName, UserManager.DISALLOW_USER_SWITCH);

裝置使用者無法透過 Android 內建 UI 新增次要使用者 因為全代管裝置的管理員會自動新增 DISALLOW_ADD_USER 使用者限制。

工作階段訊息

當使用裝置的使用者切換到新使用者時,Android 會顯示一個面板, 醒目顯示切換按鈕Android 會顯示下列訊息:

  • 裝置切換至次要執行個體時顯示的啟動使用者工作階段訊息 從主要使用者中移除
  • 裝置返回主要使用者時顯示的使用者工作階段訊息 來自次要使用者

系統不會在兩個次要使用者之間切換時顯示訊息。

由於郵件不一定適合所有情況,您可以變更 顯示這些訊息的文字舉例來說,如果您的解決方案使用臨時使用者 可以在訊息中說明這一點,例如:「停止瀏覽器」 工作階段和正在刪除個人資料...

系統會持續顯示訊息幾秒鐘,因此每一則訊息 就是簡短且明確的詞組如要自訂訊息,管理員可呼叫 DevicePolicyManager 方法 setStartUserSessionMessage()setEndUserSessionMessage(),如 範例:

Kotlin

// Short, easy-to-read messages shown at the start and end of a session.
// In your app, store these strings in a localizable resource.
internal val START_USER_SESSION_MESSAGE = "Starting guest session"
internal val END_USER_SESSION_MESSAGE = "Stopping & clearing data"

// ...
dpm.setStartUserSessionMessage(adminName, START_USER_SESSION_MESSAGE)
dpm.setEndUserSessionMessage(adminName, END_USER_SESSION_MESSAGE)

Java

// Short, easy-to-read messages shown at the start and end of a session.
// In your app, store these strings in a localizable resource.
private static final String START_USER_SESSION_MESSAGE = "Starting guest session";
private static final String END_USER_SESSION_MESSAGE = "Stopping & clearing data";

// ...
dpm.setStartUserSessionMessage(adminName, START_USER_SESSION_MESSAGE);
dpm.setEndUserSessionMessage(adminName, END_USER_SESSION_MESSAGE);

請傳送 null,刪除自訂訊息並返回 Android 預設值 訊息。如要查看目前的訊息文字,請撥打 getStartUserSessionMessage()getEndUserSessionMessage()

您的裝置政策控制器 (DPC) 應設定本地化訊息 以使用者目前所在地區的語言為準您也必須更新訊息, 使用者的語言代碼變更:

Kotlin

override fun onReceive(context: Context?, intent: Intent?) {
    // Added the <action android:name="android.intent.action.LOCALE_CHANGED" />
    // intent filter for our DeviceAdminReceiver subclass in the app manifest file.
    if (intent?.action === ACTION_LOCALE_CHANGED) {

        // Android's resources return a string suitable for the new locale.
        getManager(context).setStartUserSessionMessage(
                getWho(context),
                context?.getString(R.string.start_user_session_message))

        getManager(context).setEndUserSessionMessage(
                getWho(context),
                context?.getString(R.string.end_user_session_message))
    }
    super.onReceive(context, intent)
}

Java

public void onReceive(Context context, Intent intent) {
  // Added the <action android:name="android.intent.action.LOCALE_CHANGED" />
  // intent filter for our DeviceAdminReceiver subclass in the app manifest file.
  if (intent.getAction().equals(ACTION_LOCALE_CHANGED)) {

    // Android's resources return a string suitable for the new locale.
    getManager(context).setStartUserSessionMessage(
        getWho(context),
        context.getString(R.string.start_user_session_message));

    getManager(context).setEndUserSessionMessage(
        getWho(context),
        context.getString(R.string.end_user_session_message));
  }
  super.onReceive(context, intent);
}

裝置政策控制器 (DPC) 協調

管理次要使用者通常需要兩個 DPC 執行個體,而每個 DPC 執行個體 另一部是完全受管理的裝置,但另一部則為次要使用者所有。建立期間 新使用者,全代管裝置的管理員會設定另一個執行個體 以新使用者的身分

關聯使用者

這份開發人員指南中的部分 API 僅適用於次要使用者 有關係。由於 Android 停用了部分功能 (例如網路記錄) 您應盡快將使用者結盟。範例如下: 設定

設定

先設定新的次要使用者 (透過擁有次要使用者的裝置政策控制器) 讓使用者輕鬆使用您可以前往 DeviceAdminReceiver.onEnabled() 回呼。如果您先前 設定對 createAndManageUser() 呼叫的任何管理員額外項目,就能取得 傳回 intent 引數的值。以下範例是 DPC 關聯 叫用回呼中的新的次要使用者:

Kotlin

override fun onEnabled(context: Context?, intent: Intent?) {
    super.onEnabled(context, intent)

    // Get the affiliation ID (our DPC previously put in the extras) and
    // set the ID for this new secondary user.
    intent?.getStringExtra(AFFILIATION_ID_KEY)?.let {
        val dpm = getManager(context)
        dpm.setAffiliationIds(getWho(context), setOf(it))
    }
    // Continue setup of the new secondary user ...
}

Java

public void onEnabled(Context context, Intent intent) {
  // Get the affiliation ID (our DPC previously put in the extras) and
  // set the ID for this new secondary user.
  String affiliationId = intent.getStringExtra(AFFILIATION_ID_KEY);
  if (affiliationId != null) {
    DevicePolicyManager dpm = getManager(context);
    dpm.setAffiliationIds(getWho(context),
        new HashSet<String>(Arrays.asList(affiliationId)));
  }
  // Continue setup of the new secondary user ...
}

裝置政策控制器 (DPC) 之間的遠端程序呼叫 (RPC)

雖然這兩個 DPC 執行個體是在不同的使用者下執行,但 DPC 擁有裝置的使用者和次要使用者能夠互相通訊。 呼叫其他裝置政策控制器 (DPC) 的服務會跨越使用者界線,因此無法在 DPC 上 照常呼叫 bindService() Android:如何繫結至 另一個使用者, 打給 DevicePolicyManager.bindDeviceAdminServiceAsUser()

主要使用者和兩個關聯次要使用者會呼叫 RPC。
圖 2. 關聯主要和次要使用者的管理員 呼叫服務方法

您的 DPC 只能繫結至執行於 DevicePolicyManager.getBindDeviceAdminTargetUsers()。 以下範例顯示次要使用者繫結的管理員 完全受管理的裝置:

Kotlin

// From a secondary user, the list contains just the primary user.
dpm.getBindDeviceAdminTargetUsers(adminName).forEach {

    // Set up the callbacks for the service connection.
    val intent = Intent(mContext, FullyManagedDeviceService::class.java)
    val serviceconnection = object : ServiceConnection {
        override fun onServiceConnected(componentName: ComponentName,
                                        iBinder: IBinder) {
            // Call methods on service ...
        }
        override fun onServiceDisconnected(componentName: ComponentName) {
            // Clean up or reconnect if needed ...
        }
    }

    // Bind to the service as the primary user [it].
    val bindSuccessful = dpm.bindDeviceAdminServiceAsUser(adminName,
            intent,
            serviceconnection,
            Context.BIND_AUTO_CREATE,
            it)
}

Java

// From a secondary user, the list contains just the primary user.
List<UserHandle> targetUsers = dpm.getBindDeviceAdminTargetUsers(adminName);
if (targetUsers.isEmpty()) {
  // If the users aren't affiliated, the list doesn't contain any users.
  return;
}

// Set up the callbacks for the service connection.
Intent intent = new Intent(mContext, FullyManagedDeviceService.class);
ServiceConnection serviceconnection = new ServiceConnection() {
  @Override
  public void onServiceConnected(
      ComponentName componentName, IBinder iBinder) {
    // Call methods on service ...
  }

  @Override
  public void onServiceDisconnected(ComponentName componentName) {
    // Clean up or reconnect if needed ...
  }
};

// Bind to the service as the primary user.
UserHandle primaryUser = targetUsers.get(0);
boolean bindSuccessful = dpm.bindDeviceAdminServiceAsUser(
    adminName,
    intent,
    serviceconnection,
    Context.BIND_AUTO_CREATE,
    primaryUser);

其他資源

如要進一步瞭解專用裝置,請參閱下列文件: