diff options
Diffstat (limited to 'src/com/android/launcher3/util')
16 files changed, 392 insertions, 71 deletions
diff --git a/src/com/android/launcher3/util/ConfigMonitor.java b/src/com/android/launcher3/util/ConfigMonitor.java index 12d35e962..0f8152057 100644 --- a/src/com/android/launcher3/util/ConfigMonitor.java +++ b/src/com/android/launcher3/util/ConfigMonitor.java @@ -16,20 +16,15 @@ package com.android.launcher3.util; * limitations under the License. */ +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; import android.graphics.Point; -import android.hardware.display.DisplayManager; -import android.hardware.display.DisplayManager.DisplayListener; -import android.os.Handler; import android.util.Log; -import android.view.Display; -import android.view.WindowManager; - -import com.android.launcher3.MainThreadExecutor; import java.util.function.Consumer; @@ -37,7 +32,8 @@ import java.util.function.Consumer; * {@link BroadcastReceiver} which watches configuration changes and * notifies the callback in case changes which affect the device profile occur. */ -public class ConfigMonitor extends BroadcastReceiver implements DisplayListener { +public class ConfigMonitor extends BroadcastReceiver implements + DefaultDisplay.DisplayInfoChangeListener { private static final String TAG = "ConfigMonitor"; @@ -61,24 +57,19 @@ public class ConfigMonitor extends BroadcastReceiver implements DisplayListener mFontScale = config.fontScale; mDensity = config.densityDpi; - Display display = getDefaultDisplay(context); - mDisplayId = display.getDisplayId(); - - mRealSize = new Point(); - display.getRealSize(mRealSize); + DefaultDisplay display = DefaultDisplay.INSTANCE.get(context); + display.addChangeListener(this); + DefaultDisplay.Info displayInfo = display.getInfo(); + mDisplayId = displayInfo.id; - mSmallestSize = new Point(); - mLargestSize = new Point(); - display.getCurrentSizeRange(mSmallestSize, mLargestSize); + mRealSize = new Point(displayInfo.realSize); + mSmallestSize = new Point(displayInfo.smallestSize); + mLargestSize = new Point(displayInfo.largestSize); mCallback = callback; // Listen for configuration change mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED)); - - // Listen for display manager change - mContext.getSystemService(DisplayManager.class) - .registerDisplayListener(this, new Handler(UiThreadHelper.getBackgroundLooper())); } @Override @@ -91,26 +82,19 @@ public class ConfigMonitor extends BroadcastReceiver implements DisplayListener } @Override - public void onDisplayAdded(int displayId) { } - - @Override - public void onDisplayRemoved(int displayId) { } - - @Override - public void onDisplayChanged(int displayId) { - if (displayId != mDisplayId) { + public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) { + if (info.id != mDisplayId) { return; } - Display display = getDefaultDisplay(mContext); - display.getRealSize(mTmpPoint1); - + mTmpPoint1.set(info.realSize.x, info.realSize.y); if (!mRealSize.equals(mTmpPoint1) && !mRealSize.equals(mTmpPoint1.y, mTmpPoint1.x)) { Log.d(TAG, String.format("Display size changed from %s to %s", mRealSize, mTmpPoint1)); notifyChange(); return; } - display.getCurrentSizeRange(mTmpPoint1, mTmpPoint2); + mTmpPoint1.set(info.smallestSize.x, info.smallestSize.y); + mTmpPoint2.set(info.largestSize.x, info.largestSize.y); if (!mSmallestSize.equals(mTmpPoint1) || !mLargestSize.equals(mTmpPoint2)) { Log.d(TAG, String.format("Available size changed from [%s, %s] to [%s, %s]", mSmallestSize, mLargestSize, mTmpPoint1, mTmpPoint2)); @@ -122,18 +106,15 @@ public class ConfigMonitor extends BroadcastReceiver implements DisplayListener if (mCallback != null) { Consumer<Context> callback = mCallback; mCallback = null; - new MainThreadExecutor().execute(() -> callback.accept(mContext)); + MAIN_EXECUTOR.execute(() -> callback.accept(mContext)); } } - private Display getDefaultDisplay(Context context) { - return context.getSystemService(WindowManager.class).getDefaultDisplay(); - } - public void unregister() { try { mContext.unregisterReceiver(this); - mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this); + DefaultDisplay display = DefaultDisplay.INSTANCE.get(mContext); + display.removeChangeListener(this); } catch (Exception e) { Log.e(TAG, "Failed to unregister config monitor", e); } diff --git a/src/com/android/launcher3/util/DefaultDisplay.java b/src/com/android/launcher3/util/DefaultDisplay.java index 7719f084d..8529d50ca 100644 --- a/src/com/android/launcher3/util/DefaultDisplay.java +++ b/src/com/android/launcher3/util/DefaultDisplay.java @@ -15,12 +15,15 @@ */ package com.android.launcher3.util; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + import android.content.Context; import android.graphics.Point; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.os.Handler; import android.os.Message; +import android.util.DisplayMetrics; import android.util.Log; import android.view.Display; import android.view.WindowManager; @@ -54,7 +57,7 @@ public class DefaultDisplay implements DisplayListener { mChangeHandler = new Handler(this::onChange); context.getSystemService(DisplayManager.class) - .registerDisplayListener(this, new Handler(UiThreadHelper.getBackgroundLooper())); + .registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler()); } @Override @@ -122,6 +125,8 @@ public class DefaultDisplay implements DisplayListener { public final Point smallestSize; public final Point largestSize; + public final DisplayMetrics metrics; + private Info(Context context) { Display display = context.getSystemService(WindowManager.class).getDefaultDisplay(); @@ -136,6 +141,9 @@ public class DefaultDisplay implements DisplayListener { largestSize = new Point(); display.getRealSize(realSize); display.getCurrentSizeRange(smallestSize, largestSize); + + metrics = new DisplayMetrics(); + display.getMetrics(metrics); } private boolean hasDifferentSize(Info info) { diff --git a/src/com/android/launcher3/util/Executors.java b/src/com/android/launcher3/util/Executors.java new file mode 100644 index 000000000..4d5ee49e8 --- /dev/null +++ b/src/com/android/launcher3/util/Executors.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2008 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.HandlerThread; +import android.os.Looper; +import android.os.Process; + +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * Various different executors used in Launcher + */ +public class Executors { + + // These values are same as that in {@link AsyncTask}. + private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); + private static final int CORE_POOL_SIZE = CPU_COUNT + 1; + private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; + private static final int KEEP_ALIVE = 1; + + /** + * An {@link Executor} to be used with async task with no limit on the queue size. + */ + public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor( + CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, + TimeUnit.SECONDS, new LinkedBlockingQueue<>()); + + /** + * Returns the executor for running tasks on the main thread. + */ + public static final LooperExecutor MAIN_EXECUTOR = + new LooperExecutor(Looper.getMainLooper()); + + /** + * A background executor for using time sensitive actions where user is waiting for response. + */ + public static final LooperExecutor UI_HELPER_EXECUTOR = + new LooperExecutor(createAndStartNewForegroundLooper("UiThreadHelper")); + + /** + * Utility method to get a started handler thread statically + */ + public static Looper createAndStartNewLooper(String name) { + return createAndStartNewLooper(name, Process.THREAD_PRIORITY_DEFAULT); + } + + /** + * Utility method to get a started handler thread statically with the provided priority + */ + public static Looper createAndStartNewLooper(String name, int priority) { + HandlerThread thread = new HandlerThread(name, priority); + thread.start(); + return thread.getLooper(); + } + + /** + * Similar to {@link #createAndStartNewLooper(String)}, but starts the thread with + * foreground priority. + * Think before using + */ + public static Looper createAndStartNewForegroundLooper(String name) { + return createAndStartNewLooper(name, Process.THREAD_PRIORITY_FOREGROUND); + } + + /** + * Executor used for running Launcher model related tasks (eg loading icons or updated db) + */ + public static final LooperExecutor MODEL_EXECUTOR = + new LooperExecutor(createAndStartNewLooper("launcher-loader")); +} diff --git a/src/com/android/launcher3/util/IOUtils.java b/src/com/android/launcher3/util/IOUtils.java index f95f74d60..4a4a5ca22 100644 --- a/src/com/android/launcher3/util/IOUtils.java +++ b/src/com/android/launcher3/util/IOUtils.java @@ -17,10 +17,13 @@ package com.android.launcher3.util; import android.content.Context; +import android.util.Log; +import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; import java.io.ByteArrayOutputStream; +import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -35,6 +38,7 @@ import java.util.UUID; public class IOUtils { private static final int BUF_SIZE = 0x1000; // 4K + private static final String TAG = "IOUtils"; public static byte[] toByteArray(File file) throws IOException { try (InputStream in = new FileInputStream(file)) { @@ -77,4 +81,16 @@ public class IOUtils { } return file.getAbsolutePath(); } + + public static void closeSilently(Closeable c) { + if (c != null) { + try { + c.close(); + } catch (IOException e) { + if (FeatureFlags.IS_DOGFOOD_BUILD) { + Log.d(TAG, "Error closing", e); + } + } + } + } } diff --git a/src/com/android/launcher3/util/IntArray.java b/src/com/android/launcher3/util/IntArray.java index d2a551f45..7252f7ac1 100644 --- a/src/com/android/launcher3/util/IntArray.java +++ b/src/com/android/launcher3/util/IntArray.java @@ -17,6 +17,7 @@ package com.android.launcher3.util; import java.util.Arrays; +import java.util.StringTokenizer; /** * Copy of the platform hidden implementation of android.util.IntArray. @@ -248,6 +249,17 @@ public class IntArray implements Cloneable { return b.toString(); } + public static IntArray fromConcatString(String concatString) { + StringTokenizer tokenizer = new StringTokenizer(concatString, ","); + int[] array = new int[tokenizer.countTokens()]; + int count = 0; + while (tokenizer.hasMoreTokens()) { + array[count] = Integer.parseInt(tokenizer.nextToken().trim()); + count++; + } + return new IntArray(array, array.length); + } + /** * Throws {@link ArrayIndexOutOfBoundsException} if the index is out of bounds. * diff --git a/src/com/android/launcher3/util/LooperExecutor.java b/src/com/android/launcher3/util/LooperExecutor.java index cc0746997..8ac600f73 100644 --- a/src/com/android/launcher3/util/LooperExecutor.java +++ b/src/com/android/launcher3/util/LooperExecutor.java @@ -16,7 +16,9 @@ package com.android.launcher3.util; import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; +import android.os.Process; import java.util.List; import java.util.concurrent.AbstractExecutorService; @@ -47,6 +49,13 @@ public class LooperExecutor extends AbstractExecutorService { } /** + * Same as execute, but never runs the action inline. + */ + public void post(Runnable runnable) { + mHandler.post(runnable); + } + + /** * Not supported and throws an exception when used. */ @Override @@ -79,7 +88,31 @@ public class LooperExecutor extends AbstractExecutorService { */ @Override @Deprecated - public boolean awaitTermination(long l, TimeUnit timeUnit) throws InterruptedException { + public boolean awaitTermination(long l, TimeUnit timeUnit) { throw new UnsupportedOperationException(); } + + /** + * Returns the thread for this executor + */ + public Thread getThread() { + return mHandler.getLooper().getThread(); + } + + /** + * Returns the looper for this executor + */ + public Looper getLooper() { + return mHandler.getLooper(); + } + + /** + * Set the priority of a thread, based on Linux priorities. + * @param priority Linux priority level, from -20 for highest scheduling priority + * to 19 for lowest scheduling priority. + * @see Process#setThreadPriority(int, int) + */ + public void setThreadPriority(int priority) { + Process.setThreadPriority(((HandlerThread) getThread()).getThreadId(), priority); + } } diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java index e185a3199..fe9c2c468 100644 --- a/src/com/android/launcher3/util/MainThreadInitializedObject.java +++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java @@ -15,12 +15,13 @@ */ package com.android.launcher3.util; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + import android.content.Context; import android.os.Looper; import androidx.annotation.VisibleForTesting; -import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.util.ResourceBasedOverride.Overrides; import java.util.concurrent.ExecutionException; @@ -43,7 +44,7 @@ public class MainThreadInitializedObject<T> { mValue = mProvider.get(context.getApplicationContext()); } else { try { - return new MainThreadExecutor().submit(() -> get(context)).get(); + return MAIN_EXECUTOR.submit(() -> get(context)).get(); } catch (InterruptedException|ExecutionException e) { throw new RuntimeException(e); } diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java index 7439ac154..78d1d3ca8 100644 --- a/src/com/android/launcher3/util/PackageManagerHelper.java +++ b/src/com/android/launcher3/util/PackageManagerHelper.java @@ -24,9 +24,11 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; +import android.content.res.Resources; import android.graphics.Rect; import android.net.Uri; import android.os.Build; @@ -35,6 +37,7 @@ import android.os.PatternMatcher; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import android.widget.Toast; import com.android.launcher3.AppInfo; @@ -215,4 +218,76 @@ public class PackageManagerHelper { packageFilter.addDataSchemeSpecificPart(pkg, PatternMatcher.PATTERN_LITERAL); return packageFilter; } + + public static boolean isSystemApp(Context context, Intent intent) { + PackageManager pm = context.getPackageManager(); + ComponentName cn = intent.getComponent(); + String packageName = null; + if (cn == null) { + ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); + if ((info != null) && (info.activityInfo != null)) { + packageName = info.activityInfo.packageName; + } + } else { + packageName = cn.getPackageName(); + } + if (packageName == null) { + packageName = intent.getPackage(); + } + if (packageName != null) { + try { + PackageInfo info = pm.getPackageInfo(packageName, 0); + return (info != null) && (info.applicationInfo != null) && + ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0); + } catch (NameNotFoundException e) { + return false; + } + } else { + return false; + } + } + + /** + * Finds a system apk which had a broadcast receiver listening to a particular action. + * @param action intent action used to find the apk + * @return a pair of apk package name and the resources. + */ + public static Pair<String, Resources> findSystemApk(String action, PackageManager pm) { + final Intent intent = new Intent(action); + for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) { + if (info.activityInfo != null && + (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + final String packageName = info.activityInfo.packageName; + try { + final Resources res = pm.getResourcesForApplication(packageName); + return Pair.create(packageName, res); + } catch (NameNotFoundException e) { + Log.w(TAG, "Failed to find resources for " + packageName); + } + } + } + return null; + } + + /** + * Returns true if the intent is a valid launch intent for a launcher activity of an app. + * This is used to identify shortcuts which are different from the ones exposed by the + * applications' manifest file. + * + * @param launchIntent The intent that will be launched when the shortcut is clicked. + */ + public static boolean isLauncherAppTarget(Intent launchIntent) { + if (launchIntent != null + && Intent.ACTION_MAIN.equals(launchIntent.getAction()) + && launchIntent.getComponent() != null + && launchIntent.getCategories() != null + && launchIntent.getCategories().size() == 1 + && launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER) + && TextUtils.isEmpty(launchIntent.getDataString())) { + // An app target can either have no extra or have ItemInfo.EXTRA_PROFILE. + Bundle extras = launchIntent.getExtras(); + return extras == null || extras.keySet().isEmpty(); + } + return false; + } } diff --git a/src/com/android/launcher3/util/PackageUserKey.java b/src/com/android/launcher3/util/PackageUserKey.java index aa11968e4..f243ca628 100644 --- a/src/com/android/launcher3/util/PackageUserKey.java +++ b/src/com/android/launcher3/util/PackageUserKey.java @@ -6,7 +6,6 @@ import android.service.notification.StatusBarNotification; import androidx.annotation.Nullable; import com.android.launcher3.ItemInfo; -import com.android.launcher3.shortcuts.DeepShortcutManager; import java.util.Arrays; diff --git a/src/com/android/launcher3/util/Preconditions.java b/src/com/android/launcher3/util/Preconditions.java index 7ab0d3103..ed66422be 100644 --- a/src/com/android/launcher3/util/Preconditions.java +++ b/src/com/android/launcher3/util/Preconditions.java @@ -16,9 +16,10 @@ package com.android.launcher3.util; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.os.Looper; -import com.android.launcher3.LauncherModel; import com.android.launcher3.config.FeatureFlags; /** @@ -33,7 +34,7 @@ public class Preconditions { } public static void assertWorkerThread() { - if (FeatureFlags.IS_DOGFOOD_BUILD && !isSameLooper(LauncherModel.getWorkerLooper())) { + if (FeatureFlags.IS_DOGFOOD_BUILD && !isSameLooper(MODEL_EXECUTOR.getLooper())) { throw new IllegalStateException(); } } diff --git a/src/com/android/launcher3/util/SafeCloseable.java b/src/com/android/launcher3/util/SafeCloseable.java new file mode 100644 index 000000000..ba8ee04d2 --- /dev/null +++ b/src/com/android/launcher3/util/SafeCloseable.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019 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; + +/** + * Extension of closeable which does not throw an exception + */ +public interface SafeCloseable extends AutoCloseable { + + @Override + void close(); +} diff --git a/src/com/android/launcher3/util/UiThreadHelper.java b/src/com/android/launcher3/util/UiThreadHelper.java index cc442f988..f8d163230 100644 --- a/src/com/android/launcher3/util/UiThreadHelper.java +++ b/src/com/android/launcher3/util/UiThreadHelper.java @@ -15,14 +15,13 @@ */ package com.android.launcher3.util; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + import android.app.Activity; import android.content.Context; import android.os.Handler; -import android.os.HandlerThread; import android.os.IBinder; -import android.os.Looper; import android.os.Message; -import android.os.Process; import android.view.inputmethod.InputMethodManager; /** @@ -30,25 +29,15 @@ import android.view.inputmethod.InputMethodManager; */ public class UiThreadHelper { - private static HandlerThread sHandlerThread; private static Handler sHandler; private static final int MSG_HIDE_KEYBOARD = 1; private static final int MSG_SET_ORIENTATION = 2; private static final int MSG_RUN_COMMAND = 3; - public static Looper getBackgroundLooper() { - if (sHandlerThread == null) { - sHandlerThread = - new HandlerThread("UiThreadHelper", Process.THREAD_PRIORITY_FOREGROUND); - sHandlerThread.start(); - } - return sHandlerThread.getLooper(); - } - private static Handler getHandler(Context context) { if (sHandler == null) { - sHandler = new Handler(getBackgroundLooper(), + sHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), new UiCallbacks(context.getApplicationContext())); } return sHandler; diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java new file mode 100644 index 000000000..04741a165 --- /dev/null +++ b/src/com/android/launcher3/util/VibratorWrapper.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2019 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 static android.os.VibrationEffect.createPredefined; +import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED; + +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + +import android.annotation.TargetApi; +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.os.Build; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.provider.Settings; + +/** + * Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary. + */ +@TargetApi(Build.VERSION_CODES.Q) +public class VibratorWrapper { + + public static final MainThreadInitializedObject<VibratorWrapper> INSTANCE = + new MainThreadInitializedObject<>(VibratorWrapper::new); + + private static final VibrationEffect EFFECT_CLICK = + createPredefined(VibrationEffect.EFFECT_CLICK); + + /** + * Haptic when entering overview. + */ + public static final VibrationEffect OVERVIEW_HAPTIC = EFFECT_CLICK; + + private final Vibrator mVibrator; + private final boolean mHasVibrator; + + private boolean mIsHapticFeedbackEnabled; + + public VibratorWrapper(Context context) { + mVibrator = context.getSystemService(Vibrator.class); + mHasVibrator = mVibrator.hasVibrator(); + if (mHasVibrator) { + final ContentResolver resolver = context.getContentResolver(); + mIsHapticFeedbackEnabled = isHapticFeedbackEnabled(resolver); + final ContentObserver observer = new ContentObserver(MAIN_EXECUTOR.getHandler()) { + @Override + public void onChange(boolean selfChange) { + mIsHapticFeedbackEnabled = isHapticFeedbackEnabled(resolver); + } + }; + resolver.registerContentObserver(Settings.System.getUriFor(HAPTIC_FEEDBACK_ENABLED), + false /* notifyForDescendents */, observer); + } else { + mIsHapticFeedbackEnabled = false; + } + } + + private boolean isHapticFeedbackEnabled(ContentResolver resolver) { + return Settings.System.getInt(resolver, HAPTIC_FEEDBACK_ENABLED, 0) == 1; + } + + /** Vibrates with the given effect if haptic feedback is available and enabled. */ + public void vibrate(VibrationEffect vibrationEffect) { + if (mHasVibrator && mIsHapticFeedbackEnabled) { + UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(vibrationEffect)); + } + } +} diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java index acce30860..5a131c83f 100644 --- a/src/com/android/launcher3/util/ViewOnDrawExecutor.java +++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java @@ -16,13 +16,14 @@ package com.android.launcher3.util; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.os.Process; import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.ViewTreeObserver.OnDrawListener; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherModel; import java.util.ArrayList; import java.util.concurrent.Executor; @@ -54,7 +55,9 @@ public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable, mLoadAnimationCompleted = true; } - attachObserver(); + if (mAttachedView.isAttachedToWindow()) { + attachObserver(); + } } private void attachObserver() { @@ -66,7 +69,7 @@ public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable, @Override public void execute(Runnable command) { mTasks.add(command); - LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_BACKGROUND); + MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } @Override @@ -108,7 +111,7 @@ public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable, if (mLauncher != null) { mLauncher.clearPendingExecutor(this); } - LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_DEFAULT); + MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); } protected boolean isCompleted() { diff --git a/src/com/android/launcher3/util/ViewPool.java b/src/com/android/launcher3/util/ViewPool.java index 8af048d9d..5b33f1849 100644 --- a/src/com/android/launcher3/util/ViewPool.java +++ b/src/com/android/launcher3/util/ViewPool.java @@ -21,12 +21,12 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import com.android.launcher3.util.ViewPool.Reusable; - import androidx.annotation.AnyThread; import androidx.annotation.Nullable; import androidx.annotation.UiThread; +import com.android.launcher3.util.ViewPool.Reusable; + /** * Utility class to maintain a pool of reusable views. * During initialization, views are inflated on the background thread. @@ -58,14 +58,18 @@ public class ViewPool<T extends View & Reusable> { Preconditions.assertUIThread(); Handler handler = new Handler(); + // LayoutInflater is not thread save as it maintains a global variable 'mConstructorArgs'. + // Create a different copy to use on the background thread. + LayoutInflater inflater = mInflater.cloneInContext(mInflater.getContext()); + // Inflate views on a non looper thread. This allows us to catch errors like calling // "new Handler()" in constructor easily. new Thread(() -> { for (int i = 0; i < initialSize; i++) { - T view = inflateNewView(); + T view = inflateNewView(inflater); handler.post(() -> addToPool(view)); } - }).start(); + }, "ViewPool-init").start(); } @UiThread @@ -94,12 +98,12 @@ public class ViewPool<T extends View & Reusable> { mCurrentSize--; return (T) mPool[mCurrentSize]; } - return inflateNewView(); + return inflateNewView(mInflater); } @AnyThread - private T inflateNewView() { - return (T) mInflater.inflate(mLayoutId, mParent, false); + private T inflateNewView(LayoutInflater inflater) { + return (T) inflater.inflate(mLayoutId, mParent, false); } /** diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java index 5c2468740..2ad80cf66 100644 --- a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java +++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java @@ -1,5 +1,7 @@ package com.android.launcher3.util; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + import android.app.WallpaperManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -198,7 +200,7 @@ public class WallpaperOffsetInterpolator extends BroadcastReceiver { private float mOffsetX; public OffsetHandler(Context context) { - super(UiThreadHelper.getBackgroundLooper()); + super(UI_HELPER_EXECUTOR.getLooper()); mInterpolator = Interpolators.DEACCEL_1_5; mWM = WallpaperManager.getInstance(context); } |