Arbeitsprofilkontakte

In diesem Entwicklerhandbuch wird erläutert, wie Sie Ihre App so optimieren können, dass Kontaktdaten aus dem Arbeitsprofil verwendet werden. Wenn Sie die Contact APIs von Android noch nicht verwendet haben, lesen Sie sich diesen Artikel durch, um sich mit den APIs vertraut zu machen.

Übersicht

Auf Geräten mit Arbeitsprofilen werden die Kontakte für das Arbeitsprofil und das private Profil in separaten lokalen Verzeichnissen gespeichert. Wenn eine App im privaten Profil ausgeführt wird, werden standardmäßig nicht die geschäftlichen Kontakte angezeigt. Eine App kann jedoch auf Kontaktinformationen aus dem Arbeitsprofil zugreifen. Ein Beispiel dafür ist die Android Kontakte App von Google, die in den Suchergebnissen sowohl persönliche Kontakte als auch Kontakte aus dem Arbeitsverzeichnis anzeigt.

Nutzer verwenden oft private Geräte und Apps für die Arbeit. Durch die Verwendung von Kontakten aus dem Arbeitsprofil kann Ihre Anwendung Teil des Arbeitstags Ihrer Nutzer werden.

Nutzererfahrung

Überlegen Sie, wie Ihre App Kontaktinformationen aus dem Arbeitsprofil darstellen könnte. Der beste Ansatz hängt von der Art Ihrer App und dem Zweck ab, für den sie verwendet wird. Bedenken Sie jedoch Folgendes:

  • Soll Ihre App standardmäßig Kontakte aus dem Arbeitsprofil enthalten oder sollte der Nutzer zustimmen?
  • Wie wirkt sich das Mischen oder Trennen von Kontakten aus dem beruflichen und dem privaten Profil auf den Ablauf für Nutzer aus?
  • Was passiert, wenn Sie versehentlich auf einen Kontakt im Arbeitsprofil tippen?
  • Was passiert mit der Benutzeroberfläche Ihrer App, wenn die Kontakte im Arbeitsprofil nicht verfügbar sind?

In Ihrer App muss eindeutig ein Kontakt für das Arbeitsprofil angegeben werden. Vielleicht können Sie den Kontakt mit einem bekannten Arbeitssymbol kennzeichnen, z. B. einer Aktentasche.

Screenshot mit Suchergebnissen in einer Liste
Abbildung 1: So trennt die Google Kontakte App Kontakte aus Arbeitsprofilen

Die Google Kontakte App (siehe Abbildung 1) führt beispielsweise folgende Schritte aus, um eine Mischung aus Kontakten aus dem geschäftlichen und privaten Profil aufzulisten:

  1. Fügt eine Zwischenüberschrift ein, um berufliche und persönliche Abschnitte der Liste zu trennen.
  2. Kennzeichen Sie geschäftliche Kontakte mit einem Aktentaschensymbol.
  3. Öffnet einen geschäftlichen Kontakt im Arbeitsprofil, wenn Sie darauf tippen.

Wenn die Person, die das Gerät verwendet, das Arbeitsprofil deaktiviert, kann Ihre App keine Kontaktdaten aus dem Arbeitsprofil oder den Remote-Kontaktverzeichnissen der Organisation abrufen. Je nachdem, wie Sie Kontakte im Arbeitsprofil verwenden, können Sie diese Kontakte unbemerkt weglassen oder die UI-Steuerelemente deaktivieren.

Berechtigungen

Wenn Ihre App bereits mit den Kontakten des Nutzers zusammenarbeitet, haben Sie die Berechtigung READ_CONTACTS (oder möglicherweise WRITE_CONTACTS), die Sie in der Manifestdatei Ihrer App angefordert haben. Da dieselbe Person das private und das Arbeitsprofil verwendet, benötigen Sie keine weitere Berechtigung, um über das Arbeitsprofil auf die Kontaktdaten zuzugreifen.

