1 /*
2  * Copyright (C) 2020 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.settings.security;
18 
19 import android.content.Context;
20 import android.content.pm.ApplicationInfo;
21 import android.content.pm.PackageManager;
22 import android.net.Uri;
23 import android.security.AppUriAuthenticationPolicy;
24 import android.text.TextUtils;
25 import android.view.LayoutInflater;
26 import android.view.View;
27 import android.view.ViewGroup;
28 import android.widget.ImageView;
29 import android.widget.RelativeLayout;
30 import android.widget.TextView;
31 
32 import androidx.annotation.NonNull;
33 import androidx.recyclerview.widget.LinearLayoutManager;
34 import androidx.recyclerview.widget.RecyclerView;
35 
36 import com.android.settings.R;
37 
38 import java.util.ArrayList;
39 import java.util.List;
40 import java.util.Map;
41 
42 /**
43  * Adapter for the requesting credential management app. This adapter displays the details of the
44  * requesting app, including its authentication policy, when {@link RequestManageCredentials}
45  * is started.
46  * <p>
47  *
48  * @hide
49  * @see RequestManageCredentials
50  * @see AppUriAuthenticationPolicy
51  */
52 public class CredentialManagementAppAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
53 
54     private static final int HEADER_VIEW = 1;
55 
56     private final String mCredentialManagerPackage;
57     private final Map<String, Map<Uri, String>> mAppUriAuthentication;
58     private final List<String> mSortedAppNames;
59 
60     private final Context mContext;
61     private final PackageManager mPackageManager;
62     private final RecyclerView.RecycledViewPool mViewPool;
63 
64     private final boolean mIncludeHeader;
65     private final boolean mIncludeExpander;
66     private final boolean mIsLayoutRtl;
67 
68     /**
69      * View holder for the header in the request manage credentials screen.
70      */
71     public class HeaderViewHolder extends RecyclerView.ViewHolder {
72         private final ImageView mAppIconView;
73         private final TextView mTitleView;
74 
HeaderViewHolder(View view)75         public HeaderViewHolder(View view) {
76             super(view);
77             mAppIconView = view.findViewById(R.id.credential_management_app_icon);
78             mTitleView = view.findViewById(R.id.credential_management_app_title);
79         }
80 
81         /**
82          * Bind the header view and add details on the requesting app's icon and name.
83          */
bindView()84         public void bindView() {
85             try {
86                 ApplicationInfo applicationInfo =
87                         mPackageManager.getApplicationInfo(mCredentialManagerPackage, 0);
88                 mAppIconView.setImageDrawable(mPackageManager.getApplicationIcon(applicationInfo));
89                 mTitleView.setText(TextUtils.expandTemplate(
90                         mContext.getText(R.string.request_manage_credentials_title),
91                         applicationInfo.loadLabel(mPackageManager)));
92             } catch (PackageManager.NameNotFoundException e) {
93                 mAppIconView.setImageDrawable(null);
94                 mTitleView.setText(TextUtils.expandTemplate(
95                         mContext.getText(R.string.request_manage_credentials_title),
96                         mCredentialManagerPackage));
97             }
98         }
99     }
100 
101     /**
102      * View holder for the authentication policy in the request manage credentials screen.
103      */
104     public class AppAuthenticationViewHolder extends RecyclerView.ViewHolder {
105         private final ImageView mAppIconView;
106         private final TextView mAppNameView;
107         private final TextView mNumberOfUrisView;
108         private final ImageView mExpanderIconView;
109         private final RecyclerView mChildRecyclerView;
110         private final List<String> mExpandedApps;
111 
AppAuthenticationViewHolder(View view)112         public AppAuthenticationViewHolder(View view) {
113             super(view);
114             mAppIconView = view.findViewById(R.id.app_icon);
115             mAppNameView = view.findViewById(R.id.app_name);
116             mNumberOfUrisView = view.findViewById(R.id.number_of_uris);
117             mExpanderIconView = view.findViewById(R.id.expand);
118             mChildRecyclerView = view.findViewById(R.id.uris);
119             mExpandedApps = new ArrayList<>();
120 
121             if (mIsLayoutRtl) {
122                 RelativeLayout appDetails = view.findViewById(R.id.app_details);
123                 RelativeLayout.LayoutParams params =
124                         (RelativeLayout.LayoutParams) appDetails.getLayoutParams();
125                 params.addRule(RelativeLayout.LEFT_OF, R.id.app_icon);
126                 params.addRule(RelativeLayout.RIGHT_OF, R.id.expand);
127                 view.setLayoutParams(params);
128             }
129 
130             mExpanderIconView.setOnClickListener(view1 -> {
131                 final String appName = mSortedAppNames.get(getBindingAdapterPosition());
132                 if (mExpandedApps.contains(appName)) {
133                     mExpandedApps.remove(appName);
134                 } else {
135                     mExpandedApps.add(appName);
136                 }
137                 bindPolicyView(appName);
138             });
139         }
140 
141         /**
142          * Bind the app's authentication policy view at the given position. Add details on the
143          * app's icon, name and list of URIs.
144          */
bindView(int position)145         public void bindView(int position) {
146             final String appName = mSortedAppNames.get(position);
147             try {
148                 ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(appName, 0);
149                 mAppIconView.setImageDrawable(mPackageManager.getApplicationIcon(applicationInfo));
150                 mAppNameView.setText(String.valueOf(applicationInfo.loadLabel(mPackageManager)));
151             } catch (PackageManager.NameNotFoundException e) {
152                 mAppIconView.setImageDrawable(null);
153                 mAppNameView.setText(appName);
154             }
155             bindPolicyView(appName);
156         }
157 
bindPolicyView(String appName)158         private void bindPolicyView(String appName) {
159             if (mIncludeExpander) {
160                 mExpanderIconView.setVisibility(View.VISIBLE);
161                 if (mExpandedApps.contains(appName)) {
162                     mNumberOfUrisView.setVisibility(View.GONE);
163                     mExpanderIconView.setImageResource(R.drawable.ic_expand_less);
164                     bindChildView(mAppUriAuthentication.get(appName));
165                 } else {
166                     mChildRecyclerView.setVisibility(View.GONE);
167                     mNumberOfUrisView.setVisibility(View.VISIBLE);
168                     mNumberOfUrisView.setText(
169                             getNumberOfUrlsText(mAppUriAuthentication.get(appName)));
170                     mExpanderIconView.setImageResource(
171                             com.android.internal.R.drawable.ic_expand_more);
172                 }
173             } else {
174                 mNumberOfUrisView.setVisibility(View.GONE);
175                 mExpanderIconView.setVisibility(View.GONE);
176                 bindChildView(mAppUriAuthentication.get(appName));
177             }
178         }
179 
180         /**
181          * Bind the list of URIs for an app.
182          */
bindChildView(Map<Uri, String> urisToAliases)183         private void bindChildView(Map<Uri, String> urisToAliases) {
184             LinearLayoutManager layoutManager = new LinearLayoutManager(
185                     mChildRecyclerView.getContext(), RecyclerView.VERTICAL, false);
186             layoutManager.setInitialPrefetchItemCount(urisToAliases.size());
187             UriAuthenticationPolicyAdapter childItemAdapter =
188                     new UriAuthenticationPolicyAdapter(new ArrayList<>(urisToAliases.keySet()));
189             mChildRecyclerView.setLayoutManager(layoutManager);
190             mChildRecyclerView.setVisibility(View.VISIBLE);
191             mChildRecyclerView.setAdapter(childItemAdapter);
192             mChildRecyclerView.setRecycledViewPool(mViewPool);
193         }
194 
getNumberOfUrlsText(Map<Uri, String> urisToAliases)195         private String getNumberOfUrlsText(Map<Uri, String> urisToAliases) {
196             return mContext.getResources().getQuantityString(R.plurals.number_of_urls,
197                     urisToAliases.size(), urisToAliases.size());
198         }
199     }
200 
CredentialManagementAppAdapter(Context context, String credentialManagerPackage, Map<String, Map<Uri, String>> appUriAuthentication, boolean includeHeader, boolean includeExpander)201     public CredentialManagementAppAdapter(Context context, String credentialManagerPackage,
202             Map<String, Map<Uri, String>> appUriAuthentication,
203             boolean includeHeader, boolean includeExpander) {
204         mContext = context;
205         mCredentialManagerPackage = credentialManagerPackage;
206         mPackageManager = context.getPackageManager();
207         mAppUriAuthentication = appUriAuthentication;
208         mSortedAppNames = sortPackageNames(mAppUriAuthentication);
209         mViewPool = new RecyclerView.RecycledViewPool();
210         mIncludeHeader = includeHeader;
211         mIncludeExpander = includeExpander;
212         mIsLayoutRtl = context.getResources().getConfiguration().getLayoutDirection()
213                 == View.LAYOUT_DIRECTION_RTL;
214     }
215 
216     /**
217      * Sort package names in the following order:
218      * - installed apps
219      * - alphabetically
220      */
sortPackageNames(Map<String, Map<Uri, String>> authenticationPolicy)221     private List<String> sortPackageNames(Map<String, Map<Uri, String>> authenticationPolicy) {
222         List<String> packageNames = new ArrayList<>(authenticationPolicy.keySet());
223         packageNames.sort((firstPackageName, secondPackageName) -> {
224             boolean isFirstPackageInstalled = isPackageInstalled(firstPackageName);
225             boolean isSecondPackageInstalled = isPackageInstalled(secondPackageName);
226             if (isFirstPackageInstalled == isSecondPackageInstalled) {
227                 return firstPackageName.compareTo(secondPackageName);
228             } else if (isFirstPackageInstalled) {
229                 return -1;
230             } else {
231                 return 1;
232             }
233         });
234         return packageNames;
235     }
236 
isPackageInstalled(String packageName)237     private boolean isPackageInstalled(String packageName) {
238         try {
239             mPackageManager.getPackageInfo(packageName, 0);
240             return true;
241         } catch (PackageManager.NameNotFoundException e) {
242             return false;
243         }
244     }
245 
246     @NonNull
247     @Override
onCreateViewHolder(@onNull ViewGroup viewGroup, int viewType)248     public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
249         View view;
250         if (viewType == HEADER_VIEW) {
251             view = LayoutInflater.from(viewGroup.getContext())
252                     .inflate(R.layout.request_manage_credentials_header, viewGroup, false);
253             view.setEnabled(false);
254             return new HeaderViewHolder(view);
255         } else {
256             view = LayoutInflater.from(viewGroup.getContext())
257                     .inflate(R.layout.app_authentication_item, viewGroup, false);
258             return new AppAuthenticationViewHolder(view);
259         }
260     }
261 
262     @Override
onBindViewHolder(@onNull RecyclerView.ViewHolder viewHolder, int i)263     public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
264         if (viewHolder instanceof HeaderViewHolder) {
265             ((HeaderViewHolder) viewHolder).bindView();
266         } else if (viewHolder instanceof AppAuthenticationViewHolder) {
267             int position = mIncludeHeader ? i - 1 : i;
268             ((AppAuthenticationViewHolder) viewHolder).bindView(position);
269         }
270     }
271 
272     @Override
getItemCount()273     public int getItemCount() {
274         // Add an extra view to show the header view
275         return mIncludeHeader ? mAppUriAuthentication.size() + 1 : mAppUriAuthentication.size();
276     }
277 
278     @Override
getItemViewType(int position)279     public int getItemViewType(int position) {
280         if (mIncludeHeader && position == 0) {
281             return HEADER_VIEW;
282         }
283         return super.getItemViewType(position);
284     }
285 
286 }
287