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