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 */
16package com.android.packageinstaller.permission.ui.handheld;
17
18import android.app.ActionBar;
19import android.app.AlertDialog;
20import android.app.Fragment;
21import android.content.Context;
22import android.content.DialogInterface;
23import android.content.DialogInterface.OnClickListener;
24import android.content.Intent;
25import android.graphics.drawable.Drawable;
26import android.os.Bundle;
27import android.preference.Preference;
28import android.preference.Preference.OnPreferenceClickListener;
29import android.preference.PreferenceScreen;
30import android.preference.SwitchPreference;
31import android.util.ArrayMap;
32import android.util.ArraySet;
33import android.view.Menu;
34import android.view.MenuInflater;
35import android.view.MenuItem;
36import android.view.View;
37import com.android.packageinstaller.DeviceUtils;
38import com.android.packageinstaller.R;
39import com.android.packageinstaller.permission.model.AppPermissionGroup;
40import com.android.packageinstaller.permission.model.PermissionApps;
41import com.android.packageinstaller.permission.model.PermissionApps.Callback;
42import com.android.packageinstaller.permission.model.PermissionApps.PermissionApp;
43import com.android.packageinstaller.permission.utils.LocationUtils;
44import com.android.packageinstaller.permission.utils.SafetyNetLogger;
45import com.android.packageinstaller.permission.utils.Utils;
46import com.android.settingslib.HelpUtils;
47import com.android.settingslib.RestrictedLockUtils;
48
49import java.util.ArrayList;
50import java.util.List;
51
52import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
53
54public final class PermissionAppsFragment extends PermissionsFrameFragment implements Callback,
55        Preference.OnPreferenceChangeListener {
56
57    private static final int MENU_SHOW_SYSTEM = Menu.FIRST;
58    private static final int MENU_HIDE_SYSTEM = Menu.FIRST + 1;
59    private static final String KEY_SHOW_SYSTEM_PREFS = "_showSystem";
60
61    public static PermissionAppsFragment newInstance(String permissionName) {
62        return setPermissionName(new PermissionAppsFragment(), permissionName);
63    }
64
65    private static <T extends Fragment> T setPermissionName(T fragment, String permissionName) {
66        Bundle arguments = new Bundle();
67        arguments.putString(Intent.EXTRA_PERMISSION_NAME, permissionName);
68        fragment.setArguments(arguments);
69        return fragment;
70    }
71
72    private PermissionApps mPermissionApps;
73
74    private PreferenceScreen mExtraScreen;
75
76    private ArrayMap<String, AppPermissionGroup> mToggledGroups;
77    private ArraySet<String> mLauncherPkgs;
78    private boolean mHasConfirmedRevoke;
79
80    private boolean mShowSystem;
81    private boolean mHasSystemApps;
82    private MenuItem mShowSystemMenu;
83    private MenuItem mHideSystemMenu;
84
85    private Callback mOnPermissionsLoadedListener;
86
87    @Override
88    public void onCreate(Bundle savedInstanceState) {
89        super.onCreate(savedInstanceState);
90        setLoading(true /* loading */, false /* animate */);
91        setHasOptionsMenu(true);
92        final ActionBar ab = getActivity().getActionBar();
93        if (ab != null) {
94            ab.setDisplayHomeAsUpEnabled(true);
95        }
96        mLauncherPkgs = Utils.getLauncherPackages(getContext());
97
98        String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME);
99        mPermissionApps = new PermissionApps(getActivity(), groupName, this);
100        mPermissionApps.refresh(true);
101    }
102
103    @Override
104    public void onResume() {
105        super.onResume();
106        mPermissionApps.refresh(true);
107    }
108
109    @Override
110    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
111        if (mHasSystemApps) {
112            mShowSystemMenu = menu.add(Menu.NONE, MENU_SHOW_SYSTEM, Menu.NONE,
113                    R.string.menu_show_system);
114            mHideSystemMenu = menu.add(Menu.NONE, MENU_HIDE_SYSTEM, Menu.NONE,
115                    R.string.menu_hide_system);
116            updateMenu();
117        }
118
119        HelpUtils.prepareHelpMenuItem(getActivity(), menu, R.string.help_app_permissions,
120                getClass().getName());
121    }
122
123    @Override
124    public boolean onOptionsItemSelected(MenuItem item) {
125        switch (item.getItemId()) {
126            case android.R.id.home:
127                getActivity().finish();
128                return true;
129            case MENU_SHOW_SYSTEM:
130            case MENU_HIDE_SYSTEM:
131                mShowSystem = item.getItemId() == MENU_SHOW_SYSTEM;
132                if (mPermissionApps.getApps() != null) {
133                    onPermissionsLoaded(mPermissionApps);
134                }
135                updateMenu();
136                break;
137        }
138        return super.onOptionsItemSelected(item);
139    }
140
141    private void updateMenu() {
142        mShowSystemMenu.setVisible(!mShowSystem);
143        mHideSystemMenu.setVisible(mShowSystem);
144    }
145
146    @Override
147    public void onViewCreated(View view, Bundle savedInstanceState) {
148        super.onViewCreated(view, savedInstanceState);
149        bindUi(this, mPermissionApps);
150    }
151
152    private static void bindUi(Fragment fragment, PermissionApps permissionApps) {
153        final Drawable icon = permissionApps.getIcon();
154        final CharSequence label = permissionApps.getLabel();
155        final ActionBar ab = fragment.getActivity().getActionBar();
156        if (ab != null) {
157            ab.setTitle(fragment.getString(R.string.permission_title, label));
158        }
159    }
160
161    private void setOnPermissionsLoadedListener(Callback callback) {
162        mOnPermissionsLoadedListener = callback;
163    }
164
165    @Override
166    public void onPermissionsLoaded(PermissionApps permissionApps) {
167        Context context = getActivity();
168
169        if (context == null) {
170            return;
171        }
172
173        boolean isTelevision = DeviceUtils.isTelevision(context);
174        PreferenceScreen screen = getPreferenceScreen();
175        if (screen == null) {
176            screen = getPreferenceManager().createPreferenceScreen(getActivity());
177            setPreferenceScreen(screen);
178        }
179
180        screen.setOrderingAsAdded(false);
181
182        ArraySet<String> preferencesToRemove = new ArraySet<>();
183        for (int i = 0, n = screen.getPreferenceCount(); i < n; i++) {
184            preferencesToRemove.add(screen.getPreference(i).getKey());
185        }
186        if (mExtraScreen != null) {
187            for (int i = 0, n = mExtraScreen.getPreferenceCount(); i < n; i++) {
188                preferencesToRemove.add(mExtraScreen.getPreference(i).getKey());
189            }
190        }
191
192        mHasSystemApps = false;
193        boolean menuOptionsInvalided = false;
194
195        for (PermissionApp app : permissionApps.getApps()) {
196            if (!Utils.shouldShowPermission(app)) {
197                continue;
198            }
199
200            if (!app.getAppInfo().enabled) {
201                continue;
202            }
203
204            String key = app.getKey();
205            preferencesToRemove.remove(key);
206            Preference existingPref = screen.findPreference(key);
207            if (existingPref == null && mExtraScreen != null) {
208                existingPref = mExtraScreen.findPreference(key);
209            }
210
211            boolean isSystemApp = Utils.isSystem(app, mLauncherPkgs);
212
213            if (isSystemApp && !menuOptionsInvalided) {
214                mHasSystemApps = true;
215                getActivity().invalidateOptionsMenu();
216                menuOptionsInvalided = true;
217            }
218
219            if (isSystemApp && !isTelevision && !mShowSystem) {
220                if (existingPref != null) {
221                    screen.removePreference(existingPref);
222                }
223                continue;
224            }
225
226            if (existingPref != null) {
227                // If existing preference - only update its state.
228                final boolean isPolicyFixed = app.isPolicyFixed();
229                EnforcedAdmin enforcedAdmin = RestrictedLockUtils.getProfileOrDeviceOwner(
230                        getActivity(), app.getUserId());
231                if (!isTelevision && (existingPref instanceof RestrictedSwitchPreference)) {
232                    ((RestrictedSwitchPreference) existingPref).setDisabledByAdmin(
233                            isPolicyFixed ? enforcedAdmin : null);
234                    existingPref.setSummary(isPolicyFixed ?
235                            getString(R.string.disabled_by_admin_summary_text) : null);
236                } else {
237                    existingPref.setEnabled(!isPolicyFixed);
238                    existingPref.setSummary(isPolicyFixed ?
239                            getString(R.string.permission_summary_enforced_by_policy) : null);
240                }
241                existingPref.setPersistent(false);
242                if (existingPref instanceof SwitchPreference) {
243                    ((SwitchPreference) existingPref)
244                            .setChecked(app.areRuntimePermissionsGranted());
245                }
246                continue;
247            }
248
249            RestrictedSwitchPreference pref = new RestrictedSwitchPreference(context);
250            pref.setOnPreferenceChangeListener(this);
251            pref.setKey(app.getKey());
252            pref.setIcon(app.getIcon());
253            pref.setTitle(app.getLabel());
254            EnforcedAdmin enforcedAdmin = RestrictedLockUtils.getProfileOrDeviceOwner(
255                    getActivity(), app.getUserId());
256            if (app.isPolicyFixed()) {
257                if (!isTelevision && enforcedAdmin != null) {
258                    pref.setDisabledByAdmin(enforcedAdmin);
259                    pref.setSummary(R.string.disabled_by_admin_summary_text);
260                } else {
261                    pref.setEnabled(false);
262                    pref.setSummary(R.string.permission_summary_enforced_by_policy);
263                }
264            }
265            pref.setPersistent(false);
266            pref.setChecked(app.areRuntimePermissionsGranted());
267
268            if (isSystemApp && isTelevision) {
269                if (mExtraScreen == null) {
270                    mExtraScreen = getPreferenceManager().createPreferenceScreen(context);
271                }
272                mExtraScreen.addPreference(pref);
273            } else {
274                screen.addPreference(pref);
275            }
276        }
277
278        if (mExtraScreen != null) {
279            preferencesToRemove.remove(KEY_SHOW_SYSTEM_PREFS);
280            Preference pref = screen.findPreference(KEY_SHOW_SYSTEM_PREFS);
281
282            if (pref == null) {
283                pref = new Preference(context);
284                pref.setKey(KEY_SHOW_SYSTEM_PREFS);
285                pref.setIcon(Utils.applyTint(context, R.drawable.ic_toc,
286                        android.R.attr.colorControlNormal));
287                pref.setTitle(R.string.preference_show_system_apps);
288                pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
289                    @Override
290                    public boolean onPreferenceClick(Preference preference) {
291                        SystemAppsFragment frag = new SystemAppsFragment();
292                        setPermissionName(frag, getArguments().getString(Intent.EXTRA_PERMISSION_NAME));
293                        frag.setTargetFragment(PermissionAppsFragment.this, 0);
294                        getFragmentManager().beginTransaction()
295                            .replace(android.R.id.content, frag)
296                            .addToBackStack("SystemApps")
297                            .commit();
298                        return true;
299                    }
300                });
301                screen.addPreference(pref);
302            }
303
304            int grantedCount = 0;
305            for (int i = 0, n = mExtraScreen.getPreferenceCount(); i < n; i++) {
306                if (((SwitchPreference) mExtraScreen.getPreference(i)).isChecked()) {
307                    grantedCount++;
308                }
309            }
310            pref.setSummary(getString(R.string.app_permissions_group_summary,
311                    grantedCount, mExtraScreen.getPreferenceCount()));
312        }
313
314        for (String key : preferencesToRemove) {
315            Preference pref = screen.findPreference(key);
316            if (pref != null) {
317                screen.removePreference(pref);
318            } else if (mExtraScreen != null) {
319                pref = mExtraScreen.findPreference(key);
320                if (pref != null) {
321                    mExtraScreen.removePreference(pref);
322                }
323            }
324        }
325
326        setLoading(false /* loading */, true /* animate */);
327
328        if (mOnPermissionsLoadedListener != null) {
329            mOnPermissionsLoadedListener.onPermissionsLoaded(permissionApps);
330        }
331    }
332
333    @Override
334    public boolean onPreferenceChange(final Preference preference, Object newValue) {
335        String pkg = preference.getKey();
336        final PermissionApp app = mPermissionApps.getApp(pkg);
337
338        if (app == null) {
339            return false;
340        }
341
342        addToggledGroup(app.getPackageName(), app.getPermissionGroup());
343
344        if (LocationUtils.isLocationGroupAndProvider(mPermissionApps.getGroupName(),
345                app.getPackageName())) {
346            LocationUtils.showLocationDialog(getContext(), app.getLabel());
347            return false;
348        }
349        if (newValue == Boolean.TRUE) {
350            app.grantRuntimePermissions();
351        } else {
352            final boolean grantedByDefault = app.hasGrantedByDefaultPermissions();
353            if (grantedByDefault || (!app.hasRuntimePermissions() && !mHasConfirmedRevoke)) {
354                new AlertDialog.Builder(getContext())
355                        .setMessage(grantedByDefault ? R.string.system_warning
356                                : R.string.old_sdk_deny_warning)
357                        .setNegativeButton(R.string.cancel, null)
358                        .setPositiveButton(R.string.grant_dialog_button_deny_anyway,
359                                new OnClickListener() {
360                            @Override
361                            public void onClick(DialogInterface dialog, int which) {
362                                ((SwitchPreference) preference).setChecked(false);
363                                app.revokeRuntimePermissions();
364                                if (!grantedByDefault) {
365                                    mHasConfirmedRevoke = true;
366                                }
367                            }
368                        })
369                        .show();
370                return false;
371            } else {
372                app.revokeRuntimePermissions();
373            }
374        }
375        return true;
376    }
377
378    @Override
379    public void onPause() {
380        super.onPause();
381        logToggledGroups();
382    }
383
384    private void addToggledGroup(String packageName, AppPermissionGroup group) {
385        if (mToggledGroups == null) {
386            mToggledGroups = new ArrayMap<>();
387        }
388        // Double toggle is back to initial state.
389        if (mToggledGroups.containsKey(packageName)) {
390            mToggledGroups.remove(packageName);
391        } else {
392            mToggledGroups.put(packageName, group);
393        }
394    }
395
396    private void logToggledGroups() {
397        if (mToggledGroups != null) {
398            final int groupCount = mToggledGroups.size();
399            for (int i = 0; i < groupCount; i++) {
400                String packageName = mToggledGroups.keyAt(i);
401                List<AppPermissionGroup> groups = new ArrayList<>();
402                groups.add(mToggledGroups.valueAt(i));
403                SafetyNetLogger.logPermissionsToggled(packageName, groups);
404            }
405            mToggledGroups = null;
406        }
407    }
408
409    public static class SystemAppsFragment extends PermissionsFrameFragment implements Callback {
410        PermissionAppsFragment mOuterFragment;
411
412        @Override
413        public void onCreate(Bundle savedInstanceState) {
414            mOuterFragment = (PermissionAppsFragment) getTargetFragment();
415            setLoading(true /* loading */, false /* animate */);
416            super.onCreate(savedInstanceState);
417            if (mOuterFragment.mExtraScreen != null) {
418                setPreferenceScreen();
419            } else {
420                mOuterFragment.setOnPermissionsLoadedListener(this);
421            }
422        }
423
424        @Override
425        public void onViewCreated(View view, Bundle savedInstanceState) {
426            super.onViewCreated(view, savedInstanceState);
427            String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME);
428            PermissionApps permissionApps = new PermissionApps(getActivity(), groupName, null);
429            bindUi(this, permissionApps);
430        }
431
432        @Override
433        public void onPermissionsLoaded(PermissionApps permissionApps) {
434            setPreferenceScreen();
435            mOuterFragment.setOnPermissionsLoadedListener(null);
436        }
437
438        private void setPreferenceScreen() {
439            setPreferenceScreen(mOuterFragment.mExtraScreen);
440            setLoading(false /* loading */, true /* animate */);
441        }
442    }
443}
444