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