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 */
16 
17 package com.android.packageinstaller.permission.ui.television;
18 
19 import android.app.ActionBar;
20 import android.app.Activity;
21 import android.app.AlertDialog;
22 import android.app.Fragment;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.content.DialogInterface.OnClickListener;
26 import android.content.Intent;
27 import android.content.pm.ApplicationInfo;
28 import android.content.pm.PackageInfo;
29 import android.content.pm.PackageManager;
30 import android.graphics.drawable.Drawable;
31 import android.net.Uri;
32 import android.os.Bundle;
33 import android.provider.Settings;
34 import android.support.v14.preference.SwitchPreference;
35 import android.support.v7.preference.Preference;
36 import android.support.v7.preference.Preference.OnPreferenceChangeListener;
37 import android.support.v7.preference.Preference.OnPreferenceClickListener;
38 import android.support.v7.preference.PreferenceScreen;
39 import android.support.v7.preference.PreferenceViewHolder;
40 import android.util.Log;
41 import android.view.Menu;
42 import android.view.MenuInflater;
43 import android.view.MenuItem;
44 import android.view.View;
45 import android.widget.Toast;
46 
47 import com.android.packageinstaller.R;
48 
49 import com.android.packageinstaller.permission.model.AppPermissionGroup;
50 import com.android.packageinstaller.permission.model.AppPermissions;
51 import com.android.packageinstaller.permission.ui.ReviewPermissionsActivity;
52 import com.android.packageinstaller.permission.utils.LocationUtils;
53 import com.android.packageinstaller.permission.utils.SafetyNetLogger;
54 import com.android.packageinstaller.permission.utils.Utils;
55 
56 import java.util.ArrayList;
57 import java.util.List;
58 
59 public final class AppPermissionsFragment extends SettingsWithHeader
60         implements OnPreferenceChangeListener {
61 
62     private static final String LOG_TAG = "ManagePermsFragment";
63 
64     static final String EXTRA_HIDE_INFO_BUTTON = "hideInfoButton";
65 
66     private static final int MENU_ALL_PERMS = 0;
67 
68     private List<AppPermissionGroup> mToggledGroups;
69     private AppPermissions mAppPermissions;
70     private PreferenceScreen mExtraScreen;
71 
72     private boolean mHasConfirmedRevoke;
73 
newInstance(String packageName)74     public static AppPermissionsFragment newInstance(String packageName) {
75         return setPackageName(new AppPermissionsFragment(), packageName);
76     }
77 
setPackageName(T fragment, String packageName)78     private static <T extends Fragment> T setPackageName(T fragment, String packageName) {
79         Bundle arguments = new Bundle();
80         arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
81         fragment.setArguments(arguments);
82         return fragment;
83     }
84 
85     @Override
onCreate(Bundle savedInstanceState)86     public void onCreate(Bundle savedInstanceState) {
87         super.onCreate(savedInstanceState);
88         setLoading(true /* loading */, false /* animate */);
89         setHasOptionsMenu(true);
90         final ActionBar ab = getActivity().getActionBar();
91         if (ab != null) {
92             ab.setDisplayHomeAsUpEnabled(true);
93         }
94 
95         String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
96         Activity activity = getActivity();
97         PackageInfo packageInfo = getPackageInfo(activity, packageName);
98         if (packageInfo == null) {
99             Toast.makeText(activity, R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show();
100             activity.finish();
101             return;
102         }
103 
104 
105         mAppPermissions = new AppPermissions(activity, packageInfo, null, true, new Runnable() {
106             @Override
107             public void run() {
108                 getActivity().finish();
109             }
110         });
111 
112         if (mAppPermissions.isReviewRequired()) {
113             Intent intent = new Intent(getActivity(), ReviewPermissionsActivity.class);
114             intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
115             startActivity(intent);
116             getActivity().finish();
117             return;
118         }
119 
120         loadPreferences();
121     }
122 
123     @Override
onResume()124     public void onResume() {
125         super.onResume();
126         mAppPermissions.refresh();
127         loadPreferences();
128         setPreferencesCheckedState();
129     }
130 
131     @Override
onOptionsItemSelected(MenuItem item)132     public boolean onOptionsItemSelected(MenuItem item) {
133         switch (item.getItemId()) {
134             case android.R.id.home: {
135                 getActivity().finish();
136                 return true;
137             }
138 
139             case MENU_ALL_PERMS: {
140                 Fragment frag = AllAppPermissionsFragment.newInstance(
141                         getArguments().getString(Intent.EXTRA_PACKAGE_NAME));
142                 getFragmentManager().beginTransaction()
143                         .replace(android.R.id.content, frag)
144                         .addToBackStack("AllPerms")
145                         .commit();
146                 return true;
147             }
148         }
149         return super.onOptionsItemSelected(item);
150     }
151 
152     @Override
onViewCreated(View view, Bundle savedInstanceState)153     public void onViewCreated(View view, Bundle savedInstanceState) {
154         super.onViewCreated(view, savedInstanceState);
155         if (mAppPermissions != null) {
156             bindUi(this, mAppPermissions.getPackageInfo());
157         }
158     }
159 
160     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)161     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
162         super.onCreateOptionsMenu(menu, inflater);
163         menu.add(Menu.NONE, MENU_ALL_PERMS, Menu.NONE, R.string.all_permissions);
164     }
165 
bindUi(SettingsWithHeader fragment, PackageInfo packageInfo)166     private static void bindUi(SettingsWithHeader fragment, PackageInfo packageInfo) {
167         Activity activity = fragment.getActivity();
168         PackageManager pm = activity.getPackageManager();
169         ApplicationInfo appInfo = packageInfo.applicationInfo;
170         Intent infoIntent = null;
171         if (!activity.getIntent().getBooleanExtra(EXTRA_HIDE_INFO_BUTTON, false)) {
172             infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
173                     .setData(Uri.fromParts("package", packageInfo.packageName, null));
174         }
175 
176         Drawable icon = appInfo.loadIcon(pm);
177         CharSequence label = appInfo.loadLabel(pm);
178         fragment.setHeader(icon, label, infoIntent, fragment.getString(
179                 R.string.app_permissions_decor_title));
180     }
181 
loadPreferences()182     private void loadPreferences() {
183         Context context = getPreferenceManager().getContext();
184         if (context == null) {
185             return;
186         }
187 
188         PreferenceScreen screen = getPreferenceScreen();
189         screen.removeAll();
190         screen.addPreference(createHeaderLineTwoPreference(context));
191 
192         if (mExtraScreen != null) {
193             mExtraScreen.removeAll();
194             mExtraScreen = null;
195         }
196 
197         final Preference extraPerms = new Preference(context);
198         extraPerms.setIcon(R.drawable.ic_toc);
199         extraPerms.setTitle(R.string.additional_permissions);
200 
201         for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
202             if (!Utils.shouldShowPermission(group, mAppPermissions.getPackageInfo().packageName)) {
203                 continue;
204             }
205 
206             boolean isPlatform = group.getDeclaringPackage().equals(Utils.OS_PKG);
207 
208             SwitchPreference preference = new SwitchPreference(context);
209             preference.setOnPreferenceChangeListener(this);
210             preference.setKey(group.getName());
211             Drawable icon = Utils.loadDrawable(context.getPackageManager(),
212                     group.getIconPkg(), group.getIconResId());
213             preference.setIcon(Utils.applyTint(getContext(), icon,
214                     android.R.attr.colorControlNormal));
215             preference.setTitle(group.getLabel());
216             if (group.isPolicyFixed()) {
217                 preference.setSummary(getString(R.string.permission_summary_enforced_by_policy));
218             }
219             preference.setPersistent(false);
220             preference.setEnabled(!group.isPolicyFixed());
221             preference.setChecked(group.areRuntimePermissionsGranted());
222 
223             if (isPlatform) {
224                 screen.addPreference(preference);
225             } else {
226                 if (mExtraScreen == null) {
227                     mExtraScreen = getPreferenceManager().createPreferenceScreen(context);
228                     mExtraScreen.addPreference(createHeaderLineTwoPreference(context));
229                 }
230                 mExtraScreen.addPreference(preference);
231             }
232         }
233 
234         if (mExtraScreen != null) {
235             extraPerms.setOnPreferenceClickListener(new OnPreferenceClickListener() {
236                 @Override
237                 public boolean onPreferenceClick(Preference preference) {
238                     AdditionalPermissionsFragment frag = new AdditionalPermissionsFragment();
239                     setPackageName(frag, getArguments().getString(Intent.EXTRA_PACKAGE_NAME));
240                     frag.setTargetFragment(AppPermissionsFragment.this, 0);
241                     getFragmentManager().beginTransaction()
242                             .replace(android.R.id.content, frag)
243                             .addToBackStack(null)
244                             .commit();
245                     return true;
246                 }
247             });
248             int count = mExtraScreen.getPreferenceCount();
249             extraPerms.setSummary(getResources().getQuantityString(
250                     R.plurals.additional_permissions_more, count, count));
251             screen.addPreference(extraPerms);
252         }
253 
254         setLoading(false /* loading */, true /* animate */);
255     }
256 
257     /**
258      * Creates a heading below decor_title and above the rest of the preferences. This heading
259      * displays the app name and banner icon. It's used in both system and additional permissions
260      * fragments for each app. The styling used is the same as a leanback preference with a
261      * customized background color
262      * @param context The context the preferences created on
263      * @return The preference header to be inserted as the first preference in the list.
264      */
createHeaderLineTwoPreference(Context context)265     private Preference createHeaderLineTwoPreference(Context context) {
266         Preference headerLineTwo = new Preference(context) {
267             @Override
268             public void onBindViewHolder(PreferenceViewHolder holder) {
269                 super.onBindViewHolder(holder);
270                 holder.itemView.setBackgroundColor(
271                         getResources().getColor(R.color.lb_header_banner_color));
272             }
273         };
274         headerLineTwo.setKey(HEADER_PREFERENCE_KEY);
275         headerLineTwo.setSelectable(false);
276         headerLineTwo.setTitle(mLabel);
277         headerLineTwo.setIcon(mIcon);
278         return headerLineTwo;
279     }
280 
281     @Override
onPreferenceChange(final Preference preference, Object newValue)282     public boolean onPreferenceChange(final Preference preference, Object newValue) {
283         String groupName = preference.getKey();
284         final AppPermissionGroup group = mAppPermissions.getPermissionGroup(groupName);
285 
286         if (group == null) {
287             return false;
288         }
289 
290         addToggledGroup(group);
291 
292         if (LocationUtils.isLocationGroupAndProvider(group.getName(), group.getApp().packageName)) {
293             LocationUtils.showLocationDialog(getContext(), mAppPermissions.getAppLabel());
294             return false;
295         }
296         if (newValue == Boolean.TRUE) {
297             group.grantRuntimePermissions(false);
298         } else {
299             final boolean grantedByDefault = group.hasGrantedByDefaultPermission();
300             if (grantedByDefault || (!group.doesSupportRuntimePermissions()
301                     && !mHasConfirmedRevoke)) {
302                 new AlertDialog.Builder(getContext())
303                         .setMessage(grantedByDefault ? R.string.system_warning
304                                 : R.string.old_sdk_deny_warning)
305                         .setNegativeButton(R.string.cancel, null)
306                         .setPositiveButton(R.string.grant_dialog_button_deny_anyway,
307                                 new OnClickListener() {
308                             @Override
309                             public void onClick(DialogInterface dialog, int which) {
310                                 ((SwitchPreference) preference).setChecked(false);
311                                 group.revokeRuntimePermissions(false);
312                                 if (!grantedByDefault) {
313                                     mHasConfirmedRevoke = true;
314                                 }
315                             }
316                         })
317                         .show();
318                 return false;
319             } else {
320                 group.revokeRuntimePermissions(false);
321             }
322         }
323 
324         return true;
325     }
326 
327     @Override
onPause()328     public void onPause() {
329         super.onPause();
330         logToggledGroups();
331     }
332 
addToggledGroup(AppPermissionGroup group)333     private void addToggledGroup(AppPermissionGroup group) {
334         if (mToggledGroups == null) {
335             mToggledGroups = new ArrayList<>();
336         }
337         // Double toggle is back to initial state.
338         if (mToggledGroups.contains(group)) {
339             mToggledGroups.remove(group);
340         } else {
341             mToggledGroups.add(group);
342         }
343     }
344 
logToggledGroups()345     private void logToggledGroups() {
346         if (mToggledGroups != null) {
347             String packageName = mAppPermissions.getPackageInfo().packageName;
348             SafetyNetLogger.logPermissionsToggled(packageName, mToggledGroups);
349             mToggledGroups = null;
350         }
351     }
352 
setPreferencesCheckedState()353     private void setPreferencesCheckedState() {
354         setPreferencesCheckedState(getPreferenceScreen());
355         if (mExtraScreen != null) {
356             setPreferencesCheckedState(mExtraScreen);
357         }
358     }
359 
setPreferencesCheckedState(PreferenceScreen screen)360     private void setPreferencesCheckedState(PreferenceScreen screen) {
361         int preferenceCount = screen.getPreferenceCount();
362         for (int i = 0; i < preferenceCount; i++) {
363             Preference preference = screen.getPreference(i);
364             if (preference instanceof SwitchPreference) {
365                 SwitchPreference switchPref = (SwitchPreference) preference;
366                 AppPermissionGroup group = mAppPermissions.getPermissionGroup(switchPref.getKey());
367                 if (group != null) {
368                     switchPref.setChecked(group.areRuntimePermissionsGranted());
369                 }
370             }
371         }
372     }
373 
getPackageInfo(Activity activity, String packageName)374     private static PackageInfo getPackageInfo(Activity activity, String packageName) {
375         try {
376             return activity.getPackageManager().getPackageInfo(
377                     packageName, PackageManager.GET_PERMISSIONS);
378         } catch (PackageManager.NameNotFoundException e) {
379             Log.i(LOG_TAG, "No package:" + activity.getCallingPackage(), e);
380             return null;
381         }
382     }
383 
384     public static class AdditionalPermissionsFragment extends SettingsWithHeader {
385         AppPermissionsFragment mOuterFragment;
386 
387         @Override
onCreate(Bundle savedInstanceState)388         public void onCreate(Bundle savedInstanceState) {
389             mOuterFragment = (AppPermissionsFragment) getTargetFragment();
390             super.onCreate(savedInstanceState);
391             setHasOptionsMenu(true);
392         }
393 
394         @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)395         public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
396             setPreferenceScreen(mOuterFragment.mExtraScreen);
397         }
398 
399         @Override
onViewCreated(View view, Bundle savedInstanceState)400         public void onViewCreated(View view, Bundle savedInstanceState) {
401             super.onViewCreated(view, savedInstanceState);
402             String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
403             bindUi(this, getPackageInfo(getActivity(), packageName));
404         }
405 
bindUi(SettingsWithHeader fragment, PackageInfo packageInfo)406         private static void bindUi(SettingsWithHeader fragment, PackageInfo packageInfo) {
407             Activity activity = fragment.getActivity();
408             PackageManager pm = activity.getPackageManager();
409             ApplicationInfo appInfo = packageInfo.applicationInfo;
410             Intent infoIntent = null;
411             if (!activity.getIntent().getBooleanExtra(EXTRA_HIDE_INFO_BUTTON, false)) {
412                 infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
413                         .setData(Uri.fromParts("package", packageInfo.packageName, null));
414             }
415 
416             Drawable icon = appInfo.loadIcon(pm);
417             CharSequence label = appInfo.loadLabel(pm);
418             fragment.setHeader(icon, label, infoIntent, fragment.getString(
419                     R.string.additional_permissions_decor_title));
420         }
421 
422         @Override
onOptionsItemSelected(MenuItem item)423         public boolean onOptionsItemSelected(MenuItem item) {
424             switch (item.getItemId()) {
425             case android.R.id.home:
426                 getFragmentManager().popBackStack();
427                 return true;
428             }
429             return super.onOptionsItemSelected(item);
430         }
431     }
432 }
433