Extracts carrier information from QSFooterImpl

QSCarrierGroup performs the same logic and visuals that were inside
QSFooterImpl

Extracted to QSCarrier

Supports up to 3 sims

Test: atest QSFooterImpl
Test: atest QSCarrierGroup
Test: manual
Change-Id: I44e6f917e176af7f305a1de235e1a5be52da579c
This commit is contained in:
Fabian Kozynski
2019-02-27 11:36:02 -05:00
parent 0506e3c7b5
commit a48d2d09d5
9 changed files with 617 additions and 378 deletions

View File

@@ -14,9 +14,9 @@
~ limitations under the License
-->
<LinearLayout
<com.android.systemui.qs.QSCarrier
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/linear_footer_carrier"
android:id="@+id/linear_carrier"
android:layout_width="0dp"
android:layout_height="match_parent"
android:orientation="horizontal"
@@ -35,7 +35,7 @@
android:layout_marginEnd="8dp"
android:visibility="gone" />
<view class="com.android.systemui.qs.QSFooterImpl$QSCarrierText"
<view class="com.android.systemui.qs.QSCarrier$QSCarrierText"
android:id="@+id/qs_carrier_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
@@ -46,4 +46,4 @@
android:textDirection="locale"
android:singleLine="true" />
</LinearLayout>
</com.android.systemui.qs.QSCarrier>

View File

@@ -0,0 +1,60 @@
<!--
Copyright (C) 2019 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
-->
<!-- Extends LinearLayout -->
<com.android.systemui.qs.QSCarrierGroup
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/qs_mobile"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical|start"
android:orientation="horizontal"
android:layout_marginEnd="32dp">
<include
layout="@layout/qs_carrier"
android:id="@+id/carrier1" />
<View
android:id="@+id/qs_carrier_divider1"
android:layout_width="2dp"
android:layout_height="match_parent"
android:layout_marginTop="15dp"
android:layout_marginBottom="15dp"
android:background="?android:attr/dividerVertical"
android:visibility="gone" />
<include
layout="@layout/qs_carrier"
android:id="@+id/carrier2"
android:visibility="gone"/>
<View
android:id="@+id/qs_carrier_divider2"
android:layout_width="2dp"
android:layout_height="match_parent"
android:layout_marginTop="15dp"
android:layout_marginBottom="15dp"
android:background="?android:attr/dividerVertical"
android:visibility="gone" />
<include
layout="@layout/qs_carrier"
android:id="@+id/carrier3"
android:visibility="gone"/>
</com.android.systemui.qs.QSCarrierGroup>

View File

@@ -41,34 +41,8 @@
android:layout_gravity="center_vertical"
android:gravity="end" >
<LinearLayout
android:id="@+id/qs_mobile"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical|start"
android:orientation="horizontal"
android:layout_marginEnd="32dp">
<include
layout="@layout/qs_footer_carrier"
android:id="@+id/carrier1" />
<View
android:id="@+id/qs_carrier_divider"
android:layout_width="2dp"
android:layout_height="match_parent"
android:layout_marginTop="15dp"
android:layout_marginBottom="15dp"
android:background="?android:attr/dividerVertical"
android:visibility="gone" />
<include
layout="@layout/qs_footer_carrier"
android:id="@+id/carrier2"
android:visibility="gone"/>
</LinearLayout>
<include layout="@layout/qs_carrier_group"
android:id="@+id/carrier_group"/>
<com.android.systemui.qs.PageIndicator
android:id="@+id/footer_page_indicator"

View File

