進階主題

這些章節僅供參考,您不需要閱讀 由上至下。

使用架構 API:

這些 API 會包裝在 SDK 中,以取得更一致的 API 介面 (例如 避免使用 UserHandle 物件),但您目前可以直接呼叫這些物件。

導入作業非常簡單,如果您可以互動,請繼續操作。如果不是 但您可以要求之後顯示使用者提示/橫幅/工具提示等如果使用者 同意前往「設定」,建立要求意圖並使用 Context#startActivity 即可將使用者帶往該處。你可以使用 即可偵測這項功能的變更時間,或只在使用者嘗試存取 返回。

如要進行測試,您必須在工作資料夾中開啟 TestDPC,前往 頁面,然後選取將套件名稱加入連結應用程式的許可清單。這個 模仿管理員「加入許可清單」

詞彙解釋

本節定義與開發跨設定檔相關的重要詞彙。

跨設定檔設定

跨設定檔設定將相關的跨設定檔供應商歸為一組 類別並提供跨設定檔功能的一般設定。 通常每個程式碼集都會有一個 @CrossProfileConfiguration 註解 但在某些複雜的應用程式中,可能還會有多種。

設定檔連接器

連接器用來管理設定檔之間的連線。通常是每個跨設定檔 類型會指向特定的「連接器」。單一跨設定檔類型 設定必須使用相同的連接器。

跨設定檔供應商類別

跨設定檔供應商類別將相關的跨設定檔類型歸為一組。

Mediator

中介者位於高階和低階程式碼之間,將呼叫分配至 正確的設定檔並合併結果您只需要讀取這張投影片 或個人檔案感知此為架構概念,而非內建功能 SDK。

跨設定檔類型

跨設定檔類型是包含註解方法的類別或介面。 @CrossProfile。這類程式碼不需具備設定檔感知功能, 最好只處理本機資料

設定檔類型

設定檔類型
目前正在執行的有效設定檔。
其他(如果有的話) 系統執行該設定檔的設定檔。
個人使用者 0,裝置中的設定檔無法 已關閉。
公司通常是使用者 10 (但可能會更高),可切換開啟和關閉,用於容納工作應用程式和資料。
主要視需要由應用程式定義。要變更設定檔的設定檔 會顯示兩個設定檔的合併檢視畫面。
次要如果已定義主要值,次要則是次要設定檔 則不是主要資源
供應商主要付款資料的供應商包括 次要付款資料的供應商只是次要付款資料本身。

設定檔 ID

代表個人資料類型 (個人或工作) 的類別。這些會 能由多個設定檔執行的方法所傳回,並可用來執行 加上該設定檔的程式碼這些項目可序列化為 int,方便您使用 如果 30 天內讀取資料不到一次 建議使用 Coldline Storage

本指南將概述在 Android 應用程式中建構高效且可維護的跨設定檔功能的建議結構。

CrossProfileConnector 轉換為單例模式

在應用程式的生命週期中,您只能使用一個執行個體 否則就會建立平行連線都可以 使用 Dagger 等依附元件插入架構 經典單例模式 圖案、 在新類別中或現有類別中

在發出呼叫時,將產生的設定檔例項插入或傳入您的類別,而非透過方法建立

這樣您就能傳入自動產生的 FakeProfile 例項, 稍後將建立單元測試

思考中介模式

這個常見的模式是將其中一個現有 API 設為現有 API (例如 getEvents()) 可對所有呼叫端建立設定檔感知功能在這種情況下,您現有的 API 成為「中介者」方法或類別,內含要產生的新呼叫 跨設定檔程式碼

如此一來,您就不必強制所有呼叫端呼叫跨設定檔呼叫 作為 API 的一部分

請考慮是否要將介面方法加註為 @CrossProfile,以免在供應器中公開實作類別

這個做法可與依附元件插入架構完美搭配運作。

如果您從跨設定檔呼叫接收任何資料,請考慮是否要新增欄位來參照來源設定檔

這可能是不錯的做法,因為您可能想在 UI 層知道這點 (例如在工作內容中加入徽章圖示)。如有任何資料 和 ID 等 ID 就不再是唯一的 ID

跨設定檔

本節將概述如何建立自己的「跨設定檔」互動。

主要設定檔

本文件範例中的大多數呼叫都包含明確指示 和/或個人資料夾

