1 /* 2 * Copyright (C) 2022 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.worker; 18 19 import static com.android.devicelockcontroller.common.DeviceLockConstants.DeviceIdType.DEVICE_ID_TYPE_IMEI; 20 import static com.android.devicelockcontroller.common.DeviceLockConstants.DeviceIdType.DEVICE_ID_TYPE_MEID; 21 import static com.android.devicelockcontroller.common.DeviceLockConstants.EXTRA_MANDATORY_PROVISION; 22 import static com.android.devicelockcontroller.common.DeviceLockConstants.EXTRA_PROVISIONING_TYPE; 23 import static com.android.devicelockcontroller.common.DeviceLockConstants.READY_FOR_PROVISION; 24 import static com.android.devicelockcontroller.common.DeviceLockConstants.RETRY_CHECK_IN; 25 import static com.android.devicelockcontroller.common.DeviceLockConstants.STATUS_UNSPECIFIED; 26 import static com.android.devicelockcontroller.common.DeviceLockConstants.STOP_CHECK_IN; 27 import static com.android.devicelockcontroller.common.DeviceLockConstants.TOTAL_DEVICE_ID_TYPES; 28 import static com.android.devicelockcontroller.policy.DeviceStateController.DeviceEvent.PROVISIONING_SUCCESS; 29 30 import android.content.Context; 31 import android.os.Bundle; 32 import android.telephony.TelephonyManager; 33 import android.util.ArraySet; 34 35 import androidx.annotation.NonNull; 36 import androidx.annotation.VisibleForTesting; 37 import androidx.annotation.WorkerThread; 38 import androidx.work.BackoffPolicy; 39 import androidx.work.Constraints; 40 import androidx.work.ExistingWorkPolicy; 41 import androidx.work.NetworkType; 42 import androidx.work.OneTimeWorkRequest; 43 import androidx.work.OutOfQuotaPolicy; 44 import androidx.work.WorkManager; 45 46 import com.android.devicelockcontroller.R; 47 import com.android.devicelockcontroller.common.DeviceId; 48 import com.android.devicelockcontroller.policy.DevicePolicyController; 49 import com.android.devicelockcontroller.policy.DeviceStateController; 50 import com.android.devicelockcontroller.policy.PolicyObjectsInterface; 51 import com.android.devicelockcontroller.provision.grpc.GetDeviceCheckInStatusGrpcResponse; 52 import com.android.devicelockcontroller.provision.grpc.ProvisioningConfiguration; 53 import com.android.devicelockcontroller.storage.GlobalParametersClient; 54 import com.android.devicelockcontroller.storage.SetupParametersClient; 55 import com.android.devicelockcontroller.util.LogUtil; 56 57 import com.google.common.util.concurrent.FutureCallback; 58 import com.google.common.util.concurrent.Futures; 59 import com.google.common.util.concurrent.ListenableFuture; 60 import com.google.common.util.concurrent.MoreExecutors; 61 62 import java.time.Duration; 63 import java.time.Instant; 64 import java.util.Locale; 65 66 /** 67 * Helper class to perform the device check-in process with device lock backend server 68 */ 69 public final class DeviceCheckInHelper extends AbstractDeviceCheckInHelper { 70 @VisibleForTesting 71 public static final String CHECK_IN_WORK_NAME = "checkIn"; 72 private static final String TAG = "DeviceCheckInHelper"; 73 private static final int CHECK_IN_INTERVAL_HOURS = 1; 74 private final Context mAppContext; 75 private final TelephonyManager mTelephonyManager; 76 DeviceCheckInHelper(Context appContext)77 public DeviceCheckInHelper(Context appContext) { 78 mAppContext = appContext; 79 mTelephonyManager = mAppContext.getSystemService(TelephonyManager.class); 80 } 81 82 /** 83 * Enqueue the DeviceCheckIn work request to WorkManager 84 * 85 * @param isExpedited If true, the work request should be expedited; 86 */ 87 @Override enqueueDeviceCheckInWork(boolean isExpedited)88 public void enqueueDeviceCheckInWork(boolean isExpedited) { 89 enqueueDeviceCheckInWork(isExpedited, Duration.ZERO); 90 } 91 92 /** 93 * Enqueue the DeviceCheckIn work request to WorkManager 94 * 95 * @param isExpedited If true, the work request should be expedited; 96 * @param delay The duration that need to be delayed before performing check-in. 97 */ enqueueDeviceCheckInWork(boolean isExpedited, Duration delay)98 private void enqueueDeviceCheckInWork(boolean isExpedited, Duration delay) { 99 LogUtil.i(TAG, "enqueueDeviceCheckInWork with delay: " + delay); 100 final OneTimeWorkRequest.Builder builder = 101 new OneTimeWorkRequest.Builder(DeviceCheckInWorker.class) 102 .setConstraints( 103 new Constraints.Builder().setRequiredNetworkType( 104 NetworkType.CONNECTED).build()) 105 .setInitialDelay(delay) 106 .setBackoffCriteria(BackoffPolicy.LINEAR, 107 Duration.ofHours(CHECK_IN_INTERVAL_HOURS)); 108 if (isExpedited) builder.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST); 109 WorkManager.getInstance(mAppContext).enqueueUniqueWork(CHECK_IN_WORK_NAME, 110 ExistingWorkPolicy.REPLACE, builder.build()); 111 } 112 113 114 @Override 115 @NonNull getDeviceUniqueIds()116 ArraySet<DeviceId> getDeviceUniqueIds() { 117 final int deviceIdTypeBitmap = mAppContext.getResources().getInteger( 118 R.integer.device_id_type_bitmap); 119 if (deviceIdTypeBitmap < 0) { 120 LogUtil.e(TAG, "getDeviceId: Cannot get device_id_type_bitmap"); 121 return new ArraySet<>(); 122 } 123 124 return getDeviceAvailableUniqueIds(deviceIdTypeBitmap); 125 } 126 127 @VisibleForTesting getDeviceAvailableUniqueIds(int deviceIdTypeBitmap)128 ArraySet<DeviceId> getDeviceAvailableUniqueIds(int deviceIdTypeBitmap) { 129 130 final int totalSlotCount = mTelephonyManager.getActiveModemCount(); 131 final int maximumIdCount = TOTAL_DEVICE_ID_TYPES * totalSlotCount; 132 final ArraySet<DeviceId> deviceIds = new ArraySet<>(maximumIdCount); 133 if (maximumIdCount == 0) return deviceIds; 134 135 for (int i = 0; i < totalSlotCount; i++) { 136 if ((deviceIdTypeBitmap & (1 << DEVICE_ID_TYPE_IMEI)) != 0) { 137 final String imei = mTelephonyManager.getImei(i); 138 139 if (imei != null) { 140 deviceIds.add(new DeviceId(DEVICE_ID_TYPE_IMEI, imei)); 141 } 142 } 143 144 if ((deviceIdTypeBitmap & (1 << DEVICE_ID_TYPE_MEID)) != 0) { 145 final String meid = mTelephonyManager.getMeid(i); 146 147 if (meid != null) { 148 deviceIds.add(new DeviceId(DEVICE_ID_TYPE_MEID, meid)); 149 } 150 } 151 } 152 153 return deviceIds; 154 } 155 156 @Override 157 @NonNull getCarrierInfo()158 String getCarrierInfo() { 159 // TODO(b/267507927): Figure out if we need carrier info of all sims. 160 return mTelephonyManager.getSimOperator(); 161 } 162 163 @Override 164 @WorkerThread handleGetDeviceCheckInStatusResponse( @onNull GetDeviceCheckInStatusGrpcResponse response)165 boolean handleGetDeviceCheckInStatusResponse( 166 @NonNull GetDeviceCheckInStatusGrpcResponse response) { 167 Futures.getUnchecked(GlobalParametersClient.getInstance().setRegisteredDeviceId( 168 response.getRegisteredDeviceIdentifier())); 169 LogUtil.d(TAG, "check in succeed: " + response.getDeviceCheckInStatus()); 170 switch (response.getDeviceCheckInStatus()) { 171 case READY_FOR_PROVISION: 172 PolicyObjectsInterface policies = 173 (PolicyObjectsInterface) mAppContext.getApplicationContext(); 174 return handleProvisionReadyResponse( 175 response, 176 policies.getStateController(), 177 policies.getPolicyController()); 178 case RETRY_CHECK_IN: 179 Duration delay = Duration.between(Instant.now(), response.getNextCheckInTime()); 180 delay = delay.isNegative() ? Duration.ZERO : delay; 181 enqueueDeviceCheckInWork(false, delay); 182 return true; 183 case STOP_CHECK_IN: 184 Futures.getUnchecked(GlobalParametersClient.getInstance().setNeedCheckIn(false)); 185 return true; 186 case STATUS_UNSPECIFIED: 187 default: 188 return false; 189 } 190 } 191 192 @VisibleForTesting 193 @WorkerThread handleProvisionReadyResponse( @onNull GetDeviceCheckInStatusGrpcResponse response, DeviceStateController stateController, DevicePolicyController devicePolicyController)194 boolean handleProvisionReadyResponse( 195 @NonNull GetDeviceCheckInStatusGrpcResponse response, 196 DeviceStateController stateController, 197 DevicePolicyController devicePolicyController) { 198 Futures.getUnchecked(GlobalParametersClient.getInstance().setProvisionForced( 199 response.isProvisionForced())); 200 final ProvisioningConfiguration configuration = response.getProvisioningConfig(); 201 if (configuration == null) { 202 LogUtil.e(TAG, "Provisioning Configuration is not provided by server!"); 203 return false; 204 } 205 final Bundle provisionBundle = configuration.toBundle(); 206 provisionBundle.putInt(EXTRA_PROVISIONING_TYPE, response.getProvisioningType()); 207 provisionBundle.putBoolean(EXTRA_MANDATORY_PROVISION, 208 response.isProvisioningMandatory()); 209 Futures.getUnchecked( 210 SetupParametersClient.getInstance().createPrefs(provisionBundle)); 211 setProvisionSucceeded(stateController, devicePolicyController, mAppContext, 212 response.isProvisioningMandatory()); 213 return true; 214 } 215 216 /** 217 * Helper method to set the state for PROVISIONING_SUCCESS event. 218 */ setProvisionSucceeded(DeviceStateController stateController, DevicePolicyController devicePolicyController, Context mAppContext, final boolean isMandatory)219 public static void setProvisionSucceeded(DeviceStateController stateController, 220 DevicePolicyController devicePolicyController, 221 Context mAppContext, final boolean isMandatory) { 222 FutureCallback<Void> futureCallback = new FutureCallback<>() { 223 @Override 224 public void onSuccess(Void result) { 225 LogUtil.i(TAG, 226 String.format(Locale.US, 227 "State transition succeeded for event: %s", 228 DeviceStateController.eventToString(PROVISIONING_SUCCESS))); 229 devicePolicyController.enqueueStartLockTaskModeWorker(isMandatory); 230 } 231 232 @Override 233 public void onFailure(Throwable t) { 234 //TODO: Reset the state to where it can successfully transition. 235 LogUtil.e(TAG, 236 String.format(Locale.US, 237 "State transition failed for event: %s", 238 DeviceStateController.eventToString(PROVISIONING_SUCCESS)), t); 239 } 240 }; 241 mAppContext.getMainExecutor().execute( 242 () -> { 243 ListenableFuture<Void> tasks = Futures.whenAllSucceed( 244 GlobalParametersClient.getInstance().setNeedCheckIn(false), 245 stateController.setNextStateForEvent(PROVISIONING_SUCCESS)) 246 .call(() -> null, MoreExecutors.directExecutor()); 247 Futures.addCallback(tasks, futureCallback, MoreExecutors.directExecutor()); 248 }); 249 } 250 } 251