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

Commit b8be3698 authored by Fabian Kozynski's avatar Fabian Kozynski Committed by Automerger Merge Worker
Browse files

Merge "Store state of active TileService" into sc-dev am: 22a01eca

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/14937370

Change-Id: Id8897654ed8092472bc5ff0ea1317beb69cc57e0
parents 4129bc97 22a01eca
Loading
Loading
Loading
Loading
+12 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
@@ -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
@@ -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();
@@ -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();
            }
+64 −13
Original line number Diff line number Diff line
@@ -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;
@@ -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;

@@ -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,
@@ -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);
@@ -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
@@ -191,7 +210,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener

    @Override
    public void onTileChanged(ComponentName tile) {
        updateDefaultTileAndIcon();
        mHandler.post(this::updateDefaultTileAndIcon);
    }

    @Override
@@ -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());
    }

@@ -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 = "";
@@ -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;
@@ -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) {
@@ -509,7 +559,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
                    mActivityStarter,
                    mQSLogger,
                    action,
                    mUserContext
                    mUserContext,
                    mCustomTileStatePersister
            );
        }
    }
+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()
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -204,7 +204,7 @@ public class TileServices extends IQSService.Stub {
                tileServiceManager.clearPendingBind();
                tileServiceManager.setLastUpdate(System.currentTimeMillis());
            }
            customTile.updateState(tile);
            customTile.updateTileState(tile);
            customTile.refreshState();
        }
    }
+2 −1
Original line number Diff line number Diff line
@@ -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