توسيع نطاق إشعارات المراسلة لتشمل Android Auto

description: Extend messaging notifications to Android Auto using the MessagingStyle API. keywords_public: Android Auto,messaging notifications,MessagingStyle,NotificationCompat.Action,communication apps,car development,IntentService,RemoteInput,PendingIntent

يمكن للتطبيقات التي تتيح المراسلة توسيع نطاق إشعارات المراسلة الخاصة بها لتسمح لتطبيق Android Auto باستهلاكها عند تشغيله. يعرض Android Auto هذه الإشعارات ويسمح للمستخدمين بقراءة الرسائل والردّ عليها في واجهة متّسقة لا تشتّت الانتباه. عند استخدام واجهة برمجة التطبيقات MessagingStyle، ستحصل على إشعارات رسائل محسّنة لجميع أجهزة Android، بما في ذلك Android Auto. تشمل التحسينات واجهة مستخدم متخصّصة لإشعارات الرسائل، ورسومًا متحركة محسّنة، ودعمًا للصور المضمّنة.

يوضّح لك هذا الدليل كيفية توسيع نطاق تطبيق يعرض الرسائل للمستخدم ويتلقّى ردوده، مثل تطبيق محادثة، لنقل عرض الرسائل وتلقّي الردود إلى Android Auto. من خلال هذا التكامل، لا يمكن للمستخدمين الاطّلاع إلا على سجلّ الرسائل من الإشعارات التي تم تلقّيها أثناء جلسة Android Auto النشطة. لعرض الرسائل الواردة قبل بدء جلسة Android Auto النشطة ، يمكنك إنشاء تجربة مراسلة مستندة إلى نموذج.

للحصول على إرشادات التصميم ذات الصلة، يُرجى الاطّلاع على مقالة تطبيقات التواصل في مركز التصميم للسيارات.

البدء

لتوفير خدمة المراسلة لتطبيق Android Auto، يجب أن يصرّح تطبيقك بتوافقه مع Android Auto في ملف البيان وأن يكون قادرًا على تنفيذ ما يلي:

المفاهيم والكائنات

قبل البدء في تصميم تطبيقك، من المفيد فهم كيفية تعامل Android Auto مع المراسلة.

يُطلق على جزء الاتصال الفردي اسم رسالة ويتم تمثيله من خلال الفئة MessagingStyle.Message. تحتوي الرسالة على المُرسِل ومحتوى الرسالة ووقت إرسالها.

يُطلق على التواصل بين المستخدمين اسم محادثة ويتم تمثيله من خلال كائن MessagingStyle. تحتوي المحادثة، أو MessagingStyle، على عنوان والرسائل وما إذا كانت المحادثة بين مجموعة من المستخدمين.

لإعلام المستخدمين بالتعديلات التي تطرأ على محادثة، مثل رسالة جديدة، تنشر التطبيقات Notification على نظام Android. يستخدم هذا Notification كائن MessagingStyle لعرض واجهة مستخدم خاصة بالمراسلة في لوحة الإشعارات. يمرر نظام Android الأساسي أيضًا هذا Notification إلى Android Auto، و يتم استخراج MessagingStyle واستخدامه لنشر إشعار على شاشة السيارة.

يطلب Android Auto أيضًا من التطبيقات إضافة Action كائنات إلى Notification للسماح للمستخدم بالردّ على رسالة أو وضع علامة "مقروءة" عليها مباشرةً من شاشة السيارة.

باختصار، يتم تمثيل محادثة واحدة من خلال كائن Notification الذي تم تنسيقه باستخدام كائن MessagingStyle. يحتوي MessagingStyle على جميع الرسائل ضمن تلك المحادثة في كائن واحد أو أكثر من كائنات MessagingStyle.Message. ولكي يكون التطبيق متوافقًا مع Android Auto، يجب أن يرفق كائنات الردّ و وضع علامة "مقروءة" Action بـ Notification.

مسار المراسلة

يصف هذا القسم مسارًا نموذجيًا للمراسلة بين تطبيقك وAndroid Auto.

  1. يتلقّى تطبيقك رسالة.
  2. ينشئ تطبيقك إشعارًا MessagingStyle يتضمّن كائنات Action للردّ ووضع علامة "مقروءة".
  3. يتلقّى Android Auto حدث "إشعار جديد" من نظام Android ويجد MessagingStyle وAction للردّ وAction لوضع علامة "مقروءة".
  4. ينشئ Android Auto إشعارًا ويعرضه في السيارة.
  5. إذا نقر المستخدم على الإشعار على شاشة السيارة، سيؤدي Android Auto إلى تشغيل Action لوضع علامة "مقروءة".
    • في الخلفية، يجب أن يتعامل تطبيقك مع حدث وضع علامة "مقروءة".
  6. إذا ردّ المستخدم على الإشعار باستخدام صوته، يضع Android Auto نسخة مكتوبة من ردّ المستخدم في Action للردّ ثم يشغّله.
    • في الخلفية، يجب أن يتعامل تطبيقك مع حدث الردّ هذا.

الافتراضات الأولية

لا ترشدك هذه الصفحة إلى كيفية إنشاء تطبيق مراسلة كامل. يتضمّن نموذج الرمز البرمجي التالي بعض العناصر التي يحتاجها تطبيقك قبل البدء في إتاحة المراسلة باستخدام Android Auto:

data class YourAppConversation(
        val id: Int,
        val title: String,
        val recipients: MutableList<YourAppUser>,
        val icon: Bitmap) {
    companion object {
        /** Fetches [YourAppConversation] by its [id]. */
        fun getById(id: Int): YourAppConversation = // ...
    }

    /** Replies to this conversation with the given [message]. */
    fun reply(message: String) {}

    /** Marks this conversation as read. */
    fun markAsRead() {}

    /** Retrieves all unread messages from this conversation. */
    fun getUnreadMessages(): List<YourAppMessage> { return /* ... */ }
}
data class YourAppUser(val id: Int, val name: String, val icon: Uri)
data class YourAppMessage(
    val id: Int,
    val sender: YourAppUser,
    val body: String,
    val timeReceived: Long)

الإعلان عن التوافق مع Android Auto

عندما يتلقّى Android Auto إشعارًا من تطبيق مراسلة، يتحقّق من أنّ التطبيق قد أعلن عن توافقه مع Android Auto. لإتاحة هذا التوافق، أضِف الإدخال التالي في ملف بيان تطبيقك:

<application>
    ...
    <meta-data
        android:name="com.google.android.gms.car.application"
        android:resource="@xml/automotive_app_desc"/>
    ...
</application>

يشير إدخال ملف البيان هذا إلى ملف XML آخر، وهو automotive_app_desc.xml، الذي عليك إنشاؤه في دليل res/xml لوحدة تطبيقك. في automotive_app_desc.xml، أعلن عن إمكانات Android Auto التي يتيحها تطبيقك. للإعلان عن التوافق مع الإشعارات، أضِف ما يلي:

<automotiveApp>
    <uses name="notification" />
</automotiveApp>

إذا كان من الممكن ضبط تطبيقك كمعالج SMS تلقائي، احرص على تضمين العنصر التالي.<uses> إذا لم تفعل ذلك، سيستخدم Android Auto المعالج التلقائي المضمّن للتعامل مع رسائل SMS/MMS الواردة عندما يتم ضبط تطبيقك كمعالج SMS تلقائي، ما قد يؤدي إلى إشعارات مكرّرة.

<automotiveApp>
    ...
    <uses name="sms" />
</automotiveApp>

استيراد مكتبة AndroidX الأساسية

يتطلّب إنشاء الإشعارات لاستخدامها مع Android Auto مكتبة AndroidX الأساسية. استورِد المكتبة إلى مشروعك على النحو التالي:

  1. في ملف build.gradle على المستوى الأعلى، أضِف تبعية على مستودع Maven من Google، كما هو موضّح في المثال التالي:

أنيق

allprojects {
    repositories {
        google()
    }
}

Kotlin

allprojects {
    repositories {
        google()
    }
}
  1. في ملف build.gradle لوحدة تطبيقك، أضِف تبعية مكتبة AndroidX الأساسية ، كما هو موضّح في المثال التالي:

أنيق

dependencies {
    // If your app is written in Java
    implementation 'androidx.core:core:1.19.0'

    // If your app is written in Kotlin
    implementation 'androidx.core:core-ktx:1.19.0'
}

Kotlin

dependencies {
    // If your app is written in Java
    implementation("androidx.core:core:1.19.0")

    // If your app is written in Kotlin
    implementation("androidx.core:core-ktx:1.19.0")
}

التعامل مع إجراءات المستخدم

يحتاج تطبيق المراسلة إلى طريقة للتعامل مع تعديل محادثة من خلال Action. بالنسبة إلى Android Auto، هناك نوعان من كائنات Action التي يحتاج تطبيقك إلى التعامل معهما: الردّ ووضع علامة "مقروءة". ننصحك بالتعامل معهما باستخدام an IntentService، الذي يوفّر المرونة اللازمة للتعامل مع المكالمات التي قد تكون مكلفة في الخلفية، ما يحرّر سلسلة التعليمات الرئيسية لتطبيقك.

تحديد إجراءات النية

إجراءات Intent هي سلاسل أساسية من اختيارك تحدّد الغرض من Intent. بما أنّ خدمة واحدة يمكنها التعامل مع أنواع متعدّدة من النوايا، فمن الأسهل تحديد سلاسل إجراءات متعدّدة بدلاً من تحديد مكوّنات IntentService متعدّدة.

يحتوي نموذج تطبيق المراسلة في هذا الدليل على نوعَي الإجراءات المطلوبَين: الردّ ووضع علامة "مقروءة"، كما هو موضّح في نموذج الرمز البرمجي التالي:

private const val ACTION_REPLY = "com.example.REPLY"
private const val ACTION_MARK_AS_READ = "com.example.MARK_AS_READ"

إنشاء الخدمة

لإنشاء خدمة تتعامل مع كائنات Action هذه، تحتاج إلى معرّف المحادثة، وهو بنية بيانات عشوائية يحدّدها تطبيقك وتعرّف المحادثة. تحتاج أيضًا إلى مفتاح إدخال عن بُعد، الذي تتم مناقشته بالتفصيل لاحقًا في هذا القسم. ينشئ نموذج الرمز البرمجي التالي خدمة للتعامل مع الإجراءات المطلوبة:

private const val EXTRA_CONVERSATION_ID_KEY = "conversation_id"
private const val REMOTE_INPUT_RESULT_KEY = "reply_input"

/**
 * An [IntentService] that handles reply and mark-as-read actions for
 * [YourAppConversation]s.
 */
class MessagingService : IntentService("MessagingService") {
    override fun onHandleIntent(intent: Intent?) {
        // Fetches internal data.
        val conversationId = intent!!.getIntExtra(EXTRA_CONVERSATION_ID_KEY, -1)

        // Searches the database for that conversation.
        val conversation = YourAppConversation.getById(conversationId)

        // Handles the action that was requested in the intent. The TODOs
        // are addressed in a later section.
        when (intent.action) {
            ACTION_REPLY -> TODO()
            ACTION_MARK_AS_READ -> TODO()
        }
    }
}

لربط هذه الخدمة بتطبيقك، عليك أيضًا تسجيل الخدمة في ملف بيان تطبيقك، كما هو موضّح في المثال التالي:

<application>
    <service android:name="com.example.MessagingService" />
    ...
</application>

إنشاء النوايا والتعامل معها

لا يمكن للتطبيقات الأخرى، بما في ذلك Android Auto، الحصول على Intent الذي يشغّل MessagingService لأنّه يتم تمرير النوايا إلى التطبيقات الأخرى من خلال PendingIntent. بسبب هذا القيد، أنشئ RemoteInput كائن للسماح للتطبيقات الأخرى بتوفير نص الردّ لتطبيقك، كما هو موضّح في الـ مثال التالي:

/**
 * Creates a [RemoteInput] that lets remote apps provide a response string
 * to the underlying [Intent] within a [PendingIntent].
 */
fun createReplyRemoteInput(context: Context): RemoteInput {
    // RemoteInput.Builder accepts a single parameter: the key to use to store
    // the response in.
    return RemoteInput.Builder(REMOTE_INPUT_RESULT_KEY).build()
    // Note that the RemoteInput has no knowledge of the conversation. This is
    // because the data for the RemoteInput is bound to the reply Intent using
    // static methods in the RemoteInput class.
}

/** Creates an [Intent] that handles replying to the given [appConversation]. */
fun createReplyIntent(
        context: Context, appConversation: YourAppConversation): Intent {
    // Creates the intent backed by the MessagingService.
    val intent = Intent(context, MessagingService::class.java)

    // Lets the MessagingService know this is a reply request.
    intent.action = ACTION_REPLY

    // Provides the ID of the conversation that the reply applies to.
    intent.putExtra(EXTRA_CONVERSATION_ID_KEY, appConversation.id)

    return intent
}

في عبارة ACTION_REPLY ضمن MessagingService، استخرِج المعلومات التي يتم إدخالها في Intent للردّ، كما هو موضّح في المثال التالي:

ACTION_REPLY -> {
    // Extracts reply response from the intent using the same key that the
    // RemoteInput uses.
    val results: Bundle = RemoteInput.getResultsFromIntent(intent)
    val message = results.getString(REMOTE_INPUT_RESULT_KEY)

    // This conversation object comes from the MessagingService.
    conversation.reply(message)
}

يمكنك التعامل مع Intent لوضع علامة "مقروءة" بطريقة مماثلة. ومع ذلك، لا يتطلّب ذلك RemoteInput، كما هو موضّح في المثال التالي:

/** Creates an [Intent] that handles marking the [appConversation] as read. */
fun createMarkAsReadIntent(
        context: Context, appConversation: YourAppConversation): Intent {
    val intent = Intent(context, MessagingService::class.java)
    intent.action = ACTION_MARK_AS_READ
    intent.putExtra(EXTRA_CONVERSATION_ID_KEY, appConversation.id)
    return intent
}

لا تتطلّب عبارة ACTION_MARK_AS_READ ضمن MessagingService أي منطق إضافي، كما هو موضّح في المثال التالي:

// Marking as read has no other logic.
ACTION_MARK_AS_READ -> conversation.markAsRead()

إعلام المستخدمين بالرسائل

بعد اكتمال عملية التعامل مع إجراءات المحادثة، تتمثل الخطوة التالية في إنشاء إشعارات متوافقة مع Android Auto.

إنشاء الإجراءات

يمكن تمرير كائنات Action إلى تطبيقات أخرى باستخدام Notification لتشغيل الطرق في التطبيق الأصلي. بهذه الطريقة يمكن لتطبيق Android Auto وضع علامة "مقروءة" على محادثة أو الردّ عليها.

لإنشاء Action، ابدأ بـ Intent. يوضّح المثال التالي كيفية إنشاء "reply" باستخدام الطريقة من القسم السابق:IntentcreateReplyIntent()

fun createReplyAction(
        context: Context, appConversation: YourAppConversation): Action {
    val replyIntent: Intent = createReplyIntent(context, appConversation)
    // ...

بعد ذلك، غلِّف Intent في PendingIntent، ما يجهّزه للاستخدام في التطبيقات الخارجية. يمنع PendingIntent جميع عمليات الوصول إلى Intent من خلال عرض مجموعة محدّدة فقط من الطرق التي تسمح للتطبيق المستلِم بتشغيل Intent أو الحصول على اسم حزمة التطبيق الأصلي. لا يمكن للتطبيق الخارجي الوصول إلى Intent الأساسي أو البيانات الموجودة فيه.

    // ...
    val replyPendingIntent = PendingIntent.getService(
        context,
        createReplyId(appConversation), // Method explained later.
        replyIntent,
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
    // ...

قبل إعداد Action للردّ، يُرجى العِلم أنّ Android Auto يتطلّب ثلاثة شروط لـ Action للردّ:

  • يجب ضبط الإجراء الدلالي على Action.SEMANTIC_ACTION_REPLY.
  • يجب أن يشير Action إلى أنّه لن يعرض أي واجهة مستخدم عند تشغيله.
  • يجب أن يحتوي Action على RemoteInput واحد.

يضبط نموذج الرمز البرمجي التالي Action للردّ يستوفي المتطلبات المذكورة سابقًا:

    // ...
    val replyAction = Action.Builder(R.drawable.reply, "Reply", replyPendingIntent)
        // Provides context to what firing the Action does.
        .setSemanticAction(Action.SEMANTIC_ACTION_REPLY)

        // The action doesn't show any UI, as required by Android Auto.
        .setShowsUserInterface(false)

        // Don't forget the reply RemoteInput. Android Auto will use this to
        // make a system call that will add the response string into
        // the reply intent so it can be extracted by the messaging app.
        .addRemoteInput(createReplyRemoteInput(context))
        .build()

    return replyAction
}

إنّ التعامل مع إجراء وضع علامة "مقروءة" مشابه، باستثناء أنّه لا يتضمّن RemoteInput. لذلك، يتطلّب Android Auto شرطَين لـ Action لوضع علامة "مقروءة":

  • يتم ضبط الإجراء الدلالي على Action.SEMANTIC_ACTION_MARK_AS_READ.
  • يشير الإجراء إلى أنّه لن يعرض أي واجهة مستخدم عند تشغيله.

يضبط نموذج الرمز البرمجي التالي Action لوضع علامة "مقروءة" يستوفي هذه المتطلبات:

fun createMarkAsReadAction(
        context: Context, appConversation: YourAppConversation): Action {
    val markAsReadIntent = createMarkAsReadIntent(context, appConversation)
    val markAsReadPendingIntent = PendingIntent.getService(
            context,
            createMarkAsReadId(appConversation), // Method explained later.
            markAsReadIntent,
            PendingIntent.FLAG_UPDATE_CURRENT  or PendingIntent.FLAG_IMMUTABLE)
    val markAsReadAction = Action.Builder(
            R.drawable.mark_as_read, "Mark as Read", markAsReadPendingIntent)
        .setSemanticAction(Action.SEMANTIC_ACTION_MARK_AS_READ)
        .setShowsUserInterface(false)
        .build()
    return markAsReadAction
}

عند إنشاء النوايا المعلقة، يمكنك استخدام طريقتَين: createReplyId وcreateMarkAsReadId. تعمل هاتان الطريقتان كرموز طلب لكل PendingIntent، ويستخدمهما Android للتحكّم في النوايا المعلقة الحالية. يجب أن تعرض طرق create معرّفات فريدة لكل محادثة، ولكن يجب أن تعرض عمليات الاستدعاء المتكرّرة للمحادثة نفسها المعرّف الفريد الذي تم إنشاؤه من قبل .

لنأخذ مثالاً يتضمّن محادثتَين، A وB: معرّف الردّ للمحادثة A هو 100، ومعرّف وضع علامة "مقروءة" هو 101. معرّف الردّ للمحادثة B هو 102، ومعرّف وضع علامة "مقروءة" هو 103. إذا تم تعديل المحادثة A، سيظلّ معرّفا الردّ ووضع علامة "مقروءة" هما 100 و101. لمزيد من المعلومات، يُرجى الاطّلاع على PendingIntent.FLAG_UPDATE_CURRENT.

إنشاء MessagingStyle

MessagingStyle هو حامل معلومات المراسلة، وهو ما يستخدمه Android Auto لقراءة كل رسالة بصوت عالٍ في محادثة.

أولاً، عليك تحديد مستخدم الجهاز ككائن Person، كما هو موضّح في المثال التالي:

fun createMessagingStyle(
        context: Context, appConversation: YourAppConversation): MessagingStyle {
    // Method defined by the messaging app.
    val appDeviceUser: YourAppUser = getAppDeviceUser()

    val devicePerson = Person.Builder()
        // The display name (also the name that's read aloud in Android auto).
        .setName(appDeviceUser.name)

        // The icon to show in the notification shade in the system UI (outside
        // of Android Auto).
        .setIcon(appDeviceUser.icon)

        // A unique key in case there are multiple people in this conversation with
        // the same name.
        .setKey(appDeviceUser.id)
        .build()
    // ...

يمكنك بعد ذلك إنشاء كائن MessagingStyle وتقديم بعض التفاصيل حول المحادثة.

    // ...
    val messagingStyle = MessagingStyle(devicePerson)

    // Sets the conversation title. If the app's target version is lower
    // than P, this will automatically mark the conversation as a group (to
    // maintain backward compatibility). Use `setGroupConversation` after
    // setting the conversation title to explicitly override this behavior. See
    // the documentation for more information.
    messagingStyle.setConversationTitle(appConversation.title)

    // Group conversation means there is more than 1 recipient, so set it as such.
    messagingStyle.setGroupConversation(appConversation.recipients.size > 1)
    // ...

أخيرًا، أضِف الرسائل غير المقروءة.

    // ...
    for (appMessage in appConversation.getUnreadMessages()) {
        // The sender is also represented using a Person object.
        val senderPerson = Person.Builder()
            .setName(appMessage.sender.name)
            .setIcon(appMessage.sender.icon)
            .setKey(appMessage.sender.id)
            .build()

        // Adds the message. More complex messages, like images,
        // can be created and added by instantiating the MessagingStyle.Message
        // class directly. See documentation for details.
        messagingStyle.addMessage(
                appMessage.body, appMessage.timeReceived, senderPerson)
    }

    return messagingStyle
}

تجميع الإشعار وإرساله

بعد إنشاء كائنَي Action وMessagingStyle، يمكنك إنشاء Notification ونشره.

fun notify(context: Context, appConversation: YourAppConversation) {
    // Creates the actions and MessagingStyle.
    val replyAction = createReplyAction(context, appConversation)
    val markAsReadAction = createMarkAsReadAction(context, appConversation)
    val messagingStyle = createMessagingStyle(context, appConversation)

    // Creates the notification.
    val notification = NotificationCompat.Builder(context, channel)
        // A required field for the Android UI.
        .setSmallIcon(R.drawable.notification_icon)

        // Shows in Android Auto as the conversation image.
        .setLargeIcon(appConversation.icon)

        // Adds MessagingStyle.
        .setStyle(messagingStyle)

        // Adds reply action.
        .addAction(replyAction)

        // Makes the mark-as-read action invisible, so it doesn't appear
        // in the Android UI but the app satisfies Android Auto's
        // mark-as-read Action requirement. Both required actions can be made
        // visible or invisible; it is a stylistic choice.
        .addInvisibleAction(markAsReadAction)

        .build()

    // Posts the notification for the user to see.
    val notificationManagerCompat = NotificationManagerCompat.from(context)
    notificationManagerCompat.notify(appConversation.id, notification)
}

مراجع إضافية

الإبلاغ عن مشكلة في إشعار المراسلة على Android Auto

إذا واجهت مشكلة أثناء تطوير إشعارات المراسلة لتطبيق Android Auto، يمكنك الإبلاغ عنها باستخدام أداة تتبُّع المشاكل من Google. احرص على ملء جميع المعلومات المطلوبة في نموذج المشكلة.

إنشاء مشكلة جديدة

قبل تقديم مشكلة جديدة، تحقَّق مما إذا تم الإبلاغ عنها من قبل في قائمة المشاكل. يمكنك الاشتراك في المشاكل والتصويت عليها من خلال النقر على النجمة لمشكلة في أداة التتبُّع. لمزيد من المعلومات، يُرجى الاطّلاع على مقالة الاشتراك في مشكلة.