From ef861375eebd9ac6cce7c0bb163380ab1c951063 Mon Sep 17 00:00:00 2001 From: Svetoslav Date: Fri, 20 Nov 2015 14:00:11 -0800 Subject: resolve merge conflicts of c10abb25f3 to cw-e-dev. Change-Id: I3fe38a9ac62466b38efec834dceb712d2782c518 --- res/drawable/header_background.xml | 21 - res/drawable/ic_dialog_alert_material.xml | 25 ++ res/drawable/ic_more_items.xml | 29 ++ res/layout/app_details.xml | 8 +- res/layout/grant_permissions.xml | 19 +- res/layout/header.xml | 15 +- res/layout/install_confirm.xml | 3 +- res/layout/permission_description.xml | 5 +- res/layout/permissions_frame.xml | 4 +- res/layout/preference_permissions.xml | 2 +- res/layout/preference_permissions_switch.xml | 2 +- res/layout/uninstall_confirm.xml | 3 +- res/values-television/colors.xml | 7 + res/values-television/styles.xml | 37 ++ res/values/colors.xml | 31 -- res/values/styles.xml | 31 +- res/values/themes.xml | 10 +- .../permission/model/PermissionApps.java | 3 +- .../permission/ui/AllAppPermissionsFragment.java | 217 ---------- .../permission/ui/AppPermissionsFragment.java | 403 ------------------ .../permission/ui/ButtonBarLayout.java | 117 ++++++ .../permission/ui/GrantPermissionsActivity.java | 12 +- .../ui/GrantPermissionsDefaultViewHandler.java | 462 --------------------- .../ui/GrantPermissionsTvViewHandler.java | 130 ------ .../permission/ui/GrantPermissionsViewHandler.java | 2 +- .../permission/ui/ManagePermissionsActivity.java | 24 +- .../permission/ui/ManagePermissionsFragment.java | 267 ------------ .../permission/ui/OverlayWarningDialog.java | 1 + .../permission/ui/PermissionAppsFragment.java | 434 ------------------- .../permission/ui/PreferenceImageView.java | 69 +++ .../ui/handheld/AllAppPermissionsFragment.java | 214 ++++++++++ .../ui/handheld/AppPermissionsFragment.java | 404 ++++++++++++++++++ .../handheld/GrantPermissionsViewHandlerImpl.java | 462 +++++++++++++++++++++ .../ui/handheld/ManagePermissionsFragment.java | 268 ++++++++++++ .../ui/handheld/PermissionAppsFragment.java | 427 +++++++++++++++++++ .../ui/handheld/PermissionsFrameFragment.java | 121 ++++++ .../permission/ui/handheld/SettingsWithHeader.java | 84 ++++ .../ui/television/AllAppPermissionsFragment.java | 217 ++++++++++ .../ui/television/AppPermissionsFragment.java | 404 ++++++++++++++++++ .../GrantPermissionsViewHandlerImpl.java | 131 ++++++ .../ui/television/ManagePermissionsFragment.java | 267 ++++++++++++ .../ui/television/PermissionAppsFragment.java | 435 +++++++++++++++++++ .../ui/television/PermissionsFrameFragment.java | 203 +++++++++ .../ui/television/SettingsWithHeader.java | 85 ++++ .../permission/utils/LocationUtils.java | 17 +- 45 files changed, 4081 insertions(+), 2051 deletions(-) delete mode 100644 res/drawable/header_background.xml create mode 100644 res/drawable/ic_dialog_alert_material.xml create mode 100644 res/drawable/ic_more_items.xml create mode 100644 res/values-television/styles.xml delete mode 100755 res/values/colors.xml delete mode 100644 src/com/android/packageinstaller/permission/ui/AllAppPermissionsFragment.java delete mode 100644 src/com/android/packageinstaller/permission/ui/AppPermissionsFragment.java create mode 100644 src/com/android/packageinstaller/permission/ui/ButtonBarLayout.java delete mode 100644 src/com/android/packageinstaller/permission/ui/GrantPermissionsDefaultViewHandler.java delete mode 100644 src/com/android/packageinstaller/permission/ui/GrantPermissionsTvViewHandler.java delete mode 100644 src/com/android/packageinstaller/permission/ui/ManagePermissionsFragment.java delete mode 100644 src/com/android/packageinstaller/permission/ui/PermissionAppsFragment.java create mode 100644 src/com/android/packageinstaller/permission/ui/PreferenceImageView.java create mode 100644 src/com/android/packageinstaller/permission/ui/handheld/AllAppPermissionsFragment.java create mode 100644 src/com/android/packageinstaller/permission/ui/handheld/AppPermissionsFragment.java create mode 100644 src/com/android/packageinstaller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.java create mode 100644 src/com/android/packageinstaller/permission/ui/handheld/ManagePermissionsFragment.java create mode 100644 src/com/android/packageinstaller/permission/ui/handheld/PermissionAppsFragment.java create mode 100644 src/com/android/packageinstaller/permission/ui/handheld/PermissionsFrameFragment.java create mode 100644 src/com/android/packageinstaller/permission/ui/handheld/SettingsWithHeader.java create mode 100644 src/com/android/packageinstaller/permission/ui/television/AllAppPermissionsFragment.java create mode 100644 src/com/android/packageinstaller/permission/ui/television/AppPermissionsFragment.java create mode 100644 src/com/android/packageinstaller/permission/ui/television/GrantPermissionsViewHandlerImpl.java create mode 100644 src/com/android/packageinstaller/permission/ui/television/ManagePermissionsFragment.java create mode 100644 src/com/android/packageinstaller/permission/ui/television/PermissionAppsFragment.java create mode 100644 src/com/android/packageinstaller/permission/ui/television/PermissionsFrameFragment.java create mode 100644 src/com/android/packageinstaller/permission/ui/television/SettingsWithHeader.java diff --git a/res/drawable/header_background.xml b/res/drawable/header_background.xml deleted file mode 100644 index 77db9e03..00000000 --- a/res/drawable/header_background.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/res/drawable/ic_dialog_alert_material.xml b/res/drawable/ic_dialog_alert_material.xml new file mode 100644 index 00000000..8bd2e0ba --- /dev/null +++ b/res/drawable/ic_dialog_alert_material.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/drawable/ic_more_items.xml b/res/drawable/ic_more_items.xml new file mode 100644 index 00000000..5fdcdcef --- /dev/null +++ b/res/drawable/ic_more_items.xml @@ -0,0 +1,29 @@ + + + + + + + \ No newline at end of file diff --git a/res/layout/app_details.xml b/res/layout/app_details.xml index 6a330c74..8f7d2cff 100644 --- a/res/layout/app_details.xml +++ b/res/layout/app_details.xml @@ -4,9 +4,9 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -32,7 +32,7 @@ installation screens android:layout_width="32dip" android:layout_height="32dip" android:layout_marginStart="8dip" - android:background="@color/transparent" + android:background="@android:color/transparent" android:layout_alignParentStart="true" android:gravity="start" android:scaleType="centerCrop"/> @@ -61,6 +61,6 @@ installation screens android:layout_width="match_parent" android:layout_height="wrap_content" /> - + diff --git a/res/layout/grant_permissions.xml b/res/layout/grant_permissions.xml index b3565240..086a541a 100644 --- a/res/layout/grant_permissions.xml +++ b/res/layout/grant_permissions.xml @@ -18,6 +18,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" > + - @@ -72,14 +73,16 @@ android:layout_width="0dp" android:layout_height="0dp" android:layout_weight="1" - android:visibility="invisible" /> + android:visibility="invisible" > + - + + diff --git a/res/layout/header.xml b/res/layout/header.xml index 0e000a4d..f158e442 100644 --- a/res/layout/header.xml +++ b/res/layout/header.xml @@ -17,9 +17,8 @@ + android:background="?android:attr/colorPrimary" + android:gravity="center_vertical" > - - - diff --git a/res/layout/install_confirm.xml b/res/layout/install_confirm.xml index 86ea1652..72d5e377 100644 --- a/res/layout/install_confirm.xml +++ b/res/layout/install_confirm.xml @@ -31,9 +31,10 @@ android:id="@+id/install_confirm_question" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:paddingStart="16dip" + android:paddingEnd="16dip" android:text="@string/install_confirm_question" android:textAppearance="?android:attr/textAppearanceMedium" - style="@style/padded" android:paddingTop="4dip" /> + android:scaleType="fitCenter" > + diff --git a/res/layout/permissions_frame.xml b/res/layout/permissions_frame.xml index 8f1f2789..156af57e 100644 --- a/res/layout/permissions_frame.xml +++ b/res/layout/permissions_frame.xml @@ -30,8 +30,8 @@ android:layout_height="match_parent" android:text="@string/no_permissions" android:gravity="center" - android:textAppearance="@android:style/TextAppearance.Large" - /> + style="?android:attr/textAppearanceLarge"> + diff --git a/res/layout/preference_permissions.xml b/res/layout/preference_permissions.xml index 67b4469c..631d56bd 100644 --- a/res/layout/preference_permissions.xml +++ b/res/layout/preference_permissions.xml @@ -37,7 +37,7 @@ android:paddingEnd="20dp" android:paddingTop="4dp" android:paddingBottom="4dp"> - - diff --git a/res/values-television/colors.xml b/res/values-television/colors.xml index eb6bfa7d..08209600 100644 --- a/res/values-television/colors.xml +++ b/res/values-television/colors.xml @@ -15,6 +15,13 @@ --> + + #FFF1F1F1 + #88F1F1F1 + #88F1F1F1 + #FF111111 + #77000000 + #ff263238 @color/grant_permissions_white_text_alpha_100 @color/grant_permissions_white_text_alpha_100 diff --git a/res/values-television/styles.xml b/res/values-television/styles.xml new file mode 100644 index 00000000..5f712f7d --- /dev/null +++ b/res/values-television/styles.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + diff --git a/res/values/colors.xml b/res/values/colors.xml deleted file mode 100755 index 549f55fd..00000000 --- a/res/values/colors.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - #cc222222 - #00000000 - #ff37474f - @*android:color/primary_text_default_material_light - #60000000 - - #FFF1F1F1 - #88F1F1F1 - #88F1F1F1 - #FF111111 - #77000000 - - - diff --git a/res/values/styles.xml b/res/values/styles.xml index 9c166971..7f6a4693 100755 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -15,37 +15,22 @@ --> - - - - - - - - - + diff --git a/res/values/themes.xml b/res/values/themes.xml index 2f9c2b55..026c77fa 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -16,11 +16,13 @@ --> - + diff --git a/src/com/android/packageinstaller/permission/model/PermissionApps.java b/src/com/android/packageinstaller/permission/model/PermissionApps.java index 9365bf13..e5d96d55 100644 --- a/src/com/android/packageinstaller/permission/model/PermissionApps.java +++ b/src/com/android/packageinstaller/permission/model/PermissionApps.java @@ -31,6 +31,7 @@ import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; +import com.android.packageinstaller.R; import com.android.packageinstaller.permission.utils.Utils; import java.util.ArrayList; @@ -275,7 +276,7 @@ public class PermissionApps { if (info.icon != 0) { mIcon = info.loadUnbadgedIcon(mPm); } else { - mIcon = mContext.getDrawable(com.android.internal.R.drawable.ic_perm_device_info); + mIcon = mContext.getDrawable(R.drawable.ic_perm_device_info); } mIcon = Utils.applyTint(mContext, mIcon, android.R.attr.colorControlNormal); } diff --git a/src/com/android/packageinstaller/permission/ui/AllAppPermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/AllAppPermissionsFragment.java deleted file mode 100644 index 2fb9a510..00000000 --- a/src/com/android/packageinstaller/permission/ui/AllAppPermissionsFragment.java +++ /dev/null @@ -1,217 +0,0 @@ -/* -* Copyright (C) 2015 The Android Open Source Project -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -package com.android.packageinstaller.permission.ui; - -import android.app.ActionBar; -import android.app.AlertDialog; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageItemInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.PermissionGroupInfo; -import android.content.pm.PermissionInfo; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Bundle; -import android.provider.Settings; -import android.support.v7.preference.Preference; -import android.support.v7.preference.Preference.OnPreferenceClickListener; -import android.support.v7.preference.PreferenceCategory; -import android.support.v7.preference.PreferenceGroup; -import android.util.Log; -import android.view.MenuItem; - -import com.android.packageinstaller.R; -import com.android.packageinstaller.permission.utils.Utils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; - -public final class AllAppPermissionsFragment extends SettingsWithHeader { - - private static final String LOG_TAG = "AllAppPermissionsFragment"; - - private static final String KEY_OTHER = "other_perms"; - - public static AllAppPermissionsFragment newInstance(String packageName) { - AllAppPermissionsFragment instance = new AllAppPermissionsFragment(); - Bundle arguments = new Bundle(); - arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName); - instance.setArguments(arguments); - return instance; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - final ActionBar ab = getActivity().getActionBar(); - if (ab != null) { - ab.setTitle(R.string.all_permissions); - ab.setDisplayHomeAsUpEnabled(true); - } - } - - @Override - public void onResume() { - super.onResume(); - updateUi(); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: { - getFragmentManager().popBackStack(); - return true; - } - } - return super.onOptionsItemSelected(item); - } - - private void updateUi() { - if (getPreferenceScreen() != null) { - getPreferenceScreen().removeAll(); - } - addPreferencesFromResource(R.xml.all_permissions); - PreferenceGroup otherGroup = (PreferenceGroup) findPreference(KEY_OTHER); - ArrayList prefs = new ArrayList<>(); // Used for sorting. - prefs.add(otherGroup); - String pkg = getArguments().getString(Intent.EXTRA_PACKAGE_NAME); - otherGroup.removeAll(); - PackageManager pm = getContext().getPackageManager(); - - try { - PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_PERMISSIONS); - - ApplicationInfo appInfo = info.applicationInfo; - final Drawable icon = appInfo.loadIcon(pm); - final CharSequence label = appInfo.loadLabel(pm); - Intent infoIntent = null; - if (!getActivity().getIntent().getBooleanExtra( - AppPermissionsFragment.EXTRA_HIDE_INFO_BUTTON, false)) { - infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) - .setData(Uri.fromParts("package", pkg, null)); - } - setHeader(icon, label, infoIntent); - - if (info.requestedPermissions != null) { - for (int i = 0; i < info.requestedPermissions.length; i++) { - PermissionInfo perm; - try { - perm = pm.getPermissionInfo(info.requestedPermissions[i], 0); - } catch (NameNotFoundException e) { - Log.e(LOG_TAG, - "Can't get permission info for " + info.requestedPermissions[i], e); - continue; - } - - if ((perm.flags & PermissionInfo.FLAG_INSTALLED) == 0 - || (perm.flags & PermissionInfo.FLAG_HIDDEN) != 0) { - continue; - } - - if (perm.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) { - PermissionGroupInfo group = getGroup(perm.group, pm); - PreferenceGroup pref = - findOrCreate(group != null ? group : perm, pm, prefs); - pref.addPreference(getPreference(perm, group, pm)); - } else if (perm.protectionLevel == PermissionInfo.PROTECTION_NORMAL) { - PermissionGroupInfo group = getGroup(perm.group, pm); - otherGroup.addPreference(getPreference(perm, group, pm)); - } - } - } - } catch (NameNotFoundException e) { - Log.e(LOG_TAG, "Problem getting package info for " + pkg, e); - } - // Sort an ArrayList of the groups and then set the order from the sorting. - Collections.sort(prefs, new Comparator() { - @Override - public int compare(Preference lhs, Preference rhs) { - String lKey = lhs.getKey(); - String rKey = rhs.getKey(); - if (lKey.equals(KEY_OTHER)) { - return 1; - } else if (rKey.equals(KEY_OTHER)) { - return -1; - } else if (Utils.isModernPermissionGroup(lKey) - != Utils.isModernPermissionGroup(rKey)) { - return Utils.isModernPermissionGroup(lKey) ? -1 : 1; - } - return lhs.getTitle().toString().compareTo(rhs.getTitle().toString()); - } - }); - for (int i = 0; i < prefs.size(); i++) { - prefs.get(i).setOrder(i); - } - } - - private PermissionGroupInfo getGroup(String group, PackageManager pm) { - try { - return pm.getPermissionGroupInfo(group, 0); - } catch (NameNotFoundException e) { - return null; - } - } - - private PreferenceGroup findOrCreate(PackageItemInfo group, PackageManager pm, - ArrayList prefs) { - PreferenceGroup pref = (PreferenceGroup) findPreference(group.name); - if (pref == null) { - pref = new PreferenceCategory(getContext()); - pref.setKey(group.name); - pref.setLayoutResource(R.layout.preference_category_material); - pref.setTitle(group.loadLabel(pm)); - prefs.add(pref); - getPreferenceScreen().addPreference(pref); - } - return pref; - } - - private Preference getPreference(PermissionInfo perm, PermissionGroupInfo group, - PackageManager pm) { - Preference pref = new Preference(getContext()); - pref.setLayoutResource(R.layout.preference_permissions); - Drawable icon = null; - if (perm.icon != 0) { - icon = perm.loadIcon(pm); - } else if (group != null && group.icon != 0) { - icon = group.loadIcon(pm); - } else { - icon = getContext().getDrawable(R.drawable.ic_perm_device_info); - } - pref.setIcon(Utils.applyTint(getContext(), icon, android.R.attr.colorControlNormal)); - pref.setTitle(perm.loadLabel(pm)); - final CharSequence desc = perm.loadDescription(pm); - pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - new AlertDialog.Builder(getContext()) - .setMessage(desc) - .setPositiveButton(android.R.string.ok, null) - .show(); - return true; - } - }); - - return pref; - } -} \ No newline at end of file diff --git a/src/com/android/packageinstaller/permission/ui/AppPermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/AppPermissionsFragment.java deleted file mode 100644 index 6396c61e..00000000 --- a/src/com/android/packageinstaller/permission/ui/AppPermissionsFragment.java +++ /dev/null @@ -1,403 +0,0 @@ -/* -* Copyright (C) 2015 The Android Open Source Project -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -package com.android.packageinstaller.permission.ui; - -import android.annotation.Nullable; -import android.app.ActionBar; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Fragment; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Bundle; -import android.provider.Settings; -import android.support.v14.preference.SwitchPreference; -import android.support.v7.preference.Preference; -import android.support.v7.preference.Preference.OnPreferenceChangeListener; -import android.support.v7.preference.Preference.OnPreferenceClickListener; -import android.support.v7.preference.PreferenceScreen; -import android.util.Log; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -import com.android.packageinstaller.R; -import com.android.packageinstaller.permission.model.AppPermissionGroup; -import com.android.packageinstaller.permission.model.AppPermissions; -import com.android.packageinstaller.permission.utils.LocationUtils; -import com.android.packageinstaller.permission.utils.SafetyNetLogger; -import com.android.packageinstaller.permission.utils.Utils; - -import java.util.ArrayList; -import java.util.List; - -public final class AppPermissionsFragment extends SettingsWithHeader - implements OnPreferenceChangeListener { - - private static final String LOG_TAG = "ManagePermsFragment"; - - static final String EXTRA_HIDE_INFO_BUTTON = "hideInfoButton"; - - private static final int MENU_ALL_PERMS = 0; - - private List mToggledGroups; - private AppPermissions mAppPermissions; - private PreferenceScreen mExtraScreen; - - private boolean mHasConfirmedRevoke; - - public static AppPermissionsFragment newInstance(String packageName) { - return setPackageName(new AppPermissionsFragment(), packageName); - } - - private static T setPackageName(T fragment, String packageName) { - Bundle arguments = new Bundle(); - arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName); - fragment.setArguments(arguments); - return fragment; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setLoading(true /* loading */, false /* animate */); - setHasOptionsMenu(true); - final ActionBar ab = getActivity().getActionBar(); - if (ab != null) { - ab.setDisplayHomeAsUpEnabled(true); - } - - String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME); - Activity activity = getActivity(); - PackageInfo packageInfo = getPackageInfo(activity, packageName); - if (packageInfo == null) { - Toast.makeText(activity, R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show(); - activity.finish(); - return; - } - - mAppPermissions = new AppPermissions(activity, packageInfo, null, true, new Runnable() { - @Override - public void run() { - getActivity().finish(); - } - }); - loadPreferences(); - } - - @Override - public void onResume() { - super.onResume(); - mAppPermissions.refresh(); - setPreferencesCheckedState(); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: { - getActivity().finish(); - return true; - } - - case MENU_ALL_PERMS: { - Fragment frag = AllAppPermissionsFragment.newInstance( - getArguments().getString(Intent.EXTRA_PACKAGE_NAME)); - getFragmentManager().beginTransaction() - .replace(android.R.id.content, frag) - .addToBackStack("AllPerms") - .commit(); - return true; - } - } - return super.onOptionsItemSelected(item); - } - - @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - if (mAppPermissions != null) { - bindUi(this, mAppPermissions.getPackageInfo()); - } - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - menu.add(Menu.NONE, MENU_ALL_PERMS, Menu.NONE, R.string.all_permissions); - } - - private static void bindUi(SettingsWithHeader fragment, PackageInfo packageInfo) { - Activity activity = fragment.getActivity(); - PackageManager pm = activity.getPackageManager(); - ApplicationInfo appInfo = packageInfo.applicationInfo; - Intent infoIntent = null; - if (!activity.getIntent().getBooleanExtra(EXTRA_HIDE_INFO_BUTTON, false)) { - infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) - .setData(Uri.fromParts("package", packageInfo.packageName, null)); - } - - Drawable icon = appInfo.loadIcon(pm); - CharSequence label = appInfo.loadLabel(pm); - fragment.setHeader(icon, label, infoIntent); - - ActionBar ab = activity.getActionBar(); - if (ab != null) { - ab.setTitle(R.string.app_permissions); - } - - ViewGroup rootView = (ViewGroup) fragment.getView(); - ImageView iconView = (ImageView) rootView.findViewById(R.id.lb_icon); - if (iconView != null) { - iconView.setImageDrawable(icon); - } - TextView titleView = (TextView) rootView.findViewById(R.id.lb_title); - if (titleView != null) { - titleView.setText(R.string.app_permissions); - } - TextView breadcrumbView = (TextView) rootView.findViewById(R.id.lb_breadcrumb); - if (breadcrumbView != null) { - breadcrumbView.setText(label); - } - } - - private void loadPreferences() { - Context context = getPreferenceManager().getContext(); - if (context == null) { - return; - } - - PreferenceScreen screen = getPreferenceScreen(); - screen.removeAll(); - - if (mExtraScreen != null) { - mExtraScreen.removeAll(); - } - - final Preference extraPerms = new Preference(context); - extraPerms.setIcon(R.drawable.ic_toc); - extraPerms.setTitle(R.string.additional_permissions); - - for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) { - if (!Utils.shouldShowPermission(group, mAppPermissions.getPackageInfo().packageName)) { - continue; - } - - boolean isPlatform = group.getDeclaringPackage().equals(Utils.OS_PKG); - - SwitchPreference preference = new SwitchPreference(context); - preference.setOnPreferenceChangeListener(this); - preference.setKey(group.getName()); - Drawable icon = Utils.loadDrawable(context.getPackageManager(), - group.getIconPkg(), group.getIconResId()); - preference.setIcon(Utils.applyTint(getContext(), icon, - android.R.attr.colorControlNormal)); - preference.setTitle(group.getLabel()); - if (group.isPolicyFixed()) { - preference.setSummary(getString(R.string.permission_summary_enforced_by_policy)); - } - preference.setPersistent(false); - preference.setEnabled(!group.isPolicyFixed()); - preference.setChecked(group.areRuntimePermissionsGranted()); - - if (isPlatform) { - screen.addPreference(preference); - } else { - if (mExtraScreen == null) { - mExtraScreen = getPreferenceManager().createPreferenceScreen(context); - } - mExtraScreen.addPreference(preference); - } - } - - if (mExtraScreen != null) { - extraPerms.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - AdditionalPermissionsFragment frag = new AdditionalPermissionsFragment(); - setPackageName(frag, getArguments().getString(Intent.EXTRA_PACKAGE_NAME)); - frag.setTargetFragment(AppPermissionsFragment.this, 0); - getFragmentManager().beginTransaction() - .replace(android.R.id.content, frag) - .addToBackStack(null) - .commit(); - return true; - } - }); - int count = mExtraScreen.getPreferenceCount(); - extraPerms.setSummary(getResources().getQuantityString( - R.plurals.additional_permissions_more, count, count)); - screen.addPreference(extraPerms); - } - - setLoading(false /* loading */, true /* animate */); - } - - @Override - public boolean onPreferenceChange(final Preference preference, Object newValue) { - String groupName = preference.getKey(); - final AppPermissionGroup group = mAppPermissions.getPermissionGroup(groupName); - - if (group == null) { - return false; - } - - OverlayTouchActivity activity = (OverlayTouchActivity) getActivity(); - if (activity.isObscuredTouch()) { - activity.showOverlayDialog(); - return false; - } - - addToggledGroup(group); - - if (LocationUtils.isLocationGroupAndProvider(group.getName(), group.getApp().packageName)) { - LocationUtils.showLocationDialog(getContext(), mAppPermissions.getAppLabel()); - return false; - } - if (newValue == Boolean.TRUE) { - group.grantRuntimePermissions(false); - } else { - final boolean grantedByDefault = group.hasGrantedByDefaultPermission(); - if (grantedByDefault || (!group.hasRuntimePermission() && !mHasConfirmedRevoke)) { - new AlertDialog.Builder(getContext()) - .setMessage(grantedByDefault ? R.string.system_warning - : R.string.old_sdk_deny_warning) - .setNegativeButton(R.string.cancel, null) - .setPositiveButton(R.string.grant_dialog_button_deny, - new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - ((SwitchPreference) preference).setChecked(false); - group.revokeRuntimePermissions(false); - if (!grantedByDefault) { - mHasConfirmedRevoke = true; - } - } - }) - .show(); - return false; - } else { - group.revokeRuntimePermissions(false); - } - } - - return true; - } - - @Override - public void onPause() { - super.onPause(); - logToggledGroups(); - } - - private void addToggledGroup(AppPermissionGroup group) { - if (mToggledGroups == null) { - mToggledGroups = new ArrayList<>(); - } - // Double toggle is back to initial state. - if (mToggledGroups.contains(group)) { - mToggledGroups.remove(group); - } else { - mToggledGroups.add(group); - } - } - - private void logToggledGroups() { - if (mToggledGroups != null) { - String packageName = mAppPermissions.getPackageInfo().packageName; - SafetyNetLogger.logPermissionsToggled(packageName, mToggledGroups); - mToggledGroups = null; - } - } - - private void setPreferencesCheckedState() { - setPreferencesCheckedState(getPreferenceScreen()); - if (mExtraScreen != null) { - setPreferencesCheckedState(mExtraScreen); - } - } - - private void setPreferencesCheckedState(PreferenceScreen screen) { - int preferenceCount = screen.getPreferenceCount(); - for (int i = 0; i < preferenceCount; i++) { - Preference preference = screen.getPreference(i); - if (preference instanceof SwitchPreference) { - SwitchPreference switchPref = (SwitchPreference) preference; - AppPermissionGroup group = mAppPermissions.getPermissionGroup(switchPref.getKey()); - if (group != null) { - switchPref.setChecked(group.areRuntimePermissionsGranted()); - } - } - } - } - - private static PackageInfo getPackageInfo(Activity activity, String packageName) { - try { - return activity.getPackageManager().getPackageInfo( - packageName, PackageManager.GET_PERMISSIONS); - } catch (PackageManager.NameNotFoundException e) { - Log.i(LOG_TAG, "No package:" + activity.getCallingPackage(), e); - return null; - } - } - - public static class AdditionalPermissionsFragment extends SettingsWithHeader { - AppPermissionsFragment mOuterFragment; - - @Override - public void onCreate(Bundle savedInstanceState) { - mOuterFragment = (AppPermissionsFragment) getTargetFragment(); - super.onCreate(savedInstanceState); - setHeader(mOuterFragment.mIcon, mOuterFragment.mLabel, mOuterFragment.mInfoIntent); - setHasOptionsMenu(true); - } - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - setPreferenceScreen(mOuterFragment.mExtraScreen); - } - - @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME); - bindUi(this, getPackageInfo(getActivity(), packageName)); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - getFragmentManager().popBackStack(); - return true; - } - return super.onOptionsItemSelected(item); - } - } -} diff --git a/src/com/android/packageinstaller/permission/ui/ButtonBarLayout.java b/src/com/android/packageinstaller/permission/ui/ButtonBarLayout.java new file mode 100644 index 00000000..59e54707 --- /dev/null +++ b/src/com/android/packageinstaller/permission/ui/ButtonBarLayout.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.permission.ui; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.widget.LinearLayout; +import com.android.packageinstaller.R; + +/** + * An extension of LinearLayout that automatically switches to vertical + * orientation when it can't fit its child views horizontally. + */ +public class ButtonBarLayout extends LinearLayout { + /** Whether the current configuration allows stacking. */ + private boolean mAllowStacking; + + private int mLastWidthSize = -1; + + public ButtonBarLayout(Context context, AttributeSet attrs) { + super(context, attrs); + mAllowStacking = true; + } + + public void setAllowStacking(boolean allowStacking) { + if (mAllowStacking != allowStacking) { + mAllowStacking = allowStacking; + if (!mAllowStacking && getOrientation() == LinearLayout.VERTICAL) { + setStacked(false); + } + requestLayout(); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int widthSize = MeasureSpec.getSize(widthMeasureSpec); + + if (mAllowStacking) { + if (widthSize > mLastWidthSize && isStacked()) { + // We're being measured wider this time, try un-stacking. + setStacked(false); + } + + mLastWidthSize = widthSize; + } + + boolean needsRemeasure = false; + + // If we're not stacked, make sure the measure spec is AT_MOST rather + // than EXACTLY. This ensures that we'll still get TOO_SMALL so that we + // know to stack the buttons. + final int initialWidthMeasureSpec; + if (!isStacked() && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) { + initialWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST); + + // We'll need to remeasure again to fill excess space. + needsRemeasure = true; + } else { + initialWidthMeasureSpec = widthMeasureSpec; + } + + super.onMeasure(initialWidthMeasureSpec, heightMeasureSpec); + + if (mAllowStacking && !isStacked()) { + final int measuredWidth = getMeasuredWidthAndState(); + final int measuredWidthState = measuredWidth & MEASURED_STATE_MASK; + if (measuredWidthState == MEASURED_STATE_TOO_SMALL) { + setStacked(true); + + // Measure again in the new orientation. + needsRemeasure = true; + } + } + + if (needsRemeasure) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } + + private void setStacked(boolean stacked) { + setOrientation(stacked ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL); + setGravity(stacked ? Gravity.RIGHT : Gravity.BOTTOM); + + final View spacer = findViewById(R.id.spacer); + if (spacer != null) { + spacer.setVisibility(stacked ? View.GONE : View.INVISIBLE); + } + + // Reverse the child order. This is specific to the Material button + // bar's layout XML and will probably not generalize. + final int childCount = getChildCount(); + for (int i = childCount - 2; i >= 0; i--) { + bringChildToFront(getChildAt(i)); + } + } + + private boolean isStacked() { + return getOrientation() == LinearLayout.VERTICAL; + } +} diff --git a/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java b/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java index ffa8bf35..102fd6ef 100644 --- a/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java +++ b/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java @@ -26,11 +26,12 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PermissionInfo; import android.content.res.Resources; +import android.graphics.Typeface; import android.graphics.drawable.Icon; import android.hardware.camera2.utils.ArrayUtils; import android.os.Bundle; import android.text.SpannableString; -import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; @@ -73,11 +74,13 @@ public class GrantPermissionsActivity extends OverlayTouchActivity setTitle(R.string.permission_request_title); if (DeviceUtils.isTelevision(this)) { - mViewHandler = new GrantPermissionsTvViewHandler(this).setResultListener(this); + mViewHandler = new com.android.packageinstaller.permission.ui.television + .GrantPermissionsViewHandlerImpl(this).setResultListener(this); } else if (DeviceUtils.isWear(this)) { mViewHandler = new GrantPermissionsWatchViewHandler(this).setResultListener(this); } else { - mViewHandler = new GrantPermissionsDefaultViewHandler(this).setResultListener(this); + mViewHandler = new com.android.packageinstaller.permission.ui.handheld + .GrantPermissionsViewHandlerImpl(this).setResultListener(this); } mRequestedPermissions = getIntent().getStringArrayExtra( @@ -209,8 +212,7 @@ public class GrantPermissionsActivity extends OverlayTouchActivity // Color the app name. int appLabelStart = message.toString().indexOf(appLabel.toString(), 0); int appLabelLength = appLabel.length(); - int color = getColor(R.color.grant_permissions_app_color); - message.setSpan(new ForegroundColorSpan(color), appLabelStart, + message.setSpan(new StyleSpan(Typeface.BOLD), appLabelStart, appLabelStart + appLabelLength, 0); // Set the new grant view diff --git a/src/com/android/packageinstaller/permission/ui/GrantPermissionsDefaultViewHandler.java b/src/com/android/packageinstaller/permission/ui/GrantPermissionsDefaultViewHandler.java deleted file mode 100644 index c5d78784..00000000 --- a/src/com/android/packageinstaller/permission/ui/GrantPermissionsDefaultViewHandler.java +++ /dev/null @@ -1,462 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.packageinstaller.permission.ui; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.drawable.Icon; -import android.os.Bundle; -import android.util.SparseArray; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnLayoutChangeListener; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.ViewRootImpl; -import android.view.WindowManager.LayoutParams; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.ImageView; -import android.widget.TextView; - -import com.android.internal.widget.ButtonBarLayout; -import com.android.packageinstaller.R; - -import java.util.ArrayList; - -final class GrantPermissionsDefaultViewHandler - implements GrantPermissionsViewHandler, OnClickListener { - - public static final String ARG_GROUP_NAME = "ARG_GROUP_NAME"; - public static final String ARG_GROUP_COUNT = "ARG_GROUP_COUNT"; - public static final String ARG_GROUP_INDEX = "ARG_GROUP_INDEX"; - public static final String ARG_GROUP_ICON = "ARG_GROUP_ICON"; - public static final String ARG_GROUP_MESSAGE = "ARG_GROUP_MESSAGE"; - public static final String ARG_GROUP_SHOW_DO_NOT_ASK = "ARG_GROUP_SHOW_DO_NOT_ASK"; - public static final String ARG_GROUP_DO_NOT_ASK_CHECKED = "ARG_GROUP_DO_NOT_ASK_CHECKED"; - - // Animation parameters. - private static final long SIZE_START_DELAY = 300; - private static final long SIZE_START_LENGTH = 233; - private static final long FADE_OUT_START_DELAY = 300; - private static final long FADE_OUT_START_LENGTH = 217; - private static final long TRANSLATE_START_DELAY = 367; - private static final long TRANSLATE_LENGTH = 317; - private static final long GROUP_UPDATE_DELAY = 400; - private static final long DO_NOT_ASK_CHECK_DELAY = 450; - - private final Context mContext; - - private ResultListener mResultListener; - - private String mGroupName; - private int mGroupCount; - private int mGroupIndex; - private Icon mGroupIcon; - private CharSequence mGroupMessage; - private boolean mShowDonNotAsk; - private boolean mDoNotAskChecked; - - private ImageView mIconView; - private TextView mCurrentGroupView; - private TextView mMessageView; - private CheckBox mDoNotAskCheckbox; - private Button mAllowButton; - - private ArrayList mHeightControllers; - private ManualLayoutFrame mRootView; - - // Needed for animation - private ViewGroup mDescContainer; - private ViewGroup mCurrentDesc; - private ViewGroup mNextDesc; - - private ViewGroup mDialogContainer; - - private final Runnable mUpdateGroup = new Runnable() { - @Override - public void run() { - updateGroup(); - } - }; - - GrantPermissionsDefaultViewHandler(Context context) { - mContext = context; - } - - @Override - public GrantPermissionsDefaultViewHandler setResultListener(ResultListener listener) { - mResultListener = listener; - return this; - } - - @Override - public void saveInstanceState(Bundle arguments) { - arguments.putString(ARG_GROUP_NAME, mGroupName); - arguments.putInt(ARG_GROUP_COUNT, mGroupCount); - arguments.putInt(ARG_GROUP_INDEX, mGroupIndex); - arguments.putParcelable(ARG_GROUP_ICON, mGroupIcon); - arguments.putCharSequence(ARG_GROUP_MESSAGE, mGroupMessage); - arguments.putBoolean(ARG_GROUP_SHOW_DO_NOT_ASK, mShowDonNotAsk); - arguments.putBoolean(ARG_GROUP_DO_NOT_ASK_CHECKED, mDoNotAskCheckbox.isChecked()); - } - - @Override - public void loadInstanceState(Bundle savedInstanceState) { - mGroupName = savedInstanceState.getString(ARG_GROUP_NAME); - mGroupMessage = savedInstanceState.getCharSequence(ARG_GROUP_MESSAGE); - mGroupIcon = savedInstanceState.getParcelable(ARG_GROUP_ICON); - mGroupCount = savedInstanceState.getInt(ARG_GROUP_COUNT); - mGroupIndex = savedInstanceState.getInt(ARG_GROUP_INDEX); - mShowDonNotAsk = savedInstanceState.getBoolean(ARG_GROUP_SHOW_DO_NOT_ASK); - mDoNotAskChecked = savedInstanceState.getBoolean(ARG_GROUP_DO_NOT_ASK_CHECKED); - } - - @Override - public void updateUi(String groupName, int groupCount, int groupIndex, Icon icon, - CharSequence message, boolean showDonNotAsk) { - mGroupName = groupName; - mGroupCount = groupCount; - mGroupIndex = groupIndex; - mGroupIcon = icon; - mGroupMessage = message; - mShowDonNotAsk = showDonNotAsk; - mDoNotAskChecked = false; - // If this is a second (or later) permission and the views exist, then animate. - if (mIconView != null) { - if (mGroupIndex > 0) { - // The first message will be announced as the title of the activity, all others - // we need to announce ourselves. - mDescContainer.announceForAccessibility(message); - animateToPermission(); - } else { - updateDescription(); - updateGroup(); - updateDoNotAskCheckBox(); - } - } - } - - private void animateToPermission() { - if (mHeightControllers == null) { - // We need to manually control the height of any views heigher than the root that - // we inflate. Find all the views up to the root and create ViewHeightControllers for - // them. - mHeightControllers = new ArrayList<>(); - ViewRootImpl viewRoot = mRootView.getViewRootImpl(); - ViewParent v = mRootView.getParent(); - addHeightController(mDialogContainer); - addHeightController(mRootView); - while (v != viewRoot) { - addHeightController((View) v); - v = v.getParent(); - } - // On the heighest level view, we want to setTop rather than setBottom to control the - // height, this way the dialog will grow up rather than down. - ViewHeightController realRootView = - mHeightControllers.get(mHeightControllers.size() - 1); - realRootView.setControlTop(true); - } - - // Grab the current height/y positions, then wait for the layout to change, - // so we can get the end height/y positions. - final SparseArray startPositions = getViewPositions(); - final int startHeight = mRootView.getLayoutHeight(); - mRootView.addOnLayoutChangeListener(new OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, - int oldTop, int oldRight, int oldBottom) { - mRootView.removeOnLayoutChangeListener(this); - SparseArray endPositions = getViewPositions(); - int endHeight = mRootView.getLayoutHeight(); - if (startPositions.get(R.id.do_not_ask_checkbox) == 0 - && endPositions.get(R.id.do_not_ask_checkbox) != 0) { - // If the checkbox didn't have a position before but has one now then set - // the start position to the end position because it just became visible. - startPositions.put(R.id.do_not_ask_checkbox, - endPositions.get(R.id.do_not_ask_checkbox)); - } - animateYPos(startPositions, endPositions, endHeight - startHeight); - } - }); - - // Fade out old description group and scale out the icon for it. - Interpolator interpolator = AnimationUtils.loadInterpolator(mContext, - android.R.interpolator.fast_out_linear_in); - mIconView.animate() - .scaleX(0) - .scaleY(0) - .setStartDelay(FADE_OUT_START_DELAY) - .setDuration(FADE_OUT_START_LENGTH) - .setInterpolator(interpolator) - .start(); - mCurrentDesc.animate() - .alpha(0) - .setStartDelay(FADE_OUT_START_DELAY) - .setDuration(FADE_OUT_START_LENGTH) - .setInterpolator(interpolator) - .setListener(null) - .start(); - - // Update the index of the permission after the animations have started. - mCurrentGroupView.getHandler().postDelayed(mUpdateGroup, GROUP_UPDATE_DELAY); - - // Add the new description and translate it in. - mNextDesc = (ViewGroup) LayoutInflater.from(mContext).inflate( - R.layout.permission_description, mDescContainer, false); - - mMessageView = (TextView) mNextDesc.findViewById(R.id.permission_message); - mIconView = (ImageView) mNextDesc.findViewById(R.id.permission_icon); - updateDescription(); - - int width = mDescContainer.getRootView().getWidth(); - mDescContainer.addView(mNextDesc); - mNextDesc.setTranslationX(width); - - final View oldDesc = mCurrentDesc; - // Remove the old view from the description, so that we can shrink if necessary. - mDescContainer.removeView(oldDesc); - oldDesc.setPadding(mDescContainer.getLeft(), mDescContainer.getTop(), - mRootView.getRight() - mDescContainer.getRight(), 0); - mRootView.addView(oldDesc); - - mCurrentDesc = mNextDesc; - mNextDesc.animate() - .translationX(0) - .setStartDelay(TRANSLATE_START_DELAY) - .setDuration(TRANSLATE_LENGTH) - .setInterpolator(AnimationUtils.loadInterpolator(mContext, - android.R.interpolator.linear_out_slow_in)) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - // This is the longest animation, when it finishes, we are done. - mRootView.removeView(oldDesc); - } - }) - .start(); - - boolean visibleBefore = mDoNotAskCheckbox.getVisibility() == View.VISIBLE; - updateDoNotAskCheckBox(); - boolean visibleAfter = mDoNotAskCheckbox.getVisibility() == View.VISIBLE; - if (visibleBefore != visibleAfter) { - Animation anim = AnimationUtils.loadAnimation(mContext, - visibleAfter ? android.R.anim.fade_in : android.R.anim.fade_out); - anim.setStartOffset(visibleAfter ? DO_NOT_ASK_CHECK_DELAY : 0); - mDoNotAskCheckbox.startAnimation(anim); - } - } - - private void addHeightController(View v) { - ViewHeightController heightController = new ViewHeightController(v); - heightController.setHeight(v.getHeight()); - mHeightControllers.add(heightController); - } - - private SparseArray getViewPositions() { - SparseArray locMap = new SparseArray<>(); - final int N = mDialogContainer.getChildCount(); - for (int i = 0; i < N; i++) { - View child = mDialogContainer.getChildAt(i); - if (child.getId() <= 0) { - // Only track views with ids. - continue; - } - locMap.put(child.getId(), child.getY()); - } - return locMap; - } - - private void animateYPos(SparseArray startPositions, SparseArray endPositions, - int heightDiff) { - final int N = startPositions.size(); - for (int i = 0; i < N; i++) { - int key = startPositions.keyAt(i); - float start = startPositions.get(key); - float end = endPositions.get(key); - if (start != end) { - final View child = mDialogContainer.findViewById(key); - child.setTranslationY(start - end); - child.animate() - .setStartDelay(SIZE_START_DELAY) - .setDuration(SIZE_START_LENGTH) - .translationY(0) - .start(); - } - } - for (int i = 0; i < mHeightControllers.size(); i++) { - mHeightControllers.get(i).animateAddHeight(heightDiff); - } - } - - @Override - public View createView() { - mRootView = (ManualLayoutFrame) LayoutInflater.from(mContext) - .inflate(R.layout.grant_permissions, null); - ((ButtonBarLayout) mRootView.findViewById(R.id.button_group)).setAllowStacking( - Resources.getSystem().getBoolean( - com.android.internal.R.bool.allow_stacked_button_bar)); - - mDialogContainer = (ViewGroup) mRootView.findViewById(R.id.dialog_container); - mMessageView = (TextView) mRootView.findViewById(R.id.permission_message); - mIconView = (ImageView) mRootView.findViewById(R.id.permission_icon); - mCurrentGroupView = (TextView) mRootView.findViewById(R.id.current_page_text); - mDoNotAskCheckbox = (CheckBox) mRootView.findViewById(R.id.do_not_ask_checkbox); - mAllowButton = (Button) mRootView.findViewById(R.id.permission_allow_button); - - mDescContainer = (ViewGroup) mRootView.findViewById(R.id.desc_container); - mCurrentDesc = (ViewGroup) mRootView.findViewById(R.id.perm_desc_root); - - mAllowButton.setOnClickListener(this); - mRootView.findViewById(R.id.permission_deny_button).setOnClickListener(this); - mDoNotAskCheckbox.setOnClickListener(this); - - if (mGroupName != null) { - updateDescription(); - updateGroup(); - updateDoNotAskCheckBox(); - } - - return mRootView; - } - - @Override - public void updateWindowAttributes(LayoutParams outLayoutParams) { - // No-op - } - - private void updateDescription() { - mIconView.setImageDrawable(mGroupIcon.loadDrawable(mContext)); - mMessageView.setText(mGroupMessage); - } - - private void updateGroup() { - if (mGroupCount > 1) { - mCurrentGroupView.setVisibility(View.VISIBLE); - mCurrentGroupView.setText(mContext.getString(R.string.current_permission_template, - mGroupIndex + 1, mGroupCount)); - } else { - mCurrentGroupView.setVisibility(View.INVISIBLE); - } - } - - private void updateDoNotAskCheckBox() { - if (mShowDonNotAsk) { - mDoNotAskCheckbox.setVisibility(View.VISIBLE); - mDoNotAskCheckbox.setOnClickListener(this); - mDoNotAskCheckbox.setChecked(mDoNotAskChecked); - } else { - mDoNotAskCheckbox.setVisibility(View.GONE); - mDoNotAskCheckbox.setOnClickListener(null); - } - } - - @Override - public void onClick(View view) { - switch (view.getId()) { - case R.id.permission_allow_button: - if (mResultListener != null) { - view.clearAccessibilityFocus(); - mResultListener.onPermissionGrantResult(mGroupName, true, false); - } - break; - case R.id.permission_deny_button: - mAllowButton.setEnabled(true); - if (mResultListener != null) { - view.clearAccessibilityFocus(); - mResultListener.onPermissionGrantResult(mGroupName, false, - mDoNotAskCheckbox.isChecked()); - } - break; - case R.id.do_not_ask_checkbox: - mAllowButton.setEnabled(!mDoNotAskCheckbox.isChecked()); - break; - } - } - - @Override - public void onBackPressed() { - if (mResultListener != null) { - final boolean doNotAskAgain = mDoNotAskCheckbox.isChecked(); - mResultListener.onPermissionGrantResult(mGroupName, false, doNotAskAgain); - } - } - - /** - * Manually controls the height of a view through getBottom/setTop. Also listens - * for layout changes and sets the height again to be sure it doesn't change. - */ - private static final class ViewHeightController implements OnLayoutChangeListener { - private final View mView; - private int mHeight; - private int mNextHeight; - private boolean mControlTop; - private ObjectAnimator mAnimator; - - public ViewHeightController(View view) { - mView = view; - mView.addOnLayoutChangeListener(this); - } - - public void setControlTop(boolean controlTop) { - mControlTop = controlTop; - } - - public void animateAddHeight(int heightDiff) { - if (heightDiff != 0) { - if (mNextHeight == 0) { - mNextHeight = mHeight; - } - mNextHeight += heightDiff; - if (mAnimator != null) { - mAnimator.cancel(); - } - mAnimator = ObjectAnimator.ofInt(this, "height", mHeight, mNextHeight); - mAnimator.setStartDelay(SIZE_START_DELAY); - mAnimator.setDuration(SIZE_START_LENGTH); - mAnimator.start(); - } - } - - public void setHeight(int height) { - mHeight = height; - updateHeight(); - } - - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, - int oldTop, int oldRight, int oldBottom) { - // Ensure that the height never changes. - updateHeight(); - } - - private void updateHeight() { - if (mControlTop) { - mView.setTop(mView.getBottom() - mHeight); - } else { - mView.setBottom(mView.getTop() + mHeight); - } - } - } -} diff --git a/src/com/android/packageinstaller/permission/ui/GrantPermissionsTvViewHandler.java b/src/com/android/packageinstaller/permission/ui/GrantPermissionsTvViewHandler.java deleted file mode 100644 index 0e979ab6..00000000 --- a/src/com/android/packageinstaller/permission/ui/GrantPermissionsTvViewHandler.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.android.packageinstaller.permission.ui; - -import android.content.Context; -import android.graphics.PixelFormat; -import android.graphics.drawable.Icon; -import android.os.Bundle; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.WindowManager; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.packageinstaller.R; - -/** - * TV-specific view handler for the grant permissions activity. - */ -final class GrantPermissionsTvViewHandler implements GrantPermissionsViewHandler, OnClickListener { - - private static final String ARG_GROUP_NAME = "ARG_GROUP_NAME"; - - private final Context mContext; - - private ResultListener mResultListener; - - private String mGroupName; - - private LinearLayout mRootView; - private TextView mMessageView; - private ImageView mIconView; - private TextView mCurrentGroupView; - private Button mAllowButton; - private Button mSoftDenyButton; - private Button mHardDenyButton; - - GrantPermissionsTvViewHandler(Context context) { - mContext = context; - } - - @Override - public GrantPermissionsTvViewHandler setResultListener(ResultListener listener) { - mResultListener = listener; - return this; - } - - @Override - public View createView() { - mRootView = (LinearLayout) LayoutInflater.from(mContext) - .inflate(R.layout.grant_permissions, null); - - mMessageView = (TextView) mRootView.findViewById(R.id.permission_message); - mIconView = (ImageView) mRootView.findViewById(R.id.permission_icon); - mCurrentGroupView = (TextView) mRootView.findViewById(R.id.current_page_text); - mAllowButton = (Button) mRootView.findViewById(R.id.permission_allow_button); - mSoftDenyButton = (Button) mRootView.findViewById(R.id.permission_deny_button); - mHardDenyButton = (Button) mRootView.findViewById( - R.id.permission_deny_dont_ask_again_button); - - mAllowButton.setOnClickListener(this); - mSoftDenyButton.setOnClickListener(this); - mHardDenyButton.setOnClickListener(this); - - return mRootView; - } - - @Override - public void updateWindowAttributes(WindowManager.LayoutParams outLayoutParams) { - outLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT; - outLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT; - outLayoutParams.format = PixelFormat.OPAQUE; - outLayoutParams.gravity = Gravity.BOTTOM; - outLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; - outLayoutParams.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; - } - - @Override - public void updateUi(String groupName, int groupCount, int groupIndex, Icon icon, - CharSequence message, boolean showDoNotAsk) { - mGroupName = groupName; - - mMessageView.setText(message); - mIconView.setImageIcon(icon); - mHardDenyButton.setVisibility(showDoNotAsk ? View.VISIBLE : View.GONE); - if (groupCount > 1) { - mCurrentGroupView.setVisibility(View.VISIBLE); - mCurrentGroupView.setText(mContext.getString(R.string.current_permission_template, - groupIndex + 1, groupCount)); - } else { - mCurrentGroupView.setVisibility(View.INVISIBLE); - } - } - - @Override - public void saveInstanceState(Bundle outState) { - outState.putString(ARG_GROUP_NAME, mGroupName); - } - - @Override - public void loadInstanceState(Bundle savedInstanceState) { - mGroupName = savedInstanceState.getString(ARG_GROUP_NAME); - } - - @Override - public void onClick(View view) { - boolean granted = false; - boolean doNotAskAgain = false; - switch (view.getId()) { - case R.id.permission_allow_button: - granted = true; - break; - case R.id.permission_deny_dont_ask_again_button: - doNotAskAgain = true; - break; - } - if (mResultListener != null) { - mResultListener.onPermissionGrantResult(mGroupName, granted, doNotAskAgain); - } - } - - @Override - public void onBackPressed() { - if (mResultListener != null) { - mResultListener.onPermissionGrantResult(mGroupName, false, false); - } - } -} diff --git a/src/com/android/packageinstaller/permission/ui/GrantPermissionsViewHandler.java b/src/com/android/packageinstaller/permission/ui/GrantPermissionsViewHandler.java index 4032abb2..5e2259af 100644 --- a/src/com/android/packageinstaller/permission/ui/GrantPermissionsViewHandler.java +++ b/src/com/android/packageinstaller/permission/ui/GrantPermissionsViewHandler.java @@ -25,7 +25,7 @@ import android.view.WindowManager; * Class for managing the presentation and user interaction of the "grant * permissions" user interface. */ -interface GrantPermissionsViewHandler { +public interface GrantPermissionsViewHandler { /** * Listener interface for getting notified when the user responds to a diff --git a/src/com/android/packageinstaller/permission/ui/ManagePermissionsActivity.java b/src/com/android/packageinstaller/permission/ui/ManagePermissionsActivity.java index f7fcec5e..419dbf42 100644 --- a/src/com/android/packageinstaller/permission/ui/ManagePermissionsActivity.java +++ b/src/com/android/packageinstaller/permission/ui/ManagePermissionsActivity.java @@ -20,6 +20,7 @@ import android.app.Fragment; import android.content.Intent; import android.os.Bundle; import android.util.Log; +import com.android.packageinstaller.permission.utils.Utils; import com.android.packageinstaller.permission.ui.wear.AppPermissionsFragmentWear; import com.android.packageinstaller.DeviceUtils; @@ -40,7 +41,13 @@ public final class ManagePermissionsActivity extends OverlayTouchActivity { switch (action) { case Intent.ACTION_MANAGE_PERMISSIONS: { - fragment = ManagePermissionsFragment.newInstance(); + if (Utils.isTelevision(this)) { + fragment = com.android.packageinstaller.permission.ui.television + .ManagePermissionsFragment.newInstance(); + } else { + fragment = com.android.packageinstaller.permission.ui.handheld + .ManagePermissionsFragment.newInstance(); + } } break; case Intent.ACTION_MANAGE_APP_PERMISSIONS: { @@ -50,11 +57,14 @@ public final class ManagePermissionsActivity extends OverlayTouchActivity { finish(); return; } - if (DeviceUtils.isWear(this)) { fragment = AppPermissionsFragmentWear.newInstance(packageName); + } else if (Utils.isTelevision(this)) { + fragment = com.android.packageinstaller.permission.ui.television + .AppPermissionsFragment.newInstance(packageName); } else { - fragment = AppPermissionsFragment.newInstance(packageName); + fragment = com.android.packageinstaller.permission.ui.handheld + .AppPermissionsFragment.newInstance(packageName); } } break; @@ -65,7 +75,13 @@ public final class ManagePermissionsActivity extends OverlayTouchActivity { finish(); return; } - fragment = PermissionAppsFragment.newInstance(permissionName); + if (Utils.isTelevision(this)) { + fragment = com.android.packageinstaller.permission.ui.television + .PermissionAppsFragment.newInstance(permissionName); + } else { + fragment = com.android.packageinstaller.permission.ui.handheld + .PermissionAppsFragment.newInstance(permissionName); + } } break; default: { diff --git a/src/com/android/packageinstaller/permission/ui/ManagePermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/ManagePermissionsFragment.java deleted file mode 100644 index e5e06e09..00000000 --- a/src/com/android/packageinstaller/permission/ui/ManagePermissionsFragment.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.packageinstaller.permission.ui; - -import android.annotation.Nullable; -import android.app.ActionBar; -import android.app.FragmentTransaction; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.support.v7.preference.Preference; -import android.support.v7.preference.Preference.OnPreferenceClickListener; -import android.support.v7.preference.PreferenceScreen; -import android.util.ArraySet; -import android.util.Log; -import android.view.MenuItem; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import com.android.packageinstaller.R; -import com.android.packageinstaller.permission.model.PermissionApps; -import com.android.packageinstaller.permission.model.PermissionApps.PmCache; -import com.android.packageinstaller.permission.model.PermissionGroup; -import com.android.packageinstaller.permission.model.PermissionGroups; -import com.android.packageinstaller.permission.utils.Utils; - -import java.util.List; - -public final class ManagePermissionsFragment extends PermissionsFrameFragment - implements PermissionGroups.PermissionsGroupsChangeCallback, OnPreferenceClickListener { - private static final String LOG_TAG = "ManagePermissionsFragment"; - - private static final String OS_PKG = "android"; - - private static final String EXTRA_PREFS_KEY = "extra_prefs_key"; - - private ArraySet mLauncherPkgs; - - private PermissionGroups mPermissions; - - private PreferenceScreen mExtraScreen; - - public static ManagePermissionsFragment newInstance() { - return new ManagePermissionsFragment(); - } - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - setLoading(true /* loading */, false /* animate */); - setHasOptionsMenu(true); - final ActionBar ab = getActivity().getActionBar(); - if (ab != null) { - ab.setDisplayHomeAsUpEnabled(true); - } - mLauncherPkgs = Utils.getLauncherPackages(getContext()); - mPermissions = new PermissionGroups(getActivity(), getLoaderManager(), this); - } - - @Override - public void onResume() { - super.onResume(); - mPermissions.refresh(); - updatePermissionsUi(); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - getActivity().finish(); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - public boolean onPreferenceClick(Preference preference) { - String key = preference.getKey(); - - PermissionGroup group = mPermissions.getGroup(key); - if (group == null) { - return false; - } - - Intent intent = new Intent(Intent.ACTION_MANAGE_PERMISSION_APPS) - .putExtra(Intent.EXTRA_PERMISSION_NAME, key); - try { - getActivity().startActivity(intent); - } catch (ActivityNotFoundException e) { - Log.w(LOG_TAG, "No app to handle " + intent); - } - - return true; - } - - @Override - public void onPermissionGroupsChanged() { - updatePermissionsUi(); - } - - @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - bindPermissionUi(getActivity(), getView()); - } - - private static void bindPermissionUi(@Nullable Context context, @Nullable View rootView) { - if (context == null || rootView == null) { - return; - } - - ImageView iconView = (ImageView) rootView.findViewById(R.id.lb_icon); - if (iconView != null) { - // Set the icon as the background instead of the image because ImageView - // doesn't properly scale vector drawables beyond their intrinsic size - Drawable icon = context.getDrawable(R.drawable.ic_lock); - icon.setTint(context.getColor(R.color.off_white)); - iconView.setBackground(icon); - } - TextView titleView = (TextView) rootView.findViewById(R.id.lb_title); - if (titleView != null) { - titleView.setText(R.string.app_permissions); - } - TextView breadcrumbView = (TextView) rootView.findViewById(R.id.lb_breadcrumb); - if (breadcrumbView != null) { - breadcrumbView.setText(R.string.app_permissions_breadcrumb); - } - } - - private void updatePermissionsUi() { - Context context = getPreferenceManager().getContext(); - if (context == null) { - return; - } - - List groups = mPermissions.getGroups(); - PreferenceScreen screen = getPreferenceScreen(); - - // Use this to speed up getting the info for all of the PermissionApps below. - // Create a new one for each refresh to make sure it has fresh data. - PmCache cache = new PmCache(getContext().getPackageManager()); - for (PermissionGroup group : groups) { - boolean isSystemPermission = group.getDeclaringPackage().equals(OS_PKG); - - Preference preference = findPreference(group.getName()); - if (preference == null && mExtraScreen != null) { - preference = mExtraScreen.findPreference(group.getName()); - } - if (preference == null) { - preference = new Preference(context); - preference.setOnPreferenceClickListener(this); - preference.setKey(group.getName()); - preference.setIcon(Utils.applyTint(context, group.getIcon(), - android.R.attr.colorControlNormal)); - preference.setTitle(group.getLabel()); - // Set blank summary so that no resizing/jumping happens when the summary is loaded. - preference.setSummary(" "); - preference.setPersistent(false); - if (isSystemPermission) { - screen.addPreference(preference); - } else { - if (mExtraScreen == null) { - mExtraScreen = getPreferenceManager().createPreferenceScreen(context); - } - mExtraScreen.addPreference(preference); - } - } - final Preference finalPref = preference; - - new PermissionApps(getContext(), group.getName(), new PermissionApps.Callback() { - @Override - public void onPermissionsLoaded(PermissionApps permissionApps) { - if (getActivity() == null) { - return; - } - int granted = permissionApps.getGrantedCount(mLauncherPkgs); - int total = permissionApps.getTotalCount(mLauncherPkgs); - finalPref.setSummary(getString(R.string.app_permissions_group_summary, - granted, total)); - } - }, cache).refresh(false); - } - - if (mExtraScreen != null && mExtraScreen.getPreferenceCount() > 0 - && screen.findPreference(EXTRA_PREFS_KEY) == null) { - Preference extraScreenPreference = new Preference(context); - extraScreenPreference.setKey(EXTRA_PREFS_KEY); - extraScreenPreference.setIcon(Utils.applyTint(context, - com.android.internal.R.drawable.ic_more_items, - android.R.attr.colorControlNormal)); - extraScreenPreference.setTitle(R.string.additional_permissions); - extraScreenPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - AdditionalPermissionsFragment frag = new AdditionalPermissionsFragment(); - frag.setTargetFragment(ManagePermissionsFragment.this, 0); - FragmentTransaction ft = getFragmentManager().beginTransaction(); - ft.replace(android.R.id.content, frag); - ft.addToBackStack(null); - ft.commit(); - return true; - } - }); - int count = mExtraScreen.getPreferenceCount(); - extraScreenPreference.setSummary(getResources().getQuantityString( - R.plurals.additional_permissions_more, count, count)); - screen.addPreference(extraScreenPreference); - } - if (screen.getPreferenceCount() != 0) { - setLoading(false /* loading */, true /* animate */); - } - } - - public static class AdditionalPermissionsFragment extends PermissionsFrameFragment { - @Override - public void onCreate(Bundle icicle) { - setLoading(true /* loading */, false /* animate */); - super.onCreate(icicle); - getActivity().setTitle(R.string.additional_permissions); - setHasOptionsMenu(true); - } - - @Override - public void onDestroy() { - getActivity().setTitle(R.string.app_permissions); - super.onDestroy(); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - getFragmentManager().popBackStack(); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - bindPermissionUi(getActivity(), getView()); - } - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - setPreferenceScreen(((ManagePermissionsFragment) getTargetFragment()).mExtraScreen); - setLoading(false /* loading */, true /* animate */); - } - } -} diff --git a/src/com/android/packageinstaller/permission/ui/OverlayWarningDialog.java b/src/com/android/packageinstaller/permission/ui/OverlayWarningDialog.java index a7c1e2a1..61734b47 100644 --- a/src/com/android/packageinstaller/permission/ui/OverlayWarningDialog.java +++ b/src/com/android/packageinstaller/permission/ui/OverlayWarningDialog.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.android.packageinstaller.permission.ui; import android.app.Activity; diff --git a/src/com/android/packageinstaller/permission/ui/PermissionAppsFragment.java b/src/com/android/packageinstaller/permission/ui/PermissionAppsFragment.java deleted file mode 100644 index 1e588939..00000000 --- a/src/com/android/packageinstaller/permission/ui/PermissionAppsFragment.java +++ /dev/null @@ -1,434 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.packageinstaller.permission.ui; - -import android.app.ActionBar; -import android.app.AlertDialog; -import android.app.Fragment; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.support.v14.preference.SwitchPreference; -import android.support.v4.util.ArrayMap; -import android.support.v7.preference.Preference; -import android.support.v7.preference.Preference.OnPreferenceChangeListener; -import android.support.v7.preference.Preference.OnPreferenceClickListener; -import android.support.v7.preference.PreferenceScreen; -import android.util.ArraySet; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import com.android.packageinstaller.DeviceUtils; -import com.android.packageinstaller.R; -import com.android.packageinstaller.permission.model.AppPermissionGroup; -import com.android.packageinstaller.permission.model.PermissionApps; -import com.android.packageinstaller.permission.model.PermissionApps.Callback; -import com.android.packageinstaller.permission.model.PermissionApps.PermissionApp; -import com.android.packageinstaller.permission.utils.LocationUtils; -import com.android.packageinstaller.permission.utils.SafetyNetLogger; -import com.android.packageinstaller.permission.utils.Utils; - -import java.util.ArrayList; -import java.util.List; - -public final class PermissionAppsFragment extends PermissionsFrameFragment implements Callback, - OnPreferenceChangeListener { - - private static final int MENU_SHOW_SYSTEM = Menu.FIRST; - private static final int MENU_HIDE_SYSTEM = Menu.FIRST + 1; - private static final String KEY_SHOW_SYSTEM_PREFS = "_showSystem"; - - public static PermissionAppsFragment newInstance(String permissionName) { - return setPermissionName(new PermissionAppsFragment(), permissionName); - } - - private static T setPermissionName(T fragment, String permissionName) { - Bundle arguments = new Bundle(); - arguments.putString(Intent.EXTRA_PERMISSION_NAME, permissionName); - fragment.setArguments(arguments); - return fragment; - } - - private PermissionApps mPermissionApps; - - private PreferenceScreen mExtraScreen; - - private ArrayMap mToggledGroups; - private ArraySet mLauncherPkgs; - private boolean mHasConfirmedRevoke; - - private boolean mShowSystem; - private MenuItem mShowSystemMenu; - private MenuItem mHideSystemMenu; - - private Callback mOnPermissionsLoadedListener; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setLoading(true /* loading */, false /* animate */); - setHasOptionsMenu(true); - final ActionBar ab = getActivity().getActionBar(); - if (ab != null) { - ab.setDisplayHomeAsUpEnabled(true); - } - mLauncherPkgs = Utils.getLauncherPackages(getContext()); - - String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME); - mPermissionApps = new PermissionApps(getActivity(), groupName, this); - mPermissionApps.refresh(true); - } - - @Override - public void onResume() { - super.onResume(); - mPermissionApps.refresh(true); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - mShowSystemMenu = menu.add(Menu.NONE, MENU_SHOW_SYSTEM, Menu.NONE, - R.string.menu_show_system); - mHideSystemMenu = menu.add(Menu.NONE, MENU_HIDE_SYSTEM, Menu.NONE, - R.string.menu_hide_system); - updateMenu(); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - getActivity().finish(); - return true; - case MENU_SHOW_SYSTEM: - case MENU_HIDE_SYSTEM: - mShowSystem = item.getItemId() == MENU_SHOW_SYSTEM; - if (mPermissionApps.getApps() != null) { - onPermissionsLoaded(mPermissionApps); - } - updateMenu(); - break; - } - return super.onOptionsItemSelected(item); - } - - private void updateMenu() { - mShowSystemMenu.setVisible(!mShowSystem); - mHideSystemMenu.setVisible(mShowSystem); - } - - @Override - protected void onSetEmptyText(TextView textView) { - textView.setText(R.string.no_apps); - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - bindUi(this, mPermissionApps); - } - - private static void bindUi(Fragment fragment, PermissionApps permissionApps) { - final Drawable icon = permissionApps.getIcon(); - final CharSequence label = permissionApps.getLabel(); - final ActionBar ab = fragment.getActivity().getActionBar(); - if (ab != null) { - ab.setTitle(fragment.getString(R.string.permission_title, label)); - } - - final ViewGroup rootView = (ViewGroup) fragment.getView(); - final ImageView iconView = (ImageView) rootView.findViewById(R.id.lb_icon); - if (iconView != null) { - // Set the icon as the background instead of the image because ImageView - // doesn't properly scale vector drawables beyond their intrinsic size - iconView.setBackground(icon); - } - final TextView titleView = (TextView) rootView.findViewById(R.id.lb_title); - if (titleView != null) { - titleView.setText(label); - } - final TextView breadcrumbView = (TextView) rootView.findViewById(R.id.lb_breadcrumb); - if (breadcrumbView != null) { - breadcrumbView.setText(R.string.app_permissions); - } - } - - private void setOnPermissionsLoadedListener(Callback callback) { - mOnPermissionsLoadedListener = callback; - } - - @Override - public void onPermissionsLoaded(PermissionApps permissionApps) { - Context context = getPreferenceManager().getContext(); - - if (context == null) { - return; - } - - boolean isTelevision = DeviceUtils.isTelevision(context); - PreferenceScreen screen = getPreferenceScreen(); - - ArraySet preferencesToRemove = new ArraySet<>(); - for (int i = 0, n = screen.getPreferenceCount(); i < n; i++) { - preferencesToRemove.add(screen.getPreference(i).getKey()); - } - if (mExtraScreen != null) { - for (int i = 0, n = mExtraScreen.getPreferenceCount(); i < n; i++) { - preferencesToRemove.add(mExtraScreen.getPreference(i).getKey()); - } - } - - for (PermissionApp app : permissionApps.getApps()) { - if (!Utils.shouldShowPermission(app)) { - continue; - } - - String key = app.getKey(); - preferencesToRemove.remove(key); - Preference existingPref = screen.findPreference(key); - if (existingPref == null && mExtraScreen != null) { - existingPref = mExtraScreen.findPreference(key); - } - - boolean isSystemApp = Utils.isSystem(app, mLauncherPkgs); - if (isSystemApp && !isTelevision && !mShowSystem) { - if (existingPref != null) { - screen.removePreference(existingPref); - } - continue; - } - - if (existingPref != null) { - // If existing preference - only update its state. - if (app.isPolicyFixed()) { - existingPref.setSummary(getString( - R.string.permission_summary_enforced_by_policy)); - } - existingPref.setPersistent(false); - existingPref.setEnabled(!app.isPolicyFixed()); - if (existingPref instanceof SwitchPreference) { - ((SwitchPreference) existingPref) - .setChecked(app.areRuntimePermissionsGranted()); - } - continue; - } - - SwitchPreference pref = new SwitchPreference(context); - pref.setOnPreferenceChangeListener(this); - pref.setKey(app.getKey()); - pref.setIcon(app.getIcon()); - pref.setTitle(app.getLabel()); - if (app.isPolicyFixed()) { - pref.setSummary(getString(R.string.permission_summary_enforced_by_policy)); - } - pref.setPersistent(false); - pref.setEnabled(!app.isPolicyFixed()); - pref.setChecked(app.areRuntimePermissionsGranted()); - - if (isSystemApp && isTelevision) { - if (mExtraScreen == null) { - mExtraScreen = getPreferenceManager().createPreferenceScreen(context); - } - mExtraScreen.addPreference(pref); - } else { - screen.addPreference(pref); - } - } - - if (mExtraScreen != null) { - preferencesToRemove.remove(KEY_SHOW_SYSTEM_PREFS); - Preference pref = screen.findPreference(KEY_SHOW_SYSTEM_PREFS); - - if (pref == null) { - pref = new Preference(context); - pref.setKey(KEY_SHOW_SYSTEM_PREFS); - pref.setIcon(Utils.applyTint(context, R.drawable.ic_toc, - android.R.attr.colorControlNormal)); - pref.setTitle(R.string.preference_show_system_apps); - pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - SystemAppsFragment frag = new SystemAppsFragment(); - setPermissionName(frag, getArguments().getString(Intent.EXTRA_PERMISSION_NAME)); - frag.setTargetFragment(PermissionAppsFragment.this, 0); - getFragmentManager().beginTransaction() - .replace(android.R.id.content, frag) - .addToBackStack("SystemApps") - .commit(); - return true; - } - }); - screen.addPreference(pref); - } - - int grantedCount = 0; - for (int i = 0, n = mExtraScreen.getPreferenceCount(); i < n; i++) { - if (((SwitchPreference) mExtraScreen.getPreference(i)).isChecked()) { - grantedCount++; - } - } - pref.setSummary(getString(R.string.app_permissions_group_summary, - grantedCount, mExtraScreen.getPreferenceCount())); - } - - for (String key : preferencesToRemove) { - Preference pref = screen.findPreference(key); - if (pref != null) { - screen.removePreference(pref); - } else if (mExtraScreen != null) { - pref = mExtraScreen.findPreference(key); - if (pref != null) { - mExtraScreen.removePreference(pref); - } - } - } - - setLoading(false /* loading */, true /* animate */); - - if (mOnPermissionsLoadedListener != null) { - mOnPermissionsLoadedListener.onPermissionsLoaded(permissionApps); - } - } - - @Override - public boolean onPreferenceChange(final Preference preference, Object newValue) { - String pkg = preference.getKey(); - final PermissionApp app = mPermissionApps.getApp(pkg); - - if (app == null) { - return false; - } - - OverlayTouchActivity activity = (OverlayTouchActivity) getActivity(); - if (activity.isObscuredTouch()) { - activity.showOverlayDialog(); - return false; - } - - addToggledGroup(app.getPackageName(), app.getPermissionGroup()); - - if (LocationUtils.isLocationGroupAndProvider(mPermissionApps.getGroupName(), - app.getPackageName())) { - LocationUtils.showLocationDialog(getContext(), app.getLabel()); - return false; - } - if (newValue == Boolean.TRUE) { - app.grantRuntimePermissions(); - } else { - final boolean grantedByDefault = app.hasGrantedByDefaultPermissions(); - if (grantedByDefault || (!app.hasRuntimePermissions() && !mHasConfirmedRevoke)) { - new AlertDialog.Builder(getContext()) - .setMessage(grantedByDefault ? R.string.system_warning - : R.string.old_sdk_deny_warning) - .setNegativeButton(R.string.cancel, null) - .setPositiveButton(R.string.grant_dialog_button_deny, - new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - ((SwitchPreference) preference).setChecked(false); - app.revokeRuntimePermissions(); - if (!grantedByDefault) { - mHasConfirmedRevoke = true; - } - } - }) - .show(); - return false; - } else { - app.revokeRuntimePermissions(); - } - } - return true; - } - - @Override - public void onPause() { - super.onPause(); - logToggledGroups(); - } - - private void addToggledGroup(String packageName, AppPermissionGroup group) { - if (mToggledGroups == null) { - mToggledGroups = new ArrayMap<>(); - } - // Double toggle is back to initial state. - if (mToggledGroups.containsKey(packageName)) { - mToggledGroups.remove(packageName); - } else { - mToggledGroups.put(packageName, group); - } - } - - private void logToggledGroups() { - if (mToggledGroups != null) { - final int groupCount = mToggledGroups.size(); - for (int i = 0; i < groupCount; i++) { - String packageName = mToggledGroups.keyAt(i); - List groups = new ArrayList<>(); - groups.add(mToggledGroups.valueAt(i)); - SafetyNetLogger.logPermissionsToggled(packageName, groups); - } - mToggledGroups = null; - } - } - - public static class SystemAppsFragment extends PermissionsFrameFragment implements Callback { - PermissionAppsFragment mOuterFragment; - - @Override - public void onCreate(Bundle savedInstanceState) { - mOuterFragment = (PermissionAppsFragment) getTargetFragment(); - setLoading(true /* loading */, false /* animate */); - super.onCreate(savedInstanceState); - } - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - if (mOuterFragment.mExtraScreen != null) { - setPreferenceScreen(); - } else { - mOuterFragment.setOnPermissionsLoadedListener(this); - } - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME); - PermissionApps permissionApps = new PermissionApps(getActivity(), groupName, null); - bindUi(this, permissionApps); - } - - @Override - public void onPermissionsLoaded(PermissionApps permissionApps) { - setPreferenceScreen(); - mOuterFragment.setOnPermissionsLoadedListener(null); - } - - private void setPreferenceScreen() { - setPreferenceScreen(mOuterFragment.mExtraScreen); - setLoading(false /* loading */, true /* animate */); - } - } -} diff --git a/src/com/android/packageinstaller/permission/ui/PreferenceImageView.java b/src/com/android/packageinstaller/permission/ui/PreferenceImageView.java new file mode 100644 index 00000000..c3f51674 --- /dev/null +++ b/src/com/android/packageinstaller/permission/ui/PreferenceImageView.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.permission.ui; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.ImageView; + +/** + * Extension of ImageView that correctly applies maxWidth and maxHeight. + */ +public class PreferenceImageView extends ImageView { + + public PreferenceImageView(Context context) { + this(context, null); + } + + public PreferenceImageView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PreferenceImageView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public PreferenceImageView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) { + final int widthSize = MeasureSpec.getSize(widthMeasureSpec); + final int maxWidth = getMaxWidth(); + if (maxWidth != Integer.MAX_VALUE + && (maxWidth < widthSize || widthMode == MeasureSpec.UNSPECIFIED)) { + widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST); + } + } + + final int heightMode = MeasureSpec.getMode(heightMeasureSpec); + if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) { + final int heightSize = MeasureSpec.getSize(heightMeasureSpec); + final int maxHeight = getMaxHeight(); + if (maxHeight != Integer.MAX_VALUE + && (maxHeight < heightSize || heightMode == MeasureSpec.UNSPECIFIED)) { + heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST); + } + } + + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } +} diff --git a/src/com/android/packageinstaller/permission/ui/handheld/AllAppPermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/handheld/AllAppPermissionsFragment.java new file mode 100644 index 00000000..b3b0895c --- /dev/null +++ b/src/com/android/packageinstaller/permission/ui/handheld/AllAppPermissionsFragment.java @@ -0,0 +1,214 @@ +/* +* Copyright (C) 2015 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package com.android.packageinstaller.permission.ui.handheld; + +import android.app.ActionBar; +import android.app.AlertDialog; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PermissionGroupInfo; +import android.content.pm.PermissionInfo; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceClickListener; +import android.preference.PreferenceCategory; +import android.preference.PreferenceGroup; +import android.provider.Settings; +import android.util.Log; +import android.view.MenuItem; +import com.android.packageinstaller.R; +import com.android.packageinstaller.permission.utils.Utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +public final class AllAppPermissionsFragment extends SettingsWithHeader { + + private static final String LOG_TAG = "AllAppPermissionsFragment"; + + private static final String KEY_OTHER = "other_perms"; + + public static AllAppPermissionsFragment newInstance(String packageName) { + AllAppPermissionsFragment instance = new AllAppPermissionsFragment(); + Bundle arguments = new Bundle(); + arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName); + instance.setArguments(arguments); + return instance; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + final ActionBar ab = getActivity().getActionBar(); + if (ab != null) { + ab.setTitle(R.string.all_permissions); + ab.setDisplayHomeAsUpEnabled(true); + } + } + + @Override + public void onResume() { + super.onResume(); + updateUi(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: { + getFragmentManager().popBackStack(); + return true; + } + } + return super.onOptionsItemSelected(item); + } + + private void updateUi() { + if (getPreferenceScreen() != null) { + getPreferenceScreen().removeAll(); + } + addPreferencesFromResource(R.xml.all_permissions); + PreferenceGroup otherGroup = (PreferenceGroup) findPreference(KEY_OTHER); + ArrayList prefs = new ArrayList<>(); // Used for sorting. + prefs.add(otherGroup); + String pkg = getArguments().getString(Intent.EXTRA_PACKAGE_NAME); + otherGroup.removeAll(); + PackageManager pm = getContext().getPackageManager(); + + try { + PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_PERMISSIONS); + + ApplicationInfo appInfo = info.applicationInfo; + final Drawable icon = appInfo.loadIcon(pm); + final CharSequence label = appInfo.loadLabel(pm); + Intent infoIntent = null; + if (!getActivity().getIntent().getBooleanExtra( + AppPermissionsFragment.EXTRA_HIDE_INFO_BUTTON, false)) { + infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + .setData(Uri.fromParts("package", pkg, null)); + } + setHeader(icon, label, infoIntent); + + if (info.requestedPermissions != null) { + for (int i = 0; i < info.requestedPermissions.length; i++) { + PermissionInfo perm; + try { + perm = pm.getPermissionInfo(info.requestedPermissions[i], 0); + } catch (NameNotFoundException e) { + Log.e(LOG_TAG, + "Can't get permission info for " + info.requestedPermissions[i], e); + continue; + } + + if ((perm.flags & PermissionInfo.FLAG_INSTALLED) == 0 + || (perm.flags & PermissionInfo.FLAG_HIDDEN) != 0) { + continue; + } + + if (perm.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) { + PermissionGroupInfo group = getGroup(perm.group, pm); + PreferenceGroup pref = + findOrCreate(group != null ? group : perm, pm, prefs); + pref.addPreference(getPreference(perm, group, pm)); + } else if (perm.protectionLevel == PermissionInfo.PROTECTION_NORMAL) { + PermissionGroupInfo group = getGroup(perm.group, pm); + otherGroup.addPreference(getPreference(perm, group, pm)); + } + } + } + } catch (NameNotFoundException e) { + Log.e(LOG_TAG, "Problem getting package info for " + pkg, e); + } + // Sort an ArrayList of the groups and then set the order from the sorting. + Collections.sort(prefs, new Comparator() { + @Override + public int compare(Preference lhs, Preference rhs) { + String lKey = lhs.getKey(); + String rKey = rhs.getKey(); + if (lKey.equals(KEY_OTHER)) { + return 1; + } else if (rKey.equals(KEY_OTHER)) { + return -1; + } else if (Utils.isModernPermissionGroup(lKey) + != Utils.isModernPermissionGroup(rKey)) { + return Utils.isModernPermissionGroup(lKey) ? -1 : 1; + } + return lhs.getTitle().toString().compareTo(rhs.getTitle().toString()); + } + }); + for (int i = 0; i < prefs.size(); i++) { + prefs.get(i).setOrder(i); + } + } + + private PermissionGroupInfo getGroup(String group, PackageManager pm) { + try { + return pm.getPermissionGroupInfo(group, 0); + } catch (NameNotFoundException e) { + return null; + } + } + + private PreferenceGroup findOrCreate(PackageItemInfo group, PackageManager pm, + ArrayList prefs) { + PreferenceGroup pref = (PreferenceGroup) findPreference(group.name); + if (pref == null) { + pref = new PreferenceCategory(getContext()); + pref.setKey(group.name); + pref.setTitle(group.loadLabel(pm)); + prefs.add(pref); + getPreferenceScreen().addPreference(pref); + } + return pref; + } + + private Preference getPreference(PermissionInfo perm, PermissionGroupInfo group, + PackageManager pm) { + Preference pref = new Preference(getContext()); + Drawable icon = null; + if (perm.icon != 0) { + icon = perm.loadIcon(pm); + } else if (group != null && group.icon != 0) { + icon = group.loadIcon(pm); + } else { + icon = getContext().getDrawable(R.drawable.ic_perm_device_info); + } + pref.setIcon(Utils.applyTint(getContext(), icon, android.R.attr.colorControlNormal)); + pref.setTitle(perm.loadLabel(pm)); + final CharSequence desc = perm.loadDescription(pm); + pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + new AlertDialog.Builder(getContext()) + .setMessage(desc) + .setPositiveButton(android.R.string.ok, null) + .show(); + return true; + } + }); + + return pref; + } +} \ No newline at end of file diff --git a/src/com/android/packageinstaller/permission/ui/handheld/AppPermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/handheld/AppPermissionsFragment.java new file mode 100644 index 00000000..f56cba70 --- /dev/null +++ b/src/com/android/packageinstaller/permission/ui/handheld/AppPermissionsFragment.java @@ -0,0 +1,404 @@ +/* +* Copyright (C) 2015 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package com.android.packageinstaller.permission.ui.handheld; + +import android.annotation.Nullable; +import android.app.ActionBar; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Fragment; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.preference.Preference.OnPreferenceClickListener; +import android.preference.PreferenceScreen; +import android.preference.SwitchPreference; +import android.provider.Settings; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; +import com.android.packageinstaller.R; +import com.android.packageinstaller.permission.model.AppPermissionGroup; +import com.android.packageinstaller.permission.model.AppPermissions; +import com.android.packageinstaller.permission.ui.OverlayTouchActivity; +import com.android.packageinstaller.permission.utils.LocationUtils; +import com.android.packageinstaller.permission.utils.SafetyNetLogger; +import com.android.packageinstaller.permission.utils.Utils; + +import java.util.ArrayList; +import java.util.List; + +public final class AppPermissionsFragment extends SettingsWithHeader + implements OnPreferenceChangeListener { + + private static final String LOG_TAG = "ManagePermsFragment"; + + static final String EXTRA_HIDE_INFO_BUTTON = "hideInfoButton"; + + private static final int MENU_ALL_PERMS = 0; + + private List mToggledGroups; + private AppPermissions mAppPermissions; + private PreferenceScreen mExtraScreen; + + private boolean mHasConfirmedRevoke; + + public static AppPermissionsFragment newInstance(String packageName) { + return setPackageName(new AppPermissionsFragment(), packageName); + } + + private static T setPackageName(T fragment, String packageName) { + Bundle arguments = new Bundle(); + arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName); + fragment.setArguments(arguments); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setLoading(true /* loading */, false /* animate */); + setHasOptionsMenu(true); + final ActionBar ab = getActivity().getActionBar(); + if (ab != null) { + ab.setDisplayHomeAsUpEnabled(true); + } + + String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME); + Activity activity = getActivity(); + PackageInfo packageInfo = getPackageInfo(activity, packageName); + if (packageInfo == null) { + Toast.makeText(activity, R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show(); + activity.finish(); + return; + } + + mAppPermissions = new AppPermissions(activity, packageInfo, null, true, new Runnable() { + @Override + public void run() { + getActivity().finish(); + } + }); + loadPreferences(); + } + + @Override + public void onResume() { + super.onResume(); + mAppPermissions.refresh(); + setPreferencesCheckedState(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: { + getActivity().finish(); + return true; + } + + case MENU_ALL_PERMS: { + Fragment frag = AllAppPermissionsFragment.newInstance( + getArguments().getString(Intent.EXTRA_PACKAGE_NAME)); + getFragmentManager().beginTransaction() + .replace(android.R.id.content, frag) + .addToBackStack("AllPerms") + .commit(); + return true; + } + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + if (mAppPermissions != null) { + bindUi(this, mAppPermissions.getPackageInfo()); + } + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + menu.add(Menu.NONE, MENU_ALL_PERMS, Menu.NONE, R.string.all_permissions); + } + + private static void bindUi(SettingsWithHeader fragment, PackageInfo packageInfo) { + Activity activity = fragment.getActivity(); + PackageManager pm = activity.getPackageManager(); + ApplicationInfo appInfo = packageInfo.applicationInfo; + Intent infoIntent = null; + if (!activity.getIntent().getBooleanExtra(EXTRA_HIDE_INFO_BUTTON, false)) { + infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + .setData(Uri.fromParts("package", packageInfo.packageName, null)); + } + + Drawable icon = appInfo.loadIcon(pm); + CharSequence label = appInfo.loadLabel(pm); + fragment.setHeader(icon, label, infoIntent); + + ActionBar ab = activity.getActionBar(); + if (ab != null) { + ab.setTitle(R.string.app_permissions); + } + + ViewGroup rootView = (ViewGroup) fragment.getView(); + ImageView iconView = (ImageView) rootView.findViewById(R.id.lb_icon); + if (iconView != null) { + iconView.setImageDrawable(icon); + } + TextView titleView = (TextView) rootView.findViewById(R.id.lb_title); + if (titleView != null) { + titleView.setText(R.string.app_permissions); + } + TextView breadcrumbView = (TextView) rootView.findViewById(R.id.lb_breadcrumb); + if (breadcrumbView != null) { + breadcrumbView.setText(label); + } + } + + private void loadPreferences() { + Context context = getActivity(); + if (context == null) { + return; + } + + PreferenceScreen screen = getPreferenceScreen(); + if (screen == null) { + screen = getPreferenceManager().createPreferenceScreen(getActivity()); + setPreferenceScreen(screen); + } + + screen.removeAll(); + + if (mExtraScreen != null) { + mExtraScreen.removeAll(); + } + + final Preference extraPerms = new Preference(context); + extraPerms.setIcon(R.drawable.ic_toc); + extraPerms.setTitle(R.string.additional_permissions); + + for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) { + if (!Utils.shouldShowPermission(group, mAppPermissions.getPackageInfo().packageName)) { + continue; + } + + boolean isPlatform = group.getDeclaringPackage().equals(Utils.OS_PKG); + + SwitchPreference preference = new SwitchPreference(context); + preference.setOnPreferenceChangeListener(this); + preference.setKey(group.getName()); + Drawable icon = Utils.loadDrawable(context.getPackageManager(), + group.getIconPkg(), group.getIconResId()); + preference.setIcon(Utils.applyTint(getContext(), icon, + android.R.attr.colorControlNormal)); + preference.setTitle(group.getLabel()); + if (group.isPolicyFixed()) { + preference.setSummary(getString(R.string.permission_summary_enforced_by_policy)); + } + preference.setPersistent(false); + preference.setEnabled(!group.isPolicyFixed()); + preference.setChecked(group.areRuntimePermissionsGranted()); + + if (isPlatform) { + screen.addPreference(preference); + } else { + if (mExtraScreen == null) { + mExtraScreen = getPreferenceManager().createPreferenceScreen(context); + } + mExtraScreen.addPreference(preference); + } + } + + if (mExtraScreen != null) { + extraPerms.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + AdditionalPermissionsFragment frag = new AdditionalPermissionsFragment(); + setPackageName(frag, getArguments().getString(Intent.EXTRA_PACKAGE_NAME)); + frag.setTargetFragment(AppPermissionsFragment.this, 0); + getFragmentManager().beginTransaction() + .replace(android.R.id.content, frag) + .addToBackStack(null) + .commit(); + return true; + } + }); + int count = mExtraScreen.getPreferenceCount(); + extraPerms.setSummary(getResources().getQuantityString( + R.plurals.additional_permissions_more, count, count)); + screen.addPreference(extraPerms); + } + + setLoading(false /* loading */, true /* animate */); + } + + @Override + public boolean onPreferenceChange(final Preference preference, Object newValue) { + String groupName = preference.getKey(); + final AppPermissionGroup group = mAppPermissions.getPermissionGroup(groupName); + + if (group == null) { + return false; + } + + OverlayTouchActivity activity = (OverlayTouchActivity) getActivity(); + if (activity.isObscuredTouch()) { + activity.showOverlayDialog(); + return false; + } + + addToggledGroup(group); + + if (LocationUtils.isLocationGroupAndProvider(group.getName(), group.getApp().packageName)) { + LocationUtils.showLocationDialog(getContext(), mAppPermissions.getAppLabel()); + return false; + } + if (newValue == Boolean.TRUE) { + group.grantRuntimePermissions(false); + } else { + final boolean grantedByDefault = group.hasGrantedByDefaultPermission(); + if (grantedByDefault || (!group.hasRuntimePermission() && !mHasConfirmedRevoke)) { + new AlertDialog.Builder(getContext()) + .setMessage(grantedByDefault ? R.string.system_warning + : R.string.old_sdk_deny_warning) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.grant_dialog_button_deny, + new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + ((SwitchPreference) preference).setChecked(false); + group.revokeRuntimePermissions(false); + if (!grantedByDefault) { + mHasConfirmedRevoke = true; + } + } + }) + .show(); + return false; + } else { + group.revokeRuntimePermissions(false); + } + } + + return true; + } + + @Override + public void onPause() { + super.onPause(); + logToggledGroups(); + } + + private void addToggledGroup(AppPermissionGroup group) { + if (mToggledGroups == null) { + mToggledGroups = new ArrayList<>(); + } + // Double toggle is back to initial state. + if (mToggledGroups.contains(group)) { + mToggledGroups.remove(group); + } else { + mToggledGroups.add(group); + } + } + + private void logToggledGroups() { + if (mToggledGroups != null) { + String packageName = mAppPermissions.getPackageInfo().packageName; + SafetyNetLogger.logPermissionsToggled(packageName, mToggledGroups); + mToggledGroups = null; + } + } + + private void setPreferencesCheckedState() { + setPreferencesCheckedState(getPreferenceScreen()); + if (mExtraScreen != null) { + setPreferencesCheckedState(mExtraScreen); + } + } + + private void setPreferencesCheckedState(PreferenceScreen screen) { + int preferenceCount = screen.getPreferenceCount(); + for (int i = 0; i < preferenceCount; i++) { + Preference preference = screen.getPreference(i); + if (preference instanceof SwitchPreference) { + SwitchPreference switchPref = (SwitchPreference) preference; + AppPermissionGroup group = mAppPermissions.getPermissionGroup(switchPref.getKey()); + if (group != null) { + switchPref.setChecked(group.areRuntimePermissionsGranted()); + } + } + } + } + + private static PackageInfo getPackageInfo(Activity activity, String packageName) { + try { + return activity.getPackageManager().getPackageInfo( + packageName, PackageManager.GET_PERMISSIONS); + } catch (PackageManager.NameNotFoundException e) { + Log.i(LOG_TAG, "No package:" + activity.getCallingPackage(), e); + return null; + } + } + + public static class AdditionalPermissionsFragment extends SettingsWithHeader { + AppPermissionsFragment mOuterFragment; + + @Override + public void onCreate(Bundle savedInstanceState) { + mOuterFragment = (AppPermissionsFragment) getTargetFragment(); + super.onCreate(savedInstanceState); + setHeader(mOuterFragment.mIcon, mOuterFragment.mLabel, mOuterFragment.mInfoIntent); + setHasOptionsMenu(true); + setPreferenceScreen(mOuterFragment.mExtraScreen); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME); + bindUi(this, getPackageInfo(getActivity(), packageName)); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + getFragmentManager().popBackStack(); + return true; + } + return super.onOptionsItemSelected(item); + } + } +} diff --git a/src/com/android/packageinstaller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.java b/src/com/android/packageinstaller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.java new file mode 100644 index 00000000..2d27f069 --- /dev/null +++ b/src/com/android/packageinstaller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.java @@ -0,0 +1,462 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.permission.ui.handheld; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Icon; +import android.os.Bundle; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnLayoutChangeListener; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.ViewRootImpl; +import android.view.WindowManager.LayoutParams; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.packageinstaller.R; +import com.android.packageinstaller.permission.ui.ButtonBarLayout; +import com.android.packageinstaller.permission.ui.GrantPermissionsViewHandler; +import com.android.packageinstaller.permission.ui.ManualLayoutFrame; + +import java.util.ArrayList; + +public final class GrantPermissionsViewHandlerImpl + implements GrantPermissionsViewHandler, OnClickListener { + + public static final String ARG_GROUP_NAME = "ARG_GROUP_NAME"; + public static final String ARG_GROUP_COUNT = "ARG_GROUP_COUNT"; + public static final String ARG_GROUP_INDEX = "ARG_GROUP_INDEX"; + public static final String ARG_GROUP_ICON = "ARG_GROUP_ICON"; + public static final String ARG_GROUP_MESSAGE = "ARG_GROUP_MESSAGE"; + public static final String ARG_GROUP_SHOW_DO_NOT_ASK = "ARG_GROUP_SHOW_DO_NOT_ASK"; + public static final String ARG_GROUP_DO_NOT_ASK_CHECKED = "ARG_GROUP_DO_NOT_ASK_CHECKED"; + + // Animation parameters. + private static final long SIZE_START_DELAY = 300; + private static final long SIZE_START_LENGTH = 233; + private static final long FADE_OUT_START_DELAY = 300; + private static final long FADE_OUT_START_LENGTH = 217; + private static final long TRANSLATE_START_DELAY = 367; + private static final long TRANSLATE_LENGTH = 317; + private static final long GROUP_UPDATE_DELAY = 400; + private static final long DO_NOT_ASK_CHECK_DELAY = 450; + + private final Context mContext; + + private ResultListener mResultListener; + + private String mGroupName; + private int mGroupCount; + private int mGroupIndex; + private Icon mGroupIcon; + private CharSequence mGroupMessage; + private boolean mShowDonNotAsk; + private boolean mDoNotAskChecked; + + private ImageView mIconView; + private TextView mCurrentGroupView; + private TextView mMessageView; + private CheckBox mDoNotAskCheckbox; + private Button mAllowButton; + + private ArrayList mHeightControllers; + private ManualLayoutFrame mRootView; + + // Needed for animation + private ViewGroup mDescContainer; + private ViewGroup mCurrentDesc; + private ViewGroup mNextDesc; + + private ViewGroup mDialogContainer; + + private final Runnable mUpdateGroup = new Runnable() { + @Override + public void run() { + updateGroup(); + } + }; + + public GrantPermissionsViewHandlerImpl(Context context) { + mContext = context; + } + + @Override + public GrantPermissionsViewHandlerImpl setResultListener(ResultListener listener) { + mResultListener = listener; + return this; + } + + @Override + public void saveInstanceState(Bundle arguments) { + arguments.putString(ARG_GROUP_NAME, mGroupName); + arguments.putInt(ARG_GROUP_COUNT, mGroupCount); + arguments.putInt(ARG_GROUP_INDEX, mGroupIndex); + arguments.putParcelable(ARG_GROUP_ICON, mGroupIcon); + arguments.putCharSequence(ARG_GROUP_MESSAGE, mGroupMessage); + arguments.putBoolean(ARG_GROUP_SHOW_DO_NOT_ASK, mShowDonNotAsk); + arguments.putBoolean(ARG_GROUP_DO_NOT_ASK_CHECKED, mDoNotAskCheckbox.isChecked()); + } + + @Override + public void loadInstanceState(Bundle savedInstanceState) { + mGroupName = savedInstanceState.getString(ARG_GROUP_NAME); + mGroupMessage = savedInstanceState.getCharSequence(ARG_GROUP_MESSAGE); + mGroupIcon = savedInstanceState.getParcelable(ARG_GROUP_ICON); + mGroupCount = savedInstanceState.getInt(ARG_GROUP_COUNT); + mGroupIndex = savedInstanceState.getInt(ARG_GROUP_INDEX); + mShowDonNotAsk = savedInstanceState.getBoolean(ARG_GROUP_SHOW_DO_NOT_ASK); + mDoNotAskChecked = savedInstanceState.getBoolean(ARG_GROUP_DO_NOT_ASK_CHECKED); + } + + @Override + public void updateUi(String groupName, int groupCount, int groupIndex, Icon icon, + CharSequence message, boolean showDonNotAsk) { + mGroupName = groupName; + mGroupCount = groupCount; + mGroupIndex = groupIndex; + mGroupIcon = icon; + mGroupMessage = message; + mShowDonNotAsk = showDonNotAsk; + mDoNotAskChecked = false; + // If this is a second (or later) permission and the views exist, then animate. + if (mIconView != null) { + if (mGroupIndex > 0) { + // The first message will be announced as the title of the activity, all others + // we need to announce ourselves. + mDescContainer.announceForAccessibility(message); + animateToPermission(); + } else { + updateDescription(); + updateGroup(); + updateDoNotAskCheckBox(); + } + } + } + + private void animateToPermission() { + if (mHeightControllers == null) { + // We need to manually control the height of any views heigher than the root that + // we inflate. Find all the views up to the root and create ViewHeightControllers for + // them. + mHeightControllers = new ArrayList<>(); + ViewRootImpl viewRoot = mRootView.getViewRootImpl(); + ViewParent v = mRootView.getParent(); + addHeightController(mDialogContainer); + addHeightController(mRootView); + while (v != viewRoot) { + addHeightController((View) v); + v = v.getParent(); + } + // On the heighest level view, we want to setTop rather than setBottom to control the + // height, this way the dialog will grow up rather than down. + ViewHeightController realRootView = + mHeightControllers.get(mHeightControllers.size() - 1); + realRootView.setControlTop(true); + } + + // Grab the current height/y positions, then wait for the layout to change, + // so we can get the end height/y positions. + final SparseArray startPositions = getViewPositions(); + final int startHeight = mRootView.getLayoutHeight(); + mRootView.addOnLayoutChangeListener(new OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, + int oldTop, int oldRight, int oldBottom) { + mRootView.removeOnLayoutChangeListener(this); + SparseArray endPositions = getViewPositions(); + int endHeight = mRootView.getLayoutHeight(); + if (startPositions.get(R.id.do_not_ask_checkbox) == 0 + && endPositions.get(R.id.do_not_ask_checkbox) != 0) { + // If the checkbox didn't have a position before but has one now then set + // the start position to the end position because it just became visible. + startPositions.put(R.id.do_not_ask_checkbox, + endPositions.get(R.id.do_not_ask_checkbox)); + } + animateYPos(startPositions, endPositions, endHeight - startHeight); + } + }); + + // Fade out old description group and scale out the icon for it. + Interpolator interpolator = AnimationUtils.loadInterpolator(mContext, + android.R.interpolator.fast_out_linear_in); + mIconView.animate() + .scaleX(0) + .scaleY(0) + .setStartDelay(FADE_OUT_START_DELAY) + .setDuration(FADE_OUT_START_LENGTH) + .setInterpolator(interpolator) + .start(); + mCurrentDesc.animate() + .alpha(0) + .setStartDelay(FADE_OUT_START_DELAY) + .setDuration(FADE_OUT_START_LENGTH) + .setInterpolator(interpolator) + .setListener(null) + .start(); + + // Update the index of the permission after the animations have started. + mCurrentGroupView.getHandler().postDelayed(mUpdateGroup, GROUP_UPDATE_DELAY); + + // Add the new description and translate it in. + mNextDesc = (ViewGroup) LayoutInflater.from(mContext).inflate( + R.layout.permission_description, mDescContainer, false); + + mMessageView = (TextView) mNextDesc.findViewById(R.id.permission_message); + mIconView = (ImageView) mNextDesc.findViewById(R.id.permission_icon); + updateDescription(); + + int width = mDescContainer.getRootView().getWidth(); + mDescContainer.addView(mNextDesc); + mNextDesc.setTranslationX(width); + + final View oldDesc = mCurrentDesc; + // Remove the old view from the description, so that we can shrink if necessary. + mDescContainer.removeView(oldDesc); + oldDesc.setPadding(mDescContainer.getLeft(), mDescContainer.getTop(), + mRootView.getRight() - mDescContainer.getRight(), 0); + mRootView.addView(oldDesc); + + mCurrentDesc = mNextDesc; + mNextDesc.animate() + .translationX(0) + .setStartDelay(TRANSLATE_START_DELAY) + .setDuration(TRANSLATE_LENGTH) + .setInterpolator(AnimationUtils.loadInterpolator(mContext, + android.R.interpolator.linear_out_slow_in)) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + // This is the longest animation, when it finishes, we are done. + mRootView.removeView(oldDesc); + } + }) + .start(); + + boolean visibleBefore = mDoNotAskCheckbox.getVisibility() == View.VISIBLE; + updateDoNotAskCheckBox(); + boolean visibleAfter = mDoNotAskCheckbox.getVisibility() == View.VISIBLE; + if (visibleBefore != visibleAfter) { + Animation anim = AnimationUtils.loadAnimation(mContext, + visibleAfter ? android.R.anim.fade_in : android.R.anim.fade_out); + anim.setStartOffset(visibleAfter ? DO_NOT_ASK_CHECK_DELAY : 0); + mDoNotAskCheckbox.startAnimation(anim); + } + } + + private void addHeightController(View v) { + ViewHeightController heightController = new ViewHeightController(v); + heightController.setHeight(v.getHeight()); + mHeightControllers.add(heightController); + } + + private SparseArray getViewPositions() { + SparseArray locMap = new SparseArray<>(); + final int N = mDialogContainer.getChildCount(); + for (int i = 0; i < N; i++) { + View child = mDialogContainer.getChildAt(i); + if (child.getId() <= 0) { + // Only track views with ids. + continue; + } + locMap.put(child.getId(), child.getY()); + } + return locMap; + } + + private void animateYPos(SparseArray startPositions, SparseArray endPositions, + int heightDiff) { + final int N = startPositions.size(); + for (int i = 0; i < N; i++) { + int key = startPositions.keyAt(i); + float start = startPositions.get(key); + float end = endPositions.get(key); + if (start != end) { + final View child = mDialogContainer.findViewById(key); + child.setTranslationY(start - end); + child.animate() + .setStartDelay(SIZE_START_DELAY) + .setDuration(SIZE_START_LENGTH) + .translationY(0) + .start(); + } + } + for (int i = 0; i < mHeightControllers.size(); i++) { + mHeightControllers.get(i).animateAddHeight(heightDiff); + } + } + + @Override + public View createView() { + mRootView = (ManualLayoutFrame) LayoutInflater.from(mContext) + .inflate(R.layout.grant_permissions, null); + ((ButtonBarLayout) mRootView.findViewById(R.id.button_group)).setAllowStacking(true); + + mDialogContainer = (ViewGroup) mRootView.findViewById(R.id.dialog_container); + mMessageView = (TextView) mRootView.findViewById(R.id.permission_message); + mIconView = (ImageView) mRootView.findViewById(R.id.permission_icon); + mCurrentGroupView = (TextView) mRootView.findViewById(R.id.current_page_text); + mDoNotAskCheckbox = (CheckBox) mRootView.findViewById(R.id.do_not_ask_checkbox); + mAllowButton = (Button) mRootView.findViewById(R.id.permission_allow_button); + + mDescContainer = (ViewGroup) mRootView.findViewById(R.id.desc_container); + mCurrentDesc = (ViewGroup) mRootView.findViewById(R.id.perm_desc_root); + + mAllowButton.setOnClickListener(this); + mRootView.findViewById(R.id.permission_deny_button).setOnClickListener(this); + mDoNotAskCheckbox.setOnClickListener(this); + + if (mGroupName != null) { + updateDescription(); + updateGroup(); + updateDoNotAskCheckBox(); + } + + return mRootView; + } + + @Override + public void updateWindowAttributes(LayoutParams outLayoutParams) { + // No-op + } + + private void updateDescription() { + mIconView.setImageDrawable(mGroupIcon.loadDrawable(mContext)); + mMessageView.setText(mGroupMessage); + } + + private void updateGroup() { + if (mGroupCount > 1) { + mCurrentGroupView.setVisibility(View.VISIBLE); + mCurrentGroupView.setText(mContext.getString(R.string.current_permission_template, + mGroupIndex + 1, mGroupCount)); + } else { + mCurrentGroupView.setVisibility(View.INVISIBLE); + } + } + + private void updateDoNotAskCheckBox() { + if (mShowDonNotAsk) { + mDoNotAskCheckbox.setVisibility(View.VISIBLE); + mDoNotAskCheckbox.setOnClickListener(this); + mDoNotAskCheckbox.setChecked(mDoNotAskChecked); + } else { + mDoNotAskCheckbox.setVisibility(View.GONE); + mDoNotAskCheckbox.setOnClickListener(null); + } + } + + @Override + public void onClick(View view) { + switch (view.getId()) { + case R.id.permission_allow_button: + if (mResultListener != null) { + view.clearAccessibilityFocus(); + mResultListener.onPermissionGrantResult(mGroupName, true, false); + } + break; + case R.id.permission_deny_button: + mAllowButton.setEnabled(true); + if (mResultListener != null) { + view.clearAccessibilityFocus(); + mResultListener.onPermissionGrantResult(mGroupName, false, + mDoNotAskCheckbox.isChecked()); + } + break; + case R.id.do_not_ask_checkbox: + mAllowButton.setEnabled(!mDoNotAskCheckbox.isChecked()); + break; + } + } + + @Override + public void onBackPressed() { + if (mResultListener != null) { + final boolean doNotAskAgain = mDoNotAskCheckbox.isChecked(); + mResultListener.onPermissionGrantResult(mGroupName, false, doNotAskAgain); + } + } + + /** + * Manually controls the height of a view through getBottom/setTop. Also listens + * for layout changes and sets the height again to be sure it doesn't change. + */ + private static final class ViewHeightController implements OnLayoutChangeListener { + private final View mView; + private int mHeight; + private int mNextHeight; + private boolean mControlTop; + private ObjectAnimator mAnimator; + + public ViewHeightController(View view) { + mView = view; + mView.addOnLayoutChangeListener(this); + } + + public void setControlTop(boolean controlTop) { + mControlTop = controlTop; + } + + public void animateAddHeight(int heightDiff) { + if (heightDiff != 0) { + if (mNextHeight == 0) { + mNextHeight = mHeight; + } + mNextHeight += heightDiff; + if (mAnimator != null) { + mAnimator.cancel(); + } + mAnimator = ObjectAnimator.ofInt(this, "height", mHeight, mNextHeight); + mAnimator.setStartDelay(SIZE_START_DELAY); + mAnimator.setDuration(SIZE_START_LENGTH); + mAnimator.start(); + } + } + + public void setHeight(int height) { + mHeight = height; + updateHeight(); + } + + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, + int oldTop, int oldRight, int oldBottom) { + // Ensure that the height never changes. + updateHeight(); + } + + private void updateHeight() { + if (mControlTop) { + mView.setTop(mView.getBottom() - mHeight); + } else { + mView.setBottom(mView.getTop() + mHeight); + } + } + } +} diff --git a/src/com/android/packageinstaller/permission/ui/handheld/ManagePermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/handheld/ManagePermissionsFragment.java new file mode 100644 index 00000000..c53da879 --- /dev/null +++ b/src/com/android/packageinstaller/permission/ui/handheld/ManagePermissionsFragment.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.packageinstaller.permission.ui.handheld; + +import android.annotation.Nullable; +import android.app.ActionBar; +import android.app.FragmentTransaction; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceClickListener; +import android.preference.PreferenceScreen; +import android.util.ArraySet; +import android.util.Log; +import android.view.MenuItem; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; +import com.android.packageinstaller.R; +import com.android.packageinstaller.permission.model.PermissionApps; +import com.android.packageinstaller.permission.model.PermissionApps.PmCache; +import com.android.packageinstaller.permission.model.PermissionGroup; +import com.android.packageinstaller.permission.model.PermissionGroups; +import com.android.packageinstaller.permission.utils.Utils; + +import java.util.List; + +public final class ManagePermissionsFragment extends PermissionsFrameFragment + implements PermissionGroups.PermissionsGroupsChangeCallback, + Preference.OnPreferenceClickListener { + private static final String LOG_TAG = "ManagePermissionsFragment"; + + private static final String OS_PKG = "android"; + + private static final String EXTRA_PREFS_KEY = "extra_prefs_key"; + + private ArraySet mLauncherPkgs; + + private PermissionGroups mPermissions; + + private PreferenceScreen mExtraScreen; + + public static ManagePermissionsFragment newInstance() { + return new ManagePermissionsFragment(); + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + setLoading(true /* loading */, false /* animate */); + setHasOptionsMenu(true); + final ActionBar ab = getActivity().getActionBar(); + if (ab != null) { + ab.setDisplayHomeAsUpEnabled(true); + } + mLauncherPkgs = Utils.getLauncherPackages(getContext()); + mPermissions = new PermissionGroups(getActivity(), getLoaderManager(), this); + } + + @Override + public void onResume() { + super.onResume(); + mPermissions.refresh(); + updatePermissionsUi(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + getActivity().finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public boolean onPreferenceClick(Preference preference) { + String key = preference.getKey(); + + PermissionGroup group = mPermissions.getGroup(key); + if (group == null) { + return false; + } + + Intent intent = new Intent(Intent.ACTION_MANAGE_PERMISSION_APPS) + .putExtra(Intent.EXTRA_PERMISSION_NAME, key); + try { + getActivity().startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.w(LOG_TAG, "No app to handle " + intent); + } + + return true; + } + + @Override + public void onPermissionGroupsChanged() { + updatePermissionsUi(); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + bindPermissionUi(getActivity(), getView()); + } + + private static void bindPermissionUi(@Nullable Context context, @Nullable View rootView) { + if (context == null || rootView == null) { + return; + } + + ImageView iconView = (ImageView) rootView.findViewById(R.id.lb_icon); + if (iconView != null) { + // Set the icon as the background instead of the image because ImageView + // doesn't properly scale vector drawables beyond their intrinsic size + Drawable icon = context.getDrawable(R.drawable.ic_lock); + icon.setTint(context.getColor(R.color.off_white)); + iconView.setBackground(icon); + } + TextView titleView = (TextView) rootView.findViewById(R.id.lb_title); + if (titleView != null) { + titleView.setText(R.string.app_permissions); + } + TextView breadcrumbView = (TextView) rootView.findViewById(R.id.lb_breadcrumb); + if (breadcrumbView != null) { + breadcrumbView.setText(R.string.app_permissions_breadcrumb); + } + } + + private void updatePermissionsUi() { + Context context = getActivity(); + if (context == null) { + return; + } + + List groups = mPermissions.getGroups(); + PreferenceScreen screen = getPreferenceScreen(); + if (screen == null) { + screen = getPreferenceManager().createPreferenceScreen(getActivity()); + setPreferenceScreen(screen); + } + + // Use this to speed up getting the info for all of the PermissionApps below. + // Create a new one for each refresh to make sure it has fresh data. + PmCache cache = new PmCache(getContext().getPackageManager()); + for (PermissionGroup group : groups) { + boolean isSystemPermission = group.getDeclaringPackage().equals(OS_PKG); + + Preference preference = findPreference(group.getName()); + if (preference == null && mExtraScreen != null) { + preference = mExtraScreen.findPreference(group.getName()); + } + if (preference == null) { + preference = new Preference(context); + preference.setOnPreferenceClickListener(this); + preference.setKey(group.getName()); + preference.setIcon(Utils.applyTint(context, group.getIcon(), + android.R.attr.colorControlNormal)); + preference.setTitle(group.getLabel()); + // Set blank summary so that no resizing/jumping happens when the summary is loaded. + preference.setSummary(" "); + preference.setPersistent(false); + if (isSystemPermission) { + screen.addPreference(preference); + } else { + if (mExtraScreen == null) { + mExtraScreen = getPreferenceManager().createPreferenceScreen(context); + } + mExtraScreen.addPreference(preference); + } + } + final Preference finalPref = preference; + + new PermissionApps(getContext(), group.getName(), new PermissionApps.Callback() { + @Override + public void onPermissionsLoaded(PermissionApps permissionApps) { + if (getActivity() == null) { + return; + } + int granted = permissionApps.getGrantedCount(mLauncherPkgs); + int total = permissionApps.getTotalCount(mLauncherPkgs); + finalPref.setSummary(getString(R.string.app_permissions_group_summary, + granted, total)); + } + }, cache).refresh(false); + } + + if (mExtraScreen != null && mExtraScreen.getPreferenceCount() > 0 + && screen.findPreference(EXTRA_PREFS_KEY) == null) { + Preference extraScreenPreference = new Preference(context); + extraScreenPreference.setKey(EXTRA_PREFS_KEY); + extraScreenPreference.setIcon(Utils.applyTint(context, + R.drawable.ic_more_items, + android.R.attr.colorControlNormal)); + extraScreenPreference.setTitle(R.string.additional_permissions); + extraScreenPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + AdditionalPermissionsFragment frag = new AdditionalPermissionsFragment(); + frag.setTargetFragment(ManagePermissionsFragment.this, 0); + FragmentTransaction ft = getFragmentManager().beginTransaction(); + ft.replace(android.R.id.content, frag); + ft.addToBackStack(null); + ft.commit(); + return true; + } + }); + int count = mExtraScreen.getPreferenceCount(); + extraScreenPreference.setSummary(getResources().getQuantityString( + R.plurals.additional_permissions_more, count, count)); + screen.addPreference(extraScreenPreference); + } + if (screen.getPreferenceCount() != 0) { + setLoading(false /* loading */, true /* animate */); + } + } + + public static class AdditionalPermissionsFragment extends PermissionsFrameFragment { + @Override + public void onCreate(Bundle icicle) { + setLoading(true /* loading */, false /* animate */); + super.onCreate(icicle); + getActivity().setTitle(R.string.additional_permissions); + setHasOptionsMenu(true); + + setPreferenceScreen(((ManagePermissionsFragment) getTargetFragment()).mExtraScreen); + setLoading(false /* loading */, true /* animate */); + } + + @Override + public void onDestroy() { + getActivity().setTitle(R.string.app_permissions); + super.onDestroy(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + getFragmentManager().popBackStack(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + bindPermissionUi(getActivity(), getView()); + } + } +} diff --git a/src/com/android/packageinstaller/permission/ui/handheld/PermissionAppsFragment.java b/src/com/android/packageinstaller/permission/ui/handheld/PermissionAppsFragment.java new file mode 100644 index 00000000..554830a7 --- /dev/null +++ b/src/com/android/packageinstaller/permission/ui/handheld/PermissionAppsFragment.java @@ -0,0 +1,427 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.packageinstaller.permission.ui.handheld; + +import android.app.ActionBar; +import android.app.AlertDialog; +import android.app.Fragment; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceClickListener; +import android.preference.PreferenceScreen; +import android.preference.SwitchPreference; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import com.android.packageinstaller.R; +import com.android.packageinstaller.permission.model.AppPermissionGroup; +import com.android.packageinstaller.permission.model.PermissionApps; +import com.android.packageinstaller.permission.model.PermissionApps.Callback; +import com.android.packageinstaller.permission.model.PermissionApps.PermissionApp; +import com.android.packageinstaller.permission.ui.OverlayTouchActivity; +import com.android.packageinstaller.permission.utils.LocationUtils; +import com.android.packageinstaller.permission.utils.SafetyNetLogger; +import com.android.packageinstaller.permission.utils.Utils; + +import java.util.ArrayList; +import java.util.List; + +public final class PermissionAppsFragment extends PermissionsFrameFragment implements Callback, + Preference.OnPreferenceChangeListener { + + private static final int MENU_SHOW_SYSTEM = Menu.FIRST; + private static final int MENU_HIDE_SYSTEM = Menu.FIRST + 1; + private static final String KEY_SHOW_SYSTEM_PREFS = "_showSystem"; + + public static PermissionAppsFragment newInstance(String permissionName) { + return setPermissionName(new PermissionAppsFragment(), permissionName); + } + + private static T setPermissionName(T fragment, String permissionName) { + Bundle arguments = new Bundle(); + arguments.putString(Intent.EXTRA_PERMISSION_NAME, permissionName); + fragment.setArguments(arguments); + return fragment; + } + + private PermissionApps mPermissionApps; + + private PreferenceScreen mExtraScreen; + + private ArrayMap mToggledGroups; + private ArraySet mLauncherPkgs; + private boolean mHasConfirmedRevoke; + + private boolean mShowSystem; + private MenuItem mShowSystemMenu; + private MenuItem mHideSystemMenu; + + private Callback mOnPermissionsLoadedListener; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setLoading(true /* loading */, false /* animate */); + setHasOptionsMenu(true); + final ActionBar ab = getActivity().getActionBar(); + if (ab != null) { + ab.setDisplayHomeAsUpEnabled(true); + } + mLauncherPkgs = Utils.getLauncherPackages(getContext()); + + String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME); + mPermissionApps = new PermissionApps(getActivity(), groupName, this); + mPermissionApps.refresh(true); + } + + @Override + public void onResume() { + super.onResume(); + mPermissionApps.refresh(true); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + mShowSystemMenu = menu.add(Menu.NONE, MENU_SHOW_SYSTEM, Menu.NONE, + R.string.menu_show_system); + mHideSystemMenu = menu.add(Menu.NONE, MENU_HIDE_SYSTEM, Menu.NONE, + R.string.menu_hide_system); + updateMenu(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + getActivity().finish(); + return true; + case MENU_SHOW_SYSTEM: + case MENU_HIDE_SYSTEM: + mShowSystem = item.getItemId() == MENU_SHOW_SYSTEM; + if (mPermissionApps.getApps() != null) { + onPermissionsLoaded(mPermissionApps); + } + updateMenu(); + break; + } + return super.onOptionsItemSelected(item); + } + + private void updateMenu() { + mShowSystemMenu.setVisible(!mShowSystem); + mHideSystemMenu.setVisible(mShowSystem); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + bindUi(this, mPermissionApps); + } + + private static void bindUi(Fragment fragment, PermissionApps permissionApps) { + final Drawable icon = permissionApps.getIcon(); + final CharSequence label = permissionApps.getLabel(); + final ActionBar ab = fragment.getActivity().getActionBar(); + if (ab != null) { + ab.setTitle(fragment.getString(R.string.permission_title, label)); + } + + final ViewGroup rootView = (ViewGroup) fragment.getView(); + final ImageView iconView = (ImageView) rootView.findViewById(R.id.lb_icon); + if (iconView != null) { + // Set the icon as the background instead of the image because ImageView + // doesn't properly scale vector drawables beyond their intrinsic size + iconView.setBackground(icon); + } + final TextView titleView = (TextView) rootView.findViewById(R.id.lb_title); + if (titleView != null) { + titleView.setText(label); + } + final TextView breadcrumbView = (TextView) rootView.findViewById(R.id.lb_breadcrumb); + if (breadcrumbView != null) { + breadcrumbView.setText(R.string.app_permissions); + } + } + + private void setOnPermissionsLoadedListener(Callback callback) { + mOnPermissionsLoadedListener = callback; + } + + @Override + public void onPermissionsLoaded(PermissionApps permissionApps) { + Context context = getActivity(); + + if (context == null) { + return; + } + + boolean isTelevision = Utils.isTelevision(context); + PreferenceScreen screen = getPreferenceScreen(); + if (screen == null) { + screen = getPreferenceManager().createPreferenceScreen(getActivity()); + setPreferenceScreen(screen); + } + + ArraySet preferencesToRemove = new ArraySet<>(); + for (int i = 0, n = screen.getPreferenceCount(); i < n; i++) { + preferencesToRemove.add(screen.getPreference(i).getKey()); + } + if (mExtraScreen != null) { + for (int i = 0, n = mExtraScreen.getPreferenceCount(); i < n; i++) { + preferencesToRemove.add(mExtraScreen.getPreference(i).getKey()); + } + } + + for (PermissionApp app : permissionApps.getApps()) { + if (!Utils.shouldShowPermission(app)) { + continue; + } + + String key = app.getKey(); + preferencesToRemove.remove(key); + Preference existingPref = screen.findPreference(key); + if (existingPref == null && mExtraScreen != null) { + existingPref = mExtraScreen.findPreference(key); + } + + boolean isSystemApp = Utils.isSystem(app, mLauncherPkgs); + if (isSystemApp && !isTelevision && !mShowSystem) { + if (existingPref != null) { + screen.removePreference(existingPref); + } + continue; + } + + if (existingPref != null) { + // If existing preference - only update its state. + if (app.isPolicyFixed()) { + existingPref.setSummary(getString( + R.string.permission_summary_enforced_by_policy)); + } + existingPref.setPersistent(false); + existingPref.setEnabled(!app.isPolicyFixed()); + if (existingPref instanceof SwitchPreference) { + ((SwitchPreference) existingPref) + .setChecked(app.areRuntimePermissionsGranted()); + } + continue; + } + + SwitchPreference pref = new SwitchPreference(context); + pref.setOnPreferenceChangeListener(this); + pref.setKey(app.getKey()); + pref.setIcon(app.getIcon()); + pref.setTitle(app.getLabel()); + if (app.isPolicyFixed()) { + pref.setSummary(getString(R.string.permission_summary_enforced_by_policy)); + } + pref.setPersistent(false); + pref.setEnabled(!app.isPolicyFixed()); + pref.setChecked(app.areRuntimePermissionsGranted()); + + if (isSystemApp && isTelevision) { + if (mExtraScreen == null) { + mExtraScreen = getPreferenceManager().createPreferenceScreen(context); + } + mExtraScreen.addPreference(pref); + } else { + screen.addPreference(pref); + } + } + + if (mExtraScreen != null) { + preferencesToRemove.remove(KEY_SHOW_SYSTEM_PREFS); + Preference pref = screen.findPreference(KEY_SHOW_SYSTEM_PREFS); + + if (pref == null) { + pref = new Preference(context); + pref.setKey(KEY_SHOW_SYSTEM_PREFS); + pref.setIcon(Utils.applyTint(context, R.drawable.ic_toc, + android.R.attr.colorControlNormal)); + pref.setTitle(R.string.preference_show_system_apps); + pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + SystemAppsFragment frag = new SystemAppsFragment(); + setPermissionName(frag, getArguments().getString(Intent.EXTRA_PERMISSION_NAME)); + frag.setTargetFragment(PermissionAppsFragment.this, 0); + getFragmentManager().beginTransaction() + .replace(android.R.id.content, frag) + .addToBackStack("SystemApps") + .commit(); + return true; + } + }); + screen.addPreference(pref); + } + + int grantedCount = 0; + for (int i = 0, n = mExtraScreen.getPreferenceCount(); i < n; i++) { + if (((SwitchPreference) mExtraScreen.getPreference(i)).isChecked()) { + grantedCount++; + } + } + pref.setSummary(getString(R.string.app_permissions_group_summary, + grantedCount, mExtraScreen.getPreferenceCount())); + } + + for (String key : preferencesToRemove) { + Preference pref = screen.findPreference(key); + if (pref != null) { + screen.removePreference(pref); + } else if (mExtraScreen != null) { + pref = mExtraScreen.findPreference(key); + if (pref != null) { + mExtraScreen.removePreference(pref); + } + } + } + + setLoading(false /* loading */, true /* animate */); + + if (mOnPermissionsLoadedListener != null) { + mOnPermissionsLoadedListener.onPermissionsLoaded(permissionApps); + } + } + + @Override + public boolean onPreferenceChange(final Preference preference, Object newValue) { + String pkg = preference.getKey(); + final PermissionApp app = mPermissionApps.getApp(pkg); + + if (app == null) { + return false; + } + + OverlayTouchActivity activity = (OverlayTouchActivity) getActivity(); + if (activity.isObscuredTouch()) { + activity.showOverlayDialog(); + return false; + } + + addToggledGroup(app.getPackageName(), app.getPermissionGroup()); + + if (LocationUtils.isLocationGroupAndProvider(mPermissionApps.getGroupName(), + app.getPackageName())) { + LocationUtils.showLocationDialog(getContext(), app.getLabel()); + return false; + } + if (newValue == Boolean.TRUE) { + app.grantRuntimePermissions(); + } else { + final boolean grantedByDefault = app.hasGrantedByDefaultPermissions(); + if (grantedByDefault || (!app.hasRuntimePermissions() && !mHasConfirmedRevoke)) { + new AlertDialog.Builder(getContext()) + .setMessage(grantedByDefault ? R.string.system_warning + : R.string.old_sdk_deny_warning) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.grant_dialog_button_deny, + new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + ((SwitchPreference) preference).setChecked(false); + app.revokeRuntimePermissions(); + if (!grantedByDefault) { + mHasConfirmedRevoke = true; + } + } + }) + .show(); + return false; + } else { + app.revokeRuntimePermissions(); + } + } + return true; + } + + @Override + public void onPause() { + super.onPause(); + logToggledGroups(); + } + + private void addToggledGroup(String packageName, AppPermissionGroup group) { + if (mToggledGroups == null) { + mToggledGroups = new ArrayMap<>(); + } + // Double toggle is back to initial state. + if (mToggledGroups.containsKey(packageName)) { + mToggledGroups.remove(packageName); + } else { + mToggledGroups.put(packageName, group); + } + } + + private void logToggledGroups() { + if (mToggledGroups != null) { + final int groupCount = mToggledGroups.size(); + for (int i = 0; i < groupCount; i++) { + String packageName = mToggledGroups.keyAt(i); + List groups = new ArrayList<>(); + groups.add(mToggledGroups.valueAt(i)); + SafetyNetLogger.logPermissionsToggled(packageName, groups); + } + mToggledGroups = null; + } + } + + public static class SystemAppsFragment extends PermissionsFrameFragment implements Callback { + PermissionAppsFragment mOuterFragment; + + @Override + public void onCreate(Bundle savedInstanceState) { + mOuterFragment = (PermissionAppsFragment) getTargetFragment(); + setLoading(true /* loading */, false /* animate */); + super.onCreate(savedInstanceState); + if (mOuterFragment.mExtraScreen != null) { + setPreferenceScreen(); + } else { + mOuterFragment.setOnPermissionsLoadedListener(this); + } + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME); + PermissionApps permissionApps = new PermissionApps(getActivity(), groupName, null); + bindUi(this, permissionApps); + } + + @Override + public void onPermissionsLoaded(PermissionApps permissionApps) { + setPreferenceScreen(); + mOuterFragment.setOnPermissionsLoadedListener(null); + } + + private void setPreferenceScreen() { + setPreferenceScreen(mOuterFragment.mExtraScreen); + setLoading(false /* loading */, true /* animate */); + } + } +} diff --git a/src/com/android/packageinstaller/permission/ui/handheld/PermissionsFrameFragment.java b/src/com/android/packageinstaller/permission/ui/handheld/PermissionsFrameFragment.java new file mode 100644 index 00000000..e7f63b23 --- /dev/null +++ b/src/com/android/packageinstaller/permission/ui/handheld/PermissionsFrameFragment.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.permission.ui.handheld; + +import android.os.Bundle; +import android.preference.PreferenceFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.Animation.AnimationListener; +import android.view.animation.AnimationUtils; +import android.widget.ListView; +import android.widget.TextView; +import com.android.packageinstaller.R; + +public abstract class PermissionsFrameFragment extends PreferenceFragment { + private ViewGroup mPreferencesContainer; + + private View mLoadingView; + private ViewGroup mPrefsView; + private boolean mIsLoading; + + /** + * Returns the view group that holds the preferences objects. This will + * only be set after {@link #onCreateView} has been called. + */ + protected final ViewGroup getPreferencesContainer() { + return mPreferencesContainer; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.permissions_frame, container, + false); + mPrefsView = (ViewGroup) rootView.findViewById(R.id.prefs_container); + if (mPrefsView == null) { + mPrefsView = rootView; + } + mLoadingView = rootView.findViewById(R.id.loading_container); + mPreferencesContainer = (ViewGroup) super.onCreateView( + inflater, mPrefsView, savedInstanceState); + setLoading(mIsLoading, false, true /* force */); + mPrefsView.addView(mPreferencesContainer); + return rootView; + } + + protected void setLoading(boolean loading, boolean animate) { + setLoading(loading, animate, false); + } + + private void setLoading(boolean loading, boolean animate, boolean force) { + if (mIsLoading != loading || force) { + mIsLoading = loading; + if (getView() == null) { + // If there is no created view, there is no reason to animate. + animate = false; + } + if (mPrefsView != null) { + setViewShown(mPrefsView, !loading, animate); + } + if (mLoadingView != null) { + setViewShown(mLoadingView, loading, animate); + } + } + } + + @Override + public ListView getListView() { + ListView listView = super.getListView(); + if (listView.getEmptyView() == null) { + TextView emptyView = (TextView) getView().findViewById(R.id.no_permissions); + listView.setEmptyView(emptyView); + } + return listView; + } + + private void setViewShown(final View view, boolean shown, boolean animate) { + if (animate) { + Animation animation = AnimationUtils.loadAnimation(getContext(), + shown ? android.R.anim.fade_in : android.R.anim.fade_out); + if (shown) { + view.setVisibility(View.VISIBLE); + } else { + animation.setAnimationListener(new AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + view.setVisibility(View.INVISIBLE); + } + }); + } + view.startAnimation(animation); + } else { + view.clearAnimation(); + view.setVisibility(shown ? View.VISIBLE : View.INVISIBLE); + } + } +} diff --git a/src/com/android/packageinstaller/permission/ui/handheld/SettingsWithHeader.java b/src/com/android/packageinstaller/permission/ui/handheld/SettingsWithHeader.java new file mode 100644 index 00000000..acb3c61e --- /dev/null +++ b/src/com/android/packageinstaller/permission/ui/handheld/SettingsWithHeader.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.permission.ui.handheld; + +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.packageinstaller.R; +import com.android.packageinstaller.permission.utils.Utils; + +public abstract class SettingsWithHeader extends PermissionsFrameFragment + implements OnClickListener { + + private View mHeader; + protected Intent mInfoIntent; + protected Drawable mIcon; + protected CharSequence mLabel; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + ViewGroup root = (ViewGroup) super.onCreateView(inflater, container, savedInstanceState); + + if (!Utils.isTelevision(getContext())) { + mHeader = inflater.inflate(R.layout.header, root, false); + getPreferencesContainer().addView(mHeader, 0); + updateHeader(); + } + + return root; + } + + public void setHeader(Drawable icon, CharSequence label, Intent infoIntent) { + mIcon = icon; + mLabel = label; + mInfoIntent = infoIntent; + updateHeader(); + } + + private void updateHeader() { + if (mHeader != null) { + final ImageView appIcon = (ImageView) mHeader.findViewById(R.id.icon); + appIcon.setImageDrawable(mIcon); + + final TextView appName = (TextView) mHeader.findViewById(R.id.name); + appName.setText(mLabel); + + final View info = mHeader.findViewById(R.id.info); + if (mInfoIntent == null) { + info.setVisibility(View.GONE); + } else { + info.setVisibility(View.VISIBLE); + info.setClickable(true); + info.setOnClickListener(this); + } + } + } + + @Override + public void onClick(View v) { + getActivity().startActivity(mInfoIntent); + } +} diff --git a/src/com/android/packageinstaller/permission/ui/television/AllAppPermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/television/AllAppPermissionsFragment.java new file mode 100644 index 00000000..d4910128 --- /dev/null +++ b/src/com/android/packageinstaller/permission/ui/television/AllAppPermissionsFragment.java @@ -0,0 +1,217 @@ +/* +* Copyright (C) 2015 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package com.android.packageinstaller.permission.ui.television; + +import android.app.ActionBar; +import android.app.AlertDialog; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PermissionGroupInfo; +import android.content.pm.PermissionInfo; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; +import android.support.v7.preference.Preference; +import android.support.v7.preference.Preference.OnPreferenceClickListener; +import android.support.v7.preference.PreferenceCategory; +import android.support.v7.preference.PreferenceGroup; +import android.util.Log; +import android.view.MenuItem; + +import com.android.packageinstaller.R; +import com.android.packageinstaller.permission.utils.Utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +public final class AllAppPermissionsFragment extends SettingsWithHeader { + + private static final String LOG_TAG = "AllAppPermissionsFragment"; + + private static final String KEY_OTHER = "other_perms"; + + public static AllAppPermissionsFragment newInstance(String packageName) { + AllAppPermissionsFragment instance = new AllAppPermissionsFragment(); + Bundle arguments = new Bundle(); + arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName); + instance.setArguments(arguments); + return instance; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + final ActionBar ab = getActivity().getActionBar(); + if (ab != null) { + ab.setTitle(R.string.all_permissions); + ab.setDisplayHomeAsUpEnabled(true); + } + } + + @Override + public void onResume() { + super.onResume(); + updateUi(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: { + getFragmentManager().popBackStack(); + return true; + } + } + return super.onOptionsItemSelected(item); + } + + private void updateUi() { + if (getPreferenceScreen() != null) { + getPreferenceScreen().removeAll(); + } + addPreferencesFromResource(R.xml.all_permissions); + PreferenceGroup otherGroup = (PreferenceGroup) findPreference(KEY_OTHER); + ArrayList prefs = new ArrayList<>(); // Used for sorting. + prefs.add(otherGroup); + String pkg = getArguments().getString(Intent.EXTRA_PACKAGE_NAME); + otherGroup.removeAll(); + PackageManager pm = getContext().getPackageManager(); + + try { + PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_PERMISSIONS); + + ApplicationInfo appInfo = info.applicationInfo; + final Drawable icon = appInfo.loadIcon(pm); + final CharSequence label = appInfo.loadLabel(pm); + Intent infoIntent = null; + if (!getActivity().getIntent().getBooleanExtra( + AppPermissionsFragment.EXTRA_HIDE_INFO_BUTTON, false)) { + infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + .setData(Uri.fromParts("package", pkg, null)); + } + setHeader(icon, label, infoIntent); + + if (info.requestedPermissions != null) { + for (int i = 0; i < info.requestedPermissions.length; i++) { + PermissionInfo perm; + try { + perm = pm.getPermissionInfo(info.requestedPermissions[i], 0); + } catch (NameNotFoundException e) { + Log.e(LOG_TAG, + "Can't get permission info for " + info.requestedPermissions[i], e); + continue; + } + + if ((perm.flags & PermissionInfo.FLAG_INSTALLED) == 0 + || (perm.flags & PermissionInfo.FLAG_HIDDEN) != 0) { + continue; + } + + if (perm.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) { + PermissionGroupInfo group = getGroup(perm.group, pm); + PreferenceGroup pref = + findOrCreate(group != null ? group : perm, pm, prefs); + pref.addPreference(getPreference(perm, group, pm)); + } else if (perm.protectionLevel == PermissionInfo.PROTECTION_NORMAL) { + PermissionGroupInfo group = getGroup(perm.group, pm); + otherGroup.addPreference(getPreference(perm, group, pm)); + } + } + } + } catch (NameNotFoundException e) { + Log.e(LOG_TAG, "Problem getting package info for " + pkg, e); + } + // Sort an ArrayList of the groups and then set the order from the sorting. + Collections.sort(prefs, new Comparator() { + @Override + public int compare(Preference lhs, Preference rhs) { + String lKey = lhs.getKey(); + String rKey = rhs.getKey(); + if (lKey.equals(KEY_OTHER)) { + return 1; + } else if (rKey.equals(KEY_OTHER)) { + return -1; + } else if (Utils.isModernPermissionGroup(lKey) + != Utils.isModernPermissionGroup(rKey)) { + return Utils.isModernPermissionGroup(lKey) ? -1 : 1; + } + return lhs.getTitle().toString().compareTo(rhs.getTitle().toString()); + } + }); + for (int i = 0; i < prefs.size(); i++) { + prefs.get(i).setOrder(i); + } + } + + private PermissionGroupInfo getGroup(String group, PackageManager pm) { + try { + return pm.getPermissionGroupInfo(group, 0); + } catch (NameNotFoundException e) { + return null; + } + } + + private PreferenceGroup findOrCreate(PackageItemInfo group, PackageManager pm, + ArrayList prefs) { + PreferenceGroup pref = (PreferenceGroup) findPreference(group.name); + if (pref == null) { + pref = new PreferenceCategory(getContext()); + pref.setKey(group.name); + pref.setLayoutResource(R.layout.preference_category_material); + pref.setTitle(group.loadLabel(pm)); + prefs.add(pref); + getPreferenceScreen().addPreference(pref); + } + return pref; + } + + private Preference getPreference(PermissionInfo perm, PermissionGroupInfo group, + PackageManager pm) { + Preference pref = new Preference(getContext()); + pref.setLayoutResource(R.layout.preference_permissions); + Drawable icon = null; + if (perm.icon != 0) { + icon = perm.loadIcon(pm); + } else if (group != null && group.icon != 0) { + icon = group.loadIcon(pm); + } else { + icon = getContext().getDrawable(R.drawable.ic_perm_device_info); + } + pref.setIcon(Utils.applyTint(getContext(), icon, android.R.attr.colorControlNormal)); + pref.setTitle(perm.loadLabel(pm)); + final CharSequence desc = perm.loadDescription(pm); + pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + new AlertDialog.Builder(getContext()) + .setMessage(desc) + .setPositiveButton(android.R.string.ok, null) + .show(); + return true; + } + }); + + return pref; + } +} \ No newline at end of file diff --git a/src/com/android/packageinstaller/permission/ui/television/AppPermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/television/AppPermissionsFragment.java new file mode 100644 index 00000000..42a2661c --- /dev/null +++ b/src/com/android/packageinstaller/permission/ui/television/AppPermissionsFragment.java @@ -0,0 +1,404 @@ +/* +* Copyright (C) 2015 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package com.android.packageinstaller.permission.ui.television; + +import android.annotation.Nullable; +import android.app.ActionBar; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Fragment; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; +import android.support.v14.preference.SwitchPreference; +import android.support.v7.preference.Preference; +import android.support.v7.preference.Preference.OnPreferenceChangeListener; +import android.support.v7.preference.Preference.OnPreferenceClickListener; +import android.support.v7.preference.PreferenceScreen; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.android.packageinstaller.R; +import com.android.packageinstaller.permission.model.AppPermissionGroup; +import com.android.packageinstaller.permission.model.AppPermissions; +import com.android.packageinstaller.permission.ui.OverlayTouchActivity; +import com.android.packageinstaller.permission.utils.LocationUtils; +import com.android.packageinstaller.permission.utils.SafetyNetLogger; +import com.android.packageinstaller.permission.utils.Utils; + +import java.util.ArrayList; +import java.util.List; + +public final class AppPermissionsFragment extends SettingsWithHeader + implements OnPreferenceChangeListener { + + private static final String LOG_TAG = "ManagePermsFragment"; + + static final String EXTRA_HIDE_INFO_BUTTON = "hideInfoButton"; + + private static final int MENU_ALL_PERMS = 0; + + private List mToggledGroups; + private AppPermissions mAppPermissions; + private PreferenceScreen mExtraScreen; + + private boolean mHasConfirmedRevoke; + + public static AppPermissionsFragment newInstance(String packageName) { + return setPackageName(new AppPermissionsFragment(), packageName); + } + + private static T setPackageName(T fragment, String packageName) { + Bundle arguments = new Bundle(); + arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName); + fragment.setArguments(arguments); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setLoading(true /* loading */, false /* animate */); + setHasOptionsMenu(true); + final ActionBar ab = getActivity().getActionBar(); + if (ab != null) { + ab.setDisplayHomeAsUpEnabled(true); + } + + String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME); + Activity activity = getActivity(); + PackageInfo packageInfo = getPackageInfo(activity, packageName); + if (packageInfo == null) { + Toast.makeText(activity, R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show(); + activity.finish(); + return; + } + + mAppPermissions = new AppPermissions(activity, packageInfo, null, true, new Runnable() { + @Override + public void run() { + getActivity().finish(); + } + }); + loadPreferences(); + } + + @Override + public void onResume() { + super.onResume(); + mAppPermissions.refresh(); + setPreferencesCheckedState(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: { + getActivity().finish(); + return true; + } + + case MENU_ALL_PERMS: { + Fragment frag = AllAppPermissionsFragment.newInstance( + getArguments().getString(Intent.EXTRA_PACKAGE_NAME)); + getFragmentManager().beginTransaction() + .replace(android.R.id.content, frag) + .addToBackStack("AllPerms") + .commit(); + return true; + } + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + if (mAppPermissions != null) { + bindUi(this, mAppPermissions.getPackageInfo()); + } + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + menu.add(Menu.NONE, MENU_ALL_PERMS, Menu.NONE, R.string.all_permissions); + } + + private static void bindUi(SettingsWithHeader fragment, PackageInfo packageInfo) { + Activity activity = fragment.getActivity(); + PackageManager pm = activity.getPackageManager(); + ApplicationInfo appInfo = packageInfo.applicationInfo; + Intent infoIntent = null; + if (!activity.getIntent().getBooleanExtra(EXTRA_HIDE_INFO_BUTTON, false)) { + infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + .setData(Uri.fromParts("package", packageInfo.packageName, null)); + } + + Drawable icon = appInfo.loadIcon(pm); + CharSequence label = appInfo.loadLabel(pm); + fragment.setHeader(icon, label, infoIntent); + + ActionBar ab = activity.getActionBar(); + if (ab != null) { + ab.setTitle(R.string.app_permissions); + } + + ViewGroup rootView = (ViewGroup) fragment.getView(); + ImageView iconView = (ImageView) rootView.findViewById(R.id.lb_icon); + if (iconView != null) { + iconView.setImageDrawable(icon); + } + TextView titleView = (TextView) rootView.findViewById(R.id.lb_title); + if (titleView != null) { + titleView.setText(R.string.app_permissions); + } + TextView breadcrumbView = (TextView) rootView.findViewById(R.id.lb_breadcrumb); + if (breadcrumbView != null) { + breadcrumbView.setText(label); + } + } + + private void loadPreferences() { + Context context = getPreferenceManager().getContext(); + if (context == null) { + return; + } + + PreferenceScreen screen = getPreferenceScreen(); + screen.removeAll(); + + if (mExtraScreen != null) { + mExtraScreen.removeAll(); + } + + final Preference extraPerms = new Preference(context); + extraPerms.setIcon(R.drawable.ic_toc); + extraPerms.setTitle(R.string.additional_permissions); + + for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) { + if (!Utils.shouldShowPermission(group, mAppPermissions.getPackageInfo().packageName)) { + continue; + } + + boolean isPlatform = group.getDeclaringPackage().equals(Utils.OS_PKG); + + SwitchPreference preference = new SwitchPreference(context); + preference.setOnPreferenceChangeListener(this); + preference.setKey(group.getName()); + Drawable icon = Utils.loadDrawable(context.getPackageManager(), + group.getIconPkg(), group.getIconResId()); + preference.setIcon(Utils.applyTint(getContext(), icon, + android.R.attr.colorControlNormal)); + preference.setTitle(group.getLabel()); + if (group.isPolicyFixed()) { + preference.setSummary(getString(R.string.permission_summary_enforced_by_policy)); + } + preference.setPersistent(false); + preference.setEnabled(!group.isPolicyFixed()); + preference.setChecked(group.areRuntimePermissionsGranted()); + + if (isPlatform) { + screen.addPreference(preference); + } else { + if (mExtraScreen == null) { + mExtraScreen = getPreferenceManager().createPreferenceScreen(context); + } + mExtraScreen.addPreference(preference); + } + } + + if (mExtraScreen != null) { + extraPerms.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + AdditionalPermissionsFragment frag = new AdditionalPermissionsFragment(); + setPackageName(frag, getArguments().getString(Intent.EXTRA_PACKAGE_NAME)); + frag.setTargetFragment(AppPermissionsFragment.this, 0); + getFragmentManager().beginTransaction() + .replace(android.R.id.content, frag) + .addToBackStack(null) + .commit(); + return true; + } + }); + int count = mExtraScreen.getPreferenceCount(); + extraPerms.setSummary(getResources().getQuantityString( + R.plurals.additional_permissions_more, count, count)); + screen.addPreference(extraPerms); + } + + setLoading(false /* loading */, true /* animate */); + } + + @Override + public boolean onPreferenceChange(final Preference preference, Object newValue) { + String groupName = preference.getKey(); + final AppPermissionGroup group = mAppPermissions.getPermissionGroup(groupName); + + if (group == null) { + return false; + } + + OverlayTouchActivity activity = (OverlayTouchActivity) getActivity(); + if (activity.isObscuredTouch()) { + activity.showOverlayDialog(); + return false; + } + + addToggledGroup(group); + + if (LocationUtils.isLocationGroupAndProvider(group.getName(), group.getApp().packageName)) { + LocationUtils.showLocationDialog(getContext(), mAppPermissions.getAppLabel()); + return false; + } + if (newValue == Boolean.TRUE) { + group.grantRuntimePermissions(false); + } else { + final boolean grantedByDefault = group.hasGrantedByDefaultPermission(); + if (grantedByDefault || (!group.hasRuntimePermission() && !mHasConfirmedRevoke)) { + new AlertDialog.Builder(getContext()) + .setMessage(grantedByDefault ? R.string.system_warning + : R.string.old_sdk_deny_warning) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.grant_dialog_button_deny, + new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + ((SwitchPreference) preference).setChecked(false); + group.revokeRuntimePermissions(false); + if (!grantedByDefault) { + mHasConfirmedRevoke = true; + } + } + }) + .show(); + return false; + } else { + group.revokeRuntimePermissions(false); + } + } + + return true; + } + + @Override + public void onPause() { + super.onPause(); + logToggledGroups(); + } + + private void addToggledGroup(AppPermissionGroup group) { + if (mToggledGroups == null) { + mToggledGroups = new ArrayList<>(); + } + // Double toggle is back to initial state. + if (mToggledGroups.contains(group)) { + mToggledGroups.remove(group); + } else { + mToggledGroups.add(group); + } + } + + private void logToggledGroups() { + if (mToggledGroups != null) { + String packageName = mAppPermissions.getPackageInfo().packageName; + SafetyNetLogger.logPermissionsToggled(packageName, mToggledGroups); + mToggledGroups = null; + } + } + + private void setPreferencesCheckedState() { + setPreferencesCheckedState(getPreferenceScreen()); + if (mExtraScreen != null) { + setPreferencesCheckedState(mExtraScreen); + } + } + + private void setPreferencesCheckedState(PreferenceScreen screen) { + int preferenceCount = screen.getPreferenceCount(); + for (int i = 0; i < preferenceCount; i++) { + Preference preference = screen.getPreference(i); + if (preference instanceof SwitchPreference) { + SwitchPreference switchPref = (SwitchPreference) preference; + AppPermissionGroup group = mAppPermissions.getPermissionGroup(switchPref.getKey()); + if (group != null) { + switchPref.setChecked(group.areRuntimePermissionsGranted()); + } + } + } + } + + private static PackageInfo getPackageInfo(Activity activity, String packageName) { + try { + return activity.getPackageManager().getPackageInfo( + packageName, PackageManager.GET_PERMISSIONS); + } catch (PackageManager.NameNotFoundException e) { + Log.i(LOG_TAG, "No package:" + activity.getCallingPackage(), e); + return null; + } + } + + public static class AdditionalPermissionsFragment extends SettingsWithHeader { + AppPermissionsFragment mOuterFragment; + + @Override + public void onCreate(Bundle savedInstanceState) { + mOuterFragment = (AppPermissionsFragment) getTargetFragment(); + super.onCreate(savedInstanceState); + setHeader(mOuterFragment.mIcon, mOuterFragment.mLabel, mOuterFragment.mInfoIntent); + setHasOptionsMenu(true); + } + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + setPreferenceScreen(mOuterFragment.mExtraScreen); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME); + bindUi(this, getPackageInfo(getActivity(), packageName)); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + getFragmentManager().popBackStack(); + return true; + } + return super.onOptionsItemSelected(item); + } + } +} diff --git a/src/com/android/packageinstaller/permission/ui/television/GrantPermissionsViewHandlerImpl.java b/src/com/android/packageinstaller/permission/ui/television/GrantPermissionsViewHandlerImpl.java new file mode 100644 index 00000000..a2538821 --- /dev/null +++ b/src/com/android/packageinstaller/permission/ui/television/GrantPermissionsViewHandlerImpl.java @@ -0,0 +1,131 @@ +package com.android.packageinstaller.permission.ui.television; + +import android.content.Context; +import android.graphics.PixelFormat; +import android.graphics.drawable.Icon; +import android.os.Bundle; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.packageinstaller.R; +import com.android.packageinstaller.permission.ui.GrantPermissionsViewHandler; + +/** + * TV-specific view handler for the grant permissions activity. + */ +public final class GrantPermissionsViewHandlerImpl implements GrantPermissionsViewHandler, OnClickListener { + + private static final String ARG_GROUP_NAME = "ARG_GROUP_NAME"; + + private final Context mContext; + + private ResultListener mResultListener; + + private String mGroupName; + + private LinearLayout mRootView; + private TextView mMessageView; + private ImageView mIconView; + private TextView mCurrentGroupView; + private Button mAllowButton; + private Button mSoftDenyButton; + private Button mHardDenyButton; + + public GrantPermissionsViewHandlerImpl(Context context) { + mContext = context; + } + + @Override + public GrantPermissionsViewHandlerImpl setResultListener(ResultListener listener) { + mResultListener = listener; + return this; + } + + @Override + public View createView() { + mRootView = (LinearLayout) LayoutInflater.from(mContext) + .inflate(R.layout.grant_permissions, null); + + mMessageView = (TextView) mRootView.findViewById(R.id.permission_message); + mIconView = (ImageView) mRootView.findViewById(R.id.permission_icon); + mCurrentGroupView = (TextView) mRootView.findViewById(R.id.current_page_text); + mAllowButton = (Button) mRootView.findViewById(R.id.permission_allow_button); + mSoftDenyButton = (Button) mRootView.findViewById(R.id.permission_deny_button); + mHardDenyButton = (Button) mRootView.findViewById( + R.id.permission_deny_dont_ask_again_button); + + mAllowButton.setOnClickListener(this); + mSoftDenyButton.setOnClickListener(this); + mHardDenyButton.setOnClickListener(this); + + return mRootView; + } + + @Override + public void updateWindowAttributes(WindowManager.LayoutParams outLayoutParams) { + outLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT; + outLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT; + outLayoutParams.format = PixelFormat.OPAQUE; + outLayoutParams.gravity = Gravity.BOTTOM; + outLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; + outLayoutParams.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; + } + + @Override + public void updateUi(String groupName, int groupCount, int groupIndex, Icon icon, + CharSequence message, boolean showDoNotAsk) { + mGroupName = groupName; + + mMessageView.setText(message); + mIconView.setImageIcon(icon); + mHardDenyButton.setVisibility(showDoNotAsk ? View.VISIBLE : View.GONE); + if (groupCount > 1) { + mCurrentGroupView.setVisibility(View.VISIBLE); + mCurrentGroupView.setText(mContext.getString(R.string.current_permission_template, + groupIndex + 1, groupCount)); + } else { + mCurrentGroupView.setVisibility(View.INVISIBLE); + } + } + + @Override + public void saveInstanceState(Bundle outState) { + outState.putString(ARG_GROUP_NAME, mGroupName); + } + + @Override + public void loadInstanceState(Bundle savedInstanceState) { + mGroupName = savedInstanceState.getString(ARG_GROUP_NAME); + } + + @Override + public void onClick(View view) { + boolean granted = false; + boolean doNotAskAgain = false; + switch (view.getId()) { + case R.id.permission_allow_button: + granted = true; + break; + case R.id.permission_deny_dont_ask_again_button: + doNotAskAgain = true; + break; + } + if (mResultListener != null) { + mResultListener.onPermissionGrantResult(mGroupName, granted, doNotAskAgain); + } + } + + @Override + public void onBackPressed() { + if (mResultListener != null) { + mResultListener.onPermissionGrantResult(mGroupName, false, false); + } + } +} diff --git a/src/com/android/packageinstaller/permission/ui/television/ManagePermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/television/ManagePermissionsFragment.java new file mode 100644 index 00000000..47301f48 --- /dev/null +++ b/src/com/android/packageinstaller/permission/ui/television/ManagePermissionsFragment.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.packageinstaller.permission.ui.television; + +import android.annotation.Nullable; +import android.app.ActionBar; +import android.app.FragmentTransaction; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.v7.preference.Preference; +import android.support.v7.preference.Preference.OnPreferenceClickListener; +import android.support.v7.preference.PreferenceScreen; +import android.util.ArraySet; +import android.util.Log; +import android.view.MenuItem; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.packageinstaller.R; +import com.android.packageinstaller.permission.model.PermissionApps; +import com.android.packageinstaller.permission.model.PermissionApps.PmCache; +import com.android.packageinstaller.permission.model.PermissionGroup; +import com.android.packageinstaller.permission.model.PermissionGroups; +import com.android.packageinstaller.permission.utils.Utils; + +import java.util.List; + +public final class ManagePermissionsFragment extends PermissionsFrameFragment + implements PermissionGroups.PermissionsGroupsChangeCallback, OnPreferenceClickListener { + private static final String LOG_TAG = "ManagePermissionsFragment"; + + private static final String OS_PKG = "android"; + + private static final String EXTRA_PREFS_KEY = "extra_prefs_key"; + + private ArraySet mLauncherPkgs; + + private PermissionGroups mPermissions; + + private PreferenceScreen mExtraScreen; + + public static ManagePermissionsFragment newInstance() { + return new ManagePermissionsFragment(); + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + setLoading(true /* loading */, false /* animate */); + setHasOptionsMenu(true); + final ActionBar ab = getActivity().getActionBar(); + if (ab != null) { + ab.setDisplayHomeAsUpEnabled(true); + } + mLauncherPkgs = Utils.getLauncherPackages(getContext()); + mPermissions = new PermissionGroups(getActivity(), getLoaderManager(), this); + } + + @Override + public void onResume() { + super.onResume(); + mPermissions.refresh(); + updatePermissionsUi(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + getActivity().finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public boolean onPreferenceClick(Preference preference) { + String key = preference.getKey(); + + PermissionGroup group = mPermissions.getGroup(key); + if (group == null) { + return false; + } + + Intent intent = new Intent(Intent.ACTION_MANAGE_PERMISSION_APPS) + .putExtra(Intent.EXTRA_PERMISSION_NAME, key); + try { + getActivity().startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.w(LOG_TAG, "No app to handle " + intent); + } + + return true; + } + + @Override + public void onPermissionGroupsChanged() { + updatePermissionsUi(); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + bindPermissionUi(getActivity(), getView()); + } + + private static void bindPermissionUi(@Nullable Context context, @Nullable View rootView) { + if (context == null || rootView == null) { + return; + } + + ImageView iconView = (ImageView) rootView.findViewById(R.id.lb_icon); + if (iconView != null) { + // Set the icon as the background instead of the image because ImageView + // doesn't properly scale vector drawables beyond their intrinsic size + Drawable icon = context.getDrawable(R.drawable.ic_lock); + icon.setTint(context.getColor(R.color.off_white)); + iconView.setBackground(icon); + } + TextView titleView = (TextView) rootView.findViewById(R.id.lb_title); + if (titleView != null) { + titleView.setText(R.string.app_permissions); + } + TextView breadcrumbView = (TextView) rootView.findViewById(R.id.lb_breadcrumb); + if (breadcrumbView != null) { + breadcrumbView.setText(R.string.app_permissions_breadcrumb); + } + } + + private void updatePermissionsUi() { + Context context = getPreferenceManager().getContext(); + if (context == null) { + return; + } + + List groups = mPermissions.getGroups(); + PreferenceScreen screen = getPreferenceScreen(); + + // Use this to speed up getting the info for all of the PermissionApps below. + // Create a new one for each refresh to make sure it has fresh data. + PmCache cache = new PmCache(getContext().getPackageManager()); + for (PermissionGroup group : groups) { + boolean isSystemPermission = group.getDeclaringPackage().equals(OS_PKG); + + Preference preference = findPreference(group.getName()); + if (preference == null && mExtraScreen != null) { + preference = mExtraScreen.findPreference(group.getName()); + } + if (preference == null) { + preference = new Preference(context); + preference.setOnPreferenceClickListener(this); + preference.setKey(group.getName()); + preference.setIcon(Utils.applyTint(context, group.getIcon(), + android.R.attr.colorControlNormal)); + preference.setTitle(group.getLabel()); + // Set blank summary so that no resizing/jumping happens when the summary is loaded. + preference.setSummary(" "); + preference.setPersistent(false); + if (isSystemPermission) { + screen.addPreference(preference); + } else { + if (mExtraScreen == null) { + mExtraScreen = getPreferenceManager().createPreferenceScreen(context); + } + mExtraScreen.addPreference(preference); + } + } + final Preference finalPref = preference; + + new PermissionApps(getContext(), group.getName(), new PermissionApps.Callback() { + @Override + public void onPermissionsLoaded(PermissionApps permissionApps) { + if (getActivity() == null) { + return; + } + int granted = permissionApps.getGrantedCount(mLauncherPkgs); + int total = permissionApps.getTotalCount(mLauncherPkgs); + finalPref.setSummary(getString(R.string.app_permissions_group_summary, + granted, total)); + } + }, cache).refresh(false); + } + + if (mExtraScreen != null && mExtraScreen.getPreferenceCount() > 0 + && screen.findPreference(EXTRA_PREFS_KEY) == null) { + Preference extraScreenPreference = new Preference(context); + extraScreenPreference.setKey(EXTRA_PREFS_KEY); + extraScreenPreference.setIcon(Utils.applyTint(context, + R.drawable.ic_more_items, + android.R.attr.colorControlNormal)); + extraScreenPreference.setTitle(R.string.additional_permissions); + extraScreenPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + AdditionalPermissionsFragment frag = new AdditionalPermissionsFragment(); + frag.setTargetFragment(ManagePermissionsFragment.this, 0); + FragmentTransaction ft = getFragmentManager().beginTransaction(); + ft.replace(android.R.id.content, frag); + ft.addToBackStack(null); + ft.commit(); + return true; + } + }); + int count = mExtraScreen.getPreferenceCount(); + extraScreenPreference.setSummary(getResources().getQuantityString( + R.plurals.additional_permissions_more, count, count)); + screen.addPreference(extraScreenPreference); + } + if (screen.getPreferenceCount() != 0) { + setLoading(false /* loading */, true /* animate */); + } + } + + public static class AdditionalPermissionsFragment extends PermissionsFrameFragment { + @Override + public void onCreate(Bundle icicle) { + setLoading(true /* loading */, false /* animate */); + super.onCreate(icicle); + getActivity().setTitle(R.string.additional_permissions); + setHasOptionsMenu(true); + } + + @Override + public void onDestroy() { + getActivity().setTitle(R.string.app_permissions); + super.onDestroy(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + getFragmentManager().popBackStack(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + bindPermissionUi(getActivity(), getView()); + } + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + setPreferenceScreen(((ManagePermissionsFragment) getTargetFragment()).mExtraScreen); + setLoading(false /* loading */, true /* animate */); + } + } +} diff --git a/src/com/android/packageinstaller/permission/ui/television/PermissionAppsFragment.java b/src/com/android/packageinstaller/permission/ui/television/PermissionAppsFragment.java new file mode 100644 index 00000000..0f240bef --- /dev/null +++ b/src/com/android/packageinstaller/permission/ui/television/PermissionAppsFragment.java @@ -0,0 +1,435 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.packageinstaller.permission.ui.television; + +import android.app.ActionBar; +import android.app.AlertDialog; +import android.app.Fragment; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.v14.preference.SwitchPreference; +import android.support.v4.util.ArrayMap; +import android.support.v7.preference.Preference; +import android.support.v7.preference.Preference.OnPreferenceChangeListener; +import android.support.v7.preference.Preference.OnPreferenceClickListener; +import android.support.v7.preference.PreferenceScreen; +import android.util.ArraySet; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.packageinstaller.DeviceUtils; +import com.android.packageinstaller.R; +import com.android.packageinstaller.permission.model.AppPermissionGroup; +import com.android.packageinstaller.permission.model.PermissionApps; +import com.android.packageinstaller.permission.model.PermissionApps.Callback; +import com.android.packageinstaller.permission.model.PermissionApps.PermissionApp; +import com.android.packageinstaller.permission.ui.OverlayTouchActivity; +import com.android.packageinstaller.permission.utils.LocationUtils; +import com.android.packageinstaller.permission.utils.SafetyNetLogger; +import com.android.packageinstaller.permission.utils.Utils; + +import java.util.ArrayList; +import java.util.List; + +public final class PermissionAppsFragment extends PermissionsFrameFragment implements Callback, + OnPreferenceChangeListener { + + private static final int MENU_SHOW_SYSTEM = Menu.FIRST; + private static final int MENU_HIDE_SYSTEM = Menu.FIRST + 1; + private static final String KEY_SHOW_SYSTEM_PREFS = "_showSystem"; + + public static PermissionAppsFragment newInstance(String permissionName) { + return setPermissionName(new PermissionAppsFragment(), permissionName); + } + + private static T setPermissionName(T fragment, String permissionName) { + Bundle arguments = new Bundle(); + arguments.putString(Intent.EXTRA_PERMISSION_NAME, permissionName); + fragment.setArguments(arguments); + return fragment; + } + + private PermissionApps mPermissionApps; + + private PreferenceScreen mExtraScreen; + + private ArrayMap mToggledGroups; + private ArraySet mLauncherPkgs; + private boolean mHasConfirmedRevoke; + + private boolean mShowSystem; + private MenuItem mShowSystemMenu; + private MenuItem mHideSystemMenu; + + private Callback mOnPermissionsLoadedListener; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setLoading(true /* loading */, false /* animate */); + setHasOptionsMenu(true); + final ActionBar ab = getActivity().getActionBar(); + if (ab != null) { + ab.setDisplayHomeAsUpEnabled(true); + } + mLauncherPkgs = Utils.getLauncherPackages(getContext()); + + String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME); + mPermissionApps = new PermissionApps(getActivity(), groupName, this); + mPermissionApps.refresh(true); + } + + @Override + public void onResume() { + super.onResume(); + mPermissionApps.refresh(true); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + mShowSystemMenu = menu.add(Menu.NONE, MENU_SHOW_SYSTEM, Menu.NONE, + R.string.menu_show_system); + mHideSystemMenu = menu.add(Menu.NONE, MENU_HIDE_SYSTEM, Menu.NONE, + R.string.menu_hide_system); + updateMenu(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + getActivity().finish(); + return true; + case MENU_SHOW_SYSTEM: + case MENU_HIDE_SYSTEM: + mShowSystem = item.getItemId() == MENU_SHOW_SYSTEM; + if (mPermissionApps.getApps() != null) { + onPermissionsLoaded(mPermissionApps); + } + updateMenu(); + break; + } + return super.onOptionsItemSelected(item); + } + + private void updateMenu() { + mShowSystemMenu.setVisible(!mShowSystem); + mHideSystemMenu.setVisible(mShowSystem); + } + + @Override + protected void onSetEmptyText(TextView textView) { + textView.setText(R.string.no_apps); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + bindUi(this, mPermissionApps); + } + + private static void bindUi(Fragment fragment, PermissionApps permissionApps) { + final Drawable icon = permissionApps.getIcon(); + final CharSequence label = permissionApps.getLabel(); + final ActionBar ab = fragment.getActivity().getActionBar(); + if (ab != null) { + ab.setTitle(fragment.getString(R.string.permission_title, label)); + } + + final ViewGroup rootView = (ViewGroup) fragment.getView(); + final ImageView iconView = (ImageView) rootView.findViewById(R.id.lb_icon); + if (iconView != null) { + // Set the icon as the background instead of the image because ImageView + // doesn't properly scale vector drawables beyond their intrinsic size + iconView.setBackground(icon); + } + final TextView titleView = (TextView) rootView.findViewById(R.id.lb_title); + if (titleView != null) { + titleView.setText(label); + } + final TextView breadcrumbView = (TextView) rootView.findViewById(R.id.lb_breadcrumb); + if (breadcrumbView != null) { + breadcrumbView.setText(R.string.app_permissions); + } + } + + private void setOnPermissionsLoadedListener(Callback callback) { + mOnPermissionsLoadedListener = callback; + } + + @Override + public void onPermissionsLoaded(PermissionApps permissionApps) { + Context context = getPreferenceManager().getContext(); + + if (context == null) { + return; + } + + boolean isTelevision = DeviceUtils.isTelevision(context); + PreferenceScreen screen = getPreferenceScreen(); + + ArraySet preferencesToRemove = new ArraySet<>(); + for (int i = 0, n = screen.getPreferenceCount(); i < n; i++) { + preferencesToRemove.add(screen.getPreference(i).getKey()); + } + if (mExtraScreen != null) { + for (int i = 0, n = mExtraScreen.getPreferenceCount(); i < n; i++) { + preferencesToRemove.add(mExtraScreen.getPreference(i).getKey()); + } + } + + for (PermissionApp app : permissionApps.getApps()) { + if (!Utils.shouldShowPermission(app)) { + continue; + } + + String key = app.getKey(); + preferencesToRemove.remove(key); + Preference existingPref = screen.findPreference(key); + if (existingPref == null && mExtraScreen != null) { + existingPref = mExtraScreen.findPreference(key); + } + + boolean isSystemApp = Utils.isSystem(app, mLauncherPkgs); + if (isSystemApp && !isTelevision && !mShowSystem) { + if (existingPref != null) { + screen.removePreference(existingPref); + } + continue; + } + + if (existingPref != null) { + // If existing preference - only update its state. + if (app.isPolicyFixed()) { + existingPref.setSummary(getString( + R.string.permission_summary_enforced_by_policy)); + } + existingPref.setPersistent(false); + existingPref.setEnabled(!app.isPolicyFixed()); + if (existingPref instanceof SwitchPreference) { + ((SwitchPreference) existingPref) + .setChecked(app.areRuntimePermissionsGranted()); + } + continue; + } + + SwitchPreference pref = new SwitchPreference(context); + pref.setOnPreferenceChangeListener(this); + pref.setKey(app.getKey()); + pref.setIcon(app.getIcon()); + pref.setTitle(app.getLabel()); + if (app.isPolicyFixed()) { + pref.setSummary(getString(R.string.permission_summary_enforced_by_policy)); + } + pref.setPersistent(false); + pref.setEnabled(!app.isPolicyFixed()); + pref.setChecked(app.areRuntimePermissionsGranted()); + + if (isSystemApp && isTelevision) { + if (mExtraScreen == null) { + mExtraScreen = getPreferenceManager().createPreferenceScreen(context); + } + mExtraScreen.addPreference(pref); + } else { + screen.addPreference(pref); + } + } + + if (mExtraScreen != null) { + preferencesToRemove.remove(KEY_SHOW_SYSTEM_PREFS); + Preference pref = screen.findPreference(KEY_SHOW_SYSTEM_PREFS); + + if (pref == null) { + pref = new Preference(context); + pref.setKey(KEY_SHOW_SYSTEM_PREFS); + pref.setIcon(Utils.applyTint(context, R.drawable.ic_toc, + android.R.attr.colorControlNormal)); + pref.setTitle(R.string.preference_show_system_apps); + pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + SystemAppsFragment frag = new SystemAppsFragment(); + setPermissionName(frag, getArguments().getString(Intent.EXTRA_PERMISSION_NAME)); + frag.setTargetFragment(PermissionAppsFragment.this, 0); + getFragmentManager().beginTransaction() + .replace(android.R.id.content, frag) + .addToBackStack("SystemApps") + .commit(); + return true; + } + }); + screen.addPreference(pref); + } + + int grantedCount = 0; + for (int i = 0, n = mExtraScreen.getPreferenceCount(); i < n; i++) { + if (((SwitchPreference) mExtraScreen.getPreference(i)).isChecked()) { + grantedCount++; + } + } + pref.setSummary(getString(R.string.app_permissions_group_summary, + grantedCount, mExtraScreen.getPreferenceCount())); + } + + for (String key : preferencesToRemove) { + Preference pref = screen.findPreference(key); + if (pref != null) { + screen.removePreference(pref); + } else if (mExtraScreen != null) { + pref = mExtraScreen.findPreference(key); + if (pref != null) { + mExtraScreen.removePreference(pref); + } + } + } + + setLoading(false /* loading */, true /* animate */); + + if (mOnPermissionsLoadedListener != null) { + mOnPermissionsLoadedListener.onPermissionsLoaded(permissionApps); + } + } + + @Override + public boolean onPreferenceChange(final Preference preference, Object newValue) { + String pkg = preference.getKey(); + final PermissionApp app = mPermissionApps.getApp(pkg); + + if (app == null) { + return false; + } + + OverlayTouchActivity activity = (OverlayTouchActivity) getActivity(); + if (activity.isObscuredTouch()) { + activity.showOverlayDialog(); + return false; + } + + addToggledGroup(app.getPackageName(), app.getPermissionGroup()); + + if (LocationUtils.isLocationGroupAndProvider(mPermissionApps.getGroupName(), + app.getPackageName())) { + LocationUtils.showLocationDialog(getContext(), app.getLabel()); + return false; + } + if (newValue == Boolean.TRUE) { + app.grantRuntimePermissions(); + } else { + final boolean grantedByDefault = app.hasGrantedByDefaultPermissions(); + if (grantedByDefault || (!app.hasRuntimePermissions() && !mHasConfirmedRevoke)) { + new AlertDialog.Builder(getContext()) + .setMessage(grantedByDefault ? R.string.system_warning + : R.string.old_sdk_deny_warning) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.grant_dialog_button_deny, + new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + ((SwitchPreference) preference).setChecked(false); + app.revokeRuntimePermissions(); + if (!grantedByDefault) { + mHasConfirmedRevoke = true; + } + } + }) + .show(); + return false; + } else { + app.revokeRuntimePermissions(); + } + } + return true; + } + + @Override + public void onPause() { + super.onPause(); + logToggledGroups(); + } + + private void addToggledGroup(String packageName, AppPermissionGroup group) { + if (mToggledGroups == null) { + mToggledGroups = new ArrayMap<>(); + } + // Double toggle is back to initial state. + if (mToggledGroups.containsKey(packageName)) { + mToggledGroups.remove(packageName); + } else { + mToggledGroups.put(packageName, group); + } + } + + private void logToggledGroups() { + if (mToggledGroups != null) { + final int groupCount = mToggledGroups.size(); + for (int i = 0; i < groupCount; i++) { + String packageName = mToggledGroups.keyAt(i); + List groups = new ArrayList<>(); + groups.add(mToggledGroups.valueAt(i)); + SafetyNetLogger.logPermissionsToggled(packageName, groups); + } + mToggledGroups = null; + } + } + + public static class SystemAppsFragment extends PermissionsFrameFragment implements Callback { + PermissionAppsFragment mOuterFragment; + + @Override + public void onCreate(Bundle savedInstanceState) { + mOuterFragment = (PermissionAppsFragment) getTargetFragment(); + setLoading(true /* loading */, false /* animate */); + super.onCreate(savedInstanceState); + } + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + if (mOuterFragment.mExtraScreen != null) { + setPreferenceScreen(); + } else { + mOuterFragment.setOnPermissionsLoadedListener(this); + } + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME); + PermissionApps permissionApps = new PermissionApps(getActivity(), groupName, null); + bindUi(this, permissionApps); + } + + @Override + public void onPermissionsLoaded(PermissionApps permissionApps) { + setPreferenceScreen(); + mOuterFragment.setOnPermissionsLoadedListener(null); + } + + private void setPreferenceScreen() { + setPreferenceScreen(mOuterFragment.mExtraScreen); + setLoading(false /* loading */, true /* animate */); + } + } +} diff --git a/src/com/android/packageinstaller/permission/ui/television/PermissionsFrameFragment.java b/src/com/android/packageinstaller/permission/ui/television/PermissionsFrameFragment.java new file mode 100644 index 00000000..bc0e8457 --- /dev/null +++ b/src/com/android/packageinstaller/permission/ui/television/PermissionsFrameFragment.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.permission.ui.television; + +import android.annotation.Nullable; +import android.os.Bundle; +import android.support.v14.preference.PreferenceFragment; +import android.support.v17.leanback.widget.VerticalGridView; +import android.support.v7.preference.PreferenceScreen; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.AdapterDataObserver; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.Animation.AnimationListener; +import android.view.animation.AnimationUtils; +import android.widget.TextView; + +import com.android.packageinstaller.R; +import com.android.packageinstaller.permission.utils.Utils; + +public abstract class PermissionsFrameFragment extends PreferenceFragment { + + private static final float WINDOW_ALIGNMENT_OFFSET_PERCENT = 50; + + private ViewGroup mPreferencesContainer; + + // TV-specific instance variables + @Nullable private VerticalGridView mGridView; + + private View mLoadingView; + private ViewGroup mPrefsView; + private boolean mIsLoading; + + /** + * Returns the view group that holds the preferences objects. This will + * only be set after {@link #onCreateView} has been called. + */ + protected final ViewGroup getPreferencesContainer() { + return mPreferencesContainer; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.permissions_frame, container, + false); + mPrefsView = (ViewGroup) rootView.findViewById(R.id.prefs_container); + if (mPrefsView == null) { + mPrefsView = rootView; + } + mLoadingView = rootView.findViewById(R.id.loading_container); + mPreferencesContainer = (ViewGroup) super.onCreateView( + inflater, mPrefsView, savedInstanceState); + setLoading(mIsLoading, false, true /* force */); + mPrefsView.addView(mPreferencesContainer); + return rootView; + } + + @Override + public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) { + PreferenceScreen preferences = getPreferenceScreen(); + if (preferences == null) { + preferences = getPreferenceManager().createPreferenceScreen(getActivity()); + setPreferenceScreen(preferences); + } + } + + protected void setLoading(boolean loading, boolean animate) { + setLoading(loading, animate, false); + } + + private void setLoading(boolean loading, boolean animate, boolean force) { + if (mIsLoading != loading || force) { + mIsLoading = loading; + if (getView() == null) { + // If there is no created view, there is no reason to animate. + animate = false; + } + if (mPrefsView != null) { + setViewShown(mPrefsView, !loading, animate); + } + if (mLoadingView != null) { + setViewShown(mLoadingView, loading, animate); + } + } + } + + private void setViewShown(final View view, boolean shown, boolean animate) { + if (animate) { + Animation animation = AnimationUtils.loadAnimation(getContext(), + shown ? android.R.anim.fade_in : android.R.anim.fade_out); + if (shown) { + view.setVisibility(View.VISIBLE); + } else { + animation.setAnimationListener(new AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + view.setVisibility(View.INVISIBLE); + } + }); + } + view.startAnimation(animation); + } else { + view.clearAnimation(); + view.setVisibility(shown ? View.VISIBLE : View.INVISIBLE); + } + } + + @Override + public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, + Bundle savedInstanceState) { + if (Utils.isTelevision(getContext())) { + mGridView = (VerticalGridView) inflater.inflate( + R.layout.leanback_preferences_list, parent, false); + mGridView.setWindowAlignmentOffset(0); + mGridView.setWindowAlignmentOffsetPercent(WINDOW_ALIGNMENT_OFFSET_PERCENT); + mGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE); + mGridView.setFocusScrollStrategy(VerticalGridView.FOCUS_SCROLL_ALIGNED); + return mGridView; + } else { + return super.onCreateRecyclerView(inflater, parent, savedInstanceState); + } + } + + @Override + protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) { + final RecyclerView.Adapter adapter = super.onCreateAdapter(preferenceScreen); + + if (adapter != null) { + final TextView emptyView = (TextView) getView().findViewById(R.id.no_permissions); + onSetEmptyText(emptyView); + final RecyclerView recyclerView = getListView(); + adapter.registerAdapterDataObserver(new AdapterDataObserver() { + @Override + public void onChanged() { + checkEmpty(); + } + + @Override + public void onItemRangeInserted(int positionStart, int itemCount) { + checkEmpty(); + } + + @Override + public void onItemRangeRemoved(int positionStart, int itemCount) { + checkEmpty(); + } + + private void checkEmpty() { + boolean isEmpty = adapter.getItemCount() == 0; + emptyView.setVisibility(isEmpty ? View.VISIBLE : View.GONE); + recyclerView.setVisibility(isEmpty ? View.GONE : View.VISIBLE); + if (!isEmpty && mGridView != null) { + mGridView.requestFocus(); + } + } + }); + + boolean isEmpty = adapter.getItemCount() == 0; + emptyView.setVisibility(isEmpty ? View.VISIBLE : View.GONE); + recyclerView.setVisibility(isEmpty ? View.GONE : View.VISIBLE); + if (!isEmpty && mGridView != null) { + mGridView.requestFocus(); + } + } + + return adapter; + } + + /** + * Hook for subclasses to change the default text of the empty view. + * Base implementation leaves the default empty view text. + * + * @param textView the empty text view + */ + protected void onSetEmptyText(TextView textView) { + } +} + diff --git a/src/com/android/packageinstaller/permission/ui/television/SettingsWithHeader.java b/src/com/android/packageinstaller/permission/ui/television/SettingsWithHeader.java new file mode 100644 index 00000000..c7f5cda3 --- /dev/null +++ b/src/com/android/packageinstaller/permission/ui/television/SettingsWithHeader.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.permission.ui.television; + +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.packageinstaller.R; +import com.android.packageinstaller.permission.utils.Utils; + +public abstract class SettingsWithHeader extends PermissionsFrameFragment + implements OnClickListener { + + private View mHeader; + protected Intent mInfoIntent; + protected Drawable mIcon; + protected CharSequence mLabel; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + ViewGroup root = (ViewGroup) super.onCreateView(inflater, container, savedInstanceState); + + if (!Utils.isTelevision(getContext())) { + mHeader = inflater.inflate(R.layout.header, root, false); + getPreferencesContainer().addView(mHeader, 0); + updateHeader(); + } + + return root; + } + + public void setHeader(Drawable icon, CharSequence label, Intent infoIntent) { + mIcon = icon; + mLabel = label; + mInfoIntent = infoIntent; + updateHeader(); + } + + private void updateHeader() { + if (mHeader != null) { + final ImageView appIcon = (ImageView) mHeader.findViewById(R.id.icon); + appIcon.setImageDrawable(mIcon); + + final TextView appName = (TextView) mHeader.findViewById(R.id.name); + appName.setText(mLabel); + + final View info = mHeader.findViewById(R.id.info); + if (mInfoIntent == null) { + info.setVisibility(View.GONE); + } else { + info.setVisibility(View.VISIBLE); + info.setClickable(true); + info.setOnClickListener(this); + } + } + } + + @Override + public void onClick(View v) { + getActivity().startActivity(mInfoIntent); + } + +} diff --git a/src/com/android/packageinstaller/permission/utils/LocationUtils.java b/src/com/android/packageinstaller/permission/utils/LocationUtils.java index 512fcf44..0296ae80 100644 --- a/src/com/android/packageinstaller/permission/utils/LocationUtils.java +++ b/src/com/android/packageinstaller/permission/utils/LocationUtils.java @@ -36,23 +36,9 @@ public class LocationUtils { public static final String LOCATION_PERMISSION = Manifest.permission_group.LOCATION; - public static ArrayList getLocationProviders() { - ArrayList providers = new ArrayList<>(); - Resources res = Resources.getSystem(); - providers.add(res.getString( - com.android.internal.R.string.config_networkLocationProviderPackageName)); - - for (String provider : - res.getStringArray(com.android.internal.R.array.config_locationProviderPackageNames)) { - providers.add(provider); - } - - return providers; - } - public static void showLocationDialog(final Context context, CharSequence label) { new AlertDialog.Builder(context) - .setIcon(com.android.internal.R.drawable.ic_dialog_alert_material) + .setIcon(R.drawable.ic_dialog_alert_material) .setTitle(android.R.string.dialog_alert_title) .setMessage(context.getString(R.string.location_warning, label)) .setNegativeButton(R.string.ok, null) @@ -83,5 +69,4 @@ public class LocationUtils { return false; } } - } -- cgit v1.2.3