@@ -0,0 +1,140 @@
/*
* Copyright (C) 2019 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;
import android.content.Context;
import android.content.res.ColorStateList;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.settingslib.Utils;
import com.android.settingslib.graph.SignalDrawable;
import com.android.systemui.R;
public class QSCarrier extends LinearLayout {
private View mMobileGroup;
private TextView mCarrierText;
private ImageView mMobileSignal;
private ImageView mMobileRoaming;
private ColorStateList mColorForegroundStateList;
private float mColorForegroundIntensity;
public QSCarrier(Context context) {
super(context);
}
public QSCarrier(Context context, AttributeSet attrs) {
super(context, attrs);
}
public QSCarrier(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public QSCarrier(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mMobileGroup = findViewById(R.id.mobile_combo);
mMobileSignal = findViewById(R.id.mobile_signal);
mMobileRoaming = findViewById(R.id.mobile_roaming);
mCarrierText = findViewById(R.id.qs_carrier_text);
int colorForeground = Utils.getColorAttrDefaultColor(mContext,
android.R.attr.colorForeground);
mColorForegroundStateList = ColorStateList.valueOf(colorForeground);
mColorForegroundIntensity = QuickStatusBarHeader.getColorIntensity(colorForeground);
}
public void updateState(QSCarrierGroup.CellSignalState state) {
mMobileGroup.setVisibility(state.visible ? View.VISIBLE : View.GONE);
if (state.visible) {
mMobileRoaming.setVisibility(state.roaming ? View.VISIBLE : View.GONE);
mMobileRoaming.setImageTintList(mColorForegroundStateList);
SignalDrawable d = new SignalDrawable(mContext);
d.setDarkIntensity(mColorForegroundIntensity);
mMobileSignal.setImageDrawable(d);
mMobileSignal.setImageLevel(state.mobileSignalIconId);
StringBuilder contentDescription = new StringBuilder();
if (state.contentDescription != null) {
contentDescription.append(state.contentDescription).append(", ");
}
if (state.roaming) {
contentDescription
.append(mContext.getString(R.string.data_connection_roaming))
.append(", ");
}
// TODO: show mobile data off/no internet text for 5 seconds before carrier text
if (TextUtils.equals(state.typeContentDescription,
mContext.getString(R.string.data_connection_no_internet))
|| TextUtils.equals(state.typeContentDescription,
mContext.getString(R.string.cell_data_off_content_description))) {
contentDescription.append(state.typeContentDescription);
}
mMobileSignal.setContentDescription(contentDescription);
}
}
public void setCarrierText(CharSequence text) {
mCarrierText.setText(text);
}
/**
* TextView that changes its ellipsize value with its visibility.
*/
public static class QSCarrierText extends TextView {
public QSCarrierText(Context context) {
super(context);
}
public QSCarrierText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public QSCarrierText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public QSCarrierText(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
// Only show marquee when visible
if (visibility == VISIBLE) {
setEllipsize(TextUtils.TruncateAt.MARQUEE);
setSelected(true);
} else {
setEllipsize(TextUtils.TruncateAt.END);
setSelected(false);
}
}
}
}

View File