Ein IT-Administrator kann die Freigabekontaktdaten des Arbeitsprofils für das private Profil blockieren. Wenn ein IT-Administrator den Zugriff blockiert, werden Ihre Kontaktsuchen als leere Ergebnisse zurückgegeben. Ihre Anwendung muss keine bestimmten Fehler verarbeiten, wenn der Nutzer das Arbeitsprofil deaktiviert hat. Der Anbieter von Verzeichnisinhalten gibt weiterhin Informationen zu den geschäftlichen Kontaktverzeichnissen des Nutzers zurück (siehe Verzeichnisse). Informationen zum Testen dieser Berechtigungen finden Sie im Abschnitt Entwicklung und Tests.

Suche nach Kontakten

Sie können Kontakte aus dem Arbeitsprofil mit denselben APIs und Prozessen abrufen, die Ihre App verwendet, um Kontakte im privaten Profil abzurufen. Der Unternehmens-URI für Kontakte wird ab Android 7.0 (API-Level 24) unterstützt. Nehmen Sie die folgenden Anpassungen am URI vor:

  1. Legen Sie als Contentanbieter-URI Contacts.ENTERPRISE_CONTENT_FILTER_URI fest und geben Sie den Namen des Kontakts als Abfragestring an.
  2. Legen Sie ein zu durchsuchendes Kontaktverzeichnis fest. Beispielsweise sucht ENTERPRISE_DEFAULT nach Kontakten im lokalen Speicher des Arbeitsprofils.

Das Ändern des URI funktioniert mit jedem Contentanbieter-Mechanismus wie CursorLoader. Ideal, um Kontaktdaten in Benutzeroberflächen zu laden, da der Datenzugriff auf einem Worker-Thread erfolgt. Der Einfachheit halber wird in den Beispielen in diesem Leitfaden ContentResolver.query() aufgerufen. So finden Sie Kontakte im lokalen Kontaktverzeichnis des Arbeitsprofils:

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

Verzeichnisse

Viele Organisationen verwenden Remote-Verzeichnisse wie Microsoft Exchange oder LDAP, die Kontaktdaten für die gesamte Organisation enthalten. Ihre Anwendung kann Nutzern helfen, mit Arbeitskollegen zu kommunizieren und Inhalte mit Arbeitskollegen zu teilen, die im Verzeichnis ihrer Organisation zu finden sind. Beachten Sie, dass diese Verzeichnisse normalerweise Tausende von Kontakten enthalten und Ihre App auch eine aktive Netzwerkverbindung benötigt, um sie zu durchsuchen. Mit dem Contentanbieter Directory können Sie die Verzeichnisse abrufen, die von den Nutzerkonten verwendet werden, und weitere Informationen zu einem einzelnen Verzeichnis abrufen.

Fragen Sie den Contentanbieter Directory.ENTERPRISE_CONTENT_URI ab, um Verzeichnisse aus dem privaten Profil und dem Arbeitsprofil abzurufen, die zusammen zurückgegeben werden. Die Suche in Arbeitsprofil-Verzeichnissen wird ab Android 7.0 (API-Level 24) unterstützt. Der Nutzer Ihrer App muss weiterhin READ_CONTACTS die Berechtigung erteilen, mit seinen Kontaktverzeichnissen zu arbeiten.

Da Android Kontaktdaten in verschiedenen Arten von lokalen und Remote-Verzeichnissen speichert, verfügt die Directory-Klasse über Methoden, die Sie aufrufen können, um mehr über ein Verzeichnis zu erfahren:

isEnterpriseDirectoryId()
Rufen Sie diese Methode auf, um herauszufinden, ob das Verzeichnis aus einem Arbeitsprofil-Konto stammt. Denken Sie daran, dass der Contentanbieter ENTERPRISE_CONTENT_URI Kontaktverzeichnisse für das private und das Arbeitsprofil zusammen zurückgibt.
isRemoteDirectoryId()
Rufen Sie diese Methode auf, um herauszufinden, ob es sich um ein Remote-Verzeichnis handelt. Remote-Verzeichnisse können Kontaktspeicher von Unternehmen oder die sozialen Netzwerke des Nutzers sein.

Das folgende Beispiel zeigt, wie Sie mit diesen Methoden nach Arbeitsprofilen filtern können:

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

Im Beispiel werden die ID und der Paketname für das Verzeichnis abgerufen. Damit eine Benutzeroberfläche angezeigt wird, die Nutzern bei der Auswahl einer Kontaktverzeichnisquelle hilft, müssen Sie möglicherweise weitere Informationen über das Verzeichnis abrufen. Informationen zu weiteren möglicherweise verfügbaren Metadatenfeldern finden Sie in der Klassenreferenz zu Directory.

