Topik lanjutan

Bagian ini dimaksudkan sebagai referensi dan Anda tidak perlu membacanya dari atas ke bawah.

Menggunakan API framework:

API ini akan digabungkan dalam SDK untuk platform API yang lebih konsisten (misalnya, menghindari objek UserHandle), tetapi untuk saat ini, Anda dapat memanggilnya secara langsung.

Implementasinya sederhana: jika Anda dapat berinteraksi, lanjutkan. Jika tidak, tetapi Anda dapat memintanya, tampilkan perintah/banner/tooltip/dll. pengguna. Jika pengguna setuju untuk membuka Setelan, buat intent permintaan dan gunakan Context#startActivity untuk mengirim pengguna ke sana. Anda dapat menggunakan siaran untuk mendeteksi kapan kemampuan ini berubah, atau cukup periksa lagi saat pengguna kembali.

Untuk mengujinya, Anda harus membuka TestDPC di profil kerja, membuka bagian paling bawah, lalu memilih untuk menambahkan nama paket ke daftar yang diizinkan aplikasi yang terhubung. Tindakan ini meniru 'izinkan' admin untuk aplikasi Anda.

Glosarium

Bagian ini menjelaskan istilah utama yang terkait dengan pengembangan lintas profil.

Konfigurasi Antar-Profil

Konfigurasi Antar-Profil mengelompokkan Class Penyedia Antar-Profil terkait dan memberikan konfigurasi umum untuk fitur antar-profil. Biasanya akan ada satu anotasi @CrossProfileConfiguration per codebase, tetapi dalam beberapa aplikasi kompleks mungkin ada beberapa anotasi.

Konektor Profil

Konektor mengelola koneksi antar-profil. Biasanya, setiap jenis lintas profil akan mengarah ke Konektor tertentu. Setiap jenis lintas profil dalam satu konfigurasi harus menggunakan Konektor yang sama.

Class Penyedia Antar-Profil

Class Penyedia Lintas Profil mengelompokkan Jenis Lintas Profil terkait.

Mediator

Mediator berada di antara kode tingkat tinggi dan tingkat rendah, mendistribusikan panggilan ke profil yang benar dan menggabungkan hasil. Ini adalah satu-satunya kode yang perlu memiliki profil. Ini adalah konsep arsitektur, bukan sesuatu yang di-build ke dalam SDK.

Jenis Antar-Profil

Jenis lintas profil adalah class atau antarmuka yang berisi metode yang dianotasi @CrossProfile. Kode dalam jenis ini tidak perlu mengetahui profil dan sebaiknya hanya bertindak berdasarkan data lokalnya.

Jenis Profil

Jenis Profil
Saat iniProfil aktif tempat kita menjalankan.
Lainnya(jika ada) Profil tempat kita tidak menjalankan.
PribadiPengguna 0, profil di perangkat yang tidak dapat dinonaktifkan.
KantorBiasanya pengguna 10, tetapi mungkin lebih tinggi, dapat diaktifkan dan dinonaktifkan, digunakan untuk menyimpan aplikasi dan data kerja.
UtamaDapat ditentukan secara opsional oleh aplikasi. Profil yang menampilkan tampilan gabungan dari kedua profil.
SekunderJika primer ditentukan, sekunder adalah profil yang bukan primer.
PemasokPemasok untuk profil utama adalah kedua profil, pemasok untuk profil sekunder hanya profil sekunder itu sendiri.

ID Profil

Class yang mewakili jenis profil (pribadi atau kerja). Ini akan ditampilkan oleh metode yang berjalan di beberapa profil dan dapat digunakan untuk menjalankan lebih banyak kode di profil tersebut. Ini dapat diserialisasi ke int untuk penyimpanan yang praktis.

Panduan ini menguraikan struktur yang direkomendasikan untuk membuat fungsi lintas profil yang efisien dan dapat dikelola dalam aplikasi Android Anda.

Mengonversi CrossProfileConnector menjadi singleton

