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

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

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

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

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

البدء

لتقديم خدمة المراسلة لأجهزة 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 للسماح للمستخدم بالردّ بسرعة على الرسالة أو وضع علامة "مقروءة" عليها مباشرةً من مركز الإشعارات.

باختصار، يتم تمثيل المحادثة الفردية بكائن 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/رسائل الوسائط المتعددة الواردة عند ضبط تطبيقك كمعالج تلقائي للرسائل القصيرة، ما قد يؤدي إلى ظهور إشعارات متكررة.

<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.13.1'

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

Kotlin

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

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

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

يحتاج تطبيق المراسلة إلى طريقة للتعامل مع تعديل محادثة من خلال 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 لتشغيل الطرق في التطبيق الأصلي. وهذه هي الطريقة التي يمكن أن يضع بها 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، ومعرف mark-as-read هو 103. إذا تم تحديث المحادثة "أ"، سيظل معرّفا الرد ووضع علامة كمقروءة 100 و101. لمزيد من المعلومات، يُرجى الاطّلاع على PendingIntent.FLAG_UPDATE_CURRENT.

إنشاء نمط رسالة

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. احرص على ملء كل المعلومات المطلوبة في نموذج المشكلة.

إنشاء عدد جديد

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