Utils.java revision cb4628e81cf01fa83b92a68b6313282efc176d48
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    private static final int THRESHOLD_BRIGHT_COLOR = 160; // A color needs a brightness of at least
81    // this value to be considered bright. (brightness being between 0 and 255).
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    // TODO: Add unit tests
176    @NonNull
177    public ComponentName findDeviceAdmin(String dpcPackageName, ComponentName dpcComponentName,
178            Context context) throws IllegalProvisioningArgumentException {
179        if (dpcComponentName != null) {
180            dpcPackageName = dpcComponentName.getPackageName();
181        }
182        if (dpcPackageName == null) {
183            throw new IllegalProvisioningArgumentException("Neither the package name nor the"
184                    + " component name of the admin are supplied");
185        }
186        PackageInfo pi;
187        try {
188            pi = context.getPackageManager().getPackageInfo(dpcPackageName,
189                    PackageManager.GET_RECEIVERS | PackageManager.MATCH_DISABLED_COMPONENTS);
190        } catch (NameNotFoundException e) {
191            throw new IllegalProvisioningArgumentException("Dpc "+ dpcPackageName
192                    + " is not installed. ", e);
193        }
194        ComponentName componentName = findDeviceAdminInPackage(dpcPackageName, pi);
195        if (componentName == null) {
196            throw new IllegalProvisioningArgumentException("Cannot find admin receiver in "
197                    + "package");
198        }
199
200        if (dpcComponentName != null && !componentName.equals(dpcComponentName)) {
201            throw new IllegalProvisioningArgumentException("Device admin component name does not" +
202                    "match the package information." +
203                    " expected:" + dpcComponentName.flattenToString() +
204                    " found:" + componentName.flattenToString());
205        }
206
207        return componentName;
208    }
209
210    /**
211     * Finds a device admin in a given {@link PackageInfo} object.
212     *
213     * <p>This function returns {@code null} if no or multiple admin receivers were found, and if
214     * the package name does not match dpcPackageName.</p>
215     * @param packageName packge name that should match the {@link PackageInfo} object.
216     * @param packageInfo package info to be examined.
217     * @return admin receiver or null in case of error.
218     */
219    @Nullable
220    public ComponentName findDeviceAdminInPackage(String packageName, PackageInfo packageInfo) {
221        if (packageInfo == null || !TextUtils.equals(packageInfo.packageName, packageName)) {
222            return null;
223        }
224
225        ComponentName mdmComponentName = null;
226        for (ActivityInfo ai : packageInfo.receivers) {
227            if (TextUtils.equals(ai.permission, android.Manifest.permission.BIND_DEVICE_ADMIN)) {
228                if (mdmComponentName != null) {
229                    return null;
230                } else {
231                    mdmComponentName = new ComponentName(packageName, ai.name);
232                }
233            }
234        }
235        return mdmComponentName;
236    }
237
238    /**
239     * Returns whether the current user is the system user.
240     */
241    public boolean isCurrentUserSystem() {
242        return UserHandle.myUserId() == UserHandle.USER_SYSTEM;
243    }
244
245    /**
246     * Returns whether the device is currently managed.
247     */
248    public boolean isDeviceManaged(Context context) {
249        DevicePolicyManager dpm =
250                (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
251        return dpm.isDeviceManaged();
252    }
253
254    /**
255     * Returns true if the given package requires an update.
256     *
257     * <p>There are two cases where an update is required:
258     * 1. The package is not currently present on the device.
259     * 2. The package is present, but the version is below the minimum supported version.
260     *
261     * @param packageName the package to be checked for updates
262     * @param minSupportedVersion the minimum supported version
263     * @param context a {@link Context} object
264     */
265    public boolean packageRequiresUpdate(String packageName, int minSupportedVersion,
266            Context context) {
267        try {
268            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
269            // Always download packages if no minimum version given.
270            if (minSupportedVersion != PackageDownloadInfo.DEFAULT_MINIMUM_VERSION
271                    && packageInfo.versionCode >= minSupportedVersion) {
272                return false;
273            }
274        } catch (NameNotFoundException e) {
275            // Package not on device.
276        }
277
278        return true;
279    }
280
281    /**
282     * Returns the first existing managed profile if any present, null otherwise.
283     *
284     * <p>Note that we currently only support one managed profile per device.
285     */
286    // TODO: Add unit tests
287    public UserHandle getManagedProfile(Context context) {
288        UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
289        int currentUserId = userManager.getUserHandle();
290        List<UserInfo> userProfiles = userManager.getProfiles(currentUserId);
291        for (UserInfo profile : userProfiles) {
292            if (profile.isManagedProfile()) {
293                return new UserHandle(profile.id);
294            }
295        }
296        return null;
297    }
298
299    /**
300     * Returns the user id of an already existing managed profile or -1 if none exists.
301     */
302    // TODO: Add unit tests
303    public int alreadyHasManagedProfile(Context context) {
304        UserHandle managedUser = getManagedProfile(context);
305        if (managedUser != null) {
306            return managedUser.getIdentifier();
307        } else {
308            return -1;
309        }
310    }
311
312    /**
313     * Removes an account.
314     *
315     * <p>This removes the given account from the calling user's list of accounts.
316     *
317     * @param context a {@link Context} object
318     * @param account the account to be removed
319     */
320    // TODO: Add unit tests
321    public void removeAccount(Context context, Account account) {
322        try {
323            AccountManager accountManager =
324                    (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
325            AccountManagerFuture<Bundle> bundle = accountManager.removeAccount(account,
326                    null, null /* callback */, null /* handler */);
327            // Block to get the result of the removeAccount operation
328            if (bundle.getResult().getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
329                ProvisionLogger.logw("Account removed from the primary user.");
330            } else {
331                Intent removeIntent = (Intent) bundle.getResult().getParcelable(
332                        AccountManager.KEY_INTENT);
333                if (removeIntent != null) {
334                    ProvisionLogger.logi("Starting activity to remove account");
335                    TrampolineActivity.startActivity(context, removeIntent);
336                } else {
337                    ProvisionLogger.logw("Could not remove account from the primary user.");
338                }
339            }
340        } catch (OperationCanceledException | AuthenticatorException | IOException e) {
341            ProvisionLogger.logw("Exception removing account from the primary user.", e);
342        }
343    }
344
345    /**
346     * Returns whether FRP is supported on the device.
347     */
348    public boolean isFrpSupported(Context context) {
349        Object pdbManager = context.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
350        return pdbManager != null;
351    }
352
353    /**
354     * Translates a given managed provisioning intent to its corresponding provisioning flow, using
355     * the action from the intent.
356     *
357     * <p/>This is necessary because, unlike other provisioning actions which has 1:1 mapping, there
358     * are multiple actions that can trigger the device owner provisioning flow. This includes
359     * {@link ACTION_PROVISION_MANAGED_DEVICE}, {@link ACTION_NDEF_DISCOVERED} and
360     * {@link ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}. These 3 actions are equivalent
361     * excepts they are sent from a different source.
362     *
363     * @return the appropriate DevicePolicyManager declared action for the given incoming intent.
364     * @throws IllegalProvisioningArgumentException if intent is malformed
365     */
366    // TODO: Add unit tests
367    public String mapIntentToDpmAction(Intent intent)
368            throws IllegalProvisioningArgumentException {
369        if (intent == null || intent.getAction() == null) {
370            throw new IllegalProvisioningArgumentException("Null intent action.");
371        }
372
373        // Map the incoming intent to a DevicePolicyManager.ACTION_*, as there is a N:1 mapping in
374        // some cases.
375        String dpmProvisioningAction;
376        switch (intent.getAction()) {
377            // Trivial cases.
378            case ACTION_PROVISION_MANAGED_DEVICE:
379            case ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE:
380            case ACTION_PROVISION_MANAGED_USER:
381            case ACTION_PROVISION_MANAGED_PROFILE:
382                dpmProvisioningAction = intent.getAction();
383                break;
384
385            // NFC cases which need to take mime-type into account.
386            case ACTION_NDEF_DISCOVERED:
387                String mimeType = intent.getType();
388                if (mimeType == null) {
389                    throw new IllegalProvisioningArgumentException(
390                            "Unknown NFC bump mime-type: " + mimeType);
391                }
392                switch (mimeType) {
393                    case MIME_TYPE_PROVISIONING_NFC:
394                        dpmProvisioningAction = ACTION_PROVISION_MANAGED_DEVICE;
395                        break;
396
397                    default:
398                        throw new IllegalProvisioningArgumentException(
399                                "Unknown NFC bump mime-type: " + mimeType);
400                }
401                break;
402
403            // Device owner provisioning from a trusted app.
404            // TODO (b/27217042): review for new management modes in split system-user model
405            case ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE:
406                dpmProvisioningAction = ACTION_PROVISION_MANAGED_DEVICE;
407                break;
408
409            default:
410                throw new IllegalProvisioningArgumentException("Unknown intent action "
411                        + intent.getAction());
412        }
413        return dpmProvisioningAction;
414    }
415
416    /**
417     * Sends an intent to trigger a factory reset.
418     */
419    // TODO: Move the FR intent into a Globals class.
420    public void sendFactoryResetBroadcast(Context context, String reason) {
421        Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR);
422        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
423        intent.putExtra(Intent.EXTRA_REASON, reason);
424        context.sendBroadcast(intent);
425    }
426
427    /**
428     * Returns whether the given provisioning action is a profile owner action.
429     */
430    // TODO: Move the list of device owner actions into a Globals class.
431    public final boolean isProfileOwnerAction(String action) {
432        return action.equals(ACTION_PROVISION_MANAGED_PROFILE)
433                || action.equals(ACTION_PROVISION_MANAGED_USER);
434    }
435
436    /**
437     * Returns whether the given provisioning action is a device owner action.
438     */
439    // TODO: Move the list of device owner actions into a Globals class.
440    public final boolean isDeviceOwnerAction(String action) {
441        return action.equals(ACTION_PROVISION_MANAGED_DEVICE)
442                || action.equals(ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE);
443    }
444
445    /**
446     * Returns whether the device currently has connectivity.
447     */
448    public boolean isConnectedToNetwork(Context context) {
449        NetworkInfo info = getActiveNetworkInfo(context);
450        return info != null && info.isConnected();
451    }
452
453    /**
454     * Returns whether the device is currently connected to a wifi.
455     */
456    public boolean isConnectedToWifi(Context context) {
457        NetworkInfo info = getActiveNetworkInfo(context);
458        return info != null
459                && info.isConnected()
460                && info.getType() == ConnectivityManager.TYPE_WIFI;
461    }
462
463    /**
464     * Returns the active network info of the device.
465     */
466    public NetworkInfo getActiveNetworkInfo(Context context) {
467        ConnectivityManager cm =
468                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
469        return cm.getActiveNetworkInfo();
470    }
471
472    /**
473     * Returns whether encryption is required on this device.
474     *
475     * <p>Encryption is required if the device is not currently encrypted and the persistent
476     * system flag {@code persist.sys.no_req_encrypt} is not set.
477     */
478    public boolean isEncryptionRequired() {
479        return !isPhysicalDeviceEncrypted()
480                && !SystemProperties.getBoolean("persist.sys.no_req_encrypt", false);
481    }
482
483    /**
484     * Returns whether the device is currently encrypted.
485     */
486    public boolean isPhysicalDeviceEncrypted() {
487        return StorageManager.isEncrypted();
488    }
489
490    /**
491     * Returns the wifi pick intent.
492     */
493    // TODO: Move this intent into a Globals class.
494    public Intent getWifiPickIntent() {
495        Intent wifiIntent = new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK);
496        wifiIntent.putExtra("extra_prefs_show_button_bar", true);
497        wifiIntent.putExtra("wifi_enable_next_on_connect", true);
498        return wifiIntent;
499    }
500
501    /**
502     * Returns whether the device has a split system user.
503     *
504     * <p>Split system user means that user 0 is system only and all meat users are separate from
505     * the system user.
506     */
507    public boolean isSplitSystemUser() {
508        return UserManager.isSplitSystemUser();
509    }
510
511    /**
512     * Returns whether the currently chosen launcher supports managed profiles.
513     *
514     * <p>A launcher is deemed to support managed profiles when its target API version is at least
515     * {@link Build.VERSION_CODES#LOLLIPOP}.
516     */
517    public boolean currentLauncherSupportsManagedProfiles(Context context) {
518        Intent intent = new Intent(Intent.ACTION_MAIN);
519        intent.addCategory(Intent.CATEGORY_HOME);
520
521        PackageManager pm = context.getPackageManager();
522        ResolveInfo launcherResolveInfo = pm.resolveActivity(intent,
523                PackageManager.MATCH_DEFAULT_ONLY);
524        if (launcherResolveInfo == null) {
525            return false;
526        }
527        try {
528            // If the user has not chosen a default launcher, then launcherResolveInfo will be
529            // referring to the resolver activity. It is fine to create a managed profile in
530            // this case since there will always be at least one launcher on the device that
531            // supports managed profile feature.
532            ApplicationInfo launcherAppInfo = pm.getApplicationInfo(
533                    launcherResolveInfo.activityInfo.packageName, 0 /* default flags */);
534            return versionNumberAtLeastL(launcherAppInfo.targetSdkVersion);
535        } catch (PackageManager.NameNotFoundException e) {
536            return false;
537        }
538    }
539
540    /**
541     * Returns whether the given version number is at least lollipop.
542     *
543     * @param versionNumber the version number to be verified.
544     */
545    private boolean versionNumberAtLeastL(int versionNumber) {
546        return versionNumber >= Build.VERSION_CODES.LOLLIPOP;
547    }
548
549    /**
550     * Computes the sha 256 hash of a byte array.
551     */
552    @Nullable
553    public byte[] computeHashOfByteArray(byte[] bytes) {
554        try {
555            MessageDigest md = MessageDigest.getInstance(SHA256_TYPE);
556            md.update(bytes);
557            return md.digest();
558        } catch (NoSuchAlgorithmException e) {
559            ProvisionLogger.loge("Hashing algorithm " + SHA256_TYPE + " not supported.", e);
560            return null;
561        }
562    }
563
564    /**
565     * Computes a hash of a file with a spcific hash algorithm.
566     */
567    // TODO: Add unit tests
568    @Nullable
569    public byte[] computeHashOfFile(String fileLocation, String hashType) {
570        InputStream fis = null;
571        MessageDigest md;
572        byte hash[] = null;
573        try {
574            md = MessageDigest.getInstance(hashType);
575        } catch (NoSuchAlgorithmException e) {
576            ProvisionLogger.loge("Hashing algorithm " + hashType + " not supported.", e);
577            return null;
578        }
579        try {
580            fis = new FileInputStream(fileLocation);
581
582            byte[] buffer = new byte[256];
583            int n = 0;
584            while (n != -1) {
585                n = fis.read(buffer);
586                if (n > 0) {
587                    md.update(buffer, 0, n);
588                }
589            }
590            hash = md.digest();
591        } catch (IOException e) {
592            ProvisionLogger.loge("IO error.", e);
593        } finally {
594            // Close input stream quietly.
595            try {
596                if (fis != null) {
597                    fis.close();
598                }
599            } catch (IOException e) {
600                // Ignore.
601            }
602        }
603        return hash;
604    }
605
606    public boolean isBrightColor(int color) {
607        // This comes from the YIQ transformation. We're using the formula:
608        // Y = .299 * R + .587 * G + .114 * B
609        return Color.red(color) * 299 + Color.green(color) * 587 + Color.blue(color) * 114
610                >= 1000 * THRESHOLD_BRIGHT_COLOR;
611    }
612
613    /**
614     * Returns whether given intent can be resolved for the user.
615     */
616    public boolean canResolveIntentAsUser(Context context, Intent intent, int userId) {
617        return intent != null
618                && context.getPackageManager().resolveActivityAsUser(intent, 0, userId) != null;
619    }
620}
621