Hanya satu instance yang boleh digunakan selama siklus proses aplikasi, atau Anda akan membuat koneksi paralel. Hal ini dapat dilakukan menggunakan framework injeksi dependensi seperti Dagger, atau dengan menggunakan pola Singleton klasik, baik di class baru maupun yang sudah ada.

Masukkan atau teruskan instance Profil yang dihasilkan ke dalam class Anda saat Anda melakukan panggilan, bukan membuatnya dalam metode

Hal ini memungkinkan Anda meneruskan instance FakeProfile yang dibuat secara otomatis dalam pengujian unit nanti.

Pertimbangkan pola mediator

Pola umum ini adalah membuat salah satu API yang ada (misalnya getEvents()) memiliki profil untuk semua pemanggilnya. Dalam hal ini, API yang ada hanya dapat menjadi metode atau class 'mediator' yang berisi panggilan baru ke kode lintas profil yang dihasilkan.

Dengan cara ini, Anda tidak memaksa setiap pemanggil untuk mengetahui cara melakukan panggilan lintas profil, tetapi panggilan tersebut hanya menjadi bagian dari API Anda.

Pertimbangkan apakah akan menganotasi metode antarmuka sebagai @CrossProfile untuk menghindari keharusan mengekspos class implementasi Anda di penyedia

Hal ini berfungsi dengan baik dengan framework injeksi dependensi.

Jika Anda menerima data dari panggilan lintas profil, pertimbangkan apakah akan menambahkan kolom yang mereferensikan profil asalnya

Ini bisa menjadi praktik yang baik karena Anda mungkin ingin mengetahuinya di lapisan UI (misalnya, menambahkan ikon badge ke item kerja). Hal ini juga mungkin diperlukan jika ID data tidak lagi unik tanpanya, seperti nama paket.

Antar-Profil

Bagian ini menguraikan cara mem-build interaksi Lintas Profil Anda sendiri.

Profil Utama

Sebagian besar panggilan dalam contoh pada dokumen ini berisi petunjuk eksplisit tentang profil yang akan dijalankan, termasuk kerja, pribadi, dan keduanya.

Dalam praktiknya, untuk aplikasi dengan pengalaman gabungan di satu profil saja, Anda mungkin ingin keputusan ini bergantung pada profil yang Anda jalankan, sehingga ada metode praktis serupa yang juga mempertimbangkan hal ini, untuk menghindari codebase Anda yang dipenuhi dengan kondisional profil if-else.

Saat membuat instance konektor, Anda dapat menentukan jenis profil mana yang 'utama' (misalnya, 'KERJA'). Tindakan ini memungkinkan opsi tambahan, seperti berikut:

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();

Jenis Antar-Profil

Class dan antarmuka yang berisi metode yang dianotasi @CrossProfile disebut sebagai Jenis Lintas Profil.

Implementasi Jenis Lintas Profil harus tidak bergantung pada profil, yaitu profil tempatnya berjalan. Metode ini diizinkan untuk melakukan panggilan ke metode lain dan secara umum harus berfungsi seperti sedang berjalan di satu profil. Mereka hanya akan memiliki akses ke status di profil mereka sendiri.

Contoh Jenis Antar-Profil:

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

Anotasi class

Untuk menyediakan API terkuat, Anda harus menentukan konektor untuk setiap jenis lintas profil, seperti:

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

Hal ini bersifat opsional, tetapi berarti API yang dihasilkan akan lebih spesifik dalam jenis dan lebih ketat dalam pemeriksaan waktu kompilasi.

Antarmuka

Dengan menganotasi metode di antarmuka sebagai @CrossProfile, Anda menyatakan bahwa mungkin ada beberapa implementasi metode ini yang harus dapat diakses di seluruh profil.

Anda dapat menampilkan implementasi antarmuka Lintas Profil apa pun di Penyedia Lintas Profil dan dengan melakukannya, Anda menyatakan bahwa implementasi ini harus dapat diakses lintas profil. Anda tidak perlu menganotasi class implementasi.

Penyedia Antar-Profil

Setiap Jenis Lintas Profil harus disediakan oleh metode yang dianotasi @CrossProfileProvider. Metode ini akan dipanggil setiap kali panggilan lintas profil dilakukan, jadi sebaiknya Anda mempertahankan singleton untuk setiap jenis.

