• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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