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