مخاطبین نمایه کاری

این راهنمای توسعه‌دهنده توضیح می‌دهد که چگونه می‌توانید برنامه خود را برای استفاده از داده‌های تماس از نمایه کاری ارتقا دهید. اگر قبلاً از APIهای مخاطبین Android استفاده نکرده‌اید، برای آشنایی با APIها ، Contacts Provider را بخوانید.

نمای کلی

دستگاه‌های دارای نمایه کاری، مخاطبین را در فهرست‌های محلی جداگانه برای نمایه‌های کاری و شخصی ذخیره می‌کنند. به‌طور پیش‌فرض، وقتی برنامه‌ای در نمایه شخصی اجرا می‌شود، مخاطبین کاری را نمایش نمی‌دهد. با این حال، یک برنامه می تواند به اطلاعات تماس از نمایه کاری دسترسی پیدا کند. برای مثال، برنامه‌ای که این کار را انجام می‌دهد، برنامه Google's Android Contacts است که مخاطبین شخصی و فهرست کاری را در نتایج جستجو نشان می‌دهد.

کاربران اغلب می خواهند از دستگاه ها و برنامه های شخصی خود برای کار استفاده کنند. با استفاده از مخاطبین نمایه کاری، برنامه شما می تواند بخشی از روز کاری کاربر شما شود.

تجربه کاربری

در نظر بگیرید که چگونه برنامه شما ممکن است اطلاعات تماس را از نمایه کاری ارائه دهد. بهترین رویکرد به ماهیت برنامه شما و دلیل استفاده افراد از آن بستگی دارد، اما به موارد زیر فکر کنید:

  • آیا برنامه شما باید به طور پیش فرض شامل مخاطبین نمایه کاری باشد یا کاربر باید شرکت کند؟
  • اختلاط یا جداسازی مخاطبین کار و پروفایل شخصی چگونه بر جریان کاربر تأثیر می گذارد؟
  • ضربه زدن تصادفی یک مخاطب نمایه کاری چه تاثیری دارد؟
  • وقتی مخاطبین نمایه کاری در دسترس نباشند، چه اتفاقی برای رابط برنامه شما می‌افتد؟

برنامه شما باید به وضوح یک مخاطب نمایه کاری را نشان دهد. شاید بتوانید با استفاده از یک نماد کاری آشنا، مانند یک کیف، مخاطب را نشان کنید.

اسکرین شات که نتایج جستجو را در یک لیست نشان می دهد
شکل 1. چگونه برنامه Google Contacts مخاطبین نمایه کاری را جدا می کند

به عنوان مثال، برنامه Google Contacts (نشان داده شده در شکل 1) برای فهرست کردن ترکیبی از مخاطبین نمایه کاری و شخصی، کارهای زیر را انجام می دهد:

  1. برای جدا کردن بخش‌های کاری و شخصی از فهرست، یک عنوان فرعی درج می‌کند.
  2. نشان‌ها روی مخاطبین با نماد کیف کار می‌کنند.
  3. با ضربه زدن، مخاطب کاری را در نمایه کاری باز می کند.

اگر شخصی که از دستگاه استفاده می‌کند نمایه کاری را خاموش کند، برنامه شما نمی‌تواند اطلاعات تماس را از نمایه کاری یا فهرست‌های تماس راه دور سازمان جستجو کند. بسته به نحوه استفاده از مخاطبین نمایه کاری، می‌توانید بی‌صدا این مخاطبین را کنار بگذارید یا ممکن است لازم باشد کنترل‌های رابط کاربری را غیرفعال کنید.

مجوزها

اگر برنامه شما از قبل با مخاطبین کاربر کار می کند، مجوز READ_CONTACTS (یا احتمالا WRITE_CONTACTS ) را که در فایل مانیفست برنامه خود درخواست کرده اید، خواهید داشت. از آنجایی که همان شخص از نمایه شخصی و نمایه کاری استفاده می‌کند، برای دسترسی به اطلاعات تماس از نمایه کاری نیازی به مجوز بیشتری ندارید.

