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 static final RecentsStatsLogHelper sStatsLogHelper = 56 RecentsStatsLogHelper.getInstance(); 57 private RecentsRecyclerView mRecentsRecyclerView; 58 private GridLayoutManager mGridLayoutManager; 59 private RecentTasksViewModel mRecentTasksViewModel; 60 private Group mRecentTasksGroup; 61 private View mEmptyStateView; 62 private Animator mClearAllAnimator; 63 private NonDODisabledTaskProvider mNonDODisabledTaskProvider; 64 private Set<String> mPackagesToHideFromRecents; 65 private boolean mLaunchMostRecentTaskOnDismiss; 66 67 @Override onCreate(Bundle savedInstanceState)68 protected void onCreate(Bundle savedInstanceState) { 69 super.onCreate(savedInstanceState); 70 setContentView(R.layout.recents_activity); 71 mRecentsRecyclerView = findViewById(R.id.recent_tasks_list); 72 mRecentTasksGroup = findViewById(R.id.recent_tasks_group); 73 mEmptyStateView = findViewById(R.id.empty_state); 74 mPackagesToHideFromRecents = new HashSet<>(List.of(getResources().getStringArray( 75 R.array.packages_hidden_from_recents))); 76 mRecentTasksViewModel = RecentTasksViewModel.getInstance(); 77 mRecentTasksViewModel.addRecentTasksChangeListener(this); 78 mRecentTasksViewModel.addHiddenTaskProvider( 79 (packageName, className, baseIntent) -> 80 mPackagesToHideFromRecents.contains(packageName)); 81 mNonDODisabledTaskProvider = new NonDODisabledTaskProvider(this); 82 mRecentTasksViewModel.setDisabledTaskProvider(mNonDODisabledTaskProvider); 83 WindowMetrics windowMetrics = this.getWindowManager().getCurrentWindowMetrics(); 84 mLaunchMostRecentTaskOnDismiss = getResources().getBoolean( 85 R.bool.config_launch_most_recent_task_on_recents_dismiss); 86 mRecentTasksViewModel.init( 87 /* displayId= */ getDisplay().getDisplayId(), 88 /* windowWidth= */ windowMetrics.getBounds().width(), 89 /* windowHeight= */ windowMetrics.getBounds().height(), 90 /* windowInsets= */ windowMetrics.getWindowInsets() 91 .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()).toRect(), 92 /* defaultThumbnailColor= */ 93 getResources().getColor(R.color.default_recents_thumbnail_color, /* theme= */null)); 94 95 if (!(mRecentsRecyclerView.getLayoutManager() instanceof GridLayoutManager)) { 96 throw new UnsupportedOperationException( 97 "Only classes that inherit GridLayoutManager are supported"); 98 } 99 // getting an instance initializes StatsLogHelper 100 sStatsLogHelper.setPackageManager(getPackageManager()); 101 102 mGridLayoutManager = (GridLayoutManager) mRecentsRecyclerView.getLayoutManager(); 103 int gridSpanCount = mGridLayoutManager.getSpanCount(); 104 mGridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { 105 @Override 106 public int getSpanSize(int position) { 107 boolean isLastPosition = mRecentsRecyclerView != null 108 && mRecentsRecyclerView.getAdapter() != null 109 && position == mRecentsRecyclerView.getAdapter().getItemCount() - 1; 110 if (position == 0 || isLastPosition) { 111 return gridSpanCount; 112 } 113 return 1; 114 } 115 }); 116 117 int colSpacing = getResources().getDimensionPixelSize(R.dimen.recent_task_col_space); 118 mRecentsRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() { 119 @Override 120 public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, 121 @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { 122 outRect.right = colSpacing / 2; 123 outRect.left = colSpacing / 2; 124 } 125 }); 126 127 ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new TaskTouchHelperCallback( 128 /* dragDirs= */ 0, ItemTouchHelper.UP, 129 getResources().getFloat(R.dimen.recent_task_swiped_threshold))); 130 itemTouchHelper.attachToRecyclerView(mRecentsRecyclerView); 131 132 mClearAllAnimator = AnimatorInflater.loadAnimator(this, 133 R.animator.recents_clear_all); 134 mClearAllAnimator.addListener(new AnimatorListenerAdapter() { 135 @Override 136 public void onAnimationEnd(Animator animation) { 137 super.onAnimationEnd(animation); 138 mRecentTasksViewModel.removeAllRecentTasks(); 139 launchHomeIntent(); 140 } 141 }); 142 mClearAllAnimator.setTarget(mRecentsRecyclerView); 143 View.OnClickListener clearAllOnClickListener = v -> mClearAllAnimator.start(); 144 145 mRecentsRecyclerView.setAdapter(new RecentTasksAdapter(this, getLayoutInflater(), 146 itemTouchHelper, clearAllOnClickListener)); 147 } 148 149 @Override onResume()150 protected void onResume() { 151 super.onResume(); 152 if (OPEN_RECENT_TASK_ACTION.equals(getIntent().getAction())) { 153 if (mLaunchMostRecentTaskOnDismiss) { 154 mRecentTasksViewModel.openMostRecentTask(); 155 } 156 return; 157 } 158 mRecentTasksViewModel.fetchRecentTaskList(); 159 resetViewState(); 160 mRecentsRecyclerView.resetPadding(); 161 sStatsLogHelper.logSessionStarted(); 162 } 163 164 @Override onNewIntent(Intent intent)165 protected void onNewIntent(Intent intent) { 166 super.onNewIntent(intent); 167 setIntent(intent); 168 } 169 170 @Override onStop()171 protected void onStop() { 172 super.onStop(); 173 sStatsLogHelper.logSessionFinished(); 174 mRecentTasksViewModel.clearCache(); 175 } 176 177 @Override onDestroy()178 protected void onDestroy() { 179 super.onDestroy(); 180 mNonDODisabledTaskProvider.terminate(); 181 mRecentTasksViewModel.terminate(); 182 if (mClearAllAnimator.isRunning()) { 183 mClearAllAnimator.end(); 184 } 185 mClearAllAnimator.removeAllListeners(); 186 } 187 188 @Override onRecentTasksFetched()189 public void onRecentTasksFetched() { 190 resetViewState(); 191 mRecentsRecyclerView.resetPadding(); 192 } 193 194 @Override onOpenRecentTaskFail()195 public void onOpenRecentTaskFail() { 196 // notify the user when there is a failure to open a task from recents 197 Toast.makeText(this, R.string.failure_opening_recent_task_message, LENGTH_SHORT).show(); 198 } 199 200 @Override onOpenTopRunningTaskFail()201 public void onOpenTopRunningTaskFail() { 202 launchHomeIntent(); 203 } 204 205 @Override onRecentTaskRemoved(int position)206 public void onRecentTaskRemoved(int position) { 207 mRecentsRecyclerView.resetPadding(); 208 if (mRecentTasksViewModel.getRecentTasksSize() == 0) { 209 launchHomeIntent(); 210 } 211 } 212 213 /** 214 * Launches the Home Activity. 215 */ launchHomeIntent()216 protected void launchHomeIntent() { 217 Intent homeActivityIntent = new Intent(Intent.ACTION_MAIN); 218 homeActivityIntent.addCategory(Intent.CATEGORY_HOME); 219 homeActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 220 startActivity(homeActivityIntent); 221 } 222 resetViewState()223 private void resetViewState() { 224 boolean isRecentTaskListEmpty = mRecentTasksViewModel.getRecentTasksSize() == 0; 225 if (!isRecentTaskListEmpty) { 226 mRecentsRecyclerView.setAlpha(1f); 227 mGridLayoutManager.scrollToPositionWithOffset(/* position= */ 0, /* offset= */ 0); 228 } 229 mRecentTasksGroup.setVisibility(isRecentTaskListEmpty ? View.GONE : View.VISIBLE); 230 mEmptyStateView.setVisibility(isRecentTaskListEmpty ? View.VISIBLE : View.GONE); 231 } 232 } 233