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