یک سرپرست فناوری اطلاعات می‌تواند نمایه کاری را که اطلاعات تماس را با نمایه شخصی به اشتراک می‌گذارد مسدود کند . اگر یک سرپرست فناوری اطلاعات دسترسی را مسدود کند، جستجوهای مخاطبین شما به عنوان نتایج خالی برگردانده می شوند. اگر کاربر نمایه کاری را خاموش کرده باشد، برنامه شما نیازی به رسیدگی به خطاهای خاصی ندارد. ارائه دهنده محتوای دایرکتوری به بازگرداندن اطلاعات مربوط به دایرکتوری های تماس کاری کاربر ادامه می دهد (به بخش راهنماها مراجعه کنید). برای آزمایش این مجوزها، به بخش توسعه و آزمایش مراجعه کنید.

جستجوهای تماس

می‌توانید با استفاده از همان APIها و فرآیندهایی که برنامه شما برای دریافت مخاطبین در نمایه شخصی استفاده می‌کند، مخاطبین را از نمایه کاری دریافت کنید. URI سازمانی برای مخاطبین در Android 7.0 (سطح API 24) یا بالاتر پشتیبانی می‌شود. باید تنظیمات زیر را در URI انجام دهید:

  1. URI ارائه‌دهنده محتوا را روی Contacts.ENTERPRISE_CONTENT_FILTER_URI تنظیم کنید و نام مخاطب را به‌عنوان یک رشته جستجو ارائه کنید.
  2. یک فهرست تماس برای جستجو تنظیم کنید. برای مثال، ENTERPRISE_DEFAULT مخاطبین را در فروشگاه محلی نمایه کاری پیدا می‌کند.

تغییر URI با هر مکانیزم ارائه‌دهنده محتوا مانند CursorLoader کار می‌کند - ایده‌آل برای بارگیری داده‌های تماس در رابط‌های کاربر، زیرا دسترسی به داده‌ها در یک رشته کارگر اتفاق می‌افتد. برای سادگی، مثال‌های موجود در این راهنما ContentResolver.query() را فراخوانی می‌کنند. در اینجا نحوه یافتن مخاطبین در فهرست تماس محلی نمایه کاری آمده است:

کاتلین

// First confirm the device user has given permission for the personal profile.
// There isn't a separate work permission, but an IT admin can block access.
val readContactsPermission =
  ContextCompat.checkSelfPermission(getBaseContext(), Manifest.permission.READ_CONTACTS)
if (readContactsPermission != PackageManager.PERMISSION_GRANTED) {
  return
}

// Fetch Jackie, James, & Jason (and anyone else whose names begin with "ja").
val nameQuery = Uri.encode("ja")

// Build the URI to look up work profile contacts whose name matches. Query
// the default work profile directory which is the locally-stored contacts.
val contentFilterUri =
  ContactsContract.Contacts.ENTERPRISE_CONTENT_FILTER_URI
    .buildUpon()
    .appendPath(nameQuery)
    .appendQueryParameter(
      ContactsContract.DIRECTORY_PARAM_KEY,
      ContactsContract.Directory.ENTERPRISE_DEFAULT.toString()
    )
    .build()

// Query the content provider using the generated URI.
var cursor =
  getContentResolver()
    .query(
      contentFilterUri,
      arrayOf(
        ContactsContract.Contacts._ID,
        ContactsContract.Contacts.LOOKUP_KEY,
        ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
      ),
      null,
      null,
      null
    )

// Print any results found using the work profile contacts' display name.
cursor?.use {
  while (it.moveToNext()) {
    Log.i(TAG, "Work profile contact: ${it.getString(2)}")
  }
}

جاوا

// First confirm the device user has given permission for the personal profile.
// There isn't a separate work permission, but an IT admin can block access.
int readContactsPermission = ContextCompat.checkSelfPermission(
    getBaseContext(), Manifest.permission.READ_CONTACTS);
if (readContactsPermission != PackageManager.PERMISSION_GRANTED) {
  return;
}

// Fetch Jackie, James, & Jason (and anyone else whose names begin with "ja").
String nameQuery = Uri.encode("ja");

// Build the URI to look up work profile contacts whose name matches. Query
// the default work profile directory which is the locally stored contacts.
Uri contentFilterUri = ContactsContract.Contacts.ENTERPRISE_CONTENT_FILTER_URI
    .buildUpon()
    .appendPath(nameQuery)
    .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
        String.valueOf(ContactsContract.Directory.ENTERPRISE_DEFAULT))
    .build();

