Loading packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +12 −1 Original line number Diff line number Diff line Loading @@ -45,7 +45,9 @@ import com.android.systemui.plugins.qs.QSFactory; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTileView; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.external.CustomTileStatePersister; import com.android.systemui.qs.external.TileLifecycleManager; import com.android.systemui.qs.external.TileServiceKey; import com.android.systemui.qs.external.TileServices; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.settings.UserTracker; Loading Loading @@ -93,6 +95,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D private final QSLogger mQSLogger; private final UiEventLogger mUiEventLogger; private final InstanceIdSequence mInstanceIdSequence; private final CustomTileStatePersister mCustomTileStatePersister; private final List<Callback> mCallbacks = new ArrayList<>(); private AutoTileManager mAutoTiles; Loading @@ -119,7 +122,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D QSLogger qsLogger, UiEventLogger uiEventLogger, UserTracker userTracker, SecureSettings secureSettings) { SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister) { mIconController = iconController; mContext = context; mUserContext = context; Loading @@ -139,6 +143,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D mDumpManager.registerDumpable(TAG, this); mUserTracker = userTracker; mSecureSettings = secureSettings; mCustomTileStatePersister = customTileStatePersister; mainHandler.post(() -> { // This is technically a hack to avoid circular dependency of Loading Loading @@ -418,6 +423,11 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D changeTiles(mTileSpecs, newSpecs); } /** * Change the tiles triggered by the user editing. * <p> * This is not called on device start, or on user change. */ public void changeTiles(List<String> previousTiles, List<String> newTiles) { final List<String> copy = new ArrayList<>(previousTiles); final int NP = copy.size(); Loading @@ -433,6 +443,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D mBroadcastDispatcher); lifecycleManager.onStopListening(); lifecycleManager.onTileRemoved(); mCustomTileStatePersister.removeState(new TileServiceKey(component, mCurrentUser)); TileLifecycleManager.setTileAdded(mContext, component, false); lifecycleManager.flushMessagesAndUnbind(); } Loading packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +64 −13 Original line number Diff line number Diff line Loading @@ -46,6 +46,7 @@ import android.widget.Switch; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; Loading Loading @@ -85,6 +86,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener private final IQSTileService mService; private final TileServiceManager mServiceManager; private final int mUser; private final CustomTileStatePersister mCustomTileStatePersister; private android.graphics.drawable.Icon mDefaultIcon; private CharSequence mDefaultLabel; Loading @@ -94,6 +96,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener private boolean mIsTokenGranted; private boolean mIsShowingDialog; private final TileServiceKey mKey; private CustomTile( QSHost host, Looper backgroundLooper, Loading @@ -104,7 +108,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener ActivityStarter activityStarter, QSLogger qsLogger, String action, Context userContext Context userContext, CustomTileStatePersister customTileStatePersister ) { super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); Loading @@ -113,15 +118,29 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mTile = new Tile(); mUserContext = userContext; mUser = mUserContext.getUserId(); updateDefaultTileAndIcon(); mKey = new TileServiceKey(mComponent, mUser); mServiceManager = host.getTileServices().getTileWrapper(this); mService = mServiceManager.getTileService(); mCustomTileStatePersister = customTileStatePersister; } @Override protected void handleInitialize() { updateDefaultTileAndIcon(); if (mServiceManager.isToggleableTile()) { // Replace states with BooleanState resetStates(); } mService = mServiceManager.getTileService(); mServiceManager.setTileChangeListener(this); if (mServiceManager.isActiveTile()) { Tile t = mCustomTileStatePersister.readState(mKey); if (t != null) { applyTileState(t, /* overwriteNulls */ false); mServiceManager.clearPendingBind(); refreshState(); } } } @Override Loading Loading @@ -191,7 +210,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener @Override public void onTileChanged(ComponentName tile) { updateDefaultTileAndIcon(); mHandler.post(this::updateDefaultTileAndIcon); } @Override Loading @@ -213,16 +232,44 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener } public Tile getQsTile() { // TODO(b/191145007) Move to background thread safely updateDefaultTileAndIcon(); return mTile; } public void updateState(Tile tile) { /** * Update state of {@link this#mTile} from a remote {@link TileService}. * @param tile tile populated with state to apply */ public void updateTileState(Tile tile) { // This comes from a binder call IQSService.updateQsTile mHandler.post(() -> handleUpdateTileState(tile)); } private void handleUpdateTileState(Tile tile) { applyTileState(tile, /* overwriteNulls */ true); if (mServiceManager.isActiveTile()) { mCustomTileStatePersister.persistState(mKey, tile); } } @WorkerThread private void applyTileState(Tile tile, boolean overwriteNulls) { if (tile.getIcon() != null || overwriteNulls) { mTile.setIcon(tile.getIcon()); } if (tile.getLabel() != null || overwriteNulls) { mTile.setLabel(tile.getLabel()); } if (tile.getSubtitle() != null || overwriteNulls) { mTile.setSubtitle(tile.getSubtitle()); } if (tile.getContentDescription() != null || overwriteNulls) { mTile.setContentDescription(tile.getContentDescription()); } if (tile.getStateDescription() != null || overwriteNulls) { mTile.setStateDescription(tile.getStateDescription()); } mTile.setState(tile.getState()); } Loading Loading @@ -459,6 +506,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener final StatusBarStateController mStatusBarStateController; final ActivityStarter mActivityStarter; final QSLogger mQSLogger; final CustomTileStatePersister mCustomTileStatePersister; Context mUserContext; String mSpec = ""; Loading @@ -472,7 +520,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger QSLogger qsLogger, CustomTileStatePersister customTileStatePersister ) { mQSHostLazy = hostLazy; mBackgroundLooper = backgroundLooper; Loading @@ -482,6 +531,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mStatusBarStateController = statusBarStateController; mActivityStarter = activityStarter; mQSLogger = qsLogger; mCustomTileStatePersister = customTileStatePersister; } Builder setSpec(@NonNull String spec) { Loading Loading @@ -509,7 +559,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mActivityStarter, mQSLogger, action, mUserContext mUserContext, mCustomTileStatePersister ); } } Loading packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt 0 → 100644 +122 −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.systemui.qs.external import android.content.ComponentName import android.content.Context import android.service.quicksettings.Tile import android.util.Log import com.android.internal.annotations.VisibleForTesting import org.json.JSONException import org.json.JSONObject import javax.inject.Inject data class TileServiceKey(val componentName: ComponentName, val user: Int) { private val string = "${componentName.flattenToString()}:$user" override fun toString() = string } private const val STATE = "state" private const val LABEL = "label" private const val SUBTITLE = "subtitle" private const val CONTENT_DESCRIPTION = "content_description" private const val STATE_DESCRIPTION = "state_description" /** * Persists and retrieves state for [CustomTile]. * * This class will persists to a fixed [SharedPreference] file a state for a pair of [ComponentName] * and user id ([TileServiceKey]). * * It persists the state from a [Tile] necessary to present the view in the same state when * retrieved, with the exception of the icon. */ class CustomTileStatePersister @Inject constructor(context: Context) { companion object { private const val FILE_NAME = "custom_tiles_state" } private val sharedPreferences = context.getSharedPreferences(FILE_NAME, 0) /** * Read the state from [SharedPreferences]. * * Returns `null` if the tile has no saved state. * * Any fields that have not been saved will be set to `null` */ fun readState(key: TileServiceKey): Tile? { val state = sharedPreferences.getString(key.toString(), null) ?: return null return try { readTileFromString(state) } catch (e: JSONException) { Log.e("TileServicePersistence", "Bad saved state: $state", e) null } } /** * Persists the state into [SharedPreferences]. * * The implementation does not store fields that are `null` or icons. */ fun persistState(key: TileServiceKey, tile: Tile) { val state = writeToString(tile) sharedPreferences.edit().putString(key.toString(), state).apply() } /** * Removes the state for a given tile, user pair. * * Used when the tile is removed by the user. */ fun removeState(key: TileServiceKey) { sharedPreferences.edit().remove(key.toString()).apply() } } @VisibleForTesting internal fun readTileFromString(stateString: String): Tile { val json = JSONObject(stateString) return Tile().apply { state = json.getInt(STATE) label = json.getStringOrNull(LABEL) subtitle = json.getStringOrNull(SUBTITLE) contentDescription = json.getStringOrNull(CONTENT_DESCRIPTION) stateDescription = json.getStringOrNull(STATE_DESCRIPTION) } } // Properties with null values will not be saved to the Json string in any way. This makes sure // to properly retrieve a null in that case. private fun JSONObject.getStringOrNull(name: String): String? { return if (has(name)) getString(name) else null } @VisibleForTesting internal fun writeToString(tile: Tile): String { // Not storing the icon return with(tile) { JSONObject() .put(STATE, state) .put(LABEL, label) .put(SUBTITLE, subtitle) .put(CONTENT_DESCRIPTION, contentDescription) .put(STATE_DESCRIPTION, stateDescription) .toString() } } packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +1 −1 Original line number Diff line number Diff line Loading @@ -204,7 +204,7 @@ public class TileServices extends IQSService.Stub { tileServiceManager.clearPendingBind(); tileServiceManager.setLastUpdate(System.currentTimeMillis()); } customTile.updateState(tile); customTile.updateTileState(tile); customTile.refreshState(); } } Loading packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +2 −1 Original line number Diff line number Diff line Loading @@ -160,7 +160,8 @@ public class QSFactoryImpl implements QSFactory { public QSTile createTile(String tileSpec) { QSTileImpl tile = createTileInternal(tileSpec); if (tile != null) { tile.handleStale(); // Tile was just created, must be stale. tile.initialize(); tile.postStale(); // Tile was just created, must be stale. } return tile; } Loading Loading
packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +12 −1 Original line number Diff line number Diff line Loading @@ -45,7 +45,9 @@ import com.android.systemui.plugins.qs.QSFactory; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTileView; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.external.CustomTileStatePersister; import com.android.systemui.qs.external.TileLifecycleManager; import com.android.systemui.qs.external.TileServiceKey; import com.android.systemui.qs.external.TileServices; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.settings.UserTracker; Loading Loading @@ -93,6 +95,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D private final QSLogger mQSLogger; private final UiEventLogger mUiEventLogger; private final InstanceIdSequence mInstanceIdSequence; private final CustomTileStatePersister mCustomTileStatePersister; private final List<Callback> mCallbacks = new ArrayList<>(); private AutoTileManager mAutoTiles; Loading @@ -119,7 +122,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D QSLogger qsLogger, UiEventLogger uiEventLogger, UserTracker userTracker, SecureSettings secureSettings) { SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister) { mIconController = iconController; mContext = context; mUserContext = context; Loading @@ -139,6 +143,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D mDumpManager.registerDumpable(TAG, this); mUserTracker = userTracker; mSecureSettings = secureSettings; mCustomTileStatePersister = customTileStatePersister; mainHandler.post(() -> { // This is technically a hack to avoid circular dependency of Loading Loading @@ -418,6 +423,11 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D changeTiles(mTileSpecs, newSpecs); } /** * Change the tiles triggered by the user editing. * <p> * This is not called on device start, or on user change. */ public void changeTiles(List<String> previousTiles, List<String> newTiles) { final List<String> copy = new ArrayList<>(previousTiles); final int NP = copy.size(); Loading @@ -433,6 +443,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D mBroadcastDispatcher); lifecycleManager.onStopListening(); lifecycleManager.onTileRemoved(); mCustomTileStatePersister.removeState(new TileServiceKey(component, mCurrentUser)); TileLifecycleManager.setTileAdded(mContext, component, false); lifecycleManager.flushMessagesAndUnbind(); } Loading
packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +64 −13 Original line number Diff line number Diff line Loading @@ -46,6 +46,7 @@ import android.widget.Switch; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; Loading Loading @@ -85,6 +86,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener private final IQSTileService mService; private final TileServiceManager mServiceManager; private final int mUser; private final CustomTileStatePersister mCustomTileStatePersister; private android.graphics.drawable.Icon mDefaultIcon; private CharSequence mDefaultLabel; Loading @@ -94,6 +96,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener private boolean mIsTokenGranted; private boolean mIsShowingDialog; private final TileServiceKey mKey; private CustomTile( QSHost host, Looper backgroundLooper, Loading @@ -104,7 +108,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener ActivityStarter activityStarter, QSLogger qsLogger, String action, Context userContext Context userContext, CustomTileStatePersister customTileStatePersister ) { super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); Loading @@ -113,15 +118,29 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mTile = new Tile(); mUserContext = userContext; mUser = mUserContext.getUserId(); updateDefaultTileAndIcon(); mKey = new TileServiceKey(mComponent, mUser); mServiceManager = host.getTileServices().getTileWrapper(this); mService = mServiceManager.getTileService(); mCustomTileStatePersister = customTileStatePersister; } @Override protected void handleInitialize() { updateDefaultTileAndIcon(); if (mServiceManager.isToggleableTile()) { // Replace states with BooleanState resetStates(); } mService = mServiceManager.getTileService(); mServiceManager.setTileChangeListener(this); if (mServiceManager.isActiveTile()) { Tile t = mCustomTileStatePersister.readState(mKey); if (t != null) { applyTileState(t, /* overwriteNulls */ false); mServiceManager.clearPendingBind(); refreshState(); } } } @Override Loading Loading @@ -191,7 +210,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener @Override public void onTileChanged(ComponentName tile) { updateDefaultTileAndIcon(); mHandler.post(this::updateDefaultTileAndIcon); } @Override Loading @@ -213,16 +232,44 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener } public Tile getQsTile() { // TODO(b/191145007) Move to background thread safely updateDefaultTileAndIcon(); return mTile; } public void updateState(Tile tile) { /** * Update state of {@link this#mTile} from a remote {@link TileService}. * @param tile tile populated with state to apply */ public void updateTileState(Tile tile) { // This comes from a binder call IQSService.updateQsTile mHandler.post(() -> handleUpdateTileState(tile)); } private void handleUpdateTileState(Tile tile) { applyTileState(tile, /* overwriteNulls */ true); if (mServiceManager.isActiveTile()) { mCustomTileStatePersister.persistState(mKey, tile); } } @WorkerThread private void applyTileState(Tile tile, boolean overwriteNulls) { if (tile.getIcon() != null || overwriteNulls) { mTile.setIcon(tile.getIcon()); } if (tile.getLabel() != null || overwriteNulls) { mTile.setLabel(tile.getLabel()); } if (tile.getSubtitle() != null || overwriteNulls) { mTile.setSubtitle(tile.getSubtitle()); } if (tile.getContentDescription() != null || overwriteNulls) { mTile.setContentDescription(tile.getContentDescription()); } if (tile.getStateDescription() != null || overwriteNulls) { mTile.setStateDescription(tile.getStateDescription()); } mTile.setState(tile.getState()); } Loading Loading @@ -459,6 +506,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener final StatusBarStateController mStatusBarStateController; final ActivityStarter mActivityStarter; final QSLogger mQSLogger; final CustomTileStatePersister mCustomTileStatePersister; Context mUserContext; String mSpec = ""; Loading @@ -472,7 +520,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger QSLogger qsLogger, CustomTileStatePersister customTileStatePersister ) { mQSHostLazy = hostLazy; mBackgroundLooper = backgroundLooper; Loading @@ -482,6 +531,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mStatusBarStateController = statusBarStateController; mActivityStarter = activityStarter; mQSLogger = qsLogger; mCustomTileStatePersister = customTileStatePersister; } Builder setSpec(@NonNull String spec) { Loading Loading @@ -509,7 +559,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mActivityStarter, mQSLogger, action, mUserContext mUserContext, mCustomTileStatePersister ); } } Loading
packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt 0 → 100644 +122 −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.systemui.qs.external import android.content.ComponentName import android.content.Context import android.service.quicksettings.Tile import android.util.Log import com.android.internal.annotations.VisibleForTesting import org.json.JSONException import org.json.JSONObject import javax.inject.Inject data class TileServiceKey(val componentName: ComponentName, val user: Int) { private val string = "${componentName.flattenToString()}:$user" override fun toString() = string } private const val STATE = "state" private const val LABEL = "label" private const val SUBTITLE = "subtitle" private const val CONTENT_DESCRIPTION = "content_description" private const val STATE_DESCRIPTION = "state_description" /** * Persists and retrieves state for [CustomTile]. * * This class will persists to a fixed [SharedPreference] file a state for a pair of [ComponentName] * and user id ([TileServiceKey]). * * It persists the state from a [Tile] necessary to present the view in the same state when * retrieved, with the exception of the icon. */ class CustomTileStatePersister @Inject constructor(context: Context) { companion object { private const val FILE_NAME = "custom_tiles_state" } private val sharedPreferences = context.getSharedPreferences(FILE_NAME, 0) /** * Read the state from [SharedPreferences]. * * Returns `null` if the tile has no saved state. * * Any fields that have not been saved will be set to `null` */ fun readState(key: TileServiceKey): Tile? { val state = sharedPreferences.getString(key.toString(), null) ?: return null return try { readTileFromString(state) } catch (e: JSONException) { Log.e("TileServicePersistence", "Bad saved state: $state", e) null } } /** * Persists the state into [SharedPreferences]. * * The implementation does not store fields that are `null` or icons. */ fun persistState(key: TileServiceKey, tile: Tile) { val state = writeToString(tile) sharedPreferences.edit().putString(key.toString(), state).apply() } /** * Removes the state for a given tile, user pair. * * Used when the tile is removed by the user. */ fun removeState(key: TileServiceKey) { sharedPreferences.edit().remove(key.toString()).apply() } } @VisibleForTesting internal fun readTileFromString(stateString: String): Tile { val json = JSONObject(stateString) return Tile().apply { state = json.getInt(STATE) label = json.getStringOrNull(LABEL) subtitle = json.getStringOrNull(SUBTITLE) contentDescription = json.getStringOrNull(CONTENT_DESCRIPTION) stateDescription = json.getStringOrNull(STATE_DESCRIPTION) } } // Properties with null values will not be saved to the Json string in any way. This makes sure // to properly retrieve a null in that case. private fun JSONObject.getStringOrNull(name: String): String? { return if (has(name)) getString(name) else null } @VisibleForTesting internal fun writeToString(tile: Tile): String { // Not storing the icon return with(tile) { JSONObject() .put(STATE, state) .put(LABEL, label) .put(SUBTITLE, subtitle) .put(CONTENT_DESCRIPTION, contentDescription) .put(STATE_DESCRIPTION, stateDescription) .toString() } }
packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +1 −1 Original line number Diff line number Diff line Loading @@ -204,7 +204,7 @@ public class TileServices extends IQSService.Stub { tileServiceManager.clearPendingBind(); tileServiceManager.setLastUpdate(System.currentTimeMillis()); } customTile.updateState(tile); customTile.updateTileState(tile); customTile.refreshState(); } } Loading
packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +2 −1 Original line number Diff line number Diff line Loading @@ -160,7 +160,8 @@ public class QSFactoryImpl implements QSFactory { public QSTile createTile(String tileSpec) { QSTileImpl tile = createTileInternal(tileSpec); if (tile != null) { tile.handleStale(); // Tile was just created, must be stale. tile.initialize(); tile.postStale(); // Tile was just created, must be stale. } return tile; } Loading