1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.devicelockcontroller.provision.grpc; 18 19 import android.content.Context; 20 import android.content.SharedPreferences; 21 import android.net.ConnectivityManager; 22 import android.os.Build; 23 import android.os.UserHandle; 24 import android.util.ArraySet; 25 26 import androidx.annotation.NonNull; 27 import androidx.annotation.Nullable; 28 import androidx.annotation.WorkerThread; 29 30 import com.android.devicelockcontroller.common.DeviceId; 31 import com.android.devicelockcontroller.common.DeviceLockConstants.DeviceProvisionState; 32 import com.android.devicelockcontroller.common.DeviceLockConstants.PauseDeviceProvisioningReason; 33 import com.android.devicelockcontroller.common.DeviceLockConstants.ProvisionFailureReason; 34 import com.android.devicelockcontroller.provision.grpc.impl.DeviceCheckInClientImpl; 35 import com.android.devicelockcontroller.util.LogUtil; 36 37 import io.grpc.ClientInterceptor; 38 39 /** 40 * An abstract class that's intended for implementation of class that manages communication with 41 * DeviceLock backend server. 42 */ 43 public abstract class DeviceCheckInClient { 44 private static final String TAG = "DeviceCheckInClient"; 45 private static final String FILENAME = "debug-check-in-preferences"; 46 47 public static final String DEVICE_CHECK_IN_CLIENT_DEBUG_CLASS_NAME = 48 "com.android.devicelockcontroller.debug.DeviceCheckInClientDebug"; 49 protected static final String DEBUG_DEVICELOCK_CHECKIN = "debug.devicelock.checkin"; 50 private static final String HOST_NAME_OVERRIDE = "host.name.override"; 51 private static volatile DeviceCheckInClient sClient; 52 53 @Nullable 54 protected static String sRegisteredId; 55 protected static String sHostName = ""; 56 protected static int sPortNumber = 0; 57 private static volatile boolean sUseDebugClient; 58 59 @Nullable 60 private static volatile SharedPreferences sSharedPreferences; 61 62 @Nullable getSharedPreferences( @ullable Context context)63 protected static synchronized SharedPreferences getSharedPreferences( 64 @Nullable Context context) { 65 if (sSharedPreferences == null && context != null) { 66 sSharedPreferences = 67 context.createContextAsUser(UserHandle.SYSTEM, /* flags= */ 68 0).createDeviceProtectedStorageContext().getSharedPreferences(FILENAME, 69 Context.MODE_PRIVATE); 70 } 71 return sSharedPreferences; 72 } 73 74 /** 75 * Override the host name so that the client always connects to it instead 76 */ setHostNameOverride(Context context, String override)77 public static void setHostNameOverride(Context context, String override) { 78 getSharedPreferences(context).edit().putString(HOST_NAME_OVERRIDE, override).apply(); 79 } 80 81 /** 82 * Get an instance of DeviceCheckInClient object. 83 */ getInstance( Context context, String hostName, int portNumber, ClientInterceptor clientInterceptor, @Nullable String registeredId)84 public static DeviceCheckInClient getInstance( 85 Context context, 86 String hostName, 87 int portNumber, 88 ClientInterceptor clientInterceptor, 89 @Nullable String registeredId) { 90 boolean useDebugClient = false; 91 String hostNameOverride = ""; 92 if (Build.isDebuggable()) { 93 useDebugClient = getSharedPreferences(context).getBoolean( 94 DEBUG_DEVICELOCK_CHECKIN, /* def= */ false); 95 hostNameOverride = getSharedPreferences(context).getString( 96 HOST_NAME_OVERRIDE, /* def= */ ""); 97 if (!hostNameOverride.isEmpty()) { 98 hostName = hostNameOverride; 99 } 100 } 101 synchronized (DeviceCheckInClient.class) { 102 try { 103 boolean createRequired = 104 (sClient == null || sUseDebugClient != useDebugClient) 105 || (registeredId != null && !registeredId.equals(sRegisteredId)) 106 || (hostName != null && !hostName.equals(sHostName)); 107 108 if (createRequired) { 109 if (sClient != null) { 110 sClient.cleanUp(); 111 } 112 sHostName = hostName; 113 sPortNumber = portNumber; 114 sRegisteredId = registeredId; 115 sUseDebugClient = useDebugClient; 116 if (Build.isDebuggable() && sUseDebugClient) { 117 final String className = DEVICE_CHECK_IN_CLIENT_DEBUG_CLASS_NAME; 118 LogUtil.d(TAG, "Creating instance for " + className); 119 Class<?> clazz = Class.forName(className); 120 sClient = 121 (DeviceCheckInClient) clazz.getDeclaredConstructor().newInstance(); 122 } else { 123 sClient = new DeviceCheckInClientImpl(clientInterceptor, 124 context.getSystemService(ConnectivityManager.class)); 125 } 126 } 127 } catch (Exception e) { 128 throw new RuntimeException("Failed to get DeviceCheckInClient instance", e); 129 } 130 } 131 return sClient; 132 } 133 134 /** 135 * Check In with DeviceLock backend server and get the next step for the device 136 * 137 * @param deviceIds A set of all device unique identifiers, this could include IMEIs, 138 * MEIDs, etc. 139 * @param carrierInfo The information of the device's sim operator which is used to 140 * determine the device's geological location and eventually 141 * eligibility of the DeviceLock program. 142 * @param deviceLocale The locale of the device. 143 * @param deviceLockApexVersion The version of the device lock apex. 144 * @param fcmRegistrationToken The fcm registration token 145 * @return A class that encapsulate the response from the backend server. 146 */ 147 @WorkerThread getDeviceCheckInStatus( ArraySet<DeviceId> deviceIds, String carrierInfo, String deviceLocale, long deviceLockApexVersion, @Nullable String fcmRegistrationToken)148 public abstract GetDeviceCheckInStatusGrpcResponse getDeviceCheckInStatus( 149 ArraySet<DeviceId> deviceIds, String carrierInfo, 150 String deviceLocale, long deviceLockApexVersion, 151 @Nullable String fcmRegistrationToken); 152 153 /** 154 * Check if the device is in an approved country for the device lock program. 155 * 156 * @param carrierInfo The information of the device's sim operator which is used to determine 157 * the device's geological location and eventually eligibility of the 158 * DeviceLock program. Could be null if unavailable. 159 * @return A class that encapsulate the response from the backend server. 160 */ 161 @WorkerThread isDeviceInApprovedCountry( @ullable String carrierInfo)162 public abstract IsDeviceInApprovedCountryGrpcResponse isDeviceInApprovedCountry( 163 @Nullable String carrierInfo); 164 165 /** 166 * Inform the server that device provisioning has been paused for a certain amount of time. 167 * 168 * @param reason The reason that provisioning has been paused. 169 * @return A class that encapsulate the response from the backend sever. 170 */ 171 @WorkerThread pauseDeviceProvisioning( @auseDeviceProvisioningReason int reason)172 public abstract PauseDeviceProvisioningGrpcResponse pauseDeviceProvisioning( 173 @PauseDeviceProvisioningReason int reason); 174 175 /** 176 * Reports the current provision state of the device. 177 * 178 * @param lastReceivedProvisionState one of {@link DeviceProvisionState}. 179 * It must be the value from the response when this API 180 * was called last time. If this API is called for the first 181 * time, then 182 * {@link 183 * DeviceProvisionState#PROVISION_STATE_UNSPECIFIED } 184 * must be used. 185 * @param isSuccessful true if the device has been setup for DeviceLock program 186 * successful; false otherwise. 187 * @return A class that encapsulate the response from the backend server. 188 */ 189 @WorkerThread reportDeviceProvisionState( @eviceProvisionState int lastReceivedProvisionState, boolean isSuccessful, @ProvisionFailureReason int failureReason)190 public abstract ReportDeviceProvisionStateGrpcResponse reportDeviceProvisionState( 191 @DeviceProvisionState int lastReceivedProvisionState, 192 boolean isSuccessful, @ProvisionFailureReason int failureReason); 193 194 /** 195 * Update FCM registration token on device lock backend server for the given device identifiers. 196 * 197 * @param deviceIds A set of all device unique identifiers, this could include IMEIs, 198 * MEIDs, etc. 199 * @param fcmRegistrationToken The fcm registration token 200 * @return A class that encapsulate the response from the backend server. 201 */ 202 @WorkerThread updateFcmToken( ArraySet<DeviceId> deviceIds, @NonNull String fcmRegistrationToken)203 public abstract UpdateFcmTokenGrpcResponse updateFcmToken( 204 ArraySet<DeviceId> deviceIds, 205 @NonNull String fcmRegistrationToken); 206 207 /** 208 * Called when this device check in client is no longer in use and should clean up its 209 * resources. 210 */ cleanUp()211 public void cleanUp() {}; 212 } 213