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