এক্সটার্নাল ডিভাইস কন্ট্রোল করা

Android 11 ও তার পরবর্তী ভার্সনে, 'কুইক অ্যাক্সেস ডিভাইস কন্ট্রোল' ফিচার ব্যবহারকারীদের ডিফল্ট লঞ্চার থেকে তিনটি ইন্টার‍্যাকশনের মধ্যে ব্যবহারকারীর অ্যাফর্ডেন্স থেকে লাইট, থার্মোস্ট্যাট ও ক্যামেরার মতো এক্সটার্নাল ডিভাইস দ্রুত দেখতে ও কন্ট্রোল করতে দেয়। কোন লঞ্চার ব্যবহার করা হবে তা ডিভাইসের OEM বেছে নেয়। ডিভাইস অ্যাগ্রিগেটর—যেমন, Google Home—এবং থার্ড-পার্টি ভেন্ডর অ্যাপ এই স্পেসে দেখানোর জন্য ডিভাইস প্রদান করতে পারে। এই পৃষ্ঠায় দেখানো হয়েছে যে কীভাবে এই স্পেসে ডিভাইস কন্ট্রোল দেখানো যায় এবং আপনার কন্ট্রোল অ্যাপের সাথে লিঙ্ক করা যায়।

ছবি ১. Android UI-তে ডিভাইস কন্ট্রোল স্পেস।

এই সহায়তা যোগ করতে, একটি ControlsProviderService তৈরি ও ঘোষণা করুন। আপনার অ্যাপে আগে থেকে নির্ধারিত কন্ট্রোলের ধরনের উপর ভিত্তি করে কন্ট্রোল তৈরি করুন এবং তারপরে এইসব কন্ট্রোলের জন্য প্রকাশক তৈরি করুন।

ইউজার ইন্টারফেস

ডিভাইস কন্ট্রোল বিকল্পের অধীনে ডিভাইসগুলি টেমপ্লেট করা উইজেট হিসেবে দেখানো হয়। নিচের ছবিতে যেমন দেখানো হয়েছে, পাঁচটি ডিভাইস কন্ট্রোল উইজেট উপলভ্য আছে:

ডিভাইস কন্ট্রোলের জন্য উইজেট টগল করুন
টগল করুন
স্লাইডার উইজেটের মাধ্যমে টগল করুন
স্লাইডার দিয়ে টগল করুন
ডিভাইস কন্ট্রোলের জন্য রেঞ্জ স্লাইডার উইজেট
রেঞ্জ (চালু বা বন্ধ করার জন্য টগল করা যাবে না)
স্টেটলেস টগল কন্ট্রোল উইজেট
স্টেটলেস টগল
তাপমাত্রা প্যানেল উইজেট বন্ধ অবস্থায় আছে
তাপমাত্রা প্যানেল (বন্ধ)
ছবি ২. টেমপ্লেট করা উইজেটের কালেকশন।

উইজেট টাচ করে ধরে রাখলে, আরও ভালভাবে কন্ট্রোল করার জন্য আপনাকে অ্যাপে নিয়ে যাওয়া হবে। আপনি প্রতিটি উইজেটের আইকন ও রঙ কাস্টমাইজ করতে পারবেন, তবে সেরা ব্যবহারকারীর অভিজ্ঞতা পেতে, ডিফল্ট সেট ডিভাইসের সাথে ম্যাচ করলে ডিফল্ট আইকন ও রঙ ব্যবহার করুন।

তাপমাত্রা প্যানেল উইজেট খোলা অবস্থায় আছে
ছবি ৩. তাপমাত্রা প্যানেল উইজেট খুলুন।

পরিষেবা তৈরি করুন

এই বিভাগে দেখানো হয়েছে কীভাবে ControlsProviderService তৈরি করতে হয়। এই পরিষেবা Android সিস্টেম UI-কে জানায় যে আপনার অ্যাপে ডিভাইস কন্ট্রোল আছে যা Android UI-এর ডিভাইস কন্ট্রোল অংশে দেখাতে হবে।

