گسترش اعلان‌های پیام‌رسانی به 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 API استفاده می‌کنید، اعلان‌های پیام بهینه‌شده‌ای برای همه دستگاه‌های 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>

اگر برنامه‌تان می‌تواند به‌عنوان کنترل‌کننده پیش‌فرض پیامک تنظیم شود، حتماً عنصر <uses> زیر را اضافه کنید. اگر این کار را نکنید، Android Auto از گرداننده پیش‌فرض داخلی خود برای مدیریت پیامک‌ها/فراپیام‌های ورودی استفاده می‌کند، درحالی‌که برنامه شما به‌عنوان گرداننده پیش‌فرض پیامک تنظیم شده است، که این امر می‌تواند منجر به اعلان‌های تکراری شود.

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

وارد کردن کتابخانه اصلی AndroidX

ساختن اعلان‌ها برای استفاده با Android Auto به کتابخانه اصلی AndroidX نیاز دارد. کتابخانه را به‌صورت زیر به پروژه خود وارد کنید:

  1. در فایل build.gradle سطح بالا، وابستگی به مخزن Maven متعلق به Google را اضافه کنید، همان‌طور که در مثال زیر نشان داده شده است:

شیک

allprojects {
    repositories {
        google()
    }
}

کاتلین

allprojects {
    repositories {
        google()
    }
}
  1. در فایل build.gradle مدول برنامه‌تان، وابستگی کتابخانه AndroidX Core را اضافه کنید، همان‌طور که در مثال زیر نشان داده شده است:

شیک

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

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

کاتلین

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

    // If your app is written in Kotlin
    implementation("androidx.core:core-ktx:1.19.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 می‌شود دریافت کنند زیرا هدف‌ها ازطریق 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
}

عبارت switch 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 شروع کنید. مثال زیر نشان می‌دهد که چگونه بااستفاده از روش createReplyIntent() از بخش قبلی، یک «پاسخ» 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 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 ۱۰۰ است و شناسه علامت‌گذاری به‌عنوان خوانده‌شده آن ۱۰۱ است. شناسه پاسخ مکالمه «ب» ۱۰۲ است، و شناسه علامت‌گذاری به‌عنوان خوانده‌شده آن ۱۰۳ است. اگر مکالمه الف به‌روز شود، شناسه‌های پاسخ و علامت‌گذاری به‌عنوان خوانده‌شده همچنان ۱۰۰ و ۱۰۱ هستند. برای اطلاعات بیشتر، به 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 Issue Tracker گزارش کنید. حتماً همه اطلاعات درخواست‌شده را در الگوی مشکل تکمیل کنید.

ایجاد مشکل جدید

قبل‌از ثبت مشکل جدید، بررسی کنید که آیا قبلاً در فهرست مشکلات گزارش شده است یا نه. می‌توانید با کلیک کردن روی ستاره مربوط به مشکل در ردیاب، مشترک شوید و به مشکلات رأی دهید. برای اطلاعات بیشتر، به مشترک شدن در مشکل مراجعه کنید.