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 android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
20 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
21 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
22 import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
23 
24 import static com.android.devicelockcontroller.common.DeviceLockConstants.DeviceIdType.DEVICE_ID_TYPE_IMEI;
25 import static com.android.devicelockcontroller.common.DeviceLockConstants.DeviceIdType.DEVICE_ID_TYPE_MEID;
26 import static com.android.devicelockcontroller.common.DeviceLockConstants.EXTRA_ALLOW_DEBUGGING;
27 import static com.android.devicelockcontroller.common.DeviceLockConstants.EXTRA_MANDATORY_PROVISION;
28 import static com.android.devicelockcontroller.common.DeviceLockConstants.EXTRA_PROVISIONING_TYPE;
29 import static com.android.devicelockcontroller.common.DeviceLockConstants.READY_FOR_PROVISION;
30 import static com.android.devicelockcontroller.common.DeviceLockConstants.RETRY_CHECK_IN;
31 import static com.android.devicelockcontroller.common.DeviceLockConstants.STATUS_UNSPECIFIED;
32 import static com.android.devicelockcontroller.common.DeviceLockConstants.STOP_CHECK_IN;
33 import static com.android.devicelockcontroller.common.DeviceLockConstants.TOTAL_DEVICE_ID_TYPES;
34 import static com.android.devicelockcontroller.provision.worker.GetFcmTokenWorker.FCM_TOKEN_WORKER_BACKOFF_DELAY;
35 import static com.android.devicelockcontroller.provision.worker.GetFcmTokenWorker.FCM_TOKEN_WORKER_INITIAL_DELAY;
36 import static com.android.devicelockcontroller.provision.worker.GetFcmTokenWorker.FCM_TOKEN_WORK_NAME;
37 import static com.android.devicelockcontroller.receivers.CheckInBootCompletedReceiver.disableCheckInBootCompletedReceiver;
38 import static com.android.devicelockcontroller.stats.StatsLogger.CheckInRetryReason.CONFIG_UNAVAILABLE;
39 import static com.android.devicelockcontroller.stats.StatsLogger.CheckInRetryReason.NETWORK_TIME_UNAVAILABLE;
40 import static com.android.devicelockcontroller.stats.StatsLogger.CheckInRetryReason.RESPONSE_UNSPECIFIED;
41 
42 import android.content.Context;
43 import android.content.Intent;
44 import android.content.pm.PackageManager;
45 import android.net.NetworkRequest;
46 import android.os.Bundle;
47 import android.os.SystemClock;
48 import android.os.UserHandle;
49 import android.telephony.TelephonyManager;
50 import android.util.ArraySet;
51 
52 import androidx.annotation.NonNull;
53 import androidx.annotation.Nullable;
54 import androidx.annotation.VisibleForTesting;
55 import androidx.annotation.WorkerThread;
56 import androidx.work.BackoffPolicy;
57 import androidx.work.Constraints;
58 import androidx.work.ExistingWorkPolicy;
59 import androidx.work.NetworkType;
60 import androidx.work.OneTimeWorkRequest;
61 import androidx.work.WorkManager;
62 
63 import com.android.devicelockcontroller.R;
64 import com.android.devicelockcontroller.common.DeviceId;
65 import com.android.devicelockcontroller.policy.PolicyObjectsProvider;
66 import com.android.devicelockcontroller.provision.grpc.GetDeviceCheckInStatusGrpcResponse;
67 import com.android.devicelockcontroller.provision.grpc.ProvisioningConfiguration;
68 import com.android.devicelockcontroller.receivers.ProvisionReadyReceiver;
69 import com.android.devicelockcontroller.schedule.DeviceLockControllerScheduler;
70 import com.android.devicelockcontroller.stats.StatsLogger;
71 import com.android.devicelockcontroller.stats.StatsLoggerProvider;
72 import com.android.devicelockcontroller.storage.GlobalParametersClient;
73 import com.android.devicelockcontroller.storage.SetupParametersClient;
74 import com.android.devicelockcontroller.util.LogUtil;
75 
76 import com.google.common.base.Strings;
77 import com.google.common.util.concurrent.FutureCallback;
78 import com.google.common.util.concurrent.Futures;
79 import com.google.common.util.concurrent.ListenableFuture;
80 import com.google.common.util.concurrent.MoreExecutors;
81 
82 import java.time.DateTimeException;
83 import java.time.Duration;
84 
85 /**
86  * Helper class to perform the device check-in process with device lock backend server
87  */
88 public final class DeviceCheckInHelper extends AbstractDeviceCheckInHelper {
89     private static final String TAG = "DeviceCheckInHelper";
90     private final Context mAppContext;
91     private final TelephonyManager mTelephonyManager;
92     private final StatsLogger mStatsLogger;
93 
DeviceCheckInHelper(Context appContext)94     public DeviceCheckInHelper(Context appContext) {
95         mAppContext = appContext;
96         mTelephonyManager = mAppContext.getSystemService(TelephonyManager.class);
97         mStatsLogger = ((StatsLoggerProvider) mAppContext).getStatsLogger();
98     }
99 
hasGsm()100     private boolean hasGsm() {
101         return mAppContext.getPackageManager().hasSystemFeature(
102                 PackageManager.FEATURE_TELEPHONY_GSM);
103     }
104 
hasCdma()105     private boolean hasCdma() {
106         return mAppContext.getPackageManager().hasSystemFeature(
107                 PackageManager.FEATURE_TELEPHONY_CDMA);
108     }
109 
110     @Override
getDeviceUniqueIds()111     ArraySet<DeviceId> getDeviceUniqueIds() {
112         final int deviceIdTypeBitmap = mAppContext.getResources().getInteger(
113                 R.integer.device_id_type_bitmap);
114         if (deviceIdTypeBitmap < 0) {
115             LogUtil.e(TAG, "getDeviceId: Cannot get device_id_type_bitmap");
116             return new ArraySet<>();
117         }
118 
119         return getDeviceAvailableUniqueIds(deviceIdTypeBitmap);
120     }
121 
122     @VisibleForTesting
getDeviceAvailableUniqueIds(int deviceIdTypeBitmap)123     ArraySet<DeviceId> getDeviceAvailableUniqueIds(int deviceIdTypeBitmap) {
124 
125         final int totalSlotCount = mTelephonyManager.getActiveModemCount();
126         final int maximumIdCount = TOTAL_DEVICE_ID_TYPES * totalSlotCount;
127         final ArraySet<DeviceId> deviceIds = new ArraySet<>(maximumIdCount);
128         if (maximumIdCount == 0) return deviceIds;
129 
130         for (int i = 0; i < totalSlotCount; i++) {
131             if (hasGsm() && (deviceIdTypeBitmap & (1 << DEVICE_ID_TYPE_IMEI)) != 0) {
132                 final String imei = mTelephonyManager.getImei(i);
133 
134                 if (imei != null) {
135                     deviceIds.add(new DeviceId(DEVICE_ID_TYPE_IMEI, imei));
136                 }
137             }
138 
139             if (hasCdma() && (deviceIdTypeBitmap & (1 << DEVICE_ID_TYPE_MEID)) != 0) {
140                 final String meid = mTelephonyManager.getMeid(i);
141 
142                 if (meid != null) {
143                     deviceIds.add(new DeviceId(DEVICE_ID_TYPE_MEID, meid));
144                 }
145             }
146         }
147 
148         return deviceIds;
149     }
150 
151     @Override
getCarrierInfo()152     String getCarrierInfo() {
153         return mTelephonyManager.getSimOperator();
154     }
155 
156     @Override
157     @WorkerThread
handleGetDeviceCheckInStatusResponse( GetDeviceCheckInStatusGrpcResponse response, DeviceLockControllerScheduler scheduler, @Nullable String fcmRegistrationToken)158     boolean handleGetDeviceCheckInStatusResponse(
159             GetDeviceCheckInStatusGrpcResponse response,
160             DeviceLockControllerScheduler scheduler,
161             @Nullable String fcmRegistrationToken) {
162         Futures.getUnchecked(GlobalParametersClient.getInstance().setRegisteredDeviceId(
163                 response.getRegisteredDeviceIdentifier()));
164         LogUtil.d(TAG, "check in response: " + response.getDeviceCheckInStatus());
165         switch (response.getDeviceCheckInStatus()) {
166             case READY_FOR_PROVISION:
167                 boolean result = handleProvisionReadyResponse(response);
168                 disableCheckInBootCompletedReceiver(mAppContext);
169                 maybeEnqueueFcmRegistrationTokenRetrievalWork(fcmRegistrationToken);
170                 return result;
171             case RETRY_CHECK_IN:
172                 try {
173                     Duration delay = Duration.between(
174                             SystemClock.currentNetworkTimeClock().instant(),
175                             response.getNextCheckInTime());
176                     // Retry immediately if next check in time is in the past.
177                     delay = delay.isNegative() ? Duration.ZERO : delay;
178                     scheduler.scheduleRetryCheckInWork(delay);
179                     maybeEnqueueFcmRegistrationTokenRetrievalWork(fcmRegistrationToken);
180                     return true;
181                 } catch (DateTimeException e) {
182                     LogUtil.e(TAG, "No network time is available!");
183                     mStatsLogger.logCheckInRetry(NETWORK_TIME_UNAVAILABLE);
184                     return false;
185                 }
186             case STOP_CHECK_IN:
187                 final ListenableFuture<Void> clearRestrictionsFuture =
188                         ((PolicyObjectsProvider) mAppContext).getFinalizationController()
189                                 .finalizeNotEnrolledDevice();
190                 Futures.addCallback(clearRestrictionsFuture,
191                         new FutureCallback<>() {
192                             @Override
193                             public void onSuccess(Void result) {
194                                 // no-op
195                             }
196 
197                             @Override
198                             public void onFailure(Throwable t) {
199                                 LogUtil.e(TAG, "Failed to finalize device", t);
200                             }
201                         }, MoreExecutors.directExecutor()
202                 );
203                 return true;
204             case STATUS_UNSPECIFIED:
205             default:
206                 mStatsLogger.logCheckInRetry(RESPONSE_UNSPECIFIED);
207                 return false;
208         }
209     }
210 
211     /**
212      * Starts a job to retrieve the FCM registration token later if the current one used to\
213      * check-in is invalid.
214      *
215      * @param fcmRegistrationToken the current token
216      */
maybeEnqueueFcmRegistrationTokenRetrievalWork( @ullable String fcmRegistrationToken)217     private void maybeEnqueueFcmRegistrationTokenRetrievalWork(
218             @Nullable String fcmRegistrationToken) {
219         if (Strings.isNullOrEmpty(fcmRegistrationToken) || fcmRegistrationToken.isBlank()) {
220             NetworkRequest request = new NetworkRequest.Builder()
221                     .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
222                     .addCapability(NET_CAPABILITY_TRUSTED)
223                     .addCapability(NET_CAPABILITY_INTERNET)
224                     .addCapability(NET_CAPABILITY_NOT_VPN)
225                     .build();
226             OneTimeWorkRequest.Builder builder =
227                     new OneTimeWorkRequest.Builder(GetFcmTokenWorker.class)
228                             .setConstraints(
229                                     new Constraints.Builder().setRequiredNetworkRequest(request,
230                                             NetworkType.CONNECTED).build())
231                             .setInitialDelay(FCM_TOKEN_WORKER_INITIAL_DELAY)
232                             .setBackoffCriteria(
233                                     BackoffPolicy.EXPONENTIAL, FCM_TOKEN_WORKER_BACKOFF_DELAY);
234 
235             WorkManager.getInstance(mAppContext).enqueueUniqueWork(FCM_TOKEN_WORK_NAME,
236                     ExistingWorkPolicy.REPLACE, builder.build());
237         }
238     }
239 
240     @VisibleForTesting
241     @WorkerThread
handleProvisionReadyResponse( @onNull GetDeviceCheckInStatusGrpcResponse response)242     boolean handleProvisionReadyResponse(
243             @NonNull GetDeviceCheckInStatusGrpcResponse response) {
244         GlobalParametersClient globalParametersClient = GlobalParametersClient.getInstance();
245         Futures.getUnchecked(globalParametersClient.setProvisionForced(
246                 response.isProvisionForced()));
247         final ProvisioningConfiguration configuration = response.getProvisioningConfig();
248         if (configuration == null) {
249             LogUtil.e(TAG, "Provisioning Configuration is not provided by server!");
250             mStatsLogger.logCheckInRetry(CONFIG_UNAVAILABLE);
251             return false;
252         }
253         final Bundle provisionBundle = configuration.toBundle();
254         provisionBundle.putInt(EXTRA_PROVISIONING_TYPE, response.getProvisioningType());
255         provisionBundle.putBoolean(EXTRA_MANDATORY_PROVISION,
256                 response.isProvisioningMandatory());
257         provisionBundle.putBoolean(EXTRA_ALLOW_DEBUGGING, response.isDebuggingAllowed());
258         Futures.getUnchecked(
259                 SetupParametersClient.getInstance().createPrefs(provisionBundle));
260         Futures.getUnchecked(globalParametersClient.setProvisionReady(true));
261         mAppContext.sendBroadcastAsUser(
262                 new Intent(mAppContext, ProvisionReadyReceiver.class),
263                 UserHandle.ALL);
264         return true;
265     }
266 }
267