1 /*
2  * Copyright (C) 2020 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.car.notification.headsup;
18 
19 import android.content.Context;
20 import android.view.LayoutInflater;
21 import android.view.View;
22 import android.view.ViewGroup;
23 import android.view.WindowManager;
24 
25 import androidx.annotation.VisibleForTesting;
26 
27 import com.android.car.notification.CarNotificationTypeItem;
28 import com.android.car.notification.R;
29 
30 import java.util.LinkedList;
31 
32 /**
33  * Container for displaying Heads Up Notifications.
34  */
35 public abstract class CarHeadsUpNotificationContainer {
36     private static final String TAG = "CarHUNContainer";
37     private final LinkedList<HunImportance> mHunImportanceLinkedList = new LinkedList<>();
38     private final ViewGroup mHunWindow;
39     private final ViewGroup mHunContent;
40     private final boolean mShowHunOnBottom;
41 
CarHeadsUpNotificationContainer(Context context, WindowManager windowManager)42     public CarHeadsUpNotificationContainer(Context context, WindowManager windowManager) {
43         mShowHunOnBottom = context.getResources().getBoolean(
44                 R.bool.config_showHeadsUpNotificationOnBottom);
45         mHunWindow = (ViewGroup) LayoutInflater.from(context).inflate(
46                 mShowHunOnBottom ? R.layout.headsup_container_bottom
47                         : R.layout.headsup_container, /* root= */ null, /* attachToRoot= */ false);
48         mHunContent = mHunWindow.findViewById(R.id.headsup_content);
49         mHunWindow.setVisibility(View.INVISIBLE);
50         windowManager.addView(mHunWindow, getWindowManagerLayoutParams());
51     }
52 
53     /**
54      * @return {@link WindowManager.LayoutParams} to be used when adding HUN Window to {@link
55      * WindowManager}.
56      */
getWindowManagerLayoutParams()57     protected abstract WindowManager.LayoutParams getWindowManagerLayoutParams();
58 
59     /**
60      * Displays a given notification View to the user and inserts the view at Z-index according to
61      * its {@link HunImportance},
62      */
displayNotification(View notificationView, CarNotificationTypeItem notificationTypeItem)63     public void displayNotification(View notificationView,
64             CarNotificationTypeItem notificationTypeItem) {
65         HunImportance hunImportance = getImportanceForCarNotificationTypeItem(notificationTypeItem);
66 
67         displayNotificationInner(notificationView, hunImportance);
68 
69         if (shouldShowHunPanel()) {
70             getHunWindow().setVisibility(View.VISIBLE);
71         }
72     }
73 
displayNotificationInner(View notificationView, HunImportance hunImportance)74     private void displayNotificationInner(View notificationView, HunImportance hunImportance) {
75         if (mHunImportanceLinkedList.isEmpty() || hunImportance.equals(HunImportance.EMERGENCY)) {
76             mHunImportanceLinkedList.add(hunImportance);
77             getHunContent().addView(notificationView);
78             return;
79         }
80 
81         int index = 0;
82         for (; index < mHunImportanceLinkedList.size(); index++) {
83             if (hunImportance.isLessImportantThan(mHunImportanceLinkedList.get(index))) break;
84         }
85         if (index < mHunImportanceLinkedList.size()) {
86             mHunImportanceLinkedList.add(index, hunImportance);
87             getHunContent().addView(notificationView, index);
88             return;
89         }
90 
91         mHunImportanceLinkedList.add(hunImportance);
92         getHunContent().addView(notificationView);
93     }
94 
95     /**
96      * @return {@code true} if Hun panel should be set as visible after displaying HUN.
97      */
shouldShowHunPanel()98     public boolean shouldShowHunPanel() {
99         return !isVisible();
100     }
101 
102     /**
103      * Removes a given notification View from the container.
104      */
removeNotification(View notificationView)105     public void removeNotification(View notificationView) {
106         if (getHunContent().getChildCount() == 0) return;
107 
108         int index = getHunContent().indexOfChild(notificationView);
109         if (index == -1) return;
110 
111         getHunContent().removeViewAt(index);
112         mHunImportanceLinkedList.remove(index);
113 
114         if (shouldHideHunPanel()) {
115             getHunWindow().setVisibility(View.INVISIBLE);
116         }
117     }
118 
119     /**
120      * @return {@code true} if HUN panel should be set as invisible after removing a HUN.
121      */
shouldHideHunPanel()122     public boolean shouldHideHunPanel() {
123         return getHunContent().getChildCount() == 0;
124     }
125 
126     /**
127      * @return Whether or not the container is currently visible.
128      */
isVisible()129     public final boolean isVisible() {
130         return getHunWindow().getVisibility() == View.VISIBLE;
131     }
132 
133     /**
134      * @return HUN window.
135      */
getHunWindow()136     protected final ViewGroup getHunWindow() {
137         return mHunWindow;
138     }
139 
140     /**
141      * @return HUN content inside of window.
142      */
getHunContent()143     protected final ViewGroup getHunContent() {
144         return mHunContent;
145     }
146 
147     /**
148      * @return {@code true} if HUN should be shown on bottom.
149      */
getShowHunOnBottom()150     protected final boolean getShowHunOnBottom() {
151         return mShowHunOnBottom;
152     }
153 
getImportanceForCarNotificationTypeItem( CarNotificationTypeItem notificationTypeItem)154     private HunImportance getImportanceForCarNotificationTypeItem(
155             CarNotificationTypeItem notificationTypeItem) {
156         if (notificationTypeItem == CarNotificationTypeItem.EMERGENCY) {
157             return HunImportance.EMERGENCY;
158         } else if (notificationTypeItem == CarNotificationTypeItem.WARNING) {
159             return HunImportance.WARNING;
160         } else if (notificationTypeItem == CarNotificationTypeItem.NAVIGATION) {
161             return HunImportance.NAVIGATION;
162         } else if (notificationTypeItem == CarNotificationTypeItem.CALL) {
163             return HunImportance.CALL;
164         } else {
165             return HunImportance.OTHER;
166         }
167     }
168 
169     @VisibleForTesting
170     enum HunImportance {
171         OTHER(/* level= */ 0),
172         CALL(/* level= */ 1),
173         NAVIGATION(/* level= */ 2),
174         WARNING(/* level= */ 3),
175         EMERGENCY(/* level= */ 4);
176 
177         private final Integer mLevel;
178 
HunImportance(int level)179         HunImportance(int level) {
180             this.mLevel = level;
181         }
182 
isMoreImportantThan(HunImportance other)183         boolean isMoreImportantThan(HunImportance other) {
184             return this.mLevel > other.mLevel;
185         }
186 
isLessImportantThan(HunImportance other)187         boolean isLessImportantThan(HunImportance other) {
188             return this.mLevel < other.mLevel;
189         }
190     }
191 }
192