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 android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
20 import static android.content.Context.RECEIVER_EXPORTED;
21 
22 import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
23 import static com.android.launcher3.LauncherPrefs.ICON_STATE;
24 import static com.android.launcher3.LauncherPrefs.THEMED_ICONS;
25 import static com.android.launcher3.model.LoaderTask.SMARTSPACE_ON_HOME_SCREEN;
26 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
27 import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
28 import static com.android.launcher3.util.SettingsCache.PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI;
29 
30 import android.content.ComponentName;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.IntentFilter;
34 import android.content.SharedPreferences;
35 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
36 import android.content.pm.LauncherApps;
37 import android.content.pm.LauncherApps.ArchiveCompatibilityParams;
38 import android.os.UserHandle;
39 import android.util.Log;
40 
41 import androidx.annotation.Nullable;
42 import androidx.core.os.BuildCompat;
43 
44 import com.android.launcher3.graphics.IconShape;
45 import com.android.launcher3.icons.IconCache;
46 import com.android.launcher3.icons.IconProvider;
47 import com.android.launcher3.icons.LauncherIconProvider;
48 import com.android.launcher3.icons.LauncherIcons;
49 import com.android.launcher3.model.ModelLauncherCallbacks;
50 import com.android.launcher3.notification.NotificationListener;
51 import com.android.launcher3.pm.InstallSessionHelper;
52 import com.android.launcher3.pm.InstallSessionTracker;
53 import com.android.launcher3.pm.UserCache;
54 import com.android.launcher3.util.LockedUserState;
55 import com.android.launcher3.util.MainThreadInitializedObject;
56 import com.android.launcher3.util.PackageManagerHelper;
57 import com.android.launcher3.util.Preconditions;
58 import com.android.launcher3.util.RunnableList;
59 import com.android.launcher3.util.SafeCloseable;
60 import com.android.launcher3.util.SettingsCache;
61 import com.android.launcher3.util.SimpleBroadcastReceiver;
62 import com.android.launcher3.util.Themes;
63 import com.android.launcher3.util.TraceHelper;
64 import com.android.launcher3.widget.custom.CustomWidgetManager;
65 
66 public class LauncherAppState implements SafeCloseable {
67 
68     public static final String ACTION_FORCE_ROLOAD = "force-reload-launcher";
69 
70     // We do not need any synchronization for this variable as its only written on UI thread.
71     public static final MainThreadInitializedObject<LauncherAppState> INSTANCE =
72             new MainThreadInitializedObject<>(LauncherAppState::new);
73 
74     private final Context mContext;
75     private final LauncherModel mModel;
76     private final LauncherIconProvider mIconProvider;
77     private final IconCache mIconCache;
78     private final InvariantDeviceProfile mInvariantDeviceProfile;
79     private boolean mIsSafeModeEnabled;
80 
81     private final RunnableList mOnTerminateCallback = new RunnableList();
82 
getInstance(Context context)83     public static LauncherAppState getInstance(Context context) {
84         return INSTANCE.get(context);
85     }
86 
getContext()87     public Context getContext() {
88         return mContext;
89     }
90 
91     @SuppressWarnings("NewApi")
LauncherAppState(Context context)92     public LauncherAppState(Context context) {
93         this(context, LauncherFiles.APP_ICONS_DB);
94         Log.v(Launcher.TAG, "LauncherAppState initiated");
95         Preconditions.assertUIThread();
96 
97         mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
98                 () -> context.getPackageManager().isSafeMode());
99         mInvariantDeviceProfile.addOnChangeListener(modelPropertiesChanged -> {
100             if (modelPropertiesChanged) {
101                 refreshAndReloadLauncher();
102             }
103         });
104 
105         ModelLauncherCallbacks callbacks = mModel.newModelCallbacks();
106         LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
107         launcherApps.registerCallback(callbacks);
108         mOnTerminateCallback.add(() ->
109                 mContext.getSystemService(LauncherApps.class).unregisterCallback(callbacks));
110 
111         if (BuildCompat.isAtLeastV() && Flags.enableSupportForArchiving()) {
112             ArchiveCompatibilityParams params = new ArchiveCompatibilityParams();
113             params.setEnableUnarchivalConfirmation(false);
114             launcherApps.setArchiveCompatibility(params);
115         }
116 
117         SimpleBroadcastReceiver modelChangeReceiver =
118                 new SimpleBroadcastReceiver(mModel::onBroadcastIntent);
119         modelChangeReceiver.register(mContext, Intent.ACTION_LOCALE_CHANGED,
120                 ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
121         if (BuildConfig.IS_STUDIO_BUILD) {
122             mContext.registerReceiver(modelChangeReceiver, new IntentFilter(ACTION_FORCE_ROLOAD),
123                     RECEIVER_EXPORTED);
124         }
125         mOnTerminateCallback.add(() -> mContext.unregisterReceiver(modelChangeReceiver));
126 
127         SafeCloseable userChangeListener = UserCache.INSTANCE.get(mContext)
128                 .addUserEventListener(mModel::onUserEvent);
129         mOnTerminateCallback.add(userChangeListener::close);
130 
131         if (enableSmartspaceRemovalToggle()) {
132             OnSharedPreferenceChangeListener firstPagePinnedItemListener =
133                     new OnSharedPreferenceChangeListener() {
134                         @Override
135                         public void onSharedPreferenceChanged(
136                                 SharedPreferences sharedPreferences, String key) {
137                             if (SMARTSPACE_ON_HOME_SCREEN.equals(key)) {
138                                 mModel.forceReload();
139                             }
140                         }
141                     };
142             LauncherPrefs.getPrefs(mContext).registerOnSharedPreferenceChangeListener(
143                     firstPagePinnedItemListener);
144             mOnTerminateCallback.add(() -> LauncherPrefs.getPrefs(mContext)
145                     .unregisterOnSharedPreferenceChangeListener(firstPagePinnedItemListener));
146         }
147 
148         LockedUserState.get(context).runOnUserUnlocked(() -> {
149             CustomWidgetManager cwm = CustomWidgetManager.INSTANCE.get(mContext);
150             cwm.setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
151             mOnTerminateCallback.add(() -> cwm.setWidgetRefreshCallback(null));
152 
153             IconObserver observer = new IconObserver();
154             SafeCloseable iconChangeTracker = mIconProvider.registerIconChangeListener(
155                     observer, MODEL_EXECUTOR.getHandler());
156             mOnTerminateCallback.add(iconChangeTracker::close);
157             MODEL_EXECUTOR.execute(observer::verifyIconChanged);
158             LauncherPrefs.get(context).addListener(observer, THEMED_ICONS);
159             mOnTerminateCallback.add(
160                     () -> LauncherPrefs.get(mContext).removeListener(observer, THEMED_ICONS));
161 
162             InstallSessionTracker installSessionTracker =
163                     InstallSessionHelper.INSTANCE.get(context).registerInstallTracker(mModel);
164             mOnTerminateCallback.add(installSessionTracker::unregister);
165         });
166 
167         // Register an observer to rebind the notification listener when dots are re-enabled.
168         SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext);
169         SettingsCache.OnChangeListener notificationLister = this::onNotificationSettingsChanged;
170         settingsCache.register(NOTIFICATION_BADGING_URI, notificationLister);
171         onNotificationSettingsChanged(settingsCache.getValue(NOTIFICATION_BADGING_URI));
172         mOnTerminateCallback.add(() ->
173                 settingsCache.unregister(NOTIFICATION_BADGING_URI, notificationLister));
174         // Register an observer to notify Launcher about Private Space settings toggle.
175         registerPrivateSpaceHideWhenLockListener(settingsCache);
176     }
177 
LauncherAppState(Context context, @Nullable String iconCacheFileName)178     public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
179         mContext = context;
180 
181         mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
182         mIconProvider = new LauncherIconProvider(context);
183         mIconCache = new IconCache(mContext, mInvariantDeviceProfile,
184                 iconCacheFileName, mIconProvider);
185         mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext),
186                 PackageManagerHelper.INSTANCE.get(context), iconCacheFileName != null);
187         mOnTerminateCallback.add(mIconCache::close);
188         mOnTerminateCallback.add(mModel::destroy);
189     }
190 
onNotificationSettingsChanged(boolean areNotificationDotsEnabled)191     private void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
192         if (areNotificationDotsEnabled) {
193             NotificationListener.requestRebind(new ComponentName(
194                     mContext, NotificationListener.class));
195         }
196     }
197 
registerPrivateSpaceHideWhenLockListener(SettingsCache settingsCache)198     private void registerPrivateSpaceHideWhenLockListener(SettingsCache settingsCache) {
199         SettingsCache.OnChangeListener psHideWhenLockChangedListener =
200                 this::onPrivateSpaceHideWhenLockChanged;
201         settingsCache.register(PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI, psHideWhenLockChangedListener);
202         mOnTerminateCallback.add(() -> settingsCache.unregister(PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI,
203                 psHideWhenLockChangedListener));
204     }
205 
onPrivateSpaceHideWhenLockChanged(boolean isPrivateSpaceHideOnLockEnabled)206     private void onPrivateSpaceHideWhenLockChanged(boolean isPrivateSpaceHideOnLockEnabled) {
207         mModel.forceReload();
208     }
209 
refreshAndReloadLauncher()210     private void refreshAndReloadLauncher() {
211         LauncherIcons.clearPool(mContext);
212         mIconCache.updateIconParams(
213                 mInvariantDeviceProfile.fillResIconDpi, mInvariantDeviceProfile.iconBitmapSize);
214         mModel.forceReload();
215     }
216 
217     /**
218      * Call from Application.onTerminate(), which is not guaranteed to ever be called.
219      */
220     @Override
close()221     public void close() {
222         mOnTerminateCallback.executeAllAndDestroy();
223     }
224 
getIconProvider()225     public IconProvider getIconProvider() {
226         return mIconProvider;
227     }
228 
getIconCache()229     public IconCache getIconCache() {
230         return mIconCache;
231     }
232 
getModel()233     public LauncherModel getModel() {
234         return mModel;
235     }
236 
getInvariantDeviceProfile()237     public InvariantDeviceProfile getInvariantDeviceProfile() {
238         return mInvariantDeviceProfile;
239     }
240 
isSafeModeEnabled()241     public boolean isSafeModeEnabled() {
242         return mIsSafeModeEnabled;
243     }
244 
245     /**
246      * Shorthand for {@link #getInvariantDeviceProfile()}
247      */
getIDP(Context context)248     public static InvariantDeviceProfile getIDP(Context context) {
249         return InvariantDeviceProfile.INSTANCE.get(context);
250     }
251 
252     private class IconObserver
253             implements IconProvider.IconChangeListener, OnSharedPreferenceChangeListener {
254 
255         @Override
onAppIconChanged(String packageName, UserHandle user)256         public void onAppIconChanged(String packageName, UserHandle user) {
257             mModel.onAppIconChanged(packageName, user);
258         }
259 
260         @Override
onSystemIconStateChanged(String iconState)261         public void onSystemIconStateChanged(String iconState) {
262             IconShape.INSTANCE.get(mContext).pickBestShape(mContext);
263             refreshAndReloadLauncher();
264             LauncherPrefs.get(mContext).put(ICON_STATE, iconState);
265         }
266 
verifyIconChanged()267         void verifyIconChanged() {
268             String iconState = mIconProvider.getSystemIconState();
269             if (!iconState.equals(LauncherPrefs.get(mContext).get(ICON_STATE))) {
270                 onSystemIconStateChanged(iconState);
271             }
272         }
273 
274         @Override
onSharedPreferenceChanged(SharedPreferences prefs, String key)275         public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
276             if (Themes.KEY_THEMED_ICONS.equals(key)) {
277                 mIconProvider.setIconThemeSupported(Themes.isThemedIconEnabled(mContext));
278                 verifyIconChanged();
279             }
280         }
281     }
282 }
283