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