1 /* 2 * Copyright (C) 2013 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; 18 19 import android.annotation.TargetApi; 20 import android.app.SearchManager; 21 import android.content.ComponentName; 22 import android.content.ContentResolver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.content.res.Configuration; 27 import android.content.res.Resources; 28 import android.database.ContentObserver; 29 import android.graphics.Point; 30 import android.os.Build; 31 import android.os.Handler; 32 import android.util.DisplayMetrics; 33 import android.util.Log; 34 import android.view.Display; 35 import android.view.WindowManager; 36 import com.android.launcher3.compat.LauncherAppsCompat; 37 import com.android.launcher3.compat.PackageInstallerCompat; 38 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; 39 import java.lang.ref.WeakReference; 40 import java.util.ArrayList; 41 42 public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks { 43 private static final String TAG = "LauncherAppState"; 44 45 private static final boolean DEBUG = false; 46 47 private final AppFilter mAppFilter; 48 private final BuildInfo mBuildInfo; 49 private final LauncherModel mModel; 50 private final IconCache mIconCache; 51 52 private final boolean mIsScreenLarge; 53 private final float mScreenDensity; 54 private final int mLongPressTimeout = 300; 55 56 private WidgetPreviewLoader.CacheDb mWidgetPreviewCacheDb; 57 private boolean mWallpaperChangedSinceLastCheck; 58 59 private static WeakReference<LauncherProvider> sLauncherProvider; 60 private static Context sContext; 61 62 private static LauncherAppState INSTANCE; 63 64 private DynamicGrid mDynamicGrid; 65 66 public static LauncherAppState getInstance() { 67 if (INSTANCE == null) { 68 INSTANCE = new LauncherAppState(); 69 } 70 return INSTANCE; 71 } 72 73 public static LauncherAppState getInstanceNoCreate() { 74 return INSTANCE; 75 } 76 77 public Context getContext() { 78 return sContext; 79 } 80 81 public static void setApplicationContext(Context context) { 82 if (sContext != null) { 83 Log.w(Launcher.TAG, "setApplicationContext called twice! old=" + sContext + " new=" + context); 84 } 85 sContext = context.getApplicationContext(); 86 } 87 88 private LauncherAppState() { 89 if (sContext == null) { 90 throw new IllegalStateException("LauncherAppState inited before app context set"); 91 } 92 93 Log.v(Launcher.TAG, "LauncherAppState inited"); 94 95 if (sContext.getResources().getBoolean(R.bool.debug_memory_enabled)) { 96 MemoryTracker.startTrackingMe(sContext, "L"); 97 } 98 99 // set sIsScreenXLarge and mScreenDensity *before* creating icon cache 100 mIsScreenLarge = isScreenLarge(sContext.getResources()); 101 mScreenDensity = sContext.getResources().getDisplayMetrics().density; 102 103 recreateWidgetPreviewDb(); 104 mIconCache = new IconCache(sContext); 105 106 mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class)); 107 mBuildInfo = BuildInfo.loadByName(sContext.getString(R.string.build_info_class)); 108 mModel = new LauncherModel(this, mIconCache, mAppFilter); 109 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(sContext); 110 launcherApps.addOnAppsChangedCallback(mModel); 111 112 // Register intent receivers 113 IntentFilter filter = new IntentFilter(); 114 filter.addAction(Intent.ACTION_LOCALE_CHANGED); 115 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); 116 sContext.registerReceiver(mModel, filter); 117 filter = new IntentFilter(); 118 filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED); 119 sContext.registerReceiver(mModel, filter); 120 filter = new IntentFilter(); 121 filter.addAction(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED); 122 sContext.registerReceiver(mModel, filter); 123 124 // Register for changes to the favorites 125 ContentResolver resolver = sContext.getContentResolver(); 126 resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true, 127 mFavoritesObserver); 128 } 129 130 public void recreateWidgetPreviewDb() { 131 if (mWidgetPreviewCacheDb != null) { 132 mWidgetPreviewCacheDb.close(); 133 } 134 mWidgetPreviewCacheDb = new WidgetPreviewLoader.CacheDb(sContext); 135 } 136 137 /** 138 * Call from Application.onTerminate(), which is not guaranteed to ever be called. 139 */ 140 public void onTerminate() { 141 sContext.unregisterReceiver(mModel); 142 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(sContext); 143 launcherApps.removeOnAppsChangedCallback(mModel); 144 PackageInstallerCompat.getInstance(sContext).onStop(); 145 146 ContentResolver resolver = sContext.getContentResolver(); 147 resolver.unregisterContentObserver(mFavoritesObserver); 148 } 149 150 /** 151 * Receives notifications whenever the user favorites have changed. 152 */ 153 private final ContentObserver mFavoritesObserver = new ContentObserver(new Handler()) { 154 @Override 155 public void onChange(boolean selfChange) { 156 // If the database has ever changed, then we really need to force a reload of the 157 // workspace on the next load 158 mModel.resetLoadedState(false, true); 159 mModel.startLoaderFromBackground(); 160 } 161 }; 162 163 LauncherModel setLauncher(Launcher launcher) { 164 mModel.initialize(launcher); 165 return mModel; 166 } 167 168 public IconCache getIconCache() { 169 return mIconCache; 170 } 171 172 LauncherModel getModel() { 173 return mModel; 174 } 175 176 boolean shouldShowAppOrWidgetProvider(ComponentName componentName) { 177 return mAppFilter == null || mAppFilter.shouldShowApp(componentName); 178 } 179 180 WidgetPreviewLoader.CacheDb getWidgetPreviewCacheDb() { 181 return mWidgetPreviewCacheDb; 182 } 183 184 static void setLauncherProvider(LauncherProvider provider) { 185 sLauncherProvider = new WeakReference<LauncherProvider>(provider); 186 } 187 188 static LauncherProvider getLauncherProvider() { 189 return sLauncherProvider.get(); 190 } 191 192 public static String getSharedPreferencesKey() { 193 return LauncherFiles.SHARED_PREFERENCES_KEY; 194 } 195 196 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) 197 DeviceProfile initDynamicGrid(Context context) { 198 mDynamicGrid = createDynamicGrid(context, mDynamicGrid); 199 mDynamicGrid.getDeviceProfile().addCallback(this); 200 return mDynamicGrid.getDeviceProfile(); 201 } 202 203 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) 204 static DynamicGrid createDynamicGrid(Context context, DynamicGrid dynamicGrid) { 205 // Determine the dynamic grid properties 206 WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 207 Display display = wm.getDefaultDisplay(); 208 209 Point realSize = new Point(); 210 display.getRealSize(realSize); 211 DisplayMetrics dm = new DisplayMetrics(); 212 display.getMetrics(dm); 213 214 if (dynamicGrid == null) { 215 Point smallestSize = new Point(); 216 Point largestSize = new Point(); 217 display.getCurrentSizeRange(smallestSize, largestSize); 218 219 dynamicGrid = new DynamicGrid(context, 220 context.getResources(), 221 Math.min(smallestSize.x, smallestSize.y), 222 Math.min(largestSize.x, largestSize.y), 223 realSize.x, realSize.y, 224 dm.widthPixels, dm.heightPixels); 225 } 226 227 // Update the icon size 228 DeviceProfile grid = dynamicGrid.getDeviceProfile(); 229 grid.updateFromConfiguration(context, context.getResources(), 230 realSize.x, realSize.y, 231 dm.widthPixels, dm.heightPixels); 232 return dynamicGrid; 233 } 234 235 public DynamicGrid getDynamicGrid() { 236 return mDynamicGrid; 237 } 238 239 public boolean isScreenLarge() { 240 return mIsScreenLarge; 241 } 242 243 // Need a version that doesn't require an instance of LauncherAppState for the wallpaper picker 244 public static boolean isScreenLarge(Resources res) { 245 return res.getBoolean(R.bool.is_large_tablet); 246 } 247 248 public static boolean isScreenLandscape(Context context) { 249 return context.getResources().getConfiguration().orientation == 250 Configuration.ORIENTATION_LANDSCAPE; 251 } 252 253 public float getScreenDensity() { 254 return mScreenDensity; 255 } 256 257 public int getLongPressTimeout() { 258 return mLongPressTimeout; 259 } 260 261 public void onWallpaperChanged() { 262 mWallpaperChangedSinceLastCheck = true; 263 } 264 265 public boolean hasWallpaperChangedSinceLastCheck() { 266 boolean result = mWallpaperChangedSinceLastCheck; 267 mWallpaperChangedSinceLastCheck = false; 268 return result; 269 } 270 271 @Override 272 public void onAvailableSizeChanged(DeviceProfile grid) { 273 Utilities.setIconSize(grid.iconSizePx); 274 } 275 276 public static boolean isDisableAllApps() { 277 // Returns false on non-dogfood builds. 278 return getInstance().mBuildInfo.isDogfoodBuild() && 279 Utilities.isPropertyEnabled(Launcher.DISABLE_ALL_APPS_PROPERTY); 280 } 281 282 public static boolean isDogfoodBuild() { 283 return getInstance().mBuildInfo.isDogfoodBuild(); 284 } 285 286 public void setPackageState(ArrayList<PackageInstallInfo> installInfo) { 287 mModel.setPackageState(installInfo); 288 } 289 290 /** 291 * Updates the icons and label of all icons for the provided package name. 292 */ 293 public void updatePackageBadge(String packageName) { 294 mModel.updatePackageBadge(packageName); 295 } 296 } 297