Merge "First pass at setting up a mobile connectivity pipeline" into tm-qpr-dev am: deccb3d85d
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19748953 Change-Id: I9582b56172770c3a5d70fab943b931ebfa453160 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
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
|
<com.android.systemui.statusbar.StatusBarMobileView
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:systemui="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:id="@+id/mobile_combo"
|
android:id="@+id/mobile_combo"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:gravity="center_vertical" >
|
android:gravity="center_vertical" >
|
||||||
|
|
||||||
<com.android.keyguard.AlphaOptimizedLinearLayout
|
<include layout="@layout/status_bar_mobile_signal_group_inner" />
|
||||||
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>
|
|
||||||
</com.android.systemui.statusbar.StatusBarMobileView>
|
</com.android.systemui.statusbar.StatusBarMobileView>
|
||||||
|
|
||||||
|
|||||||
@@ -20,14 +20,16 @@ import android.util.AttributeSet
|
|||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A temporary base class that's shared between our old status bar wifi view implementation
|
* A temporary base class that's shared between our old status bar connectivity view implementations
|
||||||
* ([StatusBarWifiView]) and our new status bar wifi view implementation
|
* ([StatusBarWifiView], [StatusBarMobileView]) and our new status bar implementations (
|
||||||
* ([ModernStatusBarWifiView]).
|
* [ModernStatusBarWifiView], [ModernStatusBarMobileView]).
|
||||||
*
|
*
|
||||||
* Once our refactor is over, we should be able to delete this go-between class and the old view
|
* Once our refactor is over, we should be able to delete this go-between class and the old view
|
||||||
* class.
|
* class.
|
||||||
*/
|
*/
|
||||||
abstract class BaseStatusBarWifiView @JvmOverloads constructor(
|
abstract class BaseStatusBarFrameLayout
|
||||||
|
@JvmOverloads
|
||||||
|
constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttrs: Int = 0,
|
defStyleAttrs: Int = 0,
|
||||||
@@ -29,7 +29,6 @@ import android.util.AttributeSet;
|
|||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
@@ -43,7 +42,10 @@ import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconStat
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
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 {
|
StatusIconDisplayable {
|
||||||
private static final String TAG = "StatusBarMobileView";
|
private static final String TAG = "StatusBarMobileView";
|
||||||
|
|
||||||
@@ -101,11 +103,6 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
|
|||||||
super(context, attrs, defStyleAttr);
|
super(context, attrs, defStyleAttr);
|
||||||
}
|
}
|
||||||
|
|
||||||
public StatusBarMobileView(Context context, AttributeSet attrs, int defStyleAttr,
|
|
||||||
int defStyleRes) {
|
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void getDrawingRect(Rect outRect) {
|
public void getDrawingRect(Rect outRect) {
|
||||||
super.getDrawingRect(outRect);
|
super.getDrawingRect(outRect);
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import java.util.ArrayList;
|
|||||||
/**
|
/**
|
||||||
* Start small: StatusBarWifiView will be able to layout from a WifiIconState
|
* 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";
|
private static final String TAG = "StatusBarWifiView";
|
||||||
|
|
||||||
/// Used to show etc dots
|
/// 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_ICON;
|
||||||
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
|
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 static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
|
||||||
|
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
@@ -38,7 +39,7 @@ import com.android.systemui.dagger.SysUISingleton;
|
|||||||
import com.android.systemui.demomode.DemoModeCommandReceiver;
|
import com.android.systemui.demomode.DemoModeCommandReceiver;
|
||||||
import com.android.systemui.plugins.DarkIconDispatcher;
|
import com.android.systemui.plugins.DarkIconDispatcher;
|
||||||
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
|
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.StatusBarIconView;
|
||||||
import com.android.systemui.statusbar.StatusBarMobileView;
|
import com.android.systemui.statusbar.StatusBarMobileView;
|
||||||
import com.android.systemui.statusbar.StatusBarWifiView;
|
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.MobileIconState;
|
||||||
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
|
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
|
||||||
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
|
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.view.ModernStatusBarWifiView;
|
||||||
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
|
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
|
||||||
import com.android.systemui.util.Assert;
|
import com.android.systemui.util.Assert;
|
||||||
@@ -83,6 +88,12 @@ public interface StatusBarIconController {
|
|||||||
/** */
|
/** */
|
||||||
void setMobileIcons(String slot, List<MobileIconState> states);
|
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.
|
* Display the no calling & SMS icons.
|
||||||
*/
|
*/
|
||||||
@@ -141,12 +152,14 @@ public interface StatusBarIconController {
|
|||||||
StatusBarLocation location,
|
StatusBarLocation location,
|
||||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||||
WifiViewModel wifiViewModel,
|
WifiViewModel wifiViewModel,
|
||||||
|
MobileUiAdapter mobileUiAdapter,
|
||||||
MobileContextProvider mobileContextProvider,
|
MobileContextProvider mobileContextProvider,
|
||||||
DarkIconDispatcher darkIconDispatcher) {
|
DarkIconDispatcher darkIconDispatcher) {
|
||||||
super(linearLayout,
|
super(linearLayout,
|
||||||
location,
|
location,
|
||||||
statusBarPipelineFlags,
|
statusBarPipelineFlags,
|
||||||
wifiViewModel,
|
wifiViewModel,
|
||||||
|
mobileUiAdapter,
|
||||||
mobileContextProvider);
|
mobileContextProvider);
|
||||||
mIconHPadding = mContext.getResources().getDimensionPixelSize(
|
mIconHPadding = mContext.getResources().getDimensionPixelSize(
|
||||||
R.dimen.status_bar_icon_padding);
|
R.dimen.status_bar_icon_padding);
|
||||||
@@ -207,6 +220,7 @@ public interface StatusBarIconController {
|
|||||||
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
|
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
|
||||||
private final WifiViewModel mWifiViewModel;
|
private final WifiViewModel mWifiViewModel;
|
||||||
private final MobileContextProvider mMobileContextProvider;
|
private final MobileContextProvider mMobileContextProvider;
|
||||||
|
private final MobileUiAdapter mMobileUiAdapter;
|
||||||
private final DarkIconDispatcher mDarkIconDispatcher;
|
private final DarkIconDispatcher mDarkIconDispatcher;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -214,10 +228,12 @@ public interface StatusBarIconController {
|
|||||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||||
WifiViewModel wifiViewModel,
|
WifiViewModel wifiViewModel,
|
||||||
MobileContextProvider mobileContextProvider,
|
MobileContextProvider mobileContextProvider,
|
||||||
|
MobileUiAdapter mobileUiAdapter,
|
||||||
DarkIconDispatcher darkIconDispatcher) {
|
DarkIconDispatcher darkIconDispatcher) {
|
||||||
mStatusBarPipelineFlags = statusBarPipelineFlags;
|
mStatusBarPipelineFlags = statusBarPipelineFlags;
|
||||||
mWifiViewModel = wifiViewModel;
|
mWifiViewModel = wifiViewModel;
|
||||||
mMobileContextProvider = mobileContextProvider;
|
mMobileContextProvider = mobileContextProvider;
|
||||||
|
mMobileUiAdapter = mobileUiAdapter;
|
||||||
mDarkIconDispatcher = darkIconDispatcher;
|
mDarkIconDispatcher = darkIconDispatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,6 +243,7 @@ public interface StatusBarIconController {
|
|||||||
location,
|
location,
|
||||||
mStatusBarPipelineFlags,
|
mStatusBarPipelineFlags,
|
||||||
mWifiViewModel,
|
mWifiViewModel,
|
||||||
|
mMobileUiAdapter,
|
||||||
mMobileContextProvider,
|
mMobileContextProvider,
|
||||||
mDarkIconDispatcher);
|
mDarkIconDispatcher);
|
||||||
}
|
}
|
||||||
@@ -244,11 +261,14 @@ public interface StatusBarIconController {
|
|||||||
StatusBarLocation location,
|
StatusBarLocation location,
|
||||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||||
WifiViewModel wifiViewModel,
|
WifiViewModel wifiViewModel,
|
||||||
MobileContextProvider mobileContextProvider) {
|
MobileUiAdapter mobileUiAdapter,
|
||||||
|
MobileContextProvider mobileContextProvider
|
||||||
|
) {
|
||||||
super(group,
|
super(group,
|
||||||
location,
|
location,
|
||||||
statusBarPipelineFlags,
|
statusBarPipelineFlags,
|
||||||
wifiViewModel,
|
wifiViewModel,
|
||||||
|
mobileUiAdapter,
|
||||||
mobileContextProvider);
|
mobileContextProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,14 +304,18 @@ public interface StatusBarIconController {
|
|||||||
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
|
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
|
||||||
private final WifiViewModel mWifiViewModel;
|
private final WifiViewModel mWifiViewModel;
|
||||||
private final MobileContextProvider mMobileContextProvider;
|
private final MobileContextProvider mMobileContextProvider;
|
||||||
|
private final MobileUiAdapter mMobileUiAdapter;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public Factory(
|
public Factory(
|
||||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||||
WifiViewModel wifiViewModel,
|
WifiViewModel wifiViewModel,
|
||||||
MobileContextProvider mobileContextProvider) {
|
MobileUiAdapter mobileUiAdapter,
|
||||||
|
MobileContextProvider mobileContextProvider
|
||||||
|
) {
|
||||||
mStatusBarPipelineFlags = statusBarPipelineFlags;
|
mStatusBarPipelineFlags = statusBarPipelineFlags;
|
||||||
mWifiViewModel = wifiViewModel;
|
mWifiViewModel = wifiViewModel;
|
||||||
|
mMobileUiAdapter = mobileUiAdapter;
|
||||||
mMobileContextProvider = mobileContextProvider;
|
mMobileContextProvider = mobileContextProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,6 +325,7 @@ public interface StatusBarIconController {
|
|||||||
location,
|
location,
|
||||||
mStatusBarPipelineFlags,
|
mStatusBarPipelineFlags,
|
||||||
mWifiViewModel,
|
mWifiViewModel,
|
||||||
|
mMobileUiAdapter,
|
||||||
mMobileContextProvider);
|
mMobileContextProvider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,6 +340,8 @@ public interface StatusBarIconController {
|
|||||||
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
|
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
|
||||||
private final WifiViewModel mWifiViewModel;
|
private final WifiViewModel mWifiViewModel;
|
||||||
private final MobileContextProvider mMobileContextProvider;
|
private final MobileContextProvider mMobileContextProvider;
|
||||||
|
private final MobileIconsViewModel mMobileIconsViewModel;
|
||||||
|
|
||||||
protected final Context mContext;
|
protected final Context mContext;
|
||||||
protected final int mIconSize;
|
protected final int mIconSize;
|
||||||
// Whether or not these icons show up in dumpsys
|
// Whether or not these icons show up in dumpsys
|
||||||
@@ -333,7 +360,9 @@ public interface StatusBarIconController {
|
|||||||
StatusBarLocation location,
|
StatusBarLocation location,
|
||||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||||
WifiViewModel wifiViewModel,
|
WifiViewModel wifiViewModel,
|
||||||
MobileContextProvider mobileContextProvider) {
|
MobileUiAdapter mobileUiAdapter,
|
||||||
|
MobileContextProvider mobileContextProvider
|
||||||
|
) {
|
||||||
mGroup = group;
|
mGroup = group;
|
||||||
mLocation = location;
|
mLocation = location;
|
||||||
mStatusBarPipelineFlags = statusBarPipelineFlags;
|
mStatusBarPipelineFlags = statusBarPipelineFlags;
|
||||||
@@ -342,6 +371,14 @@ public interface StatusBarIconController {
|
|||||||
mContext = group.getContext();
|
mContext = group.getContext();
|
||||||
mIconSize = mContext.getResources().getDimensionPixelSize(
|
mIconSize = mContext.getResources().getDimensionPixelSize(
|
||||||
com.android.internal.R.dimen.status_bar_icon_size);
|
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() {
|
public boolean isDemoable() {
|
||||||
@@ -394,6 +431,9 @@ public interface StatusBarIconController {
|
|||||||
|
|
||||||
case TYPE_MOBILE:
|
case TYPE_MOBILE:
|
||||||
return addMobileIcon(index, slot, holder.getMobileState());
|
return addMobileIcon(index, slot, holder.getMobileState());
|
||||||
|
|
||||||
|
case TYPE_MOBILE_NEW:
|
||||||
|
return addNewMobileIcon(index, slot, holder.getTag());
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -410,7 +450,7 @@ public interface StatusBarIconController {
|
|||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
|
protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
|
||||||
final BaseStatusBarWifiView view;
|
final BaseStatusBarFrameLayout view;
|
||||||
if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
|
if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
|
||||||
view = onCreateModernStatusBarWifiView(slot);
|
view = onCreateModernStatusBarWifiView(slot);
|
||||||
// When [ModernStatusBarWifiView] is created, it will automatically apply the
|
// When [ModernStatusBarWifiView] is created, it will automatically apply the
|
||||||
@@ -429,17 +469,47 @@ public interface StatusBarIconController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@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
|
// Use the `subId` field as a key to query for the correct context
|
||||||
StatusBarMobileView view = onCreateStatusBarMobileView(state.subId, slot);
|
StatusBarMobileView mobileView = onCreateStatusBarMobileView(state.subId, slot);
|
||||||
view.applyMobileState(state);
|
mobileView.applyMobileState(state);
|
||||||
mGroup.addView(view, index, onCreateLayoutParams());
|
mGroup.addView(mobileView, index, onCreateLayoutParams());
|
||||||
|
|
||||||
if (mIsInDemoMode) {
|
if (mIsInDemoMode) {
|
||||||
Context mobileContext = mMobileContextProvider
|
Context mobileContext = mMobileContextProvider
|
||||||
.getMobileContextForSub(state.subId, mContext);
|
.getMobileContextForSub(state.subId, mContext);
|
||||||
mDemoStatusIcons.addMobileView(state, mobileContext);
|
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;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,6 +534,15 @@ public interface StatusBarIconController {
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ModernStatusBarMobileView onCreateModernStatusBarMobileView(
|
||||||
|
String slot, int subId) {
|
||||||
|
return ModernStatusBarMobileView
|
||||||
|
.constructAndBind(
|
||||||
|
mContext,
|
||||||
|
slot,
|
||||||
|
mMobileIconsViewModel.viewModelForSub(subId));
|
||||||
|
}
|
||||||
|
|
||||||
protected LinearLayout.LayoutParams onCreateLayoutParams() {
|
protected LinearLayout.LayoutParams onCreateLayoutParams() {
|
||||||
return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
|
return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
|
||||||
}
|
}
|
||||||
@@ -519,6 +598,10 @@ public interface StatusBarIconController {
|
|||||||
return;
|
return;
|
||||||
case TYPE_MOBILE:
|
case TYPE_MOBILE:
|
||||||
onSetMobileIcon(viewIndex, holder.getMobileState());
|
onSetMobileIcon(viewIndex, holder.getMobileState());
|
||||||
|
return;
|
||||||
|
case TYPE_MOBILE_NEW:
|
||||||
|
// Nothing, the icon updates itself now
|
||||||
|
return;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -542,9 +625,13 @@ public interface StatusBarIconController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void onSetMobileIcon(int viewIndex, MobileIconState state) {
|
public void onSetMobileIcon(int viewIndex, MobileIconState state) {
|
||||||
StatusBarMobileView view = (StatusBarMobileView) mGroup.getChildAt(viewIndex);
|
View view = mGroup.getChildAt(viewIndex);
|
||||||
if (view != null) {
|
if (view instanceof StatusBarMobileView) {
|
||||||
view.applyMobileState(state);
|
((StatusBarMobileView) view).applyMobileState(state);
|
||||||
|
} else {
|
||||||
|
// ModernStatusBarMobileView automatically updates via the ViewModel
|
||||||
|
throw new IllegalStateException("Cannot update ModernStatusBarMobileView outside of"
|
||||||
|
+ "the new pipeline");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mIsInDemoMode) {
|
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.CallIndicatorIconState;
|
||||||
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
|
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
|
||||||
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
|
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;
|
||||||
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
|
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
|
||||||
import com.android.systemui.tuner.TunerService;
|
import com.android.systemui.tuner.TunerService;
|
||||||
@@ -66,8 +67,8 @@ public class StatusBarIconControllerImpl implements Tunable,
|
|||||||
private final StatusBarIconList mStatusBarIconList;
|
private final StatusBarIconList mStatusBarIconList;
|
||||||
private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
|
private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
|
||||||
private final ArraySet<String> mIconHideList = new ArraySet<>();
|
private final ArraySet<String> mIconHideList = new ArraySet<>();
|
||||||
|
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
|
||||||
private Context mContext;
|
private final Context mContext;
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
@Inject
|
@Inject
|
||||||
@@ -78,9 +79,12 @@ public class StatusBarIconControllerImpl implements Tunable,
|
|||||||
ConfigurationController configurationController,
|
ConfigurationController configurationController,
|
||||||
TunerService tunerService,
|
TunerService tunerService,
|
||||||
DumpManager dumpManager,
|
DumpManager dumpManager,
|
||||||
StatusBarIconList statusBarIconList) {
|
StatusBarIconList statusBarIconList,
|
||||||
|
StatusBarPipelineFlags statusBarPipelineFlags
|
||||||
|
) {
|
||||||
mStatusBarIconList = statusBarIconList;
|
mStatusBarIconList = statusBarIconList;
|
||||||
mContext = context;
|
mContext = context;
|
||||||
|
mStatusBarPipelineFlags = statusBarPipelineFlags;
|
||||||
|
|
||||||
configurationController.addCallback(this);
|
configurationController.addCallback(this);
|
||||||
commandQueue.addCallback(this);
|
commandQueue.addCallback(this);
|
||||||
@@ -220,6 +224,11 @@ public class StatusBarIconControllerImpl implements Tunable,
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setMobileIcons(String slot, List<MobileIconState> iconStates) {
|
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);
|
Slot mobileSlot = mStatusBarIconList.getSlot(slot);
|
||||||
|
|
||||||
// Reverse the sort order to show icons with left to right([Slot1][Slot2]..).
|
// 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);
|
Collections.reverse(iconStates);
|
||||||
|
|
||||||
for (MobileIconState state : iconStates) {
|
for (MobileIconState state : iconStates) {
|
||||||
|
|
||||||
StatusBarIconHolder holder = mobileSlot.getHolderForTag(state.subId);
|
StatusBarIconHolder holder = mobileSlot.getHolderForTag(state.subId);
|
||||||
if (holder == null) {
|
if (holder == null) {
|
||||||
holder = StatusBarIconHolder.fromMobileIconState(state);
|
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.
|
* Accept a list of CallIndicatorIconStates, and show the call strength icons.
|
||||||
* @param slot statusbar slot for 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) {
|
private void handleSet(String slotName, StatusBarIconHolder holder) {
|
||||||
int viewIndex = mStatusBarIconList.getViewIndex(slotName, holder.getTag());
|
int viewIndex = mStatusBarIconList.getViewIndex(slotName, holder.getTag());
|
||||||
mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
|
mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.android.systemui.statusbar.phone;
|
package com.android.systemui.statusbar.phone;
|
||||||
|
|
||||||
|
import android.annotation.IntDef;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.drawable.Icon;
|
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.CallIndicatorIconState;
|
||||||
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
|
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
|
||||||
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
|
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
|
* 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_ICON = 0;
|
||||||
public static final int TYPE_WIFI = 1;
|
public static final int TYPE_WIFI = 1;
|
||||||
public static final int TYPE_MOBILE = 2;
|
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 StatusBarIcon mIcon;
|
||||||
private WifiIconState mWifiState;
|
private WifiIconState mWifiState;
|
||||||
private MobileIconState mMobileState;
|
private MobileIconState mMobileState;
|
||||||
private int mType = TYPE_ICON;
|
private @IconType int mType = TYPE_ICON;
|
||||||
private int mTag = 0;
|
private int mTag = 0;
|
||||||
|
|
||||||
private StatusBarIconHolder() {
|
private StatusBarIconHolder() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static StatusBarIconHolder fromIcon(StatusBarIcon icon) {
|
public static StatusBarIconHolder fromIcon(StatusBarIcon icon) {
|
||||||
@@ -79,6 +104,18 @@ public class StatusBarIconHolder {
|
|||||||
return holder;
|
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.
|
* Creates a new StatusBarIconHolder from a CallIndicatorIconState.
|
||||||
*/
|
*/
|
||||||
@@ -95,7 +132,7 @@ public class StatusBarIconHolder {
|
|||||||
return holder;
|
return holder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getType() {
|
public @IconType int getType() {
|
||||||
return mType;
|
return mType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,8 +171,12 @@ public class StatusBarIconHolder {
|
|||||||
return mWifiState.visible;
|
return mWifiState.visible;
|
||||||
case TYPE_MOBILE:
|
case TYPE_MOBILE:
|
||||||
return mMobileState.visible;
|
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:
|
case TYPE_MOBILE:
|
||||||
mMobileState.visible = visible;
|
mMobileState.visible = visible;
|
||||||
break;
|
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
|
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.ConnectivityRepository
|
||||||
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
|
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
|
||||||
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
|
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
|
||||||
@@ -30,4 +34,12 @@ abstract class StatusBarPipelineModule {
|
|||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
|
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.Gravity
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import com.android.systemui.R
|
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
|
||||||
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
|
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
|
||||||
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
|
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
|
||||||
@@ -37,7 +37,7 @@ import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
|
|||||||
class ModernStatusBarWifiView(
|
class ModernStatusBarWifiView(
|
||||||
context: Context,
|
context: Context,
|
||||||
attrs: AttributeSet?
|
attrs: AttributeSet?
|
||||||
) : BaseStatusBarWifiView(context, attrs) {
|
) : BaseStatusBarFrameLayout(context, attrs) {
|
||||||
|
|
||||||
private lateinit var slot: String
|
private lateinit var slot: String
|
||||||
private lateinit var binding: WifiViewBinder.Binding
|
private lateinit var binding: WifiViewBinder.Binding
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ public class CarrierConfigTracker
|
|||||||
private boolean mDefaultCarrierProvisionsWifiMergedNetworks;
|
private boolean mDefaultCarrierProvisionsWifiMergedNetworks;
|
||||||
private boolean mDefaultShowOperatorNameConfigLoaded;
|
private boolean mDefaultShowOperatorNameConfigLoaded;
|
||||||
private boolean mDefaultShowOperatorNameConfig;
|
private boolean mDefaultShowOperatorNameConfig;
|
||||||
|
private boolean mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfigLoaded;
|
||||||
|
private boolean mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfig;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public CarrierConfigTracker(
|
public CarrierConfigTracker(
|
||||||
@@ -206,6 +208,22 @@ public class CarrierConfigTracker
|
|||||||
return mDefaultShowOperatorNameConfig;
|
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
|
* Returns the KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL value for the given subId, or the
|
||||||
* default value if no override exists
|
* 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.MobileIconState;
|
||||||
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
|
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
|
||||||
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
|
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.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
|
||||||
import com.android.systemui.utils.leaks.LeakCheckedTest;
|
import com.android.systemui.utils.leaks.LeakCheckedTest;
|
||||||
|
|
||||||
@@ -80,6 +81,7 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
|
|||||||
StatusBarLocation.HOME,
|
StatusBarLocation.HOME,
|
||||||
mock(StatusBarPipelineFlags.class),
|
mock(StatusBarPipelineFlags.class),
|
||||||
mock(WifiViewModel.class),
|
mock(WifiViewModel.class),
|
||||||
|
mock(MobileUiAdapter.class),
|
||||||
mMobileContextProvider,
|
mMobileContextProvider,
|
||||||
mock(DarkIconDispatcher.class));
|
mock(DarkIconDispatcher.class));
|
||||||
testCallOnAdd_forManager(manager);
|
testCallOnAdd_forManager(manager);
|
||||||
@@ -123,12 +125,14 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
|
|||||||
StatusBarLocation location,
|
StatusBarLocation location,
|
||||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||||
WifiViewModel wifiViewModel,
|
WifiViewModel wifiViewModel,
|
||||||
|
MobileUiAdapter mobileUiAdapter,
|
||||||
MobileContextProvider contextProvider,
|
MobileContextProvider contextProvider,
|
||||||
DarkIconDispatcher darkIconDispatcher) {
|
DarkIconDispatcher darkIconDispatcher) {
|
||||||
super(group,
|
super(group,
|
||||||
location,
|
location,
|
||||||
statusBarPipelineFlags,
|
statusBarPipelineFlags,
|
||||||
wifiViewModel,
|
wifiViewModel,
|
||||||
|
mobileUiAdapter,
|
||||||
contextProvider,
|
contextProvider,
|
||||||
darkIconDispatcher);
|
darkIconDispatcher);
|
||||||
}
|
}
|
||||||
@@ -169,6 +173,7 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
|
|||||||
StatusBarLocation.HOME,
|
StatusBarLocation.HOME,
|
||||||
mock(StatusBarPipelineFlags.class),
|
mock(StatusBarPipelineFlags.class),
|
||||||
mock(WifiViewModel.class),
|
mock(WifiViewModel.class),
|
||||||
|
mock(MobileUiAdapter.class),
|
||||||
contextProvider);
|
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) {
|
public void setMobileIcons(String slot, List<MobileIconState> states) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNewMobileIconSubIds(List<Integer> subIds) {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states) {
|
public void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states) {
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user