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

Commit 8bac1090 authored by Lifu Tang's avatar Lifu Tang Committed by Android (Google) Code Review
Browse files

Merge "Undeprecate injector API to allow change summary"

parents 6474e309 78e0756e
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -22866,9 +22866,10 @@ package android.location {
    ctor public SettingInjectorService(java.lang.String);
    method public final android.os.IBinder onBind(android.content.Intent);
    method protected abstract boolean onGetEnabled();
    method protected abstract deprecated java.lang.String onGetSummary();
    method protected abstract java.lang.String onGetSummary();
    method public final void onStart(android.content.Intent, int);
    method public final int onStartCommand(android.content.Intent, int, int);
    method public static final void refreshSettings(android.content.Context);
    field public static final java.lang.String ACTION_INJECTED_SETTING_CHANGED = "android.location.InjectedSettingChanged";
    field public static final java.lang.String ACTION_SERVICE_INTENT = "android.location.SettingInjectorService";
    field public static final java.lang.String ATTRIBUTES_NAME = "injected-location-setting";
+44 −23
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.location;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
@@ -26,7 +27,7 @@ import android.os.RemoteException;
import android.util.Log;

/**
 * Dynamically specifies the enabled status of a preference injected into
 * Dynamically specifies the summary (subtitle) and enabled status of a preference injected into
 * the list of app settings displayed by the system settings app
 * <p/>
 * For use only by apps that are included in the system image, for preferences that affect multiple
@@ -71,12 +72,13 @@ import android.util.Log;
 * </ul>
 *
 * To ensure a good user experience, your {@link android.app.Application#onCreate()},
 * and {@link #onGetEnabled()} methods must all be fast. If either is slow,
 * it can delay the display of settings values for other apps as well. Note further that these
 * methods are called on your app's UI thread.
 * {@link #onGetSummary()}, and {@link #onGetEnabled()} methods must all be fast. If any are slow,
 * it can delay the display of settings values for other apps as well. Note further that all are
 * called on your app's UI thread.
 * <p/>
 * For compactness, only one copy of a given setting should be injected. If each account has a
 * distinct value for the setting, then only {@code settingsActivity} should display the value for
 * distinct value for the setting, then the {@link #onGetSummary()} value should represent a summary
 * of the state across all of the accounts and {@code settingsActivity} should display the value for
 * each account.
 */
public abstract class SettingInjectorService extends Service {
@@ -107,6 +109,14 @@ public abstract class SettingInjectorService extends Service {
    public static final String ACTION_INJECTED_SETTING_CHANGED =
            "android.location.InjectedSettingChanged";

    /**
     * Name of the bundle key for the string specifying the summary for the setting (e.g., "ON" or
     * "OFF").
     *
     * @hide
     */
    public static final String SUMMARY_KEY = "summary";

    /**
     * Name of the bundle key for the string specifying whether the setting is currently enabled.
     *
@@ -150,36 +160,41 @@ public abstract class SettingInjectorService extends Service {
    }

    private void onHandleIntent(Intent intent) {

        boolean enabled;
        String summary = null;
        boolean enabled = false;
        try {
            summary = onGetSummary();
            enabled = onGetEnabled();
        } catch (RuntimeException e) {
            // Exception. Send status anyway, so that settings injector can immediately start
            // loading the status of the next setting.
            sendStatus(intent, true);
            throw e;
        } finally {
            // If exception happens, send status anyway, so that settings injector can immediately
            // start loading the status of the next setting. But leave the exception uncaught to
            // crash the injector service itself.
            sendStatus(intent, summary, enabled);
        }

        sendStatus(intent, enabled);
    }

    /**
     * Send the enabled values back to the caller via the messenger encoded in the
     * intent.
     */
    private void sendStatus(Intent intent, boolean enabled) {
    private void sendStatus(Intent intent, String summary, boolean enabled) {
        Messenger messenger = intent.getParcelableExtra(MESSENGER_KEY);
        // Bail out to avoid crashing GmsCore with incoming malicious Intent.
        if (messenger == null) {
            return;
        }

        Message message = Message.obtain();
        Bundle bundle = new Bundle();
        bundle.putString(SUMMARY_KEY, summary);
        bundle.putBoolean(ENABLED_KEY, enabled);
        message.setData(bundle);

        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, mName + ": received " + intent
            Log.d(TAG, mName + ": received " + intent + ", summary=" + summary
                    + ", enabled=" + enabled + ", sending message: " + message);
        }

        Messenger messenger = intent.getParcelableExtra(MESSENGER_KEY);
        try {
            messenger.send(message);
        } catch (RemoteException e) {
@@ -188,14 +203,12 @@ public abstract class SettingInjectorService extends Service {
    }

    /**
     * This method is no longer called, because status values are no longer shown for any injected
     * setting.
     *
     * @return ignored
     * Returns the {@link android.preference.Preference#getSummary()} value (allowed to be null or
     * empty). Should not perform unpredictably-long operations such as network access--see the
     * running-time comments in the class-level javadoc.
     *
     * @deprecated not called any more
     * @return the {@link android.preference.Preference#getSummary()} value
     */
    @Deprecated
    protected abstract String onGetSummary();

    /**
@@ -217,4 +230,12 @@ public abstract class SettingInjectorService extends Service {
     * @return the {@link android.preference.Preference#isEnabled()} value
     */
    protected abstract boolean onGetEnabled();

    /**
     * Sends a broadcast to refresh the injected settings on location settings page.
     */
    public static final void refreshSettings(Context context) {
        Intent intent = new Intent(ACTION_INJECTED_SETTING_CHANGED);
        context.sendBroadcast(intent);
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -831,6 +831,10 @@
    <!-- Toast message shown when setting a new local backup password fails due to the user not supplying the correct existing password. The phrasing here is deliberately quite general. [CHAR LIMIT=80] -->
    <string name="local_backup_password_toast_validation_failure">Failure setting backup password</string>

    <!-- [CHAR LIMIT=30] Location mode screen, temporary summary text to show as the status of a location
      setting injected by an external app while the app is being queried for the actual value -->
    <string name="loading_injected_setting_summary">Loading\u2026</string>

    <!-- Name of each color mode for the display. [CHAR LIMIT=40] -->
    <string-array name="color_mode_names">
        <item>Vibrant (default)</item>
+62 −69
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import android.os.Messenger;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.IconDrawableFactory;
import android.util.Log;
@@ -44,11 +45,16 @@ import android.util.Xml;

import androidx.preference.Preference;

import com.android.settingslib.R;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -102,7 +108,7 @@ public class SettingsInjector {
    public SettingsInjector(Context context) {
        mContext = context;
        mSettings = new HashSet<Setting>();
        mHandler = new StatusLoadingHandler();
        mHandler = new StatusLoadingHandler(mSettings);
    }

    /**
@@ -165,7 +171,7 @@ public class SettingsInjector {
            Log.e(TAG, "Can't get ApplicationInfo for " + setting.packageName, e);
        }
        preference.setTitle(setting.title);
        preference.setSummary(null);
        preference.setSummary(R.string.loading_injected_setting_summary);
        preference.setIcon(appIcon);
        preference.setOnPreferenceClickListener(new ServiceSettingClickedListener(setting));
    }
@@ -180,6 +186,7 @@ public class SettingsInjector {
        final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
        final List<UserHandle> profiles = um.getUserProfiles();
        ArrayList<Preference> prefs = new ArrayList<>();
        mSettings.clear();
        for (UserHandle userHandle : profiles) {
            if (profileId == UserHandle.USER_CURRENT || profileId == userHandle.getIdentifier()) {
                Iterable<InjectedSetting> settings = getSettings(userHandle);
@@ -363,31 +370,28 @@ public class SettingsInjector {
     * SettingInjectorService}, so to reduce memory pressure we don't want to load too many at
     * once.
     */
    private final class StatusLoadingHandler extends Handler {
    private static final class StatusLoadingHandler extends Handler {
        /**
         * References all the injected settings.
         */
        WeakReference<Set<Setting>> mAllSettings;

        /**
         * Settings whose status values need to be loaded. A set is used to prevent redundant loads.
         */
        private Set<Setting> mSettingsToLoad = new HashSet<Setting>();
        private Deque<Setting> mSettingsToLoad = new ArrayDeque<Setting>();

        /**
         * Settings that are being loaded now and haven't timed out. In practice this should have
         * zero or one elements.
         */
        private Set<Setting> mSettingsBeingLoaded = new HashSet<Setting>();

        /**
         * Settings that are being loaded but have timed out. If only one setting has timed out, we
         * will go ahead and start loading the next setting so that one slow load won't delay the
         * load of the other settings.
         */
        private Set<Setting> mTimedOutSettings = new HashSet<Setting>();

        private boolean mReloadRequested;
        private Set<Setting> mSettingsBeingLoaded = new ArraySet<Setting>();

        private StatusLoadingHandler() {
        public StatusLoadingHandler(Set<Setting> allSettings) {
            super(Looper.getMainLooper());
            mAllSettings = new WeakReference<>(allSettings);
        }

        @Override
        public void handleMessage(Message msg) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
@@ -396,20 +400,24 @@ public class SettingsInjector {

            // Update state in response to message
            switch (msg.what) {
                case WHAT_RELOAD:
                    mReloadRequested = true;
                case WHAT_RELOAD: {
                    final Set<Setting> allSettings = mAllSettings.get();
                    if (allSettings != null) {
                        // Reload requested, so must reload all settings
                        mSettingsToLoad.clear();
                        mSettingsToLoad.addAll(allSettings);
                    }
                    break;
                }
                case WHAT_RECEIVED_STATUS:
                    final Setting receivedSetting = (Setting) msg.obj;
                    receivedSetting.maybeLogElapsedTime();
                    mSettingsBeingLoaded.remove(receivedSetting);
                    mTimedOutSettings.remove(receivedSetting);
                    removeMessages(WHAT_TIMEOUT, receivedSetting);
                    break;
                case WHAT_TIMEOUT:
                    final Setting timedOutSetting = (Setting) msg.obj;
                    mSettingsBeingLoaded.remove(timedOutSetting);
                    mTimedOutSettings.add(timedOutSetting);
                    if (Log.isLoggable(TAG, Log.WARN)) {
                        Log.w(TAG, "Timed out after " + timedOutSetting.getElapsedTime()
                                + " millis trying to get status for: " + timedOutSetting);
@@ -421,37 +429,22 @@ public class SettingsInjector {

            // Decide whether to load additional settings based on the new state. Start by seeing
            // if we have headroom to load another setting.
            if (mSettingsBeingLoaded.size() > 0 || mTimedOutSettings.size() > 1) {
            if (mSettingsBeingLoaded.size() > 0) {
                // Don't load any more settings until one of the pending settings has completed.
                // To reduce memory pressure, we want to be loading at most one setting (plus at
                // most one timed-out setting) at a time. This means we'll be responsible for
                // bringing in at most two services.
                // To reduce memory pressure, we want to be loading at most one setting.
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "too many services already live for " + msg + ", " + this);
                }
                return;
            }

            if (mReloadRequested && mSettingsToLoad.isEmpty() && mSettingsBeingLoaded.isEmpty()
                    && mTimedOutSettings.isEmpty()) {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "reloading because idle and reload requesteed " + msg + ", " + this);
                }
                // Reload requested, so must reload all settings
                mSettingsToLoad.addAll(mSettings);
                mReloadRequested = false;
            }

            // Remove the next setting to load from the queue, if any
            Iterator<Setting> iter = mSettingsToLoad.iterator();
            if (!iter.hasNext()) {
            if (mSettingsToLoad.isEmpty()) {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "nothing left to do for " + msg + ", " + this);
                }
                return;
            }
            Setting setting = iter.next();
            iter.remove();
            Setting setting = mSettingsToLoad.removeFirst();

            // Request the status value
            setting.startService();
@@ -473,21 +466,48 @@ public class SettingsInjector {
            return "StatusLoadingHandler{" +
                    "mSettingsToLoad=" + mSettingsToLoad +
                    ", mSettingsBeingLoaded=" + mSettingsBeingLoaded +
                    ", mTimedOutSettings=" + mTimedOutSettings +
                    ", mReloadRequested=" + mReloadRequested +
                    '}';
        }
    }

    private static class MessengerHandler extends Handler {
        private WeakReference<Setting> mSettingRef;
        private Handler mHandler;

        public MessengerHandler(Setting setting, Handler handler) {
            mSettingRef = new WeakReference(setting);
            mHandler = handler;
        }

        @Override
        public void handleMessage(Message msg) {
            final Setting setting = mSettingRef.get();
            if (setting == null) {
                return;
            }
            final Preference preference = setting.preference;
            Bundle bundle = msg.getData();
            boolean enabled = bundle.getBoolean(SettingInjectorService.ENABLED_KEY, true);
            String summary = bundle.getString(SettingInjectorService.SUMMARY_KEY, null);
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, setting + ": received " + msg + ", bundle: " + bundle);
            }
            preference.setSummary(summary);
            preference.setEnabled(enabled);
            mHandler.sendMessage(
                    mHandler.obtainMessage(WHAT_RECEIVED_STATUS, setting));
        }
    }

    /**
     * Represents an injected setting and the corresponding preference.
     */
    protected final class Setting {

        public final InjectedSetting setting;
        public final Preference preference;
        public long startMillis;


        public Setting(InjectedSetting setting, Preference preference) {
            this.setting = setting;
            this.preference = preference;
@@ -501,20 +521,6 @@ public class SettingsInjector {
                    '}';
        }

        /**
         * Returns true if they both have the same {@link #setting} value. Ignores mutable
         * {@link #preference} and {@link #startMillis} so that it's safe to use in sets.
         */
        @Override
        public boolean equals(Object o) {
            return this == o || o instanceof Setting && setting.equals(((Setting) o).setting);
        }

        @Override
        public int hashCode() {
            return setting.hashCode();
        }

        /**
         * Starts the service to fetch for the current status for the setting, and updates the
         * preference when the service replies.
@@ -529,20 +535,7 @@ public class SettingsInjector {
                }
                return;
            }
            Handler handler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    Bundle bundle = msg.getData();
                    boolean enabled = bundle.getBoolean(SettingInjectorService.ENABLED_KEY, true);
                    if (Log.isLoggable(TAG, Log.DEBUG)) {
                        Log.d(TAG, setting + ": received " + msg + ", bundle: " + bundle);
                    }
                    preference.setSummary(null);
                    preference.setEnabled(enabled);
                    mHandler.sendMessage(
                            mHandler.obtainMessage(WHAT_RECEIVED_STATUS, Setting.this));
                }
            };
            Handler handler = new MessengerHandler(this, mHandler);
            Messenger messenger = new Messenger(handler);

            Intent intent = setting.getServiceIntent();