1/*
2 * Copyright (C) 2017 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 */
16package com.android.settings.development;
17
18import android.content.ContentResolver;
19import android.content.Context;
20import android.content.Intent;
21import android.content.pm.PackageManager;
22import android.content.pm.ResolveInfo;
23import android.os.UserHandle;
24import android.os.UserManager;
25import android.provider.Settings;
26import android.support.annotation.VisibleForTesting;
27import android.support.v7.preference.Preference;
28import android.support.v7.preference.PreferenceScreen;
29
30import com.android.settings.core.PreferenceControllerMixin;
31import com.android.settingslib.RestrictedLockUtils;
32import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
33import com.android.settingslib.RestrictedSwitchPreference;
34import com.android.settingslib.core.AbstractPreferenceController;
35
36import java.util.List;
37
38/**
39 * Controller to manage the state of "Verify apps over USB" toggle.
40 */
41public class VerifyAppsOverUsbPreferenceController extends AbstractPreferenceController implements
42        PreferenceControllerMixin {
43    private static final String VERIFY_APPS_OVER_USB_KEY = "verify_apps_over_usb";
44    private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
45
46    private RestrictedSwitchPreference mPreference;
47
48    /**
49     * Class for indirection of RestrictedLockUtils for testing purposes. It would be nice to mock
50     * the appropriate methods in UserManager instead but they aren't accessible.
51     */
52    @VisibleForTesting
53    class RestrictedLockUtilsDelegate {
54        public EnforcedAdmin checkIfRestrictionEnforced(
55                Context context, String userRestriction, int userId) {
56            return RestrictedLockUtils.checkIfRestrictionEnforced(context, userRestriction, userId);
57        }
58    }
59    // NB: This field is accessed using reflection in the test, please keep name in sync.
60    private final RestrictedLockUtilsDelegate mRestrictedLockUtils =
61            new RestrictedLockUtilsDelegate();
62
63    VerifyAppsOverUsbPreferenceController(Context context) {
64        super(context);
65    }
66
67    @Override
68    public void displayPreference(PreferenceScreen screen) {
69        super.displayPreference(screen);
70        if (isAvailable()) {
71            mPreference = (RestrictedSwitchPreference)
72                    screen.findPreference(VERIFY_APPS_OVER_USB_KEY);
73        }
74    }
75
76    @Override
77    public boolean isAvailable() {
78        return Settings.Global.getInt(mContext.getContentResolver(),
79                Settings.Global.PACKAGE_VERIFIER_SETTING_VISIBLE, 1) > 0;
80    }
81
82    @Override
83    public String getPreferenceKey() {
84        return VERIFY_APPS_OVER_USB_KEY;
85    }
86
87    /** Saves the settings value when it is toggled. */
88    @Override
89    public boolean handlePreferenceTreeClick(Preference preference) {
90        if (VERIFY_APPS_OVER_USB_KEY.equals(preference.getKey())) {
91            Settings.Global.putInt(mContext.getContentResolver(),
92                    Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, mPreference.isChecked() ? 1 : 0);
93            return true;
94        }
95        return false;
96    }
97
98    /**
99     * Checks whether the toggle should be enabled depending on whether verify apps over USB is
100     * possible currently. If ADB is disabled or if package verifier does not exist, the toggle
101     * should be disabled.
102     */
103    private boolean shouldBeEnabled() {
104        final ContentResolver cr = mContext.getContentResolver();
105        if (Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) == 0) {
106            return false;
107        }
108        if (Settings.Global.getInt(cr, Settings.Global.PACKAGE_VERIFIER_ENABLE, 1) == 0) {
109            return false;
110        } else {
111            final PackageManager pm = mContext.getPackageManager();
112            final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
113            verification.setType(PACKAGE_MIME_TYPE);
114            verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
115            final List<ResolveInfo> receivers = pm.queryBroadcastReceivers(verification, 0);
116            if (receivers.size() == 0) {
117                return false;
118            }
119        }
120        return true;
121    }
122
123    /**
124     * Updates position, enabled status and maybe admin message.
125     */
126    public void updatePreference() {
127        if (!isAvailable()) {
128            return;
129        }
130
131        if (!shouldBeEnabled()) {
132            mPreference.setChecked(false);
133            mPreference.setDisabledByAdmin(null);
134            mPreference.setEnabled(false);
135            return;
136        }
137
138        final EnforcedAdmin enforcingAdmin = mRestrictedLockUtils.checkIfRestrictionEnforced(
139                        mContext, UserManager.ENSURE_VERIFY_APPS, UserHandle.myUserId());
140        if (enforcingAdmin != null) {
141            mPreference.setChecked(true);
142            mPreference.setDisabledByAdmin(enforcingAdmin);
143            return;
144        }
145
146        mPreference.setEnabled(true);
147        final boolean checked = Settings.Global.getInt(mContext.getContentResolver(),
148                Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1) != 0;
149        mPreference.setChecked(checked);
150    }
151}
152