उन्नत विषय

ये सेक्शन सिर्फ़ रेफ़रंस के लिए हैं. इन्हें शुरू से लेकर आखिर तक पढ़ना ज़रूरी नहीं है.

फ़्रेमवर्क एपीआई का इस्तेमाल करना:

इन एपीआई को SDK टूल में रैप किया जाएगा, ताकि एपीआई का इस्तेमाल करने का तरीका एक जैसा रहे.उदाहरण के लिए, UserHandle ऑब्जेक्ट से बचना. हालांकि, फ़िलहाल इन एपीआई को सीधे तौर पर कॉल किया जा सकता है.

इसे लागू करना आसान है: अगर आपके पास इंटरैक्ट करने का विकल्प है, तो आगे बढ़ें. अगर ऐसा नहीं है, लेकिन अनुरोध किया जा सकता है, तो उपयोगकर्ता को प्रॉम्प्ट/बैनर/टूलटिप वगैरह दिखाएं. अगर उपयोगकर्ता सेटिंग पर जाने के लिए सहमत होता है, तो अनुरोध इंटेंट बनाएं और उपयोगकर्ता को वहां भेजने के लिए Context#startActivity का इस्तेमाल करें. इस सुविधा में बदलाव होने पर पता लगाने के लिए, ब्रॉडकास्ट का इस्तेमाल किया जा सकता है. इसके अलावा, उपयोगकर्ता के वापस आने पर भी इसकी जांच की जा सकती है.

इसकी जांच करने के लिए, आपको अपनी वर्क प्रोफ़ाइल में TestDPC खोलना होगा. इसके बाद, सबसे नीचे जाएं और कनेक्ट किए गए ऐप्लिकेशन की अनुमति वाली सूची में अपना पैकेज का नाम जोड़ने का विकल्प चुनें. यह आपके ऐप्लिकेशन को 'अनुमति वाली सूची' में शामिल करने के लिए, एडमिन की नकल करता है.

शब्दावली

इस सेक्शन में, अलग-अलग प्रोफ़ाइलों के लिए डेवलपमेंट से जुड़े मुख्य शब्दों के बारे में बताया गया है.

क्रॉस प्रोफ़ाइल कॉन्फ़िगरेशन

क्रॉस-प्रोफ़ाइल कॉन्फ़िगरेशन, क्रॉस-प्रोफ़ाइल प्रोवाइडर से जुड़ी क्लास को एक साथ ग्रुप करता है. साथ ही, क्रॉस-प्रोफ़ाइल की सुविधाओं के लिए सामान्य कॉन्फ़िगरेशन उपलब्ध कराता है. आम तौर पर, हर कोडबेस के लिए एक @CrossProfileConfiguration एनोटेशन होगा. हालांकि, कुछ जटिल ऐप्लिकेशन में एक से ज़्यादा एनोटेशन हो सकते हैं.

प्रोफ़ाइल कनेक्टर

कनेक्टर, प्रोफ़ाइलों के बीच के कनेक्शन मैनेज करता है. आम तौर पर, हर क्रॉस प्रोफ़ाइल टाइप किसी खास कनेक्टर पर ले जाएगा. किसी एक कॉन्फ़िगरेशन में मौजूद हर क्रॉस प्रोफ़ाइल टाइप के लिए, एक ही कनेक्टर का इस्तेमाल करना ज़रूरी है.

क्रॉस प्रोफ़ाइल की सुविधा देने वाली कंपनी

क्रॉस-प्रोफ़ाइल प्रोवाइडर क्लास, मिलते-जुलते क्रॉस-प्रोफ़ाइल टाइप को एक साथ ग्रुप करती है.

Mediator

मीडिएटर, हाई-लेवल और लो-लेवल कोड के बीच काम करता है. यह सही प्रोफ़ाइलों पर कॉल भेजता है और नतीजों को मर्ज करता है. यह एक ऐसा कोड है जिसे प्रोफ़ाइल के बारे में जानकारी होनी चाहिए. यह SDK में पहले से मौजूद नहीं है, बल्कि यह आर्किटेक्चर का एक कॉन्सेप्ट है.

