1 /* 2 * Copyright (C) 2014 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.launcher3.compat; 18 19 import android.content.BroadcastReceiver; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.content.pm.ActivityInfo; 25 import android.content.pm.PackageManager; 26 import android.content.pm.PackageManager.NameNotFoundException; 27 import android.content.pm.ResolveInfo; 28 import android.graphics.Rect; 29 import android.net.Uri; 30 import android.os.Build; 31 import android.os.Bundle; 32 import android.provider.Settings; 33 34 import java.util.ArrayList; 35 import java.util.List; 36 37 /** 38 * Version of {@link LauncherAppsCompat} for devices with API level 16. 39 * Devices Pre-L don't support multiple profiles in one launcher so 40 * user parameters are ignored and all methods operate on the current user. 41 */ 42 public class LauncherAppsCompatV16 extends LauncherAppsCompat { 43 44 private PackageManager mPm; 45 private Context mContext; 46 private List<OnAppsChangedCallbackCompat> mCallbacks 47 = new ArrayList<OnAppsChangedCallbackCompat>(); 48 private PackageMonitor mPackageMonitor; 49 LauncherAppsCompatV16(Context context)50 LauncherAppsCompatV16(Context context) { 51 mPm = context.getPackageManager(); 52 mContext = context; 53 mPackageMonitor = new PackageMonitor(); 54 } 55 getActivityList(String packageName, UserHandleCompat user)56 public List<LauncherActivityInfoCompat> getActivityList(String packageName, 57 UserHandleCompat user) { 58 final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); 59 mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); 60 mainIntent.setPackage(packageName); 61 List<ResolveInfo> infos = mPm.queryIntentActivities(mainIntent, 0); 62 List<LauncherActivityInfoCompat> list = 63 new ArrayList<LauncherActivityInfoCompat>(infos.size()); 64 for (ResolveInfo info : infos) { 65 list.add(new LauncherActivityInfoCompatV16(mContext, info)); 66 } 67 return list; 68 } 69 resolveActivity(Intent intent, UserHandleCompat user)70 public LauncherActivityInfoCompat resolveActivity(Intent intent, UserHandleCompat user) { 71 ResolveInfo info = mPm.resolveActivity(intent, 0); 72 if (info != null) { 73 return new LauncherActivityInfoCompatV16(mContext, info); 74 } 75 return null; 76 } 77 startActivityForProfile(ComponentName component, UserHandleCompat user, Rect sourceBounds, Bundle opts)78 public void startActivityForProfile(ComponentName component, UserHandleCompat user, 79 Rect sourceBounds, Bundle opts) { 80 Intent launchIntent = new Intent(Intent.ACTION_MAIN); 81 launchIntent.addCategory(Intent.CATEGORY_LAUNCHER); 82 launchIntent.setComponent(component); 83 launchIntent.setSourceBounds(sourceBounds); 84 launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 85 mContext.startActivity(launchIntent, opts); 86 } 87 showAppDetailsForProfile(ComponentName component, UserHandleCompat user)88 public void showAppDetailsForProfile(ComponentName component, UserHandleCompat user) { 89 String packageName = component.getPackageName(); 90 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 91 Uri.fromParts("package", packageName, null)); 92 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | 93 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 94 mContext.startActivity(intent, null); 95 } 96 addOnAppsChangedCallback(OnAppsChangedCallbackCompat callback)97 public synchronized void addOnAppsChangedCallback(OnAppsChangedCallbackCompat callback) { 98 if (callback != null && !mCallbacks.contains(callback)) { 99 mCallbacks.add(callback); 100 if (mCallbacks.size() == 1) { 101 registerForPackageIntents(); 102 } 103 } 104 } 105 removeOnAppsChangedCallback(OnAppsChangedCallbackCompat callback)106 public synchronized void removeOnAppsChangedCallback(OnAppsChangedCallbackCompat callback) { 107 mCallbacks.remove(callback); 108 if (mCallbacks.size() == 0) { 109 unregisterForPackageIntents(); 110 } 111 } 112 isPackageEnabledForProfile(String packageName, UserHandleCompat user)113 public boolean isPackageEnabledForProfile(String packageName, UserHandleCompat user) { 114 return isAppEnabled(mPm, packageName, 0); 115 } 116 isActivityEnabledForProfile(ComponentName component, UserHandleCompat user)117 public boolean isActivityEnabledForProfile(ComponentName component, UserHandleCompat user) { 118 try { 119 ActivityInfo info = mPm.getActivityInfo(component, 0); 120 return info != null && info.isEnabled(); 121 } catch (NameNotFoundException e) { 122 return false; 123 } 124 } 125 unregisterForPackageIntents()126 private void unregisterForPackageIntents() { 127 mContext.unregisterReceiver(mPackageMonitor); 128 } 129 registerForPackageIntents()130 private void registerForPackageIntents() { 131 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 132 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 133 filter.addAction(Intent.ACTION_PACKAGE_CHANGED); 134 filter.addDataScheme("package"); 135 mContext.registerReceiver(mPackageMonitor, filter); 136 filter = new IntentFilter(); 137 filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); 138 filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); 139 mContext.registerReceiver(mPackageMonitor, filter); 140 } 141 getCallbacks()142 private synchronized List<OnAppsChangedCallbackCompat> getCallbacks() { 143 return new ArrayList<OnAppsChangedCallbackCompat>(mCallbacks); 144 } 145 146 private class PackageMonitor extends BroadcastReceiver { onReceive(Context context, Intent intent)147 public void onReceive(Context context, Intent intent) { 148 final String action = intent.getAction(); 149 final UserHandleCompat user = UserHandleCompat.myUserHandle(); 150 151 if (Intent.ACTION_PACKAGE_CHANGED.equals(action) 152 || Intent.ACTION_PACKAGE_REMOVED.equals(action) 153 || Intent.ACTION_PACKAGE_ADDED.equals(action)) { 154 final String packageName = intent.getData().getSchemeSpecificPart(); 155 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); 156 157 if (packageName == null || packageName.length() == 0) { 158 // they sent us a bad intent 159 return; 160 } 161 if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) { 162 for (OnAppsChangedCallbackCompat callback : getCallbacks()) { 163 callback.onPackageChanged(packageName, user); 164 } 165 } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { 166 if (!replacing) { 167 for (OnAppsChangedCallbackCompat callback : getCallbacks()) { 168 callback.onPackageRemoved(packageName, user); 169 } 170 } 171 // else, we are replacing the package, so a PACKAGE_ADDED will be sent 172 // later, we will update the package at this time 173 } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { 174 if (!replacing) { 175 for (OnAppsChangedCallbackCompat callback : getCallbacks()) { 176 callback.onPackageAdded(packageName, user); 177 } 178 } else { 179 for (OnAppsChangedCallbackCompat callback : getCallbacks()) { 180 callback.onPackageChanged(packageName, user); 181 } 182 } 183 } 184 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { 185 // EXTRA_REPLACING is available Kitkat onwards. For lower devices, it is broadcasted 186 // when moving a package or mounting/un-mounting external storage. Assume that 187 // it is a replacing operation. 188 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, 189 Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT); 190 String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); 191 for (OnAppsChangedCallbackCompat callback : getCallbacks()) { 192 callback.onPackagesAvailable(packages, user, replacing); 193 } 194 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { 195 // This intent is broadcasted when moving a package or mounting/un-mounting 196 // external storage. 197 // However on Kitkat this is also sent when a package is being updated, and 198 // contains an extra Intent.EXTRA_REPLACING=true for that case. 199 // Using false as default for Intent.EXTRA_REPLACING gives correct value on 200 // lower devices as the intent is not sent when the app is updating/replacing. 201 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); 202 String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); 203 for (OnAppsChangedCallbackCompat callback : getCallbacks()) { 204 callback.onPackagesUnavailable(packages, user, replacing); 205 } 206 } 207 } 208 } 209 } 210