Konstruktor

Penyedia harus memiliki konstruktor publik yang tidak menggunakan argumen atau satu argumen Context.

Metode Penyedia

Metode penyedia tidak boleh mengambil argumen atau satu argumen Context.

Injeksi Dependensi

Jika Anda menggunakan framework injeksi dependensi seperti Dagger untuk mengelola dependensi, sebaiknya Anda membuat framework tersebut membuat jenis lintas profil seperti biasa, lalu memasukkan jenis tersebut ke dalam class penyedia. Metode @CrossProfileProvider kemudian dapat menampilkan instance yang dimasukkan tersebut.

Konektor Profil

Setiap Konfigurasi Lintas Profil harus memiliki satu Konektor Profil, yang bertanggung jawab untuk mengelola koneksi ke profil lain.

Konektor Profil Default

Jika hanya ada satu Konfigurasi Lintas Profil dalam codebase, Anda dapat menghindari pembuatan Konektor Profil Anda sendiri dan menggunakan com.google.android.enterprise.connectedapps.CrossProfileConnector. Ini adalah default yang digunakan jika tidak ada yang ditentukan.

Saat membuat Konektor Lintas Profil, Anda dapat menentukan beberapa opsi di builder:

  • Layanan Eksekutor Terjadwal

    Jika Anda ingin memiliki kontrol atas thread yang dibuat oleh SDK, gunakan #setScheduledExecutorService(),

  • Binder

    Jika Anda memiliki kebutuhan khusus terkait binding profil, gunakan #setBinder. Hal ini mungkin hanya digunakan oleh Pengontrol Kebijakan Perangkat.

Konektor Profil Kustom

Anda memerlukan konektor profil kustom agar dapat menetapkan beberapa konfigurasi (menggunakan CustomProfileConnector) dan akan memerlukannya jika memerlukan beberapa konektor dalam satu codebase (misalnya, jika Anda memiliki beberapa proses, sebaiknya gunakan satu konektor per proses).

Saat membuat ProfileConnector, tampilannya akan terlihat seperti:

@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

    Untuk mengubah nama layanan yang dihasilkan (yang harus dirujuk dalam AndroidManifest.xml Anda), gunakan serviceClassName=.

  • primaryProfile

    Untuk menentukan profil utama, gunakan primaryProfile.

  • availabilityRestrictions

    Untuk mengubah batasan yang diterapkan SDK pada koneksi dan ketersediaan profil, gunakan availabilityRestrictions.

Pengontrol Kebijakan Perangkat

Jika aplikasi Anda adalah Pengontrol Kebijakan Perangkat, Anda harus menentukan instance DpcProfileBinder yang mereferensikan DeviceAdminReceiver.

Jika Anda menerapkan konektor profil Anda sendiri:

@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();
  }
}

atau menggunakan CrossProfileConnector default:

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

Konfigurasi Antar-Profil

Anotasi @CrossProfileConfiguration digunakan untuk menautkan semua jenis lintas profil menggunakan konektor untuk mengirim panggilan metode dengan benar. Untuk melakukannya, kita menganotasi class dengan @CrossProfileConfiguration yang mengarah ke setiap penyedia, seperti ini:

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

Tindakan ini akan memvalidasi bahwa untuk semua Jenis Profil Lintas, konektor profilnya sama atau tidak ada konektor yang ditentukan.

  • serviceSuperclass

    Secara default, layanan yang dihasilkan akan menggunakan android.app.Service sebagai superclass. Jika Anda memerlukan class yang berbeda (yang harus berupa subclass android.app.Service) sebagai superclass, tentukan serviceSuperclass=.

  • serviceClass

    Jika ditentukan, tidak ada layanan yang akan dihasilkan. Ini harus cocok dengan serviceClassName di konektor profil yang Anda gunakan. Layanan kustom Anda harus mengirim panggilan menggunakan class _Dispatcher yang dihasilkan seperti:

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;
  }
}

