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