1/*
2* Copyright (C) 2015 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.packageinstaller.permission.ui.handheld;
18
19import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
20
21import android.app.ActionBar;
22import android.app.Activity;
23import android.app.AlertDialog;
24import android.app.Fragment;
25import android.content.Context;
26import android.content.DialogInterface;
27import android.content.Intent;
28import android.content.pm.ApplicationInfo;
29import android.content.pm.PackageInfo;
30import android.content.pm.PackageManager;
31import android.graphics.drawable.Drawable;
32import android.net.Uri;
33import android.os.Bundle;
34import android.preference.Preference;
35import android.preference.Preference.OnPreferenceChangeListener;
36import android.preference.Preference.OnPreferenceClickListener;
37import android.preference.PreferenceScreen;
38import android.preference.SwitchPreference;
39import android.provider.Settings;
40import android.util.IconDrawableFactory;
41import android.util.Log;
42import android.view.Menu;
43import android.view.MenuInflater;
44import android.view.MenuItem;
45import android.view.View;
46import android.widget.Switch;
47import android.widget.Toast;
48
49import com.android.packageinstaller.R;
50import com.android.packageinstaller.permission.model.AppPermissionGroup;
51import com.android.packageinstaller.permission.model.AppPermissions;
52import com.android.packageinstaller.permission.model.Permission;
53import com.android.packageinstaller.permission.utils.LocationUtils;
54import com.android.packageinstaller.permission.utils.SafetyNetLogger;
55import com.android.packageinstaller.permission.utils.Utils;
56import com.android.settingslib.HelpUtils;
57import com.android.settingslib.RestrictedLockUtils;
58
59import java.util.ArrayList;
60import java.util.List;
61
62public final class AppPermissionsFragment extends SettingsWithHeader
63        implements OnPreferenceChangeListener {
64
65    private static final String LOG_TAG = "ManagePermsFragment";
66
67    static final String EXTRA_HIDE_INFO_BUTTON = "hideInfoButton";
68
69    private static final int MENU_ALL_PERMS = 0;
70
71    private List<AppPermissionGroup> mToggledGroups;
72    private AppPermissions mAppPermissions;
73    private PreferenceScreen mExtraScreen;
74
75    private boolean mHasConfirmedRevoke;
76
77    public static AppPermissionsFragment newInstance(String packageName) {
78        return setPackageName(new AppPermissionsFragment(), packageName);
79    }
80
81    private static <T extends Fragment> T setPackageName(T fragment, String packageName) {
82        Bundle arguments = new Bundle();
83        arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
84        fragment.setArguments(arguments);
85        return fragment;
86    }
87
88    @Override
89    public void onCreate(Bundle savedInstanceState) {
90        super.onCreate(savedInstanceState);
91        setLoading(true /* loading */, false /* animate */);
92        setHasOptionsMenu(true);
93        final ActionBar ab = getActivity().getActionBar();
94        if (ab != null) {
95            ab.setDisplayHomeAsUpEnabled(true);
96        }
97
98        String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
99        Activity activity = getActivity();
100        PackageInfo packageInfo = getPackageInfo(activity, packageName);
101        if (packageInfo == null) {
102            Toast.makeText(activity, R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show();
103            activity.finish();
104            return;
105        }
106
107        mAppPermissions = new AppPermissions(activity, packageInfo, null, true, new Runnable() {
108            @Override
109            public void run() {
110                getActivity().finish();
111            }
112        });
113        loadPreferences();
114    }
115
116    @Override
117    public void onResume() {
118        super.onResume();
119        mAppPermissions.refresh();
120        loadPreferences();
121        setPreferencesCheckedState();
122    }
123
124    @Override
125    public boolean onOptionsItemSelected(MenuItem item) {
126        switch (item.getItemId()) {
127            case android.R.id.home: {
128                getActivity().finish();
129                return true;
130            }
131
132            case MENU_ALL_PERMS: {
133                showAllPermissions(null);
134                return true;
135            }
136        }
137        return super.onOptionsItemSelected(item);
138    }
139
140    @Override
141    public void onViewCreated(View view, Bundle savedInstanceState) {
142        super.onViewCreated(view, savedInstanceState);
143        if (mAppPermissions != null) {
144            bindUi(this, mAppPermissions.getPackageInfo());
145        }
146    }
147
148    @Override
149    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
150        super.onCreateOptionsMenu(menu, inflater);
151        menu.add(Menu.NONE, MENU_ALL_PERMS, Menu.NONE, R.string.all_permissions);
152        HelpUtils.prepareHelpMenuItem(getActivity(), menu, R.string.help_app_permissions,
153                getClass().getName());
154    }
155
156    private void showAllPermissions(String filterGroup) {
157        Fragment frag = AllAppPermissionsFragment.newInstance(
158                getArguments().getString(Intent.EXTRA_PACKAGE_NAME),
159                filterGroup);
160        getFragmentManager().beginTransaction()
161                .replace(android.R.id.content, frag)
162                .addToBackStack("AllPerms")
163                .commit();
164    }
165
166    private static void bindUi(SettingsWithHeader fragment, PackageInfo packageInfo) {
167        Activity activity = fragment.getActivity();
168        PackageManager pm = activity.getPackageManager();
169        ApplicationInfo appInfo = packageInfo.applicationInfo;
170        Intent infoIntent = null;
171        if (!activity.getIntent().getBooleanExtra(EXTRA_HIDE_INFO_BUTTON, false)) {
172            infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
173                    .setData(Uri.fromParts("package", packageInfo.packageName, null));
174        }
175
176        Drawable icon = IconDrawableFactory.newInstance(activity).getBadgedIcon(appInfo);
177        CharSequence label = appInfo.loadLabel(pm);
178        fragment.setHeader(icon, label, infoIntent);
179
180        ActionBar ab = activity.getActionBar();
181        if (ab != null) {
182            ab.setTitle(R.string.app_permissions);
183        }
184    }
185
186    private void loadPreferences() {
187        Context context = getActivity();
188        if (context == null) {
189            return;
190        }
191
192        PreferenceScreen screen = getPreferenceScreen();
193        if (screen == null) {
194            screen = getPreferenceManager().createPreferenceScreen(getActivity());
195            setPreferenceScreen(screen);
196        }
197
198        screen.removeAll();
199
200        if (mExtraScreen != null) {
201            mExtraScreen.removeAll();
202        }
203
204        final Preference extraPerms = new Preference(context);
205        extraPerms.setIcon(R.drawable.ic_toc);
206        extraPerms.setTitle(R.string.additional_permissions);
207
208        for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
209            if (!Utils.shouldShowPermission(group, mAppPermissions.getPackageInfo().packageName)) {
210                continue;
211            }
212
213            boolean isPlatform = group.getDeclaringPackage().equals(Utils.OS_PKG);
214
215            RestrictedSwitchPreference preference = new RestrictedSwitchPreference(context);
216            preference.setChecked(group.areRuntimePermissionsGranted());
217
218            // Some groups may be a double target - one to toggle and one to fine manage
219            if (Utils.areGroupPermissionsIndividuallyControlled(getContext(), group.getName())) {
220                preference.setOnPreferenceClickListener((pref) -> {
221                    showAllPermissions(group.getName());
222                    return false;
223                });
224
225                preference.setSwitchOnClickListener(v -> {
226                    Switch switchView = (Switch) v;
227                    onPreferenceChange(preference, switchView.isChecked());
228                    updateSummaryForIndividuallyControlledPermissionGroup(
229                            group, preference);
230                    preference.setCheckedOverride(switchView.isChecked());
231                });
232
233                updateSummaryForIndividuallyControlledPermissionGroup(group, preference);
234            } else {
235                preference.setOnPreferenceChangeListener(this);
236            }
237
238            preference.setKey(group.getName());
239            Drawable icon = Utils.loadDrawable(context.getPackageManager(),
240                    group.getIconPkg(), group.getIconResId());
241            preference.setIcon(Utils.applyTint(getContext(), icon,
242                    android.R.attr.colorControlNormal));
243            preference.setTitle(group.getLabel());
244
245
246            if (group.isPolicyFixed()) {
247                EnforcedAdmin admin = RestrictedLockUtils.getProfileOrDeviceOwner(getContext(),
248                        group.getUserId());
249                if (admin != null) {
250                    preference.setDisabledByAdmin(admin);
251                    preference.setSummary(R.string.disabled_by_admin_summary_text);
252                } else {
253                    preference.setSummary(R.string.permission_summary_enforced_by_policy);
254                    preference.setEnabled(false);
255                }
256            }
257            preference.setPersistent(false);
258
259            if (isPlatform) {
260                screen.addPreference(preference);
261            } else {
262                if (mExtraScreen == null) {
263                    mExtraScreen = getPreferenceManager().createPreferenceScreen(context);
264                }
265                mExtraScreen.addPreference(preference);
266            }
267        }
268
269        if (mExtraScreen != null) {
270            extraPerms.setOnPreferenceClickListener(new OnPreferenceClickListener() {
271                @Override
272                public boolean onPreferenceClick(Preference preference) {
273                    AdditionalPermissionsFragment frag = new AdditionalPermissionsFragment();
274                    setPackageName(frag, getArguments().getString(Intent.EXTRA_PACKAGE_NAME));
275                    frag.setTargetFragment(AppPermissionsFragment.this, 0);
276                    getFragmentManager().beginTransaction()
277                            .replace(android.R.id.content, frag)
278                            .addToBackStack(null)
279                            .commit();
280                    return true;
281                }
282            });
283            int count = mExtraScreen.getPreferenceCount();
284            extraPerms.setSummary(getResources().getQuantityString(
285                    R.plurals.additional_permissions_more, count, count));
286            screen.addPreference(extraPerms);
287        }
288
289        setLoading(false /* loading */, true /* animate */);
290    }
291
292    @Override
293    public boolean onPreferenceChange(final Preference preference, Object newValue) {
294        String groupName = preference.getKey();
295        final AppPermissionGroup group = mAppPermissions.getPermissionGroup(groupName);
296
297        if (group == null) {
298            return false;
299        }
300
301        addToggledGroup(group);
302
303        if (LocationUtils.isLocationGroupAndProvider(group.getName(), group.getApp().packageName)) {
304            LocationUtils.showLocationDialog(getContext(), mAppPermissions.getAppLabel());
305            return false;
306        }
307        if (newValue == Boolean.TRUE) {
308            group.grantRuntimePermissions(false);
309        } else {
310            final boolean grantedByDefault = group.hasGrantedByDefaultPermission();
311            if (grantedByDefault || (!group.doesSupportRuntimePermissions()
312                    && !mHasConfirmedRevoke)) {
313                new AlertDialog.Builder(getContext())
314                        .setMessage(grantedByDefault ? R.string.system_warning
315                                : R.string.old_sdk_deny_warning)
316                        .setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> {
317                            if (preference instanceof MultiTargetSwitchPreference) {
318                                ((MultiTargetSwitchPreference) preference).setCheckedOverride(true);
319                            }
320                        })
321                        .setPositiveButton(R.string.grant_dialog_button_deny_anyway,
322                                (DialogInterface dialog, int which) -> {
323                            ((SwitchPreference) preference).setChecked(false);
324                            group.revokeRuntimePermissions(false);
325                            if (Utils.areGroupPermissionsIndividuallyControlled(getContext(),
326                                    group.getName())) {
327                                updateSummaryForIndividuallyControlledPermissionGroup(
328                                        group, preference);
329                            }
330                            if (!grantedByDefault) {
331                                mHasConfirmedRevoke = true;
332                            }
333                        })
334                        .show();
335                return false;
336            } else {
337                group.revokeRuntimePermissions(false);
338            }
339        }
340
341        return true;
342    }
343
344    @Override
345    public void onPause() {
346        super.onPause();
347        logToggledGroups();
348    }
349
350    private void updateSummaryForIndividuallyControlledPermissionGroup(
351            AppPermissionGroup group, Preference preference) {
352        int revokedCount = 0;
353        List<Permission> permissions = group.getPermissions();
354        final int permissionCount = permissions.size();
355        for (int i = 0; i < permissionCount; i++) {
356            Permission permission = permissions.get(i);
357            if (group.doesSupportRuntimePermissions()
358                    ? !permission.isGranted() : (!permission.isAppOpAllowed()
359                            || permission.isReviewRequired())) {
360                revokedCount++;
361            }
362        }
363
364        final int resId;
365        if (revokedCount == 0) {
366            resId = R.string.permission_revoked_none;
367        } else if (revokedCount == permissionCount) {
368            resId = R.string.permission_revoked_all;
369        } else {
370            resId = R.string.permission_revoked_count;
371        }
372
373        String summary = getString(resId, revokedCount);
374        preference.setSummary(summary);
375    }
376
377    private void addToggledGroup(AppPermissionGroup group) {
378        if (mToggledGroups == null) {
379            mToggledGroups = new ArrayList<>();
380        }
381        // Double toggle is back to initial state.
382        if (mToggledGroups.contains(group)) {
383            mToggledGroups.remove(group);
384        } else {
385            mToggledGroups.add(group);
386        }
387    }
388
389    private void logToggledGroups() {
390        if (mToggledGroups != null) {
391            String packageName = mAppPermissions.getPackageInfo().packageName;
392            SafetyNetLogger.logPermissionsToggled(packageName, mToggledGroups);
393            mToggledGroups = null;
394        }
395    }
396
397    private void setPreferencesCheckedState() {
398        setPreferencesCheckedState(getPreferenceScreen());
399        if (mExtraScreen != null) {
400            setPreferencesCheckedState(mExtraScreen);
401        }
402    }
403
404    private void setPreferencesCheckedState(PreferenceScreen screen) {
405        int preferenceCount = screen.getPreferenceCount();
406        for (int i = 0; i < preferenceCount; i++) {
407            Preference preference = screen.getPreference(i);
408            if (preference instanceof SwitchPreference) {
409                SwitchPreference switchPref = (SwitchPreference) preference;
410                AppPermissionGroup group = mAppPermissions.getPermissionGroup(switchPref.getKey());
411                if (group != null) {
412                    switchPref.setChecked(group.areRuntimePermissionsGranted());
413                }
414            }
415        }
416    }
417
418    private static PackageInfo getPackageInfo(Activity activity, String packageName) {
419        try {
420            return activity.getPackageManager().getPackageInfo(
421                    packageName, PackageManager.GET_PERMISSIONS);
422        } catch (PackageManager.NameNotFoundException e) {
423            Log.i(LOG_TAG, "No package:" + activity.getCallingPackage(), e);
424            return null;
425        }
426    }
427
428    public static class AdditionalPermissionsFragment extends SettingsWithHeader {
429        AppPermissionsFragment mOuterFragment;
430
431        @Override
432        public void onCreate(Bundle savedInstanceState) {
433            mOuterFragment = (AppPermissionsFragment) getTargetFragment();
434            super.onCreate(savedInstanceState);
435            setHeader(mOuterFragment.mIcon, mOuterFragment.mLabel, mOuterFragment.mInfoIntent);
436            setHasOptionsMenu(true);
437            setPreferenceScreen(mOuterFragment.mExtraScreen);
438        }
439
440        @Override
441        public void onViewCreated(View view, Bundle savedInstanceState) {
442            super.onViewCreated(view, savedInstanceState);
443            String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
444            bindUi(this, getPackageInfo(getActivity(), packageName));
445        }
446
447        @Override
448        public boolean onOptionsItemSelected(MenuItem item) {
449            switch (item.getItemId()) {
450            case android.R.id.home:
451                getFragmentManager().popBackStack();
452                return true;
453            }
454            return super.onOptionsItemSelected(item);
455        }
456    }
457}
458