From c4fe6c4ffd49b5981141df2b471df7ad33767ef6 Mon Sep 17 00:00:00 2001 From: Dave Mankoff Date: Mon, 18 Nov 2019 17:03:29 -0500 Subject: [PATCH] Move logic from QSCarrierGroup into Controller. This removes the injection into the QSCarrierGroup view and moves it into an associated controller instead. Controllers are added for QSCarrierGroup, QSContainer, and QuickStatusBarHeader, though only QSCarrierGroup does any heavy lifting at the moment. The other controllers are parent controllers that are needed to ensure everything is cleanly initialized when the view is inflated. Bug: 144503618 Test: atest SystemUITests && manual Change-Id: Iee539b492581f84b25811a9ae2d60426b49b075d --- .../keyguard/CarrierTextController.java | 33 ++ .../com/android/systemui/qs/QSCarrier.java | 2 +- .../android/systemui/qs/QSCarrierGroup.java | 294 +--------------- .../systemui/qs/QSCarrierGroupController.java | 331 ++++++++++++++++++ .../qs/QSContainerImplController.java | 57 +++ .../com/android/systemui/qs/QSFragment.java | 17 +- .../systemui/qs/QuickStatusBarHeader.java | 1 - .../qs/QuickStatusBarHeaderController.java | 60 ++++ .../util/InjectionInflationController.java | 6 - ...java => QSCarrierGroupControllerTest.java} | 102 ++++-- .../android/systemui/qs/QSFragmentTest.java | 4 +- 11 files changed, 577 insertions(+), 330 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroupController.java create mode 100644 packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java create mode 100644 packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java rename packages/SystemUI/tests/src/com/android/systemui/qs/{QSCarrierGroupTest.java => QSCarrierGroupControllerTest.java} (61%) diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java index 06aba405fa3e8..d66a53cd90123 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java @@ -24,6 +24,7 @@ import static com.android.systemui.DejankUtils.whitelistIpcs; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.wifi.WifiManager; import android.os.Handler; @@ -42,11 +43,14 @@ import com.android.internal.telephony.TelephonyIntents; import com.android.settingslib.WirelessUtils; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.MainResources; import com.android.systemui.keyguard.WakefulnessLifecycle; import java.util.List; import java.util.Objects; +import javax.inject.Inject; + /** * Controller that generates text including the carrier names and/or the status of all the SIM * interfaces in the device. Through a callback, the updates can be retrieved either as a list or @@ -598,6 +602,35 @@ public class CarrierTextController { return mContext.getText(carrierHelpTextId); } + public static class Builder { + private final Context mContext; + private final String mSeparator; + private boolean mShowAirplaneMode; + private boolean mShowMissingSim; + + @Inject + public Builder(Context context, @MainResources Resources resources) { + mContext = context; + mSeparator = resources.getString( + com.android.internal.R.string.kg_text_message_separator); + } + + + public Builder setShowAirplaneMode(boolean showAirplaneMode) { + mShowAirplaneMode = showAirplaneMode; + return this; + } + + public Builder setShowMissingSim(boolean showMissingSim) { + mShowMissingSim = showMissingSim; + return this; + } + + public CarrierTextController build() { + return new CarrierTextController( + mContext, mSeparator, mShowAirplaneMode, mShowMissingSim); + } + } /** * Data structure for passing information to CarrierTextController subscribers */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/QSCarrier.java index 4501b8f0a4699..4c7d82e99d063 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSCarrier.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSCarrier.java @@ -73,7 +73,7 @@ public class QSCarrier extends LinearLayout { mColorForegroundIntensity = QuickStatusBarHeader.getColorIntensity(colorForeground); } - public void updateState(QSCarrierGroup.CellSignalState state) { + public void updateState(QSCarrierGroupController.CellSignalState state) { mMobileGroup.setVisibility(state.visible ? View.VISIBLE : View.GONE); if (state.visible) { mMobileRoaming.setVisibility(state.roaming ? View.VISIBLE : View.GONE); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java b/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java index 5742787b39bca..346c75da6a89c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java @@ -16,309 +16,43 @@ package com.android.systemui.qs; -import static com.android.systemui.Dependency.BG_HANDLER; -import static com.android.systemui.Dependency.MAIN_LOOPER; -import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; - -import android.annotation.MainThread; import android.content.Context; -import android.content.Intent; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.provider.Settings; -import android.telephony.SubscriptionManager; import android.util.AttributeSet; -import android.util.Log; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; -import androidx.annotation.VisibleForTesting; - -import com.android.keyguard.CarrierTextController; -import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.dagger.qualifiers.BgHandler; -import com.android.systemui.dagger.qualifiers.MainLooper; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.statusbar.policy.NetworkController; - -import java.util.function.Consumer; - -import javax.inject.Inject; -import javax.inject.Named; /** * Displays Carrier name and network status in QS */ -public class QSCarrierGroup extends LinearLayout implements - NetworkController.SignalCallback, View.OnClickListener { - - 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 final Handler mBgHandler; - private final H mMainHandler; - - private View[] mCarrierDividers = new View[SIM_SLOTS - 1]; - private QSCarrier[] mCarrierGroups = new QSCarrier[SIM_SLOTS]; - private TextView mNoSimTextView; - private final CellSignalState[] mInfos = new CellSignalState[SIM_SLOTS]; - private CarrierTextController mCarrierTextController; - private CarrierTextController.CarrierTextCallback mCallback; - private ActivityStarter mActivityStarter; - - private boolean mListening; - - @Inject - public QSCarrierGroup(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs, - NetworkController networkController, ActivityStarter activityStarter, - @BgHandler Handler handler, - @MainLooper Looper looper) { - super(context, attrs); - mNetworkController = networkController; - mActivityStarter = activityStarter; - mBgHandler = handler; - mMainHandler = new H(looper, this::handleUpdateCarrierInfo, this::handleUpdateState); - mCallback = new Callback(mMainHandler); - } - - @VisibleForTesting - protected CarrierTextController.CarrierTextCallback getCallback() { - return mCallback; - } - - @VisibleForTesting +public class QSCarrierGroup extends LinearLayout { public QSCarrierGroup(Context context, AttributeSet attrs) { - this(context, attrs, - Dependency.get(NetworkController.class), - Dependency.get(ActivityStarter.class), - Dependency.get(BG_HANDLER), - Dependency.get(MAIN_LOOPER)); + super(context, attrs); } - @Override - public void onClick(View v) { - if (!v.isVisibleToUser()) return; - mActivityStarter.postStartActivityDismissingKeyguard(new Intent( - Settings.ACTION_WIRELESS_SETTINGS), 0); + TextView getNoSimTextView() { + return findViewById(R.id.no_carrier_text); } - @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); - - mNoSimTextView = findViewById(R.id.no_carrier_text); - - for (int i = 0; i < SIM_SLOTS; i++) { - mInfos[i] = new CellSignalState(); - mCarrierGroups[i].setOnClickListener(this); - } - mNoSimTextView.setOnClickListener(this); - - 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); + QSCarrier getCarrier1View() { + return findViewById(R.id.carrier1); } - public void setListening(boolean listening) { - if (listening == mListening) { - return; - } - mListening = listening; - mBgHandler.post(this::updateListeners); + QSCarrier getCarrier2View() { + return findViewById(R.id.carrier2); } - @Override - @VisibleForTesting - public void onDetachedFromWindow() { - setListening(false); - super.onDetachedFromWindow(); + QSCarrier getCarrier3View() { + return findViewById(R.id.carrier3); } - private void updateListeners() { - if (mListening) { - if (mNetworkController.hasVoiceCallingFeature()) { - mNetworkController.addCallback(this); - } - mCarrierTextController.setListening(mCallback); - } else { - mNetworkController.removeCallback(this); - mCarrierTextController.setListening(null); - } + View getCarrierDivider1() { + return findViewById(R.id.qs_carrier_divider1); } - @MainThread - private void handleUpdateState() { - if (!mMainHandler.getLooper().isCurrentThread()) { - mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget(); - return; - } - - 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); - } - - @MainThread - private void handleUpdateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) { - if (!mMainHandler.getLooper().isCurrentThread()) { - mMainHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget(); - return; - } - - mNoSimTextView.setVisibility(View.GONE); - if (!info.airplaneMode && 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 { - // No sims or airplane mode (but not WFC). Do not show QSCarrierGroup, instead just show - // info.carrierText in a different view. - for (int i = 0; i < SIM_SLOTS; i++) { - mInfos[i].visible = false; - mCarrierGroups[i].setCarrierText(""); - mCarrierGroups[i].setVisibility(View.GONE); - } - mNoSimTextView.setText(info.carrierText); - mNoSimTextView.setVisibility(View.VISIBLE); - } - handleUpdateState(); // handleUpdateCarrierInfo is always called from main thread. - } - - @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; - mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget(); - } - - @Override - public void setNoSims(boolean hasNoSims, boolean simDetected) { - if (hasNoSims) { - for (int i = 0; i < SIM_SLOTS; i++) { - mInfos[i].visible = false; - } - } - mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget(); - } - - static final class CellSignalState { - boolean visible; - int mobileSignalIconId; - String contentDescription; - String typeContentDescription; - boolean roaming; - } - - private static class H extends Handler { - private Consumer mUpdateCarrierInfo; - private Runnable mUpdateState; - static final int MSG_UPDATE_CARRIER_INFO = 0; - static final int MSG_UPDATE_STATE = 1; - - H(Looper looper, - Consumer updateCarrierInfo, - Runnable updateState) { - super(looper); - mUpdateCarrierInfo = updateCarrierInfo; - mUpdateState = updateState; - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_UPDATE_CARRIER_INFO: - mUpdateCarrierInfo.accept( - (CarrierTextController.CarrierTextCallbackInfo) msg.obj); - break; - case MSG_UPDATE_STATE: - mUpdateState.run(); - break; - default: - super.handleMessage(msg); - } - } - } - - private static class Callback implements CarrierTextController.CarrierTextCallback { - private H mMainHandler; - - Callback(H handler) { - mMainHandler = handler; - } - - @Override - public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) { - mMainHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget(); - } + View getCarrierDivider2() { + return findViewById(R.id.qs_carrier_divider2); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroupController.java new file mode 100644 index 0000000000000..ac94858cff270 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroupController.java @@ -0,0 +1,331 @@ +/* + * 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 android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES; + +import android.annotation.MainThread; +import android.content.Intent; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.provider.Settings; +import android.telephony.SubscriptionManager; +import android.util.Log; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.VisibleForTesting; + +import com.android.keyguard.CarrierTextController; +import com.android.systemui.dagger.qualifiers.BgHandler; +import com.android.systemui.dagger.qualifiers.MainLooper; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.statusbar.policy.NetworkController; + +import java.util.function.Consumer; + +import javax.inject.Inject; + +public class QSCarrierGroupController { + 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 ActivityStarter mActivityStarter; + private final Handler mBgHandler; + private final NetworkController mNetworkController; + private final CarrierTextController mCarrierTextController; + private final TextView mNoSimTextView; + private final H mMainHandler; + private final Callback mCallback; + private boolean mListening; + private final CellSignalState[] mInfos = + new CellSignalState[SIM_SLOTS]; + private View[] mCarrierDividers = new View[SIM_SLOTS - 1]; + private QSCarrier[] mCarrierGroups = new QSCarrier[SIM_SLOTS]; + + private final NetworkController.SignalCallback mSignalCallback = + new NetworkController.SignalCallback() { + @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; + mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget(); + } + + @Override + public void setNoSims(boolean hasNoSims, boolean simDetected) { + if (hasNoSims) { + for (int i = 0; i < SIM_SLOTS; i++) { + mInfos[i].visible = false; + } + } + mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget(); + } + }; + + private static class Callback implements CarrierTextController.CarrierTextCallback { + private H mHandler; + + Callback(H handler) { + mHandler = handler; + } + + @Override + public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) { + mHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget(); + } + } + + private QSCarrierGroupController(QSCarrierGroup view, ActivityStarter activityStarter, + @BgHandler Handler bgHandler, @MainLooper Looper mainLooper, + NetworkController networkController, + CarrierTextController.Builder carrierTextControllerBuilder) { + mActivityStarter = activityStarter; + mBgHandler = bgHandler; + mNetworkController = networkController; + mCarrierTextController = carrierTextControllerBuilder + .setShowAirplaneMode(false) + .setShowMissingSim(false) + .build(); + + View.OnClickListener onClickListener = v -> { + if (!v.isVisibleToUser()) { + return; + } + mActivityStarter.postStartActivityDismissingKeyguard( + new Intent(Settings.ACTION_WIRELESS_SETTINGS), 0); + }; + view.setOnClickListener(onClickListener); + mNoSimTextView = view.getNoSimTextView(); + mNoSimTextView.setOnClickListener(onClickListener); + mMainHandler = new H(mainLooper, this::handleUpdateCarrierInfo, this::handleUpdateState); + mCallback = new Callback(mMainHandler); + + + mCarrierGroups[0] = view.getCarrier1View(); + mCarrierGroups[1] = view.getCarrier2View(); + mCarrierGroups[2] = view.getCarrier3View(); + + mCarrierDividers[0] = view.getCarrierDivider1(); + mCarrierDividers[1] = view.getCarrierDivider2(); + + for (int i = 0; i < SIM_SLOTS; i++) { + mInfos[i] = new CellSignalState(); + mCarrierGroups[i].setOnClickListener(onClickListener); + } + view.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); + + view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + } + + @Override + public void onViewDetachedFromWindow(View v) { + setListening(false); + } + }); + } + + @VisibleForTesting + protected int getSlotIndex(int subscriptionId) { + return SubscriptionManager.getSlotIndex(subscriptionId); + } + + public void setListening(boolean listening) { + if (listening == mListening) { + return; + } + mListening = listening; + + mBgHandler.post(this::updateListeners); + } + + private void updateListeners() { + if (mListening) { + if (mNetworkController.hasVoiceCallingFeature()) { + mNetworkController.addCallback(mSignalCallback); + } + mCarrierTextController.setListening(mCallback); + } else { + mNetworkController.removeCallback(mSignalCallback); + mCarrierTextController.setListening(null); + } + } + + + @MainThread + private void handleUpdateState() { + if (!mMainHandler.getLooper().isCurrentThread()) { + mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget(); + return; + } + + 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); + } + + @MainThread + private void handleUpdateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) { + if (!mMainHandler.getLooper().isCurrentThread()) { + mMainHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget(); + return; + } + + mNoSimTextView.setVisibility(View.GONE); + if (!info.airplaneMode && 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 { + // No sims or airplane mode (but not WFC). Do not show QSCarrierGroup, instead just show + // info.carrierText in a different view. + for (int i = 0; i < SIM_SLOTS; i++) { + mInfos[i].visible = false; + mCarrierGroups[i].setCarrierText(""); + mCarrierGroups[i].setVisibility(View.GONE); + } + mNoSimTextView.setText(info.carrierText); + mNoSimTextView.setVisibility(View.VISIBLE); + } + handleUpdateState(); // handleUpdateCarrierInfo is always called from main thread. + } + + private static class H extends Handler { + private Consumer mUpdateCarrierInfo; + private Runnable mUpdateState; + static final int MSG_UPDATE_CARRIER_INFO = 0; + static final int MSG_UPDATE_STATE = 1; + + H(Looper looper, + Consumer updateCarrierInfo, + Runnable updateState) { + super(looper); + mUpdateCarrierInfo = updateCarrierInfo; + mUpdateState = updateState; + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE_CARRIER_INFO: + mUpdateCarrierInfo.accept( + (CarrierTextController.CarrierTextCallbackInfo) msg.obj); + break; + case MSG_UPDATE_STATE: + mUpdateState.run(); + break; + default: + super.handleMessage(msg); + } + } + } + + static final class CellSignalState { + boolean visible; + int mobileSignalIconId; + String contentDescription; + String typeContentDescription; + boolean roaming; + } + + public static class Builder { + private QSCarrierGroup mView; + private final ActivityStarter mActivityStarter; + private final Handler mHandler; + private final Looper mLooper; + private final NetworkController mNetworkController; + private final CarrierTextController.Builder mCarrierTextControllerBuilder; + + @Inject + public Builder(ActivityStarter activityStarter, @BgHandler Handler handler, + @MainLooper Looper looper, NetworkController networkController, + CarrierTextController.Builder carrierTextControllerBuilder) { + mActivityStarter = activityStarter; + mHandler = handler; + mLooper = looper; + mNetworkController = networkController; + mCarrierTextControllerBuilder = carrierTextControllerBuilder; + } + + Builder setQSCarrierGroup(QSCarrierGroup view) { + mView = view; + return this; + } + + public QSCarrierGroupController build() { + return new QSCarrierGroupController(mView, mActivityStarter, mHandler, mLooper, + mNetworkController, mCarrierTextControllerBuilder); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java new file mode 100644 index 0000000000000..fa3328417bd63 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java @@ -0,0 +1,57 @@ +/* + * 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 com.android.systemui.R; + +import javax.inject.Inject; + +public class QSContainerImplController { + private final QSContainerImpl mView; + private final QuickStatusBarHeaderController mQuickStatusBarHeaderController; + + private QSContainerImplController(QSContainerImpl view, + QuickStatusBarHeaderController.Builder quickStatusBarHeaderControllerBuilder) { + mView = view; + mQuickStatusBarHeaderController = quickStatusBarHeaderControllerBuilder + .setQuickStatusBarHeader(mView.findViewById(R.id.header)).build(); + } + + public void setListening(boolean listening) { + mQuickStatusBarHeaderController.setListening(listening); + } + + public static class Builder { + private final QuickStatusBarHeaderController.Builder mQuickStatusBarHeaderControllerBuilder; + private QSContainerImpl mView; + + @Inject + public Builder( + QuickStatusBarHeaderController.Builder quickStatusBarHeaderControllerBuilder) { + mQuickStatusBarHeaderControllerBuilder = quickStatusBarHeaderControllerBuilder; + } + + public Builder setQSContainerImpl(QSContainerImpl view) { + mView = view; + return this; + } + + public QSContainerImplController build() { + return new QSContainerImplController(mView, mQuickStatusBarHeaderControllerBuilder); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index ccc836f50c7bd..5b09267a9e682 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -18,7 +18,6 @@ import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.content.Context; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; @@ -81,6 +80,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; private final InjectionInflationController mInjectionInflater; + private final QSContainerImplController.Builder mQSContainerImplControllerBuilder; private final QSTileHost mHost; private boolean mShowCollapsedOnKeyguard; private boolean mLastKeyguardAndExpanded; @@ -90,15 +90,16 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca * during state transitions which often call into us. */ private int mState; + private QSContainerImplController mQSContainerImplController; @Inject public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler, - InjectionInflationController injectionInflater, - Context context, - QSTileHost qsTileHost, - StatusBarStateController statusBarStateController, CommandQueue commandQueue) { + InjectionInflationController injectionInflater, QSTileHost qsTileHost, + StatusBarStateController statusBarStateController, CommandQueue commandQueue, + QSContainerImplController.Builder qsContainerImplControllerBuilder) { mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler; mInjectionInflater = injectionInflater; + mQSContainerImplControllerBuilder = qsContainerImplControllerBuilder; commandQueue.observe(getLifecycle(), this); mHost = qsTileHost; mStatusBarStateController = statusBarStateController; @@ -121,6 +122,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mFooter = view.findViewById(R.id.qs_footer); mContainer = view.findViewById(id.quick_settings_container); + mQSContainerImplController = mQSContainerImplControllerBuilder + .setQSContainerImpl((QSContainerImpl) view) + .build(); + + mQSDetail.setQsPanel(mQSPanel, mHeader, (View) mFooter); mQSAnimator = new QSAnimator(this, mHeader.findViewById(R.id.quick_qs_panel), mQSPanel); @@ -340,6 +346,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca public void setListening(boolean listening) { if (DEBUG) Log.d(TAG, "setListening " + listening); mListening = listening; + mQSContainerImplController.setListening(listening); mHeader.setListening(listening); mFooter.setListening(listening); mQSPanel.setListening(mListening, mQsExpanded); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 19af235b4f687..02e8f59097939 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -549,7 +549,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements } mHeaderQsPanel.setListening(listening); mListening = listening; - mCarrierGroup.setListening(mListening); if (listening) { mZenController.addCallback(this); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java new file mode 100644 index 0000000000000..867677a46a588 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java @@ -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. + */ + +package com.android.systemui.qs; + +import com.android.systemui.R; + +import javax.inject.Inject; + +public class QuickStatusBarHeaderController { + private final QuickStatusBarHeader mView; + private final QSCarrierGroupController mQSCarrierGroupController; + + private QuickStatusBarHeaderController(QuickStatusBarHeader view, + QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder) { + mView = view; + mQSCarrierGroupController = qsCarrierGroupControllerBuilder + .setQSCarrierGroup(mView.findViewById(R.id.carrier_group)) + .build(); + } + + public void setListening(boolean listening) { + mQSCarrierGroupController.setListening(listening); + // TODO: move mView.setListening logic into here. + mView.setListening(listening); + } + + + public static class Builder { + private final QSCarrierGroupController.Builder mQSCarrierGroupControllerBuilder; + private QuickStatusBarHeader mView; + + @Inject + public Builder(QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder) { + mQSCarrierGroupControllerBuilder = qsCarrierGroupControllerBuilder; + } + + public Builder setQuickStatusBarHeader(QuickStatusBarHeader view) { + mView = view; + return this; + } + + public QuickStatusBarHeaderController build() { + return new QuickStatusBarHeaderController(mView, mQSCarrierGroupControllerBuilder); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java index 60d76789ff63b..6976649ec4972 100644 --- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java +++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java @@ -27,7 +27,6 @@ import com.android.keyguard.KeyguardClockSwitch; import com.android.keyguard.KeyguardMessageArea; import com.android.keyguard.KeyguardSliceView; import com.android.systemui.dagger.SystemUIRootComponent; -import com.android.systemui.qs.QSCarrierGroup; import com.android.systemui.qs.QSFooterImpl; import com.android.systemui.qs.QSPanel; import com.android.systemui.qs.QuickQSPanel; @@ -134,11 +133,6 @@ public class InjectionInflationController { */ NotificationPanelView createPanelView(); - /** - * Creates the QSCarrierGroup - */ - QSCarrierGroup createQSCarrierGroup(); - /** * Creates the Shelf. */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSCarrierGroupTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSCarrierGroupControllerTest.java similarity index 61% rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSCarrierGroupTest.java rename to packages/SystemUI/tests/src/com/android/systemui/qs/QSCarrierGroupControllerTest.java index a2a20a9538fcb..1bfe1b10299b8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSCarrierGroupTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSCarrierGroupControllerTest.java @@ -16,7 +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.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -24,55 +27,89 @@ import android.os.Handler; import android.telephony.SubscriptionManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; -import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; import androidx.test.filters.SmallTest; import com.android.keyguard.CarrierTextController; -import com.android.systemui.Dependency; -import com.android.systemui.R; +import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.utils.leaks.LeakCheckedTest; +import com.android.systemui.utils.os.FakeHandler; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; +import org.mockito.MockitoAnnotations; import org.mockito.stubbing.Answer; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @SmallTest -public class QSCarrierGroupTest extends LeakCheckedTest { +public class QSCarrierGroupControllerTest extends LeakCheckedTest { - private QSCarrierGroup mCarrierGroup; + private QSCarrierGroupController mQSCarrierGroupController; + private NetworkController.SignalCallback mSignalCallback; private CarrierTextController.CarrierTextCallback mCallback; + @Mock + private QSCarrierGroup mQSCarrierGroup; + @Mock + private ActivityStarter mActivityStarter; + @Mock + private NetworkController mNetworkController; + @Mock + private CarrierTextController.Builder mCarrierTextControllerBuilder; + @Mock + private CarrierTextController mCarrierTextController; private TestableLooper mTestableLooper; @Before public void setup() throws Exception { + MockitoAnnotations.initMocks(this); injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES); mTestableLooper = TestableLooper.get(this); - mDependency.injectTestDependency( - Dependency.BG_HANDLER, new Handler(mTestableLooper.getLooper())); - mDependency.injectTestDependency(Dependency.MAIN_LOOPER, mTestableLooper.getLooper()); - mTestableLooper.runWithLooper( - () -> mCarrierGroup = (QSCarrierGroup) LayoutInflater.from(mContext).inflate( - R.layout.qs_carrier_group, null)); - mCallback = mCarrierGroup.getCallback(); + Handler handler = new FakeHandler(TestableLooper.get(this).getLooper()); + + when(mNetworkController.hasVoiceCallingFeature()).thenReturn(true); + doAnswer(invocation -> mSignalCallback = invocation.getArgument(0)) + .when(mNetworkController) + .addCallback(any(NetworkController.SignalCallback.class)); + + when(mCarrierTextControllerBuilder.setShowAirplaneMode(anyBoolean())) + .thenReturn(mCarrierTextControllerBuilder); + when(mCarrierTextControllerBuilder.setShowMissingSim(anyBoolean())) + .thenReturn(mCarrierTextControllerBuilder); + when(mCarrierTextControllerBuilder.build()).thenReturn(mCarrierTextController); + + doAnswer(invocation -> mCallback = invocation.getArgument(0)) + .when(mCarrierTextController) + .setListening(any(CarrierTextController.CarrierTextCallback.class)); + + when(mQSCarrierGroup.getNoSimTextView()).thenReturn(new TextView(mContext)); + when(mQSCarrierGroup.getCarrier1View()).thenReturn(mock(QSCarrier.class)); + when(mQSCarrierGroup.getCarrier2View()).thenReturn(mock(QSCarrier.class)); + when(mQSCarrierGroup.getCarrier3View()).thenReturn(mock(QSCarrier.class)); + when(mQSCarrierGroup.getCarrierDivider1()).thenReturn(new View(mContext)); + when(mQSCarrierGroup.getCarrierDivider2()).thenReturn(new View(mContext)); + + mQSCarrierGroupController = new QSCarrierGroupController.Builder( + mActivityStarter, handler, TestableLooper.get(this).getLooper(), + mNetworkController, mCarrierTextControllerBuilder) + .setQSCarrierGroup(mQSCarrierGroup) + .build(); + + mQSCarrierGroupController.setListening(true); } @Test // throws no Exception public void testUpdateCarrierText_sameLengths() { - QSCarrierGroup spiedCarrierGroup = Mockito.spy(mCarrierGroup); - when(spiedCarrierGroup.getSlotIndex(anyInt())).thenAnswer( - new Answer() { - @Override - public Integer answer(InvocationOnMock invocationOnMock) throws Throwable { - return invocationOnMock.getArgument(0); - } - }); + QSCarrierGroupController spiedCarrierGroupController = + Mockito.spy(mQSCarrierGroupController); + when(spiedCarrierGroupController.getSlotIndex(anyInt())).thenAnswer( + (Answer) invocationOnMock -> invocationOnMock.getArgument(0)); // listOfCarriers length 1, subscriptionIds length 1, anySims false CarrierTextController.CarrierTextCallbackInfo @@ -115,14 +152,10 @@ public class QSCarrierGroupTest extends LeakCheckedTest { @Test // throws no Exception public void testUpdateCarrierText_differentLength() { - QSCarrierGroup spiedCarrierGroup = Mockito.spy(mCarrierGroup); - when(spiedCarrierGroup.getSlotIndex(anyInt())).thenAnswer( - new Answer() { - @Override - public Integer answer(InvocationOnMock invocationOnMock) throws Throwable { - return invocationOnMock.getArgument(0); - } - }); + QSCarrierGroupController spiedCarrierGroupController = + Mockito.spy(mQSCarrierGroupController); + when(spiedCarrierGroupController.getSlotIndex(anyInt())).thenAnswer( + (Answer) invocationOnMock -> invocationOnMock.getArgument(0)); // listOfCarriers length 2, subscriptionIds length 1, anySims false CarrierTextController.CarrierTextCallbackInfo @@ -164,9 +197,11 @@ public class QSCarrierGroupTest extends LeakCheckedTest { @Test // throws no Exception public void testUpdateCarrierText_invalidSim() { - QSCarrierGroup spiedCarrierGroup = Mockito.spy(mCarrierGroup); - when(spiedCarrierGroup.getSlotIndex(anyInt())).thenReturn( + QSCarrierGroupController spiedCarrierGroupController = + Mockito.spy(mQSCarrierGroupController); + when(spiedCarrierGroupController.getSlotIndex(anyInt())).thenReturn( SubscriptionManager.INVALID_SIM_SLOT_INDEX); + CarrierTextController.CarrierTextCallbackInfo c4 = new CarrierTextController.CarrierTextCallbackInfo( "", @@ -179,10 +214,7 @@ public class QSCarrierGroupTest extends LeakCheckedTest { @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( + mSignalCallback.setMobileDataIndicators( mock(NetworkController.IconState.class), mock(NetworkController.IconState.class), 0, 0, true, true, "", "", true, 0, true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index 6cebb1206efed..800456228e84c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -147,9 +147,9 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { new RemoteInputQuickSettingsDisabler(context, mock(ConfigurationController.class), commandQueue), new InjectionInflationController(SystemUIFactory.getInstance().getRootComponent()), - context, mock(QSTileHost.class), mock(StatusBarStateController.class), - commandQueue); + commandQueue, + mock(QSContainerImplController.Builder.class)); } }