Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 64c668e7 authored by Adam Bookatz's avatar Adam Bookatz
Browse files

Easy app-installing for Guest users

Allows an admin user to selectively install packages into a Guest user
by displaying a list of available packages that are installed in the
admin, but not in the Guest, and letting the admin choose to copy those
apps to the Guest.

Test: atest SettingsLibTests:com.android.settingslib.users.AppCopyingHelperTest

Test: Manual: install some apps in user 0, create a guest, uninstall
some system apps from it. Now, open this panel. Request some of those
apps be installed in the Guest and verify it worked.

Bug: 193281439

Change-Id: I4e6874a4ee93cd7bba96e1f6c8d04ed95873c1a2
parent b9db3d3a
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -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"
+5 −0
Original line number Diff line number Diff line
@@ -7728,6 +7728,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>
+4 −0
Original line number Diff line number Diff line
@@ -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"
+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