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