क्रॉस प्रोफ़ाइल का टाइप

क्रॉस प्रोफ़ाइल टाइप, ऐसी क्लास या इंटरफ़ेस होती है जिसमें एनोटेट किए गए मेथड होते हैं @CrossProfile. इस तरह के कोड को प्रोफ़ाइल के बारे में जानकारी नहीं होनी चाहिए. साथ ही, यह अपने स्थानीय डेटा पर ही काम करना चाहिए.

प्रोफ़ाइल के टाइप

प्रोफ़ाइल का टाइप
मौजूदावह चालू प्रोफ़ाइल जिस पर हम प्रोसेस कर रहे हैं.
अन्य(अगर मौजूद है) वह प्रोफ़ाइल जिस पर हम फ़ंक्शन को लागू नहीं कर रहे हैं.
निजीउपयोगकर्ता 0, डिवाइस पर मौजूद वह प्रोफ़ाइल जिसे बंद नहीं किया जा सकता.
ऑफ़िसआम तौर पर, उपयोगकर्ता 10 होता है, लेकिन यह ज़्यादा भी हो सकता है. इसे टॉगल करके चालू और बंद किया जा सकता है. इसका इस्तेमाल, काम से जुड़े ऐप्लिकेशन और डेटा को सेव करने के लिए किया जाता है.
प्राइमरीऐप्लिकेशन में तय किया जा सकता है. वह प्रोफ़ाइल जिसमें दोनों प्रोफ़ाइलों को मर्ज करके बनाया गया व्यू दिखता है.
Secondaryअगर प्राइमरी प्रोफ़ाइल तय की गई है, तो सेकंडरी प्रोफ़ाइल वह होती है जो प्राइमरी प्रोफ़ाइल नहीं है.
सप्लायरप्राइमरी प्रोफ़ाइल के सप्लायर, दोनों प्रोफ़ाइलें हैं, सेकंडरी प्रोफ़ाइल के सप्लायर, सिर्फ़ सेकंडरी प्रोफ़ाइल है.

प्रोफ़ाइल आइडेंटिफ़ायर

एक क्लास, जो किसी तरह की प्रोफ़ाइल (निजी या वर्क) को दिखाती है. ये वैल्यू, एक से ज़्यादा प्रोफ़ाइलों पर चलने वाले तरीकों से वापस आती हैं. इनका इस्तेमाल, उन प्रोफ़ाइलों पर ज़्यादा कोड चलाने के लिए किया जा सकता है. इन्हें आसानी से स्टोर करने के लिए, int में सीरियल किया जा सकता है.

इस गाइड में, Android ऐप्लिकेशन में अलग-अलग प्रोफ़ाइलों के लिए, बेहतर और मैनेज करने लायक सुविधाएं बनाने के लिए सुझाए गए स्ट्रक्चर के बारे में बताया गया है.

अपने CrossProfileConnector को सिंगलटन में बदलना

अपने ऐप्लिकेशन के पूरे लाइफ़साइकल के दौरान, सिर्फ़ एक इंस्टेंस का इस्तेमाल किया जाना चाहिए. ऐसा न करने पर, पैरलल कनेक्शन बन जाएंगे. ऐसा करने के लिए, Dagger जैसे डिपेंडेंसी इंजेक्शन फ़्रेमवर्क का इस्तेमाल किया जा सकता है. इसके अलावा, किसी नई या मौजूदा क्लास में क्लासिक सिंगलटन पैटर्न का इस्तेमाल भी किया जा सकता है.

कॉल करने के लिए, जनरेट किए गए प्रोफ़ाइल इंस्टेंस को अपनी क्लास में इंजेक्ट या पास करें. ऐसा करने के लिए, उसे मेथड में बनाने की ज़रूरत नहीं है

इससे, बाद में यूनिट टेस्ट में अपने-आप जनरेट हुए FakeProfile इंस्टेंस को पास किया जा सकता है.

मीडिएटर पैटर्न का इस्तेमाल करना

