From cebb50f61ed88935ba682ce69d698ad991b6dfe3 Mon Sep 17 00:00:00 2001 From: Will Date: Sun, 5 Dec 2021 14:27:13 -0800 Subject: [PATCH] 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 --- .../res/drawable/ic_signal_wifi_off.xml | 24 +++ .../res/layout/dream_overlay_container.xml | 61 ++++++ packages/SystemUI/res/values/dimens.xml | 5 + .../dreams/DreamOverlayContainerView.java | 44 ++++ .../systemui/dreams/DreamOverlayService.java | 76 +++---- .../dreams/DreamOverlayStatusBarView.java | 86 ++++++++ .../DreamOverlayStatusBarViewController.java | 172 +++++++++++++++ .../systemui/dreams/dagger/DreamModule.java | 4 +- .../dreams/dagger/DreamOverlayComponent.java | 62 ++++++ .../dreams/dagger/DreamOverlayModule.java | 105 ++++++++++ .../dreams/DreamOverlayServiceTest.java | 45 +++- ...eamOverlayStatusBarViewControllerTest.java | 195 ++++++++++++++++++ 12 files changed, 839 insertions(+), 40 deletions(-) create mode 100644 packages/SystemUI/res/drawable/ic_signal_wifi_off.xml create mode 100644 packages/SystemUI/res/layout/dream_overlay_container.xml create mode 100644 packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerView.java create mode 100644 packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java create mode 100644 packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java create mode 100644 packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java create mode 100644 packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java diff --git a/packages/SystemUI/res/drawable/ic_signal_wifi_off.xml b/packages/SystemUI/res/drawable/ic_signal_wifi_off.xml new file mode 100644 index 0000000000000..a93abb71c9d75 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_signal_wifi_off.xml @@ -0,0 +1,24 @@ + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml new file mode 100644 index 0000000000000..b611ffa6d84e1 --- /dev/null +++ b/packages/SystemUI/res/layout/dream_overlay_container.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index c7350a1eee8b3..ddec6cb373e50 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1303,4 +1303,9 @@ 16dp 100% + + + 80dp + 40dp + 8dp diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerView.java new file mode 100644 index 0000000000000..bc1f772e14bb3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerView.java @@ -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); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index 8f0ea2fb2f878..20ccacc49fc68 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -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); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java new file mode 100644 index 0000000000000..9847ef633bc17 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java @@ -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); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java new file mode 100644 index 0000000000000..5674b9f3f9fd6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java @@ -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 { + @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); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java index 7bf2361e471c4..ff5beb54bd898 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java @@ -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 { } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java new file mode 100644 index 0000000000000..a3a446a0dbcaa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java @@ -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(); +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java new file mode 100644 index 0000000000000..d0a8fad9b1e58 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java @@ -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); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java index 53bfeee9135ab..d0ec3f36427ca 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java @@ -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(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java new file mode 100644 index 0000000000000..7f72dda441d24 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java @@ -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 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 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 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 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 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); + } +}