বাহ্যিক ডিভাইস নিয়ন্ত্রণ করুন

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

চিত্র ১. অ্যান্ড্রয়েড ইউআই-তে ডিভাইস নিয়ন্ত্রণের স্থান।

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

ব্যবহারকারী ইন্টারফেস

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

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

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

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

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

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

ControlsProviderService API-টি ব্যবহারের জন্য Reactive Streams 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() {
    // ...
}

সঠিক নিয়ন্ত্রণ প্রকার নির্বাচন করুন

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

  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 এমন একটি কন্ট্রোল বোঝাতে ব্যবহৃত হয় যা স্পর্শের সুবিধা দেয় কিন্তু যার অবস্থা নির্ধারণ করা যায় না, যেমন একটি আইআর টেলিভিশন রিমোট। আপনি এই টেমপ্লেটটি ব্যবহার করে একটি রুটিন বা ম্যাক্রো সংজ্ঞায়িত করতে পারেন, যা হলো কন্ট্রোল এবং অবস্থার পরিবর্তনের একটি সমষ্টি।

এই তথ্য দিয়ে আপনি নিয়ন্ত্রণটি তৈরি করতে পারেন:

  • যখন কন্ট্রোলের অবস্থা অজানা থাকে, তখন 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 ব্যবহার করুন, কারণ Publisher-কে অবশ্যই প্রতিটি কন্ট্রোলে একটি স্টেট নির্ধারণ করতে হয়।

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

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

অ্যান্ড্রয়েড ইউআই-তে কন্ট্রোলগুলো দেখা গেলে, ব্যবহারকারীরা তাদের পছন্দের কন্ট্রোলগুলো বেছে নিতে পারবেন।

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

গ্রুভি

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

কোটলিন

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

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

ডিভাইস নিয়ন্ত্রণের জন্য সিস্টেম ব্যবহারকারী ইন্টারফেস
চিত্র ৪. সিস্টেম মেনুতে ডিভাইস নিয়ন্ত্রণসমূহ।

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

সিস্টেম মেনুতে একটি আলো এবং থার্মোস্ট্যাট নিয়ন্ত্রণ প্রদর্শিত হচ্ছে
চিত্র ৫। যোগ করার জন্য লাইট ও থার্মোস্ট্যাট কন্ট্রোল।

এখন, আপনার Service নিম্নলিখিত বিষয়গুলো যোগ করে createPublisherFor মেথডটি ইমপ্লিমেন্ট করুন:

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 মেথডটি নিম্নলিখিত কাজগুলো করার মাধ্যমে কোটলিন কোরাউটিন এবং ফ্লো ব্যবহার করে প্রয়োজনীয় রিঅ্যাক্টিভ স্ট্রিমস এপিআই পূরণ করে:

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

ক্রিয়া পরিচালনা করুন

যখন ব্যবহারকারী কোনো প্রকাশিত কন্ট্রোলের সাথে ইন্টারঅ্যাক্ট করে, তখন performControlAction মেথডটি সংকেত দেয়। প্রেরিত ControlAction এর ধরনটি অ্যাকশন নির্ধারণ করে। প্রদত্ত কন্ট্রোলের জন্য উপযুক্ত অ্যাকশনটি সম্পাদন করুন এবং তারপর অ্যান্ড্রয়েড 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)
}

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

আলো এবং থার্মোস্ট্যাট প্রদর্শনকারী কন্ট্রোল।
চিত্র ৬। আলো ও থার্মোস্ট্যাট নিয়ন্ত্রণ ব্যবস্থা।
,

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

চিত্র ১. অ্যান্ড্রয়েড ইউআই-তে ডিভাইস নিয়ন্ত্রণের স্থান।

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

ব্যবহারকারী ইন্টারফেস

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

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

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

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

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

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

ControlsProviderService API-টি ব্যবহারের জন্য Reactive Streams 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() {
    // ...
}

সঠিক নিয়ন্ত্রণ প্রকার নির্বাচন করুন

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

  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 এমন একটি কন্ট্রোল বোঝাতে ব্যবহৃত হয় যা স্পর্শের সুবিধা দেয় কিন্তু যার অবস্থা নির্ধারণ করা যায় না, যেমন একটি আইআর টেলিভিশন রিমোট। আপনি এই টেমপ্লেটটি ব্যবহার করে একটি রুটিন বা ম্যাক্রো সংজ্ঞায়িত করতে পারেন, যা হলো কন্ট্রোল এবং অবস্থার পরিবর্তনের একটি সমষ্টি।

এই তথ্য দিয়ে আপনি নিয়ন্ত্রণটি তৈরি করতে পারেন:

  • যখন কন্ট্রোলের অবস্থা অজানা থাকে, তখন 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 ব্যবহার করুন, কারণ Publisher-কে অবশ্যই প্রতিটি কন্ট্রোলে একটি স্টেট নির্ধারণ করতে হয়।

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

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

অ্যান্ড্রয়েড ইউআই-তে কন্ট্রোলগুলো দেখা গেলে, ব্যবহারকারীরা তাদের পছন্দের কন্ট্রোলগুলো বেছে নিতে পারবেন।

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

গ্রুভি

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

কোটলিন

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

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

ডিভাইস নিয়ন্ত্রণের জন্য সিস্টেম ব্যবহারকারী ইন্টারফেস
চিত্র ৪. সিস্টেম মেনুতে ডিভাইস নিয়ন্ত্রণসমূহ।

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

সিস্টেম মেনুতে একটি আলো এবং থার্মোস্ট্যাট নিয়ন্ত্রণ প্রদর্শিত হচ্ছে
চিত্র ৫। যোগ করার জন্য লাইট ও থার্মোস্ট্যাট কন্ট্রোল।

এখন, আপনার Service নিম্নলিখিত বিষয়গুলো যোগ করে createPublisherFor মেথডটি ইমপ্লিমেন্ট করুন:

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 মেথডটি নিম্নলিখিত কাজগুলো করার মাধ্যমে কোটলিন কোরাউটিন এবং ফ্লো ব্যবহার করে প্রয়োজনীয় রিঅ্যাক্টিভ স্ট্রিমস এপিআই পূরণ করে:

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

ক্রিয়া পরিচালনা করুন

যখন ব্যবহারকারী কোনো প্রকাশিত কন্ট্রোলের সাথে ইন্টারঅ্যাক্ট করে, তখন performControlAction মেথডটি সংকেত দেয়। প্রেরিত ControlAction এর ধরনটি অ্যাকশন নির্ধারণ করে। প্রদত্ত কন্ট্রোলের জন্য উপযুক্ত অ্যাকশনটি সম্পাদন করুন এবং তারপর অ্যান্ড্রয়েড 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)
}

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

আলো এবং থার্মোস্ট্যাট প্রদর্শনকারী কন্ট্রোল।
চিত্র ৬। আলো ও থার্মোস্ট্যাট নিয়ন্ত্রণ ব্যবস্থা।