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 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 qualifyingApplicationInfo = qualifyingApplication.first; 418 if (Objects.equals(qualifyingApplicationInfo.packageName, packageName)) { 419 return qualifyingApplicationInfo.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 int count = mAdapter.getCount(); 443 for (int i = 0; i < count; i++) { 444 Pair<ApplicationInfo, Boolean> qualifyingApplication = mAdapter.getItem(i); 445 if (qualifyingApplication == null) { 446 // Skip the "None" item. 447 continue; 448 } 449 boolean isHolderApplication = qualifyingApplication.second; 450 if (isHolderApplication) { 451 return qualifyingApplication.first.packageName; 452 } 453 } 454 return null; 455 } 456 reportRequestResult(int requestingUid, String requestingPackageName, String roleName, int qualifyingCount, int currentUid, String currentPackageName, int grantedAnotherUid, String grantedAnotherPackageName, int result)457 static void reportRequestResult(int requestingUid, String requestingPackageName, 458 String roleName, int qualifyingCount, int currentUid, String currentPackageName, 459 int grantedAnotherUid, String grantedAnotherPackageName, int result) { 460 Log.v(LOG_TAG, "Role request result" 461 + " requestingUid=" + requestingUid 462 + " requestingPackageName=" + requestingPackageName 463 + " roleName=" + roleName 464 + " qualifyingCount=" + qualifyingCount 465 + " currentUid=" + currentUid 466 + " currentPackageName=" + currentPackageName 467 + " grantedAnotherUid=" + grantedAnotherUid 468 + " grantedAnotherPackageName=" + grantedAnotherPackageName 469 + " result=" + result); 470 PermissionControllerStatsLog.write( 471 PermissionControllerStatsLog.ROLE_REQUEST_RESULT_REPORTED, requestingUid, 472 requestingPackageName, roleName, qualifyingCount, currentUid, currentPackageName, 473 grantedAnotherUid, grantedAnotherPackageName, result); 474 } 475 476 private static class Adapter extends BaseAdapter { 477 478 private static final String STATE_USER_CHECKED = Adapter.class.getName() 479 + ".state.USER_CHECKED"; 480 private static final String STATE_USER_CHECKED_PACKAGE_NAME = Adapter.class.getName() 481 + ".state.USER_CHECKED_PACKAGE_NAME"; 482 483 private static final int LAYOUT_TRANSITION_DURATION_MILLIS = 150; 484 485 @NonNull 486 private final ListView mListView; 487 488 @NonNull 489 private final Role mRole; 490 491 // We'll use a null to represent the "None" item. 492 @NonNull 493 private final List<Pair<ApplicationInfo, Boolean>> mQualifyingApplications = 494 new ArrayList<>(); 495 496 private boolean mHasHolderApplication; 497 498 private boolean mDontAskAgain; 499 500 // If user has ever clicked an item to mark it as checked, we no longer automatically mark 501 // the current holder as checked. 502 private boolean mUserChecked; 503 504 private boolean mPendingUserChecked; 505 // We may use a null to represent the "None" item. 506 @Nullable 507 private String mPendingUserCheckedPackageName; 508 Adapter(@onNull ListView listView, @NonNull Role role)509 Adapter(@NonNull ListView listView, @NonNull Role role) { 510 mListView = listView; 511 mRole = role; 512 } 513 onSaveInstanceState(@onNull Bundle outState)514 public void onSaveInstanceState(@NonNull Bundle outState) { 515 outState.putBoolean(STATE_USER_CHECKED, mUserChecked); 516 if (mUserChecked) { 517 outState.putString(STATE_USER_CHECKED_PACKAGE_NAME, getCheckedPackageName()); 518 } 519 } 520 onRestoreInstanceState(@onNull Bundle savedInstanceState)521 public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { 522 mPendingUserChecked = savedInstanceState.getBoolean(STATE_USER_CHECKED); 523 if (mPendingUserChecked) { 524 mPendingUserCheckedPackageName = savedInstanceState.getString( 525 STATE_USER_CHECKED_PACKAGE_NAME); 526 } 527 } 528 setDontAskAgain(boolean dontAskAgain)529 public void setDontAskAgain(boolean dontAskAgain) { 530 if (mDontAskAgain == dontAskAgain) { 531 return; 532 } 533 mDontAskAgain = dontAskAgain; 534 if (mDontAskAgain) { 535 mUserChecked = false; 536 updateItemChecked(); 537 } 538 notifyDataSetChanged(); 539 } 540 onItemClicked(int position)541 public void onItemClicked(int position) { 542 mUserChecked = true; 543 // We may need to change description based on checked state. 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 mHasHolderApplication = hasHolderApplication(qualifyingApplications); 554 notifyDataSetChanged(); 555 556 if (mPendingUserChecked) { 557 restoreItemChecked(); 558 mPendingUserChecked = false; 559 mPendingUserCheckedPackageName = null; 560 } 561 562 if (!mUserChecked) { 563 updateItemChecked(); 564 } 565 } 566 hasHolderApplication( @onNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications)567 private static boolean hasHolderApplication( 568 @NonNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications) { 569 int qualifyingApplicationsSize = qualifyingApplications.size(); 570 for (int i = 0; i < qualifyingApplicationsSize; i++) { 571 Pair<ApplicationInfo, Boolean> qualifyingApplication = qualifyingApplications.get( 572 i); 573 boolean isHolderApplication = qualifyingApplication.second; 574 575 if (isHolderApplication) { 576 return true; 577 } 578 } 579 return false; 580 } 581 restoreItemChecked()582 private void restoreItemChecked() { 583 if (mPendingUserCheckedPackageName == null) { 584 if (mRole.shouldShowNone()) { 585 mUserChecked = true; 586 mListView.setItemChecked(0, true); 587 } 588 } else { 589 int count = getCount(); 590 for (int i = 0; i < count; i++) { 591 Pair<ApplicationInfo, Boolean> qualifyingApplication = getItem(i); 592 if (qualifyingApplication == null) { 593 continue; 594 } 595 String packageName = qualifyingApplication.first.packageName; 596 597 if (Objects.equals(packageName, mPendingUserCheckedPackageName)) { 598 mUserChecked = true; 599 mListView.setItemChecked(i, true); 600 break; 601 } 602 } 603 } 604 } 605 updateItemChecked()606 private void updateItemChecked() { 607 if (!mHasHolderApplication) { 608 if (mRole.shouldShowNone()) { 609 mListView.setItemChecked(0, true); 610 } else { 611 mListView.clearChoices(); 612 } 613 } else { 614 int count = getCount(); 615 for (int i = 0; i < count; i++) { 616 Pair<ApplicationInfo, Boolean> qualifyingApplication = getItem(i); 617 if (qualifyingApplication == null) { 618 continue; 619 } 620 boolean isHolderApplication = qualifyingApplication.second; 621 622 if (isHolderApplication) { 623 mListView.setItemChecked(i, true); 624 break; 625 } 626 } 627 } 628 } 629 630 @Nullable getCheckedItem()631 public Pair<ApplicationInfo, Boolean> getCheckedItem() { 632 int position = mListView.getCheckedItemPosition(); 633 return position != AdapterView.INVALID_POSITION ? getItem(position) : null; 634 } 635 636 @Nullable getCheckedPackageName()637 public String getCheckedPackageName() { 638 Pair<ApplicationInfo, Boolean> qualifyingApplication = getCheckedItem(); 639 return qualifyingApplication == null ? null : qualifyingApplication.first.packageName; 640 } 641 isHolderApplicationChecked()642 public boolean isHolderApplicationChecked() { 643 Pair<ApplicationInfo, Boolean> qualifyingApplication = getCheckedItem(); 644 return qualifyingApplication == null ? !mHasHolderApplication 645 : qualifyingApplication.second; 646 } 647 648 @Override hasStableIds()649 public boolean hasStableIds() { 650 return true; 651 } 652 653 @Override areAllItemsEnabled()654 public boolean areAllItemsEnabled() { 655 return false; 656 } 657 658 @Override getCount()659 public int getCount() { 660 return mQualifyingApplications.size(); 661 } 662 663 @Nullable 664 @Override getItem(int position)665 public Pair<ApplicationInfo, Boolean> getItem(int position) { 666 return mQualifyingApplications.get(position); 667 } 668 669 @Override getItemId(int position)670 public long getItemId(int position) { 671 if (position >= getCount()) { 672 // Work around AbsListView.confirmCheckedPositionsById() not respecting our count. 673 return ListView.INVALID_ROW_ID; 674 } 675 Pair<ApplicationInfo, Boolean> qualifyingApplication = getItem(position); 676 return qualifyingApplication == null ? 0 677 : qualifyingApplication.first.packageName.hashCode(); 678 } 679 680 @Override isEnabled(int position)681 public boolean isEnabled(int position) { 682 if (!mDontAskAgain) { 683 return true; 684 } 685 Pair<ApplicationInfo, Boolean> qualifyingApplication = getItem(position); 686 if (qualifyingApplication == null) { 687 return !mHasHolderApplication; 688 } else { 689 boolean isHolderApplication = qualifyingApplication.second; 690 return isHolderApplication; 691 } 692 } 693 694 @NonNull 695 @Override getView(int position, @Nullable View convertView, @NonNull ViewGroup parent)696 public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { 697 Context context = parent.getContext(); 698 View view = convertView; 699 ViewHolder holder; 700 if (view != null) { 701 holder = (ViewHolder) view.getTag(); 702 } else { 703 view = LayoutInflater.from(context).inflate(R.layout.request_role_item, parent, 704 false); 705 holder = new ViewHolder(view); 706 view.setTag(holder); 707 708 holder.titleAndSubtitleLayout.getLayoutTransition().setDuration( 709 LAYOUT_TRANSITION_DURATION_MILLIS); 710 } 711 712 view.setEnabled(isEnabled(position)); 713 714 Pair<ApplicationInfo, Boolean> qualifyingApplication = getItem(position); 715 Drawable icon; 716 String title; 717 String subtitle; 718 if (qualifyingApplication == null) { 719 icon = AppCompatResources.getDrawable(context, R.drawable.ic_remove_circle); 720 title = context.getString(R.string.default_app_none); 721 subtitle = !mHasHolderApplication ? context.getString( 722 R.string.request_role_current_default) : null; 723 } else { 724 ApplicationInfo qualifyingApplicationInfo = qualifyingApplication.first; 725 icon = Utils.getBadgedIcon(context, qualifyingApplicationInfo); 726 title = Utils.getAppLabel(qualifyingApplicationInfo, context); 727 boolean isHolderApplication = qualifyingApplication.second; 728 subtitle = isHolderApplication 729 ? context.getString(R.string.request_role_current_default) 730 : mListView.isItemChecked(position) 731 ? context.getString(mRole.getRequestDescriptionResource()) : null; 732 } 733 734 holder.iconImage.setImageDrawable(icon); 735 holder.titleText.setText(title); 736 holder.subtitleText.setVisibility(!TextUtils.isEmpty(subtitle) ? View.VISIBLE 737 : View.GONE); 738 holder.subtitleText.setText(subtitle); 739 740 return view; 741 } 742 743 private static class ViewHolder { 744 745 @NonNull 746 public final ImageView iconImage; 747 @NonNull 748 public final ViewGroup titleAndSubtitleLayout; 749 @NonNull 750 public final TextView titleText; 751 @NonNull 752 public final TextView subtitleText; 753 ViewHolder(@onNull View view)754 ViewHolder(@NonNull View view) { 755 iconImage = view.requireViewById(R.id.icon); 756 titleAndSubtitleLayout = view.requireViewById(R.id.title_and_subtitle); 757 titleText = view.requireViewById(R.id.title); 758 subtitleText = view.requireViewById(R.id.subtitle); 759 } 760 } 761 } 762 } 763