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