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