ControlsProviderService API ধরে নেয় যে আপনি রিঅ্যাক্টিভ স্ট্রিম সম্পর্কে জানেন, যেমনটি রিঅ্যাক্টিভ স্ট্রিম GitHub প্রোজেক্টে সংজ্ঞায়িত করা হয়েছে এবং Java 9 Flow ইন্টারফেসে প্রয়োগ করা হয়েছে। API নিম্নলিখিত ধারণার উপর ভিত্তি করে তৈরি করা হয়েছে:

  • প্রকাশক: আপনার অ্যাপ্লিকেশন হল প্রকাশক।
  • সাবস্ক্রাইবার: সিস্টেম UI হল সাবস্ক্রাইবার এবং এটি প্রকাশকের কাছ থেকে একাধিক কন্ট্রোলের অনুরোধ করতে পারে।
  • সাবস্ক্রিপশন: যে সময়সীমার মধ্যে প্রকাশক সিস্টেম UI-তে আপডেট পাঠাতে পারেন। প্রকাশক বা সাবস্ক্রাইবার, যেকোনও একজন এই উইন্ডো বন্ধ করতে পারবেন।

পরিষেবা ঘোষণা করুন

আপনার অ্যাপকে অবশ্যই একটি পরিষেবা ঘোষণা করতে হবে—যেমন MyCustomControlService—এর অ্যাপ ম্যানিফেস্টে।

পরিষেবাতে অবশ্যই ControlsProviderService-এর জন্য একটি ইনটেন্ট ফিল্টার অন্তর্ভুক্ত থাকতে হবে। এই ফিল্টার অ্যাপ্লিকেশনগুলিকে সিস্টেম UI-তে কন্ট্রোল যোগ করার অনুমতি দেয়।

এছাড়াও, আপনার একটি label প্রয়োজন যা সিস্টেম UI-এর কন্ট্রোলে দেখানো হয়।

নিম্নলিখিত উদাহরণ থেকে কীভাবে কোনও পরিষেবা ঘোষণা করতে হয় তা জানুন:

<service
    android:name="MyCustomControlService"
    android:label="My Custom Controls"
    android:permission="android.permission.BIND_CONTROLS"
    android:exported="true"
    >
    <intent-filter>
      <action android:name="android.service.controls.ControlsProviderService" />
    </intent-filter>
</service>

এরপর, MyCustomControlService.kt নামের একটি নতুন Kotlin ফাইল তৈরি করুন এবং এটি ControlsProviderService-এ এক্সটেন্ড করুন:

class MyCustomControlService : ControlsProviderService() {
    // ...
}

সঠিক কন্ট্রোলের ধরন বেছে নিন

API কন্ট্রোল তৈরি করার জন্য বিল্ডার পদ্ধতি প্রদান করে। বিল্ডার পপুলেট করতে, আপনি যে ডিভাইস কন্ট্রোল করতে চান এবং ব্যবহারকারী কীভাবে এটির সাথে ইন্টার‍্যাক্ট করেন তা নির্ধারণ করুন। নিম্নলিখিত ধাপগুলি অনুসরণ করুন:

  1. কন্ট্রোলটি যে ধরনের ডিভাইসকে প্রতিনিধিত্ব করে সেটি বেছে নিন। DeviceTypes ক্লাস হল কাজ করে এমন সব ডিভাইসের তালিকা। UI-তে ডিভাইসের আইকন ও রঙ নির্ধারণ করতে এই ধরন ব্যবহার করা হয়।
  2. ব্যবহারকারী নিজে ইন্টার‌্যাক্ট করতে পারেন এমন নাম, ডিভাইসের লোকেশন—যেমন, রান্নাঘর—এবং কন্ট্রোলের সাথে যুক্ত অন্যান্য UI টেক্সচুয়াল এলিমেন্ট নির্ধারণ করুন।
  3. ব্যবহারকারীর ইন্টার‍্যাকশনকে সহায়তা করার জন্য সেরা টেমপ্লেট বেছে নিন। অ্যাপ্লিকেশন থেকে কন্ট্রোলকে একটি ControlTemplate অ্যাসাইন করা হয়। এই টেমপ্লেট সরাসরি ব্যবহারকারীকে কন্ট্রোল স্টেট এবং উপলভ্য ইনপুট পদ্ধতি দেখায়—অর্থাৎ, ControlAction। নিচের টেবিলে কিছু উপলভ্য টেমপ্লেট এবং সেগুলি যেসব অ্যাকশন সমর্থন করে তার রূপরেখা দেওয়া হয়েছে:
