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.nfc.NfcAdapter.ACTION_NDEF_DISCOVERED;
26 
27 import android.accounts.Account;
28 import android.accounts.AccountManager;
29 import android.accounts.AccountManagerFuture;
30 import android.accounts.AuthenticatorException;
31 import android.accounts.OperationCanceledException;
32 import android.annotation.NonNull;
33 import android.annotation.Nullable;
34 import android.app.admin.DevicePolicyManager;
35 import android.content.ComponentName;
36 import android.content.Context;
37 import android.content.Intent;
38 import android.content.pm.ActivityInfo;
39 import android.content.pm.ApplicationInfo;
40 import android.content.pm.IPackageManager;
41 import android.content.pm.PackageInfo;
42 import android.content.pm.PackageManager;
43 import android.content.pm.PackageManager.NameNotFoundException;
44 import android.content.pm.ResolveInfo;
45 import android.content.pm.UserInfo;
46 import android.graphics.Color;
47 import android.net.ConnectivityManager;
48 import android.net.NetworkInfo;
49 import android.net.wifi.WifiManager;
50 import android.os.Build;
51 import android.os.Bundle;
52 import android.os.RemoteException;
53 import android.os.ServiceManager;
54 import android.os.SystemProperties;
55 import android.os.UserHandle;
56 import android.os.UserManager;
57 import android.os.storage.StorageManager;
58 import android.text.TextUtils;
59 
60 import com.android.internal.annotations.VisibleForTesting;
61 import com.android.managedprovisioning.TrampolineActivity;
62 import com.android.managedprovisioning.model.PackageDownloadInfo;
63 
64 import java.io.FileInputStream;
65 import java.io.IOException;
66 import java.io.InputStream;
67 import java.security.MessageDigest;
68 import java.security.NoSuchAlgorithmException;
69 import java.util.HashSet;
70 import java.util.List;
71 import java.util.Set;
72 
73 /**
74  * Class containing various auxiliary methods.
75  */
76 public class Utils {
77     public static final String SHA256_TYPE = "SHA-256";
78     public static final String SHA1_TYPE = "SHA-1";
79 
80     // value chosen to match UX designs; when updating check status bar icon colors
81     private static final int THRESHOLD_BRIGHT_COLOR = 190;
82 
Utils()83     public Utils() {}
84 
85     /**
86      * Returns the currently installed system apps on a given user.
87      *
88      * <p>Calls into the {@link IPackageManager} to retrieve all installed packages on the given
89      * user and returns the package names of all system apps.
90      *
91      * @param ipm an {@link IPackageManager} object
92      * @param userId the id of the user we are interested in
93      */
getCurrentSystemApps(IPackageManager ipm, int userId)94     public Set<String> getCurrentSystemApps(IPackageManager ipm, int userId) {
95         Set<String> apps = new HashSet<String>();
96         List<ApplicationInfo> aInfos = null;
97         try {
98             aInfos = ipm.getInstalledApplications(
99                     PackageManager.GET_UNINSTALLED_PACKAGES, userId).getList();
100         } catch (RemoteException neverThrown) {
101             ProvisionLogger.loge("This should not happen.", neverThrown);
102         }
103         for (ApplicationInfo aInfo : aInfos) {
104             if ((aInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
105                 apps.add(aInfo.packageName);
106             }
107         }
108         return apps;
109     }
110 
111     /**
112      * Disables a given component in a given user.
113      *
114      * @param toDisable the component that should be disabled
115      * @param userId the id of the user where the component should be disabled.
116      */
disableComponent(ComponentName toDisable, int userId)117     public void disableComponent(ComponentName toDisable, int userId) {
118         setComponentEnabledSetting(
119                 IPackageManager.Stub.asInterface(ServiceManager.getService("package")),
120                 toDisable,
121                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
122                 userId);
123     }
124 
125     /**
126      * Enables a given component in a given user.
127      *
128      * @param toEnable the component that should be enabled
129      * @param userId the id of the user where the component should be disabled.
130      */
enableComponent(ComponentName toEnable, int userId)131     public void enableComponent(ComponentName toEnable, int userId) {
132         setComponentEnabledSetting(
133                 IPackageManager.Stub.asInterface(ServiceManager.getService("package")),
134                 toEnable,
135                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
136                 userId);
137     }
138 
139     /**
140      * Disables a given component in a given user.
141      *
142      * @param ipm an {@link IPackageManager} object
143      * @param toDisable the component that should be disabled
144      * @param userId the id of the user where the component should be disabled.
145      */
146     @VisibleForTesting
setComponentEnabledSetting(IPackageManager ipm, ComponentName toDisable, int enabledSetting, int userId)147     void setComponentEnabledSetting(IPackageManager ipm, ComponentName toDisable,
148             int enabledSetting, int userId) {
149         try {
150             ipm.setComponentEnabledSetting(toDisable,
151                     enabledSetting, PackageManager.DONT_KILL_APP,
152                     userId);
153         } catch (RemoteException neverThrown) {
154             ProvisionLogger.loge("This should not happen.", neverThrown);
155         } catch (Exception e) {
156             ProvisionLogger.logw("Component not found, not changing enabled setting: "
157                 + toDisable.toShortString());
158         }
159     }
160 
161     /**
162      * Check the validity of the admin component name supplied, or try to infer this componentName
163      * from the package.
164      *
165      * We are supporting lookup by package name for legacy reasons.
166      *
167      * If dpcComponentName is supplied (not null): dpcPackageName is ignored.
168      * Check that the package of dpcComponentName is installed, that dpcComponentName is a
169      * receiver in this package, and return it. The receiver can be in disabled state.
170      *
171      * Otherwise: dpcPackageName must be supplied (not null).
172      * Check that this package is installed, try to infer a potential device admin in this package,
173      * and return it.
174      */
175     @NonNull
findDeviceAdmin(String dpcPackageName, ComponentName dpcComponentName, Context context)176     public ComponentName findDeviceAdmin(String dpcPackageName, ComponentName dpcComponentName,
177             Context context) throws IllegalProvisioningArgumentException {
178         if (dpcComponentName != null) {
179             dpcPackageName = dpcComponentName.getPackageName();
180         }
181         if (dpcPackageName == null) {
182             throw new IllegalProvisioningArgumentException("Neither the package name nor the"
183                     + " component name of the admin are supplied");
184         }
185         PackageInfo pi;
186         try {
187             pi = context.getPackageManager().getPackageInfo(dpcPackageName,
188                     PackageManager.GET_RECEIVERS | PackageManager.MATCH_DISABLED_COMPONENTS);
189         } catch (NameNotFoundException e) {
190             throw new IllegalProvisioningArgumentException("Dpc "+ dpcPackageName
191                     + " is not installed. ", e);
192         }
193 
194         final ComponentName componentName = findDeviceAdminInPackageInfo(dpcPackageName,
195                 dpcComponentName, pi);
196         if (componentName == null) {
197             throw new IllegalProvisioningArgumentException("Cannot find any admin receiver in "
198                     + "package " + dpcPackageName + " with component " + dpcComponentName);
199         }
200         return componentName;
201     }
202 
203     /**
204      * If dpcComponentName is 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, try to infer a potential device admin component in this package info.
209      *
210      * @return infered device admin component in package info. Otherwise, null
211      */
212     @Nullable
findDeviceAdminInPackageInfo(@onNull String dpcPackageName, @Nullable ComponentName dpcComponentName, @NonNull PackageInfo pi)213     public ComponentName findDeviceAdminInPackageInfo(@NonNull String dpcPackageName,
214             @Nullable ComponentName dpcComponentName, @NonNull PackageInfo pi) {
215         if (dpcComponentName != null) {
216             if (!isComponentInPackageInfo(dpcComponentName, pi)) {
217                 ProvisionLogger.logw("The component " + dpcComponentName + " isn't registered in "
218                         + "the apk");
219                 return null;
220             }
221             return dpcComponentName;
222         } else {
223             return findDeviceAdminInPackage(dpcPackageName, pi);
224         }
225     }
226 
227     /**
228      * Finds a device admin in a given {@link PackageInfo} object.
229      *
230      * <p>This function returns {@code null} if no or multiple admin receivers were found, and if
231      * the package name does not match dpcPackageName.</p>
232      * @param packageName packge name that should match the {@link PackageInfo} object.
233      * @param packageInfo package info to be examined.
234      * @return admin receiver or null in case of error.
235      */
236     @Nullable
findDeviceAdminInPackage(String packageName, PackageInfo packageInfo)237     private ComponentName findDeviceAdminInPackage(String packageName, PackageInfo packageInfo) {
238         if (packageInfo == null || !TextUtils.equals(packageInfo.packageName, packageName)) {
239             return null;
240         }
241 
242         ComponentName mdmComponentName = null;
243         for (ActivityInfo ai : packageInfo.receivers) {
244             if (TextUtils.equals(ai.permission, android.Manifest.permission.BIND_DEVICE_ADMIN)) {
245                 if (mdmComponentName != null) {
246                     ProvisionLogger.logw("more than 1 device admin component are found");
247                     return null;
248                 } else {
249                     mdmComponentName = new ComponentName(packageName, ai.name);
250                 }
251             }
252         }
253         return mdmComponentName;
254     }
255 
isComponentInPackageInfo(ComponentName dpcComponentName, PackageInfo pi)256     private boolean isComponentInPackageInfo(ComponentName dpcComponentName,
257             PackageInfo pi) {
258         for (ActivityInfo ai : pi.receivers) {
259             if (dpcComponentName.getClassName().equals(ai.name)) {
260                 return true;
261             }
262         }
263         return false;
264     }
265 
266     /**
267      * Return if a given package has testOnly="true", in which case we'll relax certain rules
268      * for CTS.
269      *
270      * The system allows this flag to be changed when an app is updated. But
271      * {@link DevicePolicyManager} uses the persisted version to do actual checks for relevant
272      * dpm command.
273      *
274      * @see DevicePolicyManagerService#isPackageTestOnly for more info
275      */
isPackageTestOnly(PackageManager pm, String packageName, int userHandle)276     public boolean isPackageTestOnly(PackageManager pm, String packageName, int userHandle) {
277         if (TextUtils.isEmpty(packageName)) {
278             return false;
279         }
280 
281         try {
282             final ApplicationInfo ai = pm.getApplicationInfoAsUser(packageName,
283                     PackageManager.MATCH_DIRECT_BOOT_AWARE
284                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle);
285             return ai != null && (ai.flags & ApplicationInfo.FLAG_TEST_ONLY) != 0;
286         } catch (PackageManager.NameNotFoundException e) {
287             return false;
288         }
289 
290     }
291 
292     /**
293      * Returns whether the current user is the system user.
294      */
isCurrentUserSystem()295     public boolean isCurrentUserSystem() {
296         return UserHandle.myUserId() == UserHandle.USER_SYSTEM;
297     }
298 
299     /**
300      * Returns whether the device is currently managed.
301      */
isDeviceManaged(Context context)302     public boolean isDeviceManaged(Context context) {
303         DevicePolicyManager dpm =
304                 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
305         return dpm.isDeviceManaged();
306     }
307 
308     /**
309      * Returns true if the given package requires an update.
310      *
311      * <p>There are two cases where an update is required:
312      * 1. The package is not currently present on the device.
313      * 2. The package is present, but the version is below the minimum supported version.
314      *
315      * @param packageName the package to be checked for updates
316      * @param minSupportedVersion the minimum supported version
317      * @param context a {@link Context} object
318      */
packageRequiresUpdate(String packageName, int minSupportedVersion, Context context)319     public boolean packageRequiresUpdate(String packageName, int minSupportedVersion,
320             Context context) {
321         try {
322             PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
323             // Always download packages if no minimum version given.
324             if (minSupportedVersion != PackageDownloadInfo.DEFAULT_MINIMUM_VERSION
325                     && packageInfo.versionCode >= minSupportedVersion) {
326                 return false;
327             }
328         } catch (NameNotFoundException e) {
329             // Package not on device.
330         }
331 
332         return true;
333     }
334 
335     /**
336      * Returns the first existing managed profile if any present, null otherwise.
337      *
338      * <p>Note that we currently only support one managed profile per device.
339      */
340     // TODO: Add unit tests
getManagedProfile(Context context)341     public UserHandle getManagedProfile(Context context) {
342         UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
343         int currentUserId = userManager.getUserHandle();
344         List<UserInfo> userProfiles = userManager.getProfiles(currentUserId);
345         for (UserInfo profile : userProfiles) {
346             if (profile.isManagedProfile()) {
347                 return new UserHandle(profile.id);
348             }
349         }
350         return null;
351     }
352 
353     /**
354      * Returns the user id of an already existing managed profile or -1 if none exists.
355      */
356     // TODO: Add unit tests
alreadyHasManagedProfile(Context context)357     public int alreadyHasManagedProfile(Context context) {
358         UserHandle managedUser = getManagedProfile(context);
359         if (managedUser != null) {
360             return managedUser.getIdentifier();
361         } else {
362             return -1;
363         }
364     }
365 
366     /**
367      * Removes an account.
368      *
369      * <p>This removes the given account from the calling user's list of accounts.
370      *
371      * @param context a {@link Context} object
372      * @param account the account to be removed
373      */
374     // TODO: Add unit tests
removeAccount(Context context, Account account)375     public void removeAccount(Context context, Account account) {
376         try {
377             AccountManager accountManager =
378                     (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
379             AccountManagerFuture<Bundle> bundle = accountManager.removeAccount(account,
380                     null, null /* callback */, null /* handler */);
381             // Block to get the result of the removeAccount operation
382             if (bundle.getResult().getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
383                 ProvisionLogger.logw("Account removed from the primary user.");
384             } else {
385                 Intent removeIntent = (Intent) bundle.getResult().getParcelable(
386                         AccountManager.KEY_INTENT);
387                 if (removeIntent != null) {
388                     ProvisionLogger.logi("Starting activity to remove account");
389                     TrampolineActivity.startActivity(context, removeIntent);
390                 } else {
391                     ProvisionLogger.logw("Could not remove account from the primary user.");
392                 }
393             }
394         } catch (OperationCanceledException | AuthenticatorException | IOException e) {
395             ProvisionLogger.logw("Exception removing account from the primary user.", e);
396         }
397     }
398 
399     /**
400      * Returns whether FRP is supported on the device.
401      */
isFrpSupported(Context context)402     public boolean isFrpSupported(Context context) {
403         Object pdbManager = context.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
404         return pdbManager != null;
405     }
406 
407     /**
408      * Translates a given managed provisioning intent to its corresponding provisioning flow, using
409      * the action from the intent.
410      *
411      * <p/>This is necessary because, unlike other provisioning actions which has 1:1 mapping, there
412      * are multiple actions that can trigger the device owner provisioning flow. This includes
413      * {@link ACTION_PROVISION_MANAGED_DEVICE}, {@link ACTION_NDEF_DISCOVERED} and
414      * {@link ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}. These 3 actions are equivalent
415      * excepts they are sent from a different source.
416      *
417      * @return the appropriate DevicePolicyManager declared action for the given incoming intent.
418      * @throws IllegalProvisioningArgumentException if intent is malformed
419      */
420     // TODO: Add unit tests
mapIntentToDpmAction(Intent intent)421     public String mapIntentToDpmAction(Intent intent)
422             throws IllegalProvisioningArgumentException {
423         if (intent == null || intent.getAction() == null) {
424             throw new IllegalProvisioningArgumentException("Null intent action.");
425         }
426 
427         // Map the incoming intent to a DevicePolicyManager.ACTION_*, as there is a N:1 mapping in
428         // some cases.
429         String dpmProvisioningAction;
430         switch (intent.getAction()) {
431             // Trivial cases.
432             case ACTION_PROVISION_MANAGED_DEVICE:
433             case ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE:
434             case ACTION_PROVISION_MANAGED_USER:
435             case ACTION_PROVISION_MANAGED_PROFILE:
436                 dpmProvisioningAction = intent.getAction();
437                 break;
438 
439             // NFC cases which need to take mime-type into account.
440             case ACTION_NDEF_DISCOVERED:
441                 String mimeType = intent.getType();
442                 if (mimeType == null) {
443                     throw new IllegalProvisioningArgumentException(
444                             "Unknown NFC bump mime-type: " + mimeType);
445                 }
446                 switch (mimeType) {
447                     case MIME_TYPE_PROVISIONING_NFC:
448                         dpmProvisioningAction = ACTION_PROVISION_MANAGED_DEVICE;
449                         break;
450 
451                     default:
452                         throw new IllegalProvisioningArgumentException(
453                                 "Unknown NFC bump mime-type: " + mimeType);
454                 }
455                 break;
456 
457             // Device owner provisioning from a trusted app.
458             // TODO (b/27217042): review for new management modes in split system-user model
459             case ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE:
460                 dpmProvisioningAction = ACTION_PROVISION_MANAGED_DEVICE;
461                 break;
462 
463             default:
464                 throw new IllegalProvisioningArgumentException("Unknown intent action "
465                         + intent.getAction());
466         }
467         return dpmProvisioningAction;
468     }
469 
470     /**
471      * Sends an intent to trigger a factory reset.
472      */
473     // TODO: Move the FR intent into a Globals class.
sendFactoryResetBroadcast(Context context, String reason)474     public void sendFactoryResetBroadcast(Context context, String reason) {
475         Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
476         // Send explicit broadcast due to Broadcast Limitations
477         intent.setPackage("android");
478         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
479         intent.putExtra(Intent.EXTRA_REASON, reason);
480         context.sendBroadcast(intent);
481     }
482 
483     /**
484      * Returns whether the given provisioning action is a profile owner action.
485      */
486     // TODO: Move the list of device owner actions into a Globals class.
isProfileOwnerAction(String action)487     public final boolean isProfileOwnerAction(String action) {
488         return action.equals(ACTION_PROVISION_MANAGED_PROFILE)
489                 || action.equals(ACTION_PROVISION_MANAGED_USER);
490     }
491 
492     /**
493      * Returns whether the given provisioning action is a device owner action.
494      */
495     // TODO: Move the list of device owner actions into a Globals class.
isDeviceOwnerAction(String action)496     public final boolean isDeviceOwnerAction(String action) {
497         return action.equals(ACTION_PROVISION_MANAGED_DEVICE)
498                 || action.equals(ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE);
499     }
500 
501     /**
502      * Returns whether the device currently has connectivity.
503      */
isConnectedToNetwork(Context context)504     public boolean isConnectedToNetwork(Context context) {
505         NetworkInfo info = getActiveNetworkInfo(context);
506         return info != null && info.isConnected();
507     }
508 
509     /**
510      * Returns whether the device is currently connected to a wifi.
511      */
isConnectedToWifi(Context context)512     public boolean isConnectedToWifi(Context context) {
513         NetworkInfo info = getActiveNetworkInfo(context);
514         return info != null
515                 && info.isConnected()
516                 && info.getType() == ConnectivityManager.TYPE_WIFI;
517     }
518 
519     /**
520      * Returns the active network info of the device.
521      */
getActiveNetworkInfo(Context context)522     public NetworkInfo getActiveNetworkInfo(Context context) {
523         ConnectivityManager cm =
524                 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
525         return cm.getActiveNetworkInfo();
526     }
527 
528     /**
529      * Returns whether encryption is required on this device.
530      *
531      * <p>Encryption is required if the device is not currently encrypted and the persistent
532      * system flag {@code persist.sys.no_req_encrypt} is not set.
533      */
isEncryptionRequired()534     public boolean isEncryptionRequired() {
535         return !isPhysicalDeviceEncrypted()
536                 && !SystemProperties.getBoolean("persist.sys.no_req_encrypt", false);
537     }
538 
539     /**
540      * Returns whether the device is currently encrypted.
541      */
isPhysicalDeviceEncrypted()542     public boolean isPhysicalDeviceEncrypted() {
543         return StorageManager.isEncrypted();
544     }
545 
546     /**
547      * Returns the wifi pick intent.
548      */
549     // TODO: Move this intent into a Globals class.
getWifiPickIntent()550     public Intent getWifiPickIntent() {
551         Intent wifiIntent = new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK);
552         wifiIntent.putExtra("extra_prefs_show_button_bar", true);
553         wifiIntent.putExtra("wifi_enable_next_on_connect", true);
554         return wifiIntent;
555     }
556 
557     /**
558      * Returns whether the device has a split system user.
559      *
560      * <p>Split system user means that user 0 is system only and all meat users are separate from
561      * the system user.
562      */
isSplitSystemUser()563     public boolean isSplitSystemUser() {
564         return UserManager.isSplitSystemUser();
565     }
566 
567     /**
568      * Returns whether the currently chosen launcher supports managed profiles.
569      *
570      * <p>A launcher is deemed to support managed profiles when its target API version is at least
571      * {@link Build.VERSION_CODES#LOLLIPOP}.
572      */
currentLauncherSupportsManagedProfiles(Context context)573     public boolean currentLauncherSupportsManagedProfiles(Context context) {
574         Intent intent = new Intent(Intent.ACTION_MAIN);
575         intent.addCategory(Intent.CATEGORY_HOME);
576 
577         PackageManager pm = context.getPackageManager();
578         ResolveInfo launcherResolveInfo = pm.resolveActivity(intent,
579                 PackageManager.MATCH_DEFAULT_ONLY);
580         if (launcherResolveInfo == null) {
581             return false;
582         }
583         try {
584             // If the user has not chosen a default launcher, then launcherResolveInfo will be
585             // referring to the resolver activity. It is fine to create a managed profile in
586             // this case since there will always be at least one launcher on the device that
587             // supports managed profile feature.
588             ApplicationInfo launcherAppInfo = pm.getApplicationInfo(
589                     launcherResolveInfo.activityInfo.packageName, 0 /* default flags */);
590             return versionNumberAtLeastL(launcherAppInfo.targetSdkVersion);
591         } catch (PackageManager.NameNotFoundException e) {
592             return false;
593         }
594     }
595 
596     /**
597      * Returns whether the given version number is at least lollipop.
598      *
599      * @param versionNumber the version number to be verified.
600      */
versionNumberAtLeastL(int versionNumber)601     private boolean versionNumberAtLeastL(int versionNumber) {
602         return versionNumber >= Build.VERSION_CODES.LOLLIPOP;
603     }
604 
605     /**
606      * Computes the sha 256 hash of a byte array.
607      */
608     @Nullable
computeHashOfByteArray(byte[] bytes)609     public byte[] computeHashOfByteArray(byte[] bytes) {
610         try {
611             MessageDigest md = MessageDigest.getInstance(SHA256_TYPE);
612             md.update(bytes);
613             return md.digest();
614         } catch (NoSuchAlgorithmException e) {
615             ProvisionLogger.loge("Hashing algorithm " + SHA256_TYPE + " not supported.", e);
616             return null;
617         }
618     }
619 
620     /**
621      * Computes a hash of a file with a spcific hash algorithm.
622      */
623     // TODO: Add unit tests
624     @Nullable
computeHashOfFile(String fileLocation, String hashType)625     public byte[] computeHashOfFile(String fileLocation, String hashType) {
626         InputStream fis = null;
627         MessageDigest md;
628         byte hash[] = null;
629         try {
630             md = MessageDigest.getInstance(hashType);
631         } catch (NoSuchAlgorithmException e) {
632             ProvisionLogger.loge("Hashing algorithm " + hashType + " not supported.", e);
633             return null;
634         }
635         try {
636             fis = new FileInputStream(fileLocation);
637 
638             byte[] buffer = new byte[256];
639             int n = 0;
640             while (n != -1) {
641                 n = fis.read(buffer);
642                 if (n > 0) {
643                     md.update(buffer, 0, n);
644                 }
645             }
646             hash = md.digest();
647         } catch (IOException e) {
648             ProvisionLogger.loge("IO error.", e);
649         } finally {
650             // Close input stream quietly.
651             try {
652                 if (fis != null) {
653                     fis.close();
654                 }
655             } catch (IOException e) {
656                 // Ignore.
657             }
658         }
659         return hash;
660     }
661 
isBrightColor(int color)662     public boolean isBrightColor(int color) {
663         // This comes from the YIQ transformation. We're using the formula:
664         // Y = .299 * R + .587 * G + .114 * B
665         return Color.red(color) * 299 + Color.green(color) * 587 + Color.blue(color) * 114
666                 >= 1000 * THRESHOLD_BRIGHT_COLOR;
667     }
668 
669     /**
670      * Returns whether given intent can be resolved for the user.
671      */
canResolveIntentAsUser(Context context, Intent intent, int userId)672     public boolean canResolveIntentAsUser(Context context, Intent intent, int userId) {
673         return intent != null
674                 && context.getPackageManager().resolveActivityAsUser(intent, 0, userId) != null;
675     }
676 
isPackageDeviceOwner(DevicePolicyManager dpm, String packageName)677     public boolean isPackageDeviceOwner(DevicePolicyManager dpm, String packageName) {
678         final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnCallingUser();
679         return deviceOwner != null && deviceOwner.getPackageName().equals(packageName);
680     }
681 }
682