Loading res/drawable/ic_apps.xml +2 −1 Original line number Diff line number Diff line Loading @@ -17,7 +17,8 @@ android:width="24dp" android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> android:viewportHeight="24.0" android:tint="?android:attr/colorControlNormal"> <path android:pathData="M16,6C16,7.1 16.9,8 18,8C19.1,8 20,7.1 20,6C20,4.9 19.1,4 18,4C16.9,4 16,4.9 16,6ZM6,8C7.1,8 8,7.1 8,6C8,4.9 7.1,4 6,4C4.9,4 4,4.9 4,6C4,7.1 4.9,8 6,8ZM12.001,20C13.101,20 14.001,19.1 14.001,18C14.001,16.9 13.101,16 12.001,16C10.901,16 10.001,16.9 10.001,18C10.001,19.1 10.901,20 12.001,20ZM8.001,18C8.001,19.1 7.101,20 6.001,20C4.901,20 4.001,19.1 4.001,18C4.001,16.9 4.901,16 6.001,16C7.101,16 8.001,16.9 8.001,18ZM6.001,14C7.101,14 8.001,13.1 8.001,12C8.001,10.9 7.101,10 6.001,10C4.901,10 4.001,10.9 4.001,12C4.001,13.1 4.901,14 6.001,14ZM14.001,12C14.001,13.1 13.101,14 12.001,14C10.901,14 10.001,13.1 10.001,12C10.001,10.9 10.901,10 12.001,10C13.101,10 14.001,10.9 14.001,12ZM14.001,6C14.001,7.1 13.101,8 12.001,8C10.901,8 10.001,7.1 10.001,6C10.001,4.9 10.901,4 12.001,4C13.101,4 14.001,4.9 14.001,6ZM18,14C19.1,14 20,13.1 20,12C20,10.9 19.1,10 18,10C16.9,10 16,10.9 16,12C16,13.1 16.9,14 18,14ZM20,18C20,19.1 19.1,20 18,20C16.9,20 16,19.1 16,18C16,16.9 16.9,16 18,16C19.1,16 20,16.9 20,18Z" android:fillType="evenOdd" Loading res/values/strings.xml +5 −0 Original line number Diff line number Diff line Loading @@ -7724,6 +7724,11 @@ <!-- Applicaitons with restrictions - settings button [CHAR LIMIT=30] --> <string name="apps_with_restrictions_settings_button">Expand settings for application</string> <!-- Title for copying apps to another user [CHAR LIMIT=45] --> <string name="user_choose_copy_apps_to_another_user">Choose apps to install</string> <!-- Menu title for copying apps to another user [CHAR LIMIT=35] --> <string name="user_copy_apps_menu_title">Install available apps</string> <!-- NFC payment settings --><skip/> <string name="nfc_payment_settings_title">Contactless payments</string> <!-- Caption for button linking to a page explaining how Tap and Pay works--> res/xml/app_copier.xml 0 → 100644 +20 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2021 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" xmlns:settings="http://schemas.android.com/apk/res-auto" android:title="@string/user_choose_copy_apps_to_another_user"> </PreferenceScreen> res/xml/user_details_settings.xml +4 −0 Original line number Diff line number Diff line Loading @@ -29,6 +29,10 @@ android:key="app_and_content_access" android:icon="@drawable/ic_lock_closed" android:title="@string/user_restrictions_title" /> <com.android.settingslib.RestrictedPreference android:key="app_copying" android:icon="@drawable/ic_apps" android:title="@string/user_copy_apps_menu_title" /> <com.android.settingslib.RestrictedPreference android:key="remove_user" android:icon="@drawable/ic_delete" Loading src/com/android/settings/users/AppCopyFragment.java 0 → 100644 +231 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.users; import android.app.settings.SettingsEnums; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.AsyncTask; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; import androidx.preference.PreferenceGroup; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.Utils; import com.android.settingslib.users.AppCopyHelper; import com.android.settingslib.widget.AppSwitchPreference; /** * Allows an admin user to selectively copy some of their installed packages to a second user. */ public class AppCopyFragment extends SettingsPreferenceFragment { private static final String TAG = AppCopyFragment.class.getSimpleName(); private static final boolean DEBUG = false; private static final String PKG_PREFIX = "pkg_"; /** Key for extra passed in from calling fragment for the userId of the user being edited */ public static final String EXTRA_USER_ID = "user_id"; protected UserManager mUserManager; protected UserHandle mUser; private AppCopyHelper mHelper; /** List of installable apps presented to the user. */ private PreferenceGroup mAppList; private boolean mAppListChanged; private AsyncTask mAppLoadingTask; private final BroadcastReceiver mUserBackgrounding = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Log.d(TAG, "mUserBackgrounding onReceive"); // Update the user's app selection right away without waiting for a pause // onPause() might come in too late, causing apps to disappear after broadcasts // have been scheduled during user startup. if (mAppListChanged) { if (DEBUG) Log.d(TAG, "User backgrounding: installing apps"); mHelper.installSelectedApps(); if (DEBUG) Log.d(TAG, "User backgrounding: done installing apps"); } } }; private final BroadcastReceiver mPackageObserver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { onPackageChanged(intent); } }; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); init(icicle); } protected void init(Bundle icicle) { if (icicle != null) { mUser = new UserHandle(icicle.getInt(EXTRA_USER_ID)); } else { final Bundle args = getArguments(); if (args != null) { if (args.containsKey(EXTRA_USER_ID)) { mUser = new UserHandle(args.getInt(EXTRA_USER_ID)); } } } if (mUser == null) { throw new IllegalStateException("No user specified."); } mHelper = new AppCopyHelper(getContext(), mUser); mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE); addPreferencesFromResource(R.xml.app_copier); mAppList = getPreferenceScreen(); mAppList.setOrderingAsAdded(false); } @Override public int getMetricsCategory() { return SettingsEnums.USERS_APP_COPYING; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(EXTRA_USER_ID, mUser.getIdentifier()); } @Override public void onResume() { super.onResume(); getActivity().registerReceiver(mUserBackgrounding, new IntentFilter(Intent.ACTION_USER_BACKGROUND)); final IntentFilter packageFilter = new IntentFilter(); packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); packageFilter.addDataScheme("package"); getActivity().registerReceiver(mPackageObserver, packageFilter); mAppListChanged = false; if (mAppLoadingTask == null || mAppLoadingTask.getStatus() == AsyncTask.Status.FINISHED) { mAppLoadingTask = new AppLoadingTask().execute(); } } @Override public void onPause() { super.onPause(); getActivity().unregisterReceiver(mUserBackgrounding); getActivity().unregisterReceiver(mPackageObserver); if (mAppListChanged) { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { mHelper.installSelectedApps(); return null; } }.execute(); } } private void onPackageChanged(Intent intent) { final String action = intent.getAction(); final String packageName = intent.getData().getSchemeSpecificPart(); if (DEBUG) Log.d(TAG, "onPackageChanged (" + action + "): " + packageName); // Package added/removed, so check if the preference needs to be enabled final AppSwitchPreference pref = findPreference(getKeyForPackage(packageName)); if (pref == null) return; if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { pref.setEnabled(false); pref.setChecked(false); mHelper.setPackageSelected(packageName, false); } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { pref.setEnabled(true); } } private class AppLoadingTask extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... params) { mHelper.fetchAndMergeApps(); return null; } @Override protected void onPostExecute(Void result) { populateApps(); } } private void populateApps() { // Check if the user was removed in the meantime. if (Utils.getExistingUser(mUserManager, mUser) == null) { return; } mHelper.resetSelectedPackages(); mAppList.removeAll(); for (AppCopyHelper.SelectableAppInfo app : mHelper.getVisibleApps()) { if (app.packageName == null) continue; final AppSwitchPreference p = new AppSwitchPreference(getPrefContext()); p.setIcon(app.icon != null ? app.icon.mutate() : null); p.setChecked(false); p.setTitle(app.appName); p.setKey(getKeyForPackage(app.packageName)); p.setPersistent(false); p.setOnPreferenceChangeListener((preference, newValue) -> { if (!preference.isEnabled()) { // This item isn't available anymore (perhaps it was since uninstalled). if (DEBUG) Log.d(TAG, "onPreferenceChange but not enabled"); return false; } final boolean checked = (boolean) newValue; final String packageName = preference.getKey().substring(PKG_PREFIX.length()); if (DEBUG) Log.d(TAG, "onPreferenceChange: " + packageName + " check=" + newValue); mHelper.setPackageSelected(packageName, checked); mAppListChanged = true; return true; }); mAppList.addPreference(p); } mAppListChanged = true; } private String getKeyForPackage(String packageName) { return PKG_PREFIX + packageName; } } Loading
res/drawable/ic_apps.xml +2 −1 Original line number Diff line number Diff line Loading @@ -17,7 +17,8 @@ android:width="24dp" android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> android:viewportHeight="24.0" android:tint="?android:attr/colorControlNormal"> <path android:pathData="M16,6C16,7.1 16.9,8 18,8C19.1,8 20,7.1 20,6C20,4.9 19.1,4 18,4C16.9,4 16,4.9 16,6ZM6,8C7.1,8 8,7.1 8,6C8,4.9 7.1,4 6,4C4.9,4 4,4.9 4,6C4,7.1 4.9,8 6,8ZM12.001,20C13.101,20 14.001,19.1 14.001,18C14.001,16.9 13.101,16 12.001,16C10.901,16 10.001,16.9 10.001,18C10.001,19.1 10.901,20 12.001,20ZM8.001,18C8.001,19.1 7.101,20 6.001,20C4.901,20 4.001,19.1 4.001,18C4.001,16.9 4.901,16 6.001,16C7.101,16 8.001,16.9 8.001,18ZM6.001,14C7.101,14 8.001,13.1 8.001,12C8.001,10.9 7.101,10 6.001,10C4.901,10 4.001,10.9 4.001,12C4.001,13.1 4.901,14 6.001,14ZM14.001,12C14.001,13.1 13.101,14 12.001,14C10.901,14 10.001,13.1 10.001,12C10.001,10.9 10.901,10 12.001,10C13.101,10 14.001,10.9 14.001,12ZM14.001,6C14.001,7.1 13.101,8 12.001,8C10.901,8 10.001,7.1 10.001,6C10.001,4.9 10.901,4 12.001,4C13.101,4 14.001,4.9 14.001,6ZM18,14C19.1,14 20,13.1 20,12C20,10.9 19.1,10 18,10C16.9,10 16,10.9 16,12C16,13.1 16.9,14 18,14ZM20,18C20,19.1 19.1,20 18,20C16.9,20 16,19.1 16,18C16,16.9 16.9,16 18,16C19.1,16 20,16.9 20,18Z" android:fillType="evenOdd" Loading
res/values/strings.xml +5 −0 Original line number Diff line number Diff line Loading @@ -7724,6 +7724,11 @@ <!-- Applicaitons with restrictions - settings button [CHAR LIMIT=30] --> <string name="apps_with_restrictions_settings_button">Expand settings for application</string> <!-- Title for copying apps to another user [CHAR LIMIT=45] --> <string name="user_choose_copy_apps_to_another_user">Choose apps to install</string> <!-- Menu title for copying apps to another user [CHAR LIMIT=35] --> <string name="user_copy_apps_menu_title">Install available apps</string> <!-- NFC payment settings --><skip/> <string name="nfc_payment_settings_title">Contactless payments</string> <!-- Caption for button linking to a page explaining how Tap and Pay works-->
res/xml/app_copier.xml 0 → 100644 +20 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2021 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" xmlns:settings="http://schemas.android.com/apk/res-auto" android:title="@string/user_choose_copy_apps_to_another_user"> </PreferenceScreen>
res/xml/user_details_settings.xml +4 −0 Original line number Diff line number Diff line Loading @@ -29,6 +29,10 @@ android:key="app_and_content_access" android:icon="@drawable/ic_lock_closed" android:title="@string/user_restrictions_title" /> <com.android.settingslib.RestrictedPreference android:key="app_copying" android:icon="@drawable/ic_apps" android:title="@string/user_copy_apps_menu_title" /> <com.android.settingslib.RestrictedPreference android:key="remove_user" android:icon="@drawable/ic_delete" Loading
src/com/android/settings/users/AppCopyFragment.java 0 → 100644 +231 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.users; import android.app.settings.SettingsEnums; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.AsyncTask; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; import androidx.preference.PreferenceGroup; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.Utils; import com.android.settingslib.users.AppCopyHelper; import com.android.settingslib.widget.AppSwitchPreference; /** * Allows an admin user to selectively copy some of their installed packages to a second user. */ public class AppCopyFragment extends SettingsPreferenceFragment { private static final String TAG = AppCopyFragment.class.getSimpleName(); private static final boolean DEBUG = false; private static final String PKG_PREFIX = "pkg_"; /** Key for extra passed in from calling fragment for the userId of the user being edited */ public static final String EXTRA_USER_ID = "user_id"; protected UserManager mUserManager; protected UserHandle mUser; private AppCopyHelper mHelper; /** List of installable apps presented to the user. */ private PreferenceGroup mAppList; private boolean mAppListChanged; private AsyncTask mAppLoadingTask; private final BroadcastReceiver mUserBackgrounding = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Log.d(TAG, "mUserBackgrounding onReceive"); // Update the user's app selection right away without waiting for a pause // onPause() might come in too late, causing apps to disappear after broadcasts // have been scheduled during user startup. if (mAppListChanged) { if (DEBUG) Log.d(TAG, "User backgrounding: installing apps"); mHelper.installSelectedApps(); if (DEBUG) Log.d(TAG, "User backgrounding: done installing apps"); } } }; private final BroadcastReceiver mPackageObserver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { onPackageChanged(intent); } }; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); init(icicle); } protected void init(Bundle icicle) { if (icicle != null) { mUser = new UserHandle(icicle.getInt(EXTRA_USER_ID)); } else { final Bundle args = getArguments(); if (args != null) { if (args.containsKey(EXTRA_USER_ID)) { mUser = new UserHandle(args.getInt(EXTRA_USER_ID)); } } } if (mUser == null) { throw new IllegalStateException("No user specified."); } mHelper = new AppCopyHelper(getContext(), mUser); mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE); addPreferencesFromResource(R.xml.app_copier); mAppList = getPreferenceScreen(); mAppList.setOrderingAsAdded(false); } @Override public int getMetricsCategory() { return SettingsEnums.USERS_APP_COPYING; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(EXTRA_USER_ID, mUser.getIdentifier()); } @Override public void onResume() { super.onResume(); getActivity().registerReceiver(mUserBackgrounding, new IntentFilter(Intent.ACTION_USER_BACKGROUND)); final IntentFilter packageFilter = new IntentFilter(); packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); packageFilter.addDataScheme("package"); getActivity().registerReceiver(mPackageObserver, packageFilter); mAppListChanged = false; if (mAppLoadingTask == null || mAppLoadingTask.getStatus() == AsyncTask.Status.FINISHED) { mAppLoadingTask = new AppLoadingTask().execute(); } } @Override public void onPause() { super.onPause(); getActivity().unregisterReceiver(mUserBackgrounding); getActivity().unregisterReceiver(mPackageObserver); if (mAppListChanged) { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { mHelper.installSelectedApps(); return null; } }.execute(); } } private void onPackageChanged(Intent intent) { final String action = intent.getAction(); final String packageName = intent.getData().getSchemeSpecificPart(); if (DEBUG) Log.d(TAG, "onPackageChanged (" + action + "): " + packageName); // Package added/removed, so check if the preference needs to be enabled final AppSwitchPreference pref = findPreference(getKeyForPackage(packageName)); if (pref == null) return; if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { pref.setEnabled(false); pref.setChecked(false); mHelper.setPackageSelected(packageName, false); } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { pref.setEnabled(true); } } private class AppLoadingTask extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... params) { mHelper.fetchAndMergeApps(); return null; } @Override protected void onPostExecute(Void result) { populateApps(); } } private void populateApps() { // Check if the user was removed in the meantime. if (Utils.getExistingUser(mUserManager, mUser) == null) { return; } mHelper.resetSelectedPackages(); mAppList.removeAll(); for (AppCopyHelper.SelectableAppInfo app : mHelper.getVisibleApps()) { if (app.packageName == null) continue; final AppSwitchPreference p = new AppSwitchPreference(getPrefContext()); p.setIcon(app.icon != null ? app.icon.mutate() : null); p.setChecked(false); p.setTitle(app.appName); p.setKey(getKeyForPackage(app.packageName)); p.setPersistent(false); p.setOnPreferenceChangeListener((preference, newValue) -> { if (!preference.isEnabled()) { // This item isn't available anymore (perhaps it was since uninstalled). if (DEBUG) Log.d(TAG, "onPreferenceChange but not enabled"); return false; } final boolean checked = (boolean) newValue; final String packageName = preference.getKey().substring(PKG_PREFIX.length()); if (DEBUG) Log.d(TAG, "onPreferenceChange: " + packageName + " check=" + newValue); mHelper.setPackageSelected(packageName, checked); mAppListChanged = true; return true; }); mAppList.addPreference(p); } mAppListChanged = true; } private String getKeyForPackage(String packageName) { return PKG_PREFIX + packageName; } }