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