實際上,如果應用程式只將一個個人資料合併成一個個人資料,您可能很可能會 希望這項決策取決於要執行的設定檔 為避免您的 導致程式碼集與 if-else 設定檔條件相符。

建立連接器執行個體時,您可以指定設定檔類型 您的「主要」(例如「WORK」)。此方法可啟用其他選項,例如 包括:

profileCalendarDatabase.primary().getEvents();

profileCalendarDatabase.secondary().getEvents();

// Runs on all profiles if running on the primary, or just
// on the current profile if running on the secondary.
profileCalendarDatabase.suppliers().getEvents();

跨設定檔類型

包含註解 @CrossProfile 的類別和介面為 稱為「跨設定檔類型」

導入跨設定檔類型時,應不受設定檔影響, 設定 Pod他們可以呼叫其他方法和 一般來說,這些功能的運作方式應該就像在單一設定檔上運作。他們將 只能在各自的設定檔中存取狀態。

跨設定檔類型範例:

public class Calculator {
  @CrossProfile
  public int add(int a, int b) {
    return a + b;
  }
}

類別註解

如要提供最強大的 API,請為各個交叉連結 設定檔類型,如下所示:

@CrossProfile(connector=MyProfileConnector.class)
public class Calculator {
  @CrossProfile
  public int add(int a, int b) {
    return a + b;
  }
}

此為選用部分,但代表產生的 API 會針對類型更明確 並更嚴格執行編譯時間檢查作業

介面

透過在介面上以 @CrossProfile 為方法加註,即可表明此情況 這個方法的某些實作項目應該都能存取 跨設定檔

您可以將「跨設定檔」介面的任何實作傳回 設定檔供應商並聲明相關規定 這項實作應可在跨設定檔存取。您不一定要 為實作類別加上註解

跨設定檔供應商

每個跨設定檔類型都必須提供方法 已加註 @CrossProfileProvider。系統會在每次啟動 跨設定檔呼叫,因此建議您維護單例模式 每種類型

建構函式

供應器必須有一個不使用引數或 單一 Context 引數。

提供者方法

提供者方法不得包含引數或單一 Context 引數。

插入依附元件

如果您使用 Dagger 等依附元件插入架構 建議您讓該架構 然後建立在 提供者類別接著,@CrossProfileProvider 方法可以傳回這些 插入的執行個體

設定檔連接器

每個跨設定檔設定都必須有一個設定檔連接器, 負責管理其他設定檔的連結。

預設設定檔連接器

如果程式碼集中只有一個跨設定檔設定,則您可以 不需建立專屬的設定檔連接器,在 com.google.android.enterprise.connectedapps.CrossProfileConnector。這是 如未指定,則會採用預設值。

建構跨設定檔連接器時,您可以指定 建立工具:

  • 已排定執行程式服務

    如要控管 SDK 建立的執行緒,請使用 #setScheduledExecutorService(),

  • 繫結機制

    如有特定設定檔繫結需求,請使用 #setBinder。這個 只有裝置政策控制器才能使用。

自訂設定檔連接器

您需要自訂設定檔連接器,才能進行某些設定 (使用 CustomProfileConnector),需要一個 整合多個連接器 (舉例來說,如果您有多個程序, 每個程序建議一個連接器)。

建立 ProfileConnector 時應如下所示:

@GeneratedProfileConnector
public interface MyProfileConnector extends ProfileConnector {
  public static MyProfileConnector create(Context context) {
    // Configuration can be specified on the builder
    return GeneratedMyProfileConnector.builder(context).build();
  }
}
  • serviceClassName

    如要變更產生的服務名稱 (應在 您的 AndroidManifest.xml),請使用 serviceClassName=

  • primaryProfile

    如要指定主要設定檔,請使用 primaryProfile

  • availabilityRestrictions

    如要變更限制 SDK 會放置在連線和設定檔可用性,請使用 availabilityRestrictions

裝置政策控制器

如果您的應用程式是 Device Policy Controller,則必須指定 DpcProfileBinder 參照您的 DeviceAdminReceiver

如果您要實作自己的設定檔連接器:

@GeneratedProfileConnector
public interface DpcProfileConnector extends ProfileConnector {
  public static DpcProfileConnector get(Context context) {
    return GeneratedDpcProfileConnector.builder(context).setBinder(new
DpcProfileBinder(new ComponentName("com.google.testdpc",
"AdminReceiver"))).build();
  }
}

或使用預設的 CrossProfileConnector

CrossProfileConnector connector =
CrossProfileConnector.builder(context).setBinder(new DpcProfileBinder(new
ComponentName("com.google.testdpc", "AdminReceiver"))).build();

跨設定檔設定

@CrossProfileConfiguration 註解可用來連結所有交叉點 設定檔類型,以便正確調度方法呼叫。目的地: 我們會透過 @CrossProfileConfiguration 為類別加上註解,而該類別指向 每個供應商,例如:

@CrossProfileConfiguration(providers = {TestProvider.class})
public abstract class TestApplication {
}

這將驗證所有「跨設定檔」 類型 使用同一個設定檔連接器,或是未指定任何連接器。

  • serviceSuperclass

    根據預設,產生的服務會使用 android.app.Service 做為 父類別如果您需要其他類別 (本身必須是子類別) android.app.Service) 設為父類別,然後指定 serviceSuperclass=

  • serviceClass

    如果指定,系統就不會產生任何服務。這必須與您使用的設定檔連接器中的 serviceClassName 相符。您的自訂 服務應使用產生的 _Dispatcher 類別分派呼叫, 例如:

public final class TestProfileConnector_Service extends Service {
  private Stub binder = new Stub() {
    private final TestProfileConnector_Service_Dispatcher dispatcher = new
TestProfileConnector_Service_Dispatcher();

    @Override
    public void prepareCall(long callId, int blockId, int numBytes, byte[] params)
{
      dispatcher.prepareCall(callId, blockId, numBytes, params);
    }

    @Override
    public byte[] call(long callId, int blockId, long crossProfileTypeIdentifier,
int methodIdentifier, byte[] params,
    ICrossProfileCallback callback) {
      return dispatcher.call(callId, blockId, crossProfileTypeIdentifier,
methodIdentifier, params, callback);
    }

    @Override
    public byte[] fetchResponse(long callId, int blockId) {
      return dispatcher.fetchResponse(callId, blockId);
  };

  @Override
  public Binder onBind(Intent intent) {
    return binder;
  }
}

如果您需要在 跨設定檔呼叫

  • 連接器

    如果您使用預設的 CrossProfileConnector 以外的連接器, 然後,您必須使用 connector= 進行指定

顯示設定

與跨設定檔互動的應用程式每個部分都必須能查看 您的設定檔連接器。

已加上 @CrossProfileConfiguration 註解的類別必須能查看 應用程式所使用的供應商

同步呼叫

已連結的應用程式 SDK 支援同步 (封鎖) 呼叫, 因為這些限制是難以避免的然而,使用 AI 開發工具 (例如長時間封鎖來電), 建議您盡量避免同步呼叫。使用 非同步呼叫請參閱非同步呼叫 呼叫

連接夾

如果您使用的是同步呼叫,則務必確認 在執行跨設定檔呼叫之前註冊連線容器,否則 系統會擲回例外狀況詳情請參閱連線保留器。

如要新增連線持有者,請呼叫 ProfileConnector#addConnectionHolder(Object) 與任何物件搭配使用 (有可能是會使 跨設定檔呼叫)。這樣就能記錄這個物件使用 並嘗試建立連線。必須在任何同步呼叫前呼叫此方法。這是一個非封鎖式呼叫,因此 確認連線中斷後,連線可能無法進行 (或可能無法執行) 呼叫,此時則適用一般的錯誤處理行為。

呼叫時若缺少適當的跨設定檔權限 ProfileConnector#addConnectionHolder(Object)或是沒有設定檔 連線,則不會擲回錯誤,但連線的回呼永遠 物件。如稍後獲得權限,或者其他設定檔發生 那麼系統會建立連線,並呼叫回呼

或者,ProfileConnector#connect(Object) 是封鎖方法,會將物件新增為連線持有者,並建立連線或擲回 UnavailableProfileException無法從該方法呼叫 UI 執行緒

呼叫 ProfileConnector#connect(Object) 和類似的程式碼 ProfileConnector#connect 會傳回自動關閉物件 並在關閉後移除連線容器。因此,您可以使用以下方法:

try (ProfileConnectionHolder p = connector.connect()) {
  // Use the connection
}

完成同步呼叫後,您應該呼叫 ProfileConnector#removeConnectionHolder(Object)。所有連線擁有者 連線將會關閉。

連線能力

連線事件監聽器可用於在連線狀態時接收通知 以及 connector.utils().isConnected 可用於判斷 連線。例如:

// Only use this if using synchronous calls instead of Futures.
crossProfileConnector.connect(this);
crossProfileConnector.registerConnectionListener(() -> {
  if (crossProfileConnector.utils().isConnected()) {
    // Make cross-profile calls.
  }
});

非同步呼叫

在剖析資料中公開的每個方法,都必須指定為封鎖 (同步) 或非封鎖 (非同步)。任何傳回非同步資料類型 (例如 ListenableFuture) 或接受回呼參數的方法,都會標示為非阻斷。所有其他方法都會標示為封鎖。

建議使用非同步呼叫。如果一定要使用同步呼叫,請參閱 同步 通話

回呼

非阻塞呼叫最基本的類型是 void 方法,可接受 的介面,當中包含可使用 結果。若要讓這些介面與 SDK 搭配運作,介面必須 已加註 @CrossProfileCallback。例如:

@CrossProfileCallback
public interface InstallationCompleteListener {
  void installationComplete(int state);
}

這個介面隨後可做為 @CrossProfile 註解中的參數使用 方法並照常呼叫。例如:

@CrossProfile
public void install(String filename, InstallationCompleteListener callback) {
  // Do something on a separate thread and then:
  callback.installationComplete(1);
}

// In the mediator
profileInstaller.work().install(filename, (status) -> {
  // Deal with callback
}, (exception) -> {
  // Deal with possibility of profile unavailability
});

如果這個介麵包含單一方法,可採用 0 或 1 也可以同時用於對多個設定檔的呼叫。

任意數量的值都可以透過回呼傳遞,但連線會 且只有在第一個值保留期間。如需相關資訊,請參閱「連線擁有者」一節 保持連線開啟以接收更多值。

與回呼的同步方法

搭配 SDK 使用回呼的其中一項異常功能,就是 嚴格來說,編寫使用回呼的同步方法:

public void install(InstallationCompleteListener callback) {
  callback.installationComplete(1);
}

在這個例子中,儘管有回呼,此方法實際上是同步的。這個 正確執行程式碼:

System.out.println("This prints first");
installer.install(() -> {
        System.out.println("This prints second");
});
System.out.println("This prints third");

不過,透過 SDK 呼叫時,運作方式並不相同。另有 不保證會在「此列印結果」之前呼叫安裝方法 第三任何由 SDK 標示為非同步方法的使用,都必須 不就呼叫方法的時間做出任何假設。

簡易回呼

「簡易回呼」則是限制更為嚴格的回呼形式 進行跨設定檔通話時的額外功能簡單介面必須 僅包含單一方法,可接收零或一個參數。

您可以藉由指定 @CrossProfileCallback 註解中的 simple=true

簡易回呼可與 .both().suppliers()、 等等。

連接夾

當您進行非同步呼叫 (使用回呼或 Future) 時 撥打時,系統會新增連線持有人,且如果連線 例外狀況或值傳遞成功

如果預期使用回呼傳遞多個結果,則應執行以下動作: 手動新增回呼做為連線持有者:

MyCallback b = //...
connector.addConnectionHolder(b);

  profileMyClass.other().registerListener(b);

  // Now the connection will be held open indefinitely, once finished:
  connector.removeConnectionHolder(b);

也可與 try-with-resources 區塊搭配使用:

MyCallback b = //...
try (ProfileConnectionHolder p = connector.addConnectionHolder(b)) {
  profileMyClass.other().registerListener(b);

  // Other things running while we expect results
}

如果我們透過電話或日後進行通話,該連線仍會保持開啟 直到結果傳遞為止如果我們判定結果無法傳送,則會 應移除回呼或 Future,做為連線容器:

connector.removeConnectionHolder(myCallback);
connector.removeConnectionHolder(future);

如需更多資訊,請參閱連線保留器。

Futures

SDK 也原生支援 Future。唯一原生支援的 未來類型為 ListenableFuture,但自訂 Future 類型 。如要使用 Future ,您只須宣告支援的 Future 類型做為回傳 然後按一下跨設定檔方法的類型,再照常使用

這也有相同的「異常功能」視為回呼,但非同步方法 傳回 Future (例如使用 immediateFuture) 的行為會有所不同 比較以目前的設定檔和另一個設定檔執行時使用 方法只要由 SDK 標示為非同步,就不需假設 方法。

執行緒

請勿封鎖跨設定檔未來或主要上回呼的結果 執行緒。如果您這麼做,在某些情況下您的程式碼就會封鎖 無限期。這是因為與其他個人資料的連結 是建立在主執行緒上,如果遭到封鎖,則一律不發生 跨設定檔結果

可用性

供應情形事件監聽器可用於通知供應情形 而 connector.utils().isAvailable 可用於判斷 可以使用該設定檔。例如:

crossProfileConnector.registerAvailabilityListener(() -> {
  if (crossProfileConnector.utils().isAvailable()) {
    // Show cross-profile content
  } else {
    // Hide cross-profile content
  }
});

連接夾

連線容器是任意物件,會記錄為具有 建立跨設定檔連結並保持運作。

根據預設,進行非同步呼叫時,系統會新增連線容器 ,並在發生任何結果或錯誤時移除。

連線擁有者也能手動新增和移除,藉此進一步掌控 連線至網路連線擁有者可透過以下方式新增: connector.addConnectionHolder,並透過 connector.removeConnectionHolder

加入至少一個連線容器後,SDK 會嘗試 保持連線狀態在未新增任何連線容器的情況下, 可關閉連線

您必須保留所新增連線擁有者的參照,並移除連線容器 不再具有關聯性時。

同步呼叫

進行同步呼叫前,應新增連線容器。這可以 是以任何物件完成,但您必須持續追蹤該物件, 當您不再需要同步呼叫時,就會移除這個標記。

非同步呼叫

執行非同步呼叫時,會自動管理連線擁有者 這樣呼叫和第一個回應/錯誤之間才會開啟連線。 如果需要連線資訊才能繼續保留 (例如 則應該將回呼本身新增為回呼。 連線狀態容器;如果不再需要該容器,請將其移除 資料。

處理錯誤

根據預設,呼叫其他設定檔時,系統會呼叫其他設定檔 出現時,系統會擲回 UnavailableProfileException (或 傳遞到 Future,或非同步呼叫的錯誤回呼)。

為避免這種情況,開發人員可以使用 #both()#suppliers(),並 程式碼來處理結果清單中任何項目的數量 (如果 其他設定檔無法使用,或是 2 (如有)。

例外狀況

若未勾選,在呼叫目前的設定檔後發生任何例外狀況,將會 無論用來 呼叫 (#current()#personal#both 等)。

如果未勾選,在呼叫另一個設定檔後發生例外狀況 擲回 ProfileRuntimeException 中,與原始例外狀況 。無論用來發出呼叫的方法 (#other()#personal#both 等)。

ifAvailable

用來擷取及處理 UnavailableProfileException 的替代方案 例項,您可以使用 .ifAvailable() 方法提供預設值 ,系統將不會擲回 UnavailableProfileException

例如:

profileNotesDatabase.other().ifAvailable().getNumberOfNotes(/* defaultValue= */ 0);

測試

若要測試程式碼,您應該插入設定檔的執行個體 並附加至任何使用該程式碼的程式碼 (檢查設定檔可用性、 手動連線等)。您也應該插入個人資料的執行個體 以便瞭解這些容器的使用位置

我們提供可用於測試的連接器和類型偽造內容。

首先,請新增測試依附元件:

  testAnnotationProcessor
'com.google.android.enterprise.connectedapps:connectedapps-processor:1.1.2'
  testCompileOnly
'com.google.android.enterprise.connectedapps:connectedapps-testing-annotations:1.1.2'
  testImplementation
'com.google.android.enterprise.connectedapps:connectedapps-testing:1.1.2'

然後,為測試類別加上 @CrossProfileTest 註解,以識別 要測試的 @CrossProfileConfiguration 註解類別:

@CrossProfileTest(configuration = MyApplication.class)
@RunWith(RobolectricTestRunner.class)
public class NotesMediatorTest {

}

如此一來,您就能將 此外還會從 0 自動調整資源配置 您完全不必調整資源調度設定

在測試中建立這些假例項:

private final FakeCrossProfileConnector connector = new
FakeCrossProfileConnector();
private final NotesManager personalNotesManager = new NotesManager(); //
real/mock/fake
private final NotesManager workNotesManager = new NotesManager(); // real/mock/fake

private final FakeProfileNotesManager profileNotesManager =
  FakeProfileNotesManager.builder()
    .personal(personalNotesManager)
    .work(workNotesManager)
    .connector(connector)
    .build();

設定設定檔狀態:

connector.setRunningOnProfile(PERSONAL);
connector.createWorkProfile();
connector.turnOffWorkProfile();

將假連接器和跨設定檔類別傳遞至測試中的程式碼,並 然後撥打電話

呼叫將轉送至正確的目標,而在 撥打未連線或無法使用的設定檔撥打電話。

支援的類型

您不必花費太多心力,即可支援下列類型。這類員工 做為所有跨設定檔呼叫的引數或傳回類型。

  • 基本 (byteshortintlongfloatdoublecharboolean),
  • 盒裝基本產品 (java.lang.Bytejava.lang.Shortjava.lang.Integerjava.lang.Longjava.lang.Floatjava.lang.Doublejava.lang.Characterjava.lang.Booleanjava.lang.Void)、
  • java.lang.String
  • 任何實作 android.os.Parcelable 的項目;
  • 任何實作 java.io.Serializable 的項目;
  • 單一維度非原始陣列
  • java.util.Optional
  • java.util.Collection
  • java.util.List
  • java.util.Map
  • java.util.Set
  • android.util.Pair
  • com.google.common.collect.ImmutableMap

任何支援的通用類型 (例如 java.util.Collection) 都可能有 做為類型參數例如:

java.util.Collection<java.util.Map<java.lang.String,MySerializableType[]>> 是 有效的類型

Futures

以下類型僅支援傳回類型:

  • com.google.common.util.concurrent.ListenableFuture

自訂 Parcelable 包裝函式

如果您的類型不在前一個清單中,請先考慮是否可以設為 並正確實作 android.os.Parcelablejava.io.Serializable。如果 然後就無法看到 parcelable 包裝函式 增加對資料類型的支援。

自訂未來包裝函式

如要使用先前清單未列出的未來類型,請參閱「未來」 包裝函式來新增支援功能。

Parcelable 包裝函式

SDK 支援 Parcelable 包裝函式。 無法修改的類型SDK 包含許多 types 當中並未含有您需要的類型,因此必須自行編寫。

Parcelable 包裝函式是一種用來納入其他類別的類別, parcelable。根據定義的靜態合約,向 SDK 註冊 因此可用於將指定類型轉換為 parcel 類型 從 parcelable 類型中擷取該類型

註解

parcelable 包裝函式類別必須加註 @CustomParcelableWrapper。 將包裝的類別指定為 originalType。例如:

@CustomParcelableWrapper(originalType=ImmutableList.class)
``` ###
Format

Parcelable wrappers must implement `Parcelable` correctly, and must have a
static `W of(Bundler, BundlerType, T)` method which wraps the wrapped type and a
non-static `T get()` method which returns the wrapped type.

The SDK will use these methods to provide seamless support for the type.

### Bundler

To allow for wrapping generic types (such as lists and maps), the `of` method is
passed a `Bundler` which is capable of reading (using `#readFromParcel`) and
writing (using `#writeToParcel`) all supported types to a `Parcel`, and a
`BundlerType` which represents the declared type to be written.

`Bundler` and `BundlerType` instances are themselves parcelable, and should be
written as part of the parcelling of the parcelable wrapper, so that it can be
used when reconstructing the parcelable wrapper.

If the `BundlerType` represents a generic type, the type variables can be found
by calling `.typeArguments()`. Each type argument is itself a `BundlerType`.

For an example, see `ParcelableCustomWrapper`:

```java
public class CustomWrapper<F> {
  private final F value;

  public CustomWrapper(F value) {
    this.value = value;
  }
  public F value() {
    return value;
  }
}

@CustomParcelableWrapper(originalType = CustomWrapper.class)
public class ParcelableCustomWrapper<E> implements Parcelable {

  private static final int NULL = -1;
  private static final int NOT_NULL = 1;

  private final Bundler bundler;
  private final BundlerType type;
  private final CustomWrapper<E> customWrapper;

  /**
  *   Create a wrapper for a given {@link CustomWrapper}.
  *
  *   <p>The passed in {@link Bundler} must be capable of bundling {@code F}.
  */
  public static <F> ParcelableCustomWrapper<F> of(
      Bundler bundler, BundlerType type, CustomWrapper<F> customWrapper) {
    return new ParcelableCustomWrapper<>(bundler, type, customWrapper);
  }

  public CustomWrapper<E> get() {
    return customWrapper;
  }

  private ParcelableCustomWrapper(
      Bundler bundler, BundlerType type, CustomWrapper<E> customWrapper) {
    if (bundler == null || type == null) {
      throw new NullPointerException();
    }
    this.bundler = bundler;
    this.type = type;
    this.customWrapper = customWrapper;
  }

  private ParcelableCustomWrapper(Parcel in) {
    bundler = in.readParcelable(Bundler.class.getClassLoader());

    int presentValue = in.readInt();

    if (presentValue == NULL) {
      type = null;
      customWrapper = null;
      return;
    }

    type = (BundlerType) in.readParcelable(Bundler.class.getClassLoader());
    BundlerType valueType = type.typeArguments().get(0);

    @SuppressWarnings("unchecked")
    E value = (E) bundler.readFromParcel(in, valueType);

    customWrapper = new CustomWrapper<>(value);
  }

  @Override
  public void writeToParcel(Parcel dest, int flags) {
    dest.writeParcelable(bundler, flags);

    if (customWrapper == null) {
      dest.writeInt(NULL);
      return;
    }

    dest.writeInt(NOT_NULL);
    dest.writeParcelable(type, flags);
    BundlerType valueType = type.typeArguments().get(0);
    bundler.writeToParcel(dest, customWrapper.value(), valueType, flags);
  }

  @Override
  public int describeContents() {
    return 0;
  }

  @SuppressWarnings("rawtypes")
  public static final Creator<ParcelableCustomWrapper> CREATOR =
    new Creator<ParcelableCustomWrapper>() {
      @Override
      public ParcelableCustomWrapper createFromParcel(Parcel in) {
        return new ParcelableCustomWrapper(in);
      }

      @Override
      public ParcelableCustomWrapper[] newArray(int size) {
        return new ParcelableCustomWrapper[size];
      }
    };
}

透過 SDK 註冊

建立完成後,您需要註冊自訂 parcelable 包裝函式。 與 SDK 整合

方法是在parcelableWrappers={YourParcelableWrapper.class} 類別上的 CustomProfileConnector 註解或 CrossProfile 註解。

未來包裝函式

SDK 會透過日後的包裝函式來新增對設定檔的未來的支援。 SDK 預設支援 ListenableFuture,但適用於其他 Future 可以自行新增支援的類型

Future Wrapper 是專門用來包裝特定 Future 型別的類別, 可用的所有來源。須遵守已定義的靜態合約, 已向 SDK 註冊。

註解

未來的包裝函式類別必須加上 @CustomFutureWrapper 註解,並指定 以 originalType 形式傳遞包裝類別。例如:

@CustomFutureWrapper(originalType=SettableFuture.class)
``` ### Format

Future wrappers must extend
`com.google.android.enterprise.connectedapps.FutureWrapper`.

Future wrappers must have a static `W create(Bundler, BundlerType)` method which
creates an instance of the wrapper. At the same time this should create an
instance of the wrapped future type. This should be returned by a non-static `T`
`getFuture()` method. The `onResult(E)` and `onException(Throwable)` methods
must be implemented to pass the result or throwable to the wrapped future.

Future wrappers must also have a static `void writeFutureResult(Bundler,`
`BundlerType, T, FutureResultWriter<E>)` method. This should register with the
passed in future for results, and when a result is given, call
`resultWriter.onSuccess(value)`. If an exception is given,
`resultWriter.onFailure(exception)` should be called.

Finally, future wrappers must also have a static `T<Map<Profile, E>>`
`groupResults(Map<Profile, T<E>> results)` method which converts a map from
profile to future, into a future of a map from profile to result.
`CrossProfileCallbackMultiMerger` can be used to make this logic easier.

For example:

```java
/** A very simple implementation of the future pattern used to test custom future
wrappers. */
public class SimpleFuture<E> {
  public static interface Consumer<E> {
    void accept(E value);
  }
  private E value;
  private Throwable thrown;
  private final CountDownLatch countDownLatch = new CountDownLatch(1);
  private Consumer<E> callback;
  private Consumer<Throwable> exceptionCallback;

  public void set(E value) {
    this.value = value;
    countDownLatch.countDown();
    if (callback != null) {
      callback.accept(value);
    }
  }

  public void setException(Throwable t) {
    this.thrown = t;
    countDownLatch.countDown();
    if (exceptionCallback != null) {
      exceptionCallback.accept(thrown);
    }
  }

  public E get() {
    try {
      countDownLatch.await();
    } catch (InterruptedException e) {
      eturn null;
    }
    if (thrown != null) {
      throw new RuntimeException(thrown);
    }
    return value;
  }

  public void setCallback(Consumer<E> callback, Consumer<Throwable>
exceptionCallback) {
    if (value != null) {
      callback.accept(value);
    } else if (thrown != null) {
      exceptionCallback.accept(thrown);
    } else {
      this.callback = callback;
      this.exceptionCallback = exceptionCallback;
    }
  }
}
/** Wrapper for adding support for {@link SimpleFuture} to the Connected Apps SDK.
*/
@CustomFutureWrapper(originalType = SimpleFuture.class)
public final class SimpleFutureWrapper<E> extends FutureWrapper<E> {

  private final SimpleFuture<E> future = new SimpleFuture<>();

  public static <E> SimpleFutureWrapper<E> create(Bundler bundler, BundlerType
bundlerType) {
    return new SimpleFutureWrapper<>(bundler, bundlerType);
  }

  private SimpleFutureWrapper(Bundler bundler, BundlerType bundlerType) {
    super(bundler, bundlerType);
  }

  public SimpleFuture<E> getFuture() {
    return future;
  }

  @Override
  public void onResult(E result) {
    future.set(result);
  }

  @Override
  public void onException(Throwable throwable) {
    future.setException(throwable);
  }

  public static <E> void writeFutureResult(
      SimpleFuture<E> future, FutureResultWriter<E> resultWriter) {

    future.setCallback(resultWriter::onSuccess, resultWriter::onFailure);
  }

  public static <E> SimpleFuture<Map<Profile, E>> groupResults(
      Map<Profile, SimpleFuture<E>> results) {
    SimpleFuture<Map<Profile, E>> m = new SimpleFuture<>();

    CrossProfileCallbackMultiMerger<E> merger =
        new CrossProfileCallbackMultiMerger<>(results.size(), m::set);
    for (Map.Entry<Profile, SimpleFuture<E>> result : results.entrySet()) {
      result
        .getValue()
        .setCallback(
          (value) -> merger.onResult(result.getKey(), value),
          (throwable) -> merger.missingResult(result.getKey()));
    }
    return m;
  }
}

透過 SDK 註冊

建立後,如要使用自訂未來包裝函式,您需要向該包裝函式註冊 SDK。

方法是在futureWrappers={YourFutureWrapper.class} 類別上的 CustomProfileConnector 註解或 CrossProfile 註解。

直接啟動模式

如果您的應用程式支援直接啟動功能 模式 ,才能解鎖設定檔。 根據預設,SDK 只會在其他設定檔解鎖的情況下允許連線。

如要變更這項行為,如果您使用自訂設定檔連接器, 應指定 availabilityRestrictions=AvailabilityRestrictions.DIRECT_BOOT_AWARE

@GeneratedProfileConnector
@CustomProfileConnector(availabilityRestrictions=AvailabilityRestrictions.DIRECT_BO
OT_AWARE)
public interface MyProfileConnector extends ProfileConnector {
  public static MyProfileConnector create(Context context) {
    return GeneratedMyProfileConnector.builder(context).build();
  }
}

如果您使用的是 CrossProfileConnector,請使用 .setAvailabilityRestrictions(AvailabilityRestrictions.DIRECT_BOOT _AWARE已開啟 。

屆時,我們會告知供應情形,以便 個人資料呼叫 (前提是其他設定檔未解鎖)。您的責任 確保通話只會存取裝置加密儲存空間。