1 /* 2 * Copyright (C) 2023 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.recents; 18 19 import static android.widget.Toast.LENGTH_SHORT; 20 21 import android.animation.Animator; 22 import android.animation.AnimatorInflater; 23 import android.animation.AnimatorListenerAdapter; 24 import android.content.Intent; 25 import android.graphics.Rect; 26 import android.os.Bundle; 27 import android.view.View; 28 import android.view.WindowInsets; 29 import android.view.WindowMetrics; 30 import android.widget.Toast; 31 32 import androidx.annotation.NonNull; 33 import androidx.appcompat.app.AppCompatActivity; 34 import androidx.constraintlayout.widget.Group; 35 import androidx.recyclerview.widget.GridLayoutManager; 36 import androidx.recyclerview.widget.ItemTouchHelper; 37 import androidx.recyclerview.widget.RecyclerView; 38 39 import com.android.car.carlauncher.R; 40 import com.android.car.carlauncher.recents.view.RecentTasksAdapter; 41 import com.android.car.carlauncher.recents.view.RecentsRecyclerView; 42 import com.android.car.carlauncher.recents.view.TaskTouchHelperCallback; 43 44 import java.util.HashSet; 45 import java.util.List; 46 import java.util.Set; 47 48 /** 49 * Recents activity to display list of recent tasks in Car. 50 */ 51 public class CarRecentsActivity extends AppCompatActivity implements 52 RecentTasksViewModel.RecentTasksChangeListener { 53 public static final String OPEN_RECENT_TASK_ACTION = 54 "com.android.car.carlauncher.recents.OPEN_RECENT_TASK_ACTION"; 55 private RecentsRecyclerView mRecentsRecyclerView; 56 private GridLayoutManager mGridLayoutManager; 57 private RecentTasksViewModel mRecentTasksViewModel; 58 private Group mRecentTasksGroup; 59 private View mEmptyStateView; 60 private Animator mClearAllAnimator; 61 private NonDODisabledTaskProvider mNonDODisabledTaskProvider; 62 private Set<String> mPackagesToHideFromRecents; 63 64 @Override onCreate(Bundle savedInstanceState)65 protected void onCreate(Bundle savedInstanceState) { 66 super.onCreate(savedInstanceState); 67 setContentView(R.layout.recents_activity); 68 mRecentsRecyclerView = findViewById(R.id.recent_tasks_list); 69 mRecentTasksGroup = findViewById(R.id.recent_tasks_group); 70 mEmptyStateView = findViewById(R.id.empty_state); 71 mPackagesToHideFromRecents = new HashSet<>(List.of(getResources().getStringArray( 72 R.array.packages_hidden_from_recents))); 73 mRecentTasksViewModel = RecentTasksViewModel.getInstance(); 74 mRecentTasksViewModel.addRecentTasksChangeListener(this); 75 mRecentTasksViewModel.addHiddenTaskProvider( 76 (packageName, className, baseIntent) -> 77 mPackagesToHideFromRecents.contains(packageName)); 78 mNonDODisabledTaskProvider = new NonDODisabledTaskProvider(this); 79 mRecentTasksViewModel.setDisabledTaskProvider(mNonDODisabledTaskProvider); 80 WindowMetrics windowMetrics = this.getWindowManager().getCurrentWindowMetrics(); 81 mRecentTasksViewModel.init( 82 /* displayId= */ getDisplay().getDisplayId(), 83 /* windowWidth= */ windowMetrics.getBounds().width(), 84 /* windowHeight= */ windowMetrics.getBounds().height(), 85 /* windowInsets= */ windowMetrics.getWindowInsets() 86 .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()).toRect(), 87 /* defaultThumbnailColor= */ 88 getResources().getColor(R.color.default_recents_thumbnail_color, /* theme= */null)); 89 90 if (!(mRecentsRecyclerView.getLayoutManager() instanceof GridLayoutManager)) { 91 throw new UnsupportedOperationException( 92 "Only classes that inherit GridLayoutManager are supported"); 93 } 94 mGridLayoutManager = (GridLayoutManager) mRecentsRecyclerView.getLayoutManager(); 95 int gridSpanCount = mGridLayoutManager.getSpanCount(); 96 mGridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { 97 @Override 98 public int getSpanSize(int position) { 99 boolean isLastPosition = mRecentsRecyclerView != null 100 && mRecentsRecyclerView.getAdapter() != null 101 && position == mRecentsRecyclerView.getAdapter().getItemCount() - 1; 102 if (position == 0 || isLastPosition) { 103 return gridSpanCount; 104 } 105 return 1; 106 } 107 }); 108 109 int colSpacing = getResources().getDimensionPixelSize(R.dimen.recent_task_col_space); 110 mRecentsRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() { 111 @Override 112 public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, 113 @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { 114 outRect.right = colSpacing / 2; 115 outRect.left = colSpacing / 2; 116 } 117 }); 118 119 ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new TaskTouchHelperCallback( 120 /* dragDirs= */ 0, ItemTouchHelper.UP, 121 getResources().getFloat(R.dimen.recent_task_swiped_threshold))); 122 itemTouchHelper.attachToRecyclerView(mRecentsRecyclerView); 123 124 mClearAllAnimator = AnimatorInflater.loadAnimator(this, 125 R.animator.recents_clear_all); 126 mClearAllAnimator.addListener(new AnimatorListenerAdapter() { 127 @Override 128 public void onAnimationEnd(Animator animation) { 129 super.onAnimationEnd(animation); 130 mRecentTasksViewModel.removeAllRecentTasks(); 131 launchHomeIntent(); 132 } 133 }); 134 mClearAllAnimator.setTarget(mRecentsRecyclerView); 135 View.OnClickListener clearAllOnClickListener = v -> mClearAllAnimator.start(); 136 137 mRecentsRecyclerView.setAdapter(new RecentTasksAdapter(this, getLayoutInflater(), 138 itemTouchHelper, clearAllOnClickListener)); 139 } 140 141 @Override onResume()142 protected void onResume() { 143 super.onResume(); 144 if (OPEN_RECENT_TASK_ACTION.equals(getIntent().getAction())) { 145 mRecentTasksViewModel.openMostRecentTask(); 146 return; 147 } 148 mRecentTasksViewModel.fetchRecentTaskList(); 149 resetViewState(); 150 mRecentsRecyclerView.resetPadding(); 151 } 152 153 @Override onNewIntent(Intent intent)154 protected void onNewIntent(Intent intent) { 155 super.onNewIntent(intent); 156 setIntent(intent); 157 } 158 159 @Override onStop()160 protected void onStop() { 161 super.onStop(); 162 mRecentTasksViewModel.clearCache(); 163 } 164 165 @Override onDestroy()166 protected void onDestroy() { 167 super.onDestroy(); 168 mNonDODisabledTaskProvider.terminate(); 169 mRecentTasksViewModel.terminate(); 170 mClearAllAnimator.end(); 171 mClearAllAnimator.removeAllListeners(); 172 } 173 174 @Override onRecentTasksFetched()175 public void onRecentTasksFetched() { 176 resetViewState(); 177 mRecentsRecyclerView.resetPadding(); 178 } 179 180 @Override onOpenRecentTaskFail()181 public void onOpenRecentTaskFail() { 182 // notify the user when there is a failure to open a task from recents 183 Toast.makeText(this, R.string.failure_opening_recent_task_message, LENGTH_SHORT).show(); 184 } 185 186 @Override onOpenTopRunningTaskFail()187 public void onOpenTopRunningTaskFail() { 188 launchHomeIntent(); 189 } 190 191 @Override onRecentTaskRemoved(int position)192 public void onRecentTaskRemoved(int position) { 193 mRecentsRecyclerView.resetPadding(); 194 if (mRecentTasksViewModel.getRecentTasksSize() == 0) { 195 launchHomeIntent(); 196 } 197 } 198 199 /** 200 * Launches the Home Activity. 201 */ launchHomeIntent()202 protected void launchHomeIntent() { 203 Intent homeActivityIntent = new Intent(Intent.ACTION_MAIN); 204 homeActivityIntent.addCategory(Intent.CATEGORY_HOME); 205 homeActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 206 startActivity(homeActivityIntent); 207 } 208 resetViewState()209 private void resetViewState() { 210 boolean isRecentTaskListEmpty = mRecentTasksViewModel.getRecentTasksSize() == 0; 211 if (!isRecentTaskListEmpty) { 212 mRecentsRecyclerView.setAlpha(1f); 213 mGridLayoutManager.scrollToPositionWithOffset(/* position= */ 0, /* offset= */ 0); 214 } 215 mRecentTasksGroup.setVisibility(isRecentTaskListEmpty ? View.GONE : View.VISIBLE); 216 mEmptyStateView.setVisibility(isRecentTaskListEmpty ? View.VISIBLE : View.GONE); 217 } 218 } 219