Kontakty w profilu służbowym

Z tego przewodnika dla programistów dowiesz się, jak możesz wykorzystać dane kontaktowe z profilu służbowego w swojej aplikacji. Jeśli nie korzystasz jeszcze z interfejsów API kontaktów na Androidzie, przeczytaj artykuł Dostawca kontaktów, aby zapoznać się z tymi interfejsami.

Przegląd

Urządzenia z profilami służbowymi przechowują kontakty w osobnych katalogach lokalnych dla profilu służbowego i osobistego. Domyślnie, gdy aplikacja działa w profilu osobistym, nie wyświetlają się kontakty służbowe. Aplikacja ma jednak dostęp do danych kontaktowych z poziomu profilu służbowego. Dotyczy to na przykład aplikacji Kontakty na Androida, która wyświetla w wynikach wyszukiwania zarówno kontakty osobiste, jak i kontakty z katalogu służbowego.

Użytkownicy często chcą używać w pracy własnych urządzeń i aplikacji. Dzięki kontaktom w profilu służbowym Twoja aplikacja może stać się częścią dnia pracy użytkownika.

Z perspektywy użytkownika

Zastanów się, jak aplikacja może prezentować informacje kontaktowe z profilu służbowego. Wybór najlepszej metody zależy od charakteru Twojej aplikacji i powodu jej używania. Weź jednak pod uwagę te kwestie:

  • Czy aplikacja ma domyślnie zawierać kontakty z profilu służbowego, czy też użytkownik powinien wyrazić na to zgodę?
  • Jak mieszanie i oddzielanie kontaktów służbowych i osobistych wpływa na przepływ pracy użytkownika?
  • Jakie są skutki przypadkowego dotknięcia kontaktu z profilu służbowego?
  • Co się dzieje z interfejsem aplikacji, gdy nie są dostępne kontakty w profilu służbowym?

Aplikacja powinna wyraźnie wskazywać kontakt w profilu służbowym. Możesz oznaczyć kontakt plakietką znajomą, np. aktówką.

Zrzut ekranu przedstawiający wyniki wyszukiwania w formie listy
Rysunek 1. Jak aplikacja Kontakty Google rozdziela kontakty w profilu służbowym

Na przykład aplikacja Kontakty Google (pokazana na ilustracji 1) wykonuje te czynności, aby wyświetlić połączenie kontaktów służbowych i osobistych:

  1. Wstawia podtytuł, aby oddzielić sekcje służbowe i osobiste na liście.
  2. Plakietki kontaktów służbowych z ikoną aktówki.
  3. Otwiera kontakt służbowy w profilu służbowym.

Jeśli osoba używająca urządzenia wyłączy profil służbowy, aplikacja nie będzie w stanie wyszukać informacji kontaktowych w tym profilu ani w katalogach kontaktów zdalnych organizacji. W zależności od tego, jak używasz kontaktów w profilu służbowym, możesz je zignorować. Może być też konieczne wyłączenie opcji w interfejsie.

Uprawnienia

Jeśli Twoja aplikacja współpracuje już z kontaktami użytkownika, będziesz mieć uprawnienie READ_CONTACTS (lub ewentualnie WRITE_CONTACTS), o które prosisz w pliku manifestu aplikacji. Ponieważ ta sama osoba korzysta z profilu osobistego i profilu służbowego, nie potrzebujesz dalszych uprawnień, aby uzyskać dostęp do danych kontaktowych z profilu służbowego.

Administrator IT może zablokować profil służbowy udostępniający informacje kontaktowe dla profilu osobistego. Jeśli administrator IT zablokuje dostęp, wyszukiwania kontaktów będą wyświetlane jako puste wyniki. Aplikacja nie musi obsługiwać konkretnych błędów, gdy użytkownik wyłączył profil służbowy. Dostawca treści katalogów nadal zwraca informacje o katalogach służbowych użytkownika (patrz sekcja Katalogi). Informacje o tym, jak przetestować te uprawnienia, znajdziesz w sekcji Programowanie i testowanie.

