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
17package com.android.managedprovisioning.common;
18
19import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE;
20import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE;
21import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
22import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE;
23import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER;
24import static android.app.admin.DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC;
25import static android.nfc.NfcAdapter.ACTION_NDEF_DISCOVERED;
26
27import android.accounts.Account;
28import android.accounts.AccountManager;
29import android.accounts.AccountManagerFuture;
30import android.accounts.AuthenticatorException;
31import android.accounts.OperationCanceledException;
32import android.annotation.NonNull;
33import android.annotation.Nullable;
34import android.app.admin.DevicePolicyManager;
35import android.content.ComponentName;
36import android.content.Context;
37import android.content.Intent;
38import android.content.pm.ActivityInfo;
39import android.content.pm.ApplicationInfo;
40import android.content.pm.IPackageManager;
41import android.content.pm.PackageInfo;
42import android.content.pm.PackageManager;
43import android.content.pm.PackageManager.NameNotFoundException;
44import android.content.pm.ResolveInfo;
45import android.content.pm.UserInfo;
46import android.graphics.Color;
47import android.net.ConnectivityManager;
48import android.net.NetworkInfo;
49import android.net.wifi.WifiManager;
50import android.os.Build;
51import android.os.Bundle;
52import android.os.RemoteException;
53import android.os.ServiceManager;
54import android.os.SystemProperties;
55import android.os.UserHandle;
56import android.os.UserManager;
57import android.os.storage.StorageManager;
58import android.text.TextUtils;
59
60import com.android.internal.annotations.VisibleForTesting;
61import com.android.managedprovisioning.TrampolineActivity;
62import com.android.managedprovisioning.model.PackageDownloadInfo;
63
64import java.io.FileInputStream;
65import java.io.IOException;
66import java.io.InputStream;
67import java.security.MessageDigest;
68import java.security.NoSuchAlgorithmException;
69import java.util.HashSet;
70import java.util.List;
71import java.util.Set;
72
73/**
74 * Class containing various auxiliary methods.
75 */
76public 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
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     */
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     */
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     */
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
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
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
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
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
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     */
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     */
295    public boolean isCurrentUserSystem() {
296        return UserHandle.myUserId() == UserHandle.USER_SYSTEM;
297    }
298
299    /**
300     * Returns whether the device is currently managed.
301     */
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     */
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
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
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
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     */
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
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.
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.
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.
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     */
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     */
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     */
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     */
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     */
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.
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     */
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     */
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     */
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
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
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
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     */
672    public boolean canResolveIntentAsUser(Context context, Intent intent, int userId) {
673        return intent != null
674                && context.getPackageManager().resolveActivityAsUser(intent, 0, userId) != null;
675    }
676
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