직장 프로필 연락처

이 개발자 가이드에서는 연락처를 사용하도록 앱을 개선하는 방법을 설명합니다. 데이터를 가져올 수 있습니다. Android의 연락처 API를 사용한 적이 없는 경우 연락처 제공자를 읽고 살펴보겠습니다

개요

직장 프로필을 사용하는 기기는 연락처를 별도의 로컬에 저장합니다. 직장 및 개인 프로필의 디렉터리가 포함되어 있습니다. 기본적으로 애플리케이션이 실행될 때 직장 연락처를 표시하지 않습니다. 하지만 앱은 직장 프로필의 연락처 정보에 액세스할 수 없습니다. 예를 들어 이것은 Google의 Android 주소록 앱입니다. 직장 디렉토리 주소가 검색결과에 표시됩니다.

사용자는 개인 기기와 앱을 업무용으로 사용하길 원하는 경우가 많습니다. 사용 직장 프로필 연락처를 저장하는 경우 앱이 사용자의 근무일의 일부가 될 수 있습니다.

사용자 환경

앱이 직장 프로필의 연락처 정보를 어떻게 표시할 수 있을지 고려합니다. 가장 좋은 접근 방식은 앱의 특성과 사용자가 앱에서 사용하되 다음 사항을 고려하세요.

  • 앱에 직장 프로필 연락처가 기본적으로 포함되어 있는지 아니면 사용자가 선택하시겠습니까?
  • 직장 연락처와 개인 프로필 연락처를 혼합하거나 분리하면 어떻게 해야 할까요?
  • 실수로 직장 프로필 연락처를 탭하면 어떤 영향을 미치나요?
  • 직장 프로필 연락처가 없는 경우 앱 인터페이스는 어떻게 되나요? 사용할 수 있나요?

앱은 직장 프로필 연락처를 명확하게 표시해야 합니다. 배지를 획득할 수도 있습니다. 서류 가방과 같은 익숙한 직장 아이콘을 사용합니다.

목록으로 검색결과를 보여주는 스크린샷
그림 1. Google 주소록 앱에서 직장 프로필을 분리하는 방법 연락처
를 통해 개인정보처리방침을 정의할 수 있습니다.

예를 들어 Google 주소록 앱 (그림 1 참고)은 다음을 위해 다음을 실행합니다. 직장 및 개인 프로필 연락처를 모두 표시하려면 다음 단계를 따르세요.

  1. 목록에서 업무 섹션과 개인용 섹션을 구분하는 하위 헤더를 삽입합니다.
  2. 서류 가방 아이콘이 있는 직장 연락처 배지
  3. 탭하면 직장 프로필에서 직장 연락처가 열립니다.

기기 사용자가 직장 프로필을 사용 중지하면 앱에서 다음 작업을 할 수 없습니다. 직장 프로필이나 조직의 리모컨에서 연락처 정보 조회 확인할 수 있습니다. 직장 프로필 연락처를 사용하는 방법에 따라 다음 작업을 할 수 있습니다. 이러한 연락처는 자동으로 제외하지 않으면 사용자 인터페이스를 사용 중지해야 할 수 있습니다. 컨트롤을 탭합니다.

권한

앱이 이미 사용자의 연락처와 작업하고 있다면 READ_CONTACTS (또는 WRITE_CONTACTS) 권한이 있는지 확인합니다. 앱 매니페스트 파일에 있습니다. 동일한 사람이 개인 프로필과 직장을 사용하기 때문에 직장의 연락처 데이터에 액세스하기 위해 추가 권한이 필요하지 않습니다. 확인할 수 있습니다.

IT 관리자는 차단 개인 프로필과 연락처 정보를 공유하는 직장 프로필 IT 부서가 관리자가 액세스를 차단하면 연락처 검색이 빈 결과로 반환됩니다. 내 사용자가 작업을 끈 경우 앱이 특정 오류를 처리할 필요가 없음 확인할 수 있습니다. 디렉터리 콘텐츠 제공자는 계속해서 사용자의 직장 연락처 디렉토리 (디렉토리 섹션 참고)를 입력합니다. 이러한 권한을 테스트하려면 개발 및 테스트를 참고하세요. 섹션으로 이동합니다.

연락처 검색

