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

Commit af2971d8 authored by yinxu's avatar yinxu
Browse files

Add the new InternetTile for QuickSettings

Bug: 174753536
Test: Manual Tests
Change-Id: I29d80eb928493feb3ad72a3c08fe4f342e91f288
parent 7adce7f6
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -839,6 +839,8 @@
    <string name="quick_settings_user_new_user">New user</string>
    <!-- QuickSettings: Wifi [CHAR LIMIT=NONE] -->
    <string name="quick_settings_wifi_label">Wi-Fi</string>
    <!-- QuickSettings: Internet [CHAR LIMIT=NONE] -->
    <string name="quick_settings_internet_label">Internet</string>
    <!-- QuickSettings: Wifi (Not connected) [CHAR LIMIT=NONE] -->
    <string name="quick_settings_wifi_not_connected">Not Connected</string>
    <!-- QuickSettings: Wifi (No network) [CHAR LIMIT=NONE] -->
+12 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.provider.Settings.Secure;
import android.service.quicksettings.Tile;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.FeatureFlagUtils;
import android.util.Log;

import com.android.internal.logging.InstanceId;
@@ -444,6 +445,11 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
        final ArrayList<String> tiles = new ArrayList<String>();
        boolean addedDefault = false;
        Set<String> addedSpecs = new ArraySet<>();
        // TODO(b/174753536): Move it into the config file.
        if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
            tiles.add("internet");
            addedSpecs.add("internet");
        }
        for (String tile : tileList.split(",")) {
            tile = tile.trim();
            if (tile.isEmpty()) continue;
@@ -459,6 +465,12 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
                    addedDefault = true;
                }
            } else {
                // TODO(b/174753536): Move it into the config file.
                if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
                    if (tile.equals("wifi") || tile.equals("cell")) {
                        continue;
                    }
                }
                if (!addedSpecs.contains(tile)) {
                    tiles.add(tile);
                    addedSpecs.add(tile);
+6 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import com.android.systemui.qs.tiles.DataSaverTile;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.FlashlightTile;
import com.android.systemui.qs.tiles.HotspotTile;
import com.android.systemui.qs.tiles.InternetTile;
import com.android.systemui.qs.tiles.LocationTile;
import com.android.systemui.qs.tiles.NfcTile;
import com.android.systemui.qs.tiles.NightDisplayTile;
@@ -60,6 +61,7 @@ public class QSFactoryImpl implements QSFactory {
    private static final String TAG = "QSFactory";

    private final Provider<WifiTile> mWifiTileProvider;
    private final Provider<InternetTile> mInternetTileProvider;
    private final Provider<BluetoothTile> mBluetoothTileProvider;
    private final Provider<CellularTile> mCellularTileProvider;
    private final Provider<DndTile> mDndTileProvider;
@@ -89,6 +91,7 @@ public class QSFactoryImpl implements QSFactory {
            Lazy<QSHost> qsHostLazy,
            Provider<CustomTile.Builder> customTileBuilderProvider,
            Provider<WifiTile> wifiTileProvider,
            Provider<InternetTile> internetTileProvider,
            Provider<BluetoothTile> bluetoothTileProvider,
            Provider<CellularTile> cellularTileProvider,
            Provider<DndTile> dndTileProvider,
@@ -113,6 +116,7 @@ public class QSFactoryImpl implements QSFactory {
        mCustomTileBuilderProvider = customTileBuilderProvider;

        mWifiTileProvider = wifiTileProvider;
        mInternetTileProvider = internetTileProvider;
        mBluetoothTileProvider = bluetoothTileProvider;
        mCellularTileProvider = cellularTileProvider;
        mDndTileProvider = dndTileProvider;
@@ -148,6 +152,8 @@ public class QSFactoryImpl implements QSFactory {
        switch (tileSpec) {
            case "wifi":
                return mWifiTileProvider.get();
            case "internet":
                return mInternetTileProvider.get();
            case "bt":
                return mBluetoothTileProvider.get();
            case "cell":
+449 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.tiles;

import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Switch;

import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.graph.SignalDrawable;
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSIconView;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.Icon;
import com.android.systemui.plugins.qs.QSTile.SignalState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.AlphaControlledSignalTileView;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import com.android.systemui.statusbar.policy.WifiIcons;

import javax.inject.Inject;

/** Quick settings tile: Internet **/
public class InternetTile extends QSTileImpl<SignalState> {
    private static final Intent WIFI_SETTINGS = new Intent(Settings.ACTION_WIFI_SETTINGS);

    protected final NetworkController mController;
    private final DataUsageController mDataController;
    private final QSTile.SignalState mStateBeforeClick = newTileState();
    // The last updated tile state, 0: mobile, 1: wifi
    private int mLastTileState = -1;

    protected final InternetSignalCallback mSignalCallback = new InternetSignalCallback();

    @Inject
    public InternetTile(
            QSHost host,
            @Background Looper backgroundLooper,
            @Main Handler mainHandler,
            MetricsLogger metricsLogger,
            StatusBarStateController statusBarStateController,
            ActivityStarter activityStarter,
            QSLogger qsLogger,
            NetworkController networkController
    ) {
        super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
                activityStarter, qsLogger);
        mController = networkController;
        mDataController = mController.getMobileDataController();
        mController.observe(getLifecycle(), mSignalCallback);
    }

    @Override
    public SignalState newTileState() {
        return new SignalState();
    }

    @Override
    public QSIconView createTileView(Context context) {
        return new AlphaControlledSignalTileView(context);
    }

    @Override
    public Intent getLongClickIntent() {
        return WIFI_SETTINGS;
    }

    @Override
    protected void handleClick() {
        mActivityStarter.postStartActivityDismissingKeyguard(WIFI_SETTINGS, 0);
    }

    @Override
    public CharSequence getTileLabel() {
        return mContext.getString(R.string.quick_settings_internet_label);
    }

    @Override
    public int getMetricsCategory() {
        return MetricsEvent.QS_WIFI;
    }

    @Override
    public boolean isAvailable() {
        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)
                || (mController.hasMobileDataFeature()
                        && mHost.getUserContext().getUserId() == UserHandle.USER_SYSTEM);
    }

    private CharSequence getSecondaryLabel(boolean isTransient, String statusLabel) {
        return isTransient
                ? mContext.getString(R.string.quick_settings_wifi_secondary_label_transient)
                : statusLabel;
    }

    private static String removeDoubleQuotes(String string) {
        if (string == null) return null;
        final int length = string.length();
        if ((length > 1) && (string.charAt(0) == '"') && (string.charAt(length - 1) == '"')) {
            return string.substring(1, length - 1);
        }
        return string;
    }

    private static final class WifiCallbackInfo {
        boolean mEnabled;
        boolean mConnected;
        int mWifiSignalIconId;
        String mSsid;
        boolean mActivityIn;
        boolean mActivityOut;
        String mWifiSignalContentDescription;
        boolean mIsTransient;
        public String mStatusLabel;

        @Override
        public String toString() {
            return new StringBuilder("WifiCallbackInfo[")
                    .append("mEnabled=").append(mEnabled)
                    .append(",mConnected=").append(mConnected)
                    .append(",mWifiSignalIconId=").append(mWifiSignalIconId)
                    .append(",mSsid=").append(mSsid)
                    .append(",mActivityIn=").append(mActivityIn)
                    .append(",mActivityOut=").append(mActivityOut)
                    .append(",mWifiSignalContentDescription=").append(mWifiSignalContentDescription)
                    .append(",mIsTransient=").append(mIsTransient)
                    .append(']').toString();
        }
    }

    private static final class CellularCallbackInfo {
        boolean mAirplaneModeEnabled;
        CharSequence mDataSubscriptionName;
        CharSequence mDataContentDescription;
        int mMobileSignalIconId;
        boolean mActivityIn;
        boolean mActivityOut;
        boolean mNoSim;
        boolean mRoaming;
        boolean mMultipleSubs;

        @Override
        public String toString() {
            return new StringBuilder("CellularCallbackInfo[")
                .append("mAirplaneModeEnabled=").append(mAirplaneModeEnabled)
                .append(",mDataSubscriptionName=").append(mDataSubscriptionName)
                .append(",mDataContentDescription=").append(mDataContentDescription)
                .append(",mMobileSignalIconId=").append(mMobileSignalIconId)
                .append(",mActivityIn=").append(mActivityIn)
                .append(",mActivityOut=").append(mActivityOut)
                .append(",mNoSim=").append(mNoSim)
                .append(",mRoaming=").append(mRoaming)
                .append(",mMultipleSubs=").append(mMultipleSubs)
                .append(']').toString();
        }
    }

    protected final class InternetSignalCallback implements SignalCallback {
        final WifiCallbackInfo mWifiInfo = new WifiCallbackInfo();
        final CellularCallbackInfo mCellularInfo = new CellularCallbackInfo();

        @Override
        public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
                boolean activityIn, boolean activityOut, String description, boolean isTransient,
                String statusLabel) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "setWifiIndicators: "
                        + "enabled = " + enabled + ","
                        + "statusIcon = " + (statusIcon == null ? "" : statusIcon.toString()) + ","
                        + "qsIcon = " + (qsIcon == null ? "" : qsIcon.toString()) + ","
                        + "activityIn = " + activityIn + ","
                        + "activityOut = " + activityOut + ","
                        + "description = " + description + ","
                        + "isTransient = " + isTransient + ","
                        + "statusLabel = " + statusLabel);
            }
            mWifiInfo.mEnabled = enabled;
            mWifiInfo.mConnected = qsIcon.visible;
            mWifiInfo.mWifiSignalIconId = qsIcon.icon;
            mWifiInfo.mSsid = description;
            mWifiInfo.mActivityIn = activityIn;
            mWifiInfo.mActivityOut = activityOut;
            mWifiInfo.mWifiSignalContentDescription = qsIcon.contentDescription;
            mWifiInfo.mIsTransient = isTransient;
            mWifiInfo.mStatusLabel = statusLabel;
            refreshState(mWifiInfo);
        }

        @Override
        public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
                int qsType, boolean activityIn, boolean activityOut,
                CharSequence typeContentDescription,
                CharSequence typeContentDescriptionHtml, CharSequence description,
                boolean isWide, int subId, boolean roaming) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "setMobileDataIndicators: "
                        + "statusIcon = " + (statusIcon == null ? "" :  statusIcon.toString()) + ","
                        + "qsIcon = " + (qsIcon == null ? "" : qsIcon.toString()) + ","
                        + "statusType = " + statusType + ","
                        + "qsType = " + qsType + ","
                        + "activityIn = " + activityIn + ","
                        + "activityOut = " + activityOut + ","
                        + "typeContentDescription = " + typeContentDescription + ","
                        + "typeContentDescriptionHtml = " + typeContentDescriptionHtml + ","
                        + "description = " + description + ","
                        + "isWide = " + isWide + ","
                        + "subId = " + subId + ","
                        + "roaming = " + roaming);
            }
            if (qsIcon == null) {
                // Not data sim, don't display.
                return;
            }
            mCellularInfo.mDataSubscriptionName = mController.getMobileDataNetworkName();
            mCellularInfo.mDataContentDescription =
                    (description != null) ? typeContentDescriptionHtml : null;
            mCellularInfo.mMobileSignalIconId = qsIcon.icon;
            mCellularInfo.mActivityIn = activityIn;
            mCellularInfo.mActivityOut = activityOut;
            mCellularInfo.mRoaming = roaming;
            mCellularInfo.mMultipleSubs = mController.getNumberSubscriptions() > 1;
            refreshState(mCellularInfo);
        }

        @Override
        public void setNoSims(boolean show, boolean simDetected) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "setNoSims: "
                        + "show = " + show + ","
                        + "simDetected = " + simDetected);
            }
            mCellularInfo.mNoSim = show;
            if (mCellularInfo.mNoSim) {
                // Make sure signal gets cleared out when no sims.
                mCellularInfo.mMobileSignalIconId = 0;
            }
            refreshState(mCellularInfo);
        }

        @Override
        public void setIsAirplaneMode(IconState icon) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "InternetTile-setIsAirplaneMode: "
                        + "icon = " + (icon == null ? "" : icon.toString()));
            }
            mCellularInfo.mAirplaneModeEnabled = icon.visible;
            refreshState(mCellularInfo);
        }
    }

    @Override
    protected void handleUpdateState(SignalState state, Object arg) {
        Log.d(TAG, "handleUpdateState: " + "arg = " + arg);
        if (arg instanceof CellularCallbackInfo) {
            mLastTileState = 0;
            handleUpdateCellularState(state, arg);
        } else if (arg instanceof WifiCallbackInfo) {
            mLastTileState = 1;
            handleUpdateWifiState(state, arg);
        } else {
            // handleUpdateState will be triggered when user expands the QuickSetting panel with
            // arg = null, in this case the last updated CellularCallbackInfo or WifiCallbackInfo
            // should be used to refresh the tile.
            if (mLastTileState == 0) {
                handleUpdateCellularState(state, mSignalCallback.mCellularInfo);
            } else if (mLastTileState == 1) {
                handleUpdateWifiState(state, mSignalCallback.mWifiInfo);
            }
        }
    }

    private void handleUpdateWifiState(SignalState state, Object arg) {
        WifiCallbackInfo cb = (WifiCallbackInfo) arg;
        boolean wifiConnected = cb.mEnabled && (cb.mWifiSignalIconId > 0) && (cb.mSsid != null);
        boolean wifiNotConnected = (cb.mWifiSignalIconId > 0) && (cb.mSsid == null);
        boolean enabledChanging = state.value != cb.mEnabled;
        if (enabledChanging) {
            fireToggleStateChanged(cb.mEnabled);
        }
        if (state.slash == null) {
            state.slash = new SlashState();
            state.slash.rotation = 6;
        }
        state.slash.isSlashed = false;
        state.secondaryLabel = getSecondaryLabel(cb.mIsTransient, removeDoubleQuotes(cb.mSsid));
        state.state = Tile.STATE_ACTIVE;
        state.dualTarget = true;
        state.value = cb.mEnabled;
        state.activityIn = cb.mEnabled && cb.mActivityIn;
        state.activityOut = cb.mEnabled && cb.mActivityOut;
        final StringBuffer minimalContentDescription = new StringBuffer();
        final StringBuffer minimalStateDescription = new StringBuffer();
        final Resources r = mContext.getResources();
        // TODO(b/174753536): Use the new "Internet" string as state.label once available.
        if (cb.mIsTransient) {
            state.icon = ResourceIcon.get(
                com.android.internal.R.drawable.ic_signal_wifi_transient_animation);
            state.label = r.getString(R.string.quick_settings_internet_label);
        } else if (!state.value) {
            state.slash.isSlashed = true;
            state.state = Tile.STATE_INACTIVE;
            state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_DISABLED);
            state.label = r.getString(R.string.quick_settings_internet_label);
        } else if (wifiConnected) {
            state.icon = ResourceIcon.get(cb.mWifiSignalIconId);
            state.label = r.getString(R.string.quick_settings_internet_label);
        } else if (wifiNotConnected) {
            state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_NO_NETWORK);
            state.label = r.getString(R.string.quick_settings_internet_label);
        } else {
            state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_NO_NETWORK);
            state.label = r.getString(R.string.quick_settings_internet_label);
        }
        minimalContentDescription.append(
            mContext.getString(R.string.quick_settings_internet_label)).append(",");
        if (state.value) {
            if (wifiConnected) {
                minimalStateDescription.append(cb.mWifiSignalContentDescription);
                minimalContentDescription.append(removeDoubleQuotes(cb.mSsid));
                if (!TextUtils.isEmpty(state.secondaryLabel)) {
                    minimalContentDescription.append(",").append(state.secondaryLabel);
                }
            }
        }
        state.stateDescription = minimalStateDescription.toString();
        state.contentDescription = minimalContentDescription.toString();
        state.dualLabelContentDescription = r.getString(
                R.string.accessibility_quick_settings_open_settings, getTileLabel());
        state.expandedAccessibilityClassName = Switch.class.getName();
    }

    private void handleUpdateCellularState(SignalState state, Object arg) {
        CellularCallbackInfo cb = (CellularCallbackInfo) arg;
        final Resources r = mContext.getResources();
        // TODO(b/174753536): Use the new "Internet" string as state.label once available.
        state.label = r.getString(R.string.quick_settings_internet_label);
        boolean mobileDataEnabled = mDataController.isMobileDataSupported()
                && mDataController.isMobileDataEnabled();
        state.value = mobileDataEnabled;
        state.activityIn = mobileDataEnabled && cb.mActivityIn;
        state.activityOut = mobileDataEnabled && cb.mActivityOut;
        state.expandedAccessibilityClassName = Switch.class.getName();
        if (cb.mNoSim) {
            state.icon = ResourceIcon.get(R.drawable.ic_qs_no_sim);
        } else {
            state.icon = new SignalIcon(cb.mMobileSignalIconId);
        }

        if (cb.mNoSim) {
            state.state = Tile.STATE_UNAVAILABLE;
            state.secondaryLabel = r.getString(R.string.keyguard_missing_sim_message_short);
        } else if (cb.mAirplaneModeEnabled) {
            state.state = Tile.STATE_UNAVAILABLE;
            state.secondaryLabel = r.getString(R.string.status_bar_airplane);
        } else if (mobileDataEnabled) {
            state.state = Tile.STATE_ACTIVE;
            state.secondaryLabel = appendMobileDataType(cb.mDataSubscriptionName,
                    getMobileDataContentName(cb));
        } else {
            state.state = Tile.STATE_INACTIVE;
            state.secondaryLabel = r.getString(R.string.cell_data_off);
        }

        state.contentDescription = state.label;
        if (state.state == Tile.STATE_INACTIVE) {
            // This information is appended later by converting the Tile.STATE_INACTIVE state.
            state.stateDescription = "";
        } else {
            state.stateDescription = state.secondaryLabel;
        }
    }

    private CharSequence appendMobileDataType(CharSequence current, CharSequence dataType) {
        if (TextUtils.isEmpty(dataType)) {
            return Html.fromHtml((current == null ? "" : current.toString()), 0);
        }
        if (TextUtils.isEmpty(current)) {
            return Html.fromHtml((dataType == null ? "" : dataType.toString()), 0);
        }
        String concat = mContext.getString(R.string.mobile_carrier_text_format, current, dataType);
        return Html.fromHtml(concat, 0);
    }

    private CharSequence getMobileDataContentName(CellularCallbackInfo cb) {
        if (cb.mRoaming && !TextUtils.isEmpty(cb.mDataContentDescription)) {
            String roaming = mContext.getString(R.string.data_connection_roaming);
            String dataDescription =
                    cb.mDataContentDescription == null ? ""
                            : cb.mDataContentDescription.toString();
            return mContext.getString(R.string.mobile_data_text_format, roaming, dataDescription);
        }
        if (cb.mRoaming) {
            return mContext.getString(R.string.data_connection_roaming);
        }
        return cb.mDataContentDescription;
    }

    private static class SignalIcon extends Icon {
        private final int mState;
        SignalIcon(int state) {
            mState = state;
        }
        public int getState() {
            return mState;
        }

        @Override
        public Drawable getDrawable(Context context) {
            SignalDrawable d = new SignalDrawable(context);
            d.setLevel(getState());
            return d;
        }
    }
}
+9 −0
Original line number Diff line number Diff line
@@ -104,6 +104,15 @@ public interface NetworkController extends CallbackController<SignalCallback>, D
                Context context) {
            this(visible, icon, context.getString(contentDescription));
        }

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            return builder.append("[visible=").append(visible).append(',')
                .append("icon=").append(icon).append(',')
                .append("contentDescription=").append(contentDescription).append(']')
                .toString();
        }
    }

    /**