Merge "First pass at setting up a mobile connectivity pipeline" into tm-qpr-dev
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
**
|
||||
** Copyright 2022, 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.
|
||||
*/
|
||||
-->
|
||||
|
||||
<merge
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:systemui="http://schemas.android.com/apk/res-auto" >
|
||||
|
||||
<com.android.keyguard.AlphaOptimizedLinearLayout
|
||||
android:id="@+id/mobile_group"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/inout_container"
|
||||
android:layout_height="17dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center_vertical">
|
||||
<ImageView
|
||||
android:id="@+id/mobile_in"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:src="@drawable/ic_activity_down"
|
||||
android:visibility="gone"
|
||||
android:paddingEnd="2dp"
|
||||
/>
|
||||
<ImageView
|
||||
android:id="@+id/mobile_out"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:src="@drawable/ic_activity_up"
|
||||
android:paddingEnd="2dp"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
</FrameLayout>
|
||||
<ImageView
|
||||
android:id="@+id/mobile_type"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingStart="2.5dp"
|
||||
android:paddingEnd="1dp"
|
||||
android:visibility="gone" />
|
||||
<Space
|
||||
android:id="@+id/mobile_roaming_space"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="@dimen/roaming_icon_start_padding"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical">
|
||||
<com.android.systemui.statusbar.AnimatedImageView
|
||||
android:id="@+id/mobile_signal"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
systemui:hasOverlappingRendering="false"
|
||||
/>
|
||||
<ImageView
|
||||
android:id="@+id/mobile_roaming"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/stat_sys_roaming"
|
||||
android:contentDescription="@string/data_connection_roaming"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
<ImageView
|
||||
android:id="@+id/mobile_roaming_large"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/stat_sys_roaming_large"
|
||||
android:contentDescription="@string/data_connection_roaming"
|
||||
android:visibility="gone" />
|
||||
</com.android.keyguard.AlphaOptimizedLinearLayout>
|
||||
</merge>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
**
|
||||
** Copyright 2022, 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.
|
||||
*/
|
||||
-->
|
||||
<com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/mobile_combo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical" >
|
||||
|
||||
<include layout="@layout/status_bar_mobile_signal_group_inner" />
|
||||
|
||||
</com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView>
|
||||
|
||||
@@ -18,80 +18,12 @@
|
||||
-->
|
||||
<com.android.systemui.statusbar.StatusBarMobileView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:systemui="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/mobile_combo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical" >
|
||||
|
||||
<com.android.keyguard.AlphaOptimizedLinearLayout
|
||||
android:id="@+id/mobile_group"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal" >
|
||||
<include layout="@layout/status_bar_mobile_signal_group_inner" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/inout_container"
|
||||
android:layout_height="17dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center_vertical">
|
||||
<ImageView
|
||||
android:id="@+id/mobile_in"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:src="@drawable/ic_activity_down"
|
||||
android:visibility="gone"
|
||||
android:paddingEnd="2dp"
|
||||
/>
|
||||
<ImageView
|
||||
android:id="@+id/mobile_out"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:src="@drawable/ic_activity_up"
|
||||
android:paddingEnd="2dp"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
</FrameLayout>
|
||||
<ImageView
|
||||
android:id="@+id/mobile_type"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingStart="2.5dp"
|
||||
android:paddingEnd="1dp"
|
||||
android:visibility="gone" />
|
||||
<Space
|
||||
android:id="@+id/mobile_roaming_space"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="@dimen/roaming_icon_start_padding"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical">
|
||||
<com.android.systemui.statusbar.AnimatedImageView
|
||||
android:id="@+id/mobile_signal"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
systemui:hasOverlappingRendering="false"
|
||||
/>
|
||||
<ImageView
|
||||
android:id="@+id/mobile_roaming"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/stat_sys_roaming"
|
||||
android:contentDescription="@string/data_connection_roaming"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
<ImageView
|
||||
android:id="@+id/mobile_roaming_large"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/stat_sys_roaming_large"
|
||||
android:contentDescription="@string/data_connection_roaming"
|
||||
android:visibility="gone" />
|
||||
</com.android.keyguard.AlphaOptimizedLinearLayout>
|
||||
</com.android.systemui.statusbar.StatusBarMobileView>
|
||||
|
||||
|
||||
@@ -20,14 +20,16 @@ import android.util.AttributeSet
|
||||
import android.widget.FrameLayout
|
||||
|
||||
/**
|
||||
* A temporary base class that's shared between our old status bar wifi view implementation
|
||||
* ([StatusBarWifiView]) and our new status bar wifi view implementation
|
||||
* ([ModernStatusBarWifiView]).
|
||||
* A temporary base class that's shared between our old status bar connectivity view implementations
|
||||
* ([StatusBarWifiView], [StatusBarMobileView]) and our new status bar implementations (
|
||||
* [ModernStatusBarWifiView], [ModernStatusBarMobileView]).
|
||||
*
|
||||
* Once our refactor is over, we should be able to delete this go-between class and the old view
|
||||
* class.
|
||||
*/
|
||||
abstract class BaseStatusBarWifiView @JvmOverloads constructor(
|
||||
abstract class BaseStatusBarFrameLayout
|
||||
@JvmOverloads
|
||||
constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttrs: Int = 0,
|
||||
@@ -29,7 +29,6 @@ import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
@@ -43,7 +42,10 @@ import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconStat
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
|
||||
/**
|
||||
* View group for the mobile icon in the status bar
|
||||
*/
|
||||
public class StatusBarMobileView extends BaseStatusBarFrameLayout implements DarkReceiver,
|
||||
StatusIconDisplayable {
|
||||
private static final String TAG = "StatusBarMobileView";
|
||||
|
||||
@@ -101,11 +103,6 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public StatusBarMobileView(Context context, AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getDrawingRect(Rect outRect) {
|
||||
super.getDrawingRect(outRect);
|
||||
|
||||
@@ -40,7 +40,7 @@ import java.util.ArrayList;
|
||||
/**
|
||||
* Start small: StatusBarWifiView will be able to layout from a WifiIconState
|
||||
*/
|
||||
public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkReceiver {
|
||||
public class StatusBarWifiView extends BaseStatusBarFrameLayout implements DarkReceiver {
|
||||
private static final String TAG = "StatusBarWifiView";
|
||||
|
||||
/// Used to show etc dots
|
||||
|
||||
@@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone;
|
||||
|
||||
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
|
||||
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
|
||||
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW;
|
||||
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
@@ -38,7 +39,7 @@ import com.android.systemui.dagger.SysUISingleton;
|
||||
import com.android.systemui.demomode.DemoModeCommandReceiver;
|
||||
import com.android.systemui.plugins.DarkIconDispatcher;
|
||||
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
|
||||
import com.android.systemui.statusbar.BaseStatusBarWifiView;
|
||||
import com.android.systemui.statusbar.BaseStatusBarFrameLayout;
|
||||
import com.android.systemui.statusbar.StatusBarIconView;
|
||||
import com.android.systemui.statusbar.StatusBarMobileView;
|
||||
import com.android.systemui.statusbar.StatusBarWifiView;
|
||||
@@ -48,6 +49,10 @@ import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorI
|
||||
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
|
||||
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
|
||||
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
|
||||
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
|
||||
import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder;
|
||||
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
|
||||
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
|
||||
import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
|
||||
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
|
||||
import com.android.systemui.util.Assert;
|
||||
@@ -83,6 +88,12 @@ public interface StatusBarIconController {
|
||||
/** */
|
||||
void setMobileIcons(String slot, List<MobileIconState> states);
|
||||
|
||||
/**
|
||||
* This method completely replaces {@link #setMobileIcons} with the information from the new
|
||||
* mobile data pipeline. Icons will automatically keep their state up to date, so we don't have
|
||||
* to worry about funneling MobileIconState objects through anymore.
|
||||
*/
|
||||
void setNewMobileIconSubIds(List<Integer> subIds);
|
||||
/**
|
||||
* Display the no calling & SMS icons.
|
||||
*/
|
||||
@@ -141,12 +152,14 @@ public interface StatusBarIconController {
|
||||
StatusBarLocation location,
|
||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||
WifiViewModel wifiViewModel,
|
||||
MobileUiAdapter mobileUiAdapter,
|
||||
MobileContextProvider mobileContextProvider,
|
||||
DarkIconDispatcher darkIconDispatcher) {
|
||||
super(linearLayout,
|
||||
location,
|
||||
statusBarPipelineFlags,
|
||||
wifiViewModel,
|
||||
mobileUiAdapter,
|
||||
mobileContextProvider);
|
||||
mIconHPadding = mContext.getResources().getDimensionPixelSize(
|
||||
R.dimen.status_bar_icon_padding);
|
||||
@@ -207,6 +220,7 @@ public interface StatusBarIconController {
|
||||
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
|
||||
private final WifiViewModel mWifiViewModel;
|
||||
private final MobileContextProvider mMobileContextProvider;
|
||||
private final MobileUiAdapter mMobileUiAdapter;
|
||||
private final DarkIconDispatcher mDarkIconDispatcher;
|
||||
|
||||
@Inject
|
||||
@@ -214,10 +228,12 @@ public interface StatusBarIconController {
|
||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||
WifiViewModel wifiViewModel,
|
||||
MobileContextProvider mobileContextProvider,
|
||||
MobileUiAdapter mobileUiAdapter,
|
||||
DarkIconDispatcher darkIconDispatcher) {
|
||||
mStatusBarPipelineFlags = statusBarPipelineFlags;
|
||||
mWifiViewModel = wifiViewModel;
|
||||
mMobileContextProvider = mobileContextProvider;
|
||||
mMobileUiAdapter = mobileUiAdapter;
|
||||
mDarkIconDispatcher = darkIconDispatcher;
|
||||
}
|
||||
|
||||
@@ -227,6 +243,7 @@ public interface StatusBarIconController {
|
||||
location,
|
||||
mStatusBarPipelineFlags,
|
||||
mWifiViewModel,
|
||||
mMobileUiAdapter,
|
||||
mMobileContextProvider,
|
||||
mDarkIconDispatcher);
|
||||
}
|
||||
@@ -244,11 +261,14 @@ public interface StatusBarIconController {
|
||||
StatusBarLocation location,
|
||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||
WifiViewModel wifiViewModel,
|
||||
MobileContextProvider mobileContextProvider) {
|
||||
MobileUiAdapter mobileUiAdapter,
|
||||
MobileContextProvider mobileContextProvider
|
||||
) {
|
||||
super(group,
|
||||
location,
|
||||
statusBarPipelineFlags,
|
||||
wifiViewModel,
|
||||
mobileUiAdapter,
|
||||
mobileContextProvider);
|
||||
}
|
||||
|
||||
@@ -284,14 +304,18 @@ public interface StatusBarIconController {
|
||||
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
|
||||
private final WifiViewModel mWifiViewModel;
|
||||
private final MobileContextProvider mMobileContextProvider;
|
||||
private final MobileUiAdapter mMobileUiAdapter;
|
||||
|
||||
@Inject
|
||||
public Factory(
|
||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||
WifiViewModel wifiViewModel,
|
||||
MobileContextProvider mobileContextProvider) {
|
||||
MobileUiAdapter mobileUiAdapter,
|
||||
MobileContextProvider mobileContextProvider
|
||||
) {
|
||||
mStatusBarPipelineFlags = statusBarPipelineFlags;
|
||||
mWifiViewModel = wifiViewModel;
|
||||
mMobileUiAdapter = mobileUiAdapter;
|
||||
mMobileContextProvider = mobileContextProvider;
|
||||
}
|
||||
|
||||
@@ -301,6 +325,7 @@ public interface StatusBarIconController {
|
||||
location,
|
||||
mStatusBarPipelineFlags,
|
||||
mWifiViewModel,
|
||||
mMobileUiAdapter,
|
||||
mMobileContextProvider);
|
||||
}
|
||||
}
|
||||
@@ -315,6 +340,8 @@ public interface StatusBarIconController {
|
||||
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
|
||||
private final WifiViewModel mWifiViewModel;
|
||||
private final MobileContextProvider mMobileContextProvider;
|
||||
private final MobileIconsViewModel mMobileIconsViewModel;
|
||||
|
||||
protected final Context mContext;
|
||||
protected final int mIconSize;
|
||||
// Whether or not these icons show up in dumpsys
|
||||
@@ -333,7 +360,9 @@ public interface StatusBarIconController {
|
||||
StatusBarLocation location,
|
||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||
WifiViewModel wifiViewModel,
|
||||
MobileContextProvider mobileContextProvider) {
|
||||
MobileUiAdapter mobileUiAdapter,
|
||||
MobileContextProvider mobileContextProvider
|
||||
) {
|
||||
mGroup = group;
|
||||
mLocation = location;
|
||||
mStatusBarPipelineFlags = statusBarPipelineFlags;
|
||||
@@ -342,6 +371,14 @@ public interface StatusBarIconController {
|
||||
mContext = group.getContext();
|
||||
mIconSize = mContext.getResources().getDimensionPixelSize(
|
||||
com.android.internal.R.dimen.status_bar_icon_size);
|
||||
|
||||
if (statusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
|
||||
// This starts the flow for the new pipeline, and will notify us of changes
|
||||
mMobileIconsViewModel = mobileUiAdapter.createMobileIconsViewModel();
|
||||
MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
|
||||
} else {
|
||||
mMobileIconsViewModel = null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDemoable() {
|
||||
@@ -394,6 +431,9 @@ public interface StatusBarIconController {
|
||||
|
||||
case TYPE_MOBILE:
|
||||
return addMobileIcon(index, slot, holder.getMobileState());
|
||||
|
||||
case TYPE_MOBILE_NEW:
|
||||
return addNewMobileIcon(index, slot, holder.getTag());
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -410,7 +450,7 @@ public interface StatusBarIconController {
|
||||
|
||||
@VisibleForTesting
|
||||
protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
|
||||
final BaseStatusBarWifiView view;
|
||||
final BaseStatusBarFrameLayout view;
|
||||
if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
|
||||
view = onCreateModernStatusBarWifiView(slot);
|
||||
// When [ModernStatusBarWifiView] is created, it will automatically apply the
|
||||
@@ -429,17 +469,47 @@ public interface StatusBarIconController {
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
|
||||
protected StatusIconDisplayable addMobileIcon(
|
||||
int index,
|
||||
String slot,
|
||||
MobileIconState state
|
||||
) {
|
||||
if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
|
||||
throw new IllegalStateException("Attempting to add a mobile icon while the new "
|
||||
+ "pipeline is enabled is not supported");
|
||||
}
|
||||
|
||||
// Use the `subId` field as a key to query for the correct context
|
||||
StatusBarMobileView view = onCreateStatusBarMobileView(state.subId, slot);
|
||||
view.applyMobileState(state);
|
||||
mGroup.addView(view, index, onCreateLayoutParams());
|
||||
StatusBarMobileView mobileView = onCreateStatusBarMobileView(state.subId, slot);
|
||||
mobileView.applyMobileState(state);
|
||||
mGroup.addView(mobileView, index, onCreateLayoutParams());
|
||||
|
||||
if (mIsInDemoMode) {
|
||||
Context mobileContext = mMobileContextProvider
|
||||
.getMobileContextForSub(state.subId, mContext);
|
||||
mDemoStatusIcons.addMobileView(state, mobileContext);
|
||||
}
|
||||
return mobileView;
|
||||
}
|
||||
|
||||
protected StatusIconDisplayable addNewMobileIcon(
|
||||
int index,
|
||||
String slot,
|
||||
int subId
|
||||
) {
|
||||
if (!mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
|
||||
throw new IllegalStateException("Attempting to add a mobile icon using the new"
|
||||
+ "pipeline, but the enabled flag is false.");
|
||||
}
|
||||
|
||||
BaseStatusBarFrameLayout view = onCreateModernStatusBarMobileView(slot, subId);
|
||||
mGroup.addView(view, index, onCreateLayoutParams());
|
||||
|
||||
if (mIsInDemoMode) {
|
||||
// TODO (b/249790009): demo mode should be handled at the data layer in the
|
||||
// new pipeline
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -464,6 +534,15 @@ public interface StatusBarIconController {
|
||||
return view;
|
||||
}
|
||||
|
||||
private ModernStatusBarMobileView onCreateModernStatusBarMobileView(
|
||||
String slot, int subId) {
|
||||
return ModernStatusBarMobileView
|
||||
.constructAndBind(
|
||||
mContext,
|
||||
slot,
|
||||
mMobileIconsViewModel.viewModelForSub(subId));
|
||||
}
|
||||
|
||||
protected LinearLayout.LayoutParams onCreateLayoutParams() {
|
||||
return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
|
||||
}
|
||||
@@ -519,6 +598,10 @@ public interface StatusBarIconController {
|
||||
return;
|
||||
case TYPE_MOBILE:
|
||||
onSetMobileIcon(viewIndex, holder.getMobileState());
|
||||
return;
|
||||
case TYPE_MOBILE_NEW:
|
||||
// Nothing, the icon updates itself now
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -542,9 +625,13 @@ public interface StatusBarIconController {
|
||||
}
|
||||
|
||||
public void onSetMobileIcon(int viewIndex, MobileIconState state) {
|
||||
StatusBarMobileView view = (StatusBarMobileView) mGroup.getChildAt(viewIndex);
|
||||
if (view != null) {
|
||||
view.applyMobileState(state);
|
||||
View view = mGroup.getChildAt(viewIndex);
|
||||
if (view instanceof StatusBarMobileView) {
|
||||
((StatusBarMobileView) view).applyMobileState(state);
|
||||
} else {
|
||||
// ModernStatusBarMobileView automatically updates via the ViewModel
|
||||
throw new IllegalStateException("Cannot update ModernStatusBarMobileView outside of"
|
||||
+ "the new pipeline");
|
||||
}
|
||||
|
||||
if (mIsInDemoMode) {
|
||||
|
||||
@@ -40,6 +40,7 @@ import com.android.systemui.statusbar.StatusIconDisplayable;
|
||||
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
|
||||
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
|
||||
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
|
||||
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController;
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
|
||||
import com.android.systemui.tuner.TunerService;
|
||||
@@ -66,8 +67,8 @@ public class StatusBarIconControllerImpl implements Tunable,
|
||||
private final StatusBarIconList mStatusBarIconList;
|
||||
private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
|
||||
private final ArraySet<String> mIconHideList = new ArraySet<>();
|
||||
|
||||
private Context mContext;
|
||||
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
|
||||
private final Context mContext;
|
||||
|
||||
/** */
|
||||
@Inject
|
||||
@@ -78,9 +79,12 @@ public class StatusBarIconControllerImpl implements Tunable,
|
||||
ConfigurationController configurationController,
|
||||
TunerService tunerService,
|
||||
DumpManager dumpManager,
|
||||
StatusBarIconList statusBarIconList) {
|
||||
StatusBarIconList statusBarIconList,
|
||||
StatusBarPipelineFlags statusBarPipelineFlags
|
||||
) {
|
||||
mStatusBarIconList = statusBarIconList;
|
||||
mContext = context;
|
||||
mStatusBarPipelineFlags = statusBarPipelineFlags;
|
||||
|
||||
configurationController.addCallback(this);
|
||||
commandQueue.addCallback(this);
|
||||
@@ -220,6 +224,11 @@ public class StatusBarIconControllerImpl implements Tunable,
|
||||
*/
|
||||
@Override
|
||||
public void setMobileIcons(String slot, List<MobileIconState> iconStates) {
|
||||
if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
|
||||
Log.d(TAG, "ignoring old pipeline callbacks, because the new "
|
||||
+ "pipeline frontend is enabled");
|
||||
return;
|
||||
}
|
||||
Slot mobileSlot = mStatusBarIconList.getSlot(slot);
|
||||
|
||||
// Reverse the sort order to show icons with left to right([Slot1][Slot2]..).
|
||||
@@ -227,7 +236,6 @@ public class StatusBarIconControllerImpl implements Tunable,
|
||||
Collections.reverse(iconStates);
|
||||
|
||||
for (MobileIconState state : iconStates) {
|
||||
|
||||
StatusBarIconHolder holder = mobileSlot.getHolderForTag(state.subId);
|
||||
if (holder == null) {
|
||||
holder = StatusBarIconHolder.fromMobileIconState(state);
|
||||
@@ -239,6 +247,28 @@ public class StatusBarIconControllerImpl implements Tunable,
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNewMobileIconSubIds(List<Integer> subIds) {
|
||||
if (!mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
|
||||
Log.d(TAG, "ignoring new pipeline callback, "
|
||||
+ "since the frontend is disabled");
|
||||
return;
|
||||
}
|
||||
Slot mobileSlot = mStatusBarIconList.getSlot("mobile");
|
||||
|
||||
Collections.reverse(subIds);
|
||||
|
||||
for (Integer subId : subIds) {
|
||||
StatusBarIconHolder holder = mobileSlot.getHolderForTag(subId);
|
||||
if (holder == null) {
|
||||
holder = StatusBarIconHolder.fromSubIdForModernMobileIcon(subId);
|
||||
setIcon("mobile", holder);
|
||||
} else {
|
||||
// Don't have to do anything in the new world
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept a list of CallIndicatorIconStates, and show the call strength icons.
|
||||
* @param slot statusbar slot for the call strength icons
|
||||
@@ -384,8 +414,6 @@ public class StatusBarIconControllerImpl implements Tunable,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void handleSet(String slotName, StatusBarIconHolder holder) {
|
||||
int viewIndex = mStatusBarIconList.getViewIndex(slotName, holder.getTag());
|
||||
mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.systemui.statusbar.phone;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Icon;
|
||||
@@ -25,6 +26,10 @@ import com.android.internal.statusbar.StatusBarIcon;
|
||||
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
|
||||
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
|
||||
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
|
||||
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Wraps {@link com.android.internal.statusbar.StatusBarIcon} so we can still have a uniform list
|
||||
@@ -33,15 +38,35 @@ public class StatusBarIconHolder {
|
||||
public static final int TYPE_ICON = 0;
|
||||
public static final int TYPE_WIFI = 1;
|
||||
public static final int TYPE_MOBILE = 2;
|
||||
/**
|
||||
* TODO (b/249790733): address this once the new pipeline is in place
|
||||
* This type exists so that the new pipeline (see {@link MobileIconViewModel}) can be used
|
||||
* to inform the old view system about changes to the data set (the list of mobile icons). The
|
||||
* design of the new pipeline should allow for removal of this icon holder type, and obsolete
|
||||
* the need for this entire class.
|
||||
*
|
||||
* @deprecated This field only exists so the new status bar pipeline can interface with the
|
||||
* view holder system.
|
||||
*/
|
||||
@Deprecated
|
||||
public static final int TYPE_MOBILE_NEW = 3;
|
||||
|
||||
@IntDef({
|
||||
TYPE_ICON,
|
||||
TYPE_WIFI,
|
||||
TYPE_MOBILE,
|
||||
TYPE_MOBILE_NEW
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface IconType {}
|
||||
|
||||
private StatusBarIcon mIcon;
|
||||
private WifiIconState mWifiState;
|
||||
private MobileIconState mMobileState;
|
||||
private int mType = TYPE_ICON;
|
||||
private @IconType int mType = TYPE_ICON;
|
||||
private int mTag = 0;
|
||||
|
||||
private StatusBarIconHolder() {
|
||||
|
||||
}
|
||||
|
||||
public static StatusBarIconHolder fromIcon(StatusBarIcon icon) {
|
||||
@@ -79,6 +104,18 @@ public class StatusBarIconHolder {
|
||||
return holder;
|
||||
}
|
||||
|
||||
/**
|
||||
* ONLY for use with the new connectivity pipeline, where we only need a subscriptionID to
|
||||
* determine icon ordering and building the correct view model
|
||||
*/
|
||||
public static StatusBarIconHolder fromSubIdForModernMobileIcon(int subId) {
|
||||
StatusBarIconHolder holder = new StatusBarIconHolder();
|
||||
holder.mType = TYPE_MOBILE_NEW;
|
||||
holder.mTag = subId;
|
||||
|
||||
return holder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new StatusBarIconHolder from a CallIndicatorIconState.
|
||||
*/
|
||||
@@ -95,7 +132,7 @@ public class StatusBarIconHolder {
|
||||
return holder;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
public @IconType int getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
@@ -134,8 +171,12 @@ public class StatusBarIconHolder {
|
||||
return mWifiState.visible;
|
||||
case TYPE_MOBILE:
|
||||
return mMobileState.visible;
|
||||
case TYPE_MOBILE_NEW:
|
||||
//TODO (b/249790733), the new pipeline can control visibility via the ViewModel
|
||||
return true;
|
||||
|
||||
default: return true;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,6 +197,10 @@ public class StatusBarIconHolder {
|
||||
case TYPE_MOBILE:
|
||||
mMobileState.visible = visible;
|
||||
break;
|
||||
|
||||
case TYPE_MOBILE_NEW:
|
||||
//TODO (b/249790733), the new pipeline can control visibility via the ViewModel
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
package com.android.systemui.statusbar.pipeline.dagger
|
||||
|
||||
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
|
||||
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepositoryImpl
|
||||
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
|
||||
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepositoryImpl
|
||||
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
|
||||
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
|
||||
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
|
||||
@@ -30,4 +34,12 @@ abstract class StatusBarPipelineModule {
|
||||
|
||||
@Binds
|
||||
abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
|
||||
|
||||
@Binds
|
||||
abstract fun mobileSubscriptionRepository(
|
||||
impl: MobileSubscriptionRepositoryImpl
|
||||
): MobileSubscriptionRepository
|
||||
|
||||
@Binds
|
||||
abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.statusbar.pipeline.mobile.data.model
|
||||
|
||||
import android.annotation.IntRange
|
||||
import android.telephony.Annotation.DataActivityType
|
||||
import android.telephony.CellSignalStrength
|
||||
import android.telephony.TelephonyCallback.CarrierNetworkListener
|
||||
import android.telephony.TelephonyCallback.DataActivityListener
|
||||
import android.telephony.TelephonyCallback.DataConnectionStateListener
|
||||
import android.telephony.TelephonyCallback.DisplayInfoListener
|
||||
import android.telephony.TelephonyCallback.ServiceStateListener
|
||||
import android.telephony.TelephonyCallback.SignalStrengthsListener
|
||||
import android.telephony.TelephonyDisplayInfo
|
||||
import android.telephony.TelephonyManager
|
||||
|
||||
/**
|
||||
* Data class containing all of the relevant information for a particular line of service, known as
|
||||
* a Subscription in the telephony world. These models are the result of a single telephony listener
|
||||
* which has many callbacks which each modify some particular field on this object.
|
||||
*
|
||||
* The design goal here is to de-normalize fields from the system into our model fields below. So
|
||||
* any new field that needs to be tracked should be copied into this data class rather than
|
||||
* threading complex system objects through the pipeline.
|
||||
*/
|
||||
data class MobileSubscriptionModel(
|
||||
/** From [ServiceStateListener.onServiceStateChanged] */
|
||||
val isEmergencyOnly: Boolean = false,
|
||||
|
||||
/** From [SignalStrengthsListener.onSignalStrengthsChanged] */
|
||||
val isGsm: Boolean = false,
|
||||
@IntRange(from = 0, to = 4)
|
||||
val cdmaLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
|
||||
@IntRange(from = 0, to = 4)
|
||||
val primaryLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
|
||||
|
||||
/** Comes directly from [DataConnectionStateListener.onDataConnectionStateChanged] */
|
||||
val dataConnectionState: Int? = null,
|
||||
|
||||
/** From [DataActivityListener.onDataActivity]. See [TelephonyManager] for the values */
|
||||
@DataActivityType val dataActivityDirection: Int? = null,
|
||||
|
||||
/** From [CarrierNetworkListener.onCarrierNetworkChange] */
|
||||
val carrierNetworkChangeActive: Boolean? = null,
|
||||
|
||||
/** From [DisplayInfoListener.onDisplayInfoChanged] */
|
||||
val displayInfo: TelephonyDisplayInfo? = null
|
||||
)
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.statusbar.pipeline.mobile.data.repository
|
||||
|
||||
import android.telephony.CellSignalStrength
|
||||
import android.telephony.CellSignalStrengthCdma
|
||||
import android.telephony.ServiceState
|
||||
import android.telephony.SignalStrength
|
||||
import android.telephony.SubscriptionInfo
|
||||
import android.telephony.SubscriptionManager
|
||||
import android.telephony.TelephonyCallback
|
||||
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
|
||||
import android.telephony.TelephonyCallback.CarrierNetworkListener
|
||||
import android.telephony.TelephonyCallback.DataActivityListener
|
||||
import android.telephony.TelephonyCallback.DataConnectionStateListener
|
||||
import android.telephony.TelephonyCallback.DisplayInfoListener
|
||||
import android.telephony.TelephonyCallback.ServiceStateListener
|
||||
import android.telephony.TelephonyCallback.SignalStrengthsListener
|
||||
import android.telephony.TelephonyDisplayInfo
|
||||
import android.telephony.TelephonyManager
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.asExecutor
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Repo for monitoring the complete active subscription info list, to be consumed and filtered based
|
||||
* on various policy
|
||||
*/
|
||||
interface MobileSubscriptionRepository {
|
||||
/** Observable list of current mobile subscriptions */
|
||||
val subscriptionsFlow: Flow<List<SubscriptionInfo>>
|
||||
|
||||
/** Observable for the subscriptionId of the current mobile data connection */
|
||||
val activeMobileDataSubscriptionId: Flow<Int>
|
||||
|
||||
/** Get or create an observable for the given subscription ID */
|
||||
fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel>
|
||||
}
|
||||
|
||||
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SysUISingleton
|
||||
class MobileSubscriptionRepositoryImpl
|
||||
@Inject
|
||||
constructor(
|
||||
private val subscriptionManager: SubscriptionManager,
|
||||
private val telephonyManager: TelephonyManager,
|
||||
@Background private val bgDispatcher: CoroutineDispatcher,
|
||||
@Application private val scope: CoroutineScope,
|
||||
) : MobileSubscriptionRepository {
|
||||
private val subIdFlowCache: MutableMap<Int, StateFlow<MobileSubscriptionModel>> = mutableMapOf()
|
||||
|
||||
/**
|
||||
* State flow that emits the set of mobile data subscriptions, each represented by its own
|
||||
* [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
|
||||
* info object, but for now we keep track of the infos themselves.
|
||||
*/
|
||||
override val subscriptionsFlow: StateFlow<List<SubscriptionInfo>> =
|
||||
conflatedCallbackFlow {
|
||||
val callback =
|
||||
object : SubscriptionManager.OnSubscriptionsChangedListener() {
|
||||
override fun onSubscriptionsChanged() {
|
||||
trySend(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
subscriptionManager.addOnSubscriptionsChangedListener(
|
||||
bgDispatcher.asExecutor(),
|
||||
callback,
|
||||
)
|
||||
|
||||
awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
|
||||
}
|
||||
.mapLatest { fetchSubscriptionsList() }
|
||||
.stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
|
||||
|
||||
/** StateFlow that keeps track of the current active mobile data subscription */
|
||||
override val activeMobileDataSubscriptionId: StateFlow<Int> =
|
||||
conflatedCallbackFlow {
|
||||
val callback =
|
||||
object : TelephonyCallback(), ActiveDataSubscriptionIdListener {
|
||||
override fun onActiveDataSubscriptionIdChanged(subId: Int) {
|
||||
trySend(subId)
|
||||
}
|
||||
}
|
||||
|
||||
telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
|
||||
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
|
||||
}
|
||||
.stateIn(
|
||||
scope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
SubscriptionManager.INVALID_SUBSCRIPTION_ID
|
||||
)
|
||||
|
||||
/**
|
||||
* Each mobile subscription needs its own flow, which comes from registering listeners on the
|
||||
* system. Use this method to create those flows and cache them for reuse
|
||||
*/
|
||||
override fun getFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> {
|
||||
return subIdFlowCache[subId]
|
||||
?: createFlowForSubId(subId).also { subIdFlowCache[subId] = it }
|
||||
}
|
||||
|
||||
@VisibleForTesting fun getSubIdFlowCache() = subIdFlowCache
|
||||
|
||||
private fun createFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> = run {
|
||||
var state = MobileSubscriptionModel()
|
||||
conflatedCallbackFlow {
|
||||
val phony = telephonyManager.createForSubscriptionId(subId)
|
||||
// TODO (b/240569788): log all of these into the connectivity logger
|
||||
val callback =
|
||||
object :
|
||||
TelephonyCallback(),
|
||||
ServiceStateListener,
|
||||
SignalStrengthsListener,
|
||||
DataConnectionStateListener,
|
||||
DataActivityListener,
|
||||
CarrierNetworkListener,
|
||||
DisplayInfoListener {
|
||||
override fun onServiceStateChanged(serviceState: ServiceState) {
|
||||
state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
|
||||
trySend(state)
|
||||
}
|
||||
override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
|
||||
val cdmaLevel =
|
||||
signalStrength
|
||||
.getCellSignalStrengths(CellSignalStrengthCdma::class.java)
|
||||
.let { strengths ->
|
||||
if (!strengths.isEmpty()) {
|
||||
strengths[0].level
|
||||
} else {
|
||||
CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
val primaryLevel = signalStrength.level
|
||||
|
||||
state =
|
||||
state.copy(
|
||||
cdmaLevel = cdmaLevel,
|
||||
primaryLevel = primaryLevel,
|
||||
isGsm = signalStrength.isGsm,
|
||||
)
|
||||
trySend(state)
|
||||
}
|
||||
override fun onDataConnectionStateChanged(
|
||||
dataState: Int,
|
||||
networkType: Int
|
||||
) {
|
||||
state = state.copy(dataConnectionState = dataState)
|
||||
trySend(state)
|
||||
}
|
||||
override fun onDataActivity(direction: Int) {
|
||||
state = state.copy(dataActivityDirection = direction)
|
||||
trySend(state)
|
||||
}
|
||||
override fun onCarrierNetworkChange(active: Boolean) {
|
||||
state = state.copy(carrierNetworkChangeActive = active)
|
||||
trySend(state)
|
||||
}
|
||||
override fun onDisplayInfoChanged(
|
||||
telephonyDisplayInfo: TelephonyDisplayInfo
|
||||
) {
|
||||
state = state.copy(displayInfo = telephonyDisplayInfo)
|
||||
trySend(state)
|
||||
}
|
||||
}
|
||||
phony.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
|
||||
awaitClose {
|
||||
phony.unregisterTelephonyCallback(callback)
|
||||
// Release the cached flow
|
||||
subIdFlowCache.remove(subId)
|
||||
}
|
||||
}
|
||||
.stateIn(scope, SharingStarted.WhileSubscribed(), state)
|
||||
}
|
||||
|
||||
private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
|
||||
withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.statusbar.pipeline.mobile.data.repository
|
||||
|
||||
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import com.android.systemui.statusbar.policy.DeviceProvisionedController
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Repository to observe the state of [DeviceProvisionedController.isUserSetup]. This information
|
||||
* can change some policy related to display
|
||||
*/
|
||||
interface UserSetupRepository {
|
||||
/** Observable tracking [DeviceProvisionedController.isUserSetup] */
|
||||
val isUserSetupFlow: Flow<Boolean>
|
||||
}
|
||||
|
||||
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SysUISingleton
|
||||
class UserSetupRepositoryImpl
|
||||
@Inject
|
||||
constructor(
|
||||
private val deviceProvisionedController: DeviceProvisionedController,
|
||||
@Background private val bgDispatcher: CoroutineDispatcher,
|
||||
@Application scope: CoroutineScope,
|
||||
) : UserSetupRepository {
|
||||
/** State flow that tracks [DeviceProvisionedController.isUserSetup] */
|
||||
override val isUserSetupFlow: StateFlow<Boolean> =
|
||||
conflatedCallbackFlow {
|
||||
val callback =
|
||||
object : DeviceProvisionedController.DeviceProvisionedListener {
|
||||
override fun onUserSetupChanged() {
|
||||
trySend(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
deviceProvisionedController.addCallback(callback)
|
||||
|
||||
awaitClose { deviceProvisionedController.removeCallback(callback) }
|
||||
}
|
||||
.onStart { emit(Unit) }
|
||||
.mapLatest { fetchUserSetupState() }
|
||||
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
|
||||
|
||||
private suspend fun fetchUserSetupState(): Boolean =
|
||||
withContext(bgDispatcher) { deviceProvisionedController.isCurrentUserSetup }
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.statusbar.pipeline.mobile.domain.interactor
|
||||
|
||||
import android.telephony.CarrierConfigManager
|
||||
import com.android.settingslib.SignalIcon
|
||||
import com.android.settingslib.mobile.TelephonyIcons
|
||||
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
|
||||
import com.android.systemui.util.CarrierConfigTracker
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
interface MobileIconInteractor {
|
||||
/** Identifier for RAT type indicator */
|
||||
val iconGroup: Flow<SignalIcon.MobileIconGroup>
|
||||
/** True if this line of service is emergency-only */
|
||||
val isEmergencyOnly: Flow<Boolean>
|
||||
/** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
|
||||
val level: Flow<Int>
|
||||
/** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
|
||||
val numberOfLevels: Flow<Int>
|
||||
/** True when we want to draw an icon that makes room for the exclamation mark */
|
||||
val cutOut: Flow<Boolean>
|
||||
}
|
||||
|
||||
/** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
|
||||
class MobileIconInteractorImpl(
|
||||
mobileStatusInfo: Flow<MobileSubscriptionModel>,
|
||||
) : MobileIconInteractor {
|
||||
override val iconGroup: Flow<SignalIcon.MobileIconGroup> = flowOf(TelephonyIcons.THREE_G)
|
||||
override val isEmergencyOnly: Flow<Boolean> = mobileStatusInfo.map { it.isEmergencyOnly }
|
||||
|
||||
override val level: Flow<Int> =
|
||||
mobileStatusInfo.map { mobileModel ->
|
||||
// TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi]
|
||||
if (mobileModel.isGsm) {
|
||||
mobileModel.primaryLevel
|
||||
} else {
|
||||
mobileModel.cdmaLevel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This will become variable based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL]
|
||||
* once it's wired up inside of [CarrierConfigTracker]
|
||||
*/
|
||||
override val numberOfLevels: Flow<Int> = flowOf(4)
|
||||
|
||||
/** Whether or not to draw the mobile triangle as "cut out", i.e., with the exclamation mark */
|
||||
// TODO: find a better name for this?
|
||||
override val cutOut: Flow<Boolean> = flowOf(false)
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.statusbar.pipeline.mobile.domain.interactor
|
||||
|
||||
import android.telephony.CarrierConfigManager
|
||||
import android.telephony.SubscriptionInfo
|
||||
import android.telephony.SubscriptionManager
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
|
||||
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
|
||||
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
|
||||
import com.android.systemui.util.CarrierConfigTracker
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
|
||||
/**
|
||||
* Business layer logic for mobile subscription icons
|
||||
*
|
||||
* Mobile indicators represent the UI for the (potentially filtered) list of [SubscriptionInfo]s
|
||||
* that the system knows about. They obey policy that depends on OEM, carrier, and locale configs
|
||||
*/
|
||||
@SysUISingleton
|
||||
class MobileIconsInteractor
|
||||
@Inject
|
||||
constructor(
|
||||
private val mobileSubscriptionRepo: MobileSubscriptionRepository,
|
||||
private val carrierConfigTracker: CarrierConfigTracker,
|
||||
userSetupRepo: UserSetupRepository,
|
||||
) {
|
||||
private val activeMobileDataSubscriptionId =
|
||||
mobileSubscriptionRepo.activeMobileDataSubscriptionId
|
||||
|
||||
private val unfilteredSubscriptions: Flow<List<SubscriptionInfo>> =
|
||||
mobileSubscriptionRepo.subscriptionsFlow
|
||||
|
||||
/**
|
||||
* Generally, SystemUI wants to show iconography for each subscription that is listed by
|
||||
* [SubscriptionManager]. However, in the case of opportunistic subscriptions, we want to only
|
||||
* show a single representation of the pair of subscriptions. The docs define opportunistic as:
|
||||
*
|
||||
* "A subscription is opportunistic (if) the network it connects to has limited coverage"
|
||||
* https://developer.android.com/reference/android/telephony/SubscriptionManager#setOpportunistic(boolean,%20int)
|
||||
*
|
||||
* In the case of opportunistic networks (typically CBRS), we will filter out one of the
|
||||
* subscriptions based on
|
||||
* [CarrierConfigManager.KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN],
|
||||
* and by checking which subscription is opportunistic, or which one is active.
|
||||
*/
|
||||
val filteredSubscriptions: Flow<List<SubscriptionInfo>> =
|
||||
combine(unfilteredSubscriptions, activeMobileDataSubscriptionId) { unfilteredSubs, activeId
|
||||
->
|
||||
// Based on the old logic,
|
||||
if (unfilteredSubs.size != 2) {
|
||||
return@combine unfilteredSubs
|
||||
}
|
||||
|
||||
val info1 = unfilteredSubs[0]
|
||||
val info2 = unfilteredSubs[1]
|
||||
// If both subscriptions are primary, show both
|
||||
if (!info1.isOpportunistic && !info2.isOpportunistic) {
|
||||
return@combine unfilteredSubs
|
||||
}
|
||||
|
||||
// NOTE: at this point, we are now returning a single SubscriptionInfo
|
||||
|
||||
// If carrier required, always show the icon of the primary subscription.
|
||||
// Otherwise, show whichever subscription is currently active for internet.
|
||||
if (carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) {
|
||||
// return the non-opportunistic info
|
||||
return@combine if (info1.isOpportunistic) listOf(info2) else listOf(info1)
|
||||
} else {
|
||||
return@combine if (info1.subscriptionId == activeId) {
|
||||
listOf(info1)
|
||||
} else {
|
||||
listOf(info2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val isUserSetup: Flow<Boolean> = userSetupRepo.isUserSetupFlow
|
||||
|
||||
/** Vends out new [MobileIconInteractor] for a particular subId */
|
||||
fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
|
||||
MobileIconInteractorImpl(mobileSubscriptionFlowForSubId(subId))
|
||||
|
||||
/**
|
||||
* Create a new flow for a given subscription ID, which usually maps 1:1 with mobile connections
|
||||
*/
|
||||
private fun mobileSubscriptionFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> =
|
||||
mobileSubscriptionRepo.getFlowForSubId(subId)
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.statusbar.pipeline.mobile.ui
|
||||
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.statusbar.phone.StatusBarIconController
|
||||
import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager
|
||||
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
|
||||
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
/**
|
||||
* This class is intended to provide a context to collect on the
|
||||
* [MobileIconsInteractor.filteredSubscriptions] data source and supply a state flow that can
|
||||
* control [StatusBarIconController] to keep the old UI in sync with the new data source.
|
||||
*
|
||||
* It also provides a mechanism to create a top-level view model for each IconManager to know about
|
||||
* the list of available mobile lines of service for which we want to show icons.
|
||||
*/
|
||||
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SysUISingleton
|
||||
class MobileUiAdapter
|
||||
@Inject
|
||||
constructor(
|
||||
interactor: MobileIconsInteractor,
|
||||
private val iconController: StatusBarIconController,
|
||||
private val iconsViewModelFactory: MobileIconsViewModel.Factory,
|
||||
@Application scope: CoroutineScope,
|
||||
) {
|
||||
private val mobileSubIds: Flow<List<Int>> =
|
||||
interactor.filteredSubscriptions.mapLatest { infos ->
|
||||
infos.map { subscriptionInfo -> subscriptionInfo.subscriptionId }
|
||||
}
|
||||
|
||||
/**
|
||||
* We expose the list of tracked subscriptions as a flow of a list of ints, where each int is
|
||||
* the subscriptionId of the relevant subscriptions. These act as a key into the layouts which
|
||||
* house the mobile infos.
|
||||
*
|
||||
* NOTE: this should go away as the view presenter learns more about this data pipeline
|
||||
*/
|
||||
private val mobileSubIdsState: StateFlow<List<Int>> =
|
||||
mobileSubIds
|
||||
.onEach {
|
||||
// Notify the icon controller here so that it knows to add icons
|
||||
iconController.setNewMobileIconSubIds(it)
|
||||
}
|
||||
.stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
|
||||
|
||||
/**
|
||||
* Create a MobileIconsViewModel for a given [IconManager], and bind it to to the manager's
|
||||
* lifecycle. This will start collecting on [mobileSubIdsState] and link our new pipeline with
|
||||
* the old view system.
|
||||
*/
|
||||
fun createMobileIconsViewModel(): MobileIconsViewModel =
|
||||
iconsViewModelFactory.create(mobileSubIdsState)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.statusbar.pipeline.mobile.ui.binder
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.android.settingslib.graph.SignalDrawable
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.lifecycle.repeatWhenAttached
|
||||
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
object MobileIconBinder {
|
||||
/** Binds the view to the view-model, continuing to update the former based on the latter */
|
||||
@JvmStatic
|
||||
fun bind(
|
||||
view: ViewGroup,
|
||||
viewModel: MobileIconViewModel,
|
||||
) {
|
||||
val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)
|
||||
val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
|
||||
|
||||
view.isVisible = true
|
||||
iconView.isVisible = true
|
||||
|
||||
view.repeatWhenAttached {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
// Set the icon for the triangle
|
||||
launch {
|
||||
viewModel.iconId.distinctUntilChanged().collect { iconId ->
|
||||
mobileDrawable.level = iconId
|
||||
}
|
||||
}
|
||||
|
||||
// Set the tint
|
||||
launch {
|
||||
viewModel.tint.collect { tint ->
|
||||
iconView.imageTintList = ColorStateList.valueOf(tint)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.statusbar.pipeline.mobile.ui.binder
|
||||
|
||||
import android.view.View
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.android.systemui.lifecycle.repeatWhenAttached
|
||||
import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager
|
||||
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter
|
||||
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
object MobileIconsBinder {
|
||||
/**
|
||||
* Start this ViewModel collecting on the list of mobile subscriptions in the scope of [view]
|
||||
* which is passed in and managed by [IconManager]. Once the subscription list flow starts
|
||||
* collecting, [MobileUiAdapter] will send updates to the icon manager.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun bind(view: View, viewModel: MobileIconsViewModel) {
|
||||
view.repeatWhenAttached {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
launch {
|
||||
viewModel.subscriptionIdsFlow.collect {
|
||||
// TODO(b/249790733): This is an empty collect, because [MobileUiAdapter]
|
||||
// sets up a side-effect in this flow to trigger the methods on
|
||||
// [StatusBarIconController] which allows for this pipeline to be a data
|
||||
// source for the mobile icons.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.statusbar.pipeline.mobile.ui.view
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.statusbar.BaseStatusBarFrameLayout
|
||||
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
|
||||
import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder
|
||||
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
|
||||
import java.util.ArrayList
|
||||
|
||||
class ModernStatusBarMobileView(
|
||||
context: Context,
|
||||
attrs: AttributeSet?,
|
||||
) : BaseStatusBarFrameLayout(context, attrs) {
|
||||
|
||||
private lateinit var slot: String
|
||||
override fun getSlot() = slot
|
||||
|
||||
override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
override fun setStaticDrawableColor(color: Int) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
override fun setDecorColor(color: Int) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
override fun setVisibleState(state: Int, animate: Boolean) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
override fun getVisibleState(): Int {
|
||||
return STATE_ICON
|
||||
}
|
||||
|
||||
override fun isIconVisible(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Inflates a new instance of [ModernStatusBarMobileView], binds it to [viewModel], and
|
||||
* returns it.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun constructAndBind(
|
||||
context: Context,
|
||||
slot: String,
|
||||
viewModel: MobileIconViewModel,
|
||||
): ModernStatusBarMobileView {
|
||||
return (LayoutInflater.from(context)
|
||||
.inflate(R.layout.status_bar_mobile_signal_group_new, null)
|
||||
as ModernStatusBarMobileView)
|
||||
.also {
|
||||
it.slot = slot
|
||||
MobileIconBinder.bind(it, viewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.statusbar.pipeline.mobile.ui.viewmodel
|
||||
|
||||
import android.graphics.Color
|
||||
import com.android.settingslib.graph.SignalDrawable
|
||||
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
|
||||
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
|
||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
/**
|
||||
* View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
|
||||
* a single line of service via [MobileIconInteractor] and update the UI based on that
|
||||
* subscription's information.
|
||||
*
|
||||
* There will be exactly one [MobileIconViewModel] per filtered subscription offered from
|
||||
* [MobileIconsInteractor.filteredSubscriptions]
|
||||
*
|
||||
* TODO: figure out where carrier merged and VCN models go (probably here?)
|
||||
*/
|
||||
class MobileIconViewModel
|
||||
constructor(
|
||||
val subscriptionId: Int,
|
||||
iconInteractor: MobileIconInteractor,
|
||||
logger: ConnectivityPipelineLogger,
|
||||
) {
|
||||
/** An int consumable by [SignalDrawable] for display */
|
||||
var iconId: Flow<Int> =
|
||||
combine(iconInteractor.level, iconInteractor.numberOfLevels, iconInteractor.cutOut) {
|
||||
level,
|
||||
numberOfLevels,
|
||||
cutOut ->
|
||||
SignalDrawable.getState(level, numberOfLevels, cutOut)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.logOutputChange(logger, "iconId($subscriptionId)")
|
||||
|
||||
var tint: Flow<Int> = flowOf(Color.CYAN)
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.
|
||||
*/
|
||||
|
||||
@file:OptIn(InternalCoroutinesApi::class)
|
||||
|
||||
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
|
||||
|
||||
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
|
||||
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
|
||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.InternalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* View model for describing the system's current mobile cellular connections. The result is a list
|
||||
* of [MobileIconViewModel]s which describe the individual icons and can be bound to
|
||||
* [ModernStatusBarMobileView]
|
||||
*/
|
||||
class MobileIconsViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
val subscriptionIdsFlow: Flow<List<Int>>,
|
||||
private val interactor: MobileIconsInteractor,
|
||||
private val logger: ConnectivityPipelineLogger,
|
||||
) {
|
||||
/** TODO: do we need to cache these? */
|
||||
fun viewModelForSub(subId: Int): MobileIconViewModel =
|
||||
MobileIconViewModel(
|
||||
subId,
|
||||
interactor.createMobileConnectionInteractorForSubId(subId),
|
||||
logger
|
||||
)
|
||||
|
||||
class Factory
|
||||
@Inject
|
||||
constructor(
|
||||
private val interactor: MobileIconsInteractor,
|
||||
private val logger: ConnectivityPipelineLogger,
|
||||
) {
|
||||
fun create(subscriptionIdsFlow: Flow<List<Int>>): MobileIconsViewModel {
|
||||
return MobileIconsViewModel(
|
||||
subscriptionIdsFlow,
|
||||
interactor,
|
||||
logger,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.statusbar.BaseStatusBarWifiView
|
||||
import com.android.systemui.statusbar.BaseStatusBarFrameLayout
|
||||
import com.android.systemui.statusbar.StatusBarIconView
|
||||
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
|
||||
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
|
||||
@@ -37,7 +37,7 @@ import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
|
||||
class ModernStatusBarWifiView(
|
||||
context: Context,
|
||||
attrs: AttributeSet?
|
||||
) : BaseStatusBarWifiView(context, attrs) {
|
||||
) : BaseStatusBarFrameLayout(context, attrs) {
|
||||
|
||||
private lateinit var slot: String
|
||||
private lateinit var binding: WifiViewBinder.Binding
|
||||
|
||||
@@ -67,6 +67,8 @@ public class CarrierConfigTracker
|
||||
private boolean mDefaultCarrierProvisionsWifiMergedNetworks;
|
||||
private boolean mDefaultShowOperatorNameConfigLoaded;
|
||||
private boolean mDefaultShowOperatorNameConfig;
|
||||
private boolean mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfigLoaded;
|
||||
private boolean mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfig;
|
||||
|
||||
@Inject
|
||||
public CarrierConfigTracker(
|
||||
@@ -206,6 +208,22 @@ public class CarrierConfigTracker
|
||||
return mDefaultShowOperatorNameConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN value for
|
||||
* the default carrier config.
|
||||
*/
|
||||
public boolean getAlwaysShowPrimarySignalBarInOpportunisticNetworkDefault() {
|
||||
if (!mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfigLoaded) {
|
||||
mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfig = CarrierConfigManager
|
||||
.getDefaultConfig().getBoolean(CarrierConfigManager
|
||||
.KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN
|
||||
);
|
||||
mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfigLoaded = true;
|
||||
}
|
||||
|
||||
return mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL value for the given subId, or the
|
||||
* default value if no override exists
|
||||
|
||||
@@ -44,6 +44,7 @@ import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
|
||||
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
|
||||
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
|
||||
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
|
||||
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
|
||||
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
|
||||
import com.android.systemui.utils.leaks.LeakCheckedTest;
|
||||
|
||||
@@ -80,6 +81,7 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
|
||||
StatusBarLocation.HOME,
|
||||
mock(StatusBarPipelineFlags.class),
|
||||
mock(WifiViewModel.class),
|
||||
mock(MobileUiAdapter.class),
|
||||
mMobileContextProvider,
|
||||
mock(DarkIconDispatcher.class));
|
||||
testCallOnAdd_forManager(manager);
|
||||
@@ -123,12 +125,14 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
|
||||
StatusBarLocation location,
|
||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||
WifiViewModel wifiViewModel,
|
||||
MobileUiAdapter mobileUiAdapter,
|
||||
MobileContextProvider contextProvider,
|
||||
DarkIconDispatcher darkIconDispatcher) {
|
||||
super(group,
|
||||
location,
|
||||
statusBarPipelineFlags,
|
||||
wifiViewModel,
|
||||
mobileUiAdapter,
|
||||
contextProvider,
|
||||
darkIconDispatcher);
|
||||
}
|
||||
@@ -169,6 +173,7 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
|
||||
StatusBarLocation.HOME,
|
||||
mock(StatusBarPipelineFlags.class),
|
||||
mock(WifiViewModel.class),
|
||||
mock(MobileUiAdapter.class),
|
||||
contextProvider);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.statusbar.pipeline.mobile.data.repository
|
||||
|
||||
import android.telephony.SubscriptionInfo
|
||||
import android.telephony.SubscriptionManager
|
||||
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
class FakeMobileSubscriptionRepository : MobileSubscriptionRepository {
|
||||
private val _subscriptionsFlow = MutableStateFlow<List<SubscriptionInfo>>(listOf())
|
||||
override val subscriptionsFlow: Flow<List<SubscriptionInfo>> = _subscriptionsFlow
|
||||
|
||||
private val _activeMobileDataSubscriptionId =
|
||||
MutableStateFlow(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
|
||||
override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
|
||||
|
||||
private val subIdFlows = mutableMapOf<Int, MutableStateFlow<MobileSubscriptionModel>>()
|
||||
override fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> {
|
||||
return subIdFlows[subId]
|
||||
?: MutableStateFlow(MobileSubscriptionModel()).also { subIdFlows[subId] = it }
|
||||
}
|
||||
|
||||
fun setSubscriptions(subs: List<SubscriptionInfo>) {
|
||||
_subscriptionsFlow.value = subs
|
||||
}
|
||||
|
||||
fun setActiveMobileDataSubscriptionId(subId: Int) {
|
||||
_activeMobileDataSubscriptionId.value = subId
|
||||
}
|
||||
|
||||
fun setMobileSubscriptionModel(model: MobileSubscriptionModel, subId: Int) {
|
||||
val subscription = subIdFlows[subId] ?: throw Exception("no flow exists for this subId yet")
|
||||
subscription.value = model
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.statusbar.pipeline.mobile.data.repository
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
/** Defaults to `true` */
|
||||
class FakeUserSetupRepository : UserSetupRepository {
|
||||
private val _isUserSetup: MutableStateFlow<Boolean> = MutableStateFlow(true)
|
||||
override val isUserSetupFlow: Flow<Boolean> = _isUserSetup
|
||||
|
||||
fun setUserSetup(setup: Boolean) {
|
||||
_isUserSetup.value = setup
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.statusbar.pipeline.mobile.data.repository
|
||||
|
||||
import android.telephony.CellSignalStrengthCdma
|
||||
import android.telephony.ServiceState
|
||||
import android.telephony.SignalStrength
|
||||
import android.telephony.SubscriptionInfo
|
||||
import android.telephony.SubscriptionManager
|
||||
import android.telephony.TelephonyCallback
|
||||
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
|
||||
import android.telephony.TelephonyCallback.CarrierNetworkListener
|
||||
import android.telephony.TelephonyCallback.DataActivityListener
|
||||
import android.telephony.TelephonyCallback.DataConnectionStateListener
|
||||
import android.telephony.TelephonyCallback.DisplayInfoListener
|
||||
import android.telephony.TelephonyCallback.ServiceStateListener
|
||||
import android.telephony.TelephonyCallback.SignalStrengthsListener
|
||||
import android.telephony.TelephonyDisplayInfo
|
||||
import android.telephony.TelephonyManager
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.argumentCaptor
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SmallTest
|
||||
class MobileSubscriptionRepositoryTest : SysuiTestCase() {
|
||||
private lateinit var underTest: MobileSubscriptionRepositoryImpl
|
||||
|
||||
@Mock private lateinit var subscriptionManager: SubscriptionManager
|
||||
@Mock private lateinit var telephonyManager: TelephonyManager
|
||||
private val scope = CoroutineScope(IMMEDIATE)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
underTest =
|
||||
MobileSubscriptionRepositoryImpl(
|
||||
subscriptionManager,
|
||||
telephonyManager,
|
||||
IMMEDIATE,
|
||||
scope,
|
||||
)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
scope.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSubscriptions_initiallyEmpty() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
assertThat(underTest.subscriptionsFlow.value).isEqualTo(listOf<SubscriptionInfo>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSubscriptions_listUpdates() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
var latest: List<SubscriptionInfo>? = null
|
||||
|
||||
val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
|
||||
|
||||
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
|
||||
.thenReturn(listOf(SUB_1, SUB_2))
|
||||
getSubscriptionCallback().onSubscriptionsChanged()
|
||||
|
||||
assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSubscriptions_removingSub_updatesList() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
var latest: List<SubscriptionInfo>? = null
|
||||
|
||||
val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
|
||||
|
||||
// WHEN 2 networks show up
|
||||
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
|
||||
.thenReturn(listOf(SUB_1, SUB_2))
|
||||
getSubscriptionCallback().onSubscriptionsChanged()
|
||||
|
||||
// WHEN one network is removed
|
||||
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
|
||||
.thenReturn(listOf(SUB_2))
|
||||
getSubscriptionCallback().onSubscriptionsChanged()
|
||||
|
||||
// THEN the subscriptions list represents the newest change
|
||||
assertThat(latest).isEqualTo(listOf(SUB_2))
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActiveDataSubscriptionId_initialValueIsInvalidId() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
assertThat(underTest.activeMobileDataSubscriptionId.value)
|
||||
.isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActiveDataSubscriptionId_updates() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
var active: Int? = null
|
||||
|
||||
val job = underTest.activeMobileDataSubscriptionId.onEach { active = it }.launchIn(this)
|
||||
|
||||
getActiveDataSubscriptionCallback().onActiveDataSubscriptionIdChanged(SUB_2_ID)
|
||||
|
||||
assertThat(active).isEqualTo(SUB_2_ID)
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFlowForSubId_default() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
|
||||
|
||||
var latest: MobileSubscriptionModel? = null
|
||||
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
|
||||
|
||||
assertThat(latest).isEqualTo(MobileSubscriptionModel())
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFlowForSubId_emergencyOnly() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
|
||||
|
||||
var latest: MobileSubscriptionModel? = null
|
||||
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
|
||||
|
||||
val serviceState = ServiceState()
|
||||
serviceState.isEmergencyOnly = true
|
||||
|
||||
getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
|
||||
|
||||
assertThat(latest?.isEmergencyOnly).isEqualTo(true)
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFlowForSubId_emergencyOnly_toggles() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
|
||||
|
||||
var latest: MobileSubscriptionModel? = null
|
||||
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
|
||||
|
||||
val callback = getTelephonyCallbackForType<ServiceStateListener>()
|
||||
val serviceState = ServiceState()
|
||||
serviceState.isEmergencyOnly = true
|
||||
callback.onServiceStateChanged(serviceState)
|
||||
serviceState.isEmergencyOnly = false
|
||||
callback.onServiceStateChanged(serviceState)
|
||||
|
||||
assertThat(latest?.isEmergencyOnly).isEqualTo(false)
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFlowForSubId_signalStrengths_levelsUpdate() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
|
||||
|
||||
var latest: MobileSubscriptionModel? = null
|
||||
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
|
||||
|
||||
val callback = getTelephonyCallbackForType<SignalStrengthsListener>()
|
||||
val strength = signalStrength(1, 2, true)
|
||||
callback.onSignalStrengthsChanged(strength)
|
||||
|
||||
assertThat(latest?.isGsm).isEqualTo(true)
|
||||
assertThat(latest?.primaryLevel).isEqualTo(1)
|
||||
assertThat(latest?.cdmaLevel).isEqualTo(2)
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFlowForSubId_dataConnectionState() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
|
||||
|
||||
var latest: MobileSubscriptionModel? = null
|
||||
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
|
||||
|
||||
val callback = getTelephonyCallbackForType<DataConnectionStateListener>()
|
||||
callback.onDataConnectionStateChanged(100, 200 /* unused */)
|
||||
|
||||
assertThat(latest?.dataConnectionState).isEqualTo(100)
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFlowForSubId_dataActivity() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
|
||||
|
||||
var latest: MobileSubscriptionModel? = null
|
||||
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
|
||||
|
||||
val callback = getTelephonyCallbackForType<DataActivityListener>()
|
||||
callback.onDataActivity(3)
|
||||
|
||||
assertThat(latest?.dataActivityDirection).isEqualTo(3)
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFlowForSubId_carrierNetworkChange() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
|
||||
|
||||
var latest: MobileSubscriptionModel? = null
|
||||
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
|
||||
|
||||
val callback = getTelephonyCallbackForType<CarrierNetworkListener>()
|
||||
callback.onCarrierNetworkChange(true)
|
||||
|
||||
assertThat(latest?.carrierNetworkChangeActive).isEqualTo(true)
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFlowForSubId_displayInfo() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
|
||||
|
||||
var latest: MobileSubscriptionModel? = null
|
||||
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
|
||||
|
||||
val callback = getTelephonyCallbackForType<DisplayInfoListener>()
|
||||
val ti = mock<TelephonyDisplayInfo>()
|
||||
callback.onDisplayInfoChanged(ti)
|
||||
|
||||
assertThat(latest?.displayInfo).isEqualTo(ti)
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFlowForSubId_isCached() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
|
||||
|
||||
val state1 = underTest.getFlowForSubId(SUB_1_ID)
|
||||
val state2 = underTest.getFlowForSubId(SUB_1_ID)
|
||||
|
||||
assertThat(state1).isEqualTo(state2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFlowForSubId_isRemovedAfterFinish() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
|
||||
|
||||
var latest: MobileSubscriptionModel? = null
|
||||
|
||||
// Start collecting on some flow
|
||||
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
|
||||
|
||||
// There should be once cached flow now
|
||||
assertThat(underTest.getSubIdFlowCache().size).isEqualTo(1)
|
||||
|
||||
// When the job is canceled, the cache should be cleared
|
||||
job.cancel()
|
||||
|
||||
assertThat(underTest.getSubIdFlowCache().size).isEqualTo(0)
|
||||
}
|
||||
|
||||
private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
|
||||
val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
|
||||
verify(subscriptionManager)
|
||||
.addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
|
||||
return callbackCaptor.value!!
|
||||
}
|
||||
|
||||
private fun getActiveDataSubscriptionCallback(): ActiveDataSubscriptionIdListener =
|
||||
getTelephonyCallbackForType()
|
||||
|
||||
private fun getTelephonyCallbacks(): List<TelephonyCallback> {
|
||||
val callbackCaptor = argumentCaptor<TelephonyCallback>()
|
||||
verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
|
||||
return callbackCaptor.allValues
|
||||
}
|
||||
|
||||
private inline fun <reified T> getTelephonyCallbackForType(): T {
|
||||
val cbs = getTelephonyCallbacks().filterIsInstance<T>()
|
||||
assertThat(cbs.size).isEqualTo(1)
|
||||
return cbs[0]
|
||||
}
|
||||
|
||||
/** Convenience constructor for SignalStrength */
|
||||
private fun signalStrength(gsmLevel: Int, cdmaLevel: Int, isGsm: Boolean): SignalStrength {
|
||||
val signalStrength = mock<SignalStrength>()
|
||||
whenever(signalStrength.isGsm).thenReturn(isGsm)
|
||||
whenever(signalStrength.level).thenReturn(gsmLevel)
|
||||
val cdmaStrength =
|
||||
mock<CellSignalStrengthCdma>().also { whenever(it.level).thenReturn(cdmaLevel) }
|
||||
whenever(signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java))
|
||||
.thenReturn(listOf(cdmaStrength))
|
||||
|
||||
return signalStrength
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||
private const val SUB_1_ID = 1
|
||||
private val SUB_1 =
|
||||
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
|
||||
|
||||
private const val SUB_2_ID = 2
|
||||
private val SUB_2 =
|
||||
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.statusbar.pipeline.mobile.data.repository
|
||||
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.statusbar.policy.DeviceProvisionedController
|
||||
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener
|
||||
import com.android.systemui.util.mockito.argumentCaptor
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
class UserSetupRepositoryTest : SysuiTestCase() {
|
||||
private lateinit var underTest: UserSetupRepository
|
||||
@Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
|
||||
private val scope = CoroutineScope(IMMEDIATE)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
underTest =
|
||||
UserSetupRepositoryImpl(
|
||||
deviceProvisionedController,
|
||||
IMMEDIATE,
|
||||
scope,
|
||||
)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
scope.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUserSetup_defaultFalse() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
var latest: Boolean? = null
|
||||
|
||||
val job = underTest.isUserSetupFlow.onEach { latest = it }.launchIn(this)
|
||||
|
||||
assertThat(latest).isFalse()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUserSetup_updatesOnChange() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
var latest: Boolean? = null
|
||||
|
||||
val job = underTest.isUserSetupFlow.onEach { latest = it }.launchIn(this)
|
||||
|
||||
whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
|
||||
val callback = getDeviceProvisionedListener()
|
||||
callback.onUserSetupChanged()
|
||||
|
||||
assertThat(latest).isTrue()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
private fun getDeviceProvisionedListener(): DeviceProvisionedListener {
|
||||
val captor = argumentCaptor<DeviceProvisionedListener>()
|
||||
verify(deviceProvisionedController).addCallback(captor.capture())
|
||||
return captor.value!!
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.statusbar.pipeline.mobile.domain.interactor
|
||||
|
||||
import android.telephony.CellSignalStrength
|
||||
import com.android.settingslib.SignalIcon
|
||||
import com.android.settingslib.mobile.TelephonyIcons
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
class FakeMobileIconInteractor : MobileIconInteractor {
|
||||
private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.UNKNOWN)
|
||||
override val iconGroup = _iconGroup
|
||||
|
||||
private val _isEmergencyOnly = MutableStateFlow<Boolean>(false)
|
||||
override val isEmergencyOnly = _isEmergencyOnly
|
||||
|
||||
private val _level = MutableStateFlow<Int>(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
|
||||
override val level = _level
|
||||
|
||||
private val _numberOfLevels = MutableStateFlow<Int>(4)
|
||||
override val numberOfLevels = _numberOfLevels
|
||||
|
||||
private val _cutOut = MutableStateFlow<Boolean>(false)
|
||||
override val cutOut = _cutOut
|
||||
|
||||
fun setIconGroup(group: SignalIcon.MobileIconGroup) {
|
||||
_iconGroup.value = group
|
||||
}
|
||||
|
||||
fun setIsEmergencyOnly(emergency: Boolean) {
|
||||
_isEmergencyOnly.value = emergency
|
||||
}
|
||||
|
||||
fun setLevel(level: Int) {
|
||||
_level.value = level
|
||||
}
|
||||
|
||||
fun setNumberOfLevels(num: Int) {
|
||||
_numberOfLevels.value = num
|
||||
}
|
||||
|
||||
fun setCutOut(cutOut: Boolean) {
|
||||
_cutOut.value = cutOut
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.statusbar.pipeline.mobile.domain.interactor
|
||||
|
||||
import android.telephony.CellSignalStrength
|
||||
import android.telephony.SubscriptionInfo
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
|
||||
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileSubscriptionRepository
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
@SmallTest
|
||||
class MobileIconInteractorTest : SysuiTestCase() {
|
||||
private lateinit var underTest: MobileIconInteractor
|
||||
private val mobileSubscriptionRepository = FakeMobileSubscriptionRepository()
|
||||
private val sub1Flow = mobileSubscriptionRepository.getFlowForSubId(SUB_1_ID)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
underTest = MobileIconInteractorImpl(sub1Flow)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun gsm_level_default_unknown() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
mobileSubscriptionRepository.setMobileSubscriptionModel(
|
||||
MobileSubscriptionModel(isGsm = true),
|
||||
SUB_1_ID
|
||||
)
|
||||
|
||||
var latest: Int? = null
|
||||
val job = underTest.level.onEach { latest = it }.launchIn(this)
|
||||
|
||||
assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun gsm_usesGsmLevel() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
mobileSubscriptionRepository.setMobileSubscriptionModel(
|
||||
MobileSubscriptionModel(
|
||||
isGsm = true,
|
||||
primaryLevel = GSM_LEVEL,
|
||||
cdmaLevel = CDMA_LEVEL
|
||||
),
|
||||
SUB_1_ID
|
||||
)
|
||||
|
||||
var latest: Int? = null
|
||||
val job = underTest.level.onEach { latest = it }.launchIn(this)
|
||||
|
||||
assertThat(latest).isEqualTo(GSM_LEVEL)
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cdma_level_default_unknown() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
mobileSubscriptionRepository.setMobileSubscriptionModel(
|
||||
MobileSubscriptionModel(isGsm = false),
|
||||
SUB_1_ID
|
||||
)
|
||||
|
||||
var latest: Int? = null
|
||||
val job = underTest.level.onEach { latest = it }.launchIn(this)
|
||||
|
||||
assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cdma_usesCdmaLevel() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
mobileSubscriptionRepository.setMobileSubscriptionModel(
|
||||
MobileSubscriptionModel(
|
||||
isGsm = false,
|
||||
primaryLevel = GSM_LEVEL,
|
||||
cdmaLevel = CDMA_LEVEL
|
||||
),
|
||||
SUB_1_ID
|
||||
)
|
||||
|
||||
var latest: Int? = null
|
||||
val job = underTest.level.onEach { latest = it }.launchIn(this)
|
||||
|
||||
assertThat(latest).isEqualTo(CDMA_LEVEL)
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||
|
||||
private const val GSM_LEVEL = 1
|
||||
private const val CDMA_LEVEL = 2
|
||||
|
||||
private const val SUB_1_ID = 1
|
||||
private val SUB_1 =
|
||||
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
|
||||
|
||||
private const val SUB_2_ID = 2
|
||||
private val SUB_2 =
|
||||
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.statusbar.pipeline.mobile.domain.interactor
|
||||
|
||||
import android.telephony.SubscriptionInfo
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileSubscriptionRepository
|
||||
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
|
||||
import com.android.systemui.util.CarrierConfigTracker
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
class MobileIconsInteractorTest : SysuiTestCase() {
|
||||
private lateinit var underTest: MobileIconsInteractor
|
||||
private val userSetupRepository = FakeUserSetupRepository()
|
||||
private val subscriptionsRepository = FakeMobileSubscriptionRepository()
|
||||
|
||||
@Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
underTest =
|
||||
MobileIconsInteractor(
|
||||
subscriptionsRepository,
|
||||
carrierConfigTracker,
|
||||
userSetupRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@After fun tearDown() {}
|
||||
|
||||
@Test
|
||||
fun filteredSubscriptions_default() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
var latest: List<SubscriptionInfo>? = null
|
||||
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
|
||||
|
||||
assertThat(latest).isEqualTo(listOf<SubscriptionInfo>())
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
|
||||
|
||||
var latest: List<SubscriptionInfo>? = null
|
||||
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
|
||||
|
||||
assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_3() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
subscriptionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
|
||||
subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
|
||||
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
|
||||
.thenReturn(false)
|
||||
|
||||
var latest: List<SubscriptionInfo>? = null
|
||||
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
|
||||
|
||||
// Filtered subscriptions should show the active one when the config is false
|
||||
assertThat(latest).isEqualTo(listOf(SUB_3_OPP))
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_4() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
subscriptionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
|
||||
subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
|
||||
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
|
||||
.thenReturn(false)
|
||||
|
||||
var latest: List<SubscriptionInfo>? = null
|
||||
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
|
||||
|
||||
// Filtered subscriptions should show the active one when the config is false
|
||||
assertThat(latest).isEqualTo(listOf(SUB_4_OPP))
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_active_1() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
|
||||
subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
|
||||
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
|
||||
.thenReturn(true)
|
||||
|
||||
var latest: List<SubscriptionInfo>? = null
|
||||
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
|
||||
|
||||
// Filtered subscriptions should show the primary (non-opportunistic) if the config is
|
||||
// true
|
||||
assertThat(latest).isEqualTo(listOf(SUB_1))
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_nonActive_1() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
|
||||
subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
|
||||
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
|
||||
.thenReturn(true)
|
||||
|
||||
var latest: List<SubscriptionInfo>? = null
|
||||
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
|
||||
|
||||
// Filtered subscriptions should show the primary (non-opportunistic) if the config is
|
||||
// true
|
||||
assertThat(latest).isEqualTo(listOf(SUB_1))
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||
|
||||
private const val SUB_1_ID = 1
|
||||
private val SUB_1 =
|
||||
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
|
||||
|
||||
private const val SUB_2_ID = 2
|
||||
private val SUB_2 =
|
||||
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
|
||||
|
||||
private const val SUB_3_ID = 3
|
||||
private val SUB_3_OPP =
|
||||
mock<SubscriptionInfo>().also {
|
||||
whenever(it.subscriptionId).thenReturn(SUB_3_ID)
|
||||
whenever(it.isOpportunistic).thenReturn(true)
|
||||
}
|
||||
|
||||
private const val SUB_4_ID = 4
|
||||
private val SUB_4_OPP =
|
||||
mock<SubscriptionInfo>().also {
|
||||
whenever(it.subscriptionId).thenReturn(SUB_4_ID)
|
||||
whenever(it.isOpportunistic).thenReturn(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.statusbar.pipeline.mobile.ui.viewmodel
|
||||
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.settingslib.graph.SignalDrawable
|
||||
import com.android.settingslib.mobile.TelephonyIcons
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
|
||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
class MobileIconViewModelTest : SysuiTestCase() {
|
||||
private lateinit var underTest: MobileIconViewModel
|
||||
private val interactor = FakeMobileIconInteractor()
|
||||
@Mock private lateinit var logger: ConnectivityPipelineLogger
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
interactor.apply {
|
||||
setLevel(1)
|
||||
setCutOut(false)
|
||||
setIconGroup(TelephonyIcons.THREE_G)
|
||||
setIsEmergencyOnly(false)
|
||||
setNumberOfLevels(4)
|
||||
}
|
||||
underTest = MobileIconViewModel(SUB_1_ID, interactor, logger)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun iconId_correctLevel_notCutout() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
var latest: Int? = null
|
||||
val job = underTest.iconId.onEach { latest = it }.launchIn(this)
|
||||
|
||||
assertThat(latest).isEqualTo(SignalDrawable.getState(1, 4, false))
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||
private const val SUB_1_ID = 1
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,10 @@ public class FakeStatusBarIconController extends BaseLeakChecker<IconManager>
|
||||
public void setMobileIcons(String slot, List<MobileIconState> states) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNewMobileIconSubIds(List<Integer> subIds) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states) {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user