यह सामान्य पैटर्न है कि अपने किसी मौजूदा एपीआई (उदाहरण के लिए, getEvents()) को कॉल करने वाले सभी लोगों के लिए, प्रोफ़ाइल के बारे में जानकारी देने वाला बनाएं. इस मामले में, आपका मौजूदा एपीआई सिर्फ़ एक 'मीडिएटर' तरीका या क्लास बन सकता है. इसमें जनरेट किए गए क्रॉस-प्रोफ़ाइल कोड के लिए नया कॉल शामिल होता है.

इस तरह, आपको हर कॉलर को क्रॉस-प्रोफ़ाइल कॉल करने का तरीका नहीं बताना पड़ता. यह सिर्फ़ आपके एपीआई का हिस्सा बन जाता है.

किसी इंटरफ़ेस के तरीके को @CrossProfile के तौर पर एनोटेट करें, ताकि आपको किसी प्रोवाइडर में लागू की गई क्लास को एक्सपोज़ न करना पड़े

यह डिपेंडेंसी इंजेक्शन फ़्रेमवर्क के साथ अच्छी तरह से काम करता है.

अगर आपको किसी क्रॉस-प्रोफ़ाइल कॉल से कोई डेटा मिल रहा है, तो इस बात पर विचार करें कि क्या आपको उस प्रोफ़ाइल का रेफ़रंस देने वाला कोई फ़ील्ड जोड़ना है

यह एक अच्छा तरीका हो सकता है, क्योंकि हो सकता है कि आपको यूज़र इंटरफ़ेस (यूआई) लेयर पर यह जानकारी चाहिए हो. उदाहरण के लिए, काम से जुड़े आइटम में बैज आइकॉन जोड़ना. ऐसा तब भी ज़रूरी हो सकता है, जब पैकेज के नाम जैसे किसी डेटा आइडेंटिफ़ायर के बिना, वह यूनीक न रहे.

क्रॉस प्रोफ़ाइल

इस सेक्शन में, अलग-अलग प्रोफ़ाइलों के बीच इंटरैक्शन बनाने का तरीका बताया गया है.

मुख्य प्रोफ़ाइलें

इस दस्तावेज़ में दिए गए ज़्यादातर कॉल में, यह साफ़ तौर पर बताया गया है कि उन्हें किन प्रोफ़ाइलों पर चलाना है. इनमें वर्क प्रोफ़ाइल, निजी प्रोफ़ाइल, और दोनों शामिल हैं.

आम तौर पर, सिर्फ़ एक प्रोफ़ाइल पर मर्ज किए गए अनुभव वाले ऐप्लिकेशन के लिए, हो सकता है कि आप यह फ़ैसला उस प्रोफ़ाइल के हिसाब से लेना चाहें जिस पर ऐप्लिकेशन चल रहा है. इसलिए, ऐसे ही आसान तरीके हैं जो इस बात का भी ध्यान रखते हैं. इससे, आपके कोडबेस में if-else प्रोफ़ाइल कंडीशनल की वजह से गड़बड़ी नहीं होती.

कनेक्टर इंस्टेंस बनाते समय, यह बताया जा सकता है कि कौनसी प्रोफ़ाइल टाइप आपकी 'प्राइमरी' है. उदाहरण के लिए, 'काम'. इससे आपको ये और भी विकल्प मिलते हैं:

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 एनोटेट किया गया मैथड होता है उन्हें क्रॉस प्रोफ़ाइल टाइप कहा जाता है.

क्रॉस प्रोफ़ाइल टाइप को लागू करने के लिए, यह ज़रूरी नहीं है कि वे जिस प्रोफ़ाइल पर काम कर रहे हैं वह प्रोफ़ाइल किसी खास प्रोफ़ाइल से जुड़ी हो. उन्हें अन्य तरीकों से कॉल करने की अनुमति है और आम तौर पर, वे एक ही प्रोफ़ाइल पर चल रहे हों. उनके पास सिर्फ़ अपनी प्रोफ़ाइल में मौजूद देश/इलाके की जानकारी का ऐक्सेस होगा.

क्रॉस प्रोफ़ाइल टाइप का उदाहरण:

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

क्लास का ऐनोटेशन

