Địa chỉ liên hệ trên hồ sơ công việc

Hướng dẫn dành cho nhà phát triển này giải thích cách bạn có thể cải thiện ứng dụng của mình để sử dụng dữ liệu liên hệ từ hồ sơ công việc. Nếu bạn chưa từng sử dụng API danh bạ của Android, hãy đọc bài viết Trình cung cấp danh bạ để làm quen với các API đó.

Tổng quan

Các thiết bị có hồ sơ công việc lưu trữ danh bạ trong các thư mục cục bộ riêng biệt dành cho hồ sơ công việc và hồ sơ cá nhân. Theo mặc định, khi một ứng dụng chạy trong hồ sơ cá nhân, ứng dụng đó sẽ không hiển thị danh bạ công việc. Tuy nhiên, ứng dụng có thể truy cập vào thông tin liên hệ từ hồ sơ công việc. Ví dụ: một ứng dụng thực hiện việc này là ứng dụng Danh bạ Android của Google. Ứng dụng này hiển thị cả danh bạ cá nhân và danh bạ công việc trong kết quả tìm kiếm.

Người dùng thường muốn sử dụng các thiết bị và ứng dụng cá nhân cho công việc. Bằng cách sử dụng danh bạ trên hồ sơ công việc, ứng dụng của bạn có thể trở thành một phần trong ngày làm việc của người dùng.

Trải nghiệm người dùng

Hãy cân nhắc cách ứng dụng của bạn có thể trình bày thông tin liên hệ từ hồ sơ công việc. Phương pháp tốt nhất phụ thuộc vào bản chất của ứng dụng và lý do mọi người sử dụng ứng dụng đó, nhưng hãy cân nhắc những điều sau:

  • Theo mặc định, ứng dụng của bạn nên bao gồm danh bạ trong hồ sơ công việc hay người dùng nên chọn sử dụng?
  • Việc kết hợp hoặc tách biệt danh bạ hồ sơ công việc và hồ sơ cá nhân sẽ ảnh hưởng như thế nào đến luồng của người dùng?
  • Ảnh hưởng của việc vô tình nhấn vào một người liên hệ trong hồ sơ công việc là gì?
  • Điều gì sẽ xảy ra với giao diện của ứng dụng khi danh bạ hồ sơ công việc không có sẵn?

Ứng dụng của bạn phải chỉ rõ người liên hệ trong hồ sơ công việc. Bạn có thể gắn huy hiệu cho người liên hệ đó bằng một biểu tượng công việc quen thuộc (chẳng hạn như một chiếc cặp tài liệu).

Ảnh chụp màn hình cho thấy kết quả tìm kiếm trong một danh sách
Hình 1. Cách ứng dụng Danh bạ Google phân tách người liên hệ trong hồ sơ công việc

Ví dụ: ứng dụng Danh bạ Google (minh hoạ trong hình 1) thực hiện những việc sau để liệt kê cả danh bạ hồ sơ công việc và hồ sơ cá nhân:

  1. Chèn tiêu đề phụ để tách các phần công việc và cá nhân trong danh sách.
  2. Huy hiệu danh bạ công việc đi kèm biểu tượng cặp tài liệu.
  3. Mở một thông tin liên hệ công việc trong hồ sơ công việc khi bạn nhấn vào.

Nếu người sử dụng thiết bị tắt hồ sơ công việc, thì ứng dụng của bạn sẽ không thể tra cứu thông tin liên hệ từ hồ sơ công việc hoặc danh bạ liên hệ từ xa của tổ chức. Tuỳ thuộc vào cách bạn sử dụng danh bạ trên hồ sơ công việc, bạn có thể tự động bỏ qua những người liên hệ đó hoặc có thể cần tắt các chế độ điều khiển giao diện người dùng.

Quyền

Nếu ứng dụng của bạn đang hoạt động với danh bạ của người dùng, thì bạn sẽ thấy quyền READ_CONTACTS (hoặc có thể là WRITE_CONTACTS) của họ mà bạn đã yêu cầu trong tệp kê khai ứng dụng. Vì cùng một người sử dụng hồ sơ cá nhân và hồ sơ công việc, nên bạn không cần thêm quyền để truy cập vào dữ liệu người liên hệ từ hồ sơ công việc.

Quản trị viên CNTT có thể chặn việc chia sẻ hồ sơ công việc với hồ sơ cá nhân chia sẻ thông tin liên hệ. Nếu quản trị viên CNTT chặn quyền truy cập, thì các lượt tìm kiếm người liên hệ của bạn sẽ được trả về dưới dạng kết quả trống. Ứng dụng của bạn không cần xử lý các lỗi cụ thể nếu người dùng tắt hồ sơ công việc. Nhà cung cấp nội dung thư mục sẽ tiếp tục trả về thông tin về các danh bạ liên hệ công việc của người dùng (xem phần Thư mục). Để kiểm thử những quyền này, hãy xem phần Phát triển và kiểm thử.

