Add a status bar to dream overlay.

This change adds a status bar (with battery and wifi icons) to dream overlays.

Test: atest DreamOverlayStatusBarViewControllerTest, atest DreamOverlayServiceTest
Bug: 207681288
Change-Id: I32b1fc452364fb7d072bb2d84fd205e31909b642
This commit is contained in:
Will
2021-12-05 14:27:13 -08:00
parent 48845b2b7e
commit cebb50f61e
12 changed files with 839 additions and 40 deletions

View File

@@ -0,0 +1,24 @@
<!--
~ Copyright (C) 2021 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M18.575,15.2L7.55,4.175q1.1,-0.325 2.212,-0.462 1.113,-0.138 2.238,-0.138 3.45,0 6.55,1.45Q21.65,6.475 24,9zM20.225,23.575l-4.75,-4.75 -3.475,4.1L0,9q0.675,-0.725 1.438,-1.4 0.762,-0.675 1.612,-1.225l-2.625,-2.6L2.1,2.1l19.8,19.8z"/>
</vector>

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2021 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.dreams.DreamOverlayContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/dream_overlay_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/dream_overlay_content"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<com.android.systemui.dreams.DreamOverlayStatusBarView
android:id="@+id/dream_overlay_status_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/dream_overlay_status_bar_height"
android:layout_marginEnd="@dimen/dream_overlay_status_bar_margin"
android:layout_marginStart="@dimen/dream_overlay_status_bar_margin"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/dream_overlay_system_status"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:layout_constraintEnd_toEndOf="parent">
<com.android.systemui.statusbar.AlphaOptimizedImageView
android:id="@+id/dream_overlay_wifi_status"
android:layout_width="@dimen/status_bar_wifi_signal_size"
android:layout_height="match_parent"
android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@id/dream_overlay_battery" />
<com.android.systemui.battery.BatteryMeterView
android:id="@+id/dream_overlay_battery"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.android.systemui.dreams.DreamOverlayStatusBarView>
</com.android.systemui.dreams.DreamOverlayContainerView>

View File

@@ -1303,4 +1303,9 @@
<dimen name="keyguard_unfold_translation_x">16dp</dimen>
<dimen name="fgs_manager_min_width_minor">100%</dimen>
<!-- Dream overlay related dimensions -->
<dimen name="dream_overlay_status_bar_height">80dp</dimen>
<dimen name="dream_overlay_status_bar_margin">40dp</dimen>
<dimen name="dream_overlay_status_icon_margin">8dp</dimen>
</resources>

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2021 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.dreams;
import android.content.Context;
import android.util.AttributeSet;
import androidx.constraintlayout.widget.ConstraintLayout;
/**
* {@link DreamOverlayContainerView} contains a dream overlay and its status bar.
*/
public class DreamOverlayContainerView extends ConstraintLayout {
public DreamOverlayContainerView(Context context) {
this(context, null);
}
public DreamOverlayContainerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DreamOverlayContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public DreamOverlayContainerView(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
}

View File

@@ -29,11 +29,11 @@ import android.view.WindowInsets;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.PhoneWindow;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import java.util.concurrent.Executor;
@@ -54,10 +54,12 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
private final Executor mExecutor;
// The state controller informs the service of updates to the overlays present.
private final DreamOverlayStateController mStateController;
// The component used to resolve dream overlay dependencies.
private final DreamOverlayComponent mDreamOverlayComponent;
// The window is populated once the dream informs the service it has begun dreaming.
private Window mWindow;
private ConstraintLayout mLayout;
// The dream overlay's content view, which is located below the status bar (in z-order) and is
// the space into which widgets are placed.
private ViewGroup mDreamOverlayContentView;
private final DreamOverlayStateController.Callback mOverlayStateCallback =
new DreamOverlayStateController.Callback() {
@@ -90,12 +92,12 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
new ViewTreeObserver.OnComputeInternalInsetsListener() {
@Override
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
if (mLayout != null) {
if (mDreamOverlayContentView != null) {
inoutInfo.setTouchableInsets(
ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
final Region region = new Region();
for (int i = 0; i < mLayout.getChildCount(); i++) {
View child = mLayout.getChildAt(i);
for (int i = 0; i < mDreamOverlayContentView.getChildCount(); i++) {
View child = mDreamOverlayContentView.getChildAt(i);
final Rect rect = new Rect();
child.getGlobalVisibleRect(rect);
region.op(rect, Region.Op.UNION);
@@ -106,16 +108,30 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
}
};
@Inject
public DreamOverlayService(
Context context,
@Main Executor executor,
DreamOverlayStateController overlayStateController,
DreamOverlayComponent.Factory dreamOverlayComponentFactory) {
mContext = context;
mExecutor = executor;
mStateController = overlayStateController;
mDreamOverlayComponent = dreamOverlayComponentFactory.create();
mStateController.addCallback(mOverlayStateCallback);
}
@Override
public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
mExecutor.execute(() -> addOverlayWindowLocked(layoutParams));
}
private void reloadOverlaysLocked() {
if (mLayout == null) {
if (mDreamOverlayContentView == null) {
return;
}
mLayout.removeAllViews();
mDreamOverlayContentView.removeAllViews();
for (OverlayProvider overlayProvider : mStateController.getOverlays()) {
addOverlay(overlayProvider);
}
@@ -129,31 +145,30 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
* into the dream window.
*/
private void addOverlayWindowLocked(WindowManager.LayoutParams layoutParams) {
mWindow = new PhoneWindow(mContext);
mWindow.setAttributes(layoutParams);
mWindow.setWindowManager(null, layoutParams.token, "DreamOverlay", true);
final PhoneWindow window = new PhoneWindow(mContext);
window.setAttributes(layoutParams);
window.setWindowManager(null, layoutParams.token, "DreamOverlay", true);
mWindow.setBackgroundDrawable(new ColorDrawable(0));
window.setBackgroundDrawable(new ColorDrawable(0));
mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
window.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
window.requestFeature(Window.FEATURE_NO_TITLE);
// Hide all insets when the dream is showing
mWindow.getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars());
mWindow.setDecorFitsSystemWindows(false);
window.getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars());
window.setDecorFitsSystemWindows(false);
if (DEBUG) {
Log.d(TAG, "adding overlay window to dream");
}
mLayout = new ConstraintLayout(mContext);
mLayout.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
mLayout.addOnAttachStateChangeListener(mRootViewAttachListener);
mWindow.setContentView(mLayout);
window.setContentView(mDreamOverlayComponent.getDreamOverlayContainerView());
mDreamOverlayContentView = mDreamOverlayComponent.getDreamOverlayContentView();
mDreamOverlayComponent.getDreamOverlayStatusBarViewController().init();
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
windowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
windowManager.addView(window.getDecorView(), window.getAttributes());
mExecutor.execute(this::reloadOverlaysLocked);
}
@@ -163,11 +178,11 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
(view, layoutParams) -> {
// Always move UI related work to the main thread.
mExecutor.execute(() -> {
if (mLayout == null) {
if (mDreamOverlayContentView == null) {
return;
}
mLayout.addView(view, layoutParams);
mDreamOverlayContentView.addView(view, layoutParams);
});
},
() -> {
@@ -178,15 +193,6 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
});
}
@Inject
public DreamOverlayService(Context context, @Main Executor executor,
DreamOverlayStateController overlayStateController) {
mContext = context;
mExecutor = executor;
mStateController = overlayStateController;
mStateController.addCallback(mOverlayStateCallback);
}
@Override
public void onDestroy() {
mStateController.removeCallback(mOverlayStateCallback);

View File

@@ -0,0 +1,86 @@
/*
* Copyright (C) 2021 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.dreams;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.internal.util.Preconditions;
import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
/**
* {@link DreamOverlayStatusBarView} is the view responsible for displaying the status bar in a
* dream. The status bar includes status icons such as battery and wifi.
*/
public class DreamOverlayStatusBarView extends ConstraintLayout implements
BatteryStateChangeCallback {
private BatteryMeterView mBatteryView;
private ImageView mWifiStatusView;
public DreamOverlayStatusBarView(Context context) {
this(context, null);
}
public DreamOverlayStatusBarView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DreamOverlayStatusBarView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public DreamOverlayStatusBarView(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mBatteryView = Preconditions.checkNotNull(findViewById(R.id.dream_overlay_battery),
"R.id.dream_overlay_battery must not be null");
mWifiStatusView = Preconditions.checkNotNull(findViewById(R.id.dream_overlay_wifi_status),
"R.id.dream_overlay_wifi_status must not be null");
mWifiStatusView.setImageDrawable(getContext().getDrawable(R.drawable.ic_signal_wifi_off));
}
/**
* Whether to show the battery percent text next to the battery status icons.
* @param show True if the battery percent text should be shown.
*/
void showBatteryPercentText(boolean show) {
mBatteryView.setForceShowPercent(show);
}
/**
* Whether to show the wifi status icon.
* @param show True if the wifi status icon should be shown.
*/
void showWifiStatus(boolean show) {
// Only show the wifi status icon when wifi isn't available.
mWifiStatusView.setVisibility(show ? View.VISIBLE : View.GONE);
}
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright (C) 2021 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.dreams;
import android.annotation.IntDef;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import com.android.systemui.battery.BatteryMeterViewController;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.dreams.dagger.DreamOverlayModule;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.ViewController;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.inject.Inject;
import javax.inject.Named;
/**
* View controller for {@link DreamOverlayStatusBarView}.
*/
@DreamOverlayComponent.DreamOverlayScope
public class DreamOverlayStatusBarViewController extends ViewController<DreamOverlayStatusBarView> {
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "WIFI_STATUS_" }, value = {
WIFI_STATUS_UNKNOWN,
WIFI_STATUS_UNAVAILABLE,
WIFI_STATUS_AVAILABLE
})
private @interface WifiStatus {}
private static final int WIFI_STATUS_UNKNOWN = 0;
private static final int WIFI_STATUS_UNAVAILABLE = 1;
private static final int WIFI_STATUS_AVAILABLE = 2;
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "BATTERY_STATUS_" }, value = {
BATTERY_STATUS_UNKNOWN,
BATTERY_STATUS_NOT_CHARGING,
BATTERY_STATUS_CHARGING
})
private @interface BatteryStatus {}
private static final int BATTERY_STATUS_UNKNOWN = 0;
private static final int BATTERY_STATUS_NOT_CHARGING = 1;
private static final int BATTERY_STATUS_CHARGING = 2;
private final BatteryController mBatteryController;
private final BatteryMeterViewController mBatteryMeterViewController;
private final ConnectivityManager mConnectivityManager;
private final boolean mShowPercentAvailable;
private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
.clearCapabilities()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
private final NetworkCallback mNetworkCallback = new NetworkCallback() {
@Override
public void onCapabilitiesChanged(
Network network, NetworkCapabilities networkCapabilities) {
onWifiAvailabilityChanged(
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI));
}
@Override
public void onAvailable(Network network) {
onWifiAvailabilityChanged(true);
}
@Override
public void onLost(Network network) {
onWifiAvailabilityChanged(false);
}
};
private final BatteryController.BatteryStateChangeCallback mBatteryStateChangeCallback =
new BatteryController.BatteryStateChangeCallback() {
@Override
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
DreamOverlayStatusBarViewController.this.onBatteryLevelChanged(charging);
}
};
private @WifiStatus int mWifiStatus = WIFI_STATUS_UNKNOWN;
private @BatteryStatus int mBatteryStatus = BATTERY_STATUS_UNKNOWN;
@Inject
public DreamOverlayStatusBarViewController(
Context context,
DreamOverlayStatusBarView view,
BatteryController batteryController,
@Named(DreamOverlayModule.DREAM_OVERLAY_BATTERY_CONTROLLER)
BatteryMeterViewController batteryMeterViewController,
ConnectivityManager connectivityManager) {
super(view);
mBatteryController = batteryController;
mBatteryMeterViewController = batteryMeterViewController;
mConnectivityManager = connectivityManager;
mShowPercentAvailable = context.getResources().getBoolean(
com.android.internal.R.bool.config_battery_percentage_setting_available);
}
@Override
protected void onInit() {
super.onInit();
mBatteryMeterViewController.init();
}
@Override
protected void onViewAttached() {
mBatteryController.addCallback(mBatteryStateChangeCallback);
mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
NetworkCapabilities capabilities =
mConnectivityManager.getNetworkCapabilities(
mConnectivityManager.getActiveNetwork());
onWifiAvailabilityChanged(
capabilities != null
&& capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI));
}
@Override
protected void onViewDetached() {
mBatteryController.removeCallback(mBatteryStateChangeCallback);
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
}
/**
* Wifi availability has changed. Update the wifi status icon as appropriate.
* @param available Whether wifi is available.
*/
private void onWifiAvailabilityChanged(boolean available) {
final int newWifiStatus = available ? WIFI_STATUS_AVAILABLE : WIFI_STATUS_UNAVAILABLE;
if (mWifiStatus != newWifiStatus) {
mWifiStatus = newWifiStatus;
mView.showWifiStatus(mWifiStatus == WIFI_STATUS_UNAVAILABLE);
}
}
/**
* The battery level has changed. Update the battery status icon as appropriate.
* @param charging Whether the battery is currently charging.
*/
private void onBatteryLevelChanged(boolean charging) {
final int newBatteryStatus =
charging ? BATTERY_STATUS_CHARGING : BATTERY_STATUS_NOT_CHARGING;
if (mBatteryStatus != newBatteryStatus) {
mBatteryStatus = newBatteryStatus;
mView.showBatteryPercentText(
mBatteryStatus == BATTERY_STATUS_CHARGING && mShowPercentAvailable);
}
}
}

View File

@@ -21,8 +21,6 @@ import dagger.Module;
/**
* Dagger Module providing Communal-related functionality.
*/
@Module(subcomponents = {
AppWidgetOverlayComponent.class,
})
@Module(subcomponents = {AppWidgetOverlayComponent.class, DreamOverlayComponent.class})
public interface DreamModule {
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2021 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.dreams.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import android.view.ViewGroup;
import com.android.systemui.dreams.DreamOverlayContainerView;
import com.android.systemui.dreams.DreamOverlayStatusBarViewController;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import javax.inject.Scope;
import dagger.Subcomponent;
/**
* Dagger subcomponent for {@link DreamOverlayModule}.
*/
@Subcomponent(modules = {DreamOverlayModule.class})
@DreamOverlayComponent.DreamOverlayScope
public interface DreamOverlayComponent {
/** Simple factory for {@link DreamOverlayComponent}. */
@Subcomponent.Factory
interface Factory {
DreamOverlayComponent create();
}
/** Scope annotation for singleton items within the {@link DreamOverlayComponent}. */
@Documented
@Retention(RUNTIME)
@Scope
@interface DreamOverlayScope {}
/** Builds a {@link DreamOverlayContainerView} */
@DreamOverlayScope
DreamOverlayContainerView getDreamOverlayContainerView();
/** Builds a content view for dream overlays */
@DreamOverlayScope
ViewGroup getDreamOverlayContentView();
/** Builds a {@link DreamOverlayStatusBarViewController}. */
@DreamOverlayScope
DreamOverlayStatusBarViewController getDreamOverlayStatusBarViewController();
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright (C) 2021 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.dreams.dagger;
import android.content.ContentResolver;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import com.android.internal.util.Preconditions;
import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.battery.BatteryMeterViewController;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.DreamOverlayContainerView;
import com.android.systemui.dreams.DreamOverlayStatusBarView;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tuner.TunerService;
import javax.inject.Named;
import dagger.Module;
import dagger.Provides;
/** Dagger module for {@link DreamOverlayComponent}. */
@Module
public abstract class DreamOverlayModule {
private static final String DREAM_OVERLAY_BATTERY_VIEW = "dream_overlay_battery_view";
public static final String DREAM_OVERLAY_BATTERY_CONTROLLER =
"dream_overlay_battery_controller";
/** */
@Provides
@DreamOverlayComponent.DreamOverlayScope
public static DreamOverlayContainerView providesDreamOverlayContainerView(
LayoutInflater layoutInflater) {
return Preconditions.checkNotNull((DreamOverlayContainerView)
layoutInflater.inflate(R.layout.dream_overlay_container, null),
"R.layout.dream_layout_container could not be properly inflated");
}
/** */
@Provides
@DreamOverlayComponent.DreamOverlayScope
public static ViewGroup providesDreamOverlayContentView(DreamOverlayContainerView view) {
return Preconditions.checkNotNull(view.findViewById(R.id.dream_overlay_content),
"R.id.dream_overlay_content must not be null");
}
/** */
@Provides
@DreamOverlayComponent.DreamOverlayScope
public static DreamOverlayStatusBarView providesDreamOverlayStatusBarView(
DreamOverlayContainerView view) {
return Preconditions.checkNotNull(view.findViewById(R.id.dream_overlay_status_bar),
"R.id.status_bar must not be null");
}
/** */
@Provides
@DreamOverlayComponent.DreamOverlayScope
@Named(DREAM_OVERLAY_BATTERY_VIEW)
static BatteryMeterView providesBatteryMeterView(DreamOverlayContainerView view) {
return Preconditions.checkNotNull(view.findViewById(R.id.dream_overlay_battery),
"R.id.battery must not be null");
}
/** */
@Provides
@DreamOverlayComponent.DreamOverlayScope
@Named(DREAM_OVERLAY_BATTERY_CONTROLLER)
static BatteryMeterViewController providesBatteryMeterViewController(
@Named(DREAM_OVERLAY_BATTERY_VIEW) BatteryMeterView batteryMeterView,
ConfigurationController configurationController,
TunerService tunerService,
BroadcastDispatcher broadcastDispatcher,
@Main Handler mainHandler,
ContentResolver contentResolver,
BatteryController batteryController) {
return new BatteryMeterViewController(
batteryMeterView,
configurationController,
tunerService,
broadcastDispatcher,
mainHandler,
contentResolver,
batteryController);
}
}

View File

@@ -37,6 +37,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.utils.leaks.LeakCheckedTest;
@@ -78,16 +79,41 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
@Mock
DreamOverlayStateController mDreamOverlayStateController;
@Mock
DreamOverlayComponent.Factory mDreamOverlayStatusBarViewComponentFactory;
@Mock
DreamOverlayComponent mDreamOverlayComponent;
@Mock
DreamOverlayStatusBarViewController mDreamOverlayStatusBarViewController;
@Mock
DreamOverlayContainerView mDreamOverlayContainerView;
@Mock
ViewGroup mDreamOverlayContentView;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mContext.addMockSystemService(WindowManager.class, mWindowManager);
when(mDreamOverlayComponent.getDreamOverlayContentView())
.thenReturn(mDreamOverlayContentView);
when(mDreamOverlayComponent.getDreamOverlayContainerView())
.thenReturn(mDreamOverlayContainerView);
when(mDreamOverlayComponent.getDreamOverlayStatusBarViewController())
.thenReturn(mDreamOverlayStatusBarViewController);
when(mDreamOverlayStatusBarViewComponentFactory.create())
.thenReturn(mDreamOverlayComponent);
}
@Test
public void testInteraction() throws Exception {
final DreamOverlayService service = new DreamOverlayService(mContext, mMainExecutor,
mDreamOverlayStateController);
mDreamOverlayStateController, mDreamOverlayStatusBarViewComponentFactory);
final IBinder proxy = service.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
clearInvocations(mWindowManager);
@@ -128,7 +154,7 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
@Test
public void testListening() throws Exception {
final DreamOverlayService service = new DreamOverlayService(mContext, mMainExecutor,
mDreamOverlayStateController);
mDreamOverlayStateController, mDreamOverlayStatusBarViewComponentFactory);
final IBinder proxy = service.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
@@ -150,4 +176,19 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
// Verify provider is asked to create overlay.
verify(mProvider).onCreateOverlay(any(), any(), any());
}
@Test
public void testDreamOverlayStatusBarViewControllerInitialized() throws Exception {
final DreamOverlayService service = new DreamOverlayService(mContext, mMainExecutor,
mDreamOverlayStateController, mDreamOverlayStatusBarViewComponentFactory);
final IBinder proxy = service.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
// Inform the overlay service of dream starting.
overlay.startDream(mWindowParams, mDreamOverlayCallback);
mMainExecutor.runAllReady();
verify(mDreamOverlayStatusBarViewController).init();
}
}

View File

@@ -0,0 +1,195 @@
/*
* Copyright (C) 2021 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.dreams;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.battery.BatteryMeterViewController;
import com.android.systemui.statusbar.policy.BatteryController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
@Mock
DreamOverlayStatusBarView mView;
@Mock
BatteryController mBatteryController;
@Mock
BatteryMeterViewController mBatteryMeterViewController;
@Mock
ConnectivityManager mConnectivityManager;
@Mock
NetworkCapabilities mNetworkCapabilities;
@Mock
Network mNetwork;
DreamOverlayStatusBarViewController mController;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mController = new DreamOverlayStatusBarViewController(
mContext, mView, mBatteryController, mBatteryMeterViewController,
mConnectivityManager);
}
@Test
public void testOnInitInitializesControllers() {
mController.onInit();
verify(mBatteryMeterViewController).init();
}
@Test
public void testOnViewAttachedAddsBatteryControllerCallback() {
mController.onViewAttached();
verify(mBatteryController)
.addCallback(any(BatteryController.BatteryStateChangeCallback.class));
}
@Test
public void testOnViewAttachedRegistersNetworkCallback() {
mController.onViewAttached();
verify(mConnectivityManager)
.registerNetworkCallback(any(NetworkRequest.class), any(
ConnectivityManager.NetworkCallback.class));
}
@Test
public void testOnViewAttachedShowsWifiStatusWhenWifiUnavailable() {
when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
.thenReturn(false);
when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
mController.onViewAttached();
verify(mView).showWifiStatus(true);
}
@Test
public void testOnViewAttachedHidesWifiStatusWhenWifiAvailable() {
when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
.thenReturn(true);
when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
mController.onViewAttached();
verify(mView).showWifiStatus(false);
}
@Test
public void testOnViewAttachedShowsWifiStatusWhenNetworkCapabilitiesUnavailable() {
when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(null);
mController.onViewAttached();
verify(mView).showWifiStatus(true);
}
@Test
public void testOnViewDetachedRemovesBatteryControllerCallback() {
mController.onViewDetached();
verify(mBatteryController)
.removeCallback(any(BatteryController.BatteryStateChangeCallback.class));
}
@Test
public void testOnViewDetachedUnregistersNetworkCallback() {
mController.onViewDetached();
verify(mConnectivityManager)
.unregisterNetworkCallback(any(ConnectivityManager.NetworkCallback.class));
}
@Test
public void testBatteryPercentTextShownWhenBatteryLevelChangesWhileCharging() {
final ArgumentCaptor<BatteryController.BatteryStateChangeCallback> callbackCapture =
ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback.class);
mController.onViewAttached();
verify(mBatteryController).addCallback(callbackCapture.capture());
callbackCapture.getValue().onBatteryLevelChanged(1, true, true);
verify(mView).showBatteryPercentText(true);
}
@Test
public void testBatteryPercentTextHiddenWhenBatteryLevelChangesWhileNotCharging() {
final ArgumentCaptor<BatteryController.BatteryStateChangeCallback> callbackCapture =
ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback.class);
mController.onViewAttached();
verify(mBatteryController).addCallback(callbackCapture.capture());
callbackCapture.getValue().onBatteryLevelChanged(1, true, false);
verify(mView).showBatteryPercentText(false);
}
@Test
public void testWifiStatusHiddenWhenWifiBecomesAvailable() {
// Make sure wifi starts out unavailable when onViewAttached is called.
when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
.thenReturn(false);
when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
mController.onViewAttached();
final ArgumentCaptor<ConnectivityManager.NetworkCallback> callbackCapture =
ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
verify(mConnectivityManager).registerNetworkCallback(any(), callbackCapture.capture());
callbackCapture.getValue().onAvailable(mNetwork);
verify(mView).showWifiStatus(false);
}
@Test
public void testWifiStatusShownWhenWifiBecomesUnavailable() {
// Make sure wifi starts out available when onViewAttached is called.
when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
.thenReturn(true);
when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
mController.onViewAttached();
final ArgumentCaptor<ConnectivityManager.NetworkCallback> callbackCapture =
ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
verify(mConnectivityManager).registerNetworkCallback(any(), callbackCapture.capture());
callbackCapture.getValue().onLost(mNetwork);
verify(mView).showWifiStatus(true);
}
@Test
public void testWifiStatusHiddenWhenCapabilitiesChange() {
// Make sure wifi starts out unavailable when onViewAttached is called.
when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
.thenReturn(false);
when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
mController.onViewAttached();
final ArgumentCaptor<ConnectivityManager.NetworkCallback> callbackCapture =
ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
verify(mConnectivityManager).registerNetworkCallback(any(), callbackCapture.capture());
when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
.thenReturn(true);
callbackCapture.getValue().onCapabilitiesChanged(mNetwork, mNetworkCapabilities);
verify(mView).showWifiStatus(false);
}
}