सबसे अच्छा एपीआई उपलब्ध कराने के लिए, आपको हर क्रॉस प्रोफ़ाइल टाइप के लिए कनेक्टर की जानकारी देनी होगी. जैसे:

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

यह ज़रूरी नहीं है, लेकिन इसका मतलब है कि जनरेट किया गया एपीआई, टाइप के हिसाब से ज़्यादा सटीक होगा और कंपाइल के समय की जांच करने के लिए ज़्यादा सख्त होगा.

इंटरफ़ेस

किसी इंटरफ़ेस पर मौजूद तरीकों को @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

    जनरेट की गई सेवा का नाम बदलने के लिए, serviceClassName= का इस्तेमाल करें. इस नाम का रेफ़रंस आपके AndroidManifest.xml में होना चाहिए.

  • primaryProfile

    प्राइमरी प्रोफ़ाइल तय करने के लिए, primaryProfile का इस्तेमाल करें.

  • availabilityRestrictions

    SDK टूल, कनेक्शन और प्रोफ़ाइल की उपलब्धता पर जो पाबंदियां लगाता है उन्हें बदलने के लिए, availabilityRestrictions का इस्तेमाल करें.

डिवाइस नीति नियंत्रक

अगर आपका ऐप्लिकेशन, डिवाइस नीति कंट्रोल करने वाला ऐप्लिकेशन है, तो आपको DeviceAdminReceiver का रेफ़रंस देने वाले DpcProfileBinder का एक इंस्टेंस बताना होगा.

अगर आपको अपना प्रोफ़ाइल कनेक्टर लागू करना है, तो:

@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 टूल, सिंक्रोनस (ब्लॉकिंग) कॉल की सुविधा देता है. हालांकि, ऐसा सिर्फ़ उन मामलों में किया जाता है जहां ऐसा करना ज़रूरी हो. हालांकि, इन कॉल का इस्तेमाल करने के कई नुकसान हैं. जैसे, कॉल को लंबे समय तक ब्लॉक किया जा सकता है. इसलिए, हमारा सुझाव है कि जब भी हो सके, सिंक्रोनस कॉल से बचें. असिंक्रोनस कॉल का इस्तेमाल करने के लिए, असिंक्रोनस कॉल देखें .

कनेक्शन के मालिक

अगर सिंक्रोनस कॉल का इस्तेमाल किया जा रहा है, तो आपको यह पक्का करना होगा कि क्रॉस प्रोफ़ाइल कॉल करने से पहले, कनेक्शन होल्डर रजिस्टर हो. ऐसा न करने पर, अपवाद दिखेगा. ज़्यादा जानकारी के लिए, कनेक्शन के मालिक देखें.

कनेक्शन होल्डर जोड़ने के लिए, किसी भी ऑब्जेक्ट के साथ ProfileConnector#addConnectionHolder(Object) को कॉल करें. हो सकता है कि यह ऑब्जेक्ट, क्रॉस-प्रोफ़ाइल कॉल कर रहा हो. इससे यह रिकॉर्ड हो जाएगा कि यह ऑब्जेक्ट कनेक्शन का इस्तेमाल कर रहा है और कनेक्शन बनाने की कोशिश करेगा. इसे सिंक्रोनस कॉल करने से पहले कॉल करना चाहिए. यह कॉल ब्लॉक नहीं करता है. इसलिए, हो सकता है कि कॉल करने के समय तक कनेक्शन तैयार न हो (या हो सकता है कि कनेक्शन न हो). ऐसे में, गड़बड़ी को ठीक करने का सामान्य तरीका लागू होता है.

अगर ProfileConnector#addConnectionHolder(Object) को कॉल करते समय, आपके पास अलग-अलग प्रोफ़ाइलों के लिए ज़रूरी अनुमतियां नहीं हैं या कनेक्ट करने के लिए कोई प्रोफ़ाइल उपलब्ध नहीं है, तो कोई गड़बड़ी नहीं दिखेगी. हालांकि, कनेक्ट किए गए कॉलबैक को कभी कॉल नहीं किया जाएगा. अगर अनुमति बाद में दी जाती है या दूसरी प्रोफ़ाइल उपलब्ध हो जाती है, तो कनेक्शन तब किया जाएगा और कॉलबैक तब किया जाएगा.

