1 /* 2 * Copyright (C) 2021 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.dashboard; 18 19 import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_DETAILS_INTERACTION; 20 import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_DETAILS_INTERACTION__ACTION__INFO_ICON_CLICKED; 21 import static com.android.permissioncontroller.PermissionControllerStatsLog.write; 22 23 import android.app.Dialog; 24 import android.content.ActivityNotFoundException; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.pm.PackageManager; 28 import android.content.pm.ResolveInfo; 29 import android.graphics.drawable.Drawable; 30 import android.os.UserHandle; 31 import android.text.format.DateFormat; 32 import android.util.DisplayMetrics; 33 import android.util.Log; 34 import android.util.TypedValue; 35 import android.view.Gravity; 36 import android.view.LayoutInflater; 37 import android.view.View; 38 import android.view.ViewGroup; 39 import android.view.WindowManager; 40 import android.widget.ImageView; 41 import android.widget.LinearLayout; 42 import android.widget.TextView; 43 44 import androidx.annotation.NonNull; 45 import androidx.annotation.Nullable; 46 import androidx.preference.Preference; 47 import androidx.preference.PreferenceViewHolder; 48 import androidx.recyclerview.widget.LinearLayoutManager; 49 import androidx.recyclerview.widget.RecyclerView; 50 51 import com.android.permissioncontroller.R; 52 import com.android.permissioncontroller.permission.utils.KotlinUtils; 53 54 import java.util.ArrayList; 55 import java.util.List; 56 import java.util.Objects; 57 58 /** 59 * Preference for the permission history page 60 */ 61 public class PermissionHistoryPreference extends Preference { 62 63 private static final String LOG_TAG = "PermissionHistoryPreference"; 64 65 private final Context mContext; 66 private final UserHandle mUserHandle; 67 private final String mPackageName; 68 private final String mPermissionGroup; 69 private final String mAccessTime; 70 private final Drawable mAppIcon; 71 private final String mTitle; 72 private final float mDialogWidthScalar; 73 private final float mDialogHeightScalar; 74 private final List<Long> mAccessTimeList; 75 private final ArrayList<String> mAttributionTags; 76 private final boolean mIsLastUsage; 77 private final Intent mIntent; 78 79 private final long mSessionId; 80 81 private Drawable mWidgetIcon; 82 PermissionHistoryPreference(@onNull Context context, @NonNull UserHandle userHandle, @NonNull String pkgName, @NonNull Drawable appIcon, @NonNull String preferenceTitle, @NonNull String permissionGroup, @NonNull String accessTime, @Nullable CharSequence summaryText, @NonNull List<Long> accessTimeList, @NonNull ArrayList<String> attributionTags, boolean isLastUsage, long sessionId)83 public PermissionHistoryPreference(@NonNull Context context, 84 @NonNull UserHandle userHandle, @NonNull String pkgName, 85 @NonNull Drawable appIcon, 86 @NonNull String preferenceTitle, 87 @NonNull String permissionGroup, @NonNull String accessTime, 88 @Nullable CharSequence summaryText, @NonNull List<Long> accessTimeList, 89 @NonNull ArrayList<String> attributionTags, boolean isLastUsage, long sessionId) { 90 super(context); 91 mContext = context; 92 mUserHandle = userHandle; 93 mPackageName = pkgName; 94 mPermissionGroup = permissionGroup; 95 mAccessTime = accessTime; 96 mAppIcon = appIcon; 97 mTitle = preferenceTitle; 98 mWidgetIcon = null; 99 mAccessTimeList = accessTimeList; 100 mAttributionTags = attributionTags; 101 mIsLastUsage = isLastUsage; 102 TypedValue outValue = new TypedValue(); 103 mContext.getResources().getValue(R.dimen.permission_access_time_dialog_width_scalar, 104 outValue, true); 105 mDialogWidthScalar = outValue.getFloat(); 106 mContext.getResources().getValue(R.dimen.permission_access_time_dialog_height_scalar, 107 outValue, true); 108 mDialogHeightScalar = outValue.getFloat(); 109 mSessionId = sessionId; 110 111 setTitle(mTitle); 112 if (summaryText != null) { 113 setSummary(summaryText); 114 } 115 116 mIntent = getViewPermissionUsageForPeriodIntent(); 117 if (mIntent != null) { 118 mWidgetIcon = mContext.getDrawable(R.drawable.ic_info_outline); 119 setWidgetLayoutResource(R.layout.image_view_with_divider); 120 } 121 } 122 123 @Override onBindViewHolder(@onNull PreferenceViewHolder holder)124 public void onBindViewHolder(@NonNull PreferenceViewHolder holder) { 125 super.onBindViewHolder(holder); 126 127 ViewGroup widgetFrame = (ViewGroup) holder.findViewById(android.R.id.widget_frame); 128 LinearLayout widgetFrameParent = (LinearLayout) widgetFrame.getParent(); 129 130 View iconFrame = holder.findViewById(R.id.icon_frame); 131 widgetFrameParent.removeView(iconFrame); 132 133 ViewGroup widget = (ViewGroup) holder.findViewById(R.id.permission_history_layout); 134 if (widget == null) { 135 LayoutInflater inflater = mContext.getSystemService(LayoutInflater.class); 136 widget = (ViewGroup) inflater.inflate(R.layout.permission_history_widget, 137 widgetFrameParent, false); 138 139 widgetFrameParent.addView(widget, 0); 140 } 141 142 widgetFrameParent.setGravity(Gravity.TOP); 143 144 TextView permissionHistoryTime = widget.findViewById(R.id.permission_history_time); 145 permissionHistoryTime.setText(mAccessTime); 146 147 ImageView permissionIcon = widget.findViewById(R.id.permission_history_icon); 148 permissionIcon.setImageDrawable(mAppIcon); 149 150 ImageView widgetView = widgetFrame.findViewById(R.id.icon); 151 setInfoIcon(widgetView); 152 153 View dashLine = widget.findViewById(R.id.permission_history_dash_line); 154 dashLine.setVisibility(mIsLastUsage ? View.GONE : View.VISIBLE); 155 156 setOnPreferenceClickListener((preference) -> { 157 Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS); 158 intent.putExtra(Intent.EXTRA_USER, mUserHandle); 159 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mPackageName); 160 161 mContext.startActivity(intent); 162 return true; 163 }); 164 } 165 setHistoryIcon(ImageView widgetView)166 private void setHistoryIcon(ImageView widgetView) { 167 widgetView.setImageDrawable(mWidgetIcon); 168 widgetView.setOnClickListener(v -> { 169 Dialog dialog = new Dialog(mContext); 170 dialog.setContentView(R.layout.access_time_list_dialog); 171 172 ImageView iconView = dialog.findViewById(R.id.icon); 173 iconView.setImageDrawable(mAppIcon); 174 175 TextView titleView = dialog.findViewById(R.id.title); 176 titleView.setText(mTitle); 177 178 TextView subtitleView = dialog.findViewById(R.id.subtitle); 179 subtitleView.setText(mContext.getResources().getString( 180 R.string.permission_usage_access_dialog_subtitle, 181 KotlinUtils.INSTANCE.getPermGroupLabel(mContext, mPermissionGroup))); 182 183 RecyclerView recyclerView = dialog.findViewById(R.id.access_time_list); 184 recyclerView.setLayoutManager(new LinearLayoutManager(mContext)); 185 AccessTimeListAdapter adapter = new AccessTimeListAdapter(mAccessTimeList); 186 recyclerView.setAdapter(adapter); 187 188 if (mIntent != null) { 189 TextView learnMoreView = dialog.findViewById(R.id.learn_more); 190 learnMoreView.setVisibility(View.VISIBLE); 191 learnMoreView.setOnClickListener(v1 -> { 192 try { 193 mContext.startActivity(mIntent); 194 } catch (ActivityNotFoundException e) { 195 Log.e(LOG_TAG, "No activity found for viewing permission usage."); 196 } 197 }); 198 } 199 200 dialog.show(); 201 202 // Resize the dialog. 203 // Have to do this since the default dialog can't be set width and height. 204 DisplayMetrics displayMetrics = new DisplayMetrics(); 205 mContext.getDisplay().getMetrics(displayMetrics); 206 int displayWidth = displayMetrics.widthPixels; 207 int displayHeight = displayMetrics.heightPixels; 208 WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); 209 layoutParams.copyFrom(dialog.getWindow().getAttributes()); 210 int dialogWindowWidth = (int) (displayWidth * mDialogWidthScalar); 211 int dialogWindowHeight = (int) (displayHeight * mDialogHeightScalar); 212 layoutParams.width = dialogWindowWidth; 213 layoutParams.height = dialogWindowHeight; 214 dialog.getWindow().setAttributes(layoutParams); 215 }); 216 } 217 setInfoIcon(ImageView widgetView)218 private void setInfoIcon(ImageView widgetView) { 219 if (mIntent != null) { 220 widgetView.setImageDrawable(mWidgetIcon); 221 widgetView.setOnClickListener(v -> { 222 write(PERMISSION_DETAILS_INTERACTION, 223 mSessionId, 224 mPermissionGroup, 225 mPackageName, 226 PERMISSION_DETAILS_INTERACTION__ACTION__INFO_ICON_CLICKED); 227 try { 228 mContext.startActivity(mIntent); 229 } catch (ActivityNotFoundException e) { 230 Log.e(LOG_TAG, "No activity found for viewing permission usage."); 231 } 232 }); 233 } 234 } 235 236 /** 237 * Get a {@link Intent#ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD} intent, or null if the intent 238 * can't be handled. 239 */ getViewPermissionUsageForPeriodIntent()240 private Intent getViewPermissionUsageForPeriodIntent() { 241 Intent viewUsageIntent = new Intent(); 242 viewUsageIntent.setAction(Intent.ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD); 243 viewUsageIntent.setPackage(mPackageName); 244 viewUsageIntent.putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, mPermissionGroup); 245 viewUsageIntent.putExtra(Intent.EXTRA_ATTRIBUTION_TAGS, 246 mAttributionTags.toArray(new String[0])); 247 viewUsageIntent.putExtra(Intent.EXTRA_START_TIME, 248 mAccessTimeList.get(mAccessTimeList.size() - 1)); 249 viewUsageIntent.putExtra(Intent.EXTRA_END_TIME, mAccessTimeList.get(0)); 250 viewUsageIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 251 252 PackageManager packageManager = mContext.getPackageManager(); 253 ResolveInfo resolveInfo = packageManager.resolveActivity(viewUsageIntent, 254 PackageManager.MATCH_INSTANT); 255 if (resolveInfo != null && resolveInfo.activityInfo != null && Objects.equals( 256 resolveInfo.activityInfo.permission, 257 android.Manifest.permission.START_VIEW_PERMISSION_USAGE)) { 258 return viewUsageIntent; 259 } 260 return null; 261 } 262 263 /** 264 * Adapter for access time list dialog RecyclerView 265 */ 266 public class AccessTimeListAdapter extends 267 RecyclerView.Adapter<AccessTimeListAdapter.ViewHolder> { 268 269 /** 270 * ViewHolder for the AccessTimeListAdapter 271 */ 272 public class ViewHolder extends RecyclerView.ViewHolder { 273 public TextView accessTimeTextView; 274 ViewHolder(View itemView)275 public ViewHolder(View itemView) { 276 super(itemView); 277 278 accessTimeTextView = itemView.findViewById(R.id.access_time); 279 } 280 } 281 282 private final List<Long> mAccessTimeList; 283 AccessTimeListAdapter(List<Long> accessTimeList)284 public AccessTimeListAdapter(List<Long> accessTimeList) { 285 mAccessTimeList = accessTimeList; 286 } 287 288 @Override onCreateViewHolder(ViewGroup parent, int viewType)289 public AccessTimeListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 290 LayoutInflater inflater = LayoutInflater.from(mContext); 291 View itemView = inflater.inflate(R.layout.access_time_item, parent, false); 292 293 return new ViewHolder(itemView); 294 } 295 296 @Override onBindViewHolder(AccessTimeListAdapter.ViewHolder holder, int position)297 public void onBindViewHolder(AccessTimeListAdapter.ViewHolder holder, int position) { 298 Long accessTime = mAccessTimeList.get(position); 299 300 TextView textView = holder.accessTimeTextView; 301 textView.setText(DateFormat.getTimeFormat(mContext).format(accessTime)); 302 } 303 304 @Override getItemCount()305 public int getItemCount() { 306 return mAccessTimeList.size(); 307 } 308 } 309 }