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