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