// Query the content provider using the generated URI.
Cursor cursor = getContentResolver().query(
    contentFilterUri,
    new String[] {
        ContactsContract.Contacts._ID,
        ContactsContract.Contacts.LOOKUP_KEY,
        ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
    },
    null,
    null,
    null);
if (cursor == null) {
  return;
}

// Print any results found using the work profile contacts' display name.
try {
  while (cursor.moveToNext()) {
    Log.i(TAG, "Work profile contact: " + cursor.getString(2));
  }
} finally {
  cursor.close();
}

دایرکتوری ها

بسیاری از سازمان ها از دایرکتوری های راه دور مانند Microsoft Exchange یا LDAP استفاده می کنند که حاوی اطلاعات تماس کل سازمان است. برنامه شما می‌تواند به کاربران کمک کند تا با همکاران کاری که در فهرست راهنمای سازمانشان یافت می‌شوند ارتباط برقرار کنند و به اشتراک بگذارند. توجه داشته باشید که این دایرکتوری ها معمولاً شامل هزاران مخاطب هستند و برنامه شما نیز برای جستجو در آنها به یک اتصال شبکه فعال نیاز دارد. می‌توانید از ارائه‌دهنده محتوای Directory استفاده کنید تا فهرست‌های راهنمای استفاده شده توسط حساب‌های کاربر را دریافت کنید و درباره یک فهرست فردی اطلاعات بیشتری کسب کنید.

از ارائه‌دهنده محتوای Directory.ENTERPRISE_CONTENT_URI پرس و جو کنید تا دایرکتوری‌ها را از نمایه شخصی دریافت کنید و نمایه کاری با هم بازگردانده شود. جستجوی دایرکتوری‌های نمایه کاری در Android نسخه 7.0 (سطح API 24) یا بالاتر پشتیبانی می‌شود. برنامه شما همچنان به کاربر نیاز دارد تا به READ_CONTACTS اجازه دهد تا با فهرست‌های مخاطبین خود کار کند.

از آنجایی که Android اطلاعات تماس را در انواع مختلف دایرکتوری های محلی و راه دور ذخیره می کند، کلاس Directory روش هایی دارد که می توانید برای یافتن اطلاعات بیشتر در مورد یک فهرست، با آنها تماس بگیرید:

isEnterpriseDirectoryId()
این روش را فراخوانی کنید تا بفهمید آیا دایرکتوری از یک حساب نمایه کاری است یا خیر. به یاد داشته باشید که ارائه‌دهنده محتوای ENTERPRISE_CONTENT_URI فهرست‌های تماس برای نمایه شخصی و کاری را با هم برمی‌گرداند.
isRemoteDirectoryId()
برای اطلاع از راه دور بودن دایرکتوری با این روش تماس بگیرید. دایرکتوری های راه دور ممکن است فروشگاه های تماس سازمانی یا شبکه های اجتماعی کاربر باشند.

مثال زیر نشان می دهد که چگونه می توانید از این روش ها برای فیلتر کردن دایرکتوری های نمایه کاری استفاده کنید:

کاتلین

// First, confirm the device user has given READ_CONTACTS permission.
// This permission is still needed for directory listings ...

// Query the content provider to get directories for BOTH the personal and
// work profiles.
val cursor =
  getContentResolver()
    .query(
      ContactsContract.Directory.ENTERPRISE_CONTENT_URI,
      arrayOf(ContactsContract.Directory._ID, ContactsContract.Directory.PACKAGE_NAME),
      null,
      null,
      null
    )

// Print the package name of the work profile's local or remote contact directories.
cursor?.use {
  while (it.moveToNext()) {
    val directoryId = it.getLong(0)
    if (ContactsContract.Directory.isEnterpriseDirectoryId(directoryId)) {
      Log.i(TAG, "Directory: ${it.getString(1)}")
    }
  }
}

جاوا

// First, confirm the device user has given READ_CONTACTS permission.
// This permission is still needed for directory listings ...

