Device Trust from Android Enterprise – Hướng dẫn tích hợp

Tài liệu này là hướng dẫn chính để bạn sử dụng SDK AMAPI cho mục đích nhận tín hiệu tin cậy của thiết bị.

SDK AMAPI cho phép ứng dụng của bạn truy cập vào các tín hiệu tin cậy của thiết bị từ ứng dụng Chính sách thiết bị Android (ADP). Sau đó, ứng dụng của bạn có thể sử dụng các tín hiệu này để tính toán trạng thái tin cậy của thiết bị và thực thi logic kinh doanh đã chọn.

Điều kiện tiên quyết

  • Ứng dụng của bạn phải được uỷ quyền để đọc tín hiệu tin cậy của thiết bị:
    • Ứng dụng đồng hành trên các thiết bị do AMAPI quản lý: Các ứng dụng được chỉ định vai trò COMPANION_APP trong chính sách có thể truy cập vào tín hiệu tin cậy của thiết bị mà không cần có trong danh sách cho phép trên các thiết bị được quản lý hoàn toàn hoặc từ bên trong hồ sơ công việc (với quyền truy cập chỉ giới hạn ở các tín hiệu từ hồ sơ công việc). Để biết thông tin chi tiết, hãy xem phần Quản lý vai trò của ứng dụng, Cấu hình chính sách và Truy cập tín hiệu từ ứng dụng đồng hành.
    • Tất cả các ứng dụng và môi trường khác: Để truy cập vào tín hiệu tin cậy của thiết bị từ bên trong hồ sơ cá nhân, trên thiết bị không được quản lý, trên thiết bị do DPC tuỳ chỉnh quản lý hoặc không có vai trò COMPANION_APP, ứng dụng của bạn phải nằm trong danh sách cho phép. Để biết thông tin về cách đăng ký, hãy truy cập vào trang quyền truy cập vào tín hiệu tin cậy của thiết bị.
  • Android Enterprise đề xuất bạn tích hợp bộ API Play Integrity vào ứng dụng khách và tham khảo kết quả trước khi đọc và dựa vào tín hiệu tin cậy của thiết bị. Không nên tin tưởng những thiết bị không vượt qua quy trình kiểm tra của Play Integrity API, cũng như không nên tin tưởng bất kỳ tín hiệu nào bắt nguồn từ thiết bị được dùng để xác định trạng thái tin cậy. Bạn có thể tham khảo tài liệu về Play Integrity để biết thêm thông tin chi tiết.

Tích hợp với SDK AMAPI trong ứng dụng của bạn

Để truy cập vào tín hiệu tin cậy của thiết bị, ứng dụng của bạn phải tích hợp với SDK AMAPI. Bạn có thể tìm thêm thông tin về thư viện này và cách thêm thư viện vào ứng dụng trong hướng dẫn tích hợp SDK AMAPI.

Thêm các quyền bắt buộc

Một số tín hiệu do API Device Trust from Android Enterprise trả về yêu cầu ứng dụng phải khai báo cùng một quyền cần thiết để truy cập vào thông tin này ngay từ đầu, cụ thể là:

Tín hiệu Quyền bắt buộc
Trạng thái mạng ACCESS_NETWORK_STATE
Độ phức tạp của phương thức khoá màn hình REQUEST_PASSWORD_COMPLEXITY

Nếu các quyền này không có trong tệp AndroidManifest.xml của ứng dụng, thì API Device Trust from Android Enterprise sẽ trả về PERMISSION_ISSUE trong siêu dữ liệu của tín hiệu liên quan:

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

Để biết thêm thông tin, hãy xem danh sách các tín hiệu tin cậy hiện có của thiết bị.

Các bước để truy cập vào tín hiệu tin cậy của thiết bị

Các ứng dụng trong danh sách cho phép (bao gồm cả ứng dụng chạy trong hồ sơ cá nhân hoặc trên thiết bị không được quản lý) phải xác minh rằng môi trường ứng dụng khách đã được cập nhật và chuẩn bị môi trường đó (nếu cần) trước khi yêu cầu tín hiệu.

Các bước để ứng dụng trong danh sách cho phép truy cập vào tín hiệu tin cậy của thiết bị là:

Các bước để truy cập vào tín hiệu tin cậy của thiết bị

Xác minh môi trường ứng dụng khách

Đoạn mã ví dụ sau đây cho thấy cách sử dụng getEnvironment để đọc trạng thái hiện tại của ứng dụng ADP. Sau đó, ứng dụng của bạn có thể sử dụng một thực thể deviceClient được lưu vào bộ nhớ đệm để truy cập vào các tín hiệu tin cậy của thiết bị nếu môi trường đã sẵn sàng và được cập nhật (xem phần Truy cập vào các tín hiệu tin cậy của thiết bị).

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

Nếu ứng dụng ADP đã được cài đặt nhưng chưa được cập nhật, thì ứng dụng của bạn phải gọi prepareEnvironment để tự động cập nhật ứng dụng ADP mà không cần người dùng can thiệp.

Nếu ứng dụng ADP chưa được cài đặt, thì ứng dụng của bạn có thể gọi prepareEnvironment để nhắc người dùng cài đặt ứng dụng ADP. Hãy xem phần Chuẩn bị môi trường ứng dụng.

Chuẩn bị môi trường ứng dụng khách

  • Nếu ứng dụng ADP đã được cài đặt, API sẽ tự động cập nhật ứng dụng mà không cần người dùng can thiệp.

  • Nếu ứng dụng ADP chưa được cài đặt, API sẽ nhắc người dùng chấp nhận cài đặt ứng dụng ADP.

Cài đặt Android Device Policy

Bạn có thể đăng ký một lệnh gọi lại để theo dõi sự lựa chọn của người dùng. Hãy xem bài viết Theo dõi hoạt động tương tác của người dùng trong quá trình cài đặt ứng dụng ADP để biết thêm thông tin chi tiết.

Bạn nên thực hiện lệnh gọi prepareEnvironment từ một quy trình trên nền trước, trong quy trình trải nghiệm người dùng khi làm quen để tránh làm người dùng bất ngờ với hộp thoại phương thức Cài đặt Android Device Policy. Nếu không thể gọi từ một quy trình trên nền trước (vì đây là quy trình trên web và thành phần Android không có giao diện người dùng), thì bạn có thể gọi từ nền sau, với điều kiện là việc này phải diễn ra trong quy trình trải nghiệm người dùng khi tham gia.

Sau khi thiết lập môi trường đúng cách, bạn có thể truy cập vào Tín hiệu tin cậy của thiết bị. Xem phần Truy cập vào tín hiệu tin cậy của thiết bị.

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

Truy cập vào tín hiệu tin tưởng thiết bị

Đề xuất về cách sử dụng DeviceClient

Việc tạo một thực thể deviceClient (hoặc thực thể EnvironmentClient) sẽ thiết lập một kênh giao tiếp với ứng dụng Android Device Policy (ADP) tự động huỷ liên kết khi rảnh. Vì các thực thể ứng dụng không hiển thị phương thức tắt rõ ràng, nên việc tạo và loại bỏ nhiều thực thể sẽ khiến các kênh mồ côi bị thu gom rác.

Để tối ưu hoá hiệu suất và tránh các kênh mồ côi, hãy tạo một thực thể duy nhất của DeviceClient ở phạm vi ứng dụng bằng cách sử dụng DeviceClientFactory.create(context.applicationContext) và lưu thực thể đó vào bộ nhớ đệm để sử dụng lại trong toàn bộ ứng dụng, chẳng hạn như một singleton trong phạm vi ứng dụng hoặc sử dụng tính năng chèn phần phụ thuộc.

Để truy cập vào các tín hiệu tin cậy của thiết bị mà bạn quan tâm, hãy sử dụng thực thể deviceClient đã lưu vào bộ nhớ đệm để yêu cầu đối tượng 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());

Truy cập tín hiệu từ một ứng dụng đồng hành

Trên các thiết bị được quản lý hoàn toàn hoặc từ bên trong hồ sơ công việc được quản lý bằng Android Management API (AMAPI), một ứng dụng được chỉ định vai trò COMPANION_APP có thể đọc các tín hiệu tin cậy của thiết bị mà không cần đăng ký trong danh sách cho phép.

Khi được gọi từ bên trong hồ sơ công việc (trên thiết bị do công ty sở hữu hoặc thiết bị thuộc sở hữu cá nhân), ứng dụng của bạn phải chạy bên trong hồ sơ công việc và quyền truy cập chỉ giới hạn ở các tín hiệu từ hồ sơ công việc (ví dụ: trạng thái quản lý, độ phức tạp của phương thức khoá màn hình và các ứng dụng đã cài đặt chỉ phản ánh hồ sơ công việc).

1. Chỉ định vai trò COMPANION_APP trong chính sách

Định cấu hình chính sách EMM để cấp roleType: "COMPANION_APP" cho ứng dụng của bạn trong danh sách chính sách applications:

2. Đọc tín hiệu trực tiếp bằng DeviceClient

Tạo và lưu một thực thể deviceClient vào bộ nhớ đệm trong phạm vi ứng dụng bằng cách sử dụng DeviceClientFactory.create (xem Đề xuất sử dụng DeviceClient), sau đó yêu cầu trực tiếp đối tượng Device:

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

Xử lý lỗi

Nếu ứng dụng của bạn đưa ra yêu cầu về độ tin cậy của thiết bị và lệnh gọi không thành công, thì ứng dụng sẽ nhận được một ngoại lệ. Những trường hợp ngoại lệ này có thể xảy ra vì nhiều lý do, chẳng hạn như:

Chiến lược thử lại

Đối với các trường hợp ngoại lệ do điều kiện tạm thời, hãy triển khai chiến lược thử lại bằng thuật toán thời gian đợi luỹ thừa. Sau lỗi đầu tiên, hãy bắt đầu với độ trễ ban đầu là 5 giây trước khi thử lại.

Triển khai chiến lược thử lại với số lần thử tối đa làm điều kiện thoát, sử dụng độ trễ tăng theo cấp số nhân mỗi lần (10 giây, 20 giây, v.v.).

Theo dõi hoạt động tương tác của người dùng trong quá trình cài đặt ứng dụng ADP

Nếu thiết bị cần cài đặt ứng dụng ADP trong prepareEnvironment, thì ứng dụng của bạn có thể theo dõi hoạt động tương tác của người dùng bằng cách triển khai NotificationReceiverService để nhận thông báo ghi đè 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");
        }
    }
}

Vấn đề đã biết

Hiện không có vấn đề nào đã biết.