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 android.animation.Animator;
22 import android.animation.AnimatorListenerAdapter;
23 import android.annotation.Nullable;
24 import android.view.InsetsVisibilities;
25 import android.view.View;
26 import android.view.WindowInsetsController.Appearance;
27 import android.view.WindowInsetsController.Behavior;
28 import android.view.WindowManager;
29 import android.view.animation.AccelerateInterpolator;
30 
31 import com.android.internal.annotations.VisibleForTesting;
32 import com.android.internal.statusbar.NotificationVisibility;
33 import com.android.internal.view.AppearanceRegion;
34 import com.android.systemui.dagger.SysUISingleton;
35 import com.android.systemui.statusbar.CommandQueue;
36 import com.android.systemui.statusbar.notification.NotificationEntryListener;
37 import com.android.systemui.statusbar.notification.NotificationEntryManager;
38 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
39 
40 import javax.inject.Inject;
41 
42 /**
43  * Apps can request a low profile mode {@link View.SYSTEM_UI_FLAG_LOW_PROFILE}
44  * where status bar and navigation icons dim. In this mode, a notification dot appears
45  * where the notification icons would appear if they would be shown outside of this mode.
46  *
47  * This controller shows and hides the notification dot in the status bar to indicate
48  * whether there are notifications when the device is in {@link View.SYSTEM_UI_FLAG_LOW_PROFILE}.
49  */
50 @SysUISingleton
51 public class LightsOutNotifController {
52     private final CommandQueue mCommandQueue;
53     private final NotificationEntryManager mEntryManager;
54     private final WindowManager mWindowManager;
55 
56     /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int) */
57     @VisibleForTesting @Appearance int mAppearance;
58 
59     private int mDisplayId;
60     private View mLightsOutNotifView;
61 
62     @Inject
LightsOutNotifController(WindowManager windowManager, NotificationEntryManager entryManager, CommandQueue commandQueue)63     LightsOutNotifController(WindowManager windowManager,
64             NotificationEntryManager entryManager,
65             CommandQueue commandQueue) {
66         mWindowManager = windowManager;
67         mEntryManager = entryManager;
68         mCommandQueue = commandQueue;
69     }
70 
71     /**
72      * Sets the notification dot view after it is created in the StatusBar.
73      * This is the view this controller will show and hide depending on whether:
74      * 1. there are active notifications
75      * 2. an app has requested {@link View.SYSTEM_UI_FLAG_LOW_PROFILE}
76      */
setLightsOutNotifView(View lightsOutNotifView)77     void setLightsOutNotifView(View lightsOutNotifView) {
78         destroy();
79         mLightsOutNotifView = lightsOutNotifView;
80 
81         if (mLightsOutNotifView != null) {
82             mLightsOutNotifView.setVisibility(View.GONE);
83             mLightsOutNotifView.setAlpha(0f);
84             init();
85         }
86     }
87 
destroy()88     private void destroy() {
89         mEntryManager.removeNotificationEntryListener(mEntryListener);
90         mCommandQueue.removeCallback(mCallback);
91     }
92 
init()93     private void init() {
94         mDisplayId = mWindowManager.getDefaultDisplay().getDisplayId();
95         mEntryManager.addNotificationEntryListener(mEntryListener);
96         mCommandQueue.addCallback(mCallback);
97 
98         updateLightsOutView();
99     }
100 
hasActiveNotifications()101     private boolean hasActiveNotifications() {
102         return mEntryManager.hasActiveNotifications();
103     }
104 
105     @VisibleForTesting
updateLightsOutView()106     void updateLightsOutView() {
107         if (mLightsOutNotifView == null) {
108             return;
109         }
110 
111         final boolean showDot = shouldShowDot();
112         if (showDot != isShowingDot()) {
113             if (showDot) {
114                 mLightsOutNotifView.setAlpha(0f);
115                 mLightsOutNotifView.setVisibility(View.VISIBLE);
116             }
117 
118             mLightsOutNotifView.animate()
119                     .alpha(showDot ? 1 : 0)
120                     .setDuration(showDot ? 750 : 250)
121                     .setInterpolator(new AccelerateInterpolator(2.0f))
122                     .setListener(new AnimatorListenerAdapter() {
123                         @Override
124                         public void onAnimationEnd(Animator a) {
125                             mLightsOutNotifView.setAlpha(showDot ? 1 : 0);
126                             mLightsOutNotifView.setVisibility(showDot ? View.VISIBLE : View.GONE);
127                             // Unset the listener, otherwise this may persist for
128                             // another view property animation
129                             mLightsOutNotifView.animate().setListener(null);
130                         }
131                     })
132                     .start();
133         }
134     }
135 
136     @VisibleForTesting
isShowingDot()137     boolean isShowingDot() {
138         return mLightsOutNotifView.getVisibility() == View.VISIBLE
139                 && mLightsOutNotifView.getAlpha() == 1.0f;
140     }
141 
142     @VisibleForTesting
shouldShowDot()143     boolean shouldShowDot() {
144         return hasActiveNotifications() && areLightsOut();
145     }
146 
147     @VisibleForTesting
areLightsOut()148     boolean areLightsOut() {
149         return 0 != (mAppearance & APPEARANCE_LOW_PROFILE_BARS);
150     }
151 
152     private final CommandQueue.Callbacks mCallback = new CommandQueue.Callbacks() {
153         @Override
154         public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
155                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
156                 @Behavior int behavior, InsetsVisibilities requestedVisibilities,
157                 String packageName) {
158             if (displayId != mDisplayId) {
159                 return;
160             }
161             mAppearance = appearance;
162             updateLightsOutView();
163         }
164     };
165 
166     private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
167         // Cares about notifications post-filtering
168         @Override
169         public void onNotificationAdded(NotificationEntry entry) {
170             updateLightsOutView();
171         }
172 
173         @Override
174         public void onPostEntryUpdated(NotificationEntry entry) {
175             updateLightsOutView();
176         }
177 
178         @Override
179         public void onEntryRemoved(@Nullable NotificationEntry entry,
180                 NotificationVisibility visibility, boolean removedByUser, int reason) {
181             updateLightsOutView();
182         }
183     };
184 }
185