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

Commit 78e0756e authored by Lifu Tang's avatar Lifu Tang
Browse files

Undeprecate injector API to allow change summary

Bug: 120236748
Test: build, flash, and test manually
Change-Id: I96042f51ce3fe32d15596b026f802f89dabf8405
parent 1982ca78
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -22823,9 +22823,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();