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