blob: 55c1806e1899d9595984a170e5992c31185a1c6b [file] [log] [blame]
/*
* 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 android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_HIDING;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.StatusBarManager;
import android.content.res.Resources;
import android.hardware.SensorPrivacyManager;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.format.DateFormat;
import android.util.PluralsMessageFormatter;
import android.view.View;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.touch.TouchInsetManager;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.time.DateFormatUtil;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Executor;
import javax.inject.Inject;
/**
* View controller for {@link DreamOverlayStatusBarView}.
*/
@DreamOverlayComponent.DreamOverlayScope
public class DreamOverlayStatusBarViewController extends ViewController<DreamOverlayStatusBarView> {
private final ConnectivityManager mConnectivityManager;
private final TouchInsetManager.TouchInsetSession mTouchInsetSession;
private final NextAlarmController mNextAlarmController;
private final AlarmManager mAlarmManager;
private final Resources mResources;
private final DateFormatUtil mDateFormatUtil;
private final IndividualSensorPrivacyController mSensorPrivacyController;
private final Optional<DreamOverlayNotificationCountProvider>
mDreamOverlayNotificationCountProvider;
private final ZenModeController mZenModeController;
private final Executor mMainExecutor;
private boolean mIsAttached;
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) {
updateWifiUnavailableStatusIcon();
}
@Override
public void onAvailable(Network network) {
updateWifiUnavailableStatusIcon();
}
@Override
public void onLost(Network network) {
updateWifiUnavailableStatusIcon();
}
};
private final IndividualSensorPrivacyController.Callback mSensorCallback =
(sensor, blocked) -> updateMicCameraBlockedStatusIcon();
private final NextAlarmController.NextAlarmChangeCallback mNextAlarmCallback =
nextAlarm -> updateAlarmStatusIcon();
private final ZenModeController.Callback mZenModeCallback = new ZenModeController.Callback() {
@Override
public void onZenChanged(int zen) {
updatePriorityModeStatusIcon();
}
};
private final DreamOverlayNotificationCountProvider.Callback mNotificationCountCallback =
notificationCount -> showIcon(
DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS,
notificationCount > 0,
notificationCount > 0
? buildNotificationsContentDescription(notificationCount)
: null);
@Inject
public DreamOverlayStatusBarViewController(
DreamOverlayStatusBarView view,
@Main Resources resources,
@Main Executor mainExecutor,
ConnectivityManager connectivityManager,
TouchInsetManager.TouchInsetSession touchInsetSession,
AlarmManager alarmManager,
NextAlarmController nextAlarmController,
DateFormatUtil dateFormatUtil,
IndividualSensorPrivacyController sensorPrivacyController,
Optional<DreamOverlayNotificationCountProvider> dreamOverlayNotificationCountProvider,
ZenModeController zenModeController,
StatusBarWindowStateController statusBarWindowStateController) {
super(view);
mResources = resources;
mMainExecutor = mainExecutor;
mConnectivityManager = connectivityManager;
mTouchInsetSession = touchInsetSession;
mAlarmManager = alarmManager;
mNextAlarmController = nextAlarmController;
mDateFormatUtil = dateFormatUtil;
mSensorPrivacyController = sensorPrivacyController;
mDreamOverlayNotificationCountProvider = dreamOverlayNotificationCountProvider;
mZenModeController = zenModeController;
// Register to receive show/hide updates for the system status bar. Our custom status bar
// needs to hide when the system status bar is showing to ovoid overlapping status bars.
statusBarWindowStateController.addListener(this::onSystemStatusBarStateChanged);
}
@Override
protected void onViewAttached() {
mIsAttached = true;
mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
updateWifiUnavailableStatusIcon();
mNextAlarmController.addCallback(mNextAlarmCallback);
updateAlarmStatusIcon();
mSensorPrivacyController.addCallback(mSensorCallback);
updateMicCameraBlockedStatusIcon();
mZenModeController.addCallback(mZenModeCallback);
updatePriorityModeStatusIcon();
mDreamOverlayNotificationCountProvider.ifPresent(
provider -> provider.addCallback(mNotificationCountCallback));
mTouchInsetSession.addViewToTracking(mView);
}
@Override
protected void onViewDetached() {
mZenModeController.removeCallback(mZenModeCallback);
mSensorPrivacyController.removeCallback(mSensorCallback);
mNextAlarmController.removeCallback(mNextAlarmCallback);
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
mDreamOverlayNotificationCountProvider.ifPresent(
provider -> provider.removeCallback(mNotificationCountCallback));
mTouchInsetSession.clear();
mIsAttached = false;
}
private void updateWifiUnavailableStatusIcon() {
final NetworkCapabilities capabilities =
mConnectivityManager.getNetworkCapabilities(
mConnectivityManager.getActiveNetwork());
final boolean available = capabilities != null
&& capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
showIcon(DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, !available);
}
private void updateAlarmStatusIcon() {
final AlarmManager.AlarmClockInfo alarm =
mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT);
final boolean hasAlarm = alarm != null && alarm.getTriggerTime() > 0;
showIcon(
DreamOverlayStatusBarView.STATUS_ICON_ALARM_SET,
hasAlarm,
hasAlarm ? buildAlarmContentDescription(alarm) : null);
}
private String buildAlarmContentDescription(AlarmManager.AlarmClockInfo alarm) {
final String skeleton = mDateFormatUtil.is24HourFormat() ? "EHm" : "Ehma";
final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
final String dateString = DateFormat.format(pattern, alarm.getTriggerTime()).toString();
return mResources.getString(R.string.accessibility_quick_settings_alarm, dateString);
}
private void updateMicCameraBlockedStatusIcon() {
final boolean micBlocked = mSensorPrivacyController
.isSensorBlocked(SensorPrivacyManager.Sensors.MICROPHONE);
final boolean cameraBlocked = mSensorPrivacyController
.isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA);
@DreamOverlayStatusBarView.StatusIconType int iconType = Resources.ID_NULL;
showIcon(
DreamOverlayStatusBarView.STATUS_ICON_CAMERA_DISABLED,
!micBlocked && cameraBlocked);
showIcon(
DreamOverlayStatusBarView.STATUS_ICON_MIC_DISABLED,
micBlocked && !cameraBlocked);
showIcon(
DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED,
micBlocked && cameraBlocked);
}
private String buildNotificationsContentDescription(int notificationCount) {
return PluralsMessageFormatter.format(
mResources,
Map.of("count", notificationCount),
R.string.dream_overlay_status_bar_notification_indicator);
}
private void updatePriorityModeStatusIcon() {
showIcon(
DreamOverlayStatusBarView.STATUS_ICON_PRIORITY_MODE_ON,
mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF);
}
private void showIcon(@DreamOverlayStatusBarView.StatusIconType int iconType, boolean show) {
showIcon(iconType, show, null);
}
private void showIcon(
@DreamOverlayStatusBarView.StatusIconType int iconType,
boolean show,
@Nullable String contentDescription) {
mMainExecutor.execute(() -> {
if (mIsAttached) {
mView.showIcon(iconType, show, contentDescription);
}
});
}
private void onSystemStatusBarStateChanged(@StatusBarManager.WindowVisibleState int state) {
mMainExecutor.execute(() -> {
if (!mIsAttached) {
return;
}
switch (state) {
case WINDOW_STATE_SHOWING:
mView.setVisibility(View.INVISIBLE);
break;
case WINDOW_STATE_HIDING:
case WINDOW_STATE_HIDDEN:
mView.setVisibility(View.VISIBLE);
break;
}
});
}
}