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