টেমপ্লেট অ্যাকশন বিবরণ
ControlTemplate.getNoTemplateObject() None কন্ট্রোল সম্পর্কে তথ্য জানাতে অ্যাপ্লিকেশন এটি ব্যবহার করতে পারে, কিন্তু ব্যবহারকারী এটির সাথে ইন্টার‍্যাক্ট করতে পারবেন না।
ToggleTemplate BooleanAction এমন একটি কন্ট্রোলকে দেখায় যা চালু ও বন্ধ করা স্টেটের মধ্যে পরিবর্তন করা যায়। BooleanAction অবজেক্টে এমন একটি ফিল্ড থাকে যা ব্যবহারকারী কন্ট্রোল ট্যাপ করলে অনুরোধ করা নতুন অবস্থা দেখানোর জন্য পরিবর্তিত হয়।
RangeTemplate FloatAction নির্দিষ্ট করা সর্বনিম্ন, সর্বোচ্চ ও স্টেপ ভ্যালু সহ স্লাইডার উইজেট দেখায়। ব্যবহারকারী স্লাইডারের সাথে ইন্টার‍্যাক্ট করলে, আপডেট করা ভ্যালু সহ অ্যাপ্লিকেশনে নতুন FloatAction অবজেক্ট পাঠান।
ToggleRangeTemplate BooleanAction, FloatAction এই টেমপ্লেটটি ToggleTemplate এবং RangeTemplate-এর কম্বিনেশন। এটি টাচ ইভেন্টের পাশাপাশি স্লাইডারও সাপোর্ট করে, যেমন ডিম করা যায় এমন লাইট কন্ট্রোল করা।
TemperatureControlTemplate ModeAction, BooleanAction, FloatAction আগের অ্যাকশনগুলি এনক্যাপসুলেট করার পাশাপাশি, এই টেমপ্লেট ব্যবহারকারীকে হিট, কুল, হিট/কুল, ইকো বা অফের মতো মোড সেট করতে দেয়।
StatelessTemplate CommandAction এমন কন্ট্রোল নির্দেশ করতে ব্যবহার করা হয় যা টাচ করার সুবিধা প্রদান করে কিন্তু যার অবস্থা নির্ধারণ করা যায় না, যেমন IR টেলিভিশন রিমোট। আপনি এই টেমপ্লেট ব্যবহার করে রুটিন বা ম্যাক্রো নির্ধারণ করতে পারেন, যা কন্ট্রোল ও স্ট্যাটাস পরিবর্তনের একটি সমষ্টি।

এই তথ্য ব্যবহার করে আপনি কন্ট্রোল তৈরি করতে পারবেন:

  • কন্ট্রোলের স্ট্যাটাস অজানা থাকলে, Control.StatelessBuilder বিল্ডার ক্লাস ব্যবহার করুন।
  • কন্ট্রোলের স্ট্যাটাস জানা থাকলে Control.StatefulBuilder বিল্ডার ক্লাস ব্যবহার করুন।

যেমন, স্মার্ট লাইট বাল্ব ও থার্মোস্ট্যাট কন্ট্রোল করতে, আপনার MyCustomControlService-এ নিম্নলিখিত কনস্ট্যান্ট যোগ করুন:

private const val LIGHT_ID = 1234
private const val LIGHT_TITLE = "My fancy light"
private const val LIGHT_TYPE = DeviceTypes.TYPE_LIGHT
private const val THERMOSTAT_ID = 5678
private const val THERMOSTAT_TITLE = "My fancy thermostat"
private const val THERMOSTAT_TYPE = DeviceTypes.TYPE_THERMOSTAT

class MyCustomControlService : ControlsProviderService() {
    // ...
}

কন্ট্রোলের জন্য প্রকাশক তৈরি করুন

কন্ট্রোল তৈরি করার পরে, এটির জন্য একজন প্রকাশক প্রয়োজন। প্রকাশক সিস্টেম UI-কে কন্ট্রোলের অস্তিত্ব সম্পর্কে জানায়। ControlsProviderService ক্লাসে দুটি প্রকাশক পদ্ধতি আছে যা আপনার অ্যাপ্লিকেশন কোডে ওভাররাইড করতে হবে:

  • createPublisherForAllAvailable: আপনার অ্যাপে উপলভ্য সব কন্ট্রোলের জন্য একটি Publisher তৈরি করে। এই প্রকাশকের জন্য Control অবজেক্ট তৈরি করতে Control.StatelessBuilder ব্যবহার করুন।
  • createPublisherFor: প্রদত্ত কন্ট্রোলের তালিকার জন্য একটি Publisher তৈরি করে, যেমন তাদের স্ট্রিং শনাক্তকারী দ্বারা চিহ্নিত করা হয়। এই Control অবজেক্ট তৈরি করতে Control.StatefulBuilder ব্যবহার করুন, কারণ প্রকাশককে প্রতিটি কন্ট্রোলের জন্য একটি করে স্টেট অ্যাসাইন করতে হবে।