Ini dapat digunakan jika Anda perlu melakukan tindakan tambahan sebelum atau setelah panggilan lintas profil.

  • Konektor

    Jika menggunakan konektor selain CrossProfileConnector default, Anda harus menentukannya menggunakan connector=.

Visibilitas

Setiap bagian aplikasi Anda yang berinteraksi lintas profil harus dapat melihat Konektor Profil Anda.

Class yang dianotasi @CrossProfileConfiguration harus dapat melihat setiap penyedia yang digunakan dalam aplikasi Anda.

Panggilan Sinkron

Connected Apps SDK mendukung panggilan sinkron (pemblokiran) untuk kasus saat panggilan tersebut tidak dapat dihindari. Namun, ada sejumlah kelemahan dalam menggunakan panggilan ini (seperti potensi panggilan untuk diblokir dalam waktu lama), jadi sebaiknya Anda menghindari panggilan sinkron jika memungkinkan. Untuk menggunakan panggilan asinkron, lihat Panggilan asinkron .

Holder Koneksi

Jika menggunakan panggilan sinkron, Anda harus memastikan bahwa ada holder koneksi yang terdaftar sebelum melakukan panggilan lintas profil. Jika tidak, pengecualian akan ditampilkan. Untuk informasi selengkapnya, lihat Holder Koneksi.

Untuk menambahkan holder koneksi, panggil ProfileConnector#addConnectionHolder(Object) dengan objek apa pun (kemungkinan, instance objek yang melakukan panggilan lintas profil). Tindakan ini akan mencatat bahwa objek ini menggunakan koneksi dan akan mencoba membuat koneksi. Tindakan ini harus dipanggil sebelum panggilan sinkron dilakukan. Ini adalah panggilan non-pemblokiran sehingga koneksi mungkin tidak akan siap (atau mungkin tidak dapat dilakukan) pada saat Anda melakukan panggilan. Dalam hal ini, perilaku penanganan error yang biasa akan berlaku.

Jika Anda tidak memiliki izin lintas profil yang sesuai saat memanggil ProfileConnector#addConnectionHolder(Object) atau tidak ada profil yang tersedia untuk terhubung, tidak akan ada error yang ditampilkan, tetapi callback yang terhubung tidak akan dipanggil. Jika izin diberikan nanti atau profil lain menjadi tersedia, koneksi akan dibuat dan callback akan dipanggil.

Atau, ProfileConnector#connect(Object) adalah metode pemblokiran yang akan menambahkan objek sebagai holder koneksi dan membuat koneksi atau menampilkan UnavailableProfileException. Metode ini tidak dapat dipanggil dari UI Thread.

Panggilan ke ProfileConnector#connect(Object) dan ProfileConnector#connect serupa akan menampilkan objek penutupan otomatis yang akan otomatis menghapus holder koneksi setelah ditutup. Hal ini memungkinkan penggunaan seperti:

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

Setelah selesai melakukan panggilan sinkron, Anda harus memanggil ProfileConnector#removeConnectionHolder(Object). Setelah semua pemegang koneksi dihapus, koneksi akan ditutup.

Konektivitas

Pemroses koneksi dapat digunakan untuk mendapatkan informasi saat status koneksi berubah, dan connector.utils().isConnected dapat digunakan untuk menentukan apakah koneksi ada. Contoh:

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

Panggilan Asinkron

Setiap metode yang ditampilkan di seluruh pemisah profil harus ditetapkan sebagai pemblokiran (sinkron) atau non-pemblokiran (asinkron). Setiap metode yang menampilkan jenis data asinkron (misalnya ListenableFuture) atau menerima parameter callback akan ditandai sebagai non-blocking. Semua metode lainnya ditandai sebagai pemblokiran.

Panggilan asinkron direkomendasikan. Jika Anda harus menggunakan panggilan sinkron, lihat Panggilan Sinkron.

Callback

Jenis panggilan non-pemblokiran yang paling dasar adalah metode void yang menerima sebagai salah satu parameternya antarmuka yang berisi metode yang akan dipanggil dengan hasilnya. Agar antarmuka ini berfungsi dengan SDK, antarmuka harus dianotasi @CrossProfileCallback. Contoh:

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

Antarmuka ini kemudian dapat digunakan sebagai parameter dalam metode yang dianotasi @CrossProfile dan dipanggil seperti biasa. Contoh:

@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
});

Jika antarmuka ini berisi satu metode, yang menggunakan nol atau satu parameter, antarmuka ini juga dapat digunakan dalam panggilan ke beberapa profil sekaligus.

Jumlah nilai apa pun dapat diteruskan menggunakan callback, tetapi koneksi hanya akan terbuka untuk nilai pertama. Lihat Holder Koneksi untuk mengetahui informasi tentang menjaga koneksi tetap terbuka untuk menerima lebih banyak nilai.

Metode sinkron dengan callback

Salah satu fitur yang tidak biasa dari penggunaan callback dengan SDK adalah Anda secara teknis dapat menulis metode sinkron yang menggunakan callback:

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

Dalam hal ini, metode sebenarnya sinkron, meskipun ada callback. Kode ini akan dieksekusi dengan benar:

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

Namun, saat dipanggil menggunakan SDK, perilakunya tidak akan sama. Tidak ada jaminan bahwa metode penginstalan akan dipanggil sebelum "Ini mencetak ketiga" dicetak. Setiap penggunaan metode yang ditandai sebagai asinkron oleh SDK tidak boleh membuat asumsi tentang kapan metode akan dipanggil.

Callback Sederhana

"Callback sederhana" adalah bentuk callback yang lebih ketat yang memungkinkan fitur tambahan saat melakukan panggilan lintas profil. Antarmuka sederhana harus berisi satu metode, yang dapat menggunakan nol atau satu parameter.

Anda dapat mewajibkan antarmuka callback untuk tetap ada dengan menentukan simple=true dalam anotasi @CrossProfileCallback.

Callback sederhana dapat digunakan dengan berbagai metode seperti .both(), .suppliers(), dan lainnya.

Holder Koneksi

Saat melakukan panggilan asinkron (menggunakan callback atau future), holder koneksi akan ditambahkan saat melakukan panggilan dan dihapus saat pengecualian atau nilai diteruskan.

Jika Anda mengharapkan lebih dari satu hasil diteruskan menggunakan callback, Anda harus menambahkan callback secara manual sebagai holder koneksi:

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

  profileMyClass.other().registerListener(b);

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

Ini juga dapat digunakan dengan blok try-with-resources:

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

  // Other things running while we expect results
}

Jika kita melakukan panggilan dengan callback atau future, koneksi akan tetap terbuka hingga hasil diteruskan. Jika kami menentukan bahwa hasil tidak akan diteruskan, kita harus menghapus callback atau future sebagai holder koneksi:

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

Untuk informasi selengkapnya, lihat Holder Koneksi.

Futures

Futures juga didukung secara native oleh SDK. Satu-satunya jenis Future yang didukung secara native adalah ListenableFuture, meskipun jenis Future kustom dapat digunakan. Untuk menggunakan future, Anda cukup mendeklarasikan jenis Future yang didukung sebagai jenis nilai yang ditampilkan metode lintas profil, lalu menggunakannya seperti biasa.

Ini memiliki "fitur tidak biasa" yang sama dengan callback, dengan metode sinkron yang menampilkan future (misalnya, menggunakan immediateFuture) akan berperilaku berbeda saat dijalankan di profil saat ini dibandingkan dijalankan di profil lain. Setiap penggunaan metode yang ditandai sebagai asinkron oleh SDK tidak boleh membuat asumsi tentang kapan metode akan dipanggil.

Thread

Jangan memblokir hasil callback atau masa depan lintas profil di thread utama. Jika Anda melakukannya, dalam beberapa situasi, kode Anda akan diblokir tanpa batas waktu. Hal ini karena koneksi ke profil lain juga dibuat di thread utama, yang tidak akan pernah terjadi jika diblokir menunggu hasil lintas profil.

Ketersediaan

Pemroses ketersediaan dapat digunakan untuk mendapatkan informasi saat status ketersediaan berubah, dan connector.utils().isAvailable dapat digunakan untuk menentukan apakah profil lain tersedia untuk digunakan. Contoh:

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

Holder Koneksi

Holder koneksi adalah objek arbitrer yang dicatat sebagai memiliki dan tertarik pada koneksi lintas profil yang dibuat dan dipertahankan.

Secara default, saat melakukan panggilan asinkron, holder koneksi akan ditambahkan saat panggilan dimulai, dan dihapus saat hasil atau error terjadi.

Holder Koneksi juga dapat ditambahkan dan dihapus secara manual untuk memberikan kontrol yang lebih besar atas koneksi. Holder koneksi dapat ditambahkan menggunakan connector.addConnectionHolder, dan dihapus menggunakan connector.removeConnectionHolder.

Jika ada setidaknya satu holder koneksi yang ditambahkan, SDK akan mencoba mempertahankan koneksi. Jika tidak ada pemegang koneksi yang ditambahkan, koneksi dapat ditutup.

Anda harus mempertahankan referensi ke pemegang koneksi yang ditambahkan - dan menghapusnya jika tidak lagi relevan.

Panggilan tersinkron

Sebelum melakukan panggilan sinkron, holder koneksi harus ditambahkan. Hal ini dapat dilakukan menggunakan objek apa pun, meskipun Anda harus melacak objek tersebut agar dapat dihapus saat Anda tidak perlu lagi melakukan panggilan sinkron.

Panggilan asinkron

Saat melakukan panggilan asinkron, holder koneksi akan otomatis dikelola sehingga koneksi terbuka antara panggilan dan respons atau error pertama. Jika Anda memerlukan koneksi untuk tetap aktif setelah ini (misalnya, untuk menerima beberapa respons menggunakan satu callback), Anda harus menambahkan callback itu sendiri sebagai holder koneksi, dan menghapusnya setelah Anda tidak lagi perlu menerima data lebih lanjut.

Penanganan Error

Secara default, setiap panggilan yang dilakukan ke profil lain saat profil lain tidak tersedia akan menyebabkan UnavailableProfileException ditampilkan (atau diteruskan ke Future, atau callback error untuk panggilan asinkron).

Untuk menghindari hal ini, developer dapat menggunakan #both() atau #suppliers() dan menulis kode mereka untuk menangani jumlah entri dalam daftar yang dihasilkan (ini akan menjadi 1 jika profil lainnya tidak tersedia, atau 2 jika tersedia).

Pengecualian

