/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.devicelockcontroller.provision.grpc; import android.content.Context; import android.content.SharedPreferences; import android.net.ConnectivityManager; import android.os.Build; import android.os.UserHandle; import android.util.ArraySet; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; import com.android.devicelockcontroller.common.DeviceId; import com.android.devicelockcontroller.common.DeviceLockConstants.DeviceProvisionState; import com.android.devicelockcontroller.common.DeviceLockConstants.PauseDeviceProvisioningReason; import com.android.devicelockcontroller.common.DeviceLockConstants.ProvisionFailureReason; import com.android.devicelockcontroller.provision.grpc.impl.DeviceCheckInClientImpl; import com.android.devicelockcontroller.util.LogUtil; import io.grpc.ClientInterceptor; /** * An abstract class that's intended for implementation of class that manages communication with * DeviceLock backend server. */ public abstract class DeviceCheckInClient { private static final String TAG = "DeviceCheckInClient"; private static final String FILENAME = "debug-check-in-preferences"; public static final String DEVICE_CHECK_IN_CLIENT_DEBUG_CLASS_NAME = "com.android.devicelockcontroller.debug.DeviceCheckInClientDebug"; protected static final String DEBUG_DEVICELOCK_CHECKIN = "debug.devicelock.checkin"; private static final String HOST_NAME_OVERRIDE = "host.name.override"; private static volatile DeviceCheckInClient sClient; @Nullable protected static String sRegisteredId; protected static String sHostName = ""; protected static int sPortNumber = 0; private static volatile boolean sUseDebugClient; @Nullable private static volatile SharedPreferences sSharedPreferences; @Nullable protected static synchronized SharedPreferences getSharedPreferences( @Nullable Context context) { if (sSharedPreferences == null && context != null) { sSharedPreferences = context.createContextAsUser(UserHandle.SYSTEM, /* flags= */ 0).createDeviceProtectedStorageContext().getSharedPreferences(FILENAME, Context.MODE_PRIVATE); } return sSharedPreferences; } /** * Override the host name so that the client always connects to it instead */ public static void setHostNameOverride(Context context, String override) { getSharedPreferences(context).edit().putString(HOST_NAME_OVERRIDE, override).apply(); } /** * Get an instance of DeviceCheckInClient object. */ public static DeviceCheckInClient getInstance( Context context, String hostName, int portNumber, ClientInterceptor clientInterceptor, @Nullable String registeredId) { boolean useDebugClient = false; String hostNameOverride = ""; if (Build.isDebuggable()) { useDebugClient = getSharedPreferences(context).getBoolean( DEBUG_DEVICELOCK_CHECKIN, /* def= */ false); hostNameOverride = getSharedPreferences(context).getString( HOST_NAME_OVERRIDE, /* def= */ ""); if (!hostNameOverride.isEmpty()) { hostName = hostNameOverride; } } synchronized (DeviceCheckInClient.class) { try { boolean createRequired = (sClient == null || sUseDebugClient != useDebugClient) || (registeredId != null && !registeredId.equals(sRegisteredId)) || (hostName != null && !hostName.equals(sHostName)); if (createRequired) { if (sClient != null) { sClient.cleanUp(); } sHostName = hostName; sPortNumber = portNumber; sRegisteredId = registeredId; sUseDebugClient = useDebugClient; if (Build.isDebuggable() && sUseDebugClient) { final String className = DEVICE_CHECK_IN_CLIENT_DEBUG_CLASS_NAME; LogUtil.d(TAG, "Creating instance for " + className); Class clazz = Class.forName(className); sClient = (DeviceCheckInClient) clazz.getDeclaredConstructor().newInstance(); } else { sClient = new DeviceCheckInClientImpl(clientInterceptor, context.getSystemService(ConnectivityManager.class)); } } } catch (Exception e) { throw new RuntimeException("Failed to get DeviceCheckInClient instance", e); } } return sClient; } /** * Check In with DeviceLock backend server and get the next step for the device * * @param deviceIds A set of all device unique identifiers, this could include IMEIs, * MEIDs, etc. * @param carrierInfo The information of the device's sim operator which is used to * determine the device's geological location and eventually * eligibility of the DeviceLock program. * @param fcmRegistrationToken The fcm registration token * @return A class that encapsulate the response from the backend server. */ @WorkerThread public abstract GetDeviceCheckInStatusGrpcResponse getDeviceCheckInStatus( ArraySet deviceIds, String carrierInfo, @Nullable String fcmRegistrationToken); /** * Check if the device is in an approved country for the device lock program. * * @param carrierInfo The information of the device's sim operator which is used to determine * the device's geological location and eventually eligibility of the * DeviceLock program. Could be null if unavailable. * @return A class that encapsulate the response from the backend server. */ @WorkerThread public abstract IsDeviceInApprovedCountryGrpcResponse isDeviceInApprovedCountry( @Nullable String carrierInfo); /** * Inform the server that device provisioning has been paused for a certain amount of time. * * @param reason The reason that provisioning has been paused. * @return A class that encapsulate the response from the backend sever. */ @WorkerThread public abstract PauseDeviceProvisioningGrpcResponse pauseDeviceProvisioning( @PauseDeviceProvisioningReason int reason); /** * Reports the current provision state of the device. * * @param lastReceivedProvisionState one of {@link DeviceProvisionState}. * It must be the value from the response when this API * was called last time. If this API is called for the first * time, then * {@link * DeviceProvisionState#PROVISION_STATE_UNSPECIFIED } * must be used. * @param isSuccessful true if the device has been setup for DeviceLock program * successful; false otherwise. * @return A class that encapsulate the response from the backend server. */ @WorkerThread public abstract ReportDeviceProvisionStateGrpcResponse reportDeviceProvisionState( @DeviceProvisionState int lastReceivedProvisionState, boolean isSuccessful, @ProvisionFailureReason int failureReason); /** * Called when this device check in client is no longer in use and should clean up its * resources. */ public void cleanUp() {}; }