diff options
| -rw-r--r-- | api/current.txt | 3 | ||||
| -rw-r--r-- | location/java/android/location/SettingInjectorService.java | 67 | ||||
| -rw-r--r-- | packages/SettingsLib/res/values/strings.xml | 4 | ||||
| -rw-r--r-- | packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java | 131 |
4 files changed, 112 insertions, 93 deletions
diff --git a/api/current.txt b/api/current.txt index 3f5abe97f750..e600870d1a71 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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"; diff --git a/location/java/android/location/SettingInjectorService.java b/location/java/android/location/SettingInjectorService.java index fcd2cdec904f..c20177058b68 100644 --- a/location/java/android/location/SettingInjectorService.java +++ b/location/java/android/location/SettingInjectorService.java @@ -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 { @@ -108,6 +110,14 @@ public abstract class SettingInjectorService extends Service { "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. * * @hide @@ -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); + } } diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 2823149a1585..842779d494cd 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -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> diff --git a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java index 780fcbab9822..74057be8434b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java +++ b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java @@ -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; @@ -502,20 +522,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(); |