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.Nullable;
27 import androidx.annotation.WorkerThread;
28 
29 import com.android.devicelockcontroller.common.DeviceId;
30 import com.android.devicelockcontroller.common.DeviceLockConstants.DeviceProvisionState;
31 import com.android.devicelockcontroller.common.DeviceLockConstants.PauseDeviceProvisioningReason;
32 import com.android.devicelockcontroller.common.DeviceLockConstants.ProvisionFailureReason;
33 import com.android.devicelockcontroller.provision.grpc.impl.DeviceCheckInClientImpl;
34 import com.android.devicelockcontroller.util.LogUtil;
35 
36 import io.grpc.ClientInterceptor;
37 
38 /**
39  * An abstract class that's intended for implementation of class that manages communication with
40  * DeviceLock backend server.
41  */
42 public abstract class DeviceCheckInClient {
43     private static final String TAG = "DeviceCheckInClient";
44     private static final String FILENAME = "debug-check-in-preferences";
45 
46     public static final String DEVICE_CHECK_IN_CLIENT_DEBUG_CLASS_NAME =
47             "com.android.devicelockcontroller.debug.DeviceCheckInClientDebug";
48     protected static final String DEBUG_DEVICELOCK_CHECKIN = "debug.devicelock.checkin";
49     private static final String HOST_NAME_OVERRIDE = "host.name.override";
50     private static volatile DeviceCheckInClient sClient;
51 
52     @Nullable
53     protected static String sRegisteredId;
54     protected static String sHostName = "";
55     protected static int sPortNumber = 0;
56     private static volatile boolean sUseDebugClient;
57 
58     @Nullable
59     private static volatile SharedPreferences sSharedPreferences;
60 
61     @Nullable
getSharedPreferences( @ullable Context context)62     protected static synchronized SharedPreferences getSharedPreferences(
63             @Nullable Context context) {
64         if (sSharedPreferences == null && context != null) {
65             sSharedPreferences =
66                     context.createContextAsUser(UserHandle.SYSTEM, /* flags= */
67                             0).createDeviceProtectedStorageContext().getSharedPreferences(FILENAME,
68                             Context.MODE_PRIVATE);
69         }
70         return sSharedPreferences;
71     }
72 
73     /**
74      * Override the host name so that the client always connects to it instead
75      */
setHostNameOverride(Context context, String override)76     public static void setHostNameOverride(Context context, String override) {
77         getSharedPreferences(context).edit().putString(HOST_NAME_OVERRIDE, override).apply();
78     }
79 
80     /**
81      * Get an instance of DeviceCheckInClient object.
82      */
getInstance( Context context, String hostName, int portNumber, ClientInterceptor clientInterceptor, @Nullable String registeredId)83     public static DeviceCheckInClient getInstance(
84             Context context,
85             String hostName,
86             int portNumber,
87             ClientInterceptor clientInterceptor,
88             @Nullable String registeredId) {
89         boolean useDebugClient = false;
90         String hostNameOverride = "";
91         if (Build.isDebuggable()) {
92             useDebugClient = getSharedPreferences(context).getBoolean(
93                     DEBUG_DEVICELOCK_CHECKIN, /* def= */ false);
94             hostNameOverride = getSharedPreferences(context).getString(
95                     HOST_NAME_OVERRIDE, /* def= */ "");
96             if (!hostNameOverride.isEmpty()) {
97                 hostName = hostNameOverride;
98             }
99         }
100         synchronized (DeviceCheckInClient.class) {
101             try {
102                 boolean createRequired =
103                         (sClient == null || sUseDebugClient != useDebugClient)
104                                 || (registeredId != null && !registeredId.equals(sRegisteredId))
105                                 || (hostName != null && !hostName.equals(sHostName));
106 
107                 if (createRequired) {
108                     if (sClient != null) {
109                         sClient.cleanUp();
110                     }
111                     sHostName = hostName;
112                     sPortNumber = portNumber;
113                     sRegisteredId = registeredId;
114                     sUseDebugClient = useDebugClient;
115                     if (Build.isDebuggable() && sUseDebugClient) {
116                         final String className = DEVICE_CHECK_IN_CLIENT_DEBUG_CLASS_NAME;
117                         LogUtil.d(TAG, "Creating instance for " + className);
118                         Class<?> clazz = Class.forName(className);
119                         sClient =
120                                 (DeviceCheckInClient) clazz.getDeclaredConstructor().newInstance();
121                     } else {
122                         sClient = new DeviceCheckInClientImpl(clientInterceptor,
123                                 context.getSystemService(ConnectivityManager.class));
124                     }
125                 }
126             } catch (Exception e) {
127                 throw new RuntimeException("Failed to get DeviceCheckInClient instance", e);
128             }
129         }
130         return sClient;
131     }
132 
133     /**
134      * Check In with DeviceLock backend server and get the next step for the device
135      *
136      * @param deviceIds            A set of all device unique identifiers, this could include IMEIs,
137      *                             MEIDs, etc.
138      * @param carrierInfo          The information of the device's sim operator which is used to
139      *                             determine the device's geological location and eventually
140      *                             eligibility of the DeviceLock program.
141      * @param fcmRegistrationToken The fcm registration token
142      * @return A class that encapsulate the response from the backend server.
143      */
144     @WorkerThread
getDeviceCheckInStatus( ArraySet<DeviceId> deviceIds, String carrierInfo, @Nullable String fcmRegistrationToken)145     public abstract GetDeviceCheckInStatusGrpcResponse getDeviceCheckInStatus(
146             ArraySet<DeviceId> deviceIds, String carrierInfo,
147             @Nullable String fcmRegistrationToken);
148 
149     /**
150      * Check if the device is in an approved country for the device lock program.
151      *
152      * @param carrierInfo The information of the device's sim operator which is used to determine
153      *                    the device's geological location and eventually eligibility of the
154      *                    DeviceLock program. Could be null if unavailable.
155      * @return A class that encapsulate the response from the backend server.
156      */
157     @WorkerThread
isDeviceInApprovedCountry( @ullable String carrierInfo)158     public abstract IsDeviceInApprovedCountryGrpcResponse isDeviceInApprovedCountry(
159             @Nullable String carrierInfo);
160 
161     /**
162      * Inform the server that device provisioning has been paused for a certain amount of time.
163      *
164      * @param reason The reason that provisioning has been paused.
165      * @return A class that encapsulate the response from the backend sever.
166      */
167     @WorkerThread
pauseDeviceProvisioning( @auseDeviceProvisioningReason int reason)168     public abstract PauseDeviceProvisioningGrpcResponse pauseDeviceProvisioning(
169             @PauseDeviceProvisioningReason int reason);
170 
171     /**
172      * Reports the current provision state of the device.
173      *
174      * @param lastReceivedProvisionState one of {@link DeviceProvisionState}.
175      *                                   It must be the value from the response when this API
176      *                                   was called last time. If this API is called for the first
177      *                                   time, then
178      *                                   {@link
179      *                                   DeviceProvisionState#PROVISION_STATE_UNSPECIFIED }
180      *                                   must be used.
181      * @param isSuccessful               true if the device has been setup for DeviceLock program
182      *                                   successful; false otherwise.
183      * @return A class that encapsulate the response from the backend server.
184      */
185     @WorkerThread
reportDeviceProvisionState( @eviceProvisionState int lastReceivedProvisionState, boolean isSuccessful, @ProvisionFailureReason int failureReason)186     public abstract ReportDeviceProvisionStateGrpcResponse reportDeviceProvisionState(
187             @DeviceProvisionState int lastReceivedProvisionState,
188             boolean isSuccessful, @ProvisionFailureReason int failureReason);
189 
190     /**
191      * Called when this device check in client is no longer in use and should clean up its
192      * resources.
193      */
cleanUp()194     public void cleanUp() {};
195 }
196