• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.specialappaccess;
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.os.Bundle;
24 import android.os.UserHandle;
25 import android.util.ArrayMap;
26 import android.util.Pair;
27 
28 import androidx.annotation.NonNull;
29 import androidx.annotation.Nullable;
30 import androidx.fragment.app.Fragment;
31 import androidx.lifecycle.ViewModelProviders;
32 import androidx.preference.Preference;
33 import androidx.preference.PreferenceFragmentCompat;
34 import androidx.preference.PreferenceManager;
35 import androidx.preference.PreferenceScreen;
36 import androidx.preference.TwoStatePreference;
37 
38 import com.android.permissioncontroller.permission.utils.Utils;
39 import com.android.permissioncontroller.role.model.Role;
40 import com.android.permissioncontroller.role.model.Roles;
41 import com.android.permissioncontroller.role.ui.ManageRoleHolderStateLiveData;
42 
43 import java.util.List;
44 
45 /**
46  * Child fragment for a special app access. Must be added as a child fragment and its parent
47  * fragment must be a {@link PreferenceFragmentCompat} which implements {@link Parent}.
48  *
49  * @param <PF> type of the parent fragment
50  */
51 public class SpecialAppAccessChildFragment<PF extends PreferenceFragmentCompat
52         & SpecialAppAccessChildFragment.Parent> extends Fragment
53         implements Preference.OnPreferenceClickListener {
54 
55     private static final String PREFERENCE_EXTRA_APPLICATION_INFO =
56             SpecialAppAccessChildFragment.class.getName() + ".extra.APPLICATION_INFO";
57 
58     private static final String PREFERENCE_KEY_DESCRIPTION =
59             SpecialAppAccessChildFragment.class.getName() + ".preference.DESCRIPTION";
60 
61     private String mRoleName;
62 
63     private Role mRole;
64 
65     private SpecialAppAccessViewModel mViewModel;
66 
67     /**
68      * Create a new instance of this fragment.
69      *
70      * @param roleName the name of the role for the special app access
71      *
72      * @return a new instance of this fragment
73      */
74     @NonNull
newInstance(@onNull String roleName)75     public static SpecialAppAccessChildFragment newInstance(@NonNull String roleName) {
76         SpecialAppAccessChildFragment fragment = new SpecialAppAccessChildFragment();
77         Bundle arguments = new Bundle();
78         arguments.putString(Intent.EXTRA_ROLE_NAME, roleName);
79         fragment.setArguments(arguments);
80         return fragment;
81     }
82 
83     @Override
onCreate(@ullable Bundle savedInstanceState)84     public void onCreate(@Nullable Bundle savedInstanceState) {
85         super.onCreate(savedInstanceState);
86 
87         Bundle arguments = getArguments();
88         mRoleName = arguments.getString(Intent.EXTRA_ROLE_NAME);
89     }
90 
91     @Override
onActivityCreated(@ullable Bundle savedInstanceState)92     public void onActivityCreated(@Nullable Bundle savedInstanceState) {
93         super.onActivityCreated(savedInstanceState);
94 
95         PF preferenceFragment = requirePreferenceFragment();
96         Activity activity = requireActivity();
97         mRole = Roles.get(activity).get(mRoleName);
98         preferenceFragment.setTitle(getString(mRole.getLabelResource()));
99 
100         mViewModel = ViewModelProviders.of(this, new SpecialAppAccessViewModel.Factory(mRole,
101                 activity.getApplication())).get(SpecialAppAccessViewModel.class);
102         mViewModel.getRoleLiveData().observe(this, this::onRoleChanged);
103         mViewModel.observeManageRoleHolderState(this, this::onManageRoleHolderStateChanged);
104     }
105 
onRoleChanged( @onNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications)106     private void onRoleChanged(
107             @NonNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications) {
108         PF preferenceFragment = requirePreferenceFragment();
109         PreferenceManager preferenceManager = preferenceFragment.getPreferenceManager();
110         Context context = preferenceManager.getContext();
111 
112         PreferenceScreen preferenceScreen = preferenceFragment.getPreferenceScreen();
113         Preference oldDescriptionPreference = null;
114         ArrayMap<String, Preference> oldPreferences = new ArrayMap<>();
115         if (preferenceScreen == null) {
116             preferenceScreen = preferenceManager.createPreferenceScreen(context);
117             preferenceFragment.setPreferenceScreen(preferenceScreen);
118         } else {
119             oldDescriptionPreference = preferenceScreen.findPreference(PREFERENCE_KEY_DESCRIPTION);
120             if (oldDescriptionPreference != null) {
121                 preferenceScreen.removePreference(oldDescriptionPreference);
122                 oldDescriptionPreference.setOrder(Preference.DEFAULT_ORDER);
123             }
124             for (int i = preferenceScreen.getPreferenceCount() - 1; i >= 0; --i) {
125                 Preference preference = preferenceScreen.getPreference(i);
126 
127                 preferenceScreen.removePreference(preference);
128                 preference.setOrder(Preference.DEFAULT_ORDER);
129                 oldPreferences.put(preference.getKey(), preference);
130             }
131         }
132 
133         int qualifyingApplicationsSize = qualifyingApplications.size();
134         for (int i = 0; i < qualifyingApplicationsSize; i++) {
135             Pair<ApplicationInfo, Boolean> qualifyingApplication = qualifyingApplications.get(i);
136             ApplicationInfo qualifyingApplicationInfo = qualifyingApplication.first;
137             boolean isHolderPackage = qualifyingApplication.second;
138 
139             String key = qualifyingApplicationInfo.packageName + '_'
140                     + qualifyingApplicationInfo.uid;
141             TwoStatePreference preference = (TwoStatePreference) oldPreferences.get(key);
142             if (preference == null) {
143                 preference = preferenceFragment.createApplicationPreference(context);
144                 preference.setKey(key);
145                 preference.setIcon(Utils.getBadgedIcon(context, qualifyingApplicationInfo));
146                 preference.setTitle(Utils.getFullAppLabel(qualifyingApplicationInfo, context));
147                 preference.setPersistent(false);
148                 preference.setOnPreferenceChangeListener((preference2, newValue) -> false);
149                 preference.setOnPreferenceClickListener(this);
150                 preference.getExtras().putParcelable(PREFERENCE_EXTRA_APPLICATION_INFO,
151                         qualifyingApplicationInfo);
152             }
153 
154             preference.setChecked(isHolderPackage);
155             UserHandle user = UserHandle.getUserHandleForUid(qualifyingApplicationInfo.uid);
156             mRole.prepareApplicationPreferenceAsUser(preference, qualifyingApplicationInfo, user,
157                     context);
158 
159             preferenceScreen.addPreference(preference);
160         }
161 
162         Preference descriptionPreference = oldDescriptionPreference;
163         if (descriptionPreference == null) {
164             descriptionPreference = preferenceFragment.createFooterPreference(context);
165             descriptionPreference.setKey(PREFERENCE_KEY_DESCRIPTION);
166             descriptionPreference.setSummary(mRole.getDescriptionResource());
167         }
168         preferenceScreen.addPreference(descriptionPreference);
169 
170         preferenceFragment.onPreferenceScreenChanged();
171     }
172 
onManageRoleHolderStateChanged(@onNull ManageRoleHolderStateLiveData liveData, int state)173     private void onManageRoleHolderStateChanged(@NonNull ManageRoleHolderStateLiveData liveData,
174             int state) {
175         switch (state) {
176             case ManageRoleHolderStateLiveData.STATE_SUCCESS:
177                 String packageName = liveData.getLastPackageName();
178                 if (packageName != null && liveData.isLastAdd()) {
179                     mRole.onHolderSelectedAsUser(packageName, liveData.getLastUser(),
180                             requireContext());
181                 }
182                 liveData.resetState();
183                 break;
184             case ManageRoleHolderStateLiveData.STATE_FAILURE:
185                 liveData.resetState();
186                 break;
187         }
188     }
189 
190     @Override
onPreferenceClick(@onNull Preference preference)191     public boolean onPreferenceClick(@NonNull Preference preference) {
192         ApplicationInfo applicationInfo = preference.getExtras().getParcelable(
193                 PREFERENCE_EXTRA_APPLICATION_INFO);
194         String packageName = applicationInfo.packageName;
195         UserHandle user = UserHandle.getUserHandleForUid(applicationInfo.uid);
196         boolean allow = !((TwoStatePreference) preference).isChecked();
197         String key = preference.getKey();
198         mViewModel.setSpecialAppAccessAsUser(packageName, allow, user, key, this,
199                 this::onManageRoleHolderStateChanged);
200         return true;
201     }
202 
203     @NonNull
requirePreferenceFragment()204     private PF requirePreferenceFragment() {
205         //noinspection unchecked
206         return (PF) requireParentFragment();
207     }
208 
209     /**
210      * Interface that the parent fragment must implement.
211      */
212     public interface Parent {
213 
214         /**
215          * Set the title of the current settings page.
216          *
217          * @param title the title of the current settings page
218          */
setTitle(@onNull CharSequence title)219         void setTitle(@NonNull CharSequence title);
220 
221         /**
222          * Create a new preference for an application.
223          *
224          * @param context the {@code Context} to use when creating the preference.
225          *
226          * @return a new preference for an application
227          */
228         @NonNull
createApplicationPreference(@onNull Context context)229         TwoStatePreference createApplicationPreference(@NonNull Context context);
230 
231         /**
232          * Create a new preference for the footer.
233          *
234          * @param context the {@code Context} to use when creating the preference.
235          *
236          * @return a new preference for the footer
237          */
238         @NonNull
createFooterPreference(@onNull Context context)239         Preference createFooterPreference(@NonNull Context context);
240 
241         /**
242          * Callback when changes have been made to the {@link PreferenceScreen} of the parent
243          * {@link PreferenceFragmentCompat}.
244          */
onPreferenceScreenChanged()245         void onPreferenceScreenChanged();
246     }
247 }
248