// Query the content provider to get directories for BOTH the personal and
// work profiles.
Cursor cursor = getContentResolver().query(
    ContactsContract.Directory.ENTERPRISE_CONTENT_URI,
    new String[]{
        ContactsContract.Directory._ID,
        ContactsContract.Directory.PACKAGE_NAME
    },
    null,
    null,
    null);
if (cursor == null) {
  return;
}

// Print the package name of the work profile's local or remote contact directories.
try {
  while (cursor.moveToNext()) {
    long directoryId = cursor.getLong(0);

    if (ContactsContract.Directory.isEnterpriseDirectoryId(directoryId)) {
      Log.i(TAG, "Directory: " + cursor.getString(1));
    }
  }
} finally {
  cursor.close();
}

مثال شناسه و نام بسته را برای دایرکتوری واکشی می کند. برای نمایش رابط کاربری که به کاربران کمک می کند منبع دایرکتوری تماس را انتخاب کنند، ممکن است لازم باشد اطلاعات بیشتری در مورد دایرکتوری دریافت کنید. برای مشاهده سایر فیلدهای فراداده که ممکن است در دسترس باشند، مرجع کلاس Directory را بخوانید.

جستجوهای تلفنی

برنامه‌ها می‌توانند PhoneLookup.CONTENT_FILTER_URI را جستجو کنند تا به‌طور مؤثر داده‌های تماس را برای شماره تلفن جستجو کنند. اگر این URI را با PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI جایگزین کنید، می‌توانید نتایج جستجو را از ارائه‌دهنده مخاطبین شخصی و کاری دریافت کنید.ENTERPRISE_CONTENT_FILTER_URI. این URI محتوای نمایه کاری در Android نسخه 5.0 (سطح API 21) یا بالاتر موجود است.

مثال زیر برنامه‌ای را نشان می‌دهد که URI محتوای نمایه کاری را برای پیکربندی رابط کاربری برای تماس ورودی جستجو می‌کند:

کاتلین

fun onCreateIncomingConnection(
  connectionManagerPhoneAccount: PhoneAccountHandle,
  request: ConnectionRequest
): Connection {
  var request = request
  // Get the telephone number from the incoming request URI.
  val phoneNumber = this.extractTelephoneNumber(request.address)

  var displayName = "Unknown caller"
  var isCallerInWorkProfile = false

  // Look up contact details for the caller in the personal and work profiles.
  val lookupUri =
    Uri.withAppendedPath(
      ContactsContract.PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
      Uri.encode(phoneNumber)
    )
  val cursor =
    getContentResolver()
      .query(
        lookupUri,
        arrayOf(
          ContactsContract.PhoneLookup._ID,
          ContactsContract.PhoneLookup.DISPLAY_NAME,
          ContactsContract.PhoneLookup.CUSTOM_RINGTONE
        ),
        null,
        null,
        null
      )

  // Use the first contact found and check if they're from the work profile.
  cursor?.use {
    if (it.moveToFirst() == true) {
      displayName = it.getString(1)
      isCallerInWorkProfile = ContactsContract.Contacts.isEnterpriseContactId(it.getLong(0))
    }
  }

  // Return a configured connection object for the incoming call.
  val connection = MyAudioConnection()
  connection.setCallerDisplayName(displayName, TelecomManager.PRESENTATION_ALLOWED)

  // Our app's activity uses this value to decide whether to show a work badge.
  connection.setIsCallerInWorkProfile(isCallerInWorkProfile)

  // Configure the connection further ...
  return connection
}

جاوا

public Connection onCreateIncomingConnection (
    PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
  // Get the telephone number from the incoming request URI.
  String phoneNumber = this.extractTelephoneNumber(request.getAddress());

  String displayName = "Unknown caller";
  boolean isCallerInWorkProfile = false;

  // Look up contact details for the caller in the personal and work profiles.
  Uri lookupUri = Uri.withAppendedPath(
      ContactsContract.PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
      Uri.encode(phoneNumber));
  Cursor cursor = getContentResolver().query(
      lookupUri,
      new String[]{
          ContactsContract.PhoneLookup._ID,
          ContactsContract.PhoneLookup.DISPLAY_NAME,
          ContactsContract.PhoneLookup.CUSTOM_RINGTONE
      },
      null,
      null,
      null);

  // Use the first contact found and check if they're from the work profile.
  if (cursor != null) {
    try {
      if (cursor.moveToFirst() == true) {
        displayName = cursor.getString(1);
        isCallerInWorkProfile =
            ContactsContract.Contacts.isEnterpriseContactId(cursor.getLong(0));
      }
    } finally {
      cursor.close();
    }
  }

  // Return a configured connection object for the incoming call.
  MyConnection connection = new MyConnection();
  connection.setCallerDisplayName(displayName, TelecomManager.PRESENTATION_ALLOWED);

  // Our app's activity uses this value to decide whether to show a work badge.
  connection.setIsCallerInWorkProfile(isCallerInWorkProfile);

  // Configure the connection further ...
  return connection;
}

