• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.car.carlauncher;
18 
19 import android.annotation.Nullable;
20 import android.app.Activity;
21 import android.app.usage.UsageStats;
22 import android.app.usage.UsageStatsManager;
23 import android.car.Car;
24 import android.car.CarNotConnectedException;
25 import android.car.content.pm.CarPackageManager;
26 import android.car.drivingstate.CarUxRestrictionsManager;
27 import android.content.BroadcastReceiver;
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.IntentFilter;
32 import android.content.ServiceConnection;
33 import android.content.pm.ActivityInfo;
34 import android.content.pm.LauncherApps;
35 import android.content.pm.PackageManager;
36 import android.graphics.drawable.Drawable;
37 import android.os.Bundle;
38 import android.os.IBinder;
39 import android.support.v7.widget.GridLayoutManager;
40 import android.support.v7.widget.GridLayoutManager.SpanSizeLookup;
41 import android.text.TextUtils;
42 import android.text.format.DateUtils;
43 import android.util.Log;
44 import android.view.View;
45 
46 import java.util.ArrayList;
47 import java.util.Collections;
48 import java.util.Comparator;
49 import java.util.List;
50 
51 import androidx.car.widget.PagedListView;
52 
53 /**
54  * Launcher activity that shows a grid of apps.
55  */
56 public final class AppGridActivity extends Activity {
57 
58     private static final String TAG = "AppGridActivity";
59 
60     private int mColumnNumber;
61     private AppGridAdapter mGridAdapter;
62     private PackageManager mPackageManager;
63     private UsageStatsManager mUsageStatsManager;
64     private AppInstallUninstallReceiver mInstallUninstallReceiver;
65     private Car mCar;
66     private CarUxRestrictionsManager mCarUxRestrictionsManager;
67     private CarPackageManager mCarPackageManager;
68 
69     private ServiceConnection mCarConnectionListener = new ServiceConnection() {
70         @Override
71         public void onServiceConnected(ComponentName name, IBinder service) {
72             try {
73                 mCarUxRestrictionsManager = (CarUxRestrictionsManager) mCar.getCarManager(
74                         Car.CAR_UX_RESTRICTION_SERVICE);
75                 mGridAdapter.setIsDistractionOptimizationRequired(
76                         mCarUxRestrictionsManager
77                                 .getCurrentCarUxRestrictions()
78                                 .isRequiresDistractionOptimization());
79                 mCarUxRestrictionsManager.registerListener(
80                         restrictionInfo ->
81                                 mGridAdapter.setIsDistractionOptimizationRequired(
82                                         restrictionInfo.isRequiresDistractionOptimization()));
83 
84                 mCarPackageManager = (CarPackageManager) mCar.getCarManager(Car.PACKAGE_SERVICE);
85                 mGridAdapter.setAllApps(getAllApps());
86                 mGridAdapter.setMostRecentApps(getMostRecentApps());
87 
88             } catch (CarNotConnectedException e) {
89                 Log.e(TAG, "Car not connected in CarConnectionListener", e);
90             }
91         }
92 
93         @Override
94         public void onServiceDisconnected(ComponentName name) {
95             mCarUxRestrictionsManager = null;
96             mCarPackageManager = null;
97         }
98     };
99 
100     @Override
onCreate(@ullable Bundle savedInstanceState)101     protected void onCreate(@Nullable Bundle savedInstanceState) {
102         super.onCreate(savedInstanceState);
103         mColumnNumber = getResources().getInteger(R.integer.car_app_selector_column_number);
104         mPackageManager = getPackageManager();
105         mUsageStatsManager = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
106         mCar = Car.createCar(this, mCarConnectionListener);
107 
108         setContentView(R.layout.app_grid_activity);
109 
110         findViewById(R.id.exit_button_container).setOnClickListener(v -> finish());
111 
112         findViewById(R.id.search_button_container).setOnClickListener((View view) -> {
113             Intent intent = new Intent(this, AppSearchActivity.class);
114             startActivity(intent);
115         });
116 
117         mGridAdapter = new AppGridAdapter(this);
118         PagedListView gridView = findViewById(R.id.apps_grid);
119 
120         GridLayoutManager gridLayoutManager = new GridLayoutManager(this, mColumnNumber);
121         gridLayoutManager.setSpanSizeLookup(new SpanSizeLookup() {
122             @Override
123             public int getSpanSize(int position) {
124                 return mGridAdapter.getSpanSizeLookup(position);
125             }
126         });
127         gridView.getRecyclerView().setLayoutManager(gridLayoutManager);
128 
129         gridView.setAdapter(mGridAdapter);
130     }
131 
132     @Override
onResume()133     protected void onResume() {
134         super.onResume();
135         mGridAdapter.setAllApps(getAllApps());
136         // using onResume() to refresh most recently used apps because we want to refresh even if
137         // the app being launched crashes/doesn't cover the entire screen.
138         mGridAdapter.setMostRecentApps(getMostRecentApps());
139     }
140 
141     @Override
onStart()142     protected void onStart() {
143         super.onStart();
144         // register broadcast receiver for package installation and uninstallation
145         mInstallUninstallReceiver = new AppInstallUninstallReceiver();
146         IntentFilter filter = new IntentFilter();
147         filter.addAction(Intent.ACTION_PACKAGE_ADDED);
148         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
149         filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
150         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
151         filter.addDataScheme("package");
152         registerReceiver(mInstallUninstallReceiver, filter);
153 
154         // Connect to car service
155         mCar.connect();
156     }
157 
158     @Override
onStop()159     protected void onStop() {
160         super.onPause();
161         // disconnect from app install/uninstall receiver
162         if (mInstallUninstallReceiver != null) {
163             unregisterReceiver(mInstallUninstallReceiver);
164             mInstallUninstallReceiver = null;
165         }
166         // disconnect from car listeners
167         try {
168             if (mCarUxRestrictionsManager != null) {
169                 mCarUxRestrictionsManager.unregisterListener();
170             }
171         } catch (CarNotConnectedException e) {
172             Log.e(TAG, "Error unregistering listeners", e);
173         }
174         if (mCar != null) {
175             mCar.disconnect();
176         }
177     }
178 
getMostRecentApps()179     private List<AppMetaData> getMostRecentApps() {
180         ArrayList<AppMetaData> apps = new ArrayList<>();
181 
182         // get the usage stats starting from 1 year ago with a INTERVAL_YEARLY granularity
183         // returning entries like:
184         // "During 2017 App A is last used at 2017/12/15 18:03"
185         // "During 2017 App B is last used at 2017/6/15 10:00"
186         // "During 2018 App A is last used at 2018/1/1 15:12"
187         List<UsageStats> stats =
188                 mUsageStatsManager.queryUsageStats(
189                         UsageStatsManager.INTERVAL_YEARLY,
190                         System.currentTimeMillis() - DateUtils.YEAR_IN_MILLIS,
191                         System.currentTimeMillis());
192 
193         if (stats == null || stats.size() == 0) {
194             return apps; // empty list
195         }
196 
197         Collections.sort(stats, new LastTimeUsedComparator());
198 
199         int currentIndex = 0;
200         int itemsAdded = 0;
201         int statsSize = stats.size();
202         int itemCount = Math.min(mColumnNumber, statsSize);
203         while (itemsAdded < itemCount && currentIndex < statsSize) {
204             String packageName = stats.get(currentIndex).getPackageName();
205             currentIndex++;
206 
207             // do not include self
208             if (packageName.equals(getPackageName())) {
209                 continue;
210             }
211 
212             // do not include apps that don't support starting from launcher
213             Intent intent = getPackageManager().getLaunchIntentForPackage(packageName);
214             if (intent == null || !intent.hasCategory(Intent.CATEGORY_LAUNCHER)) {
215                 continue;
216             }
217 
218             try {
219                 // try getting activity info from package name
220                 Drawable icon = mPackageManager.getActivityIcon(intent);
221                 ActivityInfo info = mPackageManager.getActivityInfo(intent.getComponent(), 0);
222                 CharSequence displayName = info.loadLabel(mPackageManager);
223                 if (icon == null || TextUtils.isEmpty(displayName)) {
224                     continue;
225                 }
226                 boolean isDistractionOptimized = AppLauncherUtils.isActivityDistractionOptimized(
227                         mCarPackageManager, packageName, intent.getComponent().getClassName());
228                 AppMetaData app =
229                         new AppMetaData(displayName, packageName, icon, isDistractionOptimized);
230 
231                 // edge case: do not include duplicated entries
232                 // e.g. app is used at 2017/12/31 23:59, and 2018/01/01 00:00
233                 if (apps.contains(app)) {
234                     continue;
235                 }
236 
237                 apps.add(app);
238                 itemsAdded++;
239             } catch (PackageManager.NameNotFoundException e) {
240                 // this should never happen
241                 Log.e(TAG, "NameNotFoundException when getting app icon in AppGridActivity", e);
242             }
243         }
244         return apps;
245     }
246 
247     /**
248      * Comparator for {@link UsageStats} that sorts the list by the "last time used" property
249      * in descending order.
250      */
251     private static class LastTimeUsedComparator implements Comparator<UsageStats> {
252         @Override
compare(UsageStats stat1, UsageStats stat2)253         public int compare(UsageStats stat1, UsageStats stat2) {
254             Long time1 = stat1.getLastTimeUsed();
255             Long time2 = stat2.getLastTimeUsed();
256             return time2.compareTo(time1);
257         }
258     }
259 
getAllApps()260     private List<AppMetaData> getAllApps() {
261         return AppLauncherUtils.getAllLaunchableApps(
262                 getSystemService(LauncherApps.class), mCarPackageManager, mPackageManager);
263     }
264 
265     private class AppInstallUninstallReceiver extends BroadcastReceiver {
266         @Override
onReceive(Context context, Intent intent)267         public void onReceive(Context context, Intent intent) {
268             String packageName = intent.getData().getSchemeSpecificPart();
269 
270             if (TextUtils.isEmpty(packageName)) {
271                 Log.e(TAG, "System sent an empty app install/uninstall broadcast");
272                 return;
273             }
274 
275             mGridAdapter.setAllApps(getAllApps());
276         }
277     }
278 }
279