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