Add the new InternetTile for QuickSettings

Bug: 174753536
Test: Manual Tests
Change-Id: I29d80eb928493feb3ad72a3c08fe4f342e91f288
This commit is contained in:
yinxu
2020-12-03 08:30:08 -08:00
parent 7adce7f6d9
commit af2971d8c3
5 changed files with 478 additions and 0 deletions

View File

@@ -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] -->

View File

@@ -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);

View File

@@ -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":

View File

@@ -0,0 +1,449 @@
/*
* 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;
}
}
}

View File

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