1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.statusbar.phone;
18 
19 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
20 
21 import static com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.LIGHTS_OUT_NOTIF_VIEW;
22 
23 import android.animation.Animator;
24 import android.animation.AnimatorListenerAdapter;
25 import android.view.View;
26 import android.view.WindowInsets.Type.InsetsType;
27 import android.view.WindowInsetsController.Appearance;
28 import android.view.WindowInsetsController.Behavior;
29 import android.view.WindowManager;
30 import android.view.animation.AccelerateInterpolator;
31 
32 import androidx.lifecycle.Observer;
33 
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.internal.statusbar.LetterboxDetails;
36 import com.android.internal.view.AppearanceRegion;
37 import com.android.systemui.statusbar.CommandQueue;
38 import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
39 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
40 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope;
41 import com.android.systemui.util.ViewController;
42 
43 import javax.inject.Inject;
44 import javax.inject.Named;
45 
46 /**
47  * Apps can request a low profile mode {@link View#SYSTEM_UI_FLAG_LOW_PROFILE}
48  * where status bar and navigation icons dim. In this mode, a notification dot appears
49  * where the notification icons would appear if they would be shown outside of this mode.
50  *
51  * This controller shows and hides the notification dot in the status bar to indicate
52  * whether there are notifications when the device is in {@link View#SYSTEM_UI_FLAG_LOW_PROFILE}.
53  */
54 @StatusBarFragmentScope
55 public class LegacyLightsOutNotifController extends ViewController<View> {
56     private final CommandQueue mCommandQueue;
57     private final NotifLiveDataStore mNotifDataStore;
58     private final WindowManager mWindowManager;
59     private final Observer<Boolean> mObserver = hasNotifs -> updateLightsOutView();
60 
61     /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */
62     @VisibleForTesting @Appearance int mAppearance;
63 
64     private int mDisplayId;
65 
66     @Inject
LegacyLightsOutNotifController( @amedLIGHTS_OUT_NOTIF_VIEW) View lightsOutNotifView, WindowManager windowManager, NotifLiveDataStore notifDataStore, CommandQueue commandQueue)67     LegacyLightsOutNotifController(
68             @Named(LIGHTS_OUT_NOTIF_VIEW) View lightsOutNotifView,
69             WindowManager windowManager,
70             NotifLiveDataStore notifDataStore,
71             CommandQueue commandQueue) {
72         super(lightsOutNotifView);
73         mWindowManager = windowManager;
74         mNotifDataStore = notifDataStore;
75         mCommandQueue = commandQueue;
76     }
77 
78     @Override
onInit()79     protected void onInit() {
80         super.onInit();
81         NotificationsLiveDataStoreRefactor.assertInLegacyMode();
82     }
83 
84     @Override
onViewDetached()85     protected void onViewDetached() {
86         mNotifDataStore.getHasActiveNotifs().removeObserver(mObserver);
87         mCommandQueue.removeCallback(mCallback);
88     }
89 
90     @Override
onViewAttached()91     protected void onViewAttached() {
92         mView.setVisibility(View.GONE);
93         mView.setAlpha(0f);
94 
95         mDisplayId = mWindowManager.getDefaultDisplay().getDisplayId();
96         mNotifDataStore.getHasActiveNotifs().addSyncObserver(mObserver);
97         mCommandQueue.addCallback(mCallback);
98 
99         updateLightsOutView();
100     }
101 
hasActiveNotifications()102     private boolean hasActiveNotifications() {
103         return mNotifDataStore.getHasActiveNotifs().getValue();
104     }
105 
106     @VisibleForTesting
updateLightsOutView()107     void updateLightsOutView() {
108         final boolean showDot = shouldShowDot();
109         if (showDot != isShowingDot()) {
110             if (showDot) {
111                 mView.setAlpha(0f);
112                 mView.setVisibility(View.VISIBLE);
113             }
114 
115             mView.animate()
116                     .alpha(showDot ? 1 : 0)
117                     .setDuration(showDot ? 750 : 250)
118                     .setInterpolator(new AccelerateInterpolator(2.0f))
119                     .setListener(new AnimatorListenerAdapter() {
120                         @Override
121                         public void onAnimationEnd(Animator a) {
122                             mView.setAlpha(showDot ? 1 : 0);
123                             mView.setVisibility(showDot ? View.VISIBLE : View.GONE);
124                             // Unset the listener, otherwise this may persist for
125                             // another view property animation
126                             mView.animate().setListener(null);
127                         }
128                     })
129                     .start();
130         }
131     }
132 
133     @VisibleForTesting
isShowingDot()134     boolean isShowingDot() {
135         return mView.getVisibility() == View.VISIBLE
136                 && mView.getAlpha() == 1.0f;
137     }
138 
139     @VisibleForTesting
shouldShowDot()140     boolean shouldShowDot() {
141         return hasActiveNotifications() && areLightsOut();
142     }
143 
144     @VisibleForTesting
areLightsOut()145     boolean areLightsOut() {
146         return 0 != (mAppearance & APPEARANCE_LOW_PROFILE_BARS);
147     }
148 
149     private final CommandQueue.Callbacks mCallback = new CommandQueue.Callbacks() {
150         @Override
151         public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
152                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
153                 @Behavior int behavior, @InsetsType int requestedVisibleTypes,
154                 String packageName, LetterboxDetails[] letterboxDetails) {
155             if (displayId != mDisplayId) {
156                 return;
157             }
158             mAppearance = appearance;
159             updateLightsOutView();
160         }
161     };
162 }
163