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.role.ui; 18 19 import android.app.Activity; 20 import android.app.AlertDialog; 21 import android.app.Dialog; 22 import android.app.role.RoleManager; 23 import android.content.Context; 24 import android.content.DialogInterface; 25 import android.content.Intent; 26 import android.content.pm.ApplicationInfo; 27 import android.graphics.drawable.Drawable; 28 import android.os.Bundle; 29 import android.os.Process; 30 import android.os.UserHandle; 31 import android.text.TextUtils; 32 import android.util.Log; 33 import android.util.Pair; 34 import android.view.LayoutInflater; 35 import android.view.View; 36 import android.view.ViewGroup; 37 import android.view.WindowManager; 38 import android.widget.BaseAdapter; 39 import android.widget.CheckBox; 40 import android.widget.ImageView; 41 import android.widget.ListView; 42 import android.widget.TextView; 43 44 import androidx.annotation.NonNull; 45 import androidx.annotation.Nullable; 46 import androidx.appcompat.content.res.AppCompatResources; 47 import androidx.fragment.app.DialogFragment; 48 import androidx.lifecycle.ViewModelProviders; 49 50 import com.android.permissioncontroller.PermissionControllerStatsLog; 51 import com.android.permissioncontroller.R; 52 import com.android.permissioncontroller.permission.utils.PackageRemovalMonitor; 53 import com.android.permissioncontroller.permission.utils.Utils; 54 import com.android.permissioncontroller.role.model.UserDeniedManager; 55 import com.android.permissioncontroller.role.utils.PackageUtils; 56 import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils; 57 import com.android.permissioncontroller.role.utils.UiUtils; 58 import com.android.role.controller.model.Role; 59 import com.android.role.controller.model.Roles; 60 61 import java.util.ArrayList; 62 import java.util.List; 63 import java.util.Objects; 64 65 /** 66 * {@code Fragment} for a role request. 67 */ 68 public class RequestRoleFragment extends DialogFragment { 69 70 private static final String LOG_TAG = RequestRoleFragment.class.getSimpleName(); 71 72 private static final String STATE_DONT_ASK_AGAIN = RequestRoleFragment.class.getName() 73 + ".state.DONT_ASK_AGAIN"; 74 75 private String mRoleName; 76 private String mPackageName; 77 78 private Role mRole; 79 80 private ListView mListView; 81 private Adapter mAdapter; 82 @Nullable 83 private CheckBox mDontAskAgainCheck; 84 85 private RequestRoleViewModel mViewModel; 86 87 @Nullable 88 private PackageRemovalMonitor mPackageRemovalMonitor; 89 90 /** 91 * Create a new instance of this fragment. 92 * 93 * @param roleName the name of the requested role 94 * @param packageName the package name of the application requesting the role 95 * 96 * @return a new instance of this fragment 97 */ newInstance(@onNull String roleName, @NonNull String packageName)98 public static RequestRoleFragment newInstance(@NonNull String roleName, 99 @NonNull String packageName) { 100 RequestRoleFragment fragment = new RequestRoleFragment(); 101 Bundle arguments = new Bundle(); 102 arguments.putString(Intent.EXTRA_ROLE_NAME, roleName); 103 arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName); 104 fragment.setArguments(arguments); 105 return fragment; 106 } 107 108 @Override onCreate(@ullable Bundle savedInstanceState)109 public void onCreate(@Nullable Bundle savedInstanceState) { 110 super.onCreate(savedInstanceState); 111 112 Bundle arguments = getArguments(); 113 mPackageName = arguments.getString(Intent.EXTRA_PACKAGE_NAME); 114 mRoleName = arguments.getString(Intent.EXTRA_ROLE_NAME); 115 116 mRole = Roles.get(requireContext()).get(mRoleName); 117 } 118 119 @NonNull 120 @Override onCreateDialog(@ullable Bundle savedInstanceState)121 public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { 122 AlertDialog.Builder builder = new AlertDialog.Builder(requireContext(), getTheme()); 123 Context context = builder.getContext(); 124 125 RoleManager roleManager = context.getSystemService(RoleManager.class); 126 List<String> currentPackageNames = roleManager.getRoleHolders(mRoleName); 127 if (currentPackageNames.contains(mPackageName)) { 128 Log.i(LOG_TAG, "Application is already a role holder, role: " + mRoleName 129 + ", package: " + mPackageName); 130 reportRequestResult(PermissionControllerStatsLog 131 .ROLE_REQUEST_RESULT_REPORTED__RESULT__IGNORED_ALREADY_GRANTED, null); 132 clearDeniedSetResultOkAndFinish(); 133 return super.onCreateDialog(savedInstanceState); 134 } 135 136 ApplicationInfo applicationInfo = PackageUtils.getApplicationInfo(mPackageName, context); 137 if (applicationInfo == null) { 138 Log.w(LOG_TAG, "Unknown application: " + mPackageName); 139 reportRequestResult( 140 PermissionControllerStatsLog.ROLE_REQUEST_RESULT_REPORTED__RESULT__IGNORED, 141 null); 142 finish(); 143 return super.onCreateDialog(savedInstanceState); 144 } 145 Drawable icon = Utils.getBadgedIcon(context, applicationInfo); 146 String applicationLabel = Utils.getAppLabel(applicationInfo, context); 147 String title = getString(mRole.getRequestTitleResource(), applicationLabel); 148 149 LayoutInflater inflater = LayoutInflater.from(context); 150 View titleLayout = inflater.inflate(R.layout.request_role_title, null); 151 ImageView iconImage = titleLayout.requireViewById(R.id.icon); 152 iconImage.setImageDrawable(icon); 153 TextView titleText = titleLayout.requireViewById(R.id.title); 154 titleText.setText(title); 155 156 View viewLayout = inflater.inflate(R.layout.request_role_view, null); 157 mListView = viewLayout.requireViewById(R.id.list); 158 mListView.setOnItemClickListener((parent, view, position, id) -> onItemClicked(position)); 159 mAdapter = new Adapter(mListView, mRole); 160 if (savedInstanceState != null) { 161 mAdapter.onRestoreInstanceState(savedInstanceState); 162 } 163 mListView.setAdapter(mAdapter); 164 if (!mListView.isInTouchMode()) { 165 mListView.post(() -> { 166 mListView.setSelection(0); 167 mListView.requestFocus(); 168 }); 169 } 170 171 CheckBox dontAskAgainCheck = viewLayout.requireViewById(R.id.dont_ask_again); 172 boolean isDeniedOnce = UserDeniedManager.getInstance(context).isDeniedOnce(mRoleName, 173 mPackageName); 174 dontAskAgainCheck.setVisibility(isDeniedOnce ? View.VISIBLE : View.GONE); 175 if (isDeniedOnce) { 176 mDontAskAgainCheck = dontAskAgainCheck; 177 mDontAskAgainCheck.setOnClickListener(view -> updateUi()); 178 if (savedInstanceState != null) { 179 boolean dontAskAgain = savedInstanceState.getBoolean(STATE_DONT_ASK_AGAIN); 180 mDontAskAgainCheck.setChecked(dontAskAgain); 181 mAdapter.setDontAskAgain(dontAskAgain); 182 } 183 } 184 185 AlertDialog dialog = builder 186 .setCustomTitle(titleLayout) 187 .setView(viewLayout) 188 // Set the positive button listener later to avoid the automatic dismiss behavior. 189 .setPositiveButton(R.string.request_role_set_as_default, null) 190 // The default behavior for a null listener is to dismiss the dialog, not cancel. 191 .setNegativeButton(android.R.string.cancel, (dialog2, which) -> dialog2.cancel()) 192 .create(); 193 dialog.getWindow().addSystemFlags( 194 WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); 195 dialog.setOnShowListener(dialog2 -> dialog.getButton(Dialog.BUTTON_POSITIVE) 196 .setOnClickListener(view -> onSetAsDefault())); 197 return dialog; 198 } 199 200 @Override getDialog()201 public AlertDialog getDialog() { 202 return (AlertDialog) super.getDialog(); 203 } 204 205 @Override onStart()206 public void onStart() { 207 super.onStart(); 208 209 Context context = requireContext(); 210 if (PackageUtils.getApplicationInfo(mPackageName, context) == null) { 211 Log.w(LOG_TAG, "Unknown application: " + mPackageName); 212 reportRequestResult( 213 PermissionControllerStatsLog.ROLE_REQUEST_RESULT_REPORTED__RESULT__IGNORED, 214 null); 215 finish(); 216 return; 217 } 218 219 mPackageRemovalMonitor = new PackageRemovalMonitor(context, mPackageName) { 220 @Override 221 protected void onPackageRemoved() { 222 Log.w(LOG_TAG, "Application is uninstalled, role: " + mRoleName + ", package: " 223 + mPackageName); 224 reportRequestResult( 225 PermissionControllerStatsLog.ROLE_REQUEST_RESULT_REPORTED__RESULT__IGNORED, 226 null); 227 finish(); 228 } 229 }; 230 mPackageRemovalMonitor.register(); 231 232 // Postponed to onStart() so that the list view in dialog is created. 233 mViewModel = ViewModelProviders.of(this, new RequestRoleViewModel.Factory(mRole, 234 requireActivity().getApplication())).get(RequestRoleViewModel.class); 235 mViewModel.getRoleLiveData().observe(this, this::onRoleDataChanged); 236 mViewModel.getManageRoleHolderStateLiveData().observe(this, 237 this::onManageRoleHolderStateChanged); 238 } 239 240 @Override onSaveInstanceState(@onNull Bundle outState)241 public void onSaveInstanceState(@NonNull Bundle outState) { 242 super.onSaveInstanceState(outState); 243 244 mAdapter.onSaveInstanceState(outState); 245 if (mDontAskAgainCheck != null) { 246 outState.putBoolean(STATE_DONT_ASK_AGAIN, mDontAskAgainCheck.isChecked()); 247 } 248 } 249 250 @Override onStop()251 public void onStop() { 252 super.onStop(); 253 254 if (mPackageRemovalMonitor != null) { 255 mPackageRemovalMonitor.unregister(); 256 mPackageRemovalMonitor = null; 257 } 258 } 259 260 @Override onCancel(@onNull DialogInterface dialog)261 public void onCancel(@NonNull DialogInterface dialog) { 262 super.onCancel(dialog); 263 264 Log.i(LOG_TAG, "Dialog cancelled, role: " + mRoleName + ", package: " + mPackageName); 265 reportRequestResult( 266 PermissionControllerStatsLog.ROLE_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED, 267 null); 268 setDeniedOnceAndFinish(); 269 } 270 onRoleDataChanged( @onNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications)271 private void onRoleDataChanged( 272 @NonNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications) { 273 mAdapter.replace(qualifyingApplications); 274 updateUi(); 275 } 276 onItemClicked(int position)277 private void onItemClicked(int position) { 278 mAdapter.onItemClicked(position); 279 updateUi(); 280 } 281 onSetAsDefault()282 private void onSetAsDefault() { 283 if (mDontAskAgainCheck != null && mDontAskAgainCheck.isChecked()) { 284 Log.i(LOG_TAG, "Request denied with don't ask again, role: " + mRoleName + ", package: " 285 + mPackageName); 286 reportRequestResult(PermissionControllerStatsLog 287 .ROLE_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_ALWAYS, null); 288 setDeniedAlwaysAndFinish(); 289 } else { 290 setRoleHolder(); 291 } 292 } 293 setRoleHolder()294 private void setRoleHolder() { 295 String packageName = mAdapter.getCheckedPackageName(); 296 Context context = requireContext(); 297 UserHandle user = Process.myUserHandle(); 298 if (packageName == null) { 299 reportRequestResult(PermissionControllerStatsLog 300 .ROLE_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_GRANTED_ANOTHER, 301 null); 302 mRole.onNoneHolderSelectedAsUser(user, context); 303 mViewModel.getManageRoleHolderStateLiveData().clearRoleHoldersAsUser(mRoleName, 0, user, 304 context); 305 } else { 306 boolean isRequestingApplication = Objects.equals(packageName, mPackageName); 307 if (isRequestingApplication) { 308 reportRequestResult(PermissionControllerStatsLog 309 .ROLE_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED, null); 310 } else { 311 reportRequestResult(PermissionControllerStatsLog 312 .ROLE_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_GRANTED_ANOTHER, 313 packageName); 314 } 315 int flags = isRequestingApplication ? RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP : 0; 316 mViewModel.getManageRoleHolderStateLiveData().setRoleHolderAsUser(mRoleName, 317 packageName, true, flags, user, context); 318 } 319 } 320 onManageRoleHolderStateChanged(int state)321 private void onManageRoleHolderStateChanged(int state) { 322 switch (state) { 323 case ManageRoleHolderStateLiveData.STATE_IDLE: 324 case ManageRoleHolderStateLiveData.STATE_WORKING: 325 updateUi(); 326 break; 327 case ManageRoleHolderStateLiveData.STATE_SUCCESS: { 328 ManageRoleHolderStateLiveData liveData = 329 mViewModel.getManageRoleHolderStateLiveData(); 330 String packageName = liveData.getLastPackageName(); 331 if (packageName != null) { 332 mRole.onHolderSelectedAsUser(packageName, liveData.getLastUser(), 333 requireContext()); 334 } 335 if (Objects.equals(packageName, mPackageName)) { 336 Log.i(LOG_TAG, "Application added as a role holder, role: " + mRoleName 337 + ", package: " + mPackageName); 338 clearDeniedSetResultOkAndFinish(); 339 } else { 340 Log.i(LOG_TAG, "Request denied with another application added as a role holder," 341 + " role: " + mRoleName + ", package: " + mPackageName); 342 setDeniedOnceAndFinish(); 343 } 344 break; 345 } 346 case ManageRoleHolderStateLiveData.STATE_FAILURE: 347 finish(); 348 break; 349 } 350 } 351 updateUi()352 private void updateUi() { 353 boolean enabled = mViewModel.getManageRoleHolderStateLiveData().getValue() 354 == ManageRoleHolderStateLiveData.STATE_IDLE; 355 mListView.setEnabled(enabled); 356 boolean dontAskAgain = mDontAskAgainCheck != null && mDontAskAgainCheck.isChecked(); 357 mAdapter.setDontAskAgain(dontAskAgain); 358 AlertDialog dialog = getDialog(); 359 boolean hasRoleData = mViewModel.getRoleLiveData().getValue() != null; 360 dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(enabled && hasRoleData 361 && (dontAskAgain || !mAdapter.isHolderApplicationChecked())); 362 dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(enabled); 363 } 364 clearDeniedSetResultOkAndFinish()365 private void clearDeniedSetResultOkAndFinish() { 366 UserDeniedManager.getInstance(requireContext()).clearDenied(mRoleName, mPackageName); 367 requireActivity().setResult(Activity.RESULT_OK); 368 finish(); 369 } 370 setDeniedOnceAndFinish()371 private void setDeniedOnceAndFinish() { 372 UserDeniedManager.getInstance(requireContext()).setDeniedOnce(mRoleName, mPackageName); 373 finish(); 374 } 375 setDeniedAlwaysAndFinish()376 private void setDeniedAlwaysAndFinish() { 377 UserDeniedManager.getInstance(requireContext()).setDeniedAlways(mRoleName, mPackageName); 378 finish(); 379 } 380 finish()381 private void finish() { 382 requireActivity().finish(); 383 } 384 reportRequestResult(int result, @Nullable String grantedAnotherPackageName)385 private void reportRequestResult(int result, @Nullable String grantedAnotherPackageName) { 386 String holderPackageName = getHolderPackageName(); 387 reportRequestResult(getApplicationUid(mPackageName), mPackageName, mRoleName, 388 getQualifyingApplicationCount(), getQualifyingApplicationUid(holderPackageName), 389 holderPackageName, getQualifyingApplicationUid(grantedAnotherPackageName), 390 grantedAnotherPackageName, result); 391 } 392 getApplicationUid(@onNull String packageName)393 private int getApplicationUid(@NonNull String packageName) { 394 int uid = getQualifyingApplicationUid(packageName); 395 if (uid != -1) { 396 return uid; 397 } 398 ApplicationInfo applicationInfo = PackageUtils.getApplicationInfo(packageName, 399 requireActivity()); 400 if (applicationInfo == null) { 401 return -1; 402 } 403 return applicationInfo.uid; 404 } 405 getQualifyingApplicationUid(@ullable String packageName)406 private int getQualifyingApplicationUid(@Nullable String packageName) { 407 if (packageName == null || mAdapter == null) { 408 return -1; 409 } 410 int count = mAdapter.getCount(); 411 for (int i = 0; i < count; i++) { 412 Pair<ApplicationInfo, Boolean> qualifyingApplication = mAdapter.getItem(i); 413 if (qualifyingApplication == null) { 414 // Skip the "None" item. 415 continue; 416 } 417 ApplicationInfo applicationInfo = qualifyingApplication.first; 418 if (Objects.equals(applicationInfo.packageName, packageName)) { 419 return applicationInfo.uid; 420 } 421 } 422 return -1; 423 } 424 getQualifyingApplicationCount()425 private int getQualifyingApplicationCount() { 426 if (mAdapter == null) { 427 return -1; 428 } 429 int count = mAdapter.getCount(); 430 if (count > 0 && mAdapter.getItem(0) == null) { 431 // Exclude the "None" item. 432 --count; 433 } 434 return count; 435 } 436 437 @Nullable getHolderPackageName()438 private String getHolderPackageName() { 439 if (mAdapter == null) { 440 return null; 441 } 442 return mAdapter.mHolderPackageName; 443 } 444 reportRequestResult(int requestingUid, String requestingPackageName, String roleName, int qualifyingCount, int currentUid, String currentPackageName, int grantedAnotherUid, String grantedAnotherPackageName, int result)445 static void reportRequestResult(int requestingUid, String requestingPackageName, 446 String roleName, int qualifyingCount, int currentUid, String currentPackageName, 447 int grantedAnotherUid, String grantedAnotherPackageName, int result) { 448 Log.i(LOG_TAG, "Role request result" 449 + " requestingUid=" + requestingUid 450 + " requestingPackageName=" + requestingPackageName 451 + " roleName=" + roleName 452 + " qualifyingCount=" + qualifyingCount 453 + " currentUid=" + currentUid 454 + " currentPackageName=" + currentPackageName 455 + " grantedAnotherUid=" + grantedAnotherUid 456 + " grantedAnotherPackageName=" + grantedAnotherPackageName 457 + " result=" + result); 458 PermissionControllerStatsLog.write( 459 PermissionControllerStatsLog.ROLE_REQUEST_RESULT_REPORTED, requestingUid, 460 requestingPackageName, roleName, qualifyingCount, currentUid, currentPackageName, 461 grantedAnotherUid, grantedAnotherPackageName, result); 462 } 463 464 private static class Adapter extends BaseAdapter { 465 466 private static final String STATE_USER_CHECKED = Adapter.class.getName() 467 + ".state.USER_CHECKED"; 468 private static final String STATE_CHECKED_PACKAGE_NAME = Adapter.class.getName() 469 + ".state.CHECKED_PACKAGE_NAME"; 470 471 private static final int LAYOUT_TRANSITION_DURATION_MILLIS = 150; 472 473 @NonNull 474 private final ListView mListView; 475 476 @NonNull 477 private final Role mRole; 478 479 // We'll use a null to represent the "None" item. 480 @NonNull 481 private final List<Pair<ApplicationInfo, Boolean>> mQualifyingApplications = 482 new ArrayList<>(); 483 484 @Nullable 485 private String mHolderPackageName; 486 487 private boolean mDontAskAgain; 488 489 // If user has ever clicked an item to mark it as checked, we no longer automatically mark 490 // the current holder as checked. 491 private boolean mUserChecked; 492 493 @Nullable 494 private String mCheckedPackageName; 495 Adapter(@onNull ListView listView, @NonNull Role role)496 Adapter(@NonNull ListView listView, @NonNull Role role) { 497 mListView = listView; 498 mRole = role; 499 } 500 onSaveInstanceState(@onNull Bundle outState)501 public void onSaveInstanceState(@NonNull Bundle outState) { 502 outState.putBoolean(STATE_USER_CHECKED, mUserChecked); 503 if (mUserChecked) { 504 outState.putString(STATE_CHECKED_PACKAGE_NAME, mCheckedPackageName); 505 } 506 } 507 onRestoreInstanceState(@onNull Bundle savedInstanceState)508 public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { 509 mUserChecked = savedInstanceState.getBoolean(STATE_USER_CHECKED); 510 if (mUserChecked) { 511 mCheckedPackageName = savedInstanceState.getString(STATE_CHECKED_PACKAGE_NAME); 512 } 513 } 514 setDontAskAgain(boolean dontAskAgain)515 public void setDontAskAgain(boolean dontAskAgain) { 516 if (mDontAskAgain == dontAskAgain) { 517 return; 518 } 519 mDontAskAgain = dontAskAgain; 520 if (mDontAskAgain) { 521 mUserChecked = false; 522 mCheckedPackageName = mHolderPackageName; 523 } 524 notifyDataSetChanged(); 525 } 526 onItemClicked(int position)527 public void onItemClicked(int position) { 528 Pair<ApplicationInfo, Boolean> qualifyingApplication = getItem(position); 529 if (qualifyingApplication == null) { 530 mUserChecked = true; 531 mCheckedPackageName = null; 532 } else { 533 ApplicationInfo applicationInfo = qualifyingApplication.first; 534 Intent restrictionIntent = mRole.getApplicationRestrictionIntentAsUser( 535 applicationInfo, Process.myUserHandle(), mListView.getContext()); 536 if (restrictionIntent != null) { 537 mListView.getContext().startActivity(restrictionIntent); 538 return; 539 } else { 540 mUserChecked = true; 541 mCheckedPackageName = applicationInfo.packageName; 542 } 543 } 544 notifyDataSetChanged(); 545 } 546 replace(@onNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications)547 public void replace(@NonNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications) { 548 mQualifyingApplications.clear(); 549 if (mRole.shouldShowNone()) { 550 mQualifyingApplications.add(0, null); 551 } 552 mQualifyingApplications.addAll(qualifyingApplications); 553 mHolderPackageName = getHolderPackageName(qualifyingApplications); 554 555 if (mUserChecked && mCheckedPackageName != null) { 556 boolean isCheckedPackageNameFound = false; 557 int count = getCount(); 558 for (int i = 0; i < count; i++) { 559 Pair<ApplicationInfo, Boolean> qualifyingApplication = getItem(i); 560 if (qualifyingApplication == null) { 561 continue; 562 } 563 String packageName = qualifyingApplication.first.packageName; 564 565 if (Objects.equals(packageName, mCheckedPackageName)) { 566 mUserChecked = true; 567 isCheckedPackageNameFound = true; 568 break; 569 } 570 } 571 if (!isCheckedPackageNameFound) { 572 mUserChecked = false; 573 mCheckedPackageName = null; 574 } 575 } 576 577 if (!mUserChecked) { 578 mCheckedPackageName = mHolderPackageName; 579 } 580 581 notifyDataSetChanged(); 582 } 583 584 @Nullable getHolderPackageName( @onNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications)585 private static String getHolderPackageName( 586 @NonNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications) { 587 int qualifyingApplicationSize = qualifyingApplications.size(); 588 for (int i = 0; i < qualifyingApplicationSize; i++) { 589 Pair<ApplicationInfo, Boolean> qualifyingApplication = qualifyingApplications.get( 590 i); 591 if (qualifyingApplication == null) { 592 continue; 593 } 594 ApplicationInfo applicationInfo = qualifyingApplication.first; 595 boolean isHolderApplication = qualifyingApplication.second; 596 597 if (isHolderApplication) { 598 return applicationInfo.packageName; 599 } 600 } 601 return null; 602 } 603 604 @Nullable getCheckedPackageName()605 public String getCheckedPackageName() { 606 return mCheckedPackageName; 607 } 608 isHolderApplicationChecked()609 public boolean isHolderApplicationChecked() { 610 return Objects.equals(mCheckedPackageName, mHolderPackageName); 611 } 612 613 @Override hasStableIds()614 public boolean hasStableIds() { 615 return true; 616 } 617 618 @Override areAllItemsEnabled()619 public boolean areAllItemsEnabled() { 620 return false; 621 } 622 623 @Override getCount()624 public int getCount() { 625 return mQualifyingApplications.size(); 626 } 627 628 @Nullable 629 @Override getItem(int position)630 public Pair<ApplicationInfo, Boolean> getItem(int position) { 631 return mQualifyingApplications.get(position); 632 } 633 634 @Override getItemId(int position)635 public long getItemId(int position) { 636 if (position >= getCount()) { 637 // Work around AbsListView.confirmCheckedPositionsById() not respecting our count. 638 return ListView.INVALID_ROW_ID; 639 } 640 Pair<ApplicationInfo, Boolean> qualifyingApplication = getItem(position); 641 return qualifyingApplication == null ? 0 642 : qualifyingApplication.first.packageName.hashCode(); 643 } 644 645 @Override isEnabled(int position)646 public boolean isEnabled(int position) { 647 if (!mDontAskAgain) { 648 return true; 649 } 650 Pair<ApplicationInfo, Boolean> qualifyingApplication = getItem(position); 651 if (qualifyingApplication == null) { 652 return mHolderPackageName == null; 653 } else { 654 boolean isHolderApplication = qualifyingApplication.second; 655 return isHolderApplication; 656 } 657 } 658 659 @NonNull 660 @Override getView(int position, @Nullable View convertView, @NonNull ViewGroup parent)661 public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { 662 Context context = parent.getContext(); 663 CheckableLinearLayout view = (CheckableLinearLayout) convertView; 664 ViewHolder holder; 665 if (view != null) { 666 holder = (ViewHolder) view.getTag(); 667 } else { 668 view = (CheckableLinearLayout) LayoutInflater.from(context).inflate( 669 R.layout.request_role_item, parent, false); 670 holder = new ViewHolder(view); 671 view.setTag(holder); 672 673 holder.titleAndSubtitleLayout.getLayoutTransition().setDuration( 674 LAYOUT_TRANSITION_DURATION_MILLIS); 675 } 676 677 Pair<ApplicationInfo, Boolean> qualifyingApplication = getItem(position); 678 ApplicationInfo applicationInfo; 679 boolean restricted; 680 boolean checked; 681 Drawable icon; 682 String title; 683 String subtitle; 684 if (qualifyingApplication == null) { 685 applicationInfo = null; 686 restricted = false; 687 checked = mCheckedPackageName == null; 688 icon = AppCompatResources.getDrawable(context, R.drawable.ic_remove_circle); 689 title = context.getString(R.string.default_app_none); 690 subtitle = mHolderPackageName != null ? context.getString( 691 R.string.request_role_current_default) : null; 692 } else { 693 applicationInfo = qualifyingApplication.first; 694 restricted = mRole.getApplicationRestrictionIntentAsUser(applicationInfo, 695 Process.myUserHandle(), context) != null; 696 checked = Objects.equals(applicationInfo.packageName, mCheckedPackageName); 697 icon = Utils.getBadgedIcon(context, applicationInfo); 698 title = Utils.getAppLabel(applicationInfo, context); 699 boolean isHolderApplication = qualifyingApplication.second; 700 subtitle = isHolderApplication 701 ? context.getString(R.string.request_role_current_default) 702 : checked ? context.getString(mRole.getRequestDescriptionResource()) : null; 703 } 704 705 boolean enabled = isEnabled(position); 706 UiUtils.setViewTreeEnabled(view, enabled && !restricted); 707 view.setEnabled(enabled); 708 view.setChecked(checked); 709 holder.iconImage.setImageDrawable(icon); 710 holder.titleText.setText(title); 711 holder.subtitleText.setVisibility(!TextUtils.isEmpty(subtitle) ? View.VISIBLE 712 : View.GONE); 713 holder.subtitleText.setText(subtitle); 714 RoleUiBehaviorUtils.prepareRequestRoleItemViewAsUser(mRole, holder, applicationInfo, 715 Process.myUserHandle(), context); 716 717 return view; 718 } 719 720 private static class ViewHolder implements RequestRoleItemView { 721 722 @NonNull 723 public final ImageView iconImage; 724 @NonNull 725 public final ViewGroup titleAndSubtitleLayout; 726 @NonNull 727 public final TextView titleText; 728 @NonNull 729 public final TextView subtitleText; 730 ViewHolder(@onNull View view)731 ViewHolder(@NonNull View view) { 732 iconImage = view.requireViewById(R.id.icon); 733 titleAndSubtitleLayout = view.requireViewById(R.id.title_and_subtitle); 734 titleText = view.requireViewById(R.id.title); 735 subtitleText = view.requireViewById(R.id.subtitle); 736 } 737 738 @Override getIconImageView()739 public ImageView getIconImageView() { 740 return iconImage; 741 } 742 743 @Override getTitleTextView()744 public TextView getTitleTextView() { 745 return titleText; 746 } 747 748 @Override getSubtitleTextView()749 public TextView getSubtitleTextView() { 750 return subtitleText; 751 } 752 } 753 } 754 } 755