برنامه های پیام رسانی برای Android Auto بسازید

دسته ارتباطات به زودی ارائه می شود
دسته پیام‌رسانی در حال گسترش است تا از قابلیت‌های جدید، از جمله سابقه پیام و تجربه تماس پشتیبانی کند

ارتباط ماندن از طریق پیام برای بسیاری از رانندگان مهم است. برنامه‌های چت می‌توانند به کاربران اطلاع دهند که آیا باید کودکی را بردارد یا مکان شام را تغییر داده است. چارچوب Android به برنامه‌های پیام‌رسان اجازه می‌دهد تا با استفاده از یک رابط کاربری استاندارد که به رانندگان اجازه می‌دهد چشمان خود را به جاده نگاه کنند، خدمات خود را به تجربه رانندگی گسترش دهند.

برنامه‌هایی که از پیام‌رسانی پشتیبانی می‌کنند می‌توانند اعلان‌های پیام‌رسانی خود را گسترش دهند تا به Android Auto اجازه دهند هنگام اجرای Auto آن‌ها را مصرف کند. این اعلان‌ها در حالت خودکار نمایش داده می‌شوند و به کاربران اجازه می‌دهند پیام‌ها را در یک رابط ثابت و کم حواس‌پرتی بخوانند و به آنها پاسخ دهند. و وقتی از MessagingStyle API استفاده می‌کنید، اعلان‌های پیام بهینه‌شده را برای همه دستگاه‌های Android، از جمله Android Auto دریافت می‌کنید. بهینه‌سازی‌ها شامل یک رابط کاربری است که برای اعلان‌های پیام، انیمیشن‌های بهبودیافته و پشتیبانی از تصاویر درون خطی تخصصی است.

این راهنما به شما نشان می‌دهد که چگونه برنامه‌ای را که پیام‌ها را به کاربر نمایش می‌دهد و پاسخ‌های کاربر را دریافت می‌کند، مانند برنامه چت، به نمایش پیام دستی و رسید پاسخ به یک دستگاه خودکار گسترش دهید. برای راهنمایی طراحی مرتبط، به برنامه‌های پیام‌رسانی در سایت Design for Driving مراجعه کنید.

شروع کنید

برای ارائه سرویس پیام‌رسانی برای دستگاه‌های Auto، برنامه شما باید پشتیبانی خود را از Android Auto در مانیفست اعلام کند و بتواند کارهای زیر را انجام دهد:

  • اشیاء NotificationCompat.MessagingStyle را بسازید و ارسال کنید که حاوی پاسخ و علامت‌گذاری به عنوان خوانده شده Action است.
  • پاسخ دادن و علامت گذاری یک مکالمه به عنوان خوانده شده را با یک Service انجام دهید.

مفاهیم و اشیاء

قبل از شروع طراحی برنامه خود، بهتر است بدانید Android Auto چگونه پیام‌رسانی را مدیریت می‌کند.

یک تکه ارتباط منفرد یک پیام نامیده می شود و با کلاس MessagingStyle.Message نشان داده می شود. یک پیام حاوی فرستنده، محتوای پیام و زمان ارسال پیام است.

ارتباط بین کاربران مکالمه نامیده می شود و توسط یک شیء MessagingStyle نشان داده می شود. مکالمه یا MessagingStyle حاوی عنوان، پیام‌ها و اینکه آیا مکالمه بین گروهی از کاربران است یا خیر.

برای اطلاع کاربران از به‌روزرسانی‌های یک مکالمه، مانند یک پیام جدید، برنامه‌ها یک Notification به سیستم Android ارسال می‌کنند. این Notification از شیء MessagingStyle برای نمایش رابط کاربری خاص پیام‌رسانی در قسمت اعلان استفاده می‌کند. پلتفرم اندروید نیز این 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 ، Reply 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 ورودی استفاده می‌شود که می‌تواند منجر به اعلان‌های تکراری شود.

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

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

اعلان‌های ساختمان برای استفاده با دستگاه‌های 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.13.1'

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

کاتلین

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 Object وجود دارد که برنامه شما باید آن را مدیریت کند: پاسخ و علامت‌گذاری به عنوان خوانده شده. توصیه می کنیم با استفاده از IntentService آنها را مدیریت کنید، که انعطاف پذیری برای رسیدگی به تماس های بالقوه گران در پس زمینه را فراهم می کند و رشته اصلی برنامه شما را آزاد می کند.

اعمال قصد را تعریف کنید

کنش های Intent رشته های ساده ای هستند که مشخص می کنند 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)
    // ...

قبل از راه‌اندازی Reply Action ، توجه داشته باشید که Android Auto سه شرط لازم برای پاسخ Action دارد:

  • عمل معنایی باید روی Action.SEMANTIC_ACTION_REPLY تنظیم شود.SEMANTIC_ACTION_REPLY.
  • Action باید نشان دهد که در هنگام اجرا هیچ رابط کاربری را نشان نخواهد داد.
  • Action باید شامل یک RemoteInput باشد.

نمونه کد زیر یک Action Reply 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 تنظیم شده است.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
}

هنگام تولید intent های معلق، از دو روش استفاده می شود: 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 Messaging را گزارش کنید

اگر هنگام توسعه برنامه پیام‌رسانی خود برای Android Auto با مشکلی مواجه شدید، می‌توانید با استفاده از Google Issue Tracker آن را گزارش کنید. حتماً تمام اطلاعات درخواستی را در قالب شماره پر کنید.

یک شماره جدید ایجاد کنید

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