1 /*
2  * Copyright (C) 2021 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.settings.users;
18 
19 import android.app.settings.SettingsEnums;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.os.AsyncTask;
25 import android.os.Bundle;
26 import android.os.UserHandle;
27 import android.os.UserManager;
28 import android.util.Log;
29 
30 import androidx.preference.PreferenceGroup;
31 
32 import com.android.settings.R;
33 import com.android.settings.SettingsPreferenceFragment;
34 import com.android.settings.Utils;
35 import com.android.settingslib.users.AppCopyHelper;
36 import com.android.settingslib.widget.AppSwitchPreference;
37 
38 /**
39  * Allows an admin user to selectively copy some of their installed packages to a second user.
40  */
41 public class AppCopyFragment extends SettingsPreferenceFragment {
42     private static final String TAG = AppCopyFragment.class.getSimpleName();
43 
44     private static final boolean DEBUG = false;
45 
46     private static final String PKG_PREFIX = "pkg_";
47 
48     /** Key for extra passed in from calling fragment for the userId of the user being edited */
49     public static final String EXTRA_USER_ID = "user_id";
50 
51     protected UserManager mUserManager;
52     protected UserHandle mUser;
53 
54     private AppCopyHelper mHelper;
55 
56     /** List of installable apps presented to the user. */
57     private PreferenceGroup mAppList;
58 
59     private boolean mAppListChanged;
60 
61     private AsyncTask mAppLoadingTask;
62 
63     private final BroadcastReceiver mUserBackgrounding = new BroadcastReceiver() {
64         @Override
65         public void onReceive(Context context, Intent intent) {
66             if (DEBUG) Log.d(TAG, "mUserBackgrounding onReceive");
67             // Update the user's app selection right away without waiting for a pause
68             // onPause() might come in too late, causing apps to disappear after broadcasts
69             // have been scheduled during user startup.
70             if (mAppListChanged) {
71                 if (DEBUG) Log.d(TAG, "User backgrounding: installing apps");
72                 mHelper.installSelectedApps();
73                 if (DEBUG) Log.d(TAG, "User backgrounding: done installing apps");
74             }
75         }
76     };
77 
78     private final BroadcastReceiver mPackageObserver = new BroadcastReceiver() {
79         @Override
80         public void onReceive(Context context, Intent intent) {
81             onPackageChanged(intent);
82         }
83     };
84 
85     @Override
onCreate(Bundle icicle)86     public void onCreate(Bundle icicle) {
87         super.onCreate(icicle);
88         init(icicle);
89     }
90 
init(Bundle icicle)91     protected void init(Bundle icicle) {
92         if (icicle != null) {
93             mUser = new UserHandle(icicle.getInt(EXTRA_USER_ID));
94         } else {
95             final Bundle args = getArguments();
96             if (args != null) {
97                 if (args.containsKey(EXTRA_USER_ID)) {
98                     mUser = new UserHandle(args.getInt(EXTRA_USER_ID));
99                 }
100             }
101         }
102         if (mUser == null) {
103             throw new IllegalStateException("No user specified.");
104         }
105 
106         mHelper = new AppCopyHelper(getContext(), mUser);
107         mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
108 
109         addPreferencesFromResource(R.xml.app_copier);
110         mAppList = getPreferenceScreen();
111         mAppList.setOrderingAsAdded(false);
112     }
113 
114     @Override
getMetricsCategory()115     public int getMetricsCategory() {
116         return SettingsEnums.USERS_APP_COPYING;
117     }
118 
119     @Override
onSaveInstanceState(Bundle outState)120     public void onSaveInstanceState(Bundle outState) {
121         super.onSaveInstanceState(outState);
122         outState.putInt(EXTRA_USER_ID, mUser.getIdentifier());
123     }
124 
125     @Override
onResume()126     public void onResume() {
127         super.onResume();
128 
129         getActivity().registerReceiver(mUserBackgrounding,
130                 new IntentFilter(Intent.ACTION_USER_BACKGROUND));
131 
132         final IntentFilter packageFilter = new IntentFilter();
133         packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
134         packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
135         packageFilter.addDataScheme("package");
136         getActivity().registerReceiver(mPackageObserver, packageFilter);
137 
138         mAppListChanged = false;
139         if (mAppLoadingTask == null || mAppLoadingTask.getStatus() == AsyncTask.Status.FINISHED) {
140             mAppLoadingTask = new AppLoadingTask().execute();
141         }
142     }
143 
144     @Override
onPause()145     public void onPause() {
146         super.onPause();
147         getActivity().unregisterReceiver(mUserBackgrounding);
148         getActivity().unregisterReceiver(mPackageObserver);
149         if (mAppListChanged) {
150             new AsyncTask<Void, Void, Void>() {
151                 @Override
152                 protected Void doInBackground(Void... params) {
153                     mHelper.installSelectedApps();
154                     return null;
155                 }
156             }.execute();
157         }
158     }
159 
onPackageChanged(Intent intent)160     private void onPackageChanged(Intent intent) {
161         final String action = intent.getAction();
162         final String packageName = intent.getData().getSchemeSpecificPart();
163         if (DEBUG) Log.d(TAG, "onPackageChanged (" + action + "): " + packageName);
164 
165         // Package added/removed, so check if the preference needs to be enabled
166         final AppSwitchPreference pref = findPreference(getKeyForPackage(packageName));
167         if (pref == null) return;
168 
169         if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
170             pref.setEnabled(false);
171             pref.setChecked(false);
172             mHelper.setPackageSelected(packageName, false);
173         } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
174             pref.setEnabled(true);
175         }
176     }
177 
178     private class AppLoadingTask extends AsyncTask<Void, Void, Void> {
179 
180         @Override
doInBackground(Void... params)181         protected Void doInBackground(Void... params) {
182             mHelper.fetchAndMergeApps();
183             return null;
184         }
185 
186         @Override
onPostExecute(Void result)187         protected void onPostExecute(Void result) {
188             populateApps();
189         }
190     }
191 
populateApps()192     private void populateApps() {
193         // Check if the user was removed in the meantime.
194         if (Utils.getExistingUser(mUserManager, mUser) == null) {
195             return;
196         }
197         mHelper.resetSelectedPackages();
198         mAppList.removeAll();
199         for (AppCopyHelper.SelectableAppInfo app : mHelper.getVisibleApps()) {
200             if (app.packageName == null) continue;
201 
202             final AppSwitchPreference p = new AppSwitchPreference(getPrefContext());
203             p.setIcon(app.icon != null ? app.icon.mutate() : null);
204             p.setChecked(false);
205             p.setTitle(app.appName);
206             p.setKey(getKeyForPackage(app.packageName));
207             p.setPersistent(false);
208             p.setOnPreferenceChangeListener((preference, newValue) -> {
209                 if (!preference.isEnabled()) {
210                     // This item isn't available anymore (perhaps it was since uninstalled).
211                     if (DEBUG) Log.d(TAG, "onPreferenceChange but not enabled");
212                     return false;
213                 }
214 
215                 final boolean checked = (boolean) newValue;
216                 final String packageName = preference.getKey().substring(PKG_PREFIX.length());
217                 if (DEBUG) Log.d(TAG, "onPreferenceChange: " + packageName + " check=" + newValue);
218                 mHelper.setPackageSelected(packageName, checked);
219                 mAppListChanged = true;
220                 return true;
221             });
222 
223             mAppList.addPreference(p);
224         }
225         mAppListChanged = true;
226     }
227 
getKeyForPackage(String packageName)228     private String getKeyForPackage(String packageName) {
229         return PKG_PREFIX + packageName;
230     }
231 }
232