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