diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java index f92b3e33b2..a9fc1aa085 100644 --- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java +++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java @@ -18,8 +18,7 @@ package com.android.launcher3.model; import static android.content.ContentResolver.SCHEME_CONTENT; -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -import static com.android.launcher3.util.Executors.createAndStartNewLooper; +import static com.android.launcher3.Utilities.newContentObserver; import android.annotation.TargetApi; import android.app.RemoteAction; @@ -35,7 +34,7 @@ import android.os.Build; import android.os.Bundle; import android.os.DeadObjectException; import android.os.Handler; -import android.os.Message; +import android.os.Looper; import android.os.Process; import android.os.UserHandle; import android.text.TextUtils; @@ -43,7 +42,7 @@ import android.util.ArrayMap; import android.util.Log; import androidx.annotation.MainThread; -import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; import com.android.launcher3.BaseDraggingActivity; @@ -55,6 +54,7 @@ import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.popup.RemoteActionShortcut; import com.android.launcher3.popup.SystemShortcut; +import com.android.launcher3.util.BgObjectWithLooper; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.Preconditions; @@ -68,15 +68,11 @@ import java.util.Map; * Data model for digital wellbeing status of apps. */ @TargetApi(Build.VERSION_CODES.Q) -public final class WellbeingModel { +public final class WellbeingModel extends BgObjectWithLooper { private static final String TAG = "WellbeingModel"; private static final int[] RETRY_TIMES_MS = {5000, 15000, 30000}; private static final boolean DEBUG = false; - private static final int MSG_PACKAGE_ADDED = 1; - private static final int MSG_PACKAGE_REMOVED = 2; - private static final int MSG_FULL_REFRESH = 3; - private static final int UNKNOWN_MINIMAL_DEVICE_STATE = 0; private static final int IN_MINIMAL_DEVICE = 2; @@ -98,9 +94,9 @@ public final class WellbeingModel { private final Context mContext; private final String mWellbeingProviderPkg; - private final Handler mWorkerHandler; - private final ContentObserver mContentObserver; + private Handler mWorkerHandler; + private ContentObserver mContentObserver; private final Object mModelLock = new Object(); // Maps the action Id to the corresponding RemoteAction @@ -111,60 +107,67 @@ public final class WellbeingModel { private WellbeingModel(final Context context) { mContext = context; - mWorkerHandler = - new Handler(createAndStartNewLooper("WellbeingHandler"), this::handleMessage); - mWellbeingProviderPkg = mContext.getString(R.string.wellbeing_provider_pkg); - mContentObserver = new ContentObserver(MAIN_EXECUTOR.getHandler()) { - @Override - public void onChange(boolean selfChange, Uri uri) { - if (DEBUG || mIsInTest) { - Log.d(TAG, "ContentObserver.onChange() called with: selfChange = [" - + selfChange + "], uri = [" + uri + "]"); - } - Preconditions.assertUIThread(); - - if (uri.getPath().contains(PATH_ACTIONS)) { - // Wellbeing reports that app actions have changed. - updateWellbeingData(); - } else if (uri.getPath().contains(PATH_MINIMAL_DEVICE)) { - // Wellbeing reports that minimal device state or config is changed. - updateLauncherModel(context); - } - } - }; - FeatureFlags.ENABLE_MINIMAL_DEVICE.addChangeListener(mContext, () -> - updateLauncherModel(context)); + initializeInBackground("WellbeingHandler"); + } + @Override + protected void onInitialized(Looper looper) { + mWorkerHandler = new Handler(looper); + mContentObserver = newContentObserver(mWorkerHandler, this::onWellbeingUriChanged); if (!TextUtils.isEmpty(mWellbeingProviderPkg)) { - context.registerReceiver( - new SimpleBroadcastReceiver(this::onWellbeingProviderChanged), + mContext.registerReceiver( + new SimpleBroadcastReceiver(t -> restartObserver()), PackageManagerHelper.getPackageFilter(mWellbeingProviderPkg, Intent.ACTION_PACKAGE_ADDED, Intent.ACTION_PACKAGE_CHANGED, Intent.ACTION_PACKAGE_REMOVED, Intent.ACTION_PACKAGE_DATA_CLEARED, - Intent.ACTION_PACKAGE_RESTARTED)); + Intent.ACTION_PACKAGE_RESTARTED), + null, mWorkerHandler); IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addDataScheme("package"); - context.registerReceiver(new SimpleBroadcastReceiver(this::onAppPackageChanged), - filter); + mContext.registerReceiver(new SimpleBroadcastReceiver(this::onAppPackageChanged), + filter, null, mWorkerHandler); restartObserver(); } } + @WorkerThread + private void onWellbeingUriChanged(Uri uri) { + Preconditions.assertNonUiThread(); + if (DEBUG || mIsInTest) { + Log.d(TAG, "ContentObserver.onChange() called with: uri = [" + uri + "]"); + } + if (uri.getPath().contains(PATH_ACTIONS)) { + // Wellbeing reports that app actions have changed. + updateAllPackages(); + } else if (uri.getPath().contains(PATH_MINIMAL_DEVICE)) { + // Wellbeing reports that minimal device state or config is changed. + if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) { + return; + } + final Bundle extras = new Bundle(); + String dbFile; + if (isInMinimalDeviceMode()) { + dbFile = DB_NAME_MINIMAL_DEVICE; + extras.putString(LauncherProvider.KEY_LAYOUT_PROVIDER_AUTHORITY, + mWellbeingProviderPkg + ".api"); + } else { + dbFile = InvariantDeviceProfile.INSTANCE.get(mContext).dbFile; + } + LauncherSettings.Settings.call(mContext.getContentResolver(), + LauncherSettings.Settings.METHOD_SWITCH_DATABASE, + dbFile, extras); + } + } + public void setInTest(boolean inTest) { mIsInTest = inTest; } - protected void onWellbeingProviderChanged(Intent intent) { - if (DEBUG || mIsInTest) { - Log.d(TAG, "Changes to Wellbeing package: intent = [" + intent + "]"); - } - restartObserver(); - } - + @WorkerThread private void restartObserver() { final ContentResolver resolver = mContext.getContentResolver(); resolver.unregisterContentObserver(mContentObserver); @@ -179,7 +182,7 @@ public final class WellbeingModel { Log.e(TAG, "Failed to register content observer for " + actionsUri + ": " + e); if (mIsInTest) throw new RuntimeException(e); } - updateWellbeingData(); + updateAllPackages(); } @MainThread @@ -212,39 +215,6 @@ public final class WellbeingModel { } } - private void updateWellbeingData() { - mWorkerHandler.sendEmptyMessage(MSG_FULL_REFRESH); - } - - private void updateLauncherModel(@NonNull final Context context) { - if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) { - reloadLauncherInNormalMode(context); - return; - } - mWorkerHandler.post(() -> { - if (isInMinimalDeviceMode()) { - reloadLauncherInMinimalMode(context); - } else { - reloadLauncherInNormalMode(context); - } - }); - } - - private void reloadLauncherInNormalMode(@NonNull final Context context) { - LauncherSettings.Settings.call(context.getContentResolver(), - LauncherSettings.Settings.METHOD_SWITCH_DATABASE, - InvariantDeviceProfile.INSTANCE.get(context).dbFile); - } - - private void reloadLauncherInMinimalMode(@NonNull final Context context) { - final Bundle extras = new Bundle(); - extras.putString(LauncherProvider.KEY_LAYOUT_PROVIDER_AUTHORITY, - mWellbeingProviderPkg + ".api"); - LauncherSettings.Settings.call(context.getContentResolver(), - LauncherSettings.Settings.METHOD_SWITCH_DATABASE, - DB_NAME_MINIMAL_DEVICE, extras); - } - private Uri.Builder apiBuilder() { return new Uri.Builder() .scheme(SCHEME_CONTENT) @@ -277,7 +247,8 @@ public final class WellbeingModel { return false; } - private boolean updateActions(String... packageNames) { + @WorkerThread + private boolean updateActions(String[] packageNames) { if (packageNames.length == 0) { return true; } @@ -340,68 +311,51 @@ public final class WellbeingModel { return true; } - private boolean handleMessage(Message msg) { - switch (msg.what) { - case MSG_PACKAGE_REMOVED: { - String packageName = (String) msg.obj; - mWorkerHandler.removeCallbacksAndMessages(packageName); - synchronized (mModelLock) { - mPackageToActionId.remove(packageName); - } - return true; - } - case MSG_PACKAGE_ADDED: { - String packageName = (String) msg.obj; - mWorkerHandler.removeCallbacksAndMessages(packageName); - if (!updateActions(packageName)) { - scheduleRefreshRetry(msg); - } - return true; - } + @WorkerThread + private void updateActionsWithRetry(int retryCount, @Nullable String packageName) { + String[] packageNames = TextUtils.isEmpty(packageName) + ? mContext.getSystemService(LauncherApps.class) + .getActivityList(null, Process.myUserHandle()).stream() + .map(li -> li.getApplicationInfo().packageName).distinct() + .toArray(String[]::new) + : new String[] { packageName }; - case MSG_FULL_REFRESH: { - // Remove all existing messages - mWorkerHandler.removeCallbacksAndMessages(null); - final String[] packageNames = mContext.getSystemService(LauncherApps.class) - .getActivityList(null, Process.myUserHandle()).stream() - .map(li -> li.getApplicationInfo().packageName).distinct() - .toArray(String[]::new); - if (!updateActions(packageNames)) { - scheduleRefreshRetry(msg); - } - return true; - } + mWorkerHandler.removeCallbacksAndMessages(packageName); + if (updateActions(packageNames)) { + return; } - return false; - } - - private void scheduleRefreshRetry(Message originalMsg) { - int retryCount = originalMsg.arg1; if (retryCount >= RETRY_TIMES_MS.length) { // To many retries, skip return; } - - Message msg = Message.obtain(originalMsg); - msg.arg1 = retryCount + 1; - mWorkerHandler.sendMessageDelayed(msg, RETRY_TIMES_MS[retryCount]); + mWorkerHandler.postDelayed( + () -> updateActionsWithRetry(retryCount + 1, packageName), + packageName, RETRY_TIMES_MS[retryCount]); } + @WorkerThread + private void updateAllPackages() { + updateActionsWithRetry(0, null); + } + + @WorkerThread private void onAppPackageChanged(Intent intent) { if (DEBUG || mIsInTest) Log.d(TAG, "Changes in apps: intent = [" + intent + "]"); - Preconditions.assertUIThread(); + Preconditions.assertNonUiThread(); final String packageName = intent.getData().getSchemeSpecificPart(); if (packageName == null || packageName.length() == 0) { // they sent us a bad intent return; } - final String action = intent.getAction(); if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { - Message.obtain(mWorkerHandler, MSG_PACKAGE_REMOVED, packageName).sendToTarget(); + mWorkerHandler.removeCallbacksAndMessages(packageName); + synchronized (mModelLock) { + mPackageToActionId.remove(packageName); + } } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { - Message.obtain(mWorkerHandler, MSG_PACKAGE_ADDED, packageName).sendToTarget(); + updateActionsWithRetry(0, packageName); } } diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java index de44e07bae..5f0ef83382 100644 --- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java +++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java @@ -23,6 +23,7 @@ import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; +import static com.android.launcher3.Utilities.newContentObserver; import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS; @@ -73,12 +74,9 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre private static final boolean DEBUG = false; private static final String DELIMITER_DOT = "\\."; - private ContentObserver mSystemAutoRotateObserver = new ContentObserver(new Handler()) { - @Override - public void onChange(boolean selfChange) { - updateAutoRotateSetting(); - } - }; + private ContentObserver mSystemAutoRotateObserver = + newContentObserver(new Handler(), t -> updateAutoRotateSetting()); + @Retention(SOURCE) @IntDef({ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270}) public @interface SurfaceRotation {} diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 43ccb7990c..1e023dfc0a 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -34,6 +34,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; import android.content.res.Resources; +import android.database.ContentObserver; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Matrix; @@ -44,6 +45,7 @@ import android.graphics.RectF; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; +import android.net.Uri; import android.os.Build; import android.os.DeadObjectException; import android.os.Handler; @@ -83,6 +85,7 @@ import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import java.util.Locale; +import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -661,6 +664,18 @@ public final class Utilities { return slop * slop; } + /** + * Helper method to create a content provider + */ + public static ContentObserver newContentObserver(Handler handler, Consumer command) { + return new ContentObserver(handler) { + @Override + public void onChange(boolean selfChange, Uri uri) { + command.accept(uri); + } + }; + } + private static class FixedSizeEmptyDrawable extends ColorDrawable { private final int mSize; diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java index 5a60f5328a..ecf4f36c52 100644 --- a/src/com/android/launcher3/states/RotationHelper.java +++ b/src/com/android/launcher3/states/RotationHelper.java @@ -21,14 +21,9 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE; import android.app.Activity; -import android.content.ContentResolver; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.res.Resources; -import android.database.ContentObserver; -import android.os.Handler; -import android.provider.Settings; -import android.util.Log; import com.android.launcher3.R; import com.android.launcher3.Utilities; @@ -43,16 +38,6 @@ public class RotationHelper implements OnSharedPreferenceChangeListener { public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation"; - private final ContentResolver mContentResolver; - private boolean mSystemAutoRotateEnabled; - - private ContentObserver mSystemAutoRotateObserver = new ContentObserver(new Handler()) { - @Override - public void onChange(boolean selfChange) { - updateAutoRotateSetting(); - } - }; - public static boolean getAllowRotationDefaultValue() { // If the device's pixel density was scaled (usually via settings for A11y), use the // original dimensions to determine if rotation is allowed of not. @@ -106,20 +91,6 @@ public class RotationHelper implements OnSharedPreferenceChangeListener { } else { mSharedPrefs = null; } - - mContentResolver = activity.getContentResolver(); - } - - private void updateAutoRotateSetting() { - int autoRotateEnabled = 0; - try { - autoRotateEnabled = Settings.System.getInt(mContentResolver, - Settings.System.ACCELEROMETER_ROTATION); - } catch (Settings.SettingNotFoundException e) { - Log.e(TAG, "autorotate setting not found", e); - } - - mSystemAutoRotateEnabled = autoRotateEnabled == 1; } @Override @@ -129,7 +100,6 @@ public class RotationHelper implements OnSharedPreferenceChangeListener { getAllowRotationDefaultValue()); if (mHomeRotationEnabled != wasRotationEnabled) { notifyChange(); - updateAutoRotateSetting(); } } @@ -165,11 +135,6 @@ public class RotationHelper implements OnSharedPreferenceChangeListener { if (!mInitialized) { mInitialized = true; notifyChange(); - - mContentResolver.registerContentObserver( - Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), - false, mSystemAutoRotateObserver); - updateAutoRotateSetting(); } } @@ -179,7 +144,6 @@ public class RotationHelper implements OnSharedPreferenceChangeListener { if (mSharedPrefs != null) { mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this); } - mContentResolver.unregisterContentObserver(mSystemAutoRotateObserver); } } @@ -225,9 +189,8 @@ public class RotationHelper implements OnSharedPreferenceChangeListener { @Override public String toString() { return String.format("[mStateHandlerRequest=%d, mCurrentStateRequest=%d," - + " mLastActivityFlags=%d, mIgnoreAutoRotateSettings=%b, mHomeRotationEnabled=%b," - + " mSystemAutoRotateEnabled=%b]", + + " mLastActivityFlags=%d, mIgnoreAutoRotateSettings=%b, mHomeRotationEnabled=%b]", mStateHandlerRequest, mCurrentStateRequest, mLastActivityFlags, - mIgnoreAutoRotateSettings, mHomeRotationEnabled, mSystemAutoRotateEnabled); + mIgnoreAutoRotateSettings, mHomeRotationEnabled); } } diff --git a/src/com/android/launcher3/util/BgObjectWithLooper.java b/src/com/android/launcher3/util/BgObjectWithLooper.java new file mode 100644 index 0000000000..1483c43a11 --- /dev/null +++ b/src/com/android/launcher3/util/BgObjectWithLooper.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.util; + +import android.os.Looper; + +import androidx.annotation.WorkerThread; + +/** + * Utility class to define an object which does most of it's processing on a + * dedicated background thread. + */ +public abstract class BgObjectWithLooper { + + /** + * Start initialization of the object + */ + public final void initializeInBackground(String threadName) { + new Thread(this::runOnThread, threadName).start(); + } + + private void runOnThread() { + Looper.prepare(); + onInitialized(Looper.myLooper()); + Looper.loop(); + } + + /** + * Called on the background thread to handle initialization + */ + @WorkerThread + protected abstract void onInitialized(Looper looper); +}