Telefonsuche

Apps können PhoneLookup.CONTENT_FILTER_URI abfragen, um effizient Kontaktdaten für eine Telefonnummer abzurufen. Sie können Suchergebnisse sowohl von privaten als auch vom Arbeitsprofilkontaktanbieter abrufen, wenn Sie diesen URI durch PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI ersetzen. Dieser Inhalts-URI für das Arbeitsprofil ist ab Android 5.0 (API-Level 21) verfügbar.

Das folgende Beispiel zeigt eine Anwendung, die den Inhalts-URI des Arbeitsprofils abfragt, um die Benutzeroberfläche für einen eingehenden Anruf zu konfigurieren:

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

E-Mail-Suche

Ihre App kann private oder geschäftliche Kontaktdaten für eine E-Mail-Adresse durch Abfrage von Email.ENTERPRISE_CONTENT_LOOKUP_URI abrufen. Bei der Abfrage dieser URL wird zuerst in den persönlichen Kontakten nach einer genauen Übereinstimmung gesucht. Wenn der Anbieter keine persönlichen Kontakte findet, sucht er bei Arbeitskontakten nach einer Übereinstimmung. Dieser URI ist ab Android 6.0 (API-Level 23) verfügbar.

So können Sie die Kontaktdaten für eine E-Mail-Adresse nachschlagen:

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

Geschäftlichen Kontakt anzeigen

Für Apps, die im privaten Profil ausgeführt werden, kann im Arbeitsprofil eine Kontaktkarte angezeigt werden. Rufen Sie in Android 5.0 oder höher ContactsContract.QuickContact.showQuickContact() auf, um die Kontakte App im Arbeitsprofil zu starten und die Karte des Kontakts anzuzeigen.

Zum Generieren eines korrekten URI für das Arbeitsprofil müssen Sie ContactsContract.Contacts.getLookupUri() aufrufen und eine Kontakt-ID und einen Suchschlüssel übergeben. Das folgende Beispiel zeigt, wie Sie den URI abrufen und dann die Karte einblenden können:

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

Verfügbarkeit

In der folgenden Tabelle ist zusammengefasst, welche Android-Versionen die Kontaktdaten des Arbeitsprofils im privaten Profil unterstützen:

Android-Version Support
5.0 (API-Level 21) Mit PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI können Sie nach geschäftlichen Kontakten für Telefonnummern suchen.
6.0 (API-Level 23) Mit Email.ENTERPRISE_CONTENT_LOOKUP_URI können Sie nach den Namen geschäftlicher Kontakte für E-Mail-Adressen suchen.
7.0 (API-Level 24) Sie können die Namen von geschäftlichen Kontakten mit Contacts.ENTERPRISE_CONTENT_FILTER_URI abfragen.
Mit Directory.ENTERPRISE_CONTENT_URI lassen sich alle Verzeichnisse im Arbeitsprofil und im privaten Profil auflisten.

Entwicklung und Tests

So erstellen Sie ein Arbeitsprofil:

  1. Installieren Sie die Test DPC App.
  2. Öffnen Sie die App Set up Test DPC (Test-DPC einrichten) und nicht das Symbol der Test DPC App.
  3. Folgen Sie der Anleitung auf dem Bildschirm, um ein verwaltetes Profil einzurichten.
  4. Öffnen Sie im Arbeitsprofil die Kontakte App und fügen Sie Beispielkontakte hinzu.

So simulieren Sie, dass ein IT-Administrator den Zugriff auf Kontakte im Arbeitsprofil blockiert:

  1. Öffnen Sie im Arbeitsprofil die App DPC testen.
  2. Suchen Sie nach der Einstellung Profilübergreifende Kontaktsuche deaktivieren oder Profilübergreifende Anrufer-ID deaktivieren.
  3. Setzen Sie die Einstellung auf An.

Weitere Informationen zum Testen Ihrer App mit Arbeitsprofilen finden Sie unter App auf Kompatibilität mit Arbeitsprofilen testen.

Weitere Informationen

Weitere Informationen zu Kontakten oder zum Arbeitsprofil finden Sie hier: