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.television;
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.support.v14.preference.SwitchPreference;
28import android.support.v4.util.ArrayMap;
29import android.support.v7.preference.Preference;
30import android.support.v7.preference.Preference.OnPreferenceChangeListener;
31import android.support.v7.preference.Preference.OnPreferenceClickListener;
32import android.support.v7.preference.PreferenceScreen;
33import android.util.ArraySet;
34import android.view.Menu;
35import android.view.MenuInflater;
36import android.view.MenuItem;
37import android.view.View;
38import android.widget.TextView;
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.ui.ReviewPermissionsActivity;
47import com.android.packageinstaller.permission.utils.LocationUtils;
48import com.android.packageinstaller.permission.utils.SafetyNetLogger;
49import com.android.packageinstaller.permission.utils.Utils;
50
51import java.util.ArrayList;
52import java.util.List;
53
54public final class PermissionAppsFragment extends SettingsWithHeader implements Callback,
55        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
120    @Override
121    public boolean onOptionsItemSelected(MenuItem item) {
122        switch (item.getItemId()) {
123            case android.R.id.home:
124                getActivity().finish();
125                return true;
126            case MENU_SHOW_SYSTEM:
127            case MENU_HIDE_SYSTEM:
128                mShowSystem = item.getItemId() == MENU_SHOW_SYSTEM;
129                if (mPermissionApps.getApps() != null) {
130                    onPermissionsLoaded(mPermissionApps);
131                }
132                updateMenu();
133                break;
134        }
135        return super.onOptionsItemSelected(item);
136    }
137
138    private void updateMenu() {
139        mShowSystemMenu.setVisible(!mShowSystem);
140        mHideSystemMenu.setVisible(mShowSystem);
141    }
142
143    @Override
144    protected void onSetEmptyText(TextView textView) {
145        textView.setText(R.string.no_apps);
146    }
147
148    @Override
149    public void onViewCreated(View view, Bundle savedInstanceState) {
150        super.onViewCreated(view, savedInstanceState);
151        bindUi(this, mPermissionApps);
152    }
153
154    private static void bindUi(SettingsWithHeader fragment, PermissionApps permissionApps) {
155        final Drawable icon = permissionApps.getIcon();
156        final CharSequence label = permissionApps.getLabel();
157
158        fragment.setHeader(null, null, null,
159                fragment.getString(R.string.permission_apps_decor_title, label));
160    }
161
162    private void setOnPermissionsLoadedListener(Callback callback) {
163        mOnPermissionsLoadedListener = callback;
164    }
165
166    @Override
167    public void onPermissionsLoaded(PermissionApps permissionApps) {
168        Context context = getPreferenceManager().getContext();
169
170        if (context == null) {
171            return;
172        }
173
174        boolean isTelevision = DeviceUtils.isTelevision(context);
175        PreferenceScreen screen = getPreferenceScreen();
176
177        ArraySet<String> preferencesToRemove = new ArraySet<>();
178        for (int i = 0, n = screen.getPreferenceCount(); i < n; i++) {
179            preferencesToRemove.add(screen.getPreference(i).getKey());
180        }
181        if (mExtraScreen != null) {
182            for (int i = 0, n = mExtraScreen.getPreferenceCount(); i < n; i++) {
183                preferencesToRemove.add(mExtraScreen.getPreference(i).getKey());
184            }
185        }
186
187        mHasSystemApps = false;
188        boolean menuOptionsInvalided = false;
189
190        for (PermissionApp app : permissionApps.getApps()) {
191            if (!Utils.shouldShowPermission(app)) {
192                continue;
193            }
194
195            String key = app.getKey();
196            preferencesToRemove.remove(key);
197            Preference existingPref = screen.findPreference(key);
198            if (existingPref == null && mExtraScreen != null) {
199                existingPref = mExtraScreen.findPreference(key);
200            }
201
202            boolean isSystemApp = Utils.isSystem(app, mLauncherPkgs);
203
204            if (isSystemApp && !menuOptionsInvalided) {
205                mHasSystemApps = true;
206                getActivity().invalidateOptionsMenu();
207                menuOptionsInvalided = true;
208            }
209
210            if (isSystemApp && !isTelevision && !mShowSystem) {
211                if (existingPref != null) {
212                    screen.removePreference(existingPref);
213                }
214                continue;
215            }
216
217            if (existingPref != null) {
218                // If existing preference - only update its state.
219                if (app.isPolicyFixed()) {
220                    existingPref.setSummary(getString(
221                            R.string.permission_summary_enforced_by_policy));
222                }
223                existingPref.setPersistent(false);
224                existingPref.setEnabled(!app.isPolicyFixed());
225                if (existingPref instanceof SwitchPreference) {
226                    ((SwitchPreference) existingPref)
227                            .setChecked(app.areRuntimePermissionsGranted());
228                }
229                continue;
230            }
231
232            SwitchPreference pref = new SwitchPreference(context);
233            pref.setOnPreferenceChangeListener(this);
234            pref.setKey(app.getKey());
235            pref.setIcon(app.getIcon());
236            pref.setTitle(app.getLabel());
237            if (app.isPolicyFixed()) {
238                pref.setSummary(getString(R.string.permission_summary_enforced_by_policy));
239            }
240            pref.setPersistent(false);
241            pref.setEnabled(!app.isPolicyFixed());
242            pref.setChecked(app.areRuntimePermissionsGranted());
243
244            if (isSystemApp && isTelevision) {
245                if (mExtraScreen == null) {
246                    mExtraScreen = getPreferenceManager().createPreferenceScreen(context);
247                }
248                mExtraScreen.addPreference(pref);
249            } else {
250                screen.addPreference(pref);
251            }
252        }
253
254        if (mExtraScreen != null) {
255            preferencesToRemove.remove(KEY_SHOW_SYSTEM_PREFS);
256            Preference pref = screen.findPreference(KEY_SHOW_SYSTEM_PREFS);
257
258            if (pref == null) {
259                pref = new Preference(context);
260                pref.setKey(KEY_SHOW_SYSTEM_PREFS);
261                pref.setIcon(Utils.applyTint(context, R.drawable.ic_toc,
262                        android.R.attr.colorControlNormal));
263                pref.setTitle(R.string.preference_show_system_apps);
264                pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
265                    @Override
266                    public boolean onPreferenceClick(Preference preference) {
267                        SystemAppsFragment frag = new SystemAppsFragment();
268                        setPermissionName(frag, getArguments().getString(
269                                Intent.EXTRA_PERMISSION_NAME));
270                        frag.setTargetFragment(PermissionAppsFragment.this, 0);
271                        getFragmentManager().beginTransaction()
272                            .replace(android.R.id.content, frag)
273                            .addToBackStack("SystemApps")
274                            .commit();
275                        return true;
276                    }
277                });
278                screen.addPreference(pref);
279            }
280
281            int grantedCount = 0;
282            for (int i = 0, n = mExtraScreen.getPreferenceCount(); i < n; i++) {
283                if (((SwitchPreference) mExtraScreen.getPreference(i)).isChecked()) {
284                    grantedCount++;
285                }
286            }
287            pref.setSummary(getString(R.string.app_permissions_group_summary,
288                    grantedCount, mExtraScreen.getPreferenceCount()));
289        }
290
291        for (String key : preferencesToRemove) {
292            Preference pref = screen.findPreference(key);
293            if (pref != null) {
294                screen.removePreference(pref);
295            } else if (mExtraScreen != null) {
296                pref = mExtraScreen.findPreference(key);
297                if (pref != null) {
298                    mExtraScreen.removePreference(pref);
299                }
300            }
301        }
302
303        setLoading(false /* loading */, true /* animate */);
304
305        if (mOnPermissionsLoadedListener != null) {
306            mOnPermissionsLoadedListener.onPermissionsLoaded(permissionApps);
307        }
308    }
309
310    @Override
311    public boolean onPreferenceChange(final Preference preference, Object newValue) {
312        String pkg = preference.getKey();
313        final PermissionApp app = mPermissionApps.getApp(pkg);
314
315        if (app == null) {
316            return false;
317        }
318
319        if (LocationUtils.isLocationGroupAndProvider(mPermissionApps.getGroupName(),
320                app.getPackageName())) {
321            LocationUtils.showLocationDialog(getContext(), app.getLabel());
322            return false;
323        }
324
325        addToggledGroup(app.getPackageName(), app.getPermissionGroup());
326
327        if (app.isReviewRequired()) {
328            Intent intent = new Intent(getActivity(), ReviewPermissionsActivity.class);
329            intent.putExtra(Intent.EXTRA_PACKAGE_NAME, app.getPackageName());
330            startActivity(intent);
331            return false;
332        }
333
334        if (newValue == Boolean.TRUE) {
335            app.grantRuntimePermissions();
336        } else {
337            final boolean grantedByDefault = app.hasGrantedByDefaultPermissions();
338            if (grantedByDefault || (!app.doesSupportRuntimePermissions()
339                    && !mHasConfirmedRevoke)) {
340                new AlertDialog.Builder(getContext())
341                        .setMessage(grantedByDefault ? R.string.system_warning
342                                : R.string.old_sdk_deny_warning)
343                        .setNegativeButton(R.string.cancel, null)
344                        .setPositiveButton(R.string.grant_dialog_button_deny_anyway,
345                                new OnClickListener() {
346                            @Override
347                            public void onClick(DialogInterface dialog, int which) {
348                                ((SwitchPreference) preference).setChecked(false);
349                                app.revokeRuntimePermissions();
350                                if (!grantedByDefault) {
351                                    mHasConfirmedRevoke = true;
352                                }
353                            }
354                        })
355                        .show();
356                return false;
357            } else {
358                app.revokeRuntimePermissions();
359            }
360        }
361        return true;
362    }
363
364    @Override
365    public void onPause() {
366        super.onPause();
367        logToggledGroups();
368    }
369
370    private void addToggledGroup(String packageName, AppPermissionGroup group) {
371        if (mToggledGroups == null) {
372            mToggledGroups = new ArrayMap<>();
373        }
374        // Double toggle is back to initial state.
375        if (mToggledGroups.containsKey(packageName)) {
376            mToggledGroups.remove(packageName);
377        } else {
378            mToggledGroups.put(packageName, group);
379        }
380    }
381
382    private void logToggledGroups() {
383        if (mToggledGroups != null) {
384            final int groupCount = mToggledGroups.size();
385            for (int i = 0; i < groupCount; i++) {
386                String packageName = mToggledGroups.keyAt(i);
387                List<AppPermissionGroup> groups = new ArrayList<>();
388                groups.add(mToggledGroups.valueAt(i));
389                SafetyNetLogger.logPermissionsToggled(packageName, groups);
390            }
391            mToggledGroups = null;
392        }
393    }
394
395    public static class SystemAppsFragment extends SettingsWithHeader implements Callback {
396        PermissionAppsFragment mOuterFragment;
397
398        @Override
399        public void onCreate(Bundle savedInstanceState) {
400            mOuterFragment = (PermissionAppsFragment) getTargetFragment();
401            setLoading(true /* loading */, false /* animate */);
402            super.onCreate(savedInstanceState);
403        }
404
405        @Override
406        public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
407            if (mOuterFragment.mExtraScreen != null) {
408                setPreferenceScreen();
409            } else {
410                mOuterFragment.setOnPermissionsLoadedListener(this);
411            }
412        }
413
414        @Override
415        public void onViewCreated(View view, Bundle savedInstanceState) {
416            super.onViewCreated(view, savedInstanceState);
417            String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME);
418            PermissionApps permissionApps = new PermissionApps(getActivity(), groupName, null);
419            bindUi(this, permissionApps);
420        }
421
422        @Override
423        public void onResume() {
424            super.onResume();
425            mOuterFragment.mPermissionApps.refresh(true);
426        }
427
428        @Override
429        public void onDestroy() {
430            super.onDestroy();
431            mOuterFragment.setOnPermissionsLoadedListener(null);
432        }
433
434
435        private static void bindUi(SettingsWithHeader fragment, PermissionApps permissionApps) {
436            final CharSequence label = permissionApps.getLabel();
437            fragment.setHeader(null, null, null,
438                    fragment.getString(R.string.system_apps_decor_title, label));
439        }
440
441        @Override
442        public void onPermissionsLoaded(PermissionApps permissionApps) {
443            setPreferenceScreen();
444        }
445
446        private void setPreferenceScreen() {
447            setPreferenceScreen(mOuterFragment.mExtraScreen);
448            setLoading(false /* loading */, true /* animate */);
449        }
450    }
451}
452