جستجوی ایمیل

برنامه شما می‌تواند اطلاعات تماس شخصی یا کاری را برای یک آدرس ایمیل با درخواست Email.ENTERPRISE_CONTENT_LOOKUP_URI دریافت کند.ENTERPRISE_CONTENT_LOOKUP_URI. پرس و جو از این URL ابتدا مخاطبین شخصی را برای مطابقت دقیق جستجو می کند. اگر ارائه‌دهنده با هیچ یک از مخاطبین شخصی مطابقت نداشته باشد، ارائه‌دهنده مخاطبین کاری را برای یک مورد منطبق جستجو می‌کند. این URI در Android 6.0 (سطح API 23) یا بالاتر موجود است.

در اینجا نحوه جستجوی اطلاعات تماس برای آدرس ایمیل آمده است:

کاتلین

// Build the URI to look up contacts from the personal and work profiles that
// are an exact (case-insensitive) match for the email address.
val emailAddress = "somebody@example.com"
val contentFilterUri =
  Uri.withAppendedPath(
    ContactsContract.CommonDataKinds.Email.ENTERPRISE_CONTENT_LOOKUP_URI,
    Uri.encode(emailAddress)
  )

// Query the content provider to first try to match personal contacts and,
// if none are found, then try to match the work contacts.
val cursor =
  contentResolver.query(
    contentFilterUri,
    arrayOf(
      ContactsContract.CommonDataKinds.Email.CONTACT_ID,
      ContactsContract.CommonDataKinds.Email.ADDRESS,
      ContactsContract.Contacts.DISPLAY_NAME
    ),
    null,
    null,
    null
  )
    ?: return

// Print the name of the matching contact. If we want to work-badge contacts,
// we can call ContactsContract.Contacts.isEnterpriseContactId() with the ID.
cursor.use {
  while (it.moveToNext()) {
    Log.i(TAG, "Matching contact: ${it.getString(2)}")
  }
}

جاوا

// Build the URI to look up contacts from the personal and work profiles that
// are an exact (case-insensitive) match for the email address.
String emailAddress = "somebody@example.com";
Uri contentFilterUri = Uri.withAppendedPath(
    ContactsContract.CommonDataKinds.Email.ENTERPRISE_CONTENT_LOOKUP_URI,
    Uri.encode(emailAddress));

// Query the content provider to first try to match personal contacts and,
// if none are found, then try to match the work contacts.
Cursor cursor = getContentResolver().query(
    contentFilterUri,
    new String[]{
        ContactsContract.CommonDataKinds.Email.CONTACT_ID,
        ContactsContract.CommonDataKinds.Email.ADDRESS,
        ContactsContract.Contacts.DISPLAY_NAME
    },
    null,
    null,
    null);
if (cursor == null) {
  return;
}

// Print the name of the matching contact. If we want to work-badge contacts,
// we can call ContactsContract.Contacts.isEnterpriseContactId() with the ID.
try {
  while (cursor.moveToNext()) {
    Log.i(TAG, "Matching contact: " + cursor.getString(2));
  }
} finally {
  cursor.close();
}

یک مخاطب کاری را نشان دهید

برنامه‌های در حال اجرا در نمایه شخصی می‌توانند یک کارت تماس را در نمایه کاری نشان دهند. برای راه‌اندازی برنامه Contacts در نمایه کاری و نمایش کارت مخاطب، با ContactsContract.QuickContact.showQuickContact() در Android نسخه 5.0 یا بالاتر تماس بگیرید.

برای ایجاد یک URI صحیح برای نمایه کاری، باید با ContactsContract.Contacts.getLookupUri() تماس بگیرید و شناسه مخاطب و کلید جستجو را ارسال کنید. مثال زیر نشان می دهد که چگونه می توانید URI را دریافت کنید و سپس کارت را نشان دهید:

کاتلین

// Query the content provider using the ENTERPRISE_CONTENT_FILTER_URI address.
// We use the _ID and LOOKUP_KEY columns to generate a work-profile URI.
val cursor =
  getContentResolver()
    .query(
      contentFilterUri,
      arrayOf(ContactsContract.Contacts._ID, ContactsContract.Contacts.LOOKUP_KEY),
      null,
      null
    )

// Show the contact details card in the work profile's Contacts app. The URI
// must be created with getLookupUri().
cursor?.use {
  if (it.moveToFirst() == true) {
    val uri = ContactsContract.Contacts.getLookupUri(it.getLong(0), it.getString(1))
    ContactsContract.QuickContact.showQuickContact(
      activity,
      Rect(20, 20, 100, 100),
      uri,
      ContactsContract.QuickContact.MODE_LARGE,
      null
    )
  }
}

جاوا

// Query the content provider using the ENTERPRISE_CONTENT_FILTER_URI address.
// We use the _ID and LOOKUP_KEY columns to generate a work-profile URI.
Cursor cursor = getContentResolver().query(
    contentFilterUri,
    new String[] {
        ContactsContract.Contacts._ID,
        ContactsContract.Contacts.LOOKUP_KEY,
    },
    null,
    null,
    null);
if (cursor == null) {
  return;
}

// Show the contact details card in the work profile's Contacts app. The URI
// must be created with getLookupUri().
try {
  if (cursor.moveToFirst() == true) {
    Uri uri = ContactsContract.Contacts.getLookupUri(
        cursor.getLong(0), cursor.getString(1));
    ContactsContract.QuickContact.showQuickContact(
        getActivity(),
        new Rect(20, 20, 100, 100),
        uri,
        ContactsContract.QuickContact.MODE_LARGE,
        null);
  }
} finally {
  cursor.close();
}

در دسترس بودن

جدول زیر به طور خلاصه نشان می دهد که کدام نسخه اندروید از اطلاعات تماس نمایه کاری در نمایه شخصی پشتیبانی می کند:

نسخه اندروید پشتیبانی کنید
5.0 (سطح API 21) با استفاده از PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI ، نام مخاطبین محل کار را برای شماره تلفن جستجو کنید.
6.0 (سطح API 23) با استفاده از Email.ENTERPRISE_CONTENT_LOOKUP_URI ، نام مخاطبین محل کار را برای آدرس‌های ایمیل جستجو کنید.
7.0 (سطح API 24) با استفاده از Contacts.ENTERPRISE_CONTENT_FILTER_URI ، نام مخاطبین کار را از فهرست‌های کاری جستجو کنید.
با استفاده از Directory.ENTERPRISE_CONTENT_URI ، همه فهرست‌های راهنمای موجود در نمایه‌های کاری و شخصی را فهرست کنید.

توسعه و آزمایش

برای ایجاد نمایه کاری، مراحل زیر را دنبال کنید:

  1. برنامه Test DPC ما را نصب کنید.
  2. برنامه Set up Test DPC (نه نماد برنامه Test DPC) را باز کنید.
  3. دستورالعمل های روی صفحه را برای تنظیم نمایه مدیریت شده دنبال کنید.
  4. در نمایه کاری، برنامه مخاطبین را باز کنید و چند نمونه از مخاطبین را اضافه کنید.

برای شبیه‌سازی یک سرپرست فناوری اطلاعات که دسترسی به مخاطبین نمایه کاری را مسدود می‌کند، این مراحل را دنبال کنید:

  1. در نمایه کاری، برنامه Test DPC را باز کنید.
  2. تنظیمات جستجوی غیرفعال کردن مخاطبین نمایه متقابل یا غیرفعال کردن شناسه تماس گیرنده نمایه متقابل را جستجو کنید.
  3. تنظیم را روی روشن قرار دهید.

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

منابع اضافی

برای کسب اطلاعات بیشتر درباره مخاطبین یا نمایه کاری، به این منابع مراجعه کنید: