1 /*
2  * Copyright 2014, 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.managedprovisioning.common;
18 
19 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE;
20 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE;
21 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE;
22 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
23 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE;
24 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER;
25 import static android.app.admin.DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC;
26 import static android.app.admin.DevicePolicyManager.PROVISIONING_TRIGGER_CLOUD_ENROLLMENT;
27 import static android.app.admin.DevicePolicyManager.PROVISIONING_TRIGGER_QR_CODE;
28 import static android.app.admin.DevicePolicyManager.PROVISIONING_TRIGGER_UNSPECIFIED;
29 import static android.content.pm.PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS;
30 import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
31 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
32 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
33 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
34 import static android.nfc.NfcAdapter.ACTION_NDEF_DISCOVERED;
35 
36 import static com.android.managedprovisioning.common.Globals.ACTION_PROVISION_MANAGED_DEVICE_SILENTLY;
37 import static com.android.managedprovisioning.model.ProvisioningParams.PROVISIONING_MODE_FULLY_MANAGED_DEVICE;
38 import static com.android.managedprovisioning.model.ProvisioningParams.PROVISIONING_MODE_MANAGED_PROFILE;
39 import static com.android.managedprovisioning.model.ProvisioningParams.PROVISIONING_MODE_MANAGED_PROFILE_ON_FULLY_NAMAGED_DEVICE;
40 
41 import android.annotation.WorkerThread;
42 import android.net.NetworkCapabilities;
43 import android.os.Handler;
44 import android.os.Looper;
45 import com.android.managedprovisioning.R;
46 
47 import android.accounts.Account;
48 import android.accounts.AccountManager;
49 import android.accounts.AccountManagerFuture;
50 import android.accounts.AuthenticatorException;
51 import android.accounts.OperationCanceledException;
52 import android.annotation.NonNull;
53 import android.annotation.Nullable;
54 import android.annotation.StringRes;
55 import android.app.admin.DevicePolicyManager;
56 import android.content.ComponentName;
57 import android.content.Context;
58 import android.content.Intent;
59 import android.content.pm.ActivityInfo;
60 import android.content.pm.ApplicationInfo;
61 import android.content.pm.IPackageManager;
62 import android.content.pm.PackageInfo;
63 import android.content.pm.PackageManager;
64 import android.content.pm.PackageManager.NameNotFoundException;
65 import android.content.pm.ResolveInfo;
66 import android.content.pm.UserInfo;
67 import android.content.res.TypedArray;
68 import android.graphics.Color;
69 import android.net.ConnectivityManager;
70 import android.net.NetworkInfo;
71 import android.net.wifi.WifiManager;
72 import android.os.Build;
73 import android.os.Bundle;
74 import android.os.RemoteException;
75 import android.os.ServiceManager;
76 import android.os.SystemProperties;
77 import android.os.UserHandle;
78 import android.os.UserManager;
79 import android.os.storage.StorageManager;
80 import android.text.SpannableString;
81 import android.text.Spanned;
82 import android.text.TextUtils;
83 import android.text.method.LinkMovementMethod;
84 import android.text.style.ClickableSpan;
85 import android.view.View.OnClickListener;
86 import android.widget.TextView;
87 
88 import com.android.internal.annotations.VisibleForTesting;
89 import com.android.managedprovisioning.TrampolineActivity;
90 import com.android.managedprovisioning.model.CustomizationParams;
91 import com.android.managedprovisioning.model.PackageDownloadInfo;
92 import com.android.managedprovisioning.model.ProvisioningParams;
93 import com.android.managedprovisioning.preprovisioning.WebActivity;
94 
95 import java.io.FileInputStream;
96 import java.io.IOException;
97 import java.io.InputStream;
98 import java.security.MessageDigest;
99 import java.security.NoSuchAlgorithmException;
100 import java.util.Arrays;
101 import java.util.HashSet;
102 import java.util.List;
103 import java.util.Objects;
104 import java.util.Set;
105 
106 import com.google.android.setupdesign.GlifLayout;
107 import com.google.android.setupcompat.template.FooterBarMixin;
108 import com.google.android.setupcompat.template.FooterButton;
109 import com.google.android.setupcompat.template.FooterButton.ButtonType;
110 
111 /**
112  * Class containing various auxiliary methods.
113  */
114 public class Utils {
115     public static final String SHA256_TYPE = "SHA-256";
116 
117     // value chosen to match UX designs; when updating check status bar icon colors
118     private static final int THRESHOLD_BRIGHT_COLOR = 190;
119 
Utils()120     public Utils() {}
121 
122     /**
123      * Returns the system apps currently available to a given user.
124      *
125      * <p>Calls the {@link IPackageManager} to retrieve all system apps available to a user and
126      * returns their package names.
127      *
128      * @param ipm an {@link IPackageManager} object
129      * @param userId the id of the user to check the apps for
130      */
getCurrentSystemApps(IPackageManager ipm, int userId)131     public Set<String> getCurrentSystemApps(IPackageManager ipm, int userId) {
132         Set<String> apps = new HashSet<>();
133         List<ApplicationInfo> aInfos = null;
134         try {
135             aInfos = ipm.getInstalledApplications(
136                     MATCH_UNINSTALLED_PACKAGES | MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS, userId)
137                     .getList();
138         } catch (RemoteException neverThrown) {
139             ProvisionLogger.loge("This should not happen.", neverThrown);
140         }
141         for (ApplicationInfo aInfo : aInfos) {
142             if ((aInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
143                 apps.add(aInfo.packageName);
144             }
145         }
146         return apps;
147     }
148 
149     /**
150      * Disables a given component in a given user.
151      *
152      * @param toDisable the component that should be disabled
153      * @param userId the id of the user where the component should be disabled.
154      */
disableComponent(ComponentName toDisable, int userId)155     public void disableComponent(ComponentName toDisable, int userId) {
156         setComponentEnabledSetting(
157                 IPackageManager.Stub.asInterface(ServiceManager.getService("package")),
158                 toDisable,
159                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
160                 userId);
161     }
162 
163     /**
164      * Enables a given component in a given user.
165      *
166      * @param toEnable the component that should be enabled
167      * @param userId the id of the user where the component should be disabled.
168      */
enableComponent(ComponentName toEnable, int userId)169     public void enableComponent(ComponentName toEnable, int userId) {
170         setComponentEnabledSetting(
171                 IPackageManager.Stub.asInterface(ServiceManager.getService("package")),
172                 toEnable,
173                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
174                 userId);
175     }
176 
177     /**
178      * Disables a given component in a given user.
179      *
180      * @param ipm an {@link IPackageManager} object
181      * @param toDisable the component that should be disabled
182      * @param userId the id of the user where the component should be disabled.
183      */
184     @VisibleForTesting
setComponentEnabledSetting(IPackageManager ipm, ComponentName toDisable, int enabledSetting, int userId)185     void setComponentEnabledSetting(IPackageManager ipm, ComponentName toDisable,
186             int enabledSetting, int userId) {
187         try {
188             ipm.setComponentEnabledSetting(toDisable,
189                     enabledSetting, PackageManager.DONT_KILL_APP,
190                     userId);
191         } catch (RemoteException neverThrown) {
192             ProvisionLogger.loge("This should not happen.", neverThrown);
193         } catch (Exception e) {
194             ProvisionLogger.logw("Component not found, not changing enabled setting: "
195                 + toDisable.toShortString());
196         }
197     }
198 
199     /**
200      * Check the validity of the admin component name supplied, or try to infer this componentName
201      * from the package.
202      *
203      * We are supporting lookup by package name for legacy reasons.
204      *
205      * If dpcComponentName is supplied (not null): dpcPackageName is ignored.
206      * Check that the package of dpcComponentName is installed, that dpcComponentName is a
207      * receiver in this package, and return it. The receiver can be in disabled state.
208      *
209      * Otherwise: dpcPackageName must be supplied (not null).
210      * Check that this package is installed, try to infer a potential device admin in this package,
211      * and return it.
212      */
213     @NonNull
findDeviceAdmin(String dpcPackageName, ComponentName dpcComponentName, Context context, int userId)214     public ComponentName findDeviceAdmin(String dpcPackageName, ComponentName dpcComponentName,
215             Context context, int userId) throws IllegalProvisioningArgumentException {
216         if (dpcComponentName != null) {
217             dpcPackageName = dpcComponentName.getPackageName();
218         }
219         if (dpcPackageName == null) {
220             throw new IllegalProvisioningArgumentException("Neither the package name nor the"
221                     + " component name of the admin are supplied");
222         }
223         PackageInfo pi;
224         try {
225             pi = context.getPackageManager().getPackageInfoAsUser(dpcPackageName,
226                     PackageManager.GET_RECEIVERS | PackageManager.MATCH_DISABLED_COMPONENTS,
227                     userId);
228         } catch (NameNotFoundException e) {
229             throw new IllegalProvisioningArgumentException("Dpc " + dpcPackageName
230                     + " is not installed. ", e);
231         }
232 
233         final ComponentName componentName = findDeviceAdminInPackageInfo(dpcPackageName,
234                 dpcComponentName, pi);
235         if (componentName == null) {
236             throw new IllegalProvisioningArgumentException("Cannot find any admin receiver in "
237                     + "package " + dpcPackageName + " with component " + dpcComponentName);
238         }
239         return componentName;
240     }
241 
242     /**
243      * If dpcComponentName is not null: dpcPackageName is ignored.
244      * Check that the package of dpcComponentName is installed, that dpcComponentName is a
245      * receiver in this package, and return it. The receiver can be in disabled state.
246      *
247      * Otherwise, try to infer a potential device admin component in this package info.
248      *
249      * @return infered device admin component in package info. Otherwise, null
250      */
251     @Nullable
findDeviceAdminInPackageInfo(@onNull String dpcPackageName, @Nullable ComponentName dpcComponentName, @NonNull PackageInfo pi)252     public ComponentName findDeviceAdminInPackageInfo(@NonNull String dpcPackageName,
253             @Nullable ComponentName dpcComponentName, @NonNull PackageInfo pi) {
254         if (dpcComponentName != null) {
255             if (!isComponentInPackageInfo(dpcComponentName, pi)) {
256                 ProvisionLogger.logw("The component " + dpcComponentName + " isn't registered in "
257                         + "the apk");
258                 return null;
259             }
260             return dpcComponentName;
261         } else {
262             return findDeviceAdminInPackage(dpcPackageName, pi);
263         }
264     }
265 
266     /**
267      * Finds a device admin in a given {@link PackageInfo} object.
268      *
269      * <p>This function returns {@code null} if no or multiple admin receivers were found, and if
270      * the package name does not match dpcPackageName.</p>
271      * @param packageName packge name that should match the {@link PackageInfo} object.
272      * @param packageInfo package info to be examined.
273      * @return admin receiver or null in case of error.
274      */
275     @Nullable
findDeviceAdminInPackage(String packageName, PackageInfo packageInfo)276     private ComponentName findDeviceAdminInPackage(String packageName, PackageInfo packageInfo) {
277         if (packageInfo == null || !TextUtils.equals(packageInfo.packageName, packageName)) {
278             return null;
279         }
280 
281         ComponentName mdmComponentName = null;
282         for (ActivityInfo ai : packageInfo.receivers) {
283             if (TextUtils.equals(ai.permission, android.Manifest.permission.BIND_DEVICE_ADMIN)) {
284                 if (mdmComponentName != null) {
285                     ProvisionLogger.logw("more than 1 device admin component are found");
286                     return null;
287                 } else {
288                     mdmComponentName = new ComponentName(packageName, ai.name);
289                 }
290             }
291         }
292         return mdmComponentName;
293     }
294 
isComponentInPackageInfo(ComponentName dpcComponentName, PackageInfo pi)295     private boolean isComponentInPackageInfo(ComponentName dpcComponentName,
296             PackageInfo pi) {
297         for (ActivityInfo ai : pi.receivers) {
298             if (dpcComponentName.getClassName().equals(ai.name)) {
299                 return true;
300             }
301         }
302         return false;
303     }
304 
305     /**
306      * Return if a given package has testOnly="true", in which case we'll relax certain rules
307      * for CTS.
308      *
309      * The system allows this flag to be changed when an app is updated. But
310      * {@link DevicePolicyManager} uses the persisted version to do actual checks for relevant
311      * dpm command.
312      *
313      * @see DevicePolicyManagerService#isPackageTestOnly for more info
314      */
isPackageTestOnly(PackageManager pm, String packageName, int userHandle)315     public static boolean isPackageTestOnly(PackageManager pm, String packageName, int userHandle) {
316         if (TextUtils.isEmpty(packageName)) {
317             return false;
318         }
319 
320         try {
321             final ApplicationInfo ai = pm.getApplicationInfoAsUser(packageName,
322                     PackageManager.MATCH_DIRECT_BOOT_AWARE
323                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle);
324             return ai != null && (ai.flags & ApplicationInfo.FLAG_TEST_ONLY) != 0;
325         } catch (PackageManager.NameNotFoundException e) {
326             return false;
327         }
328 
329     }
330 
331     /**
332      * Returns whether the current user is the system user.
333      */
isCurrentUserSystem()334     public boolean isCurrentUserSystem() {
335         return UserHandle.myUserId() == UserHandle.USER_SYSTEM;
336     }
337 
338     /**
339      * Returns whether the device is currently managed.
340      */
isDeviceManaged(Context context)341     public boolean isDeviceManaged(Context context) {
342         DevicePolicyManager dpm =
343                 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
344         return dpm.isDeviceManaged();
345     }
346 
347     /**
348      * Returns true if the given package requires an update.
349      *
350      * <p>There are two cases where an update is required:
351      * 1. The package is not currently present on the device.
352      * 2. The package is present, but the version is below the minimum supported version.
353      *
354      * @param packageName the package to be checked for updates
355      * @param minSupportedVersion the minimum supported version
356      * @param context a {@link Context} object
357      */
packageRequiresUpdate(String packageName, int minSupportedVersion, Context context)358     public boolean packageRequiresUpdate(String packageName, int minSupportedVersion,
359             Context context) {
360         try {
361             PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
362             // Always download packages if no minimum version given.
363             if (minSupportedVersion != PackageDownloadInfo.DEFAULT_MINIMUM_VERSION
364                     && packageInfo.versionCode >= minSupportedVersion) {
365                 return false;
366             }
367         } catch (NameNotFoundException e) {
368             // Package not on device.
369         }
370 
371         return true;
372     }
373 
374     /**
375      * Returns the first existing managed profile if any present, null otherwise.
376      *
377      * <p>Note that we currently only support one managed profile per device.
378      */
379     // TODO: Add unit tests
getManagedProfile(Context context)380     public UserHandle getManagedProfile(Context context) {
381         UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
382         int currentUserId = userManager.getUserHandle();
383         List<UserInfo> userProfiles = userManager.getProfiles(currentUserId);
384         for (UserInfo profile : userProfiles) {
385             if (profile.isManagedProfile()) {
386                 return new UserHandle(profile.id);
387             }
388         }
389         return null;
390     }
391 
392     /**
393      * Returns the user id of an already existing managed profile or -1 if none exists.
394      */
395     // TODO: Add unit tests
alreadyHasManagedProfile(Context context)396     public int alreadyHasManagedProfile(Context context) {
397         UserHandle managedUser = getManagedProfile(context);
398         if (managedUser != null) {
399             return managedUser.getIdentifier();
400         } else {
401             return -1;
402         }
403     }
404 
405     /**
406      * Removes an account asynchronously.
407      *
408      * @see #removeAccount(Context, Account)
409      */
removeAccountAsync(Context context, Account accountToRemove, RemoveAccountListener callback)410     public void removeAccountAsync(Context context, Account accountToRemove,
411             RemoveAccountListener callback) {
412         new RemoveAccountAsyncTask(context, accountToRemove, this, callback).execute();
413     }
414 
415     /**
416      * Removes an account synchronously.
417      *
418      * This method is blocking and must never be called from the main thread.
419      *
420      * <p>This removes the given account from the calling user's list of accounts.
421      *
422      * @param context a {@link Context} object
423      * @param account the account to be removed
424      */
425     // TODO: Add unit tests
426     @WorkerThread
removeAccount(Context context, Account account)427     void removeAccount(Context context, Account account) {
428         final AccountManager accountManager =
429                 (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
430         final AccountManagerFuture<Bundle> bundle = accountManager.removeAccount(account,
431                 null, null /* callback */, null /* handler */);
432         // Block to get the result of the removeAccount operation
433         try {
434             final Bundle result = bundle.getResult();
435             if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, /* default */ false)) {
436                 ProvisionLogger.logw("Account removed from the primary user.");
437             } else {
438                 final Intent removeIntent = result.getParcelable(AccountManager.KEY_INTENT);
439                 if (removeIntent != null) {
440                     ProvisionLogger.logi("Starting activity to remove account");
441                     new Handler(Looper.getMainLooper()).post(() -> {
442                         TrampolineActivity.startActivity(context, removeIntent);
443                     });
444                 } else {
445                     ProvisionLogger.logw("Could not remove account from the primary user.");
446                 }
447             }
448         } catch (OperationCanceledException | AuthenticatorException | IOException e) {
449             ProvisionLogger.logw("Exception removing account from the primary user.", e);
450         }
451     }
452 
453     /**
454      * Returns whether FRP is supported on the device.
455      */
isFrpSupported(Context context)456     public boolean isFrpSupported(Context context) {
457         Object pdbManager = context.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
458         return pdbManager != null;
459     }
460 
461     /**
462      * Translates a given managed provisioning intent to its corresponding provisioning flow, using
463      * the action from the intent.
464      *
465      * <p/>This is necessary because, unlike other provisioning actions which has 1:1 mapping, there
466      * are multiple actions that can trigger the device owner provisioning flow. This includes
467      * {@link ACTION_PROVISION_MANAGED_DEVICE}, {@link ACTION_NDEF_DISCOVERED} and
468      * {@link ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}. These 3 actions are equivalent
469      * excepts they are sent from a different source.
470      *
471      * @return the appropriate DevicePolicyManager declared action for the given incoming intent.
472      * @throws IllegalProvisioningArgumentException if intent is malformed
473      */
474     // TODO: Add unit tests
mapIntentToDpmAction(Intent intent)475     public String mapIntentToDpmAction(Intent intent)
476             throws IllegalProvisioningArgumentException {
477         if (intent == null || intent.getAction() == null) {
478             throw new IllegalProvisioningArgumentException("Null intent action.");
479         }
480 
481         // Map the incoming intent to a DevicePolicyManager.ACTION_*, as there is a N:1 mapping in
482         // some cases.
483         String dpmProvisioningAction;
484         switch (intent.getAction()) {
485             // Trivial cases.
486             case ACTION_PROVISION_MANAGED_DEVICE:
487             case ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE:
488             case ACTION_PROVISION_MANAGED_USER:
489             case ACTION_PROVISION_MANAGED_PROFILE:
490             case ACTION_PROVISION_FINANCED_DEVICE:
491                 dpmProvisioningAction = intent.getAction();
492                 break;
493 
494             // Silent device owner is same as device owner.
495             case ACTION_PROVISION_MANAGED_DEVICE_SILENTLY:
496                 dpmProvisioningAction = ACTION_PROVISION_MANAGED_DEVICE;
497                 break;
498 
499             // NFC cases which need to take mime-type into account.
500             case ACTION_NDEF_DISCOVERED:
501                 String mimeType = intent.getType();
502                 if (mimeType == null) {
503                     throw new IllegalProvisioningArgumentException(
504                             "Unknown NFC bump mime-type: " + mimeType);
505                 }
506                 switch (mimeType) {
507                     case MIME_TYPE_PROVISIONING_NFC:
508                         dpmProvisioningAction = ACTION_PROVISION_MANAGED_DEVICE;
509                         break;
510 
511                     default:
512                         throw new IllegalProvisioningArgumentException(
513                                 "Unknown NFC bump mime-type: " + mimeType);
514                 }
515                 break;
516 
517             // Device owner provisioning from a trusted app.
518             // TODO (b/27217042): review for new management modes in split system-user model
519             case ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE:
520                 dpmProvisioningAction = ACTION_PROVISION_MANAGED_DEVICE;
521                 break;
522 
523             default:
524                 throw new IllegalProvisioningArgumentException("Unknown intent action "
525                         + intent.getAction());
526         }
527         return dpmProvisioningAction;
528     }
529 
530     /**
531      * Returns if the given intent for a organization owned provisioning.
532      * Only QR, cloud enrollment and NFC are owned by organization.
533      */
isOrganizationOwnedProvisioning(Intent intent)534     public boolean isOrganizationOwnedProvisioning(Intent intent) {
535         if (ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
536             return true;
537         }
538         if (!ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE.equals(intent.getAction())) {
539             return false;
540         }
541         //  Do additional check under ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE
542         // in order to exclude force DO.
543         switch (intent.getIntExtra(DevicePolicyManager.EXTRA_PROVISIONING_TRIGGER,
544                 PROVISIONING_TRIGGER_UNSPECIFIED)) {
545             case PROVISIONING_TRIGGER_CLOUD_ENROLLMENT:
546             case PROVISIONING_TRIGGER_QR_CODE:
547                 return true;
548             default:
549                 return false;
550         }
551     }
552 
isQrProvisioning(Intent intent)553     public boolean isQrProvisioning(Intent intent) {
554         return PROVISIONING_TRIGGER_QR_CODE ==
555                 intent.getIntExtra(
556                         DevicePolicyManager.EXTRA_PROVISIONING_TRIGGER,
557                         /* defValue= */ PROVISIONING_TRIGGER_UNSPECIFIED);
558     }
559 
560     /**
561      * Returns if the given parameter is for provisioning the admin integrated flow.
562      */
isAdminIntegratedFlow(ProvisioningParams params)563     public boolean isAdminIntegratedFlow(ProvisioningParams params) {
564         if (!params.isOrganizationOwnedProvisioning) {
565             return false;
566         }
567         return params.provisioningMode == PROVISIONING_MODE_FULLY_MANAGED_DEVICE
568                 || params.provisioningMode == PROVISIONING_MODE_MANAGED_PROFILE
569                 || params.provisioningMode
570                     == PROVISIONING_MODE_MANAGED_PROFILE_ON_FULLY_NAMAGED_DEVICE;
571     }
572 
573     /**
574      * Sends an intent to trigger a factory reset.
575      */
576     // TODO: Move the FR intent into a Globals class.
sendFactoryResetBroadcast(Context context, String reason)577     public void sendFactoryResetBroadcast(Context context, String reason) {
578         Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
579         // Send explicit broadcast due to Broadcast Limitations
580         intent.setPackage("android");
581         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
582         intent.putExtra(Intent.EXTRA_REASON, reason);
583         context.sendBroadcast(intent);
584     }
585 
586     /**
587      * Returns whether the given provisioning action is a profile owner action.
588      */
589     // TODO: Move the list of device owner actions into a Globals class.
isProfileOwnerAction(String action)590     public final boolean isProfileOwnerAction(String action) {
591         return ACTION_PROVISION_MANAGED_PROFILE.equals(action)
592                 || ACTION_PROVISION_MANAGED_USER.equals(action);
593     }
594 
595     /**
596      * Returns whether the given provisioning action is a device owner action.
597      */
598     // TODO: Move the list of device owner actions into a Globals class.
isDeviceOwnerAction(String action)599     public final boolean isDeviceOwnerAction(String action) {
600         return ACTION_PROVISION_MANAGED_DEVICE.equals(action)
601                 || ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE.equals(action);
602     }
603 
604     /**
605      * Returns whether the given provisioning action is a financed device action.
606      */
isFinancedDeviceAction(String action)607     public final boolean isFinancedDeviceAction(String action) {
608         return ACTION_PROVISION_FINANCED_DEVICE.equals(action);
609     }
610 
611     /**
612      * Returns whether the device currently has connectivity.
613      */
isConnectedToNetwork(Context context)614     public boolean isConnectedToNetwork(Context context) {
615         NetworkInfo info = getActiveNetworkInfo(context);
616         return info != null && info.isConnected();
617     }
618 
isMobileNetworkConnectedToInternet(Context context)619     public boolean isMobileNetworkConnectedToInternet(Context context) {
620         final ConnectivityManager connectivityManager =
621                 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
622         return Arrays.stream(connectivityManager.getAllNetworks())
623                 .filter(network -> {
624                     return Objects.nonNull(connectivityManager.getNetworkCapabilities(network));
625                 })
626                 .map(connectivityManager::getNetworkCapabilities)
627                 .filter(this::isCellularNetwork)
628                 .anyMatch(this::isConnectedToInternet);
629     }
630 
isConnectedToInternet(NetworkCapabilities capabilities)631     private boolean isConnectedToInternet(NetworkCapabilities capabilities) {
632         return capabilities.hasCapability(NET_CAPABILITY_INTERNET)
633                 && capabilities.hasCapability(NET_CAPABILITY_VALIDATED);
634     }
635 
isCellularNetwork(NetworkCapabilities capabilities)636     private boolean isCellularNetwork(NetworkCapabilities capabilities) {
637         return capabilities.hasTransport(TRANSPORT_CELLULAR);
638     }
639 
640     /**
641      * Returns whether the device is currently connected to specific network type, such as {@link
642      * ConnectivityManager.TYPE_WIFI} or {@link ConnectivityManager.TYPE_ETHERNET}
643      *
644      * {@see ConnectivityManager}
645      */
isNetworkTypeConnected(Context context, int... types)646     public boolean isNetworkTypeConnected(Context context, int... types) {
647         final NetworkInfo networkInfo = getActiveNetworkInfo(context);
648         if (networkInfo != null && networkInfo.isConnected()) {
649             final int activeNetworkType = networkInfo.getType();
650             for (int type : types) {
651                 if (activeNetworkType == type) {
652                     return true;
653                 }
654             }
655         }
656         return false;
657     }
658 
659     /**
660      * Returns the active network info of the device.
661      */
getActiveNetworkInfo(Context context)662     public NetworkInfo getActiveNetworkInfo(Context context) {
663         ConnectivityManager cm =
664                 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
665         return cm.getActiveNetworkInfo();
666     }
667 
668     /**
669      * Returns whether encryption is required on this device.
670      *
671      * <p>Encryption is required if the device is not currently encrypted and the persistent
672      * system flag {@code persist.sys.no_req_encrypt} is not set.
673      */
isEncryptionRequired()674     public boolean isEncryptionRequired() {
675         return !isPhysicalDeviceEncrypted()
676                 && !SystemProperties.getBoolean("persist.sys.no_req_encrypt", false);
677     }
678 
679     /**
680      * Returns whether the device is currently encrypted.
681      */
isPhysicalDeviceEncrypted()682     public boolean isPhysicalDeviceEncrypted() {
683         return StorageManager.isEncrypted();
684     }
685 
686     /**
687      * Returns the wifi pick intent.
688      */
689     // TODO: Move this intent into a Globals class.
getWifiPickIntent()690     public Intent getWifiPickIntent() {
691         Intent wifiIntent = new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK);
692         wifiIntent.putExtra("extra_prefs_show_button_bar", true);
693         wifiIntent.putExtra("wifi_enable_next_on_connect", true);
694         return wifiIntent;
695     }
696 
697     /**
698      * Returns whether the device has a split system user.
699      *
700      * <p>Split system user means that user 0 is system only and all meat users are separate from
701      * the system user.
702      */
isSplitSystemUser()703     public boolean isSplitSystemUser() {
704         return UserManager.isSplitSystemUser();
705     }
706 
707     /**
708      * Returns whether the currently chosen launcher supports managed profiles.
709      *
710      * <p>A launcher is deemed to support managed profiles when its target API version is at least
711      * {@link Build.VERSION_CODES#LOLLIPOP}.
712      */
currentLauncherSupportsManagedProfiles(Context context)713     public boolean currentLauncherSupportsManagedProfiles(Context context) {
714         Intent intent = new Intent(Intent.ACTION_MAIN);
715         intent.addCategory(Intent.CATEGORY_HOME);
716 
717         PackageManager pm = context.getPackageManager();
718         ResolveInfo launcherResolveInfo = pm.resolveActivity(intent,
719                 PackageManager.MATCH_DEFAULT_ONLY);
720         if (launcherResolveInfo == null) {
721             return false;
722         }
723         try {
724             // If the user has not chosen a default launcher, then launcherResolveInfo will be
725             // referring to the resolver activity. It is fine to create a managed profile in
726             // this case since there will always be at least one launcher on the device that
727             // supports managed profile feature.
728             ApplicationInfo launcherAppInfo = pm.getApplicationInfo(
729                     launcherResolveInfo.activityInfo.packageName, 0 /* default flags */);
730             return versionNumberAtLeastL(launcherAppInfo.targetSdkVersion);
731         } catch (PackageManager.NameNotFoundException e) {
732             return false;
733         }
734     }
735 
736     /**
737      * Returns whether the given version number is at least lollipop.
738      *
739      * @param versionNumber the version number to be verified.
740      */
versionNumberAtLeastL(int versionNumber)741     private boolean versionNumberAtLeastL(int versionNumber) {
742         return versionNumber >= Build.VERSION_CODES.LOLLIPOP;
743     }
744 
745     /**
746      * Computes the sha 256 hash of a byte array.
747      */
748     @Nullable
computeHashOfByteArray(byte[] bytes)749     public byte[] computeHashOfByteArray(byte[] bytes) {
750         try {
751             MessageDigest md = MessageDigest.getInstance(SHA256_TYPE);
752             md.update(bytes);
753             return md.digest();
754         } catch (NoSuchAlgorithmException e) {
755             ProvisionLogger.loge("Hashing algorithm " + SHA256_TYPE + " not supported.", e);
756             return null;
757         }
758     }
759 
760     /**
761      * Computes a hash of a file with a spcific hash algorithm.
762      */
763     // TODO: Add unit tests
764     @Nullable
computeHashOfFile(String fileLocation, String hashType)765     public byte[] computeHashOfFile(String fileLocation, String hashType) {
766         InputStream fis = null;
767         MessageDigest md;
768         byte hash[] = null;
769         try {
770             md = MessageDigest.getInstance(hashType);
771         } catch (NoSuchAlgorithmException e) {
772             ProvisionLogger.loge("Hashing algorithm " + hashType + " not supported.", e);
773             return null;
774         }
775         try {
776             fis = new FileInputStream(fileLocation);
777 
778             byte[] buffer = new byte[256];
779             int n = 0;
780             while (n != -1) {
781                 n = fis.read(buffer);
782                 if (n > 0) {
783                     md.update(buffer, 0, n);
784                 }
785             }
786             hash = md.digest();
787         } catch (IOException e) {
788             ProvisionLogger.loge("IO error.", e);
789         } finally {
790             // Close input stream quietly.
791             try {
792                 if (fis != null) {
793                     fis.close();
794                 }
795             } catch (IOException e) {
796                 // Ignore.
797             }
798         }
799         return hash;
800     }
801 
isBrightColor(int color)802     public boolean isBrightColor(int color) {
803         // This comes from the YIQ transformation. We're using the formula:
804         // Y = .299 * R + .587 * G + .114 * B
805         return Color.red(color) * 299 + Color.green(color) * 587 + Color.blue(color) * 114
806                 >= 1000 * THRESHOLD_BRIGHT_COLOR;
807     }
808 
809     /**
810      * Returns whether given intent can be resolved for the user.
811      */
canResolveIntentAsUser(Context context, Intent intent, int userId)812     public boolean canResolveIntentAsUser(Context context, Intent intent, int userId) {
813         return intent != null
814                 && context.getPackageManager().resolveActivityAsUser(intent, 0, userId) != null;
815     }
816 
isPackageDeviceOwner(DevicePolicyManager dpm, String packageName)817     public boolean isPackageDeviceOwner(DevicePolicyManager dpm, String packageName) {
818         final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnCallingUser();
819         return deviceOwner != null && deviceOwner.getPackageName().equals(packageName);
820     }
821 
getAccentColor(Context context)822     public int getAccentColor(Context context) {
823         return getAttrColor(context, android.R.attr.colorAccent);
824     }
825 
getAttrColor(Context context, int attr)826     private int getAttrColor(Context context, int attr) {
827         TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
828         int attrColor = ta.getColor(0, 0);
829         ta.recycle();
830         return attrColor;
831     }
832 
handleSupportUrl(Context context, CustomizationParams customizationParams, ClickableSpanFactory clickableSpanFactory, AccessibilityContextMenuMaker contextMenuMaker, TextView textView, String deviceProvider, String contactDeviceProvider)833     public void handleSupportUrl(Context context, CustomizationParams customizationParams,
834                 ClickableSpanFactory clickableSpanFactory,
835                 AccessibilityContextMenuMaker contextMenuMaker, TextView textView,
836                 String deviceProvider, String contactDeviceProvider) {
837         if (customizationParams.supportUrl == null) {
838             textView.setText(contactDeviceProvider);
839             return;
840         }
841         final Intent intent = WebActivity.createIntent(
842                 context, customizationParams.supportUrl, customizationParams.statusBarColor);
843 
844         handlePartialClickableTextView(textView, contactDeviceProvider, deviceProvider, intent,
845                 clickableSpanFactory);
846 
847         contextMenuMaker.registerWithActivity(textView);
848     }
849 
850     /**
851      * Utility function to make a TextView partial clickable. It also associates the TextView with
852      * an Intent. The intent will be triggered when the clickable part is clicked.
853      *
854      * @param textView The TextView which hosts the clickable string.
855      * @param content The content of the TextView.
856      * @param clickableString The substring which is clickable.
857      * @param intent The Intent that will be launched.
858      * @param clickableSpanFactory The factory which is used to create ClickableSpan to decorate
859      *                             clickable string.
860      */
handlePartialClickableTextView(TextView textView, String content, String clickableString, Intent intent, ClickableSpanFactory clickableSpanFactory)861     public void handlePartialClickableTextView(TextView textView, String content,
862             String clickableString, Intent intent, ClickableSpanFactory clickableSpanFactory) {
863         final SpannableString spannableString = new SpannableString(content);
864         if (intent != null) {
865             final ClickableSpan span = clickableSpanFactory.create(intent);
866             final int startIdx = content.indexOf(clickableString);
867             final int endIdx = startIdx + clickableString.length();
868 
869             spannableString.setSpan(span, startIdx, endIdx, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
870             textView.setMovementMethod(LinkMovementMethod.getInstance());
871         }
872 
873         textView.setText(spannableString);
874     }
875 
isSilentProvisioningForTestingDeviceOwner( Context context, ProvisioningParams params)876     public static boolean isSilentProvisioningForTestingDeviceOwner(
877                 Context context, ProvisioningParams params) {
878         final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
879         final ComponentName currentDeviceOwner =
880                 dpm.getDeviceOwnerComponentOnCallingUser();
881         final ComponentName targetDeviceAdmin = params.deviceAdminComponentName;
882 
883         switch (params.provisioningAction) {
884             case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE:
885                 return isPackageTestOnly(context, params)
886                         && currentDeviceOwner != null
887                         && targetDeviceAdmin != null
888                         && currentDeviceOwner.equals(targetDeviceAdmin);
889             default:
890                 return false;
891         }
892     }
893 
isSilentProvisioningForTestingManagedProfile( Context context, ProvisioningParams params)894     private static boolean isSilentProvisioningForTestingManagedProfile(
895         Context context, ProvisioningParams params) {
896         return DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE.equals(
897                 params.provisioningAction) && isPackageTestOnly(context, params);
898     }
899 
isSilentProvisioning(Context context, ProvisioningParams params)900     public static boolean isSilentProvisioning(Context context, ProvisioningParams params) {
901         return isSilentProvisioningForTestingManagedProfile(context, params)
902                 || isSilentProvisioningForTestingDeviceOwner(context, params);
903     }
904 
isPackageTestOnly(Context context, ProvisioningParams params)905     private static boolean isPackageTestOnly(Context context, ProvisioningParams params) {
906         final UserManager userManager = context.getSystemService(UserManager.class);
907         return isPackageTestOnly(context.getPackageManager(),
908                 params.inferDeviceAdminPackageName(), userManager.getUserHandle());
909     }
910 
addNextButton(GlifLayout layout, @NonNull OnClickListener listener)911     public static FooterButton addNextButton(GlifLayout layout, @NonNull OnClickListener listener) {
912         return setPrimaryButton(layout, listener, ButtonType.NEXT, R.string.next);
913     }
914 
addDoneButton(GlifLayout layout, @NonNull OnClickListener listener)915     public static FooterButton addDoneButton(GlifLayout layout, @NonNull OnClickListener listener) {
916         return setPrimaryButton(layout, listener, ButtonType.DONE, R.string.done);
917     }
918 
addAcceptAndContinueButton(GlifLayout layout, @NonNull OnClickListener listener)919     public static FooterButton addAcceptAndContinueButton(GlifLayout layout,
920         @NonNull OnClickListener listener) {
921         return setPrimaryButton(layout, listener, ButtonType.NEXT, R.string.accept_and_continue);
922     }
923 
setPrimaryButton(GlifLayout layout, OnClickListener listener, @ButtonType int buttonType, @StringRes int label)924     private static FooterButton setPrimaryButton(GlifLayout layout, OnClickListener listener,
925         @ButtonType int buttonType, @StringRes int label) {
926         final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
927         final FooterButton primaryButton = new FooterButton.Builder(layout.getContext())
928             .setText(label)
929             .setListener(listener)
930             .setButtonType(buttonType)
931             .setTheme(R.style.SudGlifButton_Primary)
932             .build();
933         mixin.setPrimaryButton(primaryButton);
934         return primaryButton;
935     }
936 
createCancelProvisioningResetDialogBuilder()937     public SimpleDialog.Builder createCancelProvisioningResetDialogBuilder() {
938         final int positiveResId = R.string.reset;
939         final int negativeResId = R.string.device_owner_cancel_cancel;
940         final int dialogMsgResId = R.string.this_will_reset_take_back_first_screen;
941         return getBaseDialogBuilder(positiveResId, negativeResId, dialogMsgResId)
942                 .setTitle(R.string.stop_setup_reset_device_question);
943     }
944 
createCancelProvisioningDialogBuilder()945     public SimpleDialog.Builder createCancelProvisioningDialogBuilder() {
946         final int positiveResId = R.string.profile_owner_cancel_ok;
947         final int negativeResId = R.string.profile_owner_cancel_cancel;
948         final int dialogMsgResId = R.string.profile_owner_cancel_message;
949         return getBaseDialogBuilder(positiveResId, negativeResId, dialogMsgResId);
950     }
951 
getBaseDialogBuilder( int positiveResId, int negativeResId, int dialogMsgResId)952     private SimpleDialog.Builder getBaseDialogBuilder(
953             int positiveResId, int negativeResId, int dialogMsgResId) {
954         return new SimpleDialog.Builder()
955                 .setCancelable(false)
956                 .setMessage(dialogMsgResId)
957                 .setNegativeButtonMessage(negativeResId)
958                 .setPositiveButtonMessage(positiveResId);
959     }
960 }
961