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 }