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;
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.view.ViewGroup;
39import android.widget.ImageView;
40import android.widget.TextView;
41
42import com.android.packageinstaller.R;
43import com.android.packageinstaller.permission.model.AppPermissionGroup;
44import com.android.packageinstaller.permission.model.PermissionApps;
45import com.android.packageinstaller.permission.model.PermissionApps.Callback;
46import com.android.packageinstaller.permission.model.PermissionApps.PermissionApp;
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 PermissionsFrameFragment 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 MenuItem mShowSystemMenu;
82    private MenuItem mHideSystemMenu;
83
84    private Callback mOnPermissionsLoadedListener;
85
86    @Override
87    public void onCreate(Bundle savedInstanceState) {
88        super.onCreate(savedInstanceState);
89        setLoading(true /* loading */, false /* animate */);
90        setHasOptionsMenu(true);
91        final ActionBar ab = getActivity().getActionBar();
92        if (ab != null) {
93            ab.setDisplayHomeAsUpEnabled(true);
94        }
95        mLauncherPkgs = Utils.getLauncherPackages(getContext());
96
97        String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME);
98        mPermissionApps = new PermissionApps(getActivity(), groupName, this);
99        mPermissionApps.refresh(true);
100    }
101
102    @Override
103    public void onResume() {
104        super.onResume();
105        mPermissionApps.refresh(true);
106    }
107
108    @Override
109    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
110        mShowSystemMenu = menu.add(Menu.NONE, MENU_SHOW_SYSTEM, Menu.NONE,
111                R.string.menu_show_system);
112        mHideSystemMenu = menu.add(Menu.NONE, MENU_HIDE_SYSTEM, Menu.NONE,
113                R.string.menu_hide_system);
114        updateMenu();
115    }
116
117    @Override
118    public boolean onOptionsItemSelected(MenuItem item) {
119        switch (item.getItemId()) {
120            case android.R.id.home:
121                getActivity().finish();
122                return true;
123            case MENU_SHOW_SYSTEM:
124            case MENU_HIDE_SYSTEM:
125                mShowSystem = item.getItemId() == MENU_SHOW_SYSTEM;
126                if (mPermissionApps.getApps() != null) {
127                    onPermissionsLoaded(mPermissionApps);
128                }
129                updateMenu();
130                break;
131        }
132        return super.onOptionsItemSelected(item);
133    }
134
135    private void updateMenu() {
136        mShowSystemMenu.setVisible(!mShowSystem);
137        mHideSystemMenu.setVisible(mShowSystem);
138    }
139
140    @Override
141    protected void onSetEmptyText(TextView textView) {
142        textView.setText(R.string.no_apps);
143    }
144
145    @Override
146    public void onViewCreated(View view, Bundle savedInstanceState) {
147        super.onViewCreated(view, savedInstanceState);
148        bindUi(this, mPermissionApps);
149    }
150
151    private static void bindUi(Fragment fragment, PermissionApps permissionApps) {
152        final Drawable icon = permissionApps.getIcon();
153        final CharSequence label = permissionApps.getLabel();
154        final ActionBar ab = fragment.getActivity().getActionBar();
155        if (ab != null) {
156            ab.setTitle(fragment.getString(R.string.permission_title, label));
157        }
158
159        final ViewGroup rootView = (ViewGroup) fragment.getView();
160        final ImageView iconView = (ImageView) rootView.findViewById(R.id.lb_icon);
161        if (iconView != null) {
162            // Set the icon as the background instead of the image because ImageView
163            // doesn't properly scale vector drawables beyond their intrinsic size
164            iconView.setBackground(icon);
165        }
166        final TextView titleView = (TextView) rootView.findViewById(R.id.lb_title);
167        if (titleView != null) {
168            titleView.setText(label);
169        }
170        final TextView breadcrumbView = (TextView) rootView.findViewById(R.id.lb_breadcrumb);
171        if (breadcrumbView != null) {
172            breadcrumbView.setText(R.string.app_permissions);
173        }
174    }
175
176    private void setOnPermissionsLoadedListener(Callback callback) {
177        mOnPermissionsLoadedListener = callback;
178    }
179
180    @Override
181    public void onPermissionsLoaded(PermissionApps permissionApps) {
182        Context context = getPreferenceManager().getContext();
183
184        if (context == null) {
185            return;
186        }
187
188        boolean isTelevision = Utils.isTelevision(context);
189        PreferenceScreen screen = getPreferenceScreen();
190
191        ArraySet<String> preferencesToRemove = new ArraySet<>();
192        for (int i = 0, n = screen.getPreferenceCount(); i < n; i++) {
193            preferencesToRemove.add(screen.getPreference(i).getKey());
194        }
195        if (mExtraScreen != null) {
196            for (int i = 0, n = mExtraScreen.getPreferenceCount(); i < n; i++) {
197                preferencesToRemove.add(mExtraScreen.getPreference(i).getKey());
198            }
199        }
200
201        for (PermissionApp app : permissionApps.getApps()) {
202            if (!Utils.shouldShowPermission(app)) {
203                continue;
204            }
205
206            String key = app.getKey();
207            preferencesToRemove.remove(key);
208            Preference existingPref = screen.findPreference(key);
209            if (existingPref == null && mExtraScreen != null) {
210                existingPref = mExtraScreen.findPreference(key);
211            }
212
213            boolean isSystemApp = Utils.isSystem(app, mLauncherPkgs);
214            if (isSystemApp && !isTelevision && !mShowSystem) {
215                if (existingPref != null) {
216                    screen.removePreference(existingPref);
217                }
218                continue;
219            }
220
221            if (existingPref != null) {
222                // If existing preference - only update its state.
223                if (app.isPolicyFixed()) {
224                    existingPref.setSummary(getString(
225                            R.string.permission_summary_enforced_by_policy));
226                }
227                existingPref.setPersistent(false);
228                existingPref.setEnabled(!app.isPolicyFixed());
229                if (existingPref instanceof SwitchPreference) {
230                    ((SwitchPreference) existingPref)
231                            .setChecked(app.areRuntimePermissionsGranted());
232                }
233                continue;
234            }
235
236            SwitchPreference pref = new SwitchPreference(context);
237            pref.setOnPreferenceChangeListener(this);
238            pref.setKey(app.getKey());
239            pref.setIcon(app.getIcon());
240            pref.setTitle(app.getLabel());
241            if (app.isPolicyFixed()) {
242                pref.setSummary(getString(R.string.permission_summary_enforced_by_policy));
243            }
244            pref.setPersistent(false);
245            pref.setEnabled(!app.isPolicyFixed());
246            pref.setChecked(app.areRuntimePermissionsGranted());
247
248            if (isSystemApp && isTelevision) {
249                if (mExtraScreen == null) {
250                    mExtraScreen = getPreferenceManager().createPreferenceScreen(context);
251                }
252                mExtraScreen.addPreference(pref);
253            } else {
254                screen.addPreference(pref);
255            }
256        }
257
258        if (mExtraScreen != null) {
259            preferencesToRemove.remove(KEY_SHOW_SYSTEM_PREFS);
260            Preference pref = screen.findPreference(KEY_SHOW_SYSTEM_PREFS);
261
262            if (pref == null) {
263                pref = new Preference(context);
264                pref.setKey(KEY_SHOW_SYSTEM_PREFS);
265                pref.setIcon(Utils.applyTint(context, R.drawable.ic_toc,
266                        android.R.attr.colorControlNormal));
267                pref.setTitle(R.string.preference_show_system_apps);
268                pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
269                    @Override
270                    public boolean onPreferenceClick(Preference preference) {
271                        SystemAppsFragment frag = new SystemAppsFragment();
272                        setPermissionName(frag, getArguments().getString(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        OverlayTouchActivity activity = (OverlayTouchActivity) getActivity();
323        if (activity.isObscuredTouch()) {
324            activity.showOverlayDialog();
325            return false;
326        }
327
328        addToggledGroup(app.getPackageName(), app.getPermissionGroup());
329
330        if (LocationUtils.isLocationGroupAndProvider(mPermissionApps.getGroupName(),
331                app.getPackageName())) {
332            LocationUtils.showLocationDialog(getContext(), app.getLabel());
333            return false;
334        }
335        if (newValue == Boolean.TRUE) {
336            app.grantRuntimePermissions();
337        } else {
338            final boolean grantedByDefault = app.hasGrantedByDefaultPermissions();
339            if (grantedByDefault || (!app.hasRuntimePermissions() && !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,
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 PermissionsFrameFragment 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 onPermissionsLoaded(PermissionApps permissionApps) {
424            setPreferenceScreen();
425            mOuterFragment.setOnPermissionsLoadedListener(null);
426        }
427
428        private void setPreferenceScreen() {
429            setPreferenceScreen(mOuterFragment.mExtraScreen);
430            setLoading(false /* loading */, true /* animate */);
431        }
432    }
433}
434