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 static com.android.launcher3.Utilities.getDevicePrefs; 20 import static com.android.launcher3.config.FeatureFlags.ENABLE_THEMED_ICONS; 21 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; 22 import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI; 23 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.SharedPreferences; 28 import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 29 import android.content.pm.LauncherApps; 30 import android.os.UserHandle; 31 import android.util.Log; 32 33 import androidx.annotation.Nullable; 34 35 import com.android.launcher3.config.FeatureFlags; 36 import com.android.launcher3.graphics.IconShape; 37 import com.android.launcher3.icons.IconCache; 38 import com.android.launcher3.icons.IconProvider; 39 import com.android.launcher3.icons.LauncherIcons; 40 import com.android.launcher3.notification.NotificationListener; 41 import com.android.launcher3.pm.InstallSessionHelper; 42 import com.android.launcher3.pm.InstallSessionTracker; 43 import com.android.launcher3.pm.UserCache; 44 import com.android.launcher3.util.MainThreadInitializedObject; 45 import com.android.launcher3.util.Preconditions; 46 import com.android.launcher3.util.RunnableList; 47 import com.android.launcher3.util.SafeCloseable; 48 import com.android.launcher3.util.SettingsCache; 49 import com.android.launcher3.util.SimpleBroadcastReceiver; 50 import com.android.launcher3.util.Themes; 51 import com.android.launcher3.widget.DatabaseWidgetPreviewLoader; 52 import com.android.launcher3.widget.custom.CustomWidgetManager; 53 54 public class LauncherAppState { 55 56 public static final String ACTION_FORCE_ROLOAD = "force-reload-launcher"; 57 private static final String KEY_ICON_STATE = "pref_icon_shape_path"; 58 59 // We do not need any synchronization for this variable as its only written on UI thread. 60 public static final MainThreadInitializedObject<LauncherAppState> INSTANCE = 61 new MainThreadInitializedObject<>(LauncherAppState::new); 62 63 private final Context mContext; 64 private final LauncherModel mModel; 65 private final IconProvider mIconProvider; 66 private final IconCache mIconCache; 67 private final DatabaseWidgetPreviewLoader mWidgetCache; 68 private final InvariantDeviceProfile mInvariantDeviceProfile; 69 private final RunnableList mOnTerminateCallback = new RunnableList(); 70 getInstance(final Context context)71 public static LauncherAppState getInstance(final Context context) { 72 return INSTANCE.get(context); 73 } 74 getInstanceNoCreate()75 public static LauncherAppState getInstanceNoCreate() { 76 return INSTANCE.getNoCreate(); 77 } 78 getContext()79 public Context getContext() { 80 return mContext; 81 } 82 LauncherAppState(Context context)83 public LauncherAppState(Context context) { 84 this(context, LauncherFiles.APP_ICONS_DB); 85 Log.v(Launcher.TAG, "LauncherAppState initiated"); 86 Preconditions.assertUIThread(); 87 88 mInvariantDeviceProfile.addOnChangeListener(idp -> refreshAndReloadLauncher()); 89 90 mContext.getSystemService(LauncherApps.class).registerCallback(mModel); 91 92 SimpleBroadcastReceiver modelChangeReceiver = 93 new SimpleBroadcastReceiver(mModel::onBroadcastIntent); 94 modelChangeReceiver.register(mContext, Intent.ACTION_LOCALE_CHANGED, 95 Intent.ACTION_MANAGED_PROFILE_AVAILABLE, 96 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE, 97 Intent.ACTION_MANAGED_PROFILE_UNLOCKED); 98 if (FeatureFlags.IS_STUDIO_BUILD) { 99 modelChangeReceiver.register(mContext, ACTION_FORCE_ROLOAD); 100 } 101 mOnTerminateCallback.add(() -> mContext.unregisterReceiver(modelChangeReceiver)); 102 103 CustomWidgetManager.INSTANCE.get(mContext) 104 .setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts); 105 106 SafeCloseable userChangeListener = UserCache.INSTANCE.get(mContext) 107 .addUserChangeListener(mModel::forceReload); 108 mOnTerminateCallback.add(userChangeListener::close); 109 110 IconObserver observer = new IconObserver(); 111 SafeCloseable iconChangeTracker = mIconProvider.registerIconChangeListener( 112 observer, MODEL_EXECUTOR.getHandler()); 113 mOnTerminateCallback.add(iconChangeTracker::close); 114 MODEL_EXECUTOR.execute(observer::verifyIconChanged); 115 if (ENABLE_THEMED_ICONS.get()) { 116 SharedPreferences prefs = Utilities.getPrefs(mContext); 117 prefs.registerOnSharedPreferenceChangeListener(observer); 118 mOnTerminateCallback.add( 119 () -> prefs.unregisterOnSharedPreferenceChangeListener(observer)); 120 } 121 122 InstallSessionTracker installSessionTracker = 123 InstallSessionHelper.INSTANCE.get(context).registerInstallTracker(mModel); 124 mOnTerminateCallback.add(installSessionTracker::unregister); 125 126 // Register an observer to rebind the notification listener when dots are re-enabled. 127 SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext); 128 SettingsCache.OnChangeListener notificationLister = this::onNotificationSettingsChanged; 129 settingsCache.register(NOTIFICATION_BADGING_URI, notificationLister); 130 onNotificationSettingsChanged(settingsCache.getValue(NOTIFICATION_BADGING_URI)); 131 mOnTerminateCallback.add(() -> 132 settingsCache.unregister(NOTIFICATION_BADGING_URI, notificationLister)); 133 } 134 LauncherAppState(Context context, @Nullable String iconCacheFileName)135 public LauncherAppState(Context context, @Nullable String iconCacheFileName) { 136 mContext = context; 137 138 mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context); 139 mIconProvider = new IconProvider(context, Themes.isThemedIconEnabled(context)); 140 mIconCache = new IconCache(mContext, mInvariantDeviceProfile, 141 iconCacheFileName, mIconProvider); 142 mWidgetCache = new DatabaseWidgetPreviewLoader(mContext, mIconCache); 143 mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext)); 144 mOnTerminateCallback.add(mIconCache::close); 145 } 146 onNotificationSettingsChanged(boolean areNotificationDotsEnabled)147 private void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) { 148 if (areNotificationDotsEnabled) { 149 NotificationListener.requestRebind(new ComponentName( 150 mContext, NotificationListener.class)); 151 } 152 } 153 refreshAndReloadLauncher()154 private void refreshAndReloadLauncher() { 155 LauncherIcons.clearPool(); 156 mIconCache.updateIconParams( 157 mInvariantDeviceProfile.fillResIconDpi, mInvariantDeviceProfile.iconBitmapSize); 158 mWidgetCache.refresh(); 159 mModel.forceReload(); 160 } 161 162 /** 163 * Call from Application.onTerminate(), which is not guaranteed to ever be called. 164 */ onTerminate()165 public void onTerminate() { 166 mModel.destroy(); 167 mContext.getSystemService(LauncherApps.class).unregisterCallback(mModel); 168 CustomWidgetManager.INSTANCE.get(mContext).setWidgetRefreshCallback(null); 169 mOnTerminateCallback.executeAllAndDestroy(); 170 } 171 getIconProvider()172 public IconProvider getIconProvider() { 173 return mIconProvider; 174 } 175 getIconCache()176 public IconCache getIconCache() { 177 return mIconCache; 178 } 179 getModel()180 public LauncherModel getModel() { 181 return mModel; 182 } 183 getWidgetCache()184 public DatabaseWidgetPreviewLoader getWidgetCache() { 185 return mWidgetCache; 186 } 187 getInvariantDeviceProfile()188 public InvariantDeviceProfile getInvariantDeviceProfile() { 189 return mInvariantDeviceProfile; 190 } 191 192 /** 193 * Shorthand for {@link #getInvariantDeviceProfile()} 194 */ getIDP(Context context)195 public static InvariantDeviceProfile getIDP(Context context) { 196 return InvariantDeviceProfile.INSTANCE.get(context); 197 } 198 199 private class IconObserver 200 implements IconProvider.IconChangeListener, OnSharedPreferenceChangeListener { 201 202 @Override onAppIconChanged(String packageName, UserHandle user)203 public void onAppIconChanged(String packageName, UserHandle user) { 204 mModel.onAppIconChanged(packageName, user); 205 } 206 207 @Override onSystemIconStateChanged(String iconState)208 public void onSystemIconStateChanged(String iconState) { 209 IconShape.init(mContext); 210 refreshAndReloadLauncher(); 211 getDevicePrefs(mContext).edit().putString(KEY_ICON_STATE, iconState).apply(); 212 } 213 verifyIconChanged()214 void verifyIconChanged() { 215 String iconState = mIconProvider.getSystemIconState(); 216 if (!iconState.equals(getDevicePrefs(mContext).getString(KEY_ICON_STATE, ""))) { 217 onSystemIconStateChanged(iconState); 218 } 219 } 220 221 @Override onSharedPreferenceChanged(SharedPreferences prefs, String key)222 public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { 223 if (Themes.KEY_THEMED_ICONS.equals(key)) { 224 mIconProvider.setIconThemeSupported(Themes.isThemedIconEnabled(mContext)); 225 verifyIconChanged(); 226 } 227 } 228 } 229 } 230