Device Trust from Android Enterprise - Panduan integrasi

Dokumen ini harus menjadi panduan utama Anda dalam menggunakan AMAPI SDK untuk tujuan menerima sinyal kepercayaan perangkat.

AMAPI SDK memungkinkan aplikasi Anda mengakses sinyal kepercayaan perangkat dari aplikasi Kebijakan Perangkat Android (ADP). Aplikasi Anda kemudian dapat menggunakan sinyal ini untuk menghitung status kepercayaan perangkat dan menerapkan logika bisnis sesuai pilihan.

Prasyarat

  • Aplikasi Anda harus diizinkan untuk membaca sinyal kepercayaan perangkat:
    • Aplikasi pendamping di perangkat yang dikelola AMAPI: Aplikasi yang diberi peran COMPANION_APP dalam kebijakan dapat mengakses sinyal kepercayaan perangkat tanpa berada dalam daftar yang diizinkan di perangkat terkelola sepenuhnya atau dari dalam profil kerja (dengan akses terbatas ke sinyal dari profil kerja). Untuk mengetahui detailnya, lihat Mengelola peran aplikasi, Konfigurasi Kebijakan, dan Mengakses sinyal dari aplikasi pendamping.
    • Semua aplikasi dan lingkungan lainnya: Mengakses sinyal kepercayaan perangkat dari dalam profil pribadi, di perangkat yang tidak dikelola, di perangkat yang dikelola oleh DPC kustom, atau tanpa peran COMPANION_APP mengharuskan aplikasi Anda berada dalam daftar yang diizinkan. Untuk mengetahui informasi tentang cara mendaftar, buka halaman akses sinyal kepercayaan perangkat.
  • Android Enterprise merekomendasikan untuk mengintegrasikan rangkaian API Play Integrity ke dalam aplikasi klien Anda dan merujuk ke hasilnya sebelum membaca dan mengandalkan sinyal kepercayaan perangkat. Perangkat yang gagal dalam pemeriksaan Play Integrity API tidak boleh dipercaya, begitu juga dengan sinyal apa pun yang berasal dari perangkat yang digunakan untuk menentukan postur kepercayaan. Anda dapat membaca dokumentasi Play Integrity untuk mengetahui detail selengkapnya.

Mengintegrasikan dengan AMAPI SDK di aplikasi Anda

Untuk mengakses sinyal kepercayaan perangkat, aplikasi Anda harus terintegrasi dengan AMAPI SDK. Anda dapat menemukan informasi selengkapnya tentang library ini dan cara menambahkannya ke aplikasi Anda di panduan integrasi AMAPI SDK.

Menambahkan izin yang diperlukan

Beberapa sinyal yang ditampilkan dari Device Trust from Android Enterprise API mengharuskan aplikasi mendeklarasikan izin yang sama dengan yang diperlukan untuk mengakses informasi ini sejak awal, khususnya:

Sinyal Izin yang diperlukan
Status Jaringan ACCESS_NETWORK_STATE
Kompleksitas kunci layar REQUEST_PASSWORD_COMPLEXITY

Jika izin ini tidak disertakan dalam AndroidManifest.xml aplikasi, Device Trust from Android Enterprise API akan menampilkan PERMISSION_ISSUE dalam metadata sinyal terkait:

internalDeviceSettings=DeviceSettings{
  screenLockComplexity=COMPLEXITY_UNSPECIFIED,
  internalScreenLockComplexityMetadata=Metadata{
    dataIssues=[
      DataIssue{
        issueType=PERMISSION_ISSUE,
        issueLevel=WARNING,
        issueDetails=IssueDetailsCase{none}
      }
    ]
  },

Untuk mengetahui detail tambahan, lihat daftar sinyal kepercayaan perangkat yang tersedia.

Langkah-langkah untuk mengakses sinyal kepercayaan perangkat

Aplikasi dalam daftar yang diizinkan (termasuk aplikasi yang berjalan di dalam profil pribadi atau di perangkat yang tidak dikelola) harus memverifikasi bahwa lingkungan klien sudah yang terbaru dan menyiapkannya jika perlu sebelum meminta sinyal.

Langkah-langkah untuk aplikasi dalam daftar yang diizinkan untuk mengakses sinyal kepercayaan perangkat adalah:

Langkah-langkah untuk mengakses sinyal kepercayaan perangkat

Memverifikasi lingkungan klien

Contoh kode berikut menunjukkan cara menggunakan getEnvironment untuk membaca status aplikasi ADP saat ini. Aplikasi Anda kemudian dapat menggunakan instance deviceClient yang di-cache untuk mengakses sinyal kepercayaan perangkat jika lingkungan sudah siap dan terbaru (lihat Mengakses sinyal kepercayaan perangkat).

Kotlin

import com.google.android.managementapi.common.model.Role
import com.google.android.managementapi.device.DeviceClient
import com.google.android.managementapi.device.DeviceClientFactory
import com.google.android.managementapi.device.model.GetDeviceRequest
import com.google.android.managementapi.environment.EnvironmentClient
import com.google.android.managementapi.environment.EnvironmentClientFactory
import com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.INSTALLED
import com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.NOT_INSTALLED
import com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.READY
import com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.Version.UP_TO_DATE
import com.google.android.managementapi.environment.model.GetEnvironmentRequest
import com.google.android.managementapi.environment.model.PrepareEnvironmentRequest

try {
    val context = applicationContext

    val roles = listOf(Role.builder().setRoleType(Role.RoleType.IDENTITY_PROVIDER).build())
    val request = GetEnvironmentRequest.builder().setRoles(roles).build()
    val environmentClient = EnvironmentClientFactory.create(context)
    val environmentResponse = environmentClient.getEnvironment(request)

    if (environmentResponse.hasAndroidDevicePolicyEnvironment()) {
        val adpEnvironment = environmentResponse.androidDevicePolicyEnvironment

        if (adpEnvironment.state == READY && adpEnvironment.version == UP_TO_DATE) {
            // AMAPI Environment State OK, Version OK. Requesting Device signals..
            checkDevice(deviceClient)
        } else if (adpEnvironment.state == INSTALLED) {
            // prepareEnvironment should be called, calling
            // prepareEnvironment won't show the UI
            prepareEnvironment(context, environmentClient)
        } else if (adpEnvironment.state == NOT_INSTALLED) {
            // prepareEnvironment should be called, calling
            // prepareEnvironment will show the UI
            prepareEnvironment(context, environmentClient)
        }
    }
} catch (e: Exception) {
    Log.e(TAG, "Exception", e)
}

Java

import static com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.INSTALLED;
import static com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.NOT_INSTALLED;
import static com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.READY;
import static com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.Version.UP_TO_DATE;

import com.google.android.managementapi.common.model.Role;
import com.google.android.managementapi.device.DeviceClient;
import com.google.android.managementapi.device.DeviceClientFactory;
import com.google.android.managementapi.device.model.Device;
import com.google.android.managementapi.device.model.GetDeviceRequest;
import com.google.android.managementapi.environment.EnvironmentClient;
import com.google.android.managementapi.environment.EnvironmentClientFactory;
import com.google.android.managementapi.environment.model.Environment;
import com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment;
import com.google.android.managementapi.environment.model.GetEnvironmentRequest;
import com.google.android.managementapi.environment.model.PrepareEnvironmentRequest;
import com.google.android.managementapi.environment.model.PrepareEnvironmentResponse;

try {
    Context context = getApplicationContext();

    ImmutableList roles = new ImmutableList.Builder()
            .add(Role.builder()
                    .setRoleType(Role.RoleType.IDENTITY_PROVIDER)
                    .build())
            .build();

    EnvironmentClient environmentClient = EnvironmentClientFactory.create(context);
    GetEnvironmentRequest request = GetEnvironmentRequest.builder()
            .setRoles(roles)
            .build();

    ListenableFuture getEnvironmentFuture = environmentClient.getEnvironmentAsync(request);
    Futures.addCallback(getEnvironmentFuture, new FutureCallback<>() {
        @Override
        public void onSuccess(Environment environment) {
            AndroidDevicePolicyEnvironment adpEnvironment = environment.getAndroidDevicePolicyEnvironment();

            AndroidDevicePolicyEnvironment.State state = adpEnvironment.getState();
            AndroidDevicePolicyEnvironment.Version version = adpEnvironment.getVersion();
            if (state == READY && version == UP_TO_DATE) {
                // AMAPI Environment State OK, Version OK. Requesting Device signals..
                checkDevice(deviceClient);
            } else if (state == INSTALLED) {
                // prepareEnvironment should be called, calling
                // prepareEnvironment won't show the UI
                prepareEnvironment(context, environmentClient);
            } else if (state == NOT_INSTALLED) {
                // prepareEnvironment should be called, calling
                // prepareEnvironment will show the UI
                prepareEnvironment(context, environmentClient);
            }
        }

        @Override
        public void onFailure(Throwable t) {
            Log.d(TAG, t.toString());
        }
    }, MoreExecutors.directExecutor());
} catch (Exception e) {
    Log.d(TAG, e.toString());
}

Jika aplikasi ADP diinstal, tetapi tidak diupdate, aplikasi Anda harus memanggil prepareEnvironment untuk mengupdate aplikasi ADP secara otomatis tanpa intervensi pengguna.

Jika aplikasi ADP tidak diinstal, aplikasi Anda dapat memanggil prepareEnvironment untuk meminta pengguna menginstal aplikasi ADP. Lihat Menyiapkan lingkungan klien.

Menyiapkan lingkungan klien

  • Jika aplikasi ADP sudah diinstal, API akan mengupdatenya secara otomatis tanpa intervensi pengguna.

  • Jika aplikasi ADP tidak diinstal, API akan meminta pengguna untuk menyetujui penginstalan aplikasi ADP.

Instal Android Device Policy

Anda dapat mendaftarkan callback untuk memantau pilihan pengguna. Lihat Melacak interaksi pengguna selama penginstalan aplikasi ADP untuk mengetahui detail tambahan.

Sebaiknya panggilan prepareEnvironment dilakukan dari proses latar depan, selama alur UX orientasi agar pengguna tidak terkejut dengan dialog modal Instal Android Device Policy. Jika panggilan dari proses latar depan tidak memungkinkan, karena ini adalah alur Web dan komponen Android tidak memiliki UI, panggilan dari latar belakang diizinkan dengan persyaratan bahwa hal ini terjadi selama alur UX orientasi.

Setelah lingkungan disiapkan dengan benar, Anda dapat mengakses Sinyal kepercayaan perangkat. Lihat Mengakses sinyal kepercayaan perangkat.

Kotlin

try {
    val myNotificationReceiverService = ComponentName(
        context, MyNotificationReceiverService::class.java
    )

    val roles = listOf(Role.builder().setRoleType(Role.RoleType.IDENTITY_PROVIDER).build())
    val request = PrepareEnvironmentRequest.builder().setRoles(roles).build()

    val response =
        environmentClient.prepareEnvironment(request, myNotificationReceiverService)

    val environment = response.environment
    val adpEnvironment = environment.androidDevicePolicyEnvironment

    val state = adpEnvironment.state
    val version = adpEnvironment.version
    if (state == READY && version == UP_TO_DATE) {
        // Environment is prepared, access device posture signals using
        // DeviceClient.
        checkDevice(deviceClient)
    } else {
        // The prepareEnvironment call failed to prepare
        Log.w(
            TAG, "AMAPI environment was not ready: " + state + " - " + version
        )
    }

} catch (e: java.lang.Exception) {
    Log.d(TAG, e.toString())
}

Java

try {
    ComponentName myNotificationReceiverService = new ComponentName(
            context,
            MyNotificationReceiverService.class
    );

    ImmutableList roles = new ImmutableList.Builder()
            .add(Role.builder()
                    .setRoleType(Role.RoleType.IDENTITY_PROVIDER)
                    .build())
            .build();


    PrepareEnvironmentRequest request = PrepareEnvironmentRequest.builder()
            .setRoles(roles)
            .build();

    ListenableFuture environmentFuture =
            environmentClient.prepareEnvironmentAsync(
                    request,
                    myNotificationReceiverService
            );

    Futures.addCallback(environmentFuture, new FutureCallback<>() {
        @Override
        public void onSuccess(PrepareEnvironmentResponse response) {
            Environment environment = response.getEnvironment();
            AndroidDevicePolicyEnvironment adpEnvironment = environment.getAndroidDevicePolicyEnvironment();

            AndroidDevicePolicyEnvironment.State state = adpEnvironment.getState();
            AndroidDevicePolicyEnvironment.Version version = adpEnvironment.getVersion();
            if (state == READY && version == UP_TO_DATE) {
                // AMAPI Environment State OK, Version OK. Requesting Device signals..
                checkDevice(deviceClient);
            } else {
                // The prepareEnvironment call failed to prepare
                Log.w(
                        TAG, "AMAPI environment was not ready: "
                        + adpEnvironment.getState() + " - " + adpEnvironment.getVersion()
                );
            }
        }

        @Override
        public void onFailure(@NonNull Throwable t) {
            // Handle the error
            Log.d(TAG, "AMAPI response did not contain an ADP environment");
        }
    }, MoreExecutors.directExecutor());
} catch (Exception e) {
    Log.d(TAG, e.toString());
}

Mengakses sinyal kepercayaan perangkat

Rekomendasi penggunaan DeviceClient

Membuat instance deviceClient (atau instance EnvironmentClient) akan membuat saluran komunikasi dengan aplikasi Kebijakan Perangkat Android (ADP) yang otomatis melepaskan binding saat tidak ada aktivitas. Karena instance klien tidak mengekspos metode penonaktifan eksplisit, membuat dan menghapus beberapa instance akan menyisakan saluran tanpa induk untuk pembersihan sampah memori.

Untuk mengoptimalkan performa dan menghindari saluran tanpa induk, buat satu instance DeviceClient di cakupan aplikasi menggunakan DeviceClientFactory.create(context.applicationContext) dan simpan dalam cache untuk digunakan kembali di seluruh aplikasi Anda—misalnya, sebagai singleton cakupan aplikasi atau menggunakan injeksi dependensi.

Untuk mengakses sinyal kepercayaan perangkat yang Anda minati, gunakan instance deviceClient yang di-cache untuk meminta objek Device:

Kotlin

runCatching {
    deviceClient.getDeviceAwait(GetDeviceRequest.getDefaultInstance())
}.onFailure { t ->
    Log.d(TAG, t.toString())
}.onSuccess { device ->
    // Access device posture signals available in device
    val deviceString = device.toString()
    Log.d(TAG, deviceString)
}

Java

ListenableFuture deviceFuture =
        deviceClient.getDevice(GetDeviceRequest.getDefaultInstance());

Futures.addCallback(deviceFuture, new FutureCallback() {
    @Override
    public void onSuccess(Device device) {
        // Access device posture signals available in device
        String deviceString = device.toString();
        Log.d(TAG, deviceString);
    }

    @Override
    public void onFailure(Throwable t) {
        Log.d(TAG, t.toString());
    }
}, MoreExecutors.directExecutor());

Mengakses sinyal dari aplikasi pendamping

Di perangkat terkelola sepenuhnya atau dari dalam profil kerja yang dikelola dengan Android Management API (AMAPI), aplikasi yang diberi peran COMPANION_APP dapat membaca sinyal kepercayaan perangkat tanpa terdaftar di daftar yang diizinkan.

Saat dipanggil dari dalam profil kerja (baik di perangkat milik perusahaan maupun milik pribadi), aplikasi Anda harus berjalan di dalam profil kerja, dan akses dibatasi ke sinyal dari profil kerja (misalnya, status pengelolaan, kompleksitas kunci layar, dan aplikasi yang diinstal hanya mencerminkan profil kerja).

1. Menetapkan peran COMPANION_APP dalam kebijakan

Konfigurasikan kebijakan EMM Anda untuk memberikan roleType: "COMPANION_APP" ke aplikasi Anda dalam daftar kebijakan applications:

2. Membaca sinyal langsung dengan DeviceClient

Buat dan simpan satu instance deviceClient dalam cache pada cakupan aplikasi menggunakan DeviceClientFactory.create (lihat Rekomendasi penggunaan DeviceClient), lalu minta objek Device secara langsung:

Kotlin

import android.content.Context
import android.util.Log
import com.google.android.managementapi.device.DeviceClient
import com.google.android.managementapi.device.DeviceClientFactory
import com.google.android.managementapi.device.model.GetDeviceRequest

// Instantiate once at application scope (for example, using dependency injection)
// to reuse a single DeviceClient instance across your app.
class DeviceTrustRepository(context: Context) {
    private val deviceClient: DeviceClient =
        DeviceClientFactory.create(context.applicationContext)

    suspend fun readSignalsFromCompanionApp() {
        runCatching {
            deviceClient.getDeviceAwait(GetDeviceRequest.getDefaultInstance())
        }.onSuccess { device ->
            Log.d(TAG, "Device signals: $device")
        }.onFailure { t ->
            Log.e(TAG, "Failed to read device signals", t)
        }
    }

    companion object {
        private const val TAG = "DeviceTrustRepository"
    }
}

Java

import android.content.Context;
import android.util.Log;
import com.google.android.managementapi.device.DeviceClient;
import com.google.android.managementapi.device.DeviceClientFactory;
import com.google.android.managementapi.device.model.Device;
import com.google.android.managementapi.device.model.GetDeviceRequest;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;

// Instantiate once at application scope (for example, using dependency injection)
// to reuse a single DeviceClient instance across your app.
public class DeviceTrustRepository {
    private static final String TAG = "DeviceTrustRepository";
    private final DeviceClient deviceClient;

    public DeviceTrustRepository(Context context) {
        this.deviceClient = DeviceClientFactory.create(context.getApplicationContext());
    }

    public void readSignalsFromCompanionApp() {
        ListenableFuture deviceFuture =
                deviceClient.getDevice(GetDeviceRequest.getDefaultInstance());

        Futures.addCallback(deviceFuture, new FutureCallback() {
            @Override
            public void onSuccess(Device device) {
                Log.d(TAG, "Device signals: " + device);
            }

            @Override
            public void onFailure(Throwable t) {
                Log.e(TAG, "Failed to read device signals", t);
            }
        }, MoreExecutors.directExecutor());
    }
}

Menangani error

Jika aplikasi Anda membuat permintaan kepercayaan perangkat dan panggilan gagal, aplikasi Anda akan menerima pengecualian. Pengecualian ini dapat terjadi karena berbagai alasan, seperti:

Strategi percobaan ulang

Untuk pengecualian karena kondisi sementara, terapkan strategi percobaan ulang dengan backoff eksponensial. Setelah kegagalan pertama, mulailah dengan penundaan awal selama 5 detik sebelum mencoba lagi.

Terapkan strategi percobaan ulang dengan jumlah maksimum upaya sebagai kondisi keluar menggunakan penundaan yang meningkat secara eksponensial setiap kali (10 dtk, 20 dtk, dll.).

Melacak interaksi pengguna selama penginstalan aplikasi ADP

Jika perangkat perlu menginstal aplikasi ADP selama prepareEnvironment, aplikasi Anda dapat melacak interaksi pengguna yang menerapkan NotificationReceiverService untuk menerima notifikasi yang menggantikan getPrepareEnvironmentListener:

Kotlin

import android.util.Log
import com.google.android.managementapi.environment.EnvironmentListener
import com.google.android.managementapi.environment.model.EnvironmentEvent.EventCase.Kind.ANDROID_DEVICE_POLICY_INSTALL_CONSENT_ACCEPTED
import com.google.android.managementapi.environment.model.EnvironmentEvent
import com.google.android.managementapi.notification.NotificationReceiverService

class MyNotificationReceiverService : NotificationReceiverService() {
    override fun getPrepareEnvironmentListener(): EnvironmentListener {
        return MyEnvironmentListener()
    }
}

class MyEnvironmentListener : EnvironmentListener {
    override fun onEnvironmentEvent(
        event: EnvironmentEvent
    ) {
        if (event.event.kind == ANDROID_DEVICE_POLICY_INSTALL_CONSENT_ACCEPTED) {
            Log.d(TAG, "User provided install consent")
        } else {
            Log.d(TAG, "User rejected install consent")
        }
    }

    companion object {
        private val TAG: String = MyEnvironmentListener::class.java.simpleName
    }
}

Java

import static com.google.android.managementapi.environment.model.EnvironmentEvent.EventCase.Kind.ANDROID_DEVICE_POLICY_INSTALL_CONSENT_ACCEPTED;

import android.util.Log;
import androidx.annotation.NonNull;
import com.google.android.managementapi.environment.EnvironmentListener;
import com.google.android.managementapi.environment.model.EnvironmentEvent;
import com.google.android.managementapi.notification.NotificationReceiverService;

class MyNotificationReceiverService extends NotificationReceiverService {
    @NonNull
    @Override
    protected EnvironmentListener getPrepareEnvironmentListener() {
        return new MyEnvironmentListener();
    }
}
class MyEnvironmentListener implements EnvironmentListener {
    final private String TAG = MyEnvironmentListener.class.getSimpleName();

    @Override
    public void onEnvironmentEvent(EnvironmentEvent event) {
        if (event.getEvent().getKind() == ANDROID_DEVICE_POLICY_INSTALL_CONSENT_ACCEPTED)
        {
            Log.d(TAG, "User provided install consent");
        } else {
            Log.d(TAG, "User rejected install consent");
        }
    }
}

Masalah umum

Saat ini tidak ada masalah umum.