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