직장 프로필에서 연락처를 가져올 때 개인 프로필의 연락처를 가져오는 데 사용되는 정보를 수집합니다. 엔터프라이즈 URI: 연락처는 Android 7.0 (API 수준 24) 이상에서 지원됩니다. 다음과 같이 URI를 다음과 같이 조정합니다.

  1. 콘텐츠 제공업체 URI 설정: Contacts.ENTERPRISE_CONTENT_FILTER_URI님, 연락처 이름을 쿼리 문자열로 제공합니다.
  2. 검색할 연락처 디렉터리를 설정하세요. 예를 들어 ENTERPRISE_DEFAULT가 직장에서 연락처를 찾습니다. 오프라인 매장의 이름을 입력하세요

URI 변경은 CursorLoader: 사용자 인터페이스에 연락처 데이터를 로드하는 데 적합한 이유는 다음과 같습니다. 데이터 액세스가 작업자 스레드에서 발생합니다 편의상 이 예시에서는 가이드 호출 ContentResolver.query() 다음과 같은 방법으로 직장 프로필의 로컬 연락처 디렉터리에 있는 연락처:

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

자바

// 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()
디렉터리가 원격인지 확인하려면 이 메서드를 호출합니다. 원격 디렉터리 사용자의 소셜 네트워크일 수 있습니다.

다음 예는 이러한 메서드를 사용하여 직장 프로필을 필터링하는 방법을 보여줍니다. 디렉터리:

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

자바

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

이 예시에서는 디렉터리의 ID와 패키지 이름을 가져옵니다. 사용자를 표시하려면 다음 단계를 따르세요. 인터페이스를 제공하는데 이 기능을 사용하려면 디렉터리에 대한 추가 정보를 가져옵니다. '포함'을 초과하는 다른 메타데이터 필드를 Directory 클래스 참조를 읽어보세요.

전화 조회

앱은 쿼리 가능 효율적으로 사용하려면 PhoneLookup.CONTENT_FILTER_URI 연락처 데이터를 검색할 수도 있습니다. 다음을 통해 조회 결과를 얻을 수 있습니다. 이 URI를 PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI 이 직장 프로필 콘텐츠 URI는 Android 5.0 (API 레벨 21) 또는 더 높습니다.

다음 예는 직장 프로필 콘텐츠 URI를 쿼리하는 앱을 보여줍니다. 수신 전화의 사용자 인터페이스를 구성합니다.

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
}

자바

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 이 URL을 쿼리하면 먼저 개인 연락처에서 정확히 일치하는 항목을 검색합니다. 만약 검색 공급자가 개인 연락처와 일치하지 않으면 직장 연락처를 찾습니다. 이 URI는 Android 6.0 (API 수준 23)에서 사용할 수 있습니다. 또는 그 이상일 수 있습니다.

이메일 주소의 연락처 정보를 조회하는 방법은 다음과 같습니다.

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

자바

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

직장 연락처 표시

개인 프로필에서 실행되는 앱은 직장 프로필에 연락처 카드를 표시할 수 있습니다. 전화걸기 ContactsContract.QuickContact.showQuickContact()인치 Android 5.0 이상: 직장 프로필에서 연락처 앱을 실행하고 연락처 카드를 탭합니다.

직장 프로필의 올바른 URI를 생성하려면 다음을 호출해야 합니다. ContactsContract.Contacts.getLookupUri()를 생성하고 연락처 ID와 조회 키가 있습니다. 다음 예는 URI를 가져오는 방법을 보여줍니다. 그런 다음 카드를 표시합니다.

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

자바

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

가용성

다음 표에는 직장 프로필을 지원하는 Android 버전이 요약되어 있습니다. 개인 프로필의 연락처 데이터:

Android 버전 지원
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. Test DPC 앱 설정 앱 (Test DPC 앱 아이콘 아님)을 엽니다.
  3. 화면에 표시된 안내에 따라 관리 프로필을 설정합니다.
  4. 직장 프로필에서 Contacts 앱을 열고 샘플 연락처를 추가합니다.

직장 프로필 연락처에 대한 액세스를 차단하는 IT 관리자를 시뮬레이션하려면 다음 단계를 따르세요.

  1. 직장 프로필에서 Test DPC 앱을 엽니다.
  2. 교차 프로필 연락처 검색 사용 중지 설정 또는 교차 프로필 발신번호 표시 설정을 사용 중지합니다.
  3. 설정을 사용으로 전환합니다.

직장 프로필로 앱을 테스트하는 방법을 자세히 알아보려면 앱 테스트 직장 프로필과의 호환성을 참고하세요.

추가 리소스

연락처 또는 직장 프로필에 관해 자세히 알아보려면 다음 리소스를 참고하세요.