blob: 428b5038f1500724ce0c37dc6faffc97a4eb70ff [file] [log] [blame]
/*
* 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.view.View;
import android.widget.Switch;
import androidx.annotation.Nullable;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.graph.SignalDrawable;
import com.android.settingslib.mobile.TelephonyIcons;
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.FalsingManager;
import com.android.systemui.plugins.qs.QSIconView;
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.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators;
import com.android.systemui.statusbar.policy.WifiIcons;
import java.io.FileDescriptor;
import java.io.PrintWriter;
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;
// The last updated tile state, 0: mobile, 1: wifi, 2: ethernet.
private int mLastTileState = -1;
protected final InternetSignalCallback mSignalCallback = new InternetSignalCallback();
private final InternetDialogFactory mInternetDialogFactory;
final Handler mHandler;
@Inject
public InternetTile(
QSHost host,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
MetricsLogger metricsLogger,
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger,
NetworkController networkController,
InternetDialogFactory internetDialogFactory
) {
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mInternetDialogFactory = internetDialogFactory;
mHandler = mainHandler;
mController = networkController;
mDataController = mController.getMobileDataController();
mController.observe(getLifecycle(), mSignalCallback);
}
@Override
public SignalState newTileState() {
SignalState s = new SignalState();
s.forceExpandIcon = true;
return s;
}
@Override
public QSIconView createTileView(Context context) {
return new AlphaControlledSignalTileView(context);
}
@Override
public Intent getLongClickIntent() {
return WIFI_SETTINGS;
}
@Override
protected void handleClick(@Nullable View view) {
mHandler.post(() -> {
mInternetDialogFactory.create(true);
});
}
@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 EthernetCallbackInfo {
boolean mConnected;
int mEthernetSignalIconId;
String mEthernetContentDescription;
@Override
public String toString() {
return new StringBuilder("EthernetCallbackInfo[")
.append("mConnected=").append(mConnected)
.append(",mEthernetSignalIconId=").append(mEthernetSignalIconId)
.append(",mEthernetContentDescription=").append(mEthernetContentDescription)
.append(']').toString();
}
}
private static final class WifiCallbackInfo {
boolean mAirplaneModeEnabled;
boolean mEnabled;
boolean mConnected;
int mWifiSignalIconId;
String mSsid;
boolean mActivityIn;
boolean mActivityOut;
String mWifiSignalContentDescription;
boolean mIsTransient;
public String mStatusLabel;
boolean mNoDefaultNetwork;
boolean mNoValidatedNetwork;
boolean mNoNetworksAvailable;
@Override
public String toString() {
return new StringBuilder("WifiCallbackInfo[")
.append("mAirplaneModeEnabled=").append(mAirplaneModeEnabled)
.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(",mNoDefaultNetwork=").append(mNoDefaultNetwork)
.append(",mNoValidatedNetwork=").append(mNoValidatedNetwork)
.append(",mNoNetworksAvailable=").append(mNoNetworksAvailable)
.append(']').toString();
}
}
private static final class CellularCallbackInfo {
boolean mAirplaneModeEnabled;
CharSequence mDataSubscriptionName;
CharSequence mDataContentDescription;
int mMobileSignalIconId;
int mQsTypeIcon;
boolean mActivityIn;
boolean mActivityOut;
boolean mNoSim;
boolean mRoaming;
boolean mMultipleSubs;
boolean mNoDefaultNetwork;
boolean mNoValidatedNetwork;
boolean mNoNetworksAvailable;
@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(",mQsTypeIcon=").append(mQsTypeIcon)
.append(",mActivityIn=").append(mActivityIn)
.append(",mActivityOut=").append(mActivityOut)
.append(",mNoSim=").append(mNoSim)
.append(",mRoaming=").append(mRoaming)
.append(",mMultipleSubs=").append(mMultipleSubs)
.append(",mNoDefaultNetwork=").append(mNoDefaultNetwork)
.append(",mNoValidatedNetwork=").append(mNoValidatedNetwork)
.append(",mNoNetworksAvailable=").append(mNoNetworksAvailable)
.append(']').toString();
}
}
protected final class InternetSignalCallback implements SignalCallback {
final WifiCallbackInfo mWifiInfo = new WifiCallbackInfo();
final CellularCallbackInfo mCellularInfo = new CellularCallbackInfo();
final EthernetCallbackInfo mEthernetInfo = new EthernetCallbackInfo();
@Override
public void setWifiIndicators(WifiIndicators indicators) {
if (DEBUG) {
Log.d(TAG, "setWifiIndicators: " + indicators);
}
mWifiInfo.mEnabled = indicators.enabled;
if (indicators.qsIcon == null) {
return;
}
mWifiInfo.mConnected = indicators.qsIcon.visible;
mWifiInfo.mWifiSignalIconId = indicators.qsIcon.icon;
mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription;
mWifiInfo.mEnabled = indicators.enabled;
mWifiInfo.mSsid = indicators.description;
mWifiInfo.mActivityIn = indicators.activityIn;
mWifiInfo.mActivityOut = indicators.activityOut;
mWifiInfo.mIsTransient = indicators.isTransient;
mWifiInfo.mStatusLabel = indicators.statusLabel;
refreshState(mWifiInfo);
}
@Override
public void setMobileDataIndicators(MobileDataIndicators indicators) {
if (DEBUG) {
Log.d(TAG, "setMobileDataIndicators: " + indicators);
}
if (indicators.qsIcon == null) {
// Not data sim, don't display.
return;
}
mCellularInfo.mDataSubscriptionName = indicators.description == null
? mController.getMobileDataNetworkName() : indicators.description;
mCellularInfo.mDataContentDescription = indicators.description != null
? indicators.typeContentDescriptionHtml : null;
mCellularInfo.mMobileSignalIconId = indicators.qsIcon.icon;
mCellularInfo.mQsTypeIcon = indicators.qsType;
mCellularInfo.mActivityIn = indicators.activityIn;
mCellularInfo.mActivityOut = indicators.activityOut;
mCellularInfo.mRoaming = indicators.roaming;
mCellularInfo.mMultipleSubs = mController.getNumberSubscriptions() > 1;
refreshState(mCellularInfo);
}
@Override
public void setEthernetIndicators(IconState icon) {
if (DEBUG) {
Log.d(TAG, "setEthernetIndicators: "
+ "icon = " + (icon == null ? "" : icon.toString()));
}
mEthernetInfo.mConnected = icon.visible;
mEthernetInfo.mEthernetSignalIconId = icon.icon;
mEthernetInfo.mEthernetContentDescription = icon.contentDescription;
if (icon.visible) {
refreshState(mEthernetInfo);
}
}
@Override
public void setNoSims(boolean show, boolean simDetected) {
if (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;
mCellularInfo.mQsTypeIcon = 0;
}
}
@Override
public void setIsAirplaneMode(IconState icon) {
if (DEBUG) {
Log.d(TAG, "setIsAirplaneMode: "
+ "icon = " + (icon == null ? "" : icon.toString()));
}
if (mCellularInfo.mAirplaneModeEnabled == icon.visible) {
return;
}
mCellularInfo.mAirplaneModeEnabled = icon.visible;
mWifiInfo.mAirplaneModeEnabled = icon.visible;
if (!mSignalCallback.mEthernetInfo.mConnected) {
if (mWifiInfo.mEnabled && (mWifiInfo.mWifiSignalIconId > 0)
&& (mWifiInfo.mSsid != null)) {
refreshState(mWifiInfo);
} else {
refreshState(mCellularInfo);
}
}
}
@Override
public void setConnectivityStatus(boolean noDefaultNetwork, boolean noValidatedNetwork,
boolean noNetworksAvailable) {
if (DEBUG) {
Log.d(TAG, "setConnectivityStatus: "
+ "noDefaultNetwork = " + noDefaultNetwork + ","
+ "noValidatedNetwork = " + noValidatedNetwork + ","
+ "noNetworksAvailable = " + noNetworksAvailable);
}
mCellularInfo.mNoDefaultNetwork = noDefaultNetwork;
mCellularInfo.mNoValidatedNetwork = noValidatedNetwork;
mCellularInfo.mNoNetworksAvailable = noNetworksAvailable;
mWifiInfo.mNoDefaultNetwork = noDefaultNetwork;
mWifiInfo.mNoValidatedNetwork = noValidatedNetwork;
mWifiInfo.mNoNetworksAvailable = noNetworksAvailable;
refreshState(mWifiInfo);
}
@Override
public String toString() {
return new StringBuilder("InternetSignalCallback[")
.append("mWifiInfo=").append(mWifiInfo)
.append(",mCellularInfo=").append(mCellularInfo)
.append(",mEthernetInfo=").append(mEthernetInfo)
.append(']').toString();
}
}
@Override
protected void handleUpdateState(SignalState state, Object arg) {
if (arg instanceof CellularCallbackInfo) {
mLastTileState = 0;
handleUpdateCellularState(state, arg);
} else if (arg instanceof WifiCallbackInfo) {
mLastTileState = 1;
handleUpdateWifiState(state, arg);
} else if (arg instanceof EthernetCallbackInfo) {
mLastTileState = 2;
handleUpdateEthernetState(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);
} else if (mLastTileState == 2) {
handleUpdateEthernetState(state, mSignalCallback.mEthernetInfo);
}
}
}
private void handleUpdateWifiState(SignalState state, Object arg) {
WifiCallbackInfo cb = (WifiCallbackInfo) arg;
if (DEBUG) {
Log.d(TAG, "handleUpdateWifiState: " + "WifiCallbackInfo = " + cb.toString());
}
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();
state.label = r.getString(R.string.quick_settings_internet_label);
if (cb.mAirplaneModeEnabled) {
if (!state.value) {
state.state = Tile.STATE_INACTIVE;
state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
state.secondaryLabel = r.getString(R.string.status_bar_airplane);
} else if (!wifiConnected) {
state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
if (cb.mNoNetworksAvailable) {
state.secondaryLabel =
r.getString(R.string.quick_settings_networks_unavailable);
} else {
state.secondaryLabel =
r.getString(R.string.quick_settings_networks_available);
}
} else {
state.icon = ResourceIcon.get(cb.mWifiSignalIconId);
}
} else if (cb.mNoDefaultNetwork) {
if (cb.mNoNetworksAvailable || !cb.mEnabled) {
state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
state.secondaryLabel = r.getString(R.string.quick_settings_networks_unavailable);
} else {
state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_available);
state.secondaryLabel = r.getString(R.string.quick_settings_networks_available);
}
} else if (cb.mIsTransient) {
state.icon = ResourceIcon.get(
com.android.internal.R.drawable.ic_signal_wifi_transient_animation);
} else if (!state.value) {
state.slash.isSlashed = true;
state.state = Tile.STATE_INACTIVE;
state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_DISABLED);
} else if (wifiConnected) {
state.icon = ResourceIcon.get(cb.mWifiSignalIconId);
} else if (wifiNotConnected) {
state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_NO_NETWORK);
} else {
state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_NO_NETWORK);
}
minimalContentDescription.append(
mContext.getString(R.string.quick_settings_internet_label)).append(",");
if (state.value && wifiConnected) {
minimalStateDescription.append(cb.mWifiSignalContentDescription);
minimalContentDescription.append(removeDoubleQuotes(cb.mSsid));
} else 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();
if (DEBUG) {
Log.d(TAG, "handleUpdateWifiState: " + "SignalState = " + state.toString());
}
}
private void handleUpdateCellularState(SignalState state, Object arg) {
CellularCallbackInfo cb = (CellularCallbackInfo) arg;
if (DEBUG) {
Log.d(TAG, "handleUpdateCellularState: " + "CellularCallbackInfo = " + cb.toString());
}
final Resources r = mContext.getResources();
state.label = r.getString(R.string.quick_settings_internet_label);
state.state = Tile.STATE_ACTIVE;
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.mAirplaneModeEnabled && cb.mQsTypeIcon != TelephonyIcons.ICON_CWF) {
state.state = Tile.STATE_INACTIVE;
state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
state.secondaryLabel = r.getString(R.string.status_bar_airplane);
} else if (cb.mNoDefaultNetwork) {
if (cb.mNoNetworksAvailable || !mSignalCallback.mWifiInfo.mEnabled) {
state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
state.secondaryLabel = r.getString(R.string.quick_settings_networks_unavailable);
} else {
state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_available);
state.secondaryLabel = r.getString(R.string.quick_settings_networks_available);
}
} else {
state.icon = new SignalIcon(cb.mMobileSignalIconId);
state.secondaryLabel = appendMobileDataType(cb.mDataSubscriptionName,
getMobileDataContentName(cb));
}
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;
}
if (DEBUG) {
Log.d(TAG, "handleUpdateCellularState: " + "SignalState = " + state.toString());
}
}
private void handleUpdateEthernetState(SignalState state, Object arg) {
EthernetCallbackInfo cb = (EthernetCallbackInfo) arg;
if (DEBUG) {
Log.d(TAG, "handleUpdateEthernetState: " + "EthernetCallbackInfo = " + cb.toString());
}
final Resources r = mContext.getResources();
state.label = r.getString(R.string.quick_settings_internet_label);
state.state = Tile.STATE_ACTIVE;
state.icon = ResourceIcon.get(cb.mEthernetSignalIconId);
state.secondaryLabel = cb.mEthernetContentDescription;
if (DEBUG) {
Log.d(TAG, "handleUpdateEthernetState: " + "SignalState = " + state.toString());
}
}
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;
}
}
/**
* Dumps the state of this tile along with its name.
*/
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(this.getClass().getSimpleName() + ":");
pw.print(" "); pw.println(getState().toString());
pw.print(" "); pw.println("mLastTileState=" + mLastTileState);
pw.print(" "); pw.println("mSignalCallback=" + mSignalCallback.toString());
}
}