Setiap pengecualian yang tidak dicentang yang terjadi setelah panggilan ke profil saat ini akan disebarkan seperti biasa. Hal ini berlaku terlepas dari metode yang digunakan untuk melakukan panggilan (#current(), #personal, #both, dll.).

Pengecualian yang tidak diperiksa yang terjadi setelah panggilan ke profil lain akan menyebabkan ProfileRuntimeException ditampilkan dengan pengecualian asli sebagai penyebabnya. Hal ini berlaku terlepas dari metode yang digunakan untuk melakukan panggilan (#other(), #personal, #both, dll.).

ifAvailable

Sebagai alternatif untuk menangkap dan menangani instance UnavailableProfileException, Anda dapat menggunakan metode .ifAvailable() untuk memberikan nilai default yang akan ditampilkan, bukan menampilkan UnavailableProfileException.

Contoh:

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

Pengujian

Agar kode dapat diuji, Anda harus memasukkan instance konektor profil ke kode apa pun yang menggunakannya (untuk memeriksa ketersediaan profil, untuk terhubung secara manual, dll.). Anda juga harus memasukkan instance jenis yang mengetahui profil tempat jenis tersebut digunakan.

Kami menyediakan konektor dan jenis palsu yang dapat digunakan dalam pengujian.

Pertama, tambahkan dependensi pengujian:

  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'

Kemudian, anotasikan class pengujian Anda dengan @CrossProfileTest, yang mengidentifikasi class beranotasi @CrossProfileConfiguration yang akan diuji:

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

}

Tindakan ini akan menyebabkan pembuatan palsu untuk semua jenis dan konektor yang digunakan dalam konfigurasi.

Buat instance palsu tersebut dalam pengujian Anda:

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();

Siapkan status profil:

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

Teruskan konektor palsu dan class lintas profil ke kode yang sedang diuji, lalu lakukan panggilan.

Panggilan akan dirutekan ke target yang benar - dan pengecualian akan ditampilkan saat melakukan panggilan ke profil yang terputus atau tidak tersedia.

Jenis yang Didukung

Jenis berikut didukung tanpa upaya tambahan dari Anda. Ini dapat digunakan sebagai argumen atau jenis nilai yang ditampilkan untuk semua panggilan lintas profil.

  • Primitif (byte, short, int, long, float, double, char, boolean),
  • Primitif Berkotak (java.lang.Byte, java.lang.Short, java.lang.Integer, java.lang.Long, java.lang.Float, java.lang.Double, java.lang.Character, java.lang.Boolean, java.lang.Void),
  • java.lang.String,
  • Apa pun yang mengimplementasikan android.os.Parcelable,
  • Apa pun yang mengimplementasikan java.io.Serializable,
  • Array non-primitif satu dimensi,
  • java.util.Optional,
  • java.util.Collection,
  • java.util.List,
  • java.util.Map,
  • java.util.Set,
  • android.util.Pair,
  • com.google.common.collect.ImmutableMap.

Setiap jenis generik yang didukung (misalnya java.util.Collection) dapat memiliki jenis yang didukung sebagai parameter jenisnya. Contoh:

java.util.Collection<java.util.Map<java.lang.String,MySerializableType[]>> adalah jenis yang valid.

Futures

Jenis berikut hanya didukung sebagai jenis nilai yang ditampilkan:

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

Wrapper Parcelable Kustom

Jika jenis Anda tidak ada dalam daftar sebelumnya, pertimbangkan terlebih dahulu apakah jenis tersebut dapat dibuat untuk mengimplementasikan android.os.Parcelable atau java.io.Serializable dengan benar. Jika tidak dapat melihat wrapper parcelable untuk menambahkan dukungan untuk jenis Anda.

Wrapper Future Kustom

Jika Anda ingin menggunakan jenis mendatang yang tidak ada dalam daftar sebelumnya, lihat wrapper mendatang untuk menambahkan dukungan.

Wrapper Parcelable

Wrapper Parcelable adalah cara SDK menambahkan dukungan untuk jenis yang tidak dapat di-parcel dan tidak dapat diubah. SDK menyertakan wrapper untuk banyak jenis, tetapi jika jenis yang perlu Anda gunakan tidak disertakan, Anda harus menulis sendiri.

Wrapper Parcelable adalah class yang dirancang untuk menggabungkan class lain dan membuatnya dapat di-parcel. Class ini mengikuti kontrak statis yang ditentukan dan terdaftar dengan SDK sehingga dapat digunakan untuk mengonversi jenis tertentu menjadi jenis parcelable, dan juga mengekstrak jenis tersebut dari jenis parcelable.

Annotation

Class wrapper parcelable harus dianotasi @CustomParcelableWrapper, yang menentukan class yang digabungkan sebagai originalType. Contoh:

@CustomParcelableWrapper(originalType=ImmutableList.class)

Format

Wrapper parcelable harus menerapkan Parcelable dengan benar, dan harus memiliki metode W of(Bundler, BundlerType, T) statis yang menggabungkan jenis yang digabungkan dan metode T get() non-statis yang menampilkan jenis yang digabungkan.

SDK akan menggunakan metode ini untuk memberikan dukungan yang lancar untuk jenis tersebut.

Bundler

Untuk memungkinkan penggabungan jenis generik (seperti daftar dan peta), metode of diteruskan Bundler yang dapat membaca (menggunakan #readFromParcel) dan menulis (menggunakan #writeToParcel) semua jenis yang didukung ke Parcel, dan BundlerType yang mewakili jenis yang dideklarasikan untuk ditulis.

Instance Bundler dan BundlerType itu sendiri dapat di-parcel, dan harus ditulis sebagai bagian dari pemisahan wrapper parcelable, sehingga dapat digunakan saat merekonstruksi wrapper parcelable.

Jika BundlerType mewakili jenis generik, variabel jenis dapat ditemukan dengan memanggil .typeArguments(). Setiap argumen jenis itu sendiri adalah BundlerType.

Untuk contoh, lihat ParcelableCustomWrapper:

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];
      }
    };
}

Mendaftar dengan SDK

Setelah dibuat, untuk menggunakan wrapper parcelable kustom, Anda harus mendaftarkannya dengan SDK.

Untuk melakukannya, tentukan parcelableWrappers={YourParcelableWrapper.class} dalam anotasi CustomProfileConnector atau anotasi CrossProfile pada class.

Wrapper Mendatang

Wrapper Masa Depan adalah cara SDK menambahkan dukungan untuk masa depan di seluruh profil. SDK menyertakan dukungan untuk ListenableFuture secara default, tetapi untuk jenis Future lainnya, Anda dapat menambahkan dukungan sendiri.

Future Wrapper adalah class yang dirancang untuk menggabungkan jenis Future tertentu dan membuatnya tersedia untuk SDK. Ini mengikuti kontrak statis yang ditentukan dan harus didaftarkan dengan SDK.

Annotation

Class wrapper mendatang harus dianotasi @CustomFutureWrapper, yang menentukan class yang digabungkan sebagai originalType. Contoh:

@CustomFutureWrapper(originalType=SettableFuture.class)

Format

Wrapper mendatang harus memperluas com.google.android.enterprise.connectedapps.FutureWrapper.

Wrapper mendatang harus memiliki metode W create(Bundler, BundlerType) statis yang membuat instance wrapper. Pada saat yang sama, tindakan ini akan membuat instance jenis masa mendatang yang digabungkan. Ini harus ditampilkan oleh metode T getFuture() non-statis. Metode onResult(E) dan onException(Throwable) harus diimplementasikan untuk meneruskan hasil atau throwable ke future yang digabungkan.

Wrapper mendatang juga harus memiliki metode void writeFutureResult(Bundler, BundlerType, T, FutureResultWriter<E>) statis. Ini akan terdaftar dengan yang diteruskan pada masa mendatang untuk hasil, dan saat hasil diberikan, panggil resultWriter.onSuccess(value). Jika pengecualian diberikan, resultWriter.onFailure(exception) harus dipanggil.

Terakhir, wrapper mendatang juga harus memiliki metode T<Map<Profile, E>> groupResults(Map<Profile, T<E>> results) statis yang mengonversi peta dari profil ke masa mendatang, menjadi masa mendatang peta dari profil ke hasil. CrossProfileCallbackMultiMerger dapat digunakan untuk mempermudah logika ini.

Contoh:

/** A basic 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;
  }
}

Mendaftar dengan SDK

Setelah dibuat, untuk menggunakan wrapper masa mendatang kustom, Anda harus mendaftarkannya dengan SDK.

Untuk melakukannya, tentukan futureWrappers={YourFutureWrapper.class} dalam anotasi CustomProfileConnector atau anotasi CrossProfile pada class.

Mode Direct Boot

Jika aplikasi Anda mendukung mode booting langsung, Anda mungkin perlu melakukan panggilan lintas profil sebelum profil dibuka kuncinya. Secara default, SDK hanya mengizinkan koneksi saat profil lain tidak terkunci.

Untuk mengubah perilaku ini, jika menggunakan konektor profil kustom, Anda harus menentukan 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();
  }
}

Jika Anda menggunakan CrossProfileConnector, gunakan .setAvailabilityRestrictions(AvailabilityRestrictions.DIRECT_BOOT _AWARE di builder.

Dengan perubahan ini, Anda akan diberi tahu tentang ketersediaan, dan dapat melakukan panggilan lintas profil, saat profil lain tidak dibuka kuncinya. Anda bertanggung jawab untuk memastikan panggilan Anda hanya mengakses penyimpanan yang dienkripsi dengan perangkat.