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.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP; 20 21 import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID; 22 import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_CALLER_NAME; 23 import static com.android.permissioncontroller.permission.ui.handheld.AppPermissionFragment.GRANT_CATEGORY; 24 import static com.android.permissioncontroller.permission.utils.KotlinUtilsKt.navigateSafe; 25 26 import android.Manifest; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.graphics.drawable.Drawable; 30 import android.os.Bundle; 31 import android.os.UserHandle; 32 import android.text.TextUtils; 33 import android.view.LayoutInflater; 34 import android.view.View; 35 import android.view.ViewGroup; 36 import android.widget.ImageView; 37 import android.widget.TextView; 38 39 import androidx.annotation.NonNull; 40 import androidx.annotation.Nullable; 41 import androidx.navigation.Navigation; 42 import androidx.preference.Preference; 43 import androidx.preference.PreferenceViewHolder; 44 45 import com.android.permissioncontroller.R; 46 import com.android.permissioncontroller.permission.model.AppPermissionGroup; 47 import com.android.permissioncontroller.permission.ui.LocationProviderInterceptDialog; 48 import com.android.permissioncontroller.permission.utils.LocationUtils; 49 import com.android.permissioncontroller.permission.utils.Utils; 50 51 import java.util.List; 52 53 /** 54 * A preference that links to the screen where a permission can be toggled. 55 */ 56 public class PermissionControlPreference extends Preference { 57 private final @NonNull Context mContext; 58 private @Nullable Drawable mWidgetIcon; 59 private @Nullable String mWidgetIconContentDescription; 60 private @Nullable View.OnClickListener mWidgetIconOnClickListener; 61 private @Nullable String mGranted; 62 private boolean mUseSmallerIcon; 63 private boolean mEllipsizeEnd; 64 private @Nullable List<Integer> mTitleIcons; 65 private @Nullable List<Integer> mSummaryIcons; 66 private @NonNull String mPackageName; 67 private @NonNull String mPermGroupName; 68 private @NonNull String mCaller; 69 private @NonNull long mSessionId; 70 private boolean mHasNavGraph; 71 private @NonNull UserHandle mUser; 72 PermissionControlPreference(@onNull Context context, @NonNull AppPermissionGroup group, @NonNull String caller)73 public PermissionControlPreference(@NonNull Context context, 74 @NonNull AppPermissionGroup group, @NonNull String caller) { 75 this(context, group, caller, 0); 76 } 77 PermissionControlPreference(@onNull Context context, @NonNull AppPermissionGroup group, @NonNull String caller, long sessionId)78 public PermissionControlPreference(@NonNull Context context, 79 @NonNull AppPermissionGroup group, @NonNull String caller, long sessionId) { 80 this(context, group.getApp().packageName, group.getName(), group.getUser(), caller, 81 sessionId, null, false); 82 } 83 PermissionControlPreference(@onNull Context context, @NonNull String packageName, @NonNull String permGroupName, @NonNull UserHandle user, @NonNull String caller, long sessionId, String granted, boolean hasNavGraph)84 public PermissionControlPreference(@NonNull Context context, 85 @NonNull String packageName, @NonNull String permGroupName, @NonNull UserHandle user, 86 @NonNull String caller, long sessionId, String granted, boolean hasNavGraph) { 87 super(context); 88 mContext = context; 89 mWidgetIcon = null; 90 mUseSmallerIcon = false; 91 mEllipsizeEnd = false; 92 mTitleIcons = null; 93 mSummaryIcons = null; 94 mPackageName = packageName; 95 mCaller = caller; 96 mPermGroupName = permGroupName; 97 mSessionId = sessionId; 98 mUser = user; 99 mGranted = granted; 100 mHasNavGraph = hasNavGraph; 101 } 102 103 /** 104 * Sets this preference's right icon. 105 * 106 * Note that this must be called before preference layout to take effect. 107 * 108 * @param widgetIcon the icon to use. 109 */ setRightIcon(@onNull Drawable widgetIcon)110 public void setRightIcon(@NonNull Drawable widgetIcon) { 111 mWidgetIcon = widgetIcon; 112 setWidgetLayoutResource(R.layout.image_view); 113 } 114 115 /** 116 * Sets this preference's right icon with an onClickListener. 117 * 118 * Note that this must be called before preference layout to take effect. 119 * 120 * @param widgetIcon the icon to use. 121 * @param listener the onClickListener attached to the icon. 122 */ setRightIcon(@onNull Drawable widgetIcon, @NonNull String widgetIconContentDescription, @NonNull View.OnClickListener listener)123 public void setRightIcon(@NonNull Drawable widgetIcon, 124 @NonNull String widgetIconContentDescription, @NonNull View.OnClickListener listener) { 125 mWidgetIcon = widgetIcon; 126 mWidgetIconContentDescription = widgetIconContentDescription; 127 setWidgetLayoutResource(R.layout.image_view_with_divider); 128 mWidgetIconOnClickListener = listener; 129 } 130 131 /** 132 * Sets this preference's left icon to be smaller than normal. 133 * 134 * Note that this must be called before preference layout to take effect. 135 */ useSmallerIcon()136 public void useSmallerIcon() { 137 mUseSmallerIcon = true; 138 } 139 140 /** 141 * Sets this preference's title to use an ellipsis at the end. 142 * 143 * Note that this must be called before preference layout to take effect. 144 */ setEllipsizeEnd()145 public void setEllipsizeEnd() { 146 mEllipsizeEnd = true; 147 } 148 149 /** 150 * Sets this preference's summary based on the group it represents, if applicable. 151 * 152 * @param group the permission group this preference represents. 153 */ setGroupSummary(@onNull AppPermissionGroup group)154 public void setGroupSummary(@NonNull AppPermissionGroup group) { 155 if (group.hasPermissionWithBackgroundMode() && group.areRuntimePermissionsGranted()) { 156 AppPermissionGroup backgroundGroup = group.getBackgroundPermissions(); 157 if (backgroundGroup == null || !backgroundGroup.areRuntimePermissionsGranted()) { 158 setSummary(R.string.permission_subtitle_only_in_foreground); 159 return; 160 } 161 } 162 setSummary(""); 163 } 164 165 /** 166 * Sets this preference to show the given icons to the left of its title. 167 * 168 * @param titleIcons the icons to show. 169 */ setTitleIcons(@onNull List<Integer> titleIcons)170 public void setTitleIcons(@NonNull List<Integer> titleIcons) { 171 mTitleIcons = titleIcons; 172 setLayoutResource(R.layout.preference_usage); 173 } 174 175 @Override onBindViewHolder(PreferenceViewHolder holder)176 public void onBindViewHolder(PreferenceViewHolder holder) { 177 if (mUseSmallerIcon) { 178 ImageView icon = ((ImageView) holder.findViewById(android.R.id.icon)); 179 icon.setMaxWidth( 180 mContext.getResources().getDimensionPixelSize(R.dimen.secondary_app_icon_size)); 181 icon.setMaxHeight( 182 mContext.getResources().getDimensionPixelSize(R.dimen.secondary_app_icon_size)); 183 } 184 185 super.onBindViewHolder(holder); 186 187 if (mWidgetIcon != null) { 188 View widgetFrame = holder.findViewById(android.R.id.widget_frame); 189 ImageView widgetIcon = widgetFrame.findViewById(R.id.icon); 190 widgetIcon.setImageDrawable(mWidgetIcon); 191 widgetIcon.setContentDescription(mWidgetIconContentDescription); 192 193 if (mWidgetIconOnClickListener != null) { 194 widgetFrame.findViewById(R.id.icon).setOnClickListener(mWidgetIconOnClickListener); 195 View preferenceRootView = holder.itemView; 196 preferenceRootView.setPaddingRelative( 197 preferenceRootView.getPaddingStart(), preferenceRootView.getPaddingTop(), 198 0, preferenceRootView.getPaddingBottom()); 199 } 200 } 201 202 if (mEllipsizeEnd) { 203 TextView title = (TextView) holder.findViewById(android.R.id.title); 204 title.setMaxLines(1); 205 title.setEllipsize(TextUtils.TruncateAt.END); 206 } 207 208 setIcons(holder, mSummaryIcons, R.id.summary_widget_frame); 209 setIcons(holder, mTitleIcons, R.id.title_widget_frame); 210 211 setOnPreferenceClickListener(pref -> { 212 if (LocationUtils.isLocationGroupAndProvider( 213 mContext, mPermGroupName, mPackageName)) { 214 Intent intent = new Intent(mContext, LocationProviderInterceptDialog.class); 215 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mPackageName); 216 mContext.startActivityAsUser(intent, mUser); 217 } else if (LocationUtils.isLocationGroupAndControllerExtraPackage( 218 mContext, mPermGroupName, mPackageName)) { 219 // Redirect to location controller extra package settings. 220 LocationUtils.startLocationControllerExtraPackageSettings(mContext, mUser); 221 } else if (mHasNavGraph) { 222 if (mPermGroupName.equals(Manifest.permission_group.NOTIFICATIONS)) { 223 Utils.navigateToAppNotificationSettings(mContext, mPackageName, mUser); 224 return true; 225 } 226 if (Utils.isHealthPermissionUiEnabled() 227 && mPermGroupName.equals(HEALTH_PERMISSION_GROUP)) { 228 Utils.navigateToAppHealthConnectSettings(mContext, mPackageName, mUser); 229 return true; 230 } 231 Bundle args = new Bundle(); 232 args.putString(Intent.EXTRA_PACKAGE_NAME, mPackageName); 233 args.putString(Intent.EXTRA_PERMISSION_GROUP_NAME, mPermGroupName); 234 args.putParcelable(Intent.EXTRA_USER, mUser); 235 args.putString(EXTRA_CALLER_NAME, mCaller); 236 args.putLong(EXTRA_SESSION_ID, mSessionId); 237 args.putString(GRANT_CATEGORY, mGranted); 238 navigateSafe(Navigation.findNavController(holder.itemView), R.id.perm_groups_to_app, 239 args); 240 } else { 241 // TODO ntmyren, yianyliu: Remove once Auto has been adapted to new permission model 242 // see b/150229448 243 Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSION); 244 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mPackageName); 245 intent.putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, mPermGroupName); 246 intent.putExtra(Intent.EXTRA_USER, mUser); 247 intent.putExtra(EXTRA_CALLER_NAME, mCaller); 248 intent.putExtra(EXTRA_SESSION_ID, mSessionId); 249 mContext.startActivity(intent); 250 } 251 return true; 252 }); 253 } 254 setIcons(PreferenceViewHolder holder, @Nullable List<Integer> icons, int frameId)255 private void setIcons(PreferenceViewHolder holder, @Nullable List<Integer> icons, int frameId) { 256 ViewGroup frame = (ViewGroup) holder.findViewById(frameId); 257 if (icons != null && !icons.isEmpty()) { 258 frame.setVisibility(View.VISIBLE); 259 frame.removeAllViews(); 260 LayoutInflater inflater = mContext.getSystemService(LayoutInflater.class); 261 int numIcons = icons.size(); 262 for (int i = 0; i < numIcons; i++) { 263 ViewGroup group = (ViewGroup) inflater.inflate(R.layout.title_summary_image_view, 264 null); 265 ImageView imageView = group.requireViewById(R.id.icon); 266 imageView.setImageResource(icons.get(i)); 267 frame.addView(group); 268 } 269 } else if (frame != null) { 270 frame.setVisibility(View.GONE); 271 } 272 } 273 } 274