@@ -0,0 +1,223 @@
/*
* Copyright (C) 2019 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;
import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
import android.content.Context;
import android.telephony.SubscriptionManager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import androidx.annotation.VisibleForTesting;
import com.android.keyguard.CarrierTextController;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.NetworkController;
import javax.inject.Inject;
import javax.inject.Named;
/**
* Displays Carrier name and network status in QS
*/
public class QSCarrierGroup extends LinearLayout implements
CarrierTextController.CarrierTextCallback,
NetworkController.SignalCallback {
private static final String TAG = "QSCarrierGroup";
/**
* Support up to 3 slots which is what's supported by {@link TelephonyManager#getPhoneCount}
*/
private static final int SIM_SLOTS = 3;
private final NetworkController mNetworkController;
private View[] mCarrierDividers = new View[SIM_SLOTS - 1];
private QSCarrier[] mCarrierGroups = new QSCarrier[SIM_SLOTS];
private final CellSignalState[] mInfos = new CellSignalState[SIM_SLOTS];
private CarrierTextController mCarrierTextController;
private boolean mListening;
@Inject
public QSCarrierGroup(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
NetworkController networkController) {
super(context, attrs);
mNetworkController = networkController;
}
@VisibleForTesting
public QSCarrierGroup(Context context, AttributeSet attrs) {
this(context, attrs,
Dependency.get(NetworkController.class));
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mCarrierGroups[0] = findViewById(R.id.carrier1);
mCarrierGroups[1] = findViewById(R.id.carrier2);
mCarrierGroups[2] = findViewById(R.id.carrier3);
mCarrierDividers[0] = findViewById(R.id.qs_carrier_divider1);
mCarrierDividers[1] = findViewById(R.id.qs_carrier_divider2);
for (int i = 0; i < SIM_SLOTS; i++) {
mInfos[i] = new CellSignalState();
}
CharSequence separator = mContext.getString(
com.android.internal.R.string.kg_text_message_separator);
mCarrierTextController = new CarrierTextController(mContext, separator, false, false);
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
public void setListening(boolean listening) {
if (listening == mListening) {
return;
}
mListening = listening;
updateListeners();
}
@Override
@VisibleForTesting
public void onDetachedFromWindow() {
setListening(false);
super.onDetachedFromWindow();
}
private void updateListeners() {
if (mListening) {
if (mNetworkController.hasVoiceCallingFeature()) {
mNetworkController.addCallback(this);
}
mCarrierTextController.setListening(this);
} else {
mNetworkController.removeCallback(this);
mCarrierTextController.setListening(null);
}
}
private void handleUpdateState() {
for (int i = 0; i < SIM_SLOTS; i++) {
mCarrierGroups[i].updateState(mInfos[i]);
}
mCarrierDividers[0].setVisibility(
mInfos[0].visible && mInfos[1].visible ? View.VISIBLE : View.GONE);
// This tackles the case of slots 2 being available as well as at least one other.
// In that case we show the second divider. Note that if both dividers are visible, it means
// all three slots are in use, and that is correct.
mCarrierDividers[1].setVisibility(
(mInfos[1].visible && mInfos[2].visible)
|| (mInfos[0].visible && mInfos[2].visible) ? View.VISIBLE : View.GONE);
}
@VisibleForTesting
protected int getSlotIndex(int subscriptionId) {
return SubscriptionManager.getSlotIndex(subscriptionId);
}
@Override
public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
if (info.anySimReady) {
boolean[] slotSeen = new boolean[SIM_SLOTS];
if (info.listOfCarriers.length == info.subscriptionIds.length) {
for (int i = 0; i < SIM_SLOTS && i < info.listOfCarriers.length; i++) {
int slot = getSlotIndex(info.subscriptionIds[i]);
if (slot >= SIM_SLOTS) {
Log.w(TAG, "updateInfoCarrier - slot: " + slot);
continue;
}
if (slot == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
Log.e(TAG,
"Invalid SIM slot index for subscription: "
+ info.subscriptionIds[i]);
continue;
}
mInfos[slot].visible = true;
slotSeen[slot] = true;
mCarrierGroups[slot].setCarrierText(info.listOfCarriers[i].toString().trim());
mCarrierGroups[slot].setVisibility(View.VISIBLE);
}
for (int i = 0; i < SIM_SLOTS; i++) {
if (!slotSeen[i]) {
mInfos[i].visible = false;
mCarrierGroups[i].setVisibility(View.GONE);
}
}
} else {
Log.e(TAG, "Carrier information arrays not of same length");
}
} else {
mInfos[0].visible = false;
mCarrierGroups[0].setCarrierText(info.carrierText);
mCarrierGroups[0].setVisibility(View.VISIBLE);
for (int i = 1; i < SIM_SLOTS; i++) {
mInfos[i].visible = false;
mCarrierGroups[i].setCarrierText("");
mCarrierGroups[i].setVisibility(View.GONE);
}
}
handleUpdateState();
}
@Override
public void setMobileDataIndicators(NetworkController.IconState statusIcon,
NetworkController.IconState qsIcon, int statusType,
int qsType, boolean activityIn, boolean activityOut,
String typeContentDescription,
String description, boolean isWide, int subId, boolean roaming) {
int slotIndex = getSlotIndex(subId);
if (slotIndex >= SIM_SLOTS) {
Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex);
return;
}
if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
Log.e(TAG, "Invalid SIM slot index for subscription: " + subId);
return;
}
mInfos[slotIndex].visible = statusIcon.visible;
mInfos[slotIndex].mobileSignalIconId = statusIcon.icon;
mInfos[slotIndex].contentDescription = statusIcon.contentDescription;
mInfos[slotIndex].typeContentDescription = typeContentDescription;
mInfos[slotIndex].roaming = roaming;
handleUpdateState();
}
@Override
public void setNoSims(boolean hasNoSims, boolean simDetected) {
if (hasNoSims) {
for (int i = 0; i < SIM_SLOTS; i++) {
mInfos[i].visible = false;
}
}
handleUpdateState();
}
static final class CellSignalState {
boolean visible;
int mobileSignalIconId;
String contentDescription;
String typeContentDescription;
boolean roaming;
}
}

View File

@@ -23,25 +23,19 @@ import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEX
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.graphics.PorterDuff.Mode;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.os.Bundle;
import android.os.UserManager;
import android.telephony.SubscriptionManager;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
@@ -49,11 +43,9 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.keyguard.CarrierTextController;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.settingslib.Utils;
import com.android.settingslib.drawable.UserIconDrawable;
import com.android.settingslib.graph.SignalDrawable;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.R.dimen;
@@ -62,9 +54,6 @@ import com.android.systemui.qs.TouchAnimator.Builder;
import com.android.systemui.statusbar.phone.MultiUserSwitch;
import com.android.systemui.statusbar.phone.SettingsButton;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
import com.android.systemui.tuner.TunerService;
@@ -73,15 +62,12 @@ import javax.inject.Inject;
import javax.inject.Named;
public class QSFooterImpl extends FrameLayout implements QSFooter,
OnClickListener, OnUserInfoChangedListener, EmergencyListener, SignalCallback,
CarrierTextController.CarrierTextCallback {
OnClickListener, OnUserInfoChangedListener {
private static final int SIM_SLOTS = 2;
private static final String TAG = "QSFooterImpl";
private final ActivityStarter mActivityStarter;
private final UserInfoController mUserInfoController;
private final NetworkController mNetworkController;
private final DeviceProvisionedController mDeviceProvisionedController;
private SettingsButton mSettingsButton;
protected View mSettingsContainer;
@@ -94,7 +80,7 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
private boolean mListening;
private boolean mShowEmergencyCallsOnly;
private QSCarrierGroup mCarrierGroup;
private View mDivider;
protected MultiUserSwitch mMultiUserSwitch;
private ImageView mMultiUserAvatar;
@@ -108,30 +94,15 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
private View mActionsContainer;
private View mDragHandle;
private View mCarrierDivider;
private ViewGroup mMobileFooter;
private View[] mMobileGroups = new View[SIM_SLOTS];
private ViewGroup[] mCarrierGroups = new ViewGroup[SIM_SLOTS];
private TextView[] mCarrierTexts = new TextView[SIM_SLOTS];
private ImageView[] mMobileSignals = new ImageView[SIM_SLOTS];
private ImageView[] mMobileRoamings = new ImageView[SIM_SLOTS];
private final CellSignalState[] mInfos =
new CellSignalState[]{new CellSignalState(), new CellSignalState()};
private final int mColorForeground;
private OnClickListener mExpandClickListener;
private CarrierTextController mCarrierTextController;
@Inject
public QSFooterImpl(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
ActivityStarter activityStarter, UserInfoController userInfoController,
NetworkController networkController,
DeviceProvisionedController deviceProvisionedController) {
super(context, attrs);
mColorForeground = Utils.getColorAttrDefaultColor(context, android.R.attr.colorForeground);
mActivityStarter = activityStarter;
mUserInfoController = userInfoController;
mNetworkController = networkController;
mDeviceProvisionedController = deviceProvisionedController;
}
@@ -140,7 +111,6 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
this(context, attrs,
Dependency.get(ActivityStarter.class),
Dependency.get(UserInfoController.class),
Dependency.get(NetworkController.class),
Dependency.get(DeviceProvisionedController.class));
}
@@ -159,20 +129,7 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
mSettingsContainer = findViewById(R.id.settings_button_container);
mSettingsButton.setOnClickListener(this);
mMobileFooter = findViewById(R.id.qs_mobile);
mCarrierGroups[0] = findViewById(R.id.carrier1);
mCarrierGroups[1] = findViewById(R.id.carrier2);
for (int i = 0; i < SIM_SLOTS; i++) {
mMobileGroups[i] = mCarrierGroups[i].findViewById(R.id.mobile_combo);
mMobileSignals[i] = mCarrierGroups[i].findViewById(R.id.mobile_signal);
mMobileRoamings[i] = mCarrierGroups[i].findViewById(R.id.mobile_roaming);
mCarrierTexts[i] = mCarrierGroups[i].findViewById(R.id.qs_carrier_text);
}
mCarrierDivider = findViewById(R.id.qs_carrier_divider);
CharSequence separator = mContext.getString(
com.android.internal.R.string.kg_text_message_separator);
mCarrierTextController = new CarrierTextController(mContext, separator, false, false);
mCarrierGroup = findViewById(R.id.carrier_group);
mMultiUserSwitch = findViewById(R.id.multi_user_switch);
mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
@@ -240,8 +197,7 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
private TouchAnimator createFooterAnimator() {
return new TouchAnimator.Builder()
.addFloat(mDivider, "alpha", 0, 1)
.addFloat(mMobileFooter, "alpha", 0, 0, 1)
.addFloat(mCarrierDivider, "alpha", 0, 1)
.addFloat(mCarrierGroup, "alpha", 0, 0, 1)
.addFloat(mActionsContainer, "alpha", 0, 1)
.addFloat(mDragHandle, "alpha", 1, 0, 0)
.addFloat(mPageIndicator, "alpha", 0, 1)
@@ -364,17 +320,10 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
private void updateListeners() {
if (mListening) {
mUserInfoController.addCallback(this);
if (mNetworkController.hasVoiceCallingFeature()) {
mNetworkController.addEmergencyListener(this);
mNetworkController.addCallback(this);
}
mCarrierTextController.setListening(this);
} else {
mUserInfoController.removeCallback(this);
mNetworkController.removeEmergencyListener(this);
mNetworkController.removeCallback(this);
mCarrierTextController.setListening(null);
}
mCarrierGroup.setListening(mListening);
}
@Override
@@ -429,17 +378,6 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
true /* dismissShade */);
}
@Override
public void setEmergencyCallsOnly(boolean show) {
boolean changed = show != mShowEmergencyCallsOnly;
if (changed) {
mShowEmergencyCallsOnly = show;
if (mExpanded) {
updateEverything();
}
}
}
@Override
public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
if (picture != null &&
@@ -452,162 +390,4 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
}
mMultiUserAvatar.setImageDrawable(picture);
}
private void handleUpdateState() {
for (int i = 0; i < SIM_SLOTS; i++) {
mMobileGroups[i].setVisibility(mInfos[i].visible ? View.VISIBLE : View.GONE);
if (mInfos[i].visible) {
mMobileRoamings[i].setVisibility(mInfos[i].roaming ? View.VISIBLE : View.GONE);
mMobileRoamings[i].setImageTintList(ColorStateList.valueOf(mColorForeground));
SignalDrawable d = new SignalDrawable(mContext);
d.setDarkIntensity(QuickStatusBarHeader.getColorIntensity(mColorForeground));
mMobileSignals[i].setImageDrawable(d);
mMobileSignals[i].setImageLevel(mInfos[i].mobileSignalIconId);
StringBuilder contentDescription = new StringBuilder();
if (mInfos[i].contentDescription != null) {
contentDescription.append(mInfos[i].contentDescription).append(", ");
}
if (mInfos[i].roaming) {
contentDescription
.append(mContext.getString(R.string.data_connection_roaming))
.append(", ");
}
// TODO: show mobile data off/no internet text for 5 seconds before carrier text
if (TextUtils.equals(mInfos[i].typeContentDescription,
mContext.getString(R.string.data_connection_no_internet))
|| TextUtils.equals(mInfos[i].typeContentDescription,
mContext.getString(R.string.cell_data_off_content_description))) {
contentDescription.append(mInfos[i].typeContentDescription);
}
mMobileSignals[i].setContentDescription(contentDescription);
}
}
mCarrierDivider.setVisibility(
mInfos[0].visible && mInfos[1].visible ? View.VISIBLE : View.GONE);
}
@VisibleForTesting
protected int getSlotIndex(int subscriptionId) {
return SubscriptionManager.getSlotIndex(subscriptionId);
}
@Override
public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
if (info.anySimReady) {
boolean[] slotSeen = new boolean[SIM_SLOTS];
if (info.listOfCarriers.length == info.subscriptionIds.length) {
for (int i = 0; i < SIM_SLOTS && i < info.listOfCarriers.length; i++) {
int slot = getSlotIndex(info.subscriptionIds[i]);
if (slot >= SIM_SLOTS) {
Log.w(TAG, "updateInfoCarrier - slot: " + slot);
continue;
}
if (slot == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
Log.e(TAG,
"Invalid SIM slot index for subscription: "
+ info.subscriptionIds[i]);
continue;
}
mInfos[slot].visible = true;
slotSeen[slot] = true;
mCarrierTexts[slot].setText(info.listOfCarriers[i].toString().trim());
mCarrierGroups[slot].setVisibility(View.VISIBLE);
}
for (int i = 0; i < SIM_SLOTS; i++) {
if (!slotSeen[i]) {
mInfos[i].visible = false;
mCarrierGroups[i].setVisibility(View.GONE);
}
}
} else {
Log.e(TAG, "Carrier information arrays not of same length");
}
} else {
mInfos[0].visible = false;
mCarrierTexts[0].setText(info.carrierText);
mCarrierGroups[0].setVisibility(View.VISIBLE);
for (int i = 1; i < SIM_SLOTS; i++) {
mInfos[i].visible = false;
mCarrierTexts[i].setText("");
mCarrierGroups[i].setVisibility(View.GONE);
}
}
handleUpdateState();
}
@Override
public void setMobileDataIndicators(NetworkController.IconState statusIcon,
NetworkController.IconState qsIcon, int statusType,
int qsType, boolean activityIn, boolean activityOut,
String typeContentDescription,
String description, boolean isWide, int subId, boolean roaming) {
int slotIndex = getSlotIndex(subId);
if (slotIndex >= SIM_SLOTS) {
Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex);
return;
}
if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
Log.e(TAG, "Invalid SIM slot index for subscription: " + subId);
return;
}
mInfos[slotIndex].visible = statusIcon.visible;
mInfos[slotIndex].mobileSignalIconId = statusIcon.icon;
mInfos[slotIndex].contentDescription = statusIcon.contentDescription;
mInfos[slotIndex].typeContentDescription = typeContentDescription;
mInfos[slotIndex].roaming = roaming;
handleUpdateState();
}
@Override
public void setNoSims(boolean hasNoSims, boolean simDetected) {
if (hasNoSims) {
mInfos[0].visible = false;
mInfos[1].visible = false;
}
handleUpdateState();
}
private final class CellSignalState {
boolean visible;
int mobileSignalIconId;
public String contentDescription;
String typeContentDescription;
boolean roaming;
}
/**
* TextView that changes its ellipsize value with its visibility.
*/
public static class QSCarrierText extends TextView {
public QSCarrierText(Context context) {
super(context);
}
public QSCarrierText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public QSCarrierText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public QSCarrierText(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
// Only show marquee when visible
if (visibility == VISIBLE) {
setEllipsize(TextUtils.TruncateAt.MARQUEE);
setSelected(true);
} else {
setEllipsize(TextUtils.TruncateAt.END);
setSelected(false);
}
}
}
}

View File

@@ -24,6 +24,7 @@ import android.view.LayoutInflater;
import android.view.View;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.qs.QSCarrierGroup;
import com.android.systemui.qs.QSFooterImpl;
import com.android.systemui.qs.QuickStatusBarHeader;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
@@ -124,6 +125,11 @@ public class InjectionInflationController {
* Creates the NotificationPanelView.
*/
NotificationPanelView createPanelView();
/**
* Creates the QSCarrierGroup
*/
QSCarrierGroup createQSCarrierGroup();
}
/**

View File

@@ -0,0 +1,177 @@
/*
* Copyright (C) 2019 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;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.telephony.SubscriptionManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
import androidx.test.filters.SmallTest;
import com.android.keyguard.CarrierTextController;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
public class QSCarrierGroupTest extends LeakCheckedTest {
private QSCarrierGroup mCarrierGroup;
@Before
public void setup() throws Exception {
injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
TestableLooper.get(this).runWithLooper(
() -> mCarrierGroup = (QSCarrierGroup) LayoutInflater.from(mContext).inflate(
R.layout.qs_carrier_group, null));
}
@Test // throws no Exception
public void testUpdateCarrierText_sameLengths() {
QSCarrierGroup spiedCarrierGroup = Mockito.spy(mCarrierGroup);
when(spiedCarrierGroup.getSlotIndex(anyInt())).thenAnswer(
new Answer<Integer>() {
@Override
public Integer answer(InvocationOnMock invocationOnMock) throws Throwable {
return invocationOnMock.getArgument(0);
}
});
// listOfCarriers length 1, subscriptionIds length 1, anySims false
CarrierTextController.CarrierTextCallbackInfo
c1 = new CarrierTextController.CarrierTextCallbackInfo(
"",
new CharSequence[]{""},
false,
new int[]{0});
spiedCarrierGroup.updateCarrierInfo(c1);
// listOfCarriers length 1, subscriptionIds length 1, anySims true
CarrierTextController.CarrierTextCallbackInfo
c2 = new CarrierTextController.CarrierTextCallbackInfo(
"",
new CharSequence[]{""},
true,
new int[]{0});
spiedCarrierGroup.updateCarrierInfo(c2);
// listOfCarriers length 2, subscriptionIds length 2, anySims false
CarrierTextController.CarrierTextCallbackInfo
c3 = new CarrierTextController.CarrierTextCallbackInfo(
"",
new CharSequence[]{"", ""},
false,
new int[]{0, 1});
spiedCarrierGroup.updateCarrierInfo(c3);
// listOfCarriers length 2, subscriptionIds length 2, anySims true
CarrierTextController.CarrierTextCallbackInfo
c4 = new CarrierTextController.CarrierTextCallbackInfo(
"",
new CharSequence[]{"", ""},
true,
new int[]{0, 1});
spiedCarrierGroup.updateCarrierInfo(c4);
}
@Test // throws no Exception
public void testUpdateCarrierText_differentLength() {
QSCarrierGroup spiedCarrierGroup = Mockito.spy(mCarrierGroup);
when(spiedCarrierGroup.getSlotIndex(anyInt())).thenAnswer(
new Answer<Integer>() {
@Override
public Integer answer(InvocationOnMock invocationOnMock) throws Throwable {
return invocationOnMock.getArgument(0);
}
});
// listOfCarriers length 2, subscriptionIds length 1, anySims false
CarrierTextController.CarrierTextCallbackInfo
c1 = new CarrierTextController.CarrierTextCallbackInfo(
"",
new CharSequence[]{"", ""},
false,
new int[]{0});
spiedCarrierGroup.updateCarrierInfo(c1);
// listOfCarriers length 2, subscriptionIds length 1, anySims true
CarrierTextController.CarrierTextCallbackInfo
c2 = new CarrierTextController.CarrierTextCallbackInfo(
"",
new CharSequence[]{"", ""},
true,
new int[]{0});
spiedCarrierGroup.updateCarrierInfo(c2);
// listOfCarriers length 1, subscriptionIds length 2, anySims false
CarrierTextController.CarrierTextCallbackInfo
c3 = new CarrierTextController.CarrierTextCallbackInfo(
"",
new CharSequence[]{""},
false,
new int[]{0, 1});
spiedCarrierGroup.updateCarrierInfo(c3);
// listOfCarriers length 1, subscriptionIds length 2, anySims true
CarrierTextController.CarrierTextCallbackInfo
c4 = new CarrierTextController.CarrierTextCallbackInfo(
"",
new CharSequence[]{""},
true,
new int[]{0, 1});
spiedCarrierGroup.updateCarrierInfo(c4);
}
@Test // throws no Exception
public void testUpdateCarrierText_invalidSim() {
QSCarrierGroup spiedCarrierGroup = Mockito.spy(mCarrierGroup);
when(spiedCarrierGroup.getSlotIndex(anyInt())).thenReturn(
SubscriptionManager.INVALID_SIM_SLOT_INDEX);
CarrierTextController.CarrierTextCallbackInfo
c4 = new CarrierTextController.CarrierTextCallbackInfo(
"",
new CharSequence[]{"", ""},
true,
new int[]{0, 1});
spiedCarrierGroup.updateCarrierInfo(c4);
}
@Test // throws no Exception
public void testSetMobileDataIndicators_invalidSim() {
QSCarrierGroup spiedCarrierGroup = Mockito.spy(mCarrierGroup);
when(spiedCarrierGroup.getSlotIndex(anyInt())).thenReturn(
SubscriptionManager.INVALID_SIM_SLOT_INDEX);
spiedCarrierGroup.setMobileDataIndicators(
mock(NetworkController.IconState.class),
mock(NetworkController.IconState.class),
0, 0, true, true, "", "", true, 0, true);
}
}

View File

@@ -16,13 +16,10 @@ package com.android.systemui.qs;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.telephony.SubscriptionManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -31,21 +28,16 @@ import android.view.View;
import androidx.test.filters.SmallTest;
import com.android.keyguard.CarrierTextController.CarrierTextCallbackInfo;
import com.android.systemui.R;
import com.android.systemui.R.id;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -77,117 +69,4 @@ public class QSFooterImplTest extends LeakCheckedTest {
// Verify Settings wasn't launched.
verify(mActivityStarter, never()).startActivity(any(), anyBoolean());
}
@Test // throws no Exception
public void testUpdateCarrierText_sameLengts() {
QSFooterImpl spiedFooter = Mockito.spy(mFooter);
when(spiedFooter.getSlotIndex(anyInt())).thenAnswer(
new Answer<Integer>() {
@Override
public Integer answer(InvocationOnMock invocationOnMock) throws Throwable {
return invocationOnMock.getArgument(0);
}
});
// listOfCarriers length 1, subscriptionIds length 1, anySims false
CarrierTextCallbackInfo c1 = new CarrierTextCallbackInfo(
"",
new CharSequence[]{""},
false,
new int[]{0});
spiedFooter.updateCarrierInfo(c1);
// listOfCarriers length 1, subscriptionIds length 1, anySims true
CarrierTextCallbackInfo c2 = new CarrierTextCallbackInfo(
"",
new CharSequence[]{""},
true,
new int[]{0});
spiedFooter.updateCarrierInfo(c2);
// listOfCarriers length 2, subscriptionIds length 2, anySims false
CarrierTextCallbackInfo c3 = new CarrierTextCallbackInfo(
"",
new CharSequence[]{"", ""},
false,
new int[]{0, 1});
spiedFooter.updateCarrierInfo(c3);
// listOfCarriers length 2, subscriptionIds length 2, anySims true
CarrierTextCallbackInfo c4 = new CarrierTextCallbackInfo(
"",
new CharSequence[]{"", ""},
true,
new int[]{0, 1});
spiedFooter.updateCarrierInfo(c4);
}
@Test // throws no Exception
public void testUpdateCarrierText_differentLength() {
QSFooterImpl spiedFooter = Mockito.spy(mFooter);
when(spiedFooter.getSlotIndex(anyInt())).thenAnswer(
new Answer<Integer>() {
@Override
public Integer answer(InvocationOnMock invocationOnMock) throws Throwable {
return invocationOnMock.getArgument(0);
}
});
// listOfCarriers length 2, subscriptionIds length 1, anySims false
CarrierTextCallbackInfo c1 = new CarrierTextCallbackInfo(
"",
new CharSequence[]{"", ""},
false,
new int[]{0});
spiedFooter.updateCarrierInfo(c1);
// listOfCarriers length 2, subscriptionIds length 1, anySims true
CarrierTextCallbackInfo c2 = new CarrierTextCallbackInfo(
"",
new CharSequence[]{"", ""},
true,
new int[]{0});
spiedFooter.updateCarrierInfo(c2);
// listOfCarriers length 1, subscriptionIds length 2, anySims false
CarrierTextCallbackInfo c3 = new CarrierTextCallbackInfo(
"",
new CharSequence[]{""},
false,
new int[]{0, 1});
spiedFooter.updateCarrierInfo(c3);
// listOfCarriers length 1, subscriptionIds length 2, anySims true
CarrierTextCallbackInfo c4 = new CarrierTextCallbackInfo(
"",
new CharSequence[]{""},
true,
new int[]{0, 1});
spiedFooter.updateCarrierInfo(c4);
}
@Test // throws no Exception
public void testUpdateCarrierText_invalidSim() {
QSFooterImpl spiedFooter = Mockito.spy(mFooter);
when(spiedFooter.getSlotIndex(anyInt())).thenReturn(
SubscriptionManager.INVALID_SIM_SLOT_INDEX);
CarrierTextCallbackInfo c4 = new CarrierTextCallbackInfo(
"",
new CharSequence[]{"", ""},
true,
new int[]{0, 1});
spiedFooter.updateCarrierInfo(c4);
}
@Test // throws no Exception
public void testSetMobileDataIndicators_invalidSim() {
QSFooterImpl spiedFooter = Mockito.spy(mFooter);
when(spiedFooter.getSlotIndex(anyInt())).thenReturn(
SubscriptionManager.INVALID_SIM_SLOT_INDEX);
spiedFooter.setMobileDataIndicators(
mock(NetworkController.IconState.class),
mock(NetworkController.IconState.class),
0, 0, true, true, "", "", true, 0, true);
}
}