إنشاء تطبيقات المراسلة لتطبيق Android Auto

ستتوفّر فئة "التواصل" قريبًا
يتم توسيع نطاق فئة "المراسلة" لتشمل إتاحة إمكانات جديدة، بما في ذلك سجلّ الرسائل وتجارب الاتصال.

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

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

يوضّح لك هذا الدليل كيفية توسيع نطاق تطبيق يعرض الرسائل للمستخدم ويتلقّى ردود المستخدم، مثل تطبيق محادثة، لعرض الرسائل وتلقّي الردود على جهاز Auto. للحصول على إرشادات تصميم ذات صلة، يُرجى الاطّلاع على تطبيقات المراسلة على الموقع الإلكتروني Design for Driving.

البدء

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

  • أنشئ وأرسِل عناصر NotificationCompat.MessagingStyle التي تحتوي على عناصر الردّ والوضع كرسالة تمت قراءتها Action.
  • يمكنك الردّ على محادثة ووضع علامة "مقروءة" عليها باستخدام Service.

المفاهيم والعناصر

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

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

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

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

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

باختصار، يتم تمثيل محادثة واحدة بعنصر 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 آخر عليك إنشاؤه باستخدام المسار التالي: YourAppProject/app/src/main/res/xml/automotive_app_desc.xml. في automotive_app_desc.xml، أدخِل ميزات Android Auto التي يتوافق معها تطبيقك. على سبيل المثال، للإشارة إلى توفّر الإشعارات، أدرِج ما يلي:

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

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

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

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

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

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

رائع

allprojects {
    repositories {
        google()
    }
}

Kotlin

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

رائع

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

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

Kotlin

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

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

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

يحتاج تطبيق المراسلة إلى طريقة لمعالجة تعديل محادثة من خلال Action. بالنسبة إلى Android Auto، هناك نوعان من عناصر Action التي يحتاج تطبيقك إلى التعامل معها: الردّ والوضع على أنّه تمت قراءته. ننصحك بمعالجتها باستخدام 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، لأنّه يتم تمرير Intent إلى التطبيقات الأخرى من خلال 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 لبدء Notification في التطبيق الأصلي. بهذه الطريقة، يمكن لتطبيق Android Auto وضع علامة "تمت القراءة" على محادثة أو الرد عليها.

لإنشاء Action، ابدأ بإنشاء Intent. يوضّح المثال التالي كيفية إنشاء "ردّ" Intent:

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 below.
            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() معرّفات فريدة لكل محادثة، ولكن يجب أن تُعرِض طلبات البحث المتكرّرة عن المحادثة نفسها المعرّف الفريد الذي تم إنشاؤه من قبل.

لنفترض أنّ لدينا محادثتَين، "أ" و"ب": رقم تعريف الردّ في المحادثة "أ" هو 100، ورقم تعريف وضع علامة "تمت القراءة" هو 101. رقم تعريف الردّ في المحادثة "ب" هو 102، ورقم تعريف وضع العلامة "تمت القراءة" هو 103. إذا تم تعديل المحادثة "أ"، سيظل معرّفَا الردّ والوضع كرسالة مقروءة هما 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. احرص على ملء جميع المعلومات المطلوبة في نموذج المشكلة.

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

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