Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2018 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.notifications; |
| 18 | |
Priyank Singh | 6e07e3a | 2019-02-20 23:30:52 +0000 | [diff] [blame] | 19 | import android.app.ActivityManager; |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 20 | import android.animation.Animator; |
| 21 | import android.animation.AnimatorListenerAdapter; |
| 22 | import android.animation.ValueAnimator; |
| 23 | import android.annotation.SuppressLint; |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 24 | import android.car.Car; |
| 25 | import android.car.CarNotConnectedException; |
| 26 | import android.car.drivingstate.CarUxRestrictionsManager; |
| 27 | import android.content.ComponentName; |
| 28 | import android.content.Context; |
| 29 | import android.content.ServiceConnection; |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 30 | import android.content.res.Configuration; |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 31 | import android.graphics.PixelFormat; |
| 32 | import android.os.IBinder; |
Brad Stenning | 2b48437 | 2018-11-20 13:04:55 -0800 | [diff] [blame] | 33 | import android.os.ServiceManager; |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 34 | import android.util.Log; |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 35 | import android.view.ContextThemeWrapper; |
| 36 | import android.view.GestureDetector; |
| 37 | import android.view.MotionEvent; |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 38 | import android.view.View; |
| 39 | import android.view.ViewGroup; |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 40 | import android.view.ViewTreeObserver; |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 41 | import android.view.WindowManager; |
| 42 | |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 43 | import androidx.annotation.NonNull; |
| 44 | import androidx.recyclerview.widget.RecyclerView; |
| 45 | |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 46 | import com.android.car.notification.CarNotificationListener; |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 47 | import com.android.car.notification.CarNotificationView; |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 48 | import com.android.car.notification.CarUxRestrictionManagerWrapper; |
Brad Stenning | 2b48437 | 2018-11-20 13:04:55 -0800 | [diff] [blame] | 49 | import com.android.car.notification.NotificationClickHandlerFactory; |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 50 | import com.android.car.notification.NotificationViewController; |
| 51 | import com.android.car.notification.PreprocessingManager; |
Brad Stenning | 2b48437 | 2018-11-20 13:04:55 -0800 | [diff] [blame] | 52 | import com.android.internal.statusbar.IStatusBarService; |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 53 | import com.android.systemui.Dependency; |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 54 | import com.android.systemui.R; |
| 55 | import com.android.systemui.SystemUI; |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 56 | import com.android.systemui.statusbar.FlingAnimationUtils; |
| 57 | import com.android.systemui.statusbar.policy.ConfigurationController; |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 58 | |
| 59 | /** |
| 60 | * Standalone SystemUI for displaying Notifications that have been designed to be used in the car |
| 61 | */ |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 62 | public class NotificationsUI extends SystemUI |
| 63 | implements ConfigurationController.ConfigurationListener { |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 64 | |
| 65 | private static final String TAG = "NotificationsUI"; |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 66 | // used to calculate how fast to open or close the window |
| 67 | private static final float DEFAULT_FLING_VELOCITY = 0; |
| 68 | // max time a fling animation takes |
| 69 | private static final float FLING_ANIMATION_MAX_TIME = 0.5f; |
| 70 | // acceleration rate for the fling animation |
| 71 | private static final float FLING_SPEED_UP_FACTOR = 0.6f; |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 72 | private CarNotificationListener mCarNotificationListener; |
| 73 | private CarUxRestrictionsManager mCarUxRestrictionsManager; |
Brad Stenning | 2b48437 | 2018-11-20 13:04:55 -0800 | [diff] [blame] | 74 | private NotificationClickHandlerFactory mClickHandlerFactory; |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 75 | private Car mCar; |
| 76 | private ViewGroup mCarNotificationWindow; |
| 77 | private NotificationViewController mNotificationViewController; |
| 78 | private boolean mIsShowing; |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 79 | private boolean mIsTracking; |
| 80 | private boolean mNotificationListAtBottom; |
| 81 | private boolean mNotificationListAtBottomAtTimeOfTouch; |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 82 | private CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper = |
| 83 | new CarUxRestrictionManagerWrapper(); |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 84 | // Used in the Notification panel touch listener |
| 85 | private GestureDetector mGestureDetector; |
| 86 | // Used in scrollable content of the notifications |
| 87 | private GestureDetector mScrollUpDetector; |
| 88 | private View mContent; |
| 89 | private View.OnTouchListener mOnTouchListener; |
| 90 | private FlingAnimationUtils mFlingAnimationUtils; |
| 91 | private static int sSettleOpenPercentage; |
| 92 | private static int sSettleClosePercentage; |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 93 | |
| 94 | /** |
| 95 | * Inits the window that hosts the notifications and establishes the connections |
| 96 | * to the car related services. |
| 97 | */ |
| 98 | @Override |
| 99 | public void start() { |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 100 | sSettleOpenPercentage = mContext.getResources().getInteger( |
| 101 | R.integer.notification_settle_open_percentage); |
| 102 | sSettleClosePercentage = mContext.getResources().getInteger( |
| 103 | R.integer.notification_settle_close_percentage); |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 104 | WindowManager windowManager = |
| 105 | (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 106 | mFlingAnimationUtils = new FlingAnimationUtils(mContext, |
| 107 | FLING_ANIMATION_MAX_TIME, FLING_SPEED_UP_FACTOR); |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 108 | mCarNotificationListener = new CarNotificationListener(); |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 109 | // create a notification click handler that closes the notification ui if the an activity |
| 110 | // is launched successfully |
Brad Stenning | 2b48437 | 2018-11-20 13:04:55 -0800 | [diff] [blame] | 111 | mClickHandlerFactory = new NotificationClickHandlerFactory( |
| 112 | IStatusBarService.Stub.asInterface( |
| 113 | ServiceManager.getService(Context.STATUS_BAR_SERVICE)), |
| 114 | launchResult -> { |
| 115 | if (launchResult == ActivityManager.START_TASK_TO_FRONT |
Priyank Singh | 6e07e3a | 2019-02-20 23:30:52 +0000 | [diff] [blame] | 116 | || launchResult == ActivityManager.START_SUCCESS){ |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 117 | closeCarNotifications(DEFAULT_FLING_VELOCITY); |
Brad Stenning | 2b48437 | 2018-11-20 13:04:55 -0800 | [diff] [blame] | 118 | } |
| 119 | }); |
Priyank Singh | 6e07e3a | 2019-02-20 23:30:52 +0000 | [diff] [blame] | 120 | mCarNotificationListener.registerAsSystemService(mContext, mCarUxRestrictionManagerWrapper, |
| 121 | mClickHandlerFactory); |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 122 | mCar = Car.createCar(mContext, mCarConnectionListener); |
| 123 | mCar.connect(); |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 124 | NotificationGestureListener gestureListener = new NotificationGestureListener(); |
| 125 | mGestureDetector = new GestureDetector(mContext, gestureListener); |
| 126 | mScrollUpDetector = new GestureDetector(mContext, new ScrollUpDetector()); |
| 127 | mOnTouchListener = new NotificationPanelTouchListener(); |
| 128 | mCarNotificationWindow = (ViewGroup) View.inflate(new ContextThemeWrapper(mContext, |
| 129 | R.style.Theme_Notification), |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 130 | R.layout.navigation_bar_window, null); |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 131 | mCarNotificationWindow |
| 132 | .setBackgroundColor(mContext.getColor(R.color.notification_shade_background_color)); |
Priyank Singh | 6e07e3a | 2019-02-20 23:30:52 +0000 | [diff] [blame] | 133 | inflateNotificationContent(); |
| 134 | |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 135 | WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( |
| 136 | ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, |
| 137 | WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY, |
| 138 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
| 139 | | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
| 140 | | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
| 141 | | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, |
| 142 | PixelFormat.TRANSLUCENT); |
| 143 | layoutParams.setTitle("Car Notification Window"); |
| 144 | // start in the hidden state |
| 145 | mCarNotificationWindow.setVisibility(View.GONE); |
| 146 | windowManager.addView(mCarNotificationWindow, layoutParams); |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 147 | |
| 148 | // Add this object to the SystemUI component registry such that the status bar |
| 149 | // can get a reference to it. |
| 150 | putComponent(NotificationsUI.class, this); |
| 151 | Dependency.get(ConfigurationController.class).addCallback(this); |
| 152 | } |
| 153 | |
| 154 | @SuppressLint("ClickableViewAccessibility") |
| 155 | private void inflateNotificationContent() { |
| 156 | if (mNotificationViewController != null) { |
| 157 | mNotificationViewController.disable(); |
| 158 | } |
| 159 | mCarNotificationWindow.removeAllViews(); |
| 160 | |
| 161 | mContent = View.inflate(new ContextThemeWrapper(mContext, |
| 162 | com.android.car.notification.R.style.Theme_Notification), |
| 163 | R.layout.notification_center_activity, |
| 164 | mCarNotificationWindow); |
| 165 | // set the click handler such that we can dismiss the UI when a notification is clicked |
| 166 | CarNotificationView noteView = mCarNotificationWindow.findViewById(R.id.notification_view); |
| 167 | noteView.setClickHandlerFactory(mClickHandlerFactory); |
| 168 | |
| 169 | mContent.setOnTouchListener(mOnTouchListener); |
| 170 | // set initial translation after size is calculated |
| 171 | mContent.getViewTreeObserver().addOnGlobalLayoutListener( |
| 172 | new ViewTreeObserver.OnGlobalLayoutListener() { |
| 173 | @Override |
| 174 | public void onGlobalLayout() { |
| 175 | mContent.getViewTreeObserver().removeOnGlobalLayoutListener(this); |
| 176 | if (!mIsShowing && !mIsTracking) { |
| 177 | mContent.setTranslationY(mContent.getHeight() * -1); |
| 178 | } |
| 179 | } |
| 180 | }); |
| 181 | |
| 182 | RecyclerView notificationList = mCarNotificationWindow |
| 183 | .findViewById(com.android.car.notification.R.id.recycler_view); |
| 184 | // register a scroll listener so we can figure out if we are at the bottom of the |
| 185 | // list of notifications |
| 186 | notificationList.addOnScrollListener(new RecyclerView.OnScrollListener() { |
| 187 | @Override |
| 188 | public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { |
| 189 | super.onScrolled(recyclerView, dx, dy); |
| 190 | if (!notificationList.canScrollVertically(1)) { |
| 191 | mNotificationListAtBottom = true; |
| 192 | return; |
| 193 | } |
| 194 | mNotificationListAtBottom = false; |
| 195 | mNotificationListAtBottomAtTimeOfTouch = false; |
| 196 | } |
| 197 | }); |
| 198 | // add a touch listener such that when the user scrolls up and they are at the bottom |
| 199 | // of the list we can start the closing of the view. |
| 200 | notificationList.setOnTouchListener(new NotificationListTouchListener()); |
| 201 | |
| 202 | // There's a view installed at a higher z-order such that we can intercept the ACTION_DOWN |
| 203 | // to set the initial click state. |
| 204 | mCarNotificationWindow.findViewById(R.id.glass_pane).setOnTouchListener((v, event) -> { |
Priyank Singh | 6e07e3a | 2019-02-20 23:30:52 +0000 | [diff] [blame] | 205 | if (event.getActionMasked() == MotionEvent.ACTION_UP ) { |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 206 | mNotificationListAtBottomAtTimeOfTouch = false; |
| 207 | } |
| 208 | if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { |
| 209 | mNotificationListAtBottomAtTimeOfTouch = mNotificationListAtBottom; |
| 210 | // register the down event with the gesture detectors so then know where the down |
| 211 | // started. This is needed because at this point we don't know which listener |
| 212 | // is going to handle scroll and fling events. |
| 213 | mGestureDetector.onTouchEvent(event); |
| 214 | mScrollUpDetector.onTouchEvent(event); |
| 215 | } |
| 216 | return false; |
| 217 | }); |
| 218 | |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 219 | mNotificationViewController = new NotificationViewController( |
| 220 | mCarNotificationWindow |
| 221 | .findViewById(com.android.car.notification.R.id.notification_view), |
| 222 | PreprocessingManager.getInstance(mContext), |
| 223 | mCarNotificationListener, |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 224 | mCarUxRestrictionManagerWrapper); |
| 225 | mNotificationViewController.enable(); |
| 226 | } |
| 227 | |
| 228 | // allows for day night switch |
| 229 | @Override |
| 230 | public void onConfigChanged(Configuration newConfig) { |
| 231 | inflateNotificationContent(); |
| 232 | } |
| 233 | |
| 234 | public View.OnTouchListener getDragDownListener() { |
| 235 | return mOnTouchListener; |
| 236 | } |
| 237 | |
| 238 | /** |
| 239 | * This listener is attached to the notification list UI to intercept gestures if the user |
| 240 | * is scrolling up when the notification list is at the bottom |
| 241 | */ |
| 242 | private class ScrollUpDetector extends GestureDetector.SimpleOnGestureListener { |
| 243 | |
| 244 | @Override |
| 245 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { |
| 246 | return distanceY > 0; |
| 247 | } |
Priyank Singh | a4fa291 | 2019-02-28 16:00:16 -0800 | [diff] [blame] | 248 | |
| 249 | @Override |
| 250 | public boolean onSingleTapUp(MotionEvent motionEvent) { |
| 251 | closeCarNotifications(DEFAULT_FLING_VELOCITY); |
| 252 | return false; |
| 253 | } |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 254 | } |
| 255 | |
| 256 | private class NotificationListTouchListener implements View.OnTouchListener { |
| 257 | |
| 258 | @Override |
| 259 | public boolean onTouch(View v, MotionEvent event) { |
| 260 | // reset mNotificationListAtBottomAtTimeOfTouch here since the "glass pane" will not |
| 261 | // get the up event |
Priyank Singh | 6e07e3a | 2019-02-20 23:30:52 +0000 | [diff] [blame] | 262 | if (event.getActionMasked() == MotionEvent.ACTION_UP ) { |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 263 | mNotificationListAtBottomAtTimeOfTouch = false; |
| 264 | } |
| 265 | boolean wasScrolledUp = mScrollUpDetector.onTouchEvent(event); |
| 266 | |
| 267 | if (mIsTracking |
| 268 | || (mNotificationListAtBottomAtTimeOfTouch && mNotificationListAtBottom |
| 269 | && wasScrolledUp)) { |
| 270 | mOnTouchListener.onTouch(v, event); |
| 271 | // touch event should not be propagated further |
| 272 | return true; |
| 273 | } |
| 274 | return false; |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | /** |
| 279 | * Touch listener installed on the notification panel. It is also used by the Nav and StatusBar |
| 280 | */ |
| 281 | private class NotificationPanelTouchListener implements View.OnTouchListener { |
| 282 | |
| 283 | @Override |
| 284 | public boolean onTouch(View v, MotionEvent event) { |
| 285 | boolean consumed = mGestureDetector.onTouchEvent(event); |
| 286 | if (consumed) { |
| 287 | return true; |
| 288 | } |
| 289 | if (!mIsTracking || event.getActionMasked() != MotionEvent.ACTION_UP) { |
| 290 | return false; |
| 291 | } |
| 292 | |
| 293 | float percentFromBottom = |
| 294 | Math.abs(mContent.getTranslationY() / mContent.getHeight()) * 100; |
| 295 | if (mIsShowing) { |
| 296 | if (percentFromBottom < sSettleOpenPercentage) { |
| 297 | // panel started to close but did not cross minimum threshold thus we open |
| 298 | // it back up |
| 299 | openCarNotifications(DEFAULT_FLING_VELOCITY); |
| 300 | return true; |
| 301 | } |
| 302 | // panel was lifted more than the threshold thus we close it the rest of the way |
| 303 | closeCarNotifications(DEFAULT_FLING_VELOCITY); |
| 304 | return true; |
| 305 | } |
| 306 | |
| 307 | if (percentFromBottom > sSettleClosePercentage) { |
| 308 | // panel was only peeked at thus close it back up |
| 309 | closeCarNotifications(DEFAULT_FLING_VELOCITY); |
| 310 | return true; |
| 311 | } |
| 312 | // panel has been open more than threshold thus open it the rest of the way |
| 313 | openCarNotifications(DEFAULT_FLING_VELOCITY); |
| 314 | return true; |
| 315 | |
| 316 | } |
| 317 | } |
| 318 | |
| 319 | /** |
| 320 | * Listener called by mGestureDetector. This will be initiated from the |
| 321 | * NotificationPanelTouchListener |
| 322 | */ |
| 323 | private class NotificationGestureListener extends GestureDetector.SimpleOnGestureListener { |
| 324 | private static final int SWIPE_UP_MIN_DISTANCE = 75; |
| 325 | private static final int SWIPE_DOWN_MIN_DISTANCE = 25; |
| 326 | private static final int SWIPE_MAX_OFF_PATH = 75; |
| 327 | private static final int SWIPE_THRESHOLD_VELOCITY = 200; |
| 328 | |
| 329 | @Override |
| 330 | public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, |
| 331 | float distanceY) { |
Priyank Singh | a4fa291 | 2019-02-28 16:00:16 -0800 | [diff] [blame] | 332 | boolean isDown = event1.getY() - event2.getY() < 0; |
| 333 | // CarStatusBar and NavigationBar are identical so avoid the touch if it |
| 334 | // starts from NavigationBar to open. |
| 335 | if (event1.getRawY() > mCarNotificationWindow.getHeight() && isDown |
| 336 | && mCarNotificationWindow.getVisibility() == View.GONE) { |
| 337 | mIsTracking = false; |
| 338 | return true; |
| 339 | } |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 340 | mIsTracking = true; |
| 341 | mCarNotificationWindow.setVisibility(View.VISIBLE); |
| 342 | |
| 343 | mContent.setTranslationY(Math.min(mContent.getTranslationY() - distanceY, 0)); |
| 344 | if (mContent.getTranslationY() == 0) { |
| 345 | mIsTracking = false; |
| 346 | } |
| 347 | return true; |
| 348 | } |
| 349 | |
| 350 | @Override |
| 351 | public boolean onFling(MotionEvent event1, MotionEvent event2, |
| 352 | float velocityX, float velocityY) { |
| 353 | if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH |
Priyank Singh | 6e07e3a | 2019-02-20 23:30:52 +0000 | [diff] [blame] | 354 | || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY){ |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 355 | // swipe was not vertical or was not fast enough |
| 356 | return false; |
| 357 | } |
| 358 | |
| 359 | boolean isUp = velocityY < 0; |
| 360 | float distanceDelta = Math.abs(event1.getY() - event2.getY()); |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 361 | if (isUp && distanceDelta > SWIPE_UP_MIN_DISTANCE) { |
| 362 | // fling up |
| 363 | mIsTracking = false; |
| 364 | closeCarNotifications(Math.abs(velocityY)); |
| 365 | return true; |
| 366 | |
Priyank Singh | a4fa291 | 2019-02-28 16:00:16 -0800 | [diff] [blame] | 367 | } else if (!isUp && distanceDelta > SWIPE_DOWN_MIN_DISTANCE |
| 368 | && (event1.getRawY() < mCarNotificationWindow.getHeight() |
| 369 | || mCarNotificationWindow.getVisibility() == View.VISIBLE)) { |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 370 | // fling down |
| 371 | mIsTracking = false; |
| 372 | openCarNotifications(velocityY); |
| 373 | return true; |
| 374 | } |
| 375 | |
| 376 | return false; |
| 377 | } |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 378 | } |
| 379 | |
| 380 | /** |
| 381 | * Connection callback to establish UX Restrictions |
| 382 | */ |
| 383 | private ServiceConnection mCarConnectionListener = new ServiceConnection() { |
| 384 | @Override |
| 385 | public void onServiceConnected(ComponentName name, IBinder service) { |
| 386 | try { |
| 387 | mCarUxRestrictionsManager = (CarUxRestrictionsManager) mCar.getCarManager( |
| 388 | Car.CAR_UX_RESTRICTION_SERVICE); |
| 389 | mCarUxRestrictionManagerWrapper |
| 390 | .setCarUxRestrictionsManager(mCarUxRestrictionsManager); |
| 391 | PreprocessingManager preprocessingManager = PreprocessingManager.getInstance( |
| 392 | mContext); |
| 393 | preprocessingManager |
| 394 | .setCarUxRestrictionManagerWrapper(mCarUxRestrictionManagerWrapper); |
| 395 | } catch (CarNotConnectedException e) { |
| 396 | Log.e(TAG, "Car not connected in CarConnectionListener", e); |
| 397 | } |
| 398 | } |
| 399 | |
| 400 | @Override |
| 401 | public void onServiceDisconnected(ComponentName name) { |
| 402 | Log.e(TAG, "Car service disconnected unexpectedly"); |
| 403 | } |
| 404 | }; |
| 405 | |
| 406 | /** |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 407 | * Toggles the visibility of the notifications |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 408 | */ |
| 409 | public void toggleShowingCarNotifications() { |
| 410 | if (mCarNotificationWindow.getVisibility() == View.VISIBLE) { |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 411 | closeCarNotifications(DEFAULT_FLING_VELOCITY); |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 412 | return; |
| 413 | } |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 414 | openCarNotifications(DEFAULT_FLING_VELOCITY); |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 415 | } |
| 416 | |
| 417 | /** |
| 418 | * Hides the notifications |
| 419 | */ |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 420 | public void closeCarNotifications(float velocityY) { |
| 421 | float closedTranslation = mContent.getHeight() * -1; |
| 422 | ValueAnimator animator = |
| 423 | ValueAnimator.ofFloat(mContent.getTranslationY(), closedTranslation); |
| 424 | animator.addUpdateListener( |
| 425 | animation -> mContent.setTranslationY((Float) animation.getAnimatedValue())); |
| 426 | mFlingAnimationUtils.apply( |
| 427 | animator, mContent.getTranslationY(), closedTranslation, velocityY); |
| 428 | animator.addListener(new AnimatorListenerAdapter() { |
| 429 | @Override |
| 430 | public void onAnimationEnd(Animator animation) { |
| 431 | mCarNotificationWindow.setVisibility(View.GONE); |
| 432 | } |
| 433 | }); |
| 434 | animator.start(); |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 435 | mNotificationViewController.disable(); |
| 436 | mIsShowing = false; |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 437 | mIsTracking = false; |
| 438 | RecyclerView notificationListView = mCarNotificationWindow.findViewById( |
| 439 | com.android.car.notification.R.id.recycler_view); |
| 440 | notificationListView.scrollToPosition(0); |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 441 | } |
| 442 | |
| 443 | /** |
| 444 | * Sets the notifications to visible |
| 445 | */ |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 446 | public void openCarNotifications(float velocityY) { |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 447 | mCarNotificationWindow.setVisibility(View.VISIBLE); |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 448 | |
| 449 | ValueAnimator animator = ValueAnimator.ofFloat(mContent.getTranslationY(), 0); |
| 450 | animator.addUpdateListener( |
| 451 | animation -> mContent.setTranslationY((Float) animation.getAnimatedValue())); |
| 452 | mFlingAnimationUtils.apply(animator, mContent.getTranslationY(), 0, velocityY); |
| 453 | animator.start(); |
| 454 | |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 455 | mNotificationViewController.enable(); |
| 456 | mIsShowing = true; |
Brad Stenning | c622f1d | 2019-01-29 11:24:11 -0800 | [diff] [blame] | 457 | mIsTracking = false; |
Brad Stenning | 19f236a | 2018-12-11 14:12:30 -0800 | [diff] [blame] | 458 | } |
| 459 | } |