প্রকাশক তৈরি করুন

আপনার অ্যাপ যখন প্রথমবার সিস্টেম UI-তে কন্ট্রোল প্রকাশ করে, তখন অ্যাপ প্রতিটি কন্ট্রোলের অবস্থা সম্পর্কে জানে না। স্ট্যাটাস পেতে সময় লাগতে পারে, কারণ ডিভাইস-প্রোভাইডারের নেটওয়ার্কে অনেক হপ থাকে। সিস্টেমে উপলভ্য কন্ট্রোল বিজ্ঞাপন করতে createPublisherForAllAvailable পদ্ধতি ব্যবহার করুন। এই পদ্ধতিতে Control.StatelessBuilder বিল্ডার ক্লাস ব্যবহার করা হয়, কারণ প্রতিটি কন্ট্রোলের স্ট্যাটাস অজানা।

Android UI-তে কন্ট্রোল দেখা গেলে, ব্যবহারকারীরা পছন্দের কন্ট্রোল বেছে নিতে পারবেন।

ControlsProviderService তৈরি করতে Kotlin coroutines ব্যবহার করতে, আপনার build.gradle-এ একটি নতুন ডিপেন্ডেন্সি যোগ করুন:

গ্ৰুভি

dependencies {
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4"
}

Kotlin

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4")
}

আপনার Gradle ফাইল সিঙ্ক করার পরে, createPublisherForAllAvailable প্রয়োগ করতে আপনার Service-এ নিম্নলিখিত স্নিপেট যোগ করুন:

class MyCustomControlService : ControlsProviderService() {

    override fun createPublisherForAllAvailable(): Flow.Publisher<Control> =
        flowPublish {
            send(createStatelessControl(LIGHT_ID, LIGHT_TITLE, LIGHT_TYPE))
            send(createStatelessControl(THERMOSTAT_ID, THERMOSTAT_TITLE, THERMOSTAT_TYPE))
        }

    private fun createStatelessControl(id: Int, title: String, type: Int): Control {
        val intent = Intent(this, MainActivity::class.java)
            .putExtra(EXTRA_MESSAGE, title)
            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        val action = PendingIntent.getActivity(
            this,
            id,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
        )

        return Control.StatelessBuilder(id.toString(), action)
            .setTitle(title)
            .setDeviceType(type)
            .build()
    }

    override fun createPublisherFor(controlIds: List<String>): Flow.Publisher<Control> {
        TODO()
    }

    override fun performControlAction(
        controlId: String,
        action: ControlAction,
        consumer: Consumer<Int>,
    ) {
        TODO()
    }
}

সিস্টেম মেনু নিচের দিকে সোয়াইপ করে ডিভাইস কন্ট্রোল বোতামটি খুঁজে নিন, যা ছবি ৪-এ দেখানো হয়েছে:

ডিভাইস কন্ট্রোলের জন্য সিস্টেম ইউজার ইন্টারফেস
ছবি ৪. সিস্টেম মেনুতে ডিভাইস কন্ট্রোল।

ডিভাইস কন্ট্রোল বিকল্পে ট্যাপ করলে, দ্বিতীয় স্ক্রিনে নেভিগেট করে যেখানে আপনি নিজের অ্যাপ বেছে নিতে পারবেন। অ্যাপ বেছে নেওয়ার পরে, আগের স্নিপেট কীভাবে আপনার নতুন কন্ট্রোল দেখানো কাস্টম সিস্টেম মেনু তৈরি করে তা দেখতে পাবেন, যেমনটি ৫ নম্বর ছবিতে দেখানো হয়েছে:

সিস্টেম মেনুতে আলো ও থার্মোস্ট্যাট কন্ট্রোল দেখানো হচ্ছে
ছবি ৫. লাইট ও থার্মোস্ট্যাট কন্ট্রোল যোগ করতে হবে।

এখন, createPublisherFor পদ্ধতি প্রয়োগ করুন, আপনার Service-এ নিম্নলিখিতগুলি যোগ করুন:

private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.IO + job)
private val controlFlows = mutableMapOf<String, MutableSharedFlow<Control>>()

private var toggleState = false
private var rangeState = 18f

override fun createPublisherFor(controlIds: List<String>): Flow.Publisher<Control> {
    val flow = MutableSharedFlow<Control>(replay = 2, extraBufferCapacity = 2)

    controlIds.forEach { controlFlows[it] = flow }

    scope.launch {
        delay(1000) // Retrieving the toggle state.
        flow.tryEmit(createLight())

        delay(1000) // Retrieving the range state.
        flow.tryEmit(createThermostat())
    }
    return flow.asPublisher()
}

private fun createLight() = createStatefulControl(
    LIGHT_ID,
    LIGHT_TITLE,
    LIGHT_TYPE,
    toggleState,
    ToggleTemplate(
        LIGHT_ID.toString(),
        ControlButton(
            toggleState,
            toggleState.toString().uppercase(Locale.getDefault()),
        ),
    ),
)

private fun createThermostat() = createStatefulControl(
    THERMOSTAT_ID,
    THERMOSTAT_TITLE,
    THERMOSTAT_TYPE,
    rangeState,
    RangeTemplate(
        THERMOSTAT_ID.toString(),
        15f,
        25f,
        rangeState,
        0.1f,
        "%1.1f",
    ),
)

private fun <T> createStatefulControl(
    id: Int,
    title: String,
    type: Int,
    state: T,
    template: ControlTemplate,
): Control {
    val intent = Intent(this, MainActivity::class.java)
        .putExtra(EXTRA_MESSAGE, "$title $state")
        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    val action = PendingIntent.getActivity(
        this,
        id,
        intent,
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
    )

    return Control.StatefulBuilder(id.toString(), action)
        .setTitle(title)
        .setDeviceType(type)
        .setStatus(Control.STATUS_OK)
        .setControlTemplate(template)
        .build()
}

override fun onDestroy() {
    super.onDestroy()
    job.cancel()
}

এই উদাহরণে, createPublisherFor পদ্ধতিতে আপনার অ্যাপকে যা করতে হবে তার একটি নকল বাস্তবায়ন রয়েছে: স্ট্যাটাস ফিরিয়ে আনতে আপনার ডিভাইসের সাথে যোগাযোগ করুন এবং সেই স্ট্যাটাস সিস্টেমে পাঠান।

createPublisherFor পদ্ধতিটি Kotlin coroutines এবং ফ্লো ব্যবহার করে প্রয়োজনীয় রিঅ্যাক্টিভ স্ট্রিম API-এর চাহিদা পূরণ করে, যা নিম্নলিখিত কাজগুলি করে:

  1. Flow তৈরি করে।
  2. এক সেকেন্ডের জন্য অপেক্ষা করে।
  3. স্মার্ট লাইটের স্ট্যাটাস তৈরি করে ও দেখায়।
  4. আরও এক সেকেন্ড অপেক্ষা করে।
  5. থার্মোস্ট্যাটের স্ট্যাটাস তৈরি ও এমিট করে।

অ্যাকশন ম্যানেজ করা

ব্যবহারকারী প্রকাশিত কন্ট্রোলের সাথে ইন্টার‍্যাক্ট করলে performControlAction পদ্ধতি সিগন্যাল দেয়। ControlAction-এর ধরন অনুযায়ী অ্যাকশন নির্ধারণ করা হয়। প্রদত্ত কন্ট্রোলের জন্য উপযুক্ত অ্যাকশন নিন এবং তারপর Android UI-তে ডিভাইসের স্ট্যাটাস আপডেট করুন।

উদাহরণটি সম্পূর্ণ করতে, আপনার Service-এ নিম্নলিখিতগুলি যোগ করুন:

override fun performControlAction(
    controlId: String,
    action: ControlAction,
    consumer: Consumer<Int>,
) {
    controlFlows[controlId]?.let { flow ->
        when (controlId) {
            LIGHT_ID.toString() -> {
                consumer.accept(ControlAction.RESPONSE_OK)
                if (action is BooleanAction) toggleState = action.newState
                flow.tryEmit(createLight())
            }
            THERMOSTAT_ID.toString() -> {
                consumer.accept(ControlAction.RESPONSE_OK)
                if (action is FloatAction) rangeState = action.newValue
                flow.tryEmit(createThermostat())
            }
            else -> consumer.accept(ControlAction.RESPONSE_FAIL)
        }
    } ?: consumer.accept(ControlAction.RESPONSE_FAIL)
}

অ্যাপটি চালান, ডিভাইস কন্ট্রোল মেনু অ্যাক্সেস করুন এবং আপনার লাইট ও থার্মোস্ট্যাট কন্ট্রোল দেখুন।

লাইট ও থার্মোস্ট্যাট দেখানোর কন্ট্রোল
ছবি ৬. লাইট ও থার্মোস্ট্যাট কন্ট্রোল।