1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.permissioncontroller.permission.ui.handheld; 18 19 import static android.Manifest.permission_group.CAMERA; 20 import static android.Manifest.permission_group.MICROPHONE; 21 22 import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID; 23 import static com.android.permissioncontroller.permission.debug.UtilsKt.getUsageDurationString; 24 import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_CALLER_NAME; 25 import static com.android.permissioncontroller.permission.ui.handheld.AppPermissionFragment.GRANT_CATEGORY; 26 import static com.android.permissioncontroller.permission.utils.KotlinUtilsKt.navigateSafe; 27 28 import android.content.Context; 29 import android.content.Intent; 30 import android.graphics.drawable.Drawable; 31 import android.os.Bundle; 32 import android.os.UserHandle; 33 import android.text.TextUtils; 34 import android.view.LayoutInflater; 35 import android.view.View; 36 import android.view.ViewGroup; 37 import android.widget.ImageView; 38 import android.widget.TextView; 39 40 import androidx.annotation.NonNull; 41 import androidx.annotation.Nullable; 42 import androidx.navigation.Navigation; 43 import androidx.preference.Preference; 44 import androidx.preference.PreferenceViewHolder; 45 46 import com.android.permissioncontroller.permission.model.AppPermissionGroup; 47 import com.android.permissioncontroller.permission.model.AppPermissionUsage.GroupUsage; 48 import com.android.permissioncontroller.R; 49 import com.android.permissioncontroller.permission.ui.LocationProviderInterceptDialog; 50 import com.android.permissioncontroller.permission.utils.LocationUtils; 51 52 import java.util.List; 53 54 /** 55 * A preference that links to the screen where a permission can be toggled. 56 */ 57 public class PermissionControlPreference extends Preference { 58 private final @NonNull Context mContext; 59 private @Nullable Drawable mWidgetIcon; 60 private @Nullable String mGranted; 61 private boolean mUseSmallerIcon; 62 private boolean mEllipsizeEnd; 63 private @Nullable List<Integer> mTitleIcons; 64 private @Nullable List<Integer> mSummaryIcons; 65 private @NonNull String mPackageName; 66 private @NonNull String mPermGroupName; 67 private @NonNull String mCaller; 68 private @NonNull long mSessionId; 69 private boolean mHasNavGraph; 70 private @NonNull UserHandle mUser; 71 PermissionControlPreference(@onNull Context context, @NonNull AppPermissionGroup group, @NonNull String caller)72 public PermissionControlPreference(@NonNull Context context, 73 @NonNull AppPermissionGroup group, @NonNull String caller) { 74 this(context, group, caller, 0); 75 } 76 PermissionControlPreference(@onNull Context context, @NonNull AppPermissionGroup group, @NonNull String caller, long sessionId)77 public PermissionControlPreference(@NonNull Context context, 78 @NonNull AppPermissionGroup group, @NonNull String caller, long sessionId) { 79 this(context, group.getApp().packageName, group.getName(), group.getUser(), caller, 80 sessionId, null, false); 81 } 82 PermissionControlPreference(@onNull Context context, @NonNull String packageName, @NonNull String permGroupName, @NonNull UserHandle user, @NonNull String caller, long sessionId, String granted, boolean hasNavGraph)83 public PermissionControlPreference(@NonNull Context context, 84 @NonNull String packageName, @NonNull String permGroupName, @NonNull UserHandle user, 85 @NonNull String caller, long sessionId, String granted, boolean hasNavGraph) { 86 super(context); 87 mContext = context; 88 mWidgetIcon = null; 89 mUseSmallerIcon = false; 90 mEllipsizeEnd = false; 91 mTitleIcons = null; 92 mSummaryIcons = null; 93 mPackageName = packageName; 94 mCaller = caller; 95 mPermGroupName = permGroupName; 96 mSessionId = sessionId; 97 mUser = user; 98 mGranted = granted; 99 mHasNavGraph = hasNavGraph; 100 } 101 102 /** 103 * Sets this preference's right icon. 104 * 105 * Note that this must be called before preference layout to take effect. 106 * 107 * @param widgetIcon the icon to use. 108 */ setRightIcon(@onNull Drawable widgetIcon)109 public void setRightIcon(@NonNull Drawable widgetIcon) { 110 mWidgetIcon = widgetIcon; 111 setWidgetLayoutResource(R.layout.image_view); 112 } 113 114 /** 115 * Sets this preference's left icon to be smaller than normal. 116 * 117 * Note that this must be called before preference layout to take effect. 118 */ useSmallerIcon()119 public void useSmallerIcon() { 120 mUseSmallerIcon = true; 121 } 122 123 /** 124 * Sets this preference's title to use an ellipsis at the end. 125 * 126 * Note that this must be called before preference layout to take effect. 127 */ setEllipsizeEnd()128 public void setEllipsizeEnd() { 129 mEllipsizeEnd = true; 130 } 131 132 /** 133 * Sets this preference's summary based on the group it represents, if applicable. 134 * 135 * @param group the permission group this preference represents. 136 */ setGroupSummary(@onNull AppPermissionGroup group)137 public void setGroupSummary(@NonNull AppPermissionGroup group) { 138 if (group.hasPermissionWithBackgroundMode() && group.areRuntimePermissionsGranted()) { 139 AppPermissionGroup backgroundGroup = group.getBackgroundPermissions(); 140 if (backgroundGroup == null || !backgroundGroup.areRuntimePermissionsGranted()) { 141 setSummary(R.string.permission_subtitle_only_in_foreground); 142 return; 143 } 144 } 145 setSummary(""); 146 } 147 148 /** 149 * Sets this preference's summary based on its permission usage. 150 * 151 * @param groupUsage the usage information 152 * @param accessTimeStr the string representing the last access time 153 */ setUsageSummary(@onNull GroupUsage groupUsage, @NonNull String accessTimeStr)154 public void setUsageSummary(@NonNull GroupUsage groupUsage, @NonNull String accessTimeStr) { 155 long backgroundAccessCount = groupUsage.getBackgroundAccessCount(); 156 long duration = 0; 157 String groupName = groupUsage.getGroup().getName(); 158 if (groupName.equals(CAMERA) || groupName.equals(MICROPHONE)) { 159 duration = groupUsage.getAccessDuration(); 160 } 161 if (backgroundAccessCount == 0) { 162 long numForegroundAccesses = groupUsage.getForegroundAccessCount(); 163 if (duration == 0) { 164 setSummary(mContext.getResources().getQuantityString( 165 R.plurals.permission_usage_summary, (int) numForegroundAccesses, 166 accessTimeStr, numForegroundAccesses)); 167 } else { 168 setSummary(mContext.getResources().getQuantityString( 169 R.plurals.permission_usage_summary_duration, (int) numForegroundAccesses, 170 accessTimeStr, numForegroundAccesses, 171 getUsageDurationString(mContext, groupUsage))); 172 } 173 } else { 174 long numAccesses = groupUsage.getAccessCount(); 175 if (duration == 0) { 176 setSummary(mContext.getResources().getQuantityString( 177 R.plurals.permission_usage_summary_background, (int) numAccesses, 178 accessTimeStr, numAccesses, backgroundAccessCount)); 179 } else { 180 setSummary(mContext.getResources().getQuantityString( 181 R.plurals.permission_usage_summary_background_duration, (int) numAccesses, 182 accessTimeStr, numAccesses, backgroundAccessCount, 183 getUsageDurationString(mContext, groupUsage))); 184 } 185 } 186 } 187 188 /** 189 * Sets this preference to show the given icons to the left of its title. 190 * 191 * @param titleIcons the icons to show. 192 */ setTitleIcons(@onNull List<Integer> titleIcons)193 public void setTitleIcons(@NonNull List<Integer> titleIcons) { 194 mTitleIcons = titleIcons; 195 setLayoutResource(R.layout.preference_usage); 196 } 197 198 @Override onBindViewHolder(PreferenceViewHolder holder)199 public void onBindViewHolder(PreferenceViewHolder holder) { 200 if (mUseSmallerIcon) { 201 ImageView icon = ((ImageView) holder.findViewById(android.R.id.icon)); 202 icon.setMaxWidth( 203 mContext.getResources().getDimensionPixelSize(R.dimen.secondary_app_icon_size)); 204 icon.setMaxHeight( 205 mContext.getResources().getDimensionPixelSize(R.dimen.secondary_app_icon_size)); 206 } 207 208 super.onBindViewHolder(holder); 209 210 if (mWidgetIcon != null) { 211 View widgetFrame = holder.findViewById(android.R.id.widget_frame); 212 ((ImageView) widgetFrame.findViewById(R.id.icon)).setImageDrawable(mWidgetIcon); 213 } 214 215 if (mEllipsizeEnd) { 216 TextView title = (TextView) holder.findViewById(android.R.id.title); 217 title.setMaxLines(1); 218 title.setEllipsize(TextUtils.TruncateAt.END); 219 } 220 221 setIcons(holder, mSummaryIcons, R.id.summary_widget_frame); 222 setIcons(holder, mTitleIcons, R.id.title_widget_frame); 223 224 setOnPreferenceClickListener(pref -> { 225 if (LocationUtils.isLocationGroupAndProvider( 226 mContext, mPermGroupName, mPackageName)) { 227 Intent intent = new Intent(mContext, LocationProviderInterceptDialog.class); 228 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mPackageName); 229 mContext.startActivityAsUser(intent, mUser); 230 } else if (LocationUtils.isLocationGroupAndControllerExtraPackage( 231 mContext, mPermGroupName, mPackageName)) { 232 // Redirect to location controller extra package settings. 233 LocationUtils.startLocationControllerExtraPackageSettings(mContext, mUser); 234 } else if (mHasNavGraph) { 235 Bundle args = new Bundle(); 236 args.putString(Intent.EXTRA_PACKAGE_NAME, mPackageName); 237 args.putString(Intent.EXTRA_PERMISSION_GROUP_NAME, mPermGroupName); 238 args.putParcelable(Intent.EXTRA_USER, mUser); 239 args.putString(EXTRA_CALLER_NAME, mCaller); 240 args.putLong(EXTRA_SESSION_ID, mSessionId); 241 args.putString(GRANT_CATEGORY, mGranted); 242 navigateSafe(Navigation.findNavController(holder.itemView), R.id.perm_groups_to_app, 243 args); 244 } else { 245 // TODO ntmyren, yianyliu: Remove once Auto has been adapted to new permission model 246 // see b/150229448 247 Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSION); 248 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mPackageName); 249 intent.putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, mPermGroupName); 250 intent.putExtra(Intent.EXTRA_USER, mUser); 251 intent.putExtra(EXTRA_CALLER_NAME, mCaller); 252 intent.putExtra(EXTRA_SESSION_ID, mSessionId); 253 mContext.startActivity(intent); 254 } 255 return true; 256 }); 257 } 258 setIcons(PreferenceViewHolder holder, @Nullable List<Integer> icons, int frameId)259 private void setIcons(PreferenceViewHolder holder, @Nullable List<Integer> icons, int frameId) { 260 ViewGroup frame = (ViewGroup) holder.findViewById(frameId); 261 if (icons != null && !icons.isEmpty()) { 262 frame.setVisibility(View.VISIBLE); 263 frame.removeAllViews(); 264 LayoutInflater inflater = mContext.getSystemService(LayoutInflater.class); 265 int numIcons = icons.size(); 266 for (int i = 0; i < numIcons; i++) { 267 ViewGroup group = (ViewGroup) inflater.inflate(R.layout.title_summary_image_view, 268 null); 269 ImageView imageView = group.requireViewById(R.id.icon); 270 imageView.setImageResource(icons.get(i)); 271 frame.addView(group); 272 } 273 } else if (frame != null) { 274 frame.setVisibility(View.GONE); 275 } 276 } 277 } 278