Số lượt tìm kiếm người liên hệ

Bạn có thể lấy danh bạ từ hồ sơ công việc bằng cách sử dụng chính các API và quy trình mà ứng dụng của bạn dùng để lấy danh bạ trong hồ sơ cá nhân. URI doanh nghiệp cho mục liên hệ được hỗ trợ trong Android 7.0 (API cấp 24) trở lên. Bạn cần thực hiện các điều chỉnh sau đây đối với URI:

  1. Đặt URI của nhà cung cấp nội dung thành Contacts.ENTERPRISE_CONTENT_FILTER_URI và cung cấp tên của địa chỉ liên hệ dưới dạng chuỗi truy vấn.
  2. Đặt một danh bạ người liên hệ để tìm kiếm. Ví dụ: ENTERPRISE_DEFAULT tìm các người liên hệ trong cửa hàng địa phương của hồ sơ công việc.

Việc thay đổi URI hoạt động với mọi cơ chế của nhà cung cấp nội dung, chẳng hạn như CursorLoader – lý tưởng để tải dữ liệu liên hệ vào giao diện người dùng vì hoạt động truy cập dữ liệu diễn ra trên luồng thực thi. Để cho đơn giản, các ví dụ trong hướng dẫn này sẽ gọi ContentResolver.query(). Dưới đây là cách bạn có thể tìm người liên hệ trong thư mục người liên hệ cục bộ của hồ sơ công việc:

Kotlin

// 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)}")
  }
}

Java

// 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();
}

Thư mục

Nhiều tổ chức sử dụng thư mục từ xa, chẳng hạn như Microsoft Exchange hoặc LDAP, chứa thông tin liên hệ của toàn bộ tổ chức. Ứng dụng của bạn có thể giúp người dùng giao tiếp và chia sẻ với những đồng nghiệp làm việc có trong thư mục của tổ chức của họ. Xin lưu ý rằng các thư mục này thường chứa hàng nghìn người liên hệ và ứng dụng của bạn cũng cần kết nối mạng đang hoạt động để tìm kiếm họ. Bạn có thể sử dụng nhà cung cấp nội dung Directory để lấy các thư mục mà tài khoản của người dùng sử dụng và tìm hiểu thêm về một thư mục riêng lẻ.

Truy vấn nhà cung cấp nội dung Directory.ENTERPRISE_CONTENT_URI để lấy các thư mục từ hồ sơ cá nhân và hồ sơ công việc được trả về cùng nhau. Tính năng tìm kiếm thư mục hồ sơ công việc được hỗ trợ trong Android 7.0 (API cấp 24) trở lên. Ứng dụng của bạn vẫn cần người dùng cấp các quyền READ_CONTACTS để làm việc với thư mục liên hệ của họ.

Vì Android lưu trữ thông tin liên hệ trong các loại thư mục cục bộ và từ xa, nên lớp Directory có các phương thức mà bạn có thể gọi để tìm thêm về một thư mục:

isEnterpriseDirectoryId()
Gọi phương thức này để tìm hiểu xem thư mục có phải từ tài khoản hồ sơ công việc hay không. Hãy nhớ rằng trình cung cấp nội dung ENTERPRISE_CONTENT_URI sẽ trả về thư mục liên hệ cho hồ sơ cá nhân và hồ sơ công việc cùng nhau.
isRemoteDirectoryId()
Gọi phương thức này để tìm hiểu xem thư mục có ở xa hay không. Các thư mục từ xa có thể là kho thông tin liên hệ của doanh nghiệp hoặc có thể là mạng xã hội của người dùng.

Ví dụ sau cho thấy cách bạn có thể dùng các phương thức này để lọc thư mục hồ sơ công việc:

Kotlin

// 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)}")
    }
  }
}

Java

// 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();
}

Ví dụ này sẽ tìm nạp mã nhận dạng và tên gói cho thư mục. Để hiển thị giao diện người dùng giúp người dùng chọn một nguồn thư mục liên hệ, bạn có thể cần tìm nạp thêm thông tin về thư mục đó. Để xem các trường siêu dữ liệu khác có thể có, hãy đọc tài liệu tham khảo về lớp Directory.

Tra cứu điện thoại

Các ứng dụng có thể truy vấn PhoneLookup.CONTENT_FILTER_URI để tra cứu hiệu quả dữ liệu liên hệ về một số điện thoại. Bạn có thể nhận kết quả tra cứu từ cả nhà cung cấp danh bạ cá nhân lẫn hồ sơ công việc nếu thay thế URI này bằng PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI. URI nội dung hồ sơ công việc này có trong Android 5.0 (API cấp 21) trở lên.

Ví dụ sau đây cho thấy một ứng dụng đang truy vấn URI nội dung hồ sơ công việc để định cấu hình giao diện người dùng cho cuộc gọi đến:

Kotlin

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
}

Java

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;
}

Tra cứu email

Ứng dụng của bạn có thể lấy dữ liệu liên hệ cá nhân hoặc công việc cho một địa chỉ email bằng cách truy vấn Email.ENTERPRISE_CONTENT_LOOKUP_URI. Truy vấn URL này trước tiên sẽ tìm kiếm địa chỉ liên hệ cá nhân để tìm kết quả khớp chính xác. Nếu nhà cung cấp không khớp với bất kỳ danh bạ cá nhân nào, thì nhà cung cấp sẽ tìm kiếm thông tin liên hệ công việc để tìm kết quả trùng khớp. URI này có trong Android 6.0 (API cấp 23) trở lên.

Dưới đây là cách bạn có thể tra cứu thông tin liên hệ của một địa chỉ email:

Kotlin

// 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)}")
  }
}

Java

// 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();
}

Hiển thị thông tin liên hệ tại nơi làm việc

Các ứng dụng chạy trong hồ sơ cá nhân có thể hiển thị thẻ liên hệ trong hồ sơ công việc. Gọi ContactsContract.QuickContact.showQuickContact() trong Android 5.0 trở lên để chạy ứng dụng Danh bạ trong hồ sơ công việc và hiển thị thẻ của người liên hệ đó.

Để tạo một URI chính xác cho hồ sơ công việc, bạn cần gọi ContactsContract.Contacts.getLookupUri() và chuyển mã liên hệ cũng như khoá tra cứu. Ví dụ sau cho thấy cách bạn có thể lấy URI rồi hiển thị thẻ:

Kotlin

// 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
    )
  }
}

Java

// 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();
}

Phạm vi cung cấp

Bảng sau đây tóm tắt các phiên bản Android hỗ trợ dữ liệu người liên hệ trong hồ sơ công việc trong hồ sơ cá nhân:

Phiên bản Android Hỗ trợ
5.0 (API cấp 21) Tra cứu tên người liên hệ tại nơi làm việc cho các số điện thoại bằng PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI.
6.0 (API cấp 23) Tra cứu tên người liên hệ tại nơi làm việc theo địa chỉ email bằng Email.ENTERPRISE_CONTENT_LOOKUP_URI.
7.0 (API cấp 24) Truy vấn tên người liên hệ công việc từ các thư mục công việc bằng Contacts.ENTERPRISE_CONTENT_FILTER_URI.
Liệt kê tất cả thư mục trong hồ sơ công việc và hồ sơ cá nhân bằng Directory.ENTERPRISE_CONTENT_URI.

Phát triển và kiểm thử

Để tạo hồ sơ công việc, hãy làm theo các bước sau:

  1. Cài đặt ứng dụng Test DPC (Kiểm thử DPC).
  2. Mở ứng dụng Set up Test DPC (Thiết lập ứng dụng Test DPC (không phải biểu tượng ứng dụng Test DPC).
  3. Làm theo hướng dẫn trên màn hình để thiết lập hồ sơ được quản lý.
  4. Trong hồ sơ công việc, hãy mở ứng dụng Danh bạ và thêm một số mục liên hệ mẫu.

Để mô phỏng việc quản trị viên CNTT chặn quyền truy cập vào danh bạ trong hồ sơ công việc, hãy làm theo các bước sau:

  1. Trong hồ sơ công việc, hãy mở ứng dụng Test DPC (Kiểm thử DPC).
  2. Tìm chế độ cài đặt Tắt tính năng tìm kiếm người liên hệ trên nhiều hồ sơ hoặc chế độ cài đặt Tắt tên nhận dạng người gọi trên nhiều hồ sơ.
  3. Chuyển chế độ cài đặt này sang trạng thái Bật.

Để tìm hiểu thêm về cách kiểm thử ứng dụng bằng hồ sơ công việc, vui lòng đọc bài viết Kiểm thử ứng dụng để đảm bảo khả năng tương thích với hồ sơ công việc.

Tài nguyên khác

Để tìm hiểu thêm về danh bạ hoặc hồ sơ công việc, hãy xem các tài nguyên sau: