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