1 /* <lambda>null2 * Copyright (C) 2024 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.carlauncher 18 19 import android.animation.ValueAnimator 20 import android.car.Car 21 import android.car.content.pm.CarPackageManager 22 import android.car.drivingstate.CarUxRestrictionsManager 23 import android.car.media.CarMediaManager 24 import android.content.BroadcastReceiver 25 import android.content.Intent 26 import android.content.IntentFilter 27 import android.content.pm.LauncherApps 28 import android.content.pm.PackageManager 29 import android.media.session.MediaSessionManager 30 import android.os.Bundle 31 import android.os.Handler 32 import android.os.Looper.getMainLooper 33 import android.os.Process 34 import android.os.UserManager 35 import android.util.Log 36 import android.view.DragEvent 37 import android.view.LayoutInflater 38 import android.view.SurfaceControl 39 import android.view.View 40 import android.view.ViewGroup 41 import android.view.ViewTreeObserver 42 import android.widget.FrameLayout 43 import android.widget.LinearLayout 44 import androidx.annotation.StringRes 45 import androidx.fragment.app.Fragment 46 import androidx.lifecycle.ViewModelProvider 47 import androidx.lifecycle.asLiveData 48 import androidx.recyclerview.widget.RecyclerView 49 import com.android.car.carlauncher.AppGridConstants.AppItemBoundDirection 50 import com.android.car.carlauncher.AppGridConstants.PageOrientation 51 import com.android.car.carlauncher.AppGridConstants.isHorizontal 52 import com.android.car.carlauncher.AppGridFragment.AppTypes.Companion.APP_TYPE_LAUNCHABLES 53 import com.android.car.carlauncher.AppGridFragment.AppTypes.Companion.APP_TYPE_MEDIA_SERVICES 54 import com.android.car.carlauncher.AppGridPageSnapper.AppGridPageSnapCallback 55 import com.android.car.carlauncher.AppGridPageSnapper.PageSnapListener 56 import com.android.car.carlauncher.AppGridViewModel.Companion.provideFactory 57 import com.android.car.carlauncher.datasources.AppOrderDataSource 58 import com.android.car.carlauncher.datasources.AppOrderProtoDataSourceImpl 59 import com.android.car.carlauncher.datasources.ControlCenterMirroringDataSource 60 import com.android.car.carlauncher.datasources.ControlCenterMirroringDataSourceImpl 61 import com.android.car.carlauncher.datasources.ControlCenterMirroringDataSourceImpl.MirroringServiceConnection 62 import com.android.car.carlauncher.datasources.LauncherActivitiesDataSource 63 import com.android.car.carlauncher.datasources.LauncherActivitiesDataSourceImpl 64 import com.android.car.carlauncher.datasources.MediaTemplateAppsDataSource 65 import com.android.car.carlauncher.datasources.MediaTemplateAppsDataSourceImpl 66 import com.android.car.carlauncher.datasources.UXRestrictionDataSource 67 import com.android.car.carlauncher.datasources.UXRestrictionDataSourceImpl 68 import com.android.car.carlauncher.datasources.restricted.DisabledAppsDataSource 69 import com.android.car.carlauncher.datasources.restricted.DisabledAppsDataSourceImpl 70 import com.android.car.carlauncher.datasources.restricted.TosDataSource 71 import com.android.car.carlauncher.datasources.restricted.TosDataSourceImpl 72 import com.android.car.carlauncher.datastore.launcheritem.LauncherItemListSource 73 import com.android.car.carlauncher.pagination.PageMeasurementHelper 74 import com.android.car.carlauncher.pagination.PaginationController 75 import com.android.car.carlauncher.pagination.PaginationController.DimensionUpdateCallback 76 import com.android.car.carlauncher.pagination.PaginationController.DimensionUpdateListener 77 import com.android.car.carlauncher.recyclerview.AppGridAdapter 78 import com.android.car.carlauncher.recyclerview.AppGridAdapter.AppGridAdapterListener 79 import com.android.car.carlauncher.recyclerview.AppGridItemAnimator 80 import com.android.car.carlauncher.recyclerview.AppGridLayoutManager 81 import com.android.car.carlauncher.recyclerview.AppItemViewHolder.AppItemDragCallback 82 import com.android.car.carlauncher.recyclerview.AppItemViewHolder.AppItemDragListener 83 import com.android.car.carlauncher.repositories.AppGridRepository 84 import com.android.car.carlauncher.repositories.AppGridRepositoryImpl 85 import com.android.car.carlauncher.repositories.appactions.AppLaunchProviderFactory 86 import com.android.car.carlauncher.repositories.appactions.AppShortcutsFactory 87 import com.android.car.carlauncher.repositories.appactions.AppShortcutsFactory.ShortcutsListener 88 import com.android.car.hidden.apis.HiddenApiAccess 89 import com.android.car.ui.shortcutspopup.CarUiShortcutsPopup 90 import kotlinx.coroutines.Dispatchers.Default 91 import kotlinx.coroutines.Dispatchers.IO 92 93 /** 94 * Fragment which renders the Apps based on the [Mode] provided in the [setArguments] 95 * 96 * To create an instance of this Fragment use [newInstance] 97 */ 98 class AppGridFragment : Fragment(), PageSnapListener, AppItemDragListener, DimensionUpdateListener, 99 AppGridAdapterListener { 100 101 private lateinit var car: Car 102 private lateinit var mode: Mode 103 private lateinit var snapCallback: AppGridPageSnapCallback 104 private lateinit var dragCallback: AppItemDragCallback 105 private lateinit var appGridDragController: AppGridDragController 106 private lateinit var appGridRecyclerView: AppGridRecyclerView 107 private lateinit var layoutManager: AppGridLayoutManager 108 private lateinit var pageIndicator: PageIndicator 109 private lateinit var adapter: AppGridAdapter 110 private lateinit var paginationController: PaginationController 111 private lateinit var backgroundAnimationHelper: BackgroundAnimationHelper 112 private lateinit var appGridViewModel: AppGridViewModel 113 private lateinit var banner: Banner 114 115 private var appGridMarginHorizontal = 0 116 private var appGridMarginVertical = 0 117 private var appGridWidth = 0 118 private var appGridHeight = 0 119 private var offPageHoverBeforeScrollMs = 0L 120 private var nextScrollDestination = 0 121 private var currentScrollOffset = 0 122 private var currentScrollState = 0 123 private var isCurrentlyDragging = false 124 private var carUiShortcutsPopup: CarUiShortcutsPopup? = null 125 126 @PageOrientation 127 private var pageOrientation = 0 128 129 override fun onCreateView( 130 inflater: LayoutInflater, 131 container: ViewGroup?, 132 savedInstanceState: Bundle? 133 ): View? { 134 super.onCreateView(inflater, container, savedInstanceState) 135 return inflater.inflate(R.layout.app_grid_fragment, container, false) 136 } 137 138 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 139 super.onViewCreated(view, savedInstanceState) 140 car = Car.createCar(requireContext()) ?: throw IllegalStateException("Car not initialized") 141 mode = Mode.valueOf(requireArguments().getString(MODE_INTENT_EXTRA, Mode.ALL_APPS.name)) 142 initViewModel() 143 updateMode(mode) 144 145 snapCallback = AppGridPageSnapCallback(this) 146 dragCallback = AppItemDragCallback(this) 147 148 appGridDragController = AppGridDragController() 149 offPageHoverBeforeScrollMs = resources.getInteger( 150 R.integer.ms_off_page_hover_before_scroll 151 ).toLong() 152 153 pageOrientation = 154 if (resources.getBoolean(R.bool.use_vertical_app_grid)) { 155 PageOrientation.VERTICAL 156 } else { 157 PageOrientation.HORIZONTAL 158 } 159 160 appGridRecyclerView = view.requireViewById(R.id.apps_grid) 161 appGridRecyclerView.isFocusable = false 162 layoutManager = 163 AppGridLayoutManager(requireContext(), pageOrientation) 164 appGridRecyclerView.layoutManager = layoutManager 165 166 val pageSnapper = AppGridPageSnapper( 167 requireContext(), 168 snapCallback 169 ) 170 pageSnapper.attachToRecyclerView(appGridRecyclerView) 171 172 appGridRecyclerView.itemAnimator = AppGridItemAnimator() 173 174 // hide the default scrollbar and replace it with a visual page indicator 175 appGridRecyclerView.isVerticalScrollBarEnabled = false 176 appGridRecyclerView.isHorizontalScrollBarEnabled = false 177 appGridRecyclerView.addOnScrollListener(AppGridOnScrollListener()) 178 179 // TODO: (b/271637411) move this to be contained in a scroll controller 180 pageIndicator = view.requireViewById(R.id.page_indicator) 181 val pageIndicatorContainer: FrameLayout = 182 view.requireViewById(R.id.page_indicator_container) 183 pageIndicator.setContainer(pageIndicatorContainer) 184 185 // recycler view is set to LTR to prevent layout manager from reassigning layout direction. 186 // instead, PageIndexinghelper will determine the grid index based on the system layout 187 // direction and provide LTR mapping at adapter level. 188 appGridRecyclerView.layoutDirection = View.LAYOUT_DIRECTION_LTR 189 pageIndicatorContainer.layoutDirection = View.LAYOUT_DIRECTION_LTR 190 191 adapter = AppGridAdapter( 192 requireContext(), dragCallback, snapCallback, this, mode 193 ) 194 195 adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { 196 override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) { 197 // scroll state will need to be updated after item has been dropped 198 nextScrollDestination = snapCallback.snapPosition 199 updateScrollState() 200 } 201 }) 202 appGridRecyclerView.adapter = adapter 203 204 appGridViewModel.getAppList().asLiveData().observe( 205 viewLifecycleOwner 206 ) { appItems: List<AppItem?>? -> 207 adapter.setLauncherItems(appItems) 208 nextScrollDestination = snapCallback.snapPosition 209 updateScrollState() 210 } 211 212 appGridViewModel.requiresDistractionOptimization().asLiveData().observe( 213 viewLifecycleOwner 214 ) { uxRestrictions: Boolean -> 215 handleDistractionOptimization( 216 uxRestrictions 217 ) 218 } 219 220 // set drag listener and global layout listener, which will dynamically adjust app grid 221 // height and width depending on device screen size. ize. 222 if (resources.getBoolean(R.bool.config_allow_reordering)) { 223 appGridRecyclerView.setOnDragListener(AppGridDragListener()) 224 } 225 226 // since some measurements for window size may not be available yet during onCreate or may 227 // later change, we add a listener that redraws the app grid when window size changes. 228 val windowBackground: LinearLayout = view.requireViewById(R.id.apps_grid_background) 229 windowBackground.orientation = 230 if (isHorizontal(pageOrientation)) LinearLayout.VERTICAL else LinearLayout.HORIZONTAL 231 val dimensionUpdateCallback = DimensionUpdateCallback() 232 dimensionUpdateCallback.addListener(appGridRecyclerView) 233 dimensionUpdateCallback.addListener(pageIndicator) 234 dimensionUpdateCallback.addListener(this) 235 paginationController = 236 PaginationController(windowBackground, dimensionUpdateCallback) 237 238 banner = view.requireViewById(R.id.tos_banner) 239 240 backgroundAnimationHelper = BackgroundAnimationHelper(windowBackground, banner) 241 242 setupTosBanner() 243 } 244 245 /** 246 * Updates the state of the app grid components depending on the driving state. 247 */ 248 private fun handleDistractionOptimization(requiresDistractionOptimization: Boolean) { 249 adapter.setIsDistractionOptimizationRequired(requiresDistractionOptimization) 250 if (requiresDistractionOptimization) { 251 // if the user start driving while drag is in action, we cancel existing drag operations 252 if (isCurrentlyDragging) { 253 isCurrentlyDragging = false 254 layoutManager.setShouldLayoutChildren(true) 255 appGridRecyclerView.cancelDragAndDrop() 256 } 257 dismissShortcutPopup() 258 } 259 } 260 261 private fun initViewModel() { 262 val launcherActivities: LauncherActivitiesDataSource = LauncherActivitiesDataSourceImpl( 263 requireContext().getSystemService(LauncherApps::class.java), 264 { broadcastReceiver: BroadcastReceiver?, intentFilter: IntentFilter? -> 265 requireContext().registerReceiver(broadcastReceiver, intentFilter) 266 }, 267 { broadcastReceiver: BroadcastReceiver? -> 268 requireContext().unregisterReceiver(broadcastReceiver) 269 }, 270 Process.myUserHandle(), 271 requireContext().applicationContext.resources, 272 Default 273 ) 274 val mediaTemplateApps: MediaTemplateAppsDataSource = MediaTemplateAppsDataSourceImpl( 275 requireContext().packageManager, 276 requireContext().applicationContext, 277 Default 278 ) 279 val disabledApps: DisabledAppsDataSource = DisabledAppsDataSourceImpl( 280 requireContext().contentResolver, 281 requireContext().packageManager, 282 IO 283 ) 284 val tosApps: TosDataSource = TosDataSourceImpl( 285 requireContext().contentResolver, 286 requireContext().packageManager, 287 IO 288 ) 289 val controlCenterMirroringDataSource: ControlCenterMirroringDataSource = 290 ControlCenterMirroringDataSourceImpl( 291 requireContext().applicationContext.resources, 292 { intent: Intent, serviceConnection: MirroringServiceConnection, flags: Int -> 293 requireContext().bindService(intent, serviceConnection, flags) 294 }, 295 { serviceConnection: MirroringServiceConnection -> 296 requireContext().unbindService(serviceConnection) 297 }, 298 requireContext().packageManager, 299 IO 300 ) 301 val uxRestrictionDataSource: UXRestrictionDataSource = UXRestrictionDataSourceImpl( 302 requireContext(), 303 requireNotNull(car.getCarManager(CarUxRestrictionsManager::class.java)), 304 requireNotNull(car.getCarManager(CarPackageManager::class.java)), 305 requireContext().getSystemService(MediaSessionManager::class.java), 306 requireContext().applicationContext.resources, 307 Default 308 ) 309 val appOrderDataSource: AppOrderDataSource = AppOrderProtoDataSourceImpl( 310 LauncherItemListSource(requireContext().filesDir, "order.data"), 311 IO 312 ) 313 val packageManager: PackageManager = requireContext().packageManager 314 val launchProviderFactory = AppLaunchProviderFactory( 315 requireNotNull(car.getCarManager(CarMediaManager::class.java)), 316 mode.openMediaCenter, 317 { 318 activity?.finish() 319 }, 320 requireContext().packageManager 321 ) 322 323 val appShortcutsFactory = AppShortcutsFactory( 324 requireNotNull(car.getCarManager(CarMediaManager::class.java)), 325 emptySet(), 326 object : ShortcutsListener { 327 override fun onShortcutsShow(carUiShortcutsPopup: CarUiShortcutsPopup) { 328 this@AppGridFragment.carUiShortcutsPopup = carUiShortcutsPopup 329 } 330 } 331 ) 332 val bgDispatcher = Default 333 val repo: AppGridRepository = AppGridRepositoryImpl( 334 launcherActivities, mediaTemplateApps, 335 disabledApps, tosApps, controlCenterMirroringDataSource, uxRestrictionDataSource, 336 appOrderDataSource, packageManager, launchProviderFactory, appShortcutsFactory, 337 requireContext().getSystemService(UserManager::class.java), bgDispatcher 338 ) 339 340 appGridViewModel = ViewModelProvider( 341 this, 342 provideFactory(repo, requireActivity().application, this, null) 343 )[AppGridViewModel::class.java] 344 } 345 346 private fun animateDropEnded(dragSurface: SurfaceControl?) { 347 if (dragSurface == null) { 348 if (DEBUG_BUILD) { 349 Log.d(TAG, "animateDropEnded, dragSurface unavailable") 350 } 351 return 352 } 353 // update default animation for the drag shadow after user lifts their finger 354 val txn = SurfaceControl.Transaction() 355 // set an animator to animate a delay before clearing the dragSurface 356 val delayedDismissAnimator = ValueAnimator.ofFloat(0f, 1f) 357 delayedDismissAnimator.startDelay = 358 resources.getInteger(R.integer.ms_drop_animation_delay).toLong() 359 delayedDismissAnimator.addUpdateListener { 360 txn.setAlpha(dragSurface, 0f) 361 txn.apply() 362 } 363 delayedDismissAnimator.start() 364 } 365 366 private fun setupTosBanner() { 367 appGridViewModel.getShouldShowTosBanner().asLiveData() 368 .observe( 369 viewLifecycleOwner 370 ) { showBanner: Boolean -> 371 if (showBanner) { 372 banner.visibility = View.VISIBLE 373 // Pre draw is required for animation to work. 374 banner.viewTreeObserver.addOnPreDrawListener( 375 object : ViewTreeObserver.OnPreDrawListener { 376 override fun onPreDraw(): Boolean { 377 banner.viewTreeObserver.removeOnPreDrawListener(this) 378 backgroundAnimationHelper.showBanner() 379 return true 380 } 381 } 382 ) 383 } else { 384 banner.visibility = View.GONE 385 } 386 } 387 banner.setFirstButtonOnClickListener { v: View -> 388 val tosIntent = 389 AppLauncherUtils.getIntentForTosAcceptanceFlow(v.context) 390 AppLauncherUtils.launchApp(v.context, tosIntent) 391 } 392 banner.setSecondButtonOnClickListener { _ -> 393 backgroundAnimationHelper.hideBanner() 394 appGridViewModel.saveTosBannerDismissalTime() 395 } 396 } 397 398 /** 399 * Updates the scroll state after receiving data changes, such as new apps being added or 400 * reordered, and when user returns to launcher onResume. 401 * 402 * Additionally, notify page indicator to handle resizing in case new app addition creates a 403 * new page or deleted a page. 404 */ 405 fun updateScrollState() { 406 // TODO(b/271637411): move this method into a scroll controller 407 // to calculate how many pages we need to offset, we use the scroll offset anchor position 408 // as item count and map to the page which the anchor is on. 409 val offsetPageCount = adapter.getPageCount(nextScrollDestination + 1) - 1 410 appGridRecyclerView.suppressLayout(false) 411 currentScrollOffset = 412 offsetPageCount * if (isHorizontal(pageOrientation)) { 413 appGridWidth + 2 * appGridMarginHorizontal 414 } else { 415 appGridHeight + 2 * appGridMarginVertical 416 } 417 layoutManager.scrollToPositionWithOffset( 418 offsetPageCount * appGridRecyclerView.numOfRows * appGridRecyclerView.numOfCols, 419 0 420 ) 421 pageIndicator.updateOffset(currentScrollOffset) 422 pageIndicator.updatePageCount(adapter.pageCount) 423 } 424 425 /** 426 * Change the mode of the apps shown in the AppGrid 427 * @see [Mode] 428 */ 429 fun updateMode(mode: Mode) { 430 this.mode = mode 431 appGridViewModel.updateMode(mode) 432 } 433 434 private inner class AppGridOnScrollListener : RecyclerView.OnScrollListener() { 435 override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { 436 currentScrollOffset += if (isHorizontal(pageOrientation)) dx else dy 437 pageIndicator.updateOffset(currentScrollOffset) 438 } 439 440 override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { 441 currentScrollState = newState 442 snapCallback.scrollState = currentScrollState 443 when (newState) { 444 RecyclerView.SCROLL_STATE_DRAGGING -> { 445 if (!isCurrentlyDragging) { 446 dragCallback.cancelDragTasks() 447 } 448 dismissShortcutPopup() 449 pageIndicator.animateAppearance() 450 } 451 452 RecyclerView.SCROLL_STATE_SETTLING -> pageIndicator.animateAppearance() 453 RecyclerView.SCROLL_STATE_IDLE -> { 454 if (isCurrentlyDragging) { 455 layoutManager.setShouldLayoutChildren(false) 456 } 457 pageIndicator.animateFading() 458 // in case the recyclerview was scrolled by rotary input, we need to handle 459 // focusing the correct element: either on the first or last element on page 460 appGridRecyclerView.maybeHandleRotaryFocus() 461 } 462 } 463 } 464 } 465 466 private fun dismissShortcutPopup() { 467 carUiShortcutsPopup?.let { 468 it.dismiss() 469 carUiShortcutsPopup = null 470 } 471 } 472 473 override fun onPause() { 474 dismissShortcutPopup() 475 super.onPause() 476 } 477 478 override fun onDestroy() { 479 if (car.isConnected) { 480 car.disconnect() 481 } 482 super.onDestroy() 483 } 484 485 override fun onSnapToPosition(gridPosition: Int) { 486 nextScrollDestination = gridPosition 487 } 488 489 override fun onDimensionsUpdated( 490 pageDimens: PageMeasurementHelper.PageDimensions, 491 gridDimens: PageMeasurementHelper.GridDimensions 492 ) { 493 // TODO(b/271637411): move this method into a scroll controller 494 appGridMarginHorizontal = pageDimens.marginHorizontalPx 495 appGridMarginVertical = pageDimens.marginVerticalPx 496 appGridWidth = gridDimens.gridWidthPx 497 appGridHeight = gridDimens.gridHeightPx 498 } 499 500 override fun onAppPositionChanged(newPosition: Int, appItem: AppItem) { 501 appGridViewModel.saveAppOrder(newPosition, appItem) 502 } 503 504 override fun onItemLongPressed(longPressed: Boolean) { 505 // after the user long presses the app icon, scrolling should be disabled until long press 506 // is canceled as to allow MotionEvent to be interpreted as attempt to drag the app icon. 507 appGridRecyclerView.suppressLayout(longPressed) 508 } 509 510 override fun onItemSelected(gridPositionFrom: Int) { 511 isCurrentlyDragging = true 512 layoutManager.setShouldLayoutChildren(false) 513 adapter.setDragStartPoint(gridPositionFrom) 514 dismissShortcutPopup() 515 } 516 517 override fun onItemDragged() { 518 appGridDragController.cancelDelayedPageFling() 519 } 520 521 override fun onDragExited(gridPosition: Int, exitDirection: Int) { 522 if (adapter.getOffsetBoundDirection(gridPosition) == exitDirection) { 523 appGridDragController.postDelayedPageFling(exitDirection) 524 } 525 } 526 527 override fun onItemDropped(gridPositionFrom: Int, gridPositionTo: Int) { 528 layoutManager.setShouldLayoutChildren(true) 529 adapter.moveAppItem(gridPositionFrom, gridPositionTo) 530 } 531 532 private inner class AppGridDragController() { 533 // TODO: (b/271320404) move DragController to separate directory called dragndrop and 534 // migrate logic this class and AppItemViewHolder there. 535 private val handler: Handler = Handler(getMainLooper()) 536 537 fun cancelDelayedPageFling() { 538 handler.removeCallbacksAndMessages(null) 539 } 540 541 fun postDelayedPageFling(@AppItemBoundDirection exitDirection: Int) { 542 val scrollToNextPage = 543 if (isHorizontal(pageOrientation)) { 544 exitDirection == AppItemBoundDirection.RIGHT 545 } else { 546 exitDirection == AppItemBoundDirection.BOTTOM 547 } 548 handler.removeCallbacksAndMessages(null) 549 handler.postDelayed({ 550 if (currentScrollState == RecyclerView.SCROLL_STATE_IDLE) { 551 adapter.updatePageScrollDestination(scrollToNextPage) 552 nextScrollDestination = snapCallback.snapPosition 553 layoutManager.setShouldLayoutChildren(true) 554 appGridRecyclerView.smoothScrollToPosition(nextScrollDestination) 555 } 556 // another delayed scroll will be queued to enable the user to input multiple 557 // page scrolls by holding the recyclerview at the app grid margin 558 postDelayedPageFling(exitDirection) 559 }, offPageHoverBeforeScrollMs) 560 } 561 } 562 563 /** 564 * Private onDragListener for handling dispatching off page scroll event when user holds the app 565 * icon at the page margin. 566 */ 567 private inner class AppGridDragListener : View.OnDragListener { 568 override fun onDrag(v: View, event: DragEvent): Boolean { 569 val action = event.action 570 if (action == DragEvent.ACTION_DROP || action == DragEvent.ACTION_DRAG_ENDED) { 571 isCurrentlyDragging = false 572 appGridDragController.cancelDelayedPageFling() 573 dragCallback.resetCallbackState() 574 layoutManager.setShouldLayoutChildren(true) 575 if (action == DragEvent.ACTION_DROP) { 576 return false 577 } else { 578 animateDropEnded(HiddenApiAccess.getDragSurface(event)) 579 } 580 } 581 return true 582 } 583 } 584 585 annotation class AppTypes { 586 companion object { 587 const val APP_TYPE_LAUNCHABLES = 1 588 const val APP_TYPE_MEDIA_SERVICES = 2 589 } 590 } 591 592 enum class Mode( 593 @field:StringRes @param:StringRes val titleStringId: Int, 594 @field:AppTypes @param:AppTypes val appTypes: Int, 595 val openMediaCenter: Boolean 596 ) { 597 ALL_APPS( 598 R.string.app_launcher_title_all_apps, 599 APP_TYPE_LAUNCHABLES + APP_TYPE_MEDIA_SERVICES, 600 true 601 ), 602 MEDIA_ONLY( 603 R.string.app_launcher_title_media_only, 604 APP_TYPE_MEDIA_SERVICES, 605 true 606 ), 607 MEDIA_POPUP( 608 R.string.app_launcher_title_media_only, 609 APP_TYPE_MEDIA_SERVICES, 610 false 611 ) 612 } 613 614 companion object { 615 const val TAG = "AppGridFragment" 616 const val DEBUG_BUILD = false 617 const val MODE_INTENT_EXTRA = "com.android.car.carlauncher.mode" 618 619 @JvmStatic 620 fun newInstance(mode: Mode): AppGridFragment { 621 return AppGridFragment().apply { 622 arguments = Bundle().apply { 623 putString(MODE_INTENT_EXTRA, mode.name) 624 } 625 } 626 } 627 } 628 } 629