• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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