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