इसके अलावा, ProfileConnector#connect(Object) एक ब्लॉकिंग तरीका है, जो ऑब्जेक्ट को कनेक्शन होल्डर के तौर पर जोड़ेगा और कनेक्शन बनाएगा या UnavailableProfileException को थ्रो करेगा. इस तरीके को यूज़र इंटरफ़ेस (यूआई) थ्रेड से नहीं बुलाया जा सकता.

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) को दिखाता है या कॉलबैक पैरामीटर को स्वीकार करता है उसे नॉन-ब्लॉकिंग के तौर पर मार्क किया जाता है. अन्य सभी तरीकों को 'ब्लॉक किया गया' के तौर पर मार्क किया जाता है.

हमारा सुझाव है कि एसिंक्रोनस कॉल का इस्तेमाल करें. अगर आपको सिंक किए गए कॉल का इस्तेमाल करना है, तो सिंक किए गए कॉल देखें.

कॉलबैक

नॉन-ब्लॉकिंग कॉल का सबसे बुनियादी टाइप, वॉइड मेथड है. यह अपने एक पैरामीटर के तौर पर, एक इंटरफ़ेस स्वीकार करता है. इसमें नतीजे के साथ कॉल किए जाने वाले मेथड शामिल होते हैं. इन इंटरफ़ेस को 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
});

अगर इस इंटरफ़ेस में एक ऐसा तरीका है जिसमें शून्य या एक पैरामीटर का इस्तेमाल किया जाता है, तो इसका इस्तेमाल एक साथ कई प्रोफ़ाइलों को कॉल करने के लिए भी किया जा सकता है.

कॉलबैक का इस्तेमाल करके, जितनी चाहें उतनी वैल्यू पास की जा सकती हैं. हालांकि, कनेक्टिविटी सिर्फ़ पहली वैल्यू के लिए खुली रहेगी. ज़्यादा वैल्यू पाने के लिए, कनेक्शन को खुला रखने के बारे में जानने के लिए, कनेक्शन के मालिक देखें.

कॉलबैक के साथ सिंक करने वाले तरीके

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 टूल का इस्तेमाल करके कॉल करने पर, यह उसी तरह काम नहीं करेगा. इस बात की कोई गारंटी नहीं है कि "This prints third" प्रिंट होने से पहले, इंस्टॉल करने का तरीका कॉल किया गया होगा. एसडीके के ज़रिए एसिंक्रोनस के तौर पर मार्क किए गए किसी भी तरीके का इस्तेमाल करते समय, यह नहीं माना जाना चाहिए कि उस तरीके को कब कॉल किया जाएगा.

आसान कॉलबैक

"सिंपल कॉलबैक", कॉलबैक का ज़्यादा पाबंदी वाला वर्शन है. इससे अलग-अलग प्रोफ़ाइलों पर कॉल करते समय, अतिरिक्त सुविधाओं का इस्तेमाल किया जा सकता है. आसान इंटरफ़ेस में एक ही तरीका होना चाहिए, जिसमें शून्य या एक पैरामीटर हो सकता है.

@CrossProfileCallback एनोटेशन में simple=true बताकर, यह लागू किया जा सकता है कि कॉलबैक इंटरफ़ेस बना रहे.

आसान कॉलबैक का इस्तेमाल, .both(), .suppliers(), और अन्य तरीकों के साथ किया जा सकता है.

कनेक्शन के मालिक

कॉलबैक या फ़्यूचर का इस्तेमाल करके, असाइनोक्रोनस कॉल करते समय, कॉल करने पर कनेक्शन होल्डर जोड़ा जाएगा और अपवाद या वैल्यू पास होने पर हटा दिया जाएगा.

अगर आपको कॉलबैक का इस्तेमाल करके एक से ज़्यादा नतीजे चाहिए, तो आपको कॉलबैक को कनेक्शन होल्डर के तौर पर मैन्युअल तरीके से जोड़ना चाहिए:

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
}

अगर हम कॉलबैक या फ़्यूचर कॉल की मदद से कॉल करते हैं, तो नतीजा मिलने तक कनेक्शन खुला रहेगा. अगर हमें लगता है कि कोई नतीजा नहीं मिलेगा, तो हमें कनेक्शन होल्डर के तौर पर कॉलबैक या फ़्यूचर को हटा देना चाहिए:

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

ज़्यादा जानकारी के लिए, कनेक्शन के मालिक देखें.

फ़्यूचर्स

SDK में फ़्यूचर भी नेटिव तौर पर काम करते हैं. फ़िलहाल, ListenableFuture ही आने वाले समय के लिए इस्तेमाल किया जा सकने वाला टाइप है. हालांकि, पसंद के मुताबिक आने वाले समय के टाइप का इस्तेमाल किया जा सकता है. फ़्यूचर का इस्तेमाल करने के लिए, आपको सिर्फ़ किसी फ़्यूचर टाइप को क्रॉस प्रोफ़ाइल वाले तरीके के रिटर्न टाइप के तौर पर बताना होगा. इसके बाद, इसका इस्तेमाल सामान्य तरीके से किया जा सकता है.

इसमें कॉलबैक जैसी ही "असामान्य सुविधा" होती है. इसमें, फ़्यूचर वैल्यू (उदाहरण के लिए, 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 ट्रिगर हो जाएगा (या उसे फ़्यूचर में पास कर दिया जाएगा या असाइन किए गए कॉल के लिए गड़बड़ी का कॉलबैक भेज दिया जाएगा).

इससे बचने के लिए, डेवलपर #both() या #suppliers() का इस्तेमाल कर सकते हैं. साथ ही, नतीजों की सूची में मौजूद किसी भी संख्या की एंट्री को मैनेज करने के लिए अपना कोड लिख सकते हैं. अगर दूसरी प्रोफ़ाइल उपलब्ध नहीं है, तो यह संख्या 1 होगी और अगर उपलब्ध है, तो 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 {

}

इससे कॉन्फ़िगरेशन में इस्तेमाल किए गए सभी टाइप और कनेक्टर के लिए फ़ेक जनरेट होंगे.

अपने टेस्ट में उन फ़ेक इंस्टेंस बनाएं:

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

जांचे जा रहे कोड में नकली कनेक्टर और क्रॉस प्रोफ़ाइल क्लास को पास करें और फिर कॉल करें.

कॉल सही टारगेट पर भेजे जाएंगे. साथ ही, डिसकनेक्ट या उपलब्ध न होने वाली प्रोफ़ाइलों पर कॉल करने पर, अपवाद दिखाए जाएंगे.

उगने वाले पौधों का मौजूदा टाइप

यहां दिए गए टाइप के लिए, आपको कुछ भी करने की ज़रूरत नहीं है. इनका इस्तेमाल, सभी क्रॉस-प्रोफ़ाइल कॉल के लिए, आर्ग्युमेंट या रिटर्न टाइप के तौर पर किया जा सकता है.

  • प्राइमिटिव (byte, short, int, long, float, double, char, boolean),
  • बॉक्स किए गए प्राइमिटिव (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,
  • 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[]>> एक मान्य टाइप है.

फ़्यूचर्स

ये टाइप सिर्फ़ रिटर्न टाइप के तौर पर काम करते हैं:

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

कस्टम Parcelable रैपर

अगर आपका टाइप ऊपर दी गई सूची में नहीं है, तो पहले यह देखें कि क्या इसे android.os.Parcelable या java.io.Serializable में सही तरीके से लागू किया जा सकता है. अगर ऐसा है, तो आपके टाइप के लिए सहायता जोड़ने के लिए, पार्टिकल किए जा सकने वाले रैपर नहीं दिखेंगे.

आने वाले समय में इस्तेमाल होने वाले कस्टम रैपर

अगर आपको आने वाले समय में इस्तेमाल होने वाले ऐसे टाइप का इस्तेमाल करना है जो पिछली सूची में शामिल नहीं है, तो सहायता जोड़ने के लिए future wrapper देखें.

पार्स किए जा सकने वाले रैपर

पार्सल किए जा सकने वाले रैपर, SDK टूल के ऐसे टूल होते हैं जिनकी मदद से, पार्सल किए जा सकने वाले उन टाइप के लिए सहायता जोड़ी जाती है जिनमें बदलाव नहीं किया जा सकता. SDK टूल में कई टाइप के लिए रैपर शामिल होते हैं. हालांकि, अगर आपको जिस टाइप का इस्तेमाल करना है वह शामिल नहीं है, तो आपको खुद कोड लिखना होगा.

Parcelable Wrapper एक ऐसी क्लास है जिसे किसी दूसरी क्लास को रैप करने और उसे पैकेज करने के लिए डिज़ाइन किया गया है. यह तय किए गए स्टैटिक कॉन्ट्रैक्ट का पालन करता है और SDK के साथ रजिस्टर होता है. इसलिए, इसका इस्तेमाल किसी दिए गए टाइप को पार्सल किए जा सकने वाले टाइप में बदलने के लिए किया जा सकता है. साथ ही, उस टाइप को पार्सल किए जा सकने वाले टाइप से निकाला भी जा सकता है.

टिप्पणी

पार्सल की जा सकने वाली रैपर क्लास को @CustomParcelableWrapper के तौर पर एनोटेट किया जाना चाहिए. साथ ही, रैपर की गई क्लास को originalType के तौर पर दिखाया जाना चाहिए. उदाहरण के लिए:

@CustomParcelableWrapper(originalType=ImmutableList.class)

फ़ॉर्मैट

पार्सल किए जा सकने वाले रैपर को Parcelable को सही तरीके से लागू करना चाहिए. साथ ही, इसमें एक स्टैटिक W of(Bundler, BundlerType, T) तरीका होना चाहिए, जो रैपर किए गए टाइप को रैप करता है और एक नॉन-स्टैटिक T get() तरीका होना चाहिए, जो रैपर किए गए टाइप को दिखाता है.

एसडीके, इस तरह के डेटा टाइप के लिए आसानी से सहायता देने के लिए, इन तरीकों का इस्तेमाल करेगा.

Bundler

सामान्य टाइप (जैसे, सूचियां और मैप) को रैप करने की अनुमति देने के लिए, of तरीके को एक Bundler दिया जाता है. यह Bundler, Parcel में काम करने वाले सभी टाइप को पढ़ने (#readFromParcel का इस्तेमाल करके) और लिखने (#writeToParcel का इस्तेमाल करके) में सक्षम होता है. साथ ही, इसमें एक BundlerType भी होता है, जो लिखे जाने वाले एलान किए गए टाइप को दिखाता है.

Bundler और BundlerType इंस्टेंस, खुद पार्सल किए जा सकते हैं. साथ ही, इन्हें पार्सल किए जा सकने वाले रैपर को पार्सल करने के हिस्से के तौर पर लिखा जाना चाहिए, ताकि पार्सल किए जा सकने वाले रैपर को फिर से बनाते समय इसका इस्तेमाल किया जा सके.

अगर BundlerType किसी सामान्य टाइप को दिखाता है, तो .typeArguments() को कॉल करके टाइप वैरिएबल देखे जा सकते हैं. हर टाइप आर्ग्युमेंट, खुद एक BundlerType होता है.

उदाहरण के लिए, 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];
      }
    };
}

SDK टूल के साथ रजिस्टर करना

कस्टम पार्सल करने लायक रैपर बनाने के बाद, आपको उसे SDK टूल के साथ रजिस्टर करना होगा.

ऐसा करने के लिए, किसी कक्षा पर CustomProfileConnector एनोटेशन या CrossProfile एनोटेशन में parcelableWrappers={YourParcelableWrapper.class} डालें.

आने वाले समय में उपलब्ध होने वाले रैपर

फ़्यूचर रैपर की मदद से, एसडीके सभी प्रोफ़ाइलों में फ़्यूचर के लिए सहायता जोड़ता है. SDK टूल में डिफ़ॉल्ट रूप से ListenableFuture के लिए सहायता शामिल होती है. हालांकि, आने वाले समय में अन्य टाइप के लिए, सहायता को खुद जोड़ा जा सकता है.

फ़्यूचर रैपर एक ऐसी क्लास है जिसे किसी खास फ़्यूचर टाइप को रैप करने और उसे एसडीके के लिए उपलब्ध कराने के लिए डिज़ाइन किया गया है. यह तय किए गए स्टैटिक कॉन्ट्रैक्ट का पालन करता है. साथ ही, इसे SDK टूल के साथ रजिस्टर करना ज़रूरी है.

टिप्पणी

आने वाले समय में इस्तेमाल होने वाली रैपर क्लास को @CustomFutureWrapper के तौर पर एनोटेट किया जाना चाहिए. साथ ही, रैपर की क्लास को originalType के तौर पर दिखाया जाना चाहिए. उदाहरण के लिए:

@CustomFutureWrapper(originalType=SettableFuture.class)

फ़ॉर्मैट

आने वाले समय में इस्तेमाल होने वाले रैपर, com.google.android.enterprise.connectedapps.FutureWrapper तक एक्सटेंड होने चाहिए.

आने वाले समय में इस्तेमाल होने वाले रैपर में, स्टैटिक W create(Bundler, BundlerType) तरीका होना चाहिए, जो रैपर का इंस्टेंस बनाता है. साथ ही, इससे रैप किए गए फ़्यूचर टाइप का एक इंस्टेंस बन जाना चाहिए. यह वैल्यू, किसी ऐसे T getFuture() तरीके से वापस आनी चाहिए जो स्टैटिक न हो. onResult(E) और onException(Throwable) तरीकों को लागू करना ज़रूरी है, ताकि नतीजे या throwable को रैप किए गए फ़्यूचर फ़ंक्शन में पास किया जा सके.

आने वाले समय में इस्तेमाल होने वाले रैपर में भी स्टैटिक void writeFutureResult(Bundler, BundlerType, T, FutureResultWriter<E>) तरीका होना चाहिए. नतीजों के लिए, इसे आने वाले समय में पास किए गए के तौर पर रजिस्टर किया जाना चाहिए. साथ ही, नतीजा मिलने पर, resultWriter.onSuccess(value) को कॉल करें. अगर कोई अपवाद दिया गया है, तो resultWriter.onFailure(exception) को कॉल किया जाना चाहिए.

आखिर में, फ़्यूचर रैपर में स्टैटिक T<Map<Profile, E>> groupResults(Map<Profile, T<E>> results) तरीका भी होना चाहिए, जो प्रोफ़ाइल से फ़्यूचर में बदले गए मैप को, प्रोफ़ाइल से नतीजे में बदले गए मैप के फ़्यूचर में बदलता है. इस लॉजिक को आसान बनाने के लिए, CrossProfileCallbackMultiMerger का इस्तेमाल किया जा सकता है.

उदाहरण के लिए:

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

SDK टूल के साथ रजिस्टर करना

कस्टम फ़्यूचर रैपर बनाने के बाद, उसे SDK टूल के साथ रजिस्टर करना होगा, ताकि उसका इस्तेमाल किया जा सके.

ऐसा करने के लिए, किसी कक्षा पर CustomProfileConnector एनोटेशन या CrossProfile एनोटेशन में futureWrappers={YourFutureWrapper.class} डालें.

डायरेक्ट बूट मोड

अगर आपका ऐप्लिकेशन डायरेक्ट बूट मोड के साथ काम करता है, तो प्रोफ़ाइल अनलॉक होने से पहले, आपको क्रॉस-प्रोफ़ाइल कॉल करने पड़ सकते हैं. डिफ़ॉल्ट रूप से, 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 का इस्तेमाल करें.

इस बदलाव के बाद, आपको यह जानकारी मिलेगी कि दूसरी प्रोफ़ाइल उपलब्ध है या नहीं. साथ ही, दूसरी प्रोफ़ाइल अनलॉक न होने पर भी, उस पर कॉल किए जा सकेंगे. यह पक्का करना आपकी ज़िम्मेदारी है कि आपके कॉल सिर्फ़ डिवाइस के एन्क्रिप्ट (सुरक्षित) किए गए स्टोरेज को ऐक्सेस करें.