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.content.Context;
21 import android.content.Intent;
22 import android.content.pm.ApplicationInfo;
23 import android.graphics.drawable.Drawable;
24 import android.os.Bundle;
25 import android.os.UserHandle;
26 import android.util.ArrayMap;
27 import android.util.Pair;
28 
29 import androidx.annotation.NonNull;
30 import androidx.annotation.Nullable;
31 import androidx.appcompat.content.res.AppCompatResources;
32 import androidx.fragment.app.Fragment;
33 import androidx.lifecycle.ViewModelProviders;
34 import androidx.preference.Preference;
35 import androidx.preference.PreferenceFragmentCompat;
36 import androidx.preference.PreferenceManager;
37 import androidx.preference.PreferenceScreen;
38 import androidx.preference.TwoStatePreference;
39 
40 import com.android.permissioncontroller.R;
41 import com.android.permissioncontroller.permission.utils.Utils;
42 import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils;
43 import com.android.role.controller.model.Role;
44 import com.android.role.controller.model.Roles;
45 
46 import java.util.List;
47 import java.util.Objects;
48 
49 /**
50  * Child fragment for a default app.
51  * <p>
52  * Must be added as a child fragment and its parent fragment must be a
53  * {@link PreferenceFragmentCompat} that implements {@link Parent}.
54  *
55  * @param <PF> type of the parent fragment
56  */
57 public class DefaultAppChildFragment<PF extends PreferenceFragmentCompat
58         & DefaultAppChildFragment.Parent> extends Fragment
59         implements DefaultAppConfirmationDialogFragment.Listener,
60         Preference.OnPreferenceClickListener {
61 
62     private static final String PREFERENCE_KEY_NONE = DefaultAppChildFragment.class.getName()
63             + ".preference.NONE";
64     private static final String PREFERENCE_KEY_DESCRIPTION = DefaultAppChildFragment.class.getName()
65             + ".preference.DESCRIPTION";
66 
67     @NonNull
68     private String mRoleName;
69     @NonNull
70     private UserHandle mUser;
71 
72     @NonNull
73     private Role mRole;
74 
75     @NonNull
76     private DefaultAppViewModel mViewModel;
77 
78     /**
79      * Create a new instance of this fragment.
80      *
81      * @param roleName the name of the role for the default app
82      * @param user the user for the default app
83      *
84      * @return a new instance of this fragment
85      */
86     @NonNull
newInstance(@onNull String roleName, @NonNull UserHandle user)87     public static DefaultAppChildFragment newInstance(@NonNull String roleName,
88             @NonNull UserHandle user) {
89         DefaultAppChildFragment fragment = new DefaultAppChildFragment();
90         Bundle arguments = new Bundle();
91         arguments.putString(Intent.EXTRA_ROLE_NAME, roleName);
92         arguments.putParcelable(Intent.EXTRA_USER, user);
93         fragment.setArguments(arguments);
94         return fragment;
95     }
96 
97     @Override
onCreate(@ullable Bundle savedInstanceState)98     public void onCreate(@Nullable Bundle savedInstanceState) {
99         super.onCreate(savedInstanceState);
100 
101         Bundle arguments = getArguments();
102         mRoleName = arguments.getString(Intent.EXTRA_ROLE_NAME);
103         mUser = arguments.getParcelable(Intent.EXTRA_USER);
104     }
105 
106     @Override
onActivityCreated(@ullable Bundle savedInstanceState)107     public void onActivityCreated(@Nullable Bundle savedInstanceState) {
108         super.onActivityCreated(savedInstanceState);
109 
110         PF preferenceFragment = requirePreferenceFragment();
111         Activity activity = requireActivity();
112         mRole = Roles.get(activity).get(mRoleName);
113         preferenceFragment.setTitle(getString(mRole.getLabelResource()));
114 
115         mViewModel = ViewModelProviders.of(this, new DefaultAppViewModel.Factory(mRole, mUser,
116                 activity.getApplication())).get(DefaultAppViewModel.class);
117         mViewModel.getRoleLiveData().observe(this, this::onRoleChanged);
118         mViewModel.getManageRoleHolderStateLiveData().observe(this,
119                 this::onManageRoleHolderStateChanged);
120     }
121 
onRoleChanged( @onNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications)122     private void onRoleChanged(
123             @NonNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications) {
124         PF preferenceFragment = requirePreferenceFragment();
125         PreferenceManager preferenceManager = preferenceFragment.getPreferenceManager();
126         Context context = preferenceManager.getContext();
127 
128         PreferenceScreen preferenceScreen = preferenceFragment.getPreferenceScreen();
129         Preference oldDescriptionPreference = null;
130         ArrayMap<String, Preference> oldPreferences = new ArrayMap<>();
131         if (preferenceScreen == null) {
132             preferenceScreen = preferenceManager.createPreferenceScreen(context);
133             preferenceFragment.setPreferenceScreen(preferenceScreen);
134         } else {
135             oldDescriptionPreference = preferenceScreen.findPreference(PREFERENCE_KEY_DESCRIPTION);
136             if (oldDescriptionPreference != null) {
137                 preferenceScreen.removePreference(oldDescriptionPreference);
138                 oldDescriptionPreference.setOrder(Preference.DEFAULT_ORDER);
139             }
140             for (int i = preferenceScreen.getPreferenceCount() - 1; i >= 0; --i) {
141                 Preference preference = preferenceScreen.getPreference(i);
142 
143                 preferenceScreen.removePreference(preference);
144                 preference.setOrder(Preference.DEFAULT_ORDER);
145                 oldPreferences.put(preference.getKey(), preference);
146             }
147         }
148 
149         if (mRole.shouldShowNone()) {
150             Drawable icon = AppCompatResources.getDrawable(context, R.drawable.ic_remove_circle);
151             String title = getString(R.string.default_app_none);
152             boolean noHolderApplication = !hasHolderApplication(qualifyingApplications);
153             addPreference(PREFERENCE_KEY_NONE, icon, title, noHolderApplication, null,
154                     oldPreferences, preferenceScreen, context);
155         }
156 
157         int qualifyingApplicationsSize = qualifyingApplications.size();
158         for (int i = 0; i < qualifyingApplicationsSize; i++) {
159             Pair<ApplicationInfo, Boolean> qualifyingApplication = qualifyingApplications.get(i);
160             ApplicationInfo qualifyingApplicationInfo = qualifyingApplication.first;
161             boolean isHolderApplication = qualifyingApplication.second;
162 
163             String key = qualifyingApplicationInfo.packageName;
164             Drawable icon = Utils.getBadgedIcon(context, qualifyingApplicationInfo);
165             String title = Utils.getFullAppLabel(qualifyingApplicationInfo, context);
166             addPreference(key, icon, title, isHolderApplication, qualifyingApplicationInfo,
167                     oldPreferences, preferenceScreen, context);
168         }
169 
170         Preference descriptionPreference = oldDescriptionPreference;
171         if (descriptionPreference == null) {
172             descriptionPreference = preferenceFragment.createFooterPreference();
173             descriptionPreference.setKey(PREFERENCE_KEY_DESCRIPTION);
174             descriptionPreference.setSummary(mRole.getDescriptionResource());
175         }
176         preferenceScreen.addPreference(descriptionPreference);
177 
178         preferenceFragment.onPreferenceScreenChanged();
179     }
180 
hasHolderApplication( @onNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications)181     private static boolean hasHolderApplication(
182             @NonNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications) {
183         int qualifyingApplicationsSize = qualifyingApplications.size();
184         for (int i = 0; i < qualifyingApplicationsSize; i++) {
185             Pair<ApplicationInfo, Boolean> qualifyingApplication = qualifyingApplications.get(i);
186             boolean isHolderApplication = qualifyingApplication.second;
187 
188             if (isHolderApplication) {
189                 return true;
190             }
191         }
192         return false;
193     }
194 
addPreference(@onNull String key, @NonNull Drawable icon, @NonNull CharSequence title, boolean checked, @Nullable ApplicationInfo applicationInfo, @NonNull ArrayMap<String, Preference> oldPreferences, @NonNull PreferenceScreen preferenceScreen, @NonNull Context context)195     private void addPreference(@NonNull String key, @NonNull Drawable icon,
196             @NonNull CharSequence title, boolean checked, @Nullable ApplicationInfo applicationInfo,
197             @NonNull ArrayMap<String, Preference> oldPreferences,
198             @NonNull PreferenceScreen preferenceScreen, @NonNull Context context) {
199         RoleApplicationPreference roleApplicationPreference =
200                 (RoleApplicationPreference) oldPreferences.get(key);
201         TwoStatePreference preference;
202         if (roleApplicationPreference == null) {
203             roleApplicationPreference = requirePreferenceFragment().createApplicationPreference();
204             preference = roleApplicationPreference.asTwoStatePreference();
205             preference.setKey(key);
206             preference.setIcon(icon);
207             preference.setTitle(title);
208             preference.setPersistent(false);
209             preference.setOnPreferenceChangeListener((preference2, newValue) -> false);
210             preference.setOnPreferenceClickListener(this);
211         } else {
212             preference = roleApplicationPreference.asTwoStatePreference();
213         }
214 
215         preference.setChecked(checked);
216         if (applicationInfo != null) {
217             roleApplicationPreference.setRestrictionIntent(
218                     mRole.getApplicationRestrictionIntentAsUser(applicationInfo, mUser, context));
219             RoleUiBehaviorUtils.prepareApplicationPreferenceAsUser(mRole, roleApplicationPreference,
220                     applicationInfo, mUser, context);
221         }
222 
223         preferenceScreen.addPreference(preference);
224     }
225 
onManageRoleHolderStateChanged(int state)226     private void onManageRoleHolderStateChanged(int state) {
227         ManageRoleHolderStateLiveData liveData = mViewModel.getManageRoleHolderStateLiveData();
228         switch (state) {
229             case ManageRoleHolderStateLiveData.STATE_SUCCESS:
230                 String packageName = liveData.getLastPackageName();
231                 if (packageName != null) {
232                     mRole.onHolderSelectedAsUser(packageName, liveData.getLastUser(),
233                             requireContext());
234                 }
235                 liveData.resetState();
236                 break;
237             case ManageRoleHolderStateLiveData.STATE_FAILURE:
238                 liveData.resetState();
239                 break;
240         }
241     }
242 
243     @Override
onPreferenceClick(@onNull Preference preference)244     public boolean onPreferenceClick(@NonNull Preference preference) {
245         String key = preference.getKey();
246         if (Objects.equals(key, PREFERENCE_KEY_NONE)) {
247             mViewModel.setNoneDefaultApp();
248         } else {
249             String packageName = key;
250             CharSequence confirmationMessage =
251                     RoleUiBehaviorUtils.getConfirmationMessage(mRole, packageName,
252                             requireContext());
253             if (confirmationMessage != null) {
254                 DefaultAppConfirmationDialogFragment.show(packageName, confirmationMessage, this);
255             } else {
256                 setDefaultApp(packageName);
257             }
258         }
259         return true;
260     }
261 
262     @Override
setDefaultApp(@onNull String packageName)263     public void setDefaultApp(@NonNull String packageName) {
264         mViewModel.setDefaultApp(packageName);
265     }
266 
267     @NonNull
requirePreferenceFragment()268     private PF requirePreferenceFragment() {
269         //noinspection unchecked
270         return (PF) requireParentFragment();
271     }
272 
273     /**
274      * Interface that the parent fragment must implement.
275      */
276     public interface Parent {
277 
278         /**
279          * Set the title of the current settings page.
280          *
281          * @param title the title of the current settings page
282          */
setTitle(@onNull CharSequence title)283         void setTitle(@NonNull CharSequence title);
284 
285         /**
286          * Create a new preference for an application.
287          *
288          * @return a new preference for an application
289          */
290         @NonNull
createApplicationPreference()291         RoleApplicationPreference createApplicationPreference();
292 
293         /**
294          * Create a new preference for the footer.
295          *
296          * @return a new preference for the footer
297          */
298         @NonNull
createFooterPreference()299         Preference createFooterPreference();
300 
301         /**
302          * Callback when changes have been made to the {@link PreferenceScreen} of the parent
303          * {@link PreferenceFragmentCompat}.
304          */
onPreferenceScreenChanged()305         void onPreferenceScreenChanged();
306     }
307 }
308