Wyszukiwanie kontaktów

Możesz pobrać kontakty z profilu służbowego za pomocą tych samych interfejsów API i procesów, których aplikacja używa do pobierania kontaktów do profilu osobistego. Identyfikator URI firmy dla kontaktów jest obsługiwany w Androidzie 7.0 (poziom API 24) i nowszych. Musisz wprowadzić te zmiany w identyfikatorze URI:

  1. Ustaw identyfikator URI dostawcy treści Contacts.ENTERPRISE_CONTENT_FILTER_URI i podaj nazwę kontaktu jako ciąg zapytania.
  2. Ustaw katalog kontaktów do przeszukania. Na przykład ENTERPRISE_DEFAULT znajduje kontakty w lokalnym magazynie profilu służbowego.

Zmiana identyfikatora URI działa ze wszystkimi mechanizmami dostawców treści, np. CursorLoader. Jest to idealne rozwiązanie w przypadku wczytywania danych kontaktów do interfejsów, ponieważ dostęp do danych ma miejsce w wątku roboczym. Dla uproszczenia przykłady w tym przewodniku wywołaj ContentResolver.query(). Aby znaleźć kontakty w lokalnym katalogu kontaktów profilu służbowego:

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

Katalogi

Wiele organizacji korzysta z katalogów zdalnych, takich jak Microsoft Exchange czy LDAP, które zawierają informacje kontaktowe dla całej organizacji. Twoja aplikacja może ułatwić użytkownikom komunikowanie się z pracownikami i udostępnianie im treści, które znajdują się w katalogu ich organizacji. Pamiętaj, że te katalogi zawierają zwykle tysiące kontaktów, a aplikacja wymaga aktywnego połączenia sieciowego do ich wyszukiwania. Możesz skorzystać z dostawcy treści Directory, aby pobrać katalogi używane przez konta użytkowników i dowiedzieć się więcej o poszczególnych katalogach.

Zapytanie do dostawcy treści Directory.ENTERPRISE_CONTENT_URI pozwoli Ci pobrać katalogi z profilu osobistego i z profilu służbowego. Wyszukiwanie katalogów profilu służbowego jest obsługiwane w Androidzie 7.0 (poziom interfejsu API 24) i nowszych. Aplikacja nadal wymaga od użytkownika przyznania READ_CONTACTS uprawnień do korzystania z katalogów kontaktów.

Android przechowuje informacje kontaktowe w różnych typach katalogów lokalnych i zdalnych, dlatego klasa Directory zawiera metody, które możesz wywołać, aby znaleźć więcej informacji o katalogu:

isEnterpriseDirectoryId()
Wywołaj tę metodę, aby sprawdzić, czy katalog pochodzi z konta w profilu służbowym. Pamiętaj, że dostawca treści ENTERPRISE_CONTENT_URI zwraca razem katalogi kontaktów dla profilu osobistego i służbowego.
isRemoteDirectoryId()
Wywołaj tę metodę, aby sprawdzić, czy katalog jest zdalny. Katalogi zdalne mogą obejmować firmowe bazy kontaktów lub sieci społecznościowe użytkownika.

Z przykładu poniżej dowiesz się, jak za pomocą tych metod filtrować katalogi profili służbowych:

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

Przykład pobiera identyfikator i nazwę pakietu z katalogu. Aby wyświetlić interfejs użytkownika, który ułatwia użytkownikom wybór źródła z katalogu kontaktów, konieczne może być pobranie dodatkowych informacji o tym katalogu. Aby zobaczyć inne pola metadanych, które mogą być dostępne, przeczytaj dokumentację klas Directory.

Wyszukiwania telefonów

Aplikacje mogą wysyłać zapytania do PhoneLookup.CONTENT_FILTER_URI, aby sprawnie wyszukiwać dane kontaktowe pod kątem numeru telefonu. Wyniki wyszukiwania możesz uzyskać od dostawcy kontaktów z profilu osobistego i służbowego, jeśli zastąpisz ten identyfikator URI ciągiem PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI. Ten identyfikator URI treści w profilu służbowym jest dostępny na urządzeniach z Androidem 5.0 (poziom interfejsu API 21) i nowszym.

Oto przykład aplikacji, która wysyła zapytanie do identyfikatora URI treści profilu służbowego, aby skonfigurować interfejs użytkownika pod kątem połączenia przychodzącego:

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

Wyszukiwanie e-maili

Twoja aplikacja może uzyskać osobiste i służbowe dane kontaktowe dla adresu e-mail, wysyłając zapytanie Email.ENTERPRISE_CONTENT_LOOKUP_URI. Zapytanie o ten adres URL przeszukuje kontakty osobiste pod kątem dopasowania ścisłego. Jeśli dostawca nie odpowiada żadnym kontaktom osobistym, szuka odpowiedników w kontaktach służbowych. Ten identyfikator URI jest dostępny w Androidzie 6.0 (poziom interfejsu API 23) i nowszych.

Aby wyszukać informacje kontaktowe dla danego adresu e-mail:

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

Pokaż kontakt służbowy

Aplikacje uruchomione w profilu osobistym mogą wyświetlać kartę kontaktu w profilu służbowym. Zadzwoń na ContactsContract.QuickContact.showQuickContact() na Androidzie 5.0 lub nowszym, aby uruchomić aplikację Kontakty w profilu służbowym i wyświetlić kartę kontaktu.

Aby wygenerować prawidłowy identyfikator URI profilu służbowego, wywołaj metodę ContactsContract.Contacts.getLookupUri() i przekaż identyfikator kontaktu oraz klucz wyszukiwania. Z przykładu poniżej dowiesz się, jak uzyskać identyfikator URI, a następnie wyświetlić kartę:

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

Dostępność

W tabeli poniżej znajdziesz podsumowanie wersji Androida, które obsługują dane kontaktowe z profilu służbowego w profilu osobistym:

Wersja Androida Pomoc
5.0 (poziom API 21) Wyszukaj imiona i nazwiska osób kontaktowych do pracy pod kątem numerów telefonów, korzystając z narzędzia PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI.
6.0 (poziom API 23) Wyszukaj nazwy kontaktów służbowych na potrzeby adresów e-mail za pomocą Email.ENTERPRISE_CONTENT_LOOKUP_URI.
7.0 (poziom API 24) Wyślij zapytania o nazwy kontaktów służbowych w katalogach służbowych, używając Contacts.ENTERPRISE_CONTENT_FILTER_URI.
Wyświetl listę wszystkich katalogów w profilu służbowym i osobistym, używając narzędzia Directory.ENTERPRISE_CONTENT_URI.

Programowanie i testowanie

Aby utworzyć profil służbowy, wykonaj te czynności:

  1. Zainstaluj naszą aplikację Test DPC.
  2. Otwórz aplikację Skonfiguruj Test DPC (nie ikonę aplikacji Test DPC).
  3. Postępuj zgodnie z instrukcjami wyświetlanymi na ekranie, aby skonfigurować profil zarządzany.
  4. W profilu służbowym otwórz aplikację Kontakty i dodaj przykładowe kontakty.

Aby zasymulować administratora IT blokującego dostęp do kontaktów w profilu służbowym, wykonaj te czynności:

  1. W profilu służbowym otwórz aplikację Test DPC.
  2. Wyszukaj ustawienie Wyłącz wyszukiwanie kontaktów między profilami lub Wyłącz identyfikację rozmówcy w różnych profilach.
  3. Włącz tę opcję.

Więcej informacji o testowaniu aplikacji z profilami służbowymi znajdziesz w artykule Testowanie zgodności aplikacji z profilami służbowymi.

Dodatkowe materiały

Więcej informacji o kontaktach i profilu służbowym znajdziesz w tych materiałach: