1 /*
2  * Copyright (C) 2016 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.example.sampleleanbacklauncher.apps;
18 
19 import android.app.Service;
20 import android.content.BroadcastReceiver;
21 import android.content.ComponentCallbacks2;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.content.pm.ApplicationInfo;
27 import android.content.pm.PackageManager;
28 import android.content.pm.ResolveInfo;
29 import android.content.res.Configuration;
30 import android.database.Cursor;
31 import android.net.ConnectivityManager;
32 import android.net.Uri;
33 import android.net.wifi.WifiManager;
34 import android.os.AsyncTask;
35 import android.os.Binder;
36 import android.os.IBinder;
37 import android.os.Trace;
38 import android.provider.Settings;
39 import androidx.annotation.Nullable;
40 import androidx.annotation.WorkerThread;
41 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
42 import android.telephony.PhoneStateListener;
43 import android.telephony.SignalStrength;
44 import android.telephony.TelephonyManager;
45 import android.text.TextUtils;
46 import android.util.ArrayMap;
47 import android.util.ArraySet;
48 
49 import com.example.sampleleanbacklauncher.R;
50 import com.example.sampleleanbacklauncher.LauncherConstants;
51 import com.example.sampleleanbacklauncher.notifications.NotificationsContract;
52 
53 import java.io.FileDescriptor;
54 import java.io.PrintWriter;
55 import java.util.Date;
56 import java.util.List;
57 import java.util.Map;
58 import java.util.Set;
59 
60 public class LaunchItemsManager extends Service implements ComponentCallbacks2 {
61     private static final String TAG = "LaunchItemsManager";
62 
63     public static final String ACTION_APP_LIST_INVALIDATED =
64             "LaunchItemsManager.APP_LIST_INVALIDATED";
65     public static final String ACTION_GAME_LIST_INVALIDATED =
66             "LaunchItemsManager.GAME_LIST_INVALIDATED";
67     public static final String ACTION_SETTINGS_LIST_INVALIDATED =
68             "LaunchItemsManager.SETTINGS_LIST_INVALIDATED";
69 
70     private LaunchItemsDbHelper mDbHelper;
71     private final Object mDbHelperLock = new Object();
72 
73     private Set<LaunchItem> mAppItems = new ArraySet<>();
74     private Set<LaunchItem> mGameItems = new ArraySet<>();
75     private Set<LaunchItem> mSettingsItems = new ArraySet<>();
76 
77     private Map<ComponentName, Date> mLastOpenMap;
78     private Map<ComponentName, Long> mPriorityMap;
79     private Map<String, Long> mOobPriority;
80 
81     private volatile boolean mAppListValid;
82     private volatile boolean mGameListValid;
83     private volatile boolean mSettingsListValid;
84 
85     private NotificationsLaunchItem mNotifsLaunchItem;
86     private Cursor mNotifsCountCursor = null;
87 
88     private final PackageListener mPackageListener = new PackageListener();
89     private final NetworkListener mNetworkListener = new NetworkListener();
90     private final IBinder mLocalBinder = new LocalBinder();
91 
92     // We can't query the signal strength directly, so we have to just listen for it all the time.
93     private SignalStrength mSignalStrength;
94     private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
95         @Override
96         public void onSignalStrengthsChanged(SignalStrength signalStrength) {
97             mSignalStrength = signalStrength;
98             invalidateSettingsList();
99         }
100     };
101 
102     @Override
onCreate()103     public void onCreate() {
104         super.onCreate();
105 
106         IntentFilter packageIntentFilter = new IntentFilter();
107         packageIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
108         packageIntentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
109         packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
110         packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
111         packageIntentFilter.addDataScheme("package");
112         registerReceiver(mPackageListener, packageIntentFilter);
113 
114         IntentFilter networkIntentFilter = new IntentFilter();
115         networkIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
116         networkIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
117         networkIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
118         networkIntentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
119         registerReceiver(mNetworkListener, networkIntentFilter);
120 
121         registerComponentCallbacks(this);
122 
123         getSystemService(TelephonyManager.class).listen(mPhoneStateListener,
124                 PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
125 
126         invalidateAllLists();
127     }
128 
129     @Override
onDestroy()130     public void onDestroy() {
131         super.onDestroy();
132 
133         unregisterReceiver(mPackageListener);
134         unregisterReceiver(mNetworkListener);
135 
136         unregisterComponentCallbacks(this);
137         getSystemService(TelephonyManager.class).listen(mPhoneStateListener,
138                 PhoneStateListener.LISTEN_NONE);
139     }
140 
141     @Nullable
142     @Override
onBind(Intent intent)143     public IBinder onBind(Intent intent) {
144         return mLocalBinder;
145     }
146 
147     @WorkerThread
ensureDatabase()148     private void ensureDatabase() {
149         synchronized (mDbHelperLock) {
150             if (mDbHelper == null) {
151                 mDbHelper = new LaunchItemsDbHelper(getApplicationContext());
152                 mLastOpenMap = mDbHelper.readLastOpens();
153                 mPriorityMap = mDbHelper.readOrderPriorities();
154                 final String[] oobOrder = getResources().getStringArray(R.array.oob_order);
155                 mOobPriority = new ArrayMap<>(oobOrder.length);
156                 for (int i = 0; i < oobOrder.length; i++) {
157                     mOobPriority.put(oobOrder[i], (long) oobOrder.length - i);
158                 }
159             }
160         }
161     }
162 
163     @WorkerThread
getPackagePriority(ResolveInfo info)164     private long getPackagePriority(ResolveInfo info) {
165         ensureDatabase();
166 
167         final ComponentName cn =
168                 new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
169 
170         // Since 1970 was quite a while ago, the last open time should be much larger than
171         // the numeric priority
172         Long priority = mPriorityMap.get(cn);
173         final Date lastOpen = mLastOpenMap.get(cn);
174         if (lastOpen != null) {
175             priority = lastOpen.getTime();
176         }
177         if (priority == null) {
178             if (mOobPriority.containsKey(info.activityInfo.packageName)) {
179                 priority = mOobPriority.get(info.activityInfo.packageName);
180             } else {
181                 priority = 0L;
182             }
183             mDbHelper.writeOrderPriority(cn, priority);
184             mPriorityMap.put(cn, priority);
185         }
186 
187         return priority;
188     }
189 
190     @WorkerThread
updateAppList()191     private void updateAppList() {
192         Trace.beginSection("updateAppList");
193         try {
194             final PackageManager packageManager = getPackageManager();
195             List<ResolveInfo> infos = packageManager.queryIntentActivities(
196                     new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LEANBACK_LAUNCHER),
197                     0);
198             final Set<LaunchItem> appItems = new ArraySet<>(infos.size());
199             for (ResolveInfo info : infos) {
200 
201                 ApplicationInfo appInfo = info.activityInfo.applicationInfo;
202                 if ((appInfo.flags & ApplicationInfo.FLAG_IS_GAME) == 0) {
203                     appItems.add(new LaunchItem(this, info, getPackagePriority(info)));
204                 }
205             }
206             mAppItems = appItems;
207             mAppListValid = true;
208         } finally {
209             Trace.endSection();
210         }
211     }
212 
213     @WorkerThread
updateGamesList()214     private void updateGamesList() {
215         Trace.beginSection("updateGamesList");
216         try {
217             final PackageManager packageManager = getPackageManager();
218             List<ResolveInfo> infos = packageManager.queryIntentActivities(
219                     new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LEANBACK_LAUNCHER),
220                     0);
221             final Set<LaunchItem> gameItems = new ArraySet<>(infos.size());
222             for (ResolveInfo info : infos) {
223                 ApplicationInfo appInfo = info.activityInfo.applicationInfo;
224                 if ((appInfo.flags & ApplicationInfo.FLAG_IS_GAME) != 0) {
225                     gameItems.add(new LaunchItem(this, info, getPackagePriority(info)));
226                 }
227             }
228             mGameItems = gameItems;
229             mGameListValid = true;
230         } finally {
231             Trace.endSection();
232         }
233     }
234 
235     @WorkerThread
updateSettingsList()236     private void updateSettingsList() {
237         Trace.beginSection("updateSettingsList");
238         try {
239             final PackageManager packageManager = getPackageManager();
240             List<ResolveInfo> networkInfos =
241                     packageManager.queryIntentActivities(new Intent(Settings.ACTION_WIFI_SETTINGS)
242                     .addCategory(LauncherConstants.CATEGORY_LEANBACK_SETTINGS), 0);
243             List<ResolveInfo> settingsInfos = packageManager.queryIntentActivities(
244                     new Intent(Intent.ACTION_MAIN)
245                             .addCategory(LauncherConstants.CATEGORY_LEANBACK_SETTINGS),
246                     PackageManager.GET_RESOLVED_FILTER);
247             final Set<LaunchItem> settingsItems = new ArraySet<>(settingsInfos.size());
248             for (ResolveInfo info : settingsInfos) {
249                 if (info.activityInfo == null) {
250                     continue;
251                 }
252                 boolean isNetwork = false;
253                 for (ResolveInfo networkInfo : networkInfos) {
254                     if (networkInfo.activityInfo == null) {
255                         continue;
256                     }
257                     if (TextUtils.equals(networkInfo.activityInfo.name,
258                             info.activityInfo.name)
259                         && TextUtils.equals(networkInfo.activityInfo.packageName,
260                             info.activityInfo.packageName)) {
261                         isNetwork = true;
262                         break;
263                     }
264                 }
265                 int priority = info.priority;
266                 if (isNetwork) {
267                     settingsItems.add(new NetworkLaunchItem(this, info, mSignalStrength, priority));
268                 } else {
269                     settingsItems.add(new SettingsLaunchItem(this, info, priority));
270                 }
271             }
272 
273             mNotifsLaunchItem = new NotificationsLaunchItem(this);
274             mNotifsLaunchItem.setNotificationsCount(getNotifsCount());
275             settingsItems.add(mNotifsLaunchItem);
276 
277             mSettingsItems = settingsItems;
278             mSettingsListValid = true;
279         } finally {
280             Trace.endSection();
281         }
282     }
283 
updateNotifsCountCursor(Cursor cursor)284     public void updateNotifsCountCursor(Cursor cursor) {
285         mNotifsCountCursor = cursor;
286         invalidateSettingsList();
287     }
288 
289     @WorkerThread
getNotifsCount()290     private int getNotifsCount() {
291         if (mNotifsCountCursor != null && mNotifsCountCursor.moveToFirst()) {
292             mNotifsCountCursor.moveToFirst();
293             int index = mNotifsCountCursor.getColumnIndex(NotificationsContract.COLUMN_COUNT);
294             return mNotifsCountCursor.getInt(index);
295         }
296         return 0;
297     }
298 
invalidateAllLists()299     private void invalidateAllLists() {
300         invalidateAppList();
301         invalidateGameList();
302         invalidateSettingsList();
303     }
304 
invalidateAppList()305     private void invalidateAppList() {
306         mAppListValid = false;
307         LocalBroadcastManager.getInstance(this)
308                 .sendBroadcast(new Intent(ACTION_APP_LIST_INVALIDATED));
309     }
310 
invalidateGameList()311     private void invalidateGameList() {
312         mGameListValid = false;
313         LocalBroadcastManager.getInstance(this)
314                 .sendBroadcast(new Intent(ACTION_GAME_LIST_INVALIDATED));
315     }
316 
invalidateSettingsList()317     private void invalidateSettingsList() {
318         mSettingsListValid = false;
319         LocalBroadcastManager.getInstance(this)
320                 .sendBroadcast(new Intent(ACTION_SETTINGS_LIST_INVALIDATED));
321     }
322 
323     @WorkerThread
getAppItems()324     public Set<LaunchItem> getAppItems() {
325         if (!mAppListValid) {
326             updateAppList();
327         }
328         return mAppItems;
329     }
330 
331     @WorkerThread
getGameItems()332     public Set<LaunchItem> getGameItems() {
333         if (!mGameListValid) {
334             updateGamesList();
335         }
336         return mGameItems;
337     }
338 
339     @WorkerThread
getSettingsItems()340     public Set<LaunchItem> getSettingsItems() {
341         if (!mSettingsListValid) {
342             updateSettingsList();
343         }
344         return mSettingsItems;
345     }
346 
invalidateListsForPackage(String packageName)347     private void invalidateListsForPackage(String packageName) {
348         boolean updateApps = false;
349         boolean updateGames = false;
350         boolean updateSettings = false;
351 
352         // Check if the package was previously listed in apps, games or settings
353         for (final LaunchItem item : mAppItems) {
354             if (TextUtils.equals(item.getIntent().getComponent().getPackageName(), packageName)) {
355                 updateApps = true;
356                 break;
357             }
358         }
359         if (!updateApps) {
360             // Can't be both an app and a game at the same time
361             for (final LaunchItem item : mGameItems) {
362                 if (TextUtils.equals(item.getIntent().getComponent().getPackageName(),
363                         packageName)) {
364                     updateGames = true;
365                     break;
366                 }
367             }
368         }
369         for (final LaunchItem item : mSettingsItems) {
370             if (TextUtils.equals(item.getIntent().getPackage(), packageName)) {
371                 updateSettings = true;
372                 break;
373             }
374         }
375 
376         // Check if the app will be listed in apps, games or settings
377         final List<ResolveInfo> leanbackInfos =
378                 getPackageManager().queryIntentActivities(
379                         new Intent(Intent.ACTION_MAIN)
380                                 .addCategory(Intent.CATEGORY_LEANBACK_LAUNCHER)
381                                 .setPackage(packageName),
382                         0);
383         if (!leanbackInfos.isEmpty()) {
384             ApplicationInfo applicationInfo = leanbackInfos.get(0).activityInfo.applicationInfo;
385             if ((applicationInfo.flags & ApplicationInfo.FLAG_IS_GAME) == 0) {
386                 updateApps = true;
387             } else {
388                 updateGames = true;
389             }
390         }
391         if (!updateSettings) {
392             final List<ResolveInfo> settingsInfos =
393                     getPackageManager().queryIntentActivities(
394                             new Intent(Intent.ACTION_MAIN)
395                                     .addCategory(LauncherConstants.CATEGORY_LEANBACK_SETTINGS)
396                                     .setPackage(packageName),
397                             0);
398             if (!settingsInfos.isEmpty()) {
399                 updateSettings = true;
400             }
401         }
402 
403         if (updateApps) {
404             invalidateAppList();
405         }
406         if (updateGames) {
407             invalidateGameList();
408         }
409         if (updateSettings) {
410             invalidateSettingsList();
411         }
412     }
413 
notifyItemLaunched(LaunchItem item)414     public void notifyItemLaunched(LaunchItem item) {
415         final Date now = new Date();
416         final ComponentName component = item.getIntent().getComponent();
417         mLastOpenMap.put(component, now);
418         new AsyncTask<Void, Void, Void>() {
419             @Override
420             protected Void doInBackground(Void... params) {
421                 ensureDatabase();
422                 if (component != null) {
423                     mDbHelper.writeLastOpen(component, now);
424                 }
425                 return null;
426             }
427         }.execute();
428         if (mAppItems.contains(item)) {
429             invalidateAppList();
430         } else if (mGameItems.contains(item)) {
431             invalidateGameList();
432         }
433         // No recency for settings row
434     }
435 
436     @Override
onTrimMemory(int level)437     public void onTrimMemory(int level) {}
438 
439     @Override
onConfigurationChanged(Configuration newConfig)440     public void onConfigurationChanged(Configuration newConfig) {
441         invalidateAllLists();
442     }
443 
444     @Override
onLowMemory()445     public void onLowMemory() {}
446 
447     @Override
dump(FileDescriptor fd, PrintWriter writer, String[] args)448     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
449         writer.println("App Items");
450         if (mAppItems != null) {
451             for (final LaunchItem item : mAppItems.toArray(new LaunchItem[mAppItems.size()])) {
452                 writer.println(item.toDebugString());
453             }
454         } else {
455             writer.println("Null");
456         }
457         writer.println("Game Items");
458         if (mGameItems != null) {
459             for (final LaunchItem item : mGameItems.toArray(new LaunchItem[mGameItems.size()])) {
460                 writer.println(item.toDebugString());
461             }
462         } else {
463             writer.println("Null");
464         }
465         writer.println("Settings Items");
466         if (mSettingsItems != null) {
467             for (final LaunchItem item :
468                     mSettingsItems.toArray(new LaunchItem[mSettingsItems.size()])) {
469                 writer.println(item.toDebugString());
470             }
471         } else {
472             writer.println("Null");
473         }
474     }
475 
476     public class LocalBinder extends Binder {
getLaunchItemsManager()477         public LaunchItemsManager getLaunchItemsManager() {
478             return LaunchItemsManager.this;
479         }
480     }
481 
482     public class PackageListener extends BroadcastReceiver {
483         @Override
onReceive(Context context, Intent intent)484         public void onReceive(Context context, Intent intent) {
485             final Uri packageUri = Uri.parse(intent.getDataString());
486             invalidateListsForPackage(packageUri.getSchemeSpecificPart());
487         }
488     }
489 
490     public class NetworkListener extends BroadcastReceiver {
491         @Override
onReceive(Context context, Intent intent)492         public void onReceive(Context context, Intent intent) {
493             invalidateSettingsList();
494         }
495     }
496 }
497