Snap for 5082210 from 816f38f91d to qt-release

Change-Id: Ica69623686a2d6886627ff85b30fd0912d1a1e3b
This commit is contained in:
android-build-team Robot
2018-10-21 03:14:01 +00:00
122 changed files with 3841 additions and 1568 deletions
+1
View File
@@ -23,6 +23,7 @@ java_library_static {
srcs: [
"tests/tapl/**/*.java",
"quickstep/src/com/android/quickstep/SwipeUpSetting.java",
"src/com/android/launcher3/util/SecureSettingsObserver.java",
"src/com/android/launcher3/TestProtocol.java",
],
platform_apis: true,
+32
View File
@@ -28,6 +28,34 @@ LOCAL_UNINSTALLABLE_MODULE := true
LOCAL_SDK_VERSION := current
include $(BUILD_PREBUILT)
include $(CLEAR_VARS)
LOCAL_MODULE := libPluginCore
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_CLASS := JAVA_LIBRARIES
LOCAL_SRC_FILES := libs/plugin_core.jar
LOCAL_UNINSTALLABLE_MODULE := true
LOCAL_SDK_VERSION := current
include $(BUILD_PREBUILT)
#
# Build rule for plugin lib (needed to write a plugin).
#
include $(CLEAR_VARS)
LOCAL_USE_AAPT2 := true
LOCAL_AAPT2_ONLY := true
LOCAL_MODULE_TAGS := optional
LOCAL_STATIC_JAVA_LIBRARIES := libPluginCore
LOCAL_SRC_FILES := \
$(call all-java-files-under, src_plugins)
LOCAL_SDK_VERSION := current
LOCAL_MIN_SDK_VERSION := 28
LOCAL_MODULE := LauncherPluginLib
include $(BUILD_STATIC_JAVA_LIBRARY)
#
# Build rule for Launcher3 dependencies lib.
#
@@ -40,6 +68,8 @@ LOCAL_STATIC_ANDROID_LIBRARIES := \
androidx.recyclerview_recyclerview \
androidx.dynamicanimation_dynamicanimation
LOCAL_STATIC_JAVA_LIBRARIES := LauncherPluginLib
LOCAL_SRC_FILES := \
$(call all-proto-files-under, protos) \
$(call all-proto-files-under, proto_overrides)
@@ -74,6 +104,8 @@ LOCAL_SRC_FILES := \
$(call all-java-files-under, src_flags)
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
# Proguard is disable for testing. Derivarive prjects to keep proguard enabled
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_SDK_VERSION := current
LOCAL_MIN_SDK_VERSION := 21
@@ -16,12 +16,15 @@
package com.android.launcher3.config;
import android.content.Context;
/**
* Defines a set of flags used to control various launcher behaviors
*/
public final class FeatureFlags extends BaseFlags {
private FeatureFlags() {}
private FeatureFlags() {
// Prevent instantiation
}
// Features to control Launcher3Go behavior
public static final boolean GO_DISABLE_WIDGETS = true;
Binary file not shown.
Binary file not shown.
@@ -15,10 +15,14 @@
*/
package com.android.launcher3.uioverrides;
import android.os.RemoteException;
import com.android.launcher3.Launcher;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.quickstep.QuickScrubController;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.ISystemUiProxy;
/**
* State indicating that the Launcher is behind an app
@@ -43,4 +47,27 @@ public class BackgroundAppState extends OverviewState {
float progressDelta = (transitionLength / scrollRange);
return super.getVerticalProgress(launcher) + progressDelta;
}
@Override
public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
// Initialize the recents view scale to what it would be when starting swipe up/quickscrub
RecentsView recentsView = launcher.getOverviewPanel();
recentsView.getTaskSize(sTempRect);
int appWidth = launcher.getDragLayer().getWidth();
if (recentsView.shouldUseMultiWindowTaskSizeStrategy()) {
ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(launcher).getSystemUiProxy();
if (sysUiProxy != null) {
try {
// Try to use the actual non-minimized app width (launcher will be resized to
// the non-minimized bounds, which differs from the app width in landscape
// multi-window mode
appWidth = sysUiProxy.getNonMinimizedSplitScreenSecondaryBounds().width();
} catch (RemoteException e) {
// Ignore, fall back to just using the drag layer width
}
}
}
float scale = (float) appWidth / sTempRect.width();
return new float[] { scale, 0f };
}
}
@@ -117,6 +117,7 @@ public class UiFactory {
launcher.getStateManager().addStateListener(new LauncherStateManager.StateListener() {
@Override
public void onStateSetImmediately(LauncherState state) {
onStateTransitionComplete(state);
}
@Override
@@ -142,6 +143,7 @@ public class UiFactory {
launcher.getStateManager().addStateListener(new LauncherStateManager.StateListener() {
@Override
public void onStateSetImmediately(LauncherState state) {
onStateTransitionComplete(state);
}
@Override
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2018 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.uioverrides.plugins;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
import com.android.launcher3.Utilities;
import com.android.systemui.shared.plugins.PluginEnabler;
public class PluginEnablerImpl implements PluginEnabler {
final private SharedPreferences mSharedPrefs;
public PluginEnablerImpl(Context context) {
mSharedPrefs = Utilities.getDevicePrefs(context);
}
@Override
public void setEnabled(ComponentName component, boolean enabled) {
mSharedPrefs.edit().putBoolean(toPrefString(component), enabled).apply();
}
@Override
public boolean isEnabled(ComponentName component) {
return mSharedPrefs.getBoolean(toPrefString(component), true);
}
private String toPrefString(ComponentName component) {
return "PLUGIN_ENABLED_" + component.flattenToString();
}
}
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2018 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.uioverrides.plugins;
import android.content.Context;
import android.os.Looper;
import com.android.launcher3.LauncherModel;
import com.android.systemui.shared.plugins.PluginEnabler;
import com.android.systemui.shared.plugins.PluginInitializer;
public class PluginInitializerImpl implements PluginInitializer {
@Override
public Looper getBgLooper() {
return LauncherModel.getWorkerLooper();
}
@Override
public void onPluginManagerInit() {
}
@Override
public String[] getWhitelistedPlugins(Context context) {
return new String[0];
}
@Override
public PluginEnabler getPluginEnabler(Context context) {
return new PluginEnablerImpl(context);
}
@Override
public void handleWtfs() {
}
}
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2018 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.uioverrides.plugins;
import android.content.Context;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.shared.plugins.PluginEnabler;
import com.android.systemui.shared.plugins.PluginInitializer;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginManagerImpl;
public class PluginManagerWrapper {
public static final MainThreadInitializedObject<PluginManagerWrapper> INSTANCE =
new MainThreadInitializedObject<>(PluginManagerWrapper::new);
private final PluginManager mPluginManager;
private final PluginEnabler mPluginEnabler;
private PluginManagerWrapper(Context c) {
PluginInitializer pluginInitializer = new PluginInitializerImpl();
mPluginManager = new PluginManagerImpl(c, pluginInitializer);
mPluginEnabler = pluginInitializer.getPluginEnabler(c);
}
PluginEnabler getPluginEnabler() {
return mPluginEnabler;
}
public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass) {
addPluginListener(listener, pluginClass, false);
}
public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass,
boolean allowMultiple) {
mPluginManager.addPluginListener(listener, pluginClass, allowMultiple);
}
public void removePluginListener(PluginListener<? extends Plugin> listener) {
mPluginManager.removePluginListener(listener);
}
}
@@ -0,0 +1,217 @@
/*
* Copyright (C) 2018 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.uioverrides.plugins;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import android.preference.SwitchPreference;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.view.View;
import com.android.launcher3.R;
import com.android.systemui.shared.plugins.PluginEnabler;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginPrefs;
import java.util.List;
import java.util.Set;
/**
* This class is copied from System UI Tuner, except using our PluginEnablerImpl. The reason we
* can't share a common base class in the shared lib is because the androidx preference dependency
* interferes with our recyclerview and fragment dependencies.
*/
public class PluginPreferencesFragment extends PreferenceFragment {
public static final String ACTION_PLUGIN_SETTINGS
= "com.android.systemui.action.PLUGIN_SETTINGS";
private static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN";
private PluginPrefs mPluginPrefs;
private PluginEnabler mPluginEnabler;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
getContext().registerReceiver(mReceiver, filter);
filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
getContext().registerReceiver(mReceiver, filter);
mPluginEnabler = PluginManagerWrapper.INSTANCE.get(getContext()).getPluginEnabler();
loadPrefs();
}
@Override
public void onDestroy() {
super.onDestroy();
getContext().unregisterReceiver(mReceiver);
}
private void loadPrefs() {
PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(getContext());
screen.setOrderingAsAdded(false);
Context prefContext = getContext();
mPluginPrefs = new PluginPrefs(getContext());
PackageManager pm = getContext().getPackageManager();
Set<String> pluginActions = mPluginPrefs.getPluginList();
ArrayMap<String, ArraySet<String>> plugins = new ArrayMap<>();
for (String action : pluginActions) {
String name = toName(action);
List<ResolveInfo> result = pm.queryIntentServices(
new Intent(action), PackageManager.MATCH_DISABLED_COMPONENTS);
for (ResolveInfo info : result) {
String packageName = info.serviceInfo.packageName;
if (!plugins.containsKey(packageName)) {
plugins.put(packageName, new ArraySet<>());
}
plugins.get(packageName).add(name);
}
}
List<PackageInfo> apps = pm.getPackagesHoldingPermissions(new String[]{PLUGIN_PERMISSION},
PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.GET_SERVICES);
apps.forEach(app -> {
if (!plugins.containsKey(app.packageName)) return;
SwitchPreference pref = new PluginPreference(prefContext, app, mPluginEnabler);
pref.setSummary("Plugins: " + toString(plugins.get(app.packageName)));
screen.addPreference(pref);
});
setPreferenceScreen(screen);
}
private String toString(ArraySet<String> plugins) {
StringBuilder b = new StringBuilder();
for (String string : plugins) {
if (b.length() != 0) {
b.append(", ");
}
b.append(string);
}
return b.toString();
}
private String toName(String action) {
String str = action.replace("com.android.systemui.action.PLUGIN_", "");
StringBuilder b = new StringBuilder();
for (String s : str.split("_")) {
if (b.length() != 0) {
b.append(' ');
}
b.append(s.substring(0, 1));
b.append(s.substring(1).toLowerCase());
}
return b.toString();
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
loadPrefs();
}
};
private static class PluginPreference extends SwitchPreference {
private final boolean mHasSettings;
private final PackageInfo mInfo;
private final PluginEnabler mPluginEnabler;
public PluginPreference(Context prefContext, PackageInfo info, PluginEnabler pluginEnabler) {
super(prefContext);
PackageManager pm = prefContext.getPackageManager();
mHasSettings = pm.resolveActivity(new Intent(ACTION_PLUGIN_SETTINGS)
.setPackage(info.packageName), 0) != null;
mInfo = info;
mPluginEnabler = pluginEnabler;
setTitle(info.applicationInfo.loadLabel(pm));
setChecked(isPluginEnabled());
setWidgetLayoutResource(R.layout.switch_preference_with_settings);
}
private boolean isPluginEnabled() {
for (int i = 0; i < mInfo.services.length; i++) {
ComponentName componentName = new ComponentName(mInfo.packageName,
mInfo.services[i].name);
if (!mPluginEnabler.isEnabled(componentName)) {
return false;
}
}
return true;
}
@Override
protected boolean persistBoolean(boolean isEnabled) {
boolean shouldSendBroadcast = false;
for (int i = 0; i < mInfo.services.length; i++) {
ComponentName componentName = new ComponentName(mInfo.packageName,
mInfo.services[i].name);
if (mPluginEnabler.isEnabled(componentName) != isEnabled) {
mPluginEnabler.setEnabled(componentName, isEnabled);
shouldSendBroadcast = true;
}
}
if (shouldSendBroadcast) {
final String pkg = mInfo.packageName;
final Intent intent = new Intent(PluginManager.PLUGIN_CHANGED,
pkg != null ? Uri.fromParts("package", pkg, null) : null);
getContext().sendBroadcast(intent);
}
setChecked(isEnabled);
return true;
}
@Override
protected void onBindView(View view) {
super.onBindView(view);
view.findViewById(R.id.settings).setVisibility(mHasSettings ? View.VISIBLE
: View.GONE);
view.findViewById(R.id.divider).setVisibility(mHasSettings ? View.VISIBLE
: View.GONE);
view.findViewById(R.id.settings).setOnClickListener(v -> {
ResolveInfo result = v.getContext().getPackageManager().resolveActivity(
new Intent(ACTION_PLUGIN_SETTINGS).setPackage(
mInfo.packageName), 0);
if (result != null) {
v.getContext().startActivity(new Intent().setComponent(
new ComponentName(result.activityInfo.packageName,
result.activityInfo.name)));
}
});
view.setOnLongClickListener(v -> {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package", mInfo.packageName, null));
getContext().startActivity(intent);
return true;
});
}
}
}
@@ -286,7 +286,7 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
}
if (interactionType == INTERACTION_NORMAL) {
playScaleDownAnim(anim, activity);
playScaleDownAnim(anim, activity, endState);
}
anim.setDuration(transitionLength * 2);
@@ -304,14 +304,24 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
/**
* Scale down recents from the center task being full screen to being in overview.
*/
private void playScaleDownAnim(AnimatorSet anim, Launcher launcher) {
private void playScaleDownAnim(AnimatorSet anim, Launcher launcher,
LauncherState endState) {
RecentsView recentsView = launcher.getOverviewPanel();
TaskView v = recentsView.getTaskViewAt(recentsView.getCurrentPage());
if (v == null) {
return;
}
// Setup the clip animation helper source/target rects in the final transformed state
// of the recents view (a scale may be applied prior to this animation starting to
// line up the side pages during swipe up)
float prevRvScale = recentsView.getScaleX();
float targetRvScale = endState.getOverviewScaleAndTranslationYFactor(launcher)[0];
SCALE_PROPERTY.set(recentsView, targetRvScale);
ClipAnimationHelper clipHelper = new ClipAnimationHelper();
clipHelper.fromTaskThumbnailView(v.getThumbnail(), (RecentsView) v.getParent(), null);
SCALE_PROPERTY.set(recentsView, prevRvScale);
if (!clipHelper.getSourceRect().isEmpty() && !clipHelper.getTargetRect().isEmpty()) {
float fromScale = clipHelper.getSourceRect().width()
/ clipHelper.getTargetRect().width();
@@ -43,7 +43,6 @@ public class NormalizedIconLoader extends IconLoader {
private final SparseArray<BitmapInfo> mDefaultIcons = new SparseArray<>();
private final DrawableFactory mDrawableFactory;
private final boolean mDisableColorExtraction;
private LauncherIcons mLauncherIcons;
public NormalizedIconLoader(Context context, TaskKeyLruCache<Drawable> iconCache,
LruCache<ComponentName, ActivityInfo> activityInfoCache,
@@ -73,19 +72,18 @@ public class NormalizedIconLoader extends IconLoader {
false));
}
private synchronized BitmapInfo getBitmapInfo(Drawable drawable, int userId,
private BitmapInfo getBitmapInfo(Drawable drawable, int userId,
int primaryColor, boolean isInstantApp) {
if (mLauncherIcons == null) {
mLauncherIcons = LauncherIcons.obtain(mContext);
try (LauncherIcons la = LauncherIcons.obtain(mContext)) {
if (mDisableColorExtraction) {
mLauncherIcons.disableColorExtraction();
la.disableColorExtraction();
}
}
la.setWrapperBackgroundColor(primaryColor);
mLauncherIcons.setWrapperBackgroundColor(primaryColor);
// User version code O, so that the icon is always wrapped in an adaptive icon container.
return mLauncherIcons.createBadgedIconBitmap(drawable, UserHandle.of(userId),
Build.VERSION_CODES.O, isInstantApp);
// User version code O, so that the icon is always wrapped in an adaptive icon container
return la.createBadgedIconBitmap(drawable, UserHandle.of(userId),
Build.VERSION_CODES.O, isInstantApp);
}
}
@Override
@@ -52,6 +52,7 @@ import com.android.quickstep.util.RemoteAnimationTargetSet;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.AssistDataReceiver;
import com.android.systemui.shared.system.BackgroundExecutor;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.NavigationBarCompat;
import com.android.systemui.shared.system.NavigationBarCompat.HitTarget;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
@@ -80,6 +81,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
private final OverviewCallbacks mOverviewCallbacks;
private final TaskOverlayFactory mTaskOverlayFactory;
private final TouchInteractionLog mTouchInteractionLog;
private final InputConsumerController mInputConsumer;
private final boolean mIsDeferredDownTarget;
private final PointF mDownPos = new PointF();
@@ -101,8 +103,8 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
RecentsModel recentsModel, Intent homeIntent, ActivityControlHelper activityControl,
MainThreadExecutor mainThreadExecutor, Choreographer backgroundThreadChoreographer,
@HitTarget int downHitTarget, OverviewCallbacks overviewCallbacks,
TaskOverlayFactory taskOverlayFactory, VelocityTracker velocityTracker,
TouchInteractionLog touchInteractionLog) {
TaskOverlayFactory taskOverlayFactory, InputConsumerController inputConsumer,
VelocityTracker velocityTracker, TouchInteractionLog touchInteractionLog) {
super(base);
mRunningTask = runningTaskInfo;
@@ -117,6 +119,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
mTaskOverlayFactory = taskOverlayFactory;
mTouchInteractionLog = touchInteractionLog;
mTouchInteractionLog.setTouchConsumer(this);
mInputConsumer = inputConsumer;
}
@Override
@@ -226,7 +229,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
RecentsAnimationState animationState = new RecentsAnimationState();
final WindowTransformSwipeHandler handler = new WindowTransformSwipeHandler(
animationState.id, mRunningTask, this, touchTimeMs, mActivityControlHelper,
mTouchInteractionLog);
mInputConsumer, mTouchInteractionLog);
// Preload the plan
mRecentsModel.loadTasks(mRunningTask.id, null);
@@ -15,23 +15,21 @@
*/
package com.android.quickstep;
import static com.android.quickstep.SwipeUpSetting.newSwipeUpSettingsObserver;
import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_QUICK_SCRUB;
import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_SWIPE_UP;
import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
import static com.android.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING_NAME;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.provider.Settings;
import android.util.Log;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SecureSettingsObserver;
import com.android.launcher3.util.UiThreadHelper;
import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -60,7 +58,7 @@ public class OverviewInteractionState {
private static final int MSG_SET_BACK_BUTTON_ALPHA = 201;
private static final int MSG_SET_SWIPE_UP_ENABLED = 202;
private final SwipeUpGestureEnabledSettingObserver mSwipeUpSettingObserver;
private final SecureSettingsObserver mSwipeUpSettingObserver;
private final Context mContext;
private final Handler mUiHandler;
@@ -85,9 +83,11 @@ public class OverviewInteractionState {
mBgHandler = new Handler(UiThreadHelper.getBackgroundLooper(), this::handleBgMessage);
if (SwipeUpSetting.isSwipeUpSettingAvailable()) {
mSwipeUpSettingObserver = new SwipeUpGestureEnabledSettingObserver(mUiHandler,
context.getContentResolver());
mSwipeUpSettingObserver =
newSwipeUpSettingsObserver(context, this::notifySwipeUpSettingChanged);
mSwipeUpSettingObserver.register();
mSwipeUpEnabled = mSwipeUpSettingObserver.getValue();
resetHomeBounceSeenOnQuickstepEnabledFirstTime();
} else {
mSwipeUpSettingObserver = null;
mSwipeUpEnabled = SwipeUpSetting.isSwipeUpEnabledDefaultValue();
@@ -192,34 +192,6 @@ public class OverviewInteractionState {
sendToTarget();
}
private class SwipeUpGestureEnabledSettingObserver extends ContentObserver {
private ContentResolver mResolver;
private final int defaultValue;
SwipeUpGestureEnabledSettingObserver(Handler handler, ContentResolver resolver) {
super(handler);
mResolver = resolver;
defaultValue = SwipeUpSetting.isSwipeUpEnabledDefaultValue() ? 1 : 0;
}
public void register() {
mResolver.registerContentObserver(Settings.Secure.getUriFor(SWIPE_UP_SETTING_NAME),
false, this);
mSwipeUpEnabled = getValue();
resetHomeBounceSeenOnQuickstepEnabledFirstTime();
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
notifySwipeUpSettingChanged(getValue());
}
private boolean getValue() {
return Settings.Secure.getInt(mResolver, SWIPE_UP_SETTING_NAME, defaultValue) == 1;
}
}
private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
if (mSwipeUpEnabled && !Utilities.getPrefs(mContext).getBoolean(
HAS_ENABLED_QUICKSTEP_ONCE, true)) {
@@ -53,24 +53,18 @@ public class RecentsAnimationWrapper {
new LooperExecutor(UiThreadHelper.getBackgroundLooper());
private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
private InputConsumerController mInputConsumer =
InputConsumerController.getRecentsAnimationInputConsumer();
private final InputConsumerController mInputConsumer;
private final Supplier<TouchConsumer> mTouchProxySupplier;
private boolean mInputConsumerUnregistered;
private boolean mTouchProxyEnabled;
private TouchConsumer mTouchConsumer;
private boolean mTouchInProgress;
private boolean mInputConsumerUnregisterPending;
private boolean mFinishPending;
public RecentsAnimationWrapper(Supplier<TouchConsumer> touchProxySupplier) {
// Register the input consumer on the UI thread, to ensure that it runs after any pending
// unregister calls
public RecentsAnimationWrapper(InputConsumerController inputConsumer,
Supplier<TouchConsumer> touchProxySupplier) {
mInputConsumer = inputConsumer;
mTouchProxySupplier = touchProxySupplier;
mMainThreadExecutor.execute(mInputConsumer::registerInputConsumer);
}
public synchronized void setController(
@@ -109,6 +103,7 @@ public class RecentsAnimationWrapper {
public void finish(boolean toHome, Runnable onFinishComplete) {
if (!toHome) {
mExecutorService.submit(() -> finishBg(false, onFinishComplete));
return;
}
mMainThreadExecutor.execute(() -> {
@@ -152,28 +147,12 @@ public class RecentsAnimationWrapper {
}
}
public void unregisterInputConsumer() {
mMainThreadExecutor.execute(this::unregisterInputConsumerUi);
}
private void unregisterInputConsumerUi() {
if (mTouchProxyEnabled && mTouchInProgress) {
mInputConsumerUnregisterPending = true;
} else {
mInputConsumerUnregistered = true;
mInputConsumer.unregisterInputConsumer();
}
}
public void enableTouchProxy() {
mMainThreadExecutor.execute(this::enableTouchProxyUi);
}
private void enableTouchProxyUi() {
if (!mInputConsumerUnregistered) {
mTouchProxyEnabled = true;
mInputConsumer.setTouchListener(this::onInputConsumerTouch);
}
mInputConsumer.setTouchListener(this::onInputConsumerTouch);
}
private boolean onInputConsumerTouch(MotionEvent ev) {
@@ -184,10 +163,6 @@ public class RecentsAnimationWrapper {
} else if (action == ACTION_CANCEL || action == ACTION_UP) {
// Finish any pending actions
mTouchInProgress = false;
if (mInputConsumerUnregisterPending) {
mInputConsumerUnregisterPending = false;
mInputConsumer.unregisterInputConsumer();
}
if (mFinishPending) {
mFinishPending = false;
mExecutorService.submit(() -> finishBg(true, null));
@@ -16,9 +16,15 @@
package com.android.quickstep;
import static com.android.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING_NAME;
import android.content.Context;
import android.content.res.Resources;
import android.util.Log;
import com.android.launcher3.util.SecureSettingsObserver;
import com.android.launcher3.util.SecureSettingsObserver.OnChangeListener;
public final class SwipeUpSetting {
private static final String TAG = "SwipeUpSetting";
@@ -47,4 +53,10 @@ public final class SwipeUpSetting {
public static boolean isSwipeUpEnabledDefaultValue() {
return getSystemBooleanRes(SWIPE_UP_ENABLED_DEFAULT_RES_NAME);
}
public static SecureSettingsObserver newSwipeUpSettingsObserver(Context context,
OnChangeListener listener) {
return new SecureSettingsObserver(context.getContentResolver(), listener,
SWIPE_UP_SETTING_NAME, isSwipeUpEnabledDefaultValue() ? 1 : 0);
}
}
@@ -64,7 +64,7 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut
protected T mSystemShortcut;
protected TaskSystemShortcut(T systemShortcut) {
super(systemShortcut.iconResId, systemShortcut.labelResId);
super(systemShortcut);
mSystemShortcut = systemShortcut;
}
@@ -19,7 +19,7 @@ import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_POINTER_INDEX_SHIFT;
import static android.view.MotionEvent.ACTION_UP;
import static com.android.systemui.shared.system.ActivityManagerWrapper
@@ -51,7 +51,9 @@ import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ChoreographerCompat;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.NavigationBarCompat.HitTarget;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -184,6 +186,7 @@ public class TouchInteractionService extends Service {
private OverviewCallbacks mOverviewCallbacks;
private TaskOverlayFactory mTaskOverlayFactory;
private TouchInteractionLog mTouchInteractionLog;
private InputConsumerController mInputConsumer;
private Choreographer mMainThreadChoreographer;
private Choreographer mBackgroundThreadChoreographer;
@@ -202,6 +205,8 @@ public class TouchInteractionService extends Service {
mOverviewCallbacks = OverviewCallbacks.get(this);
mTaskOverlayFactory = TaskOverlayFactory.get(this);
mTouchInteractionLog = new TouchInteractionLog();
mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
mInputConsumer.registerInputConsumer();
sConnected = true;
@@ -212,6 +217,7 @@ public class TouchInteractionService extends Service {
@Override
public void onDestroy() {
mInputConsumer.unregisterInputConsumer();
mOverviewCommandHelper.onDestroy();
sConnected = false;
super.onDestroy();
@@ -255,7 +261,7 @@ public class TouchInteractionService extends Service {
mOverviewCommandHelper.overviewIntent,
mOverviewCommandHelper.getActivityControlHelper(), mMainThreadExecutor,
mBackgroundThreadChoreographer, downHitTarget, mOverviewCallbacks,
mTaskOverlayFactory, tracker, mTouchInteractionLog);
mTaskOverlayFactory, mInputConsumer, tracker, mTouchInteractionLog);
}
}
@@ -299,7 +305,7 @@ public class TouchInteractionService extends Service {
mActivityHelper = activityHelper;
mActivity = activity;
mTarget = activity.getDragLayer();
mTouchSlop = ViewConfiguration.get(mTarget.getContext()).getScaledTouchSlop();
mTouchSlop = ViewConfiguration.get(mActivity).getScaledTouchSlop();
mStartingInActivityBounds = startingInActivityBounds;
mQuickScrubController = mActivity.<RecentsView>getOverviewPanel()
@@ -324,12 +330,6 @@ public class TouchInteractionService extends Service {
mDownPos.set(ev.getX(), ev.getY());
} else if (!mTrackingStarted) {
switch (action) {
case ACTION_POINTER_UP:
case ACTION_POINTER_DOWN:
if (!mTrackingStarted) {
mInvalidated = true;
}
break;
case ACTION_CANCEL:
case ACTION_UP:
startTouchTracking(ev, true /* updateLocationOffset */);
@@ -359,15 +359,26 @@ public class TouchInteractionService extends Service {
}
// Send down touch event
MotionEvent down = MotionEvent.obtain(ev);
MotionEvent down = MotionEvent.obtainNoHistory(ev);
down.setAction(ACTION_DOWN);
sendEvent(down);
down.recycle();
mTrackingStarted = true;
// Send pointer down for remaining pointers.
int pointerCount = ev.getPointerCount();
for (int i = 1; i < pointerCount; i++) {
down.setAction(ACTION_POINTER_DOWN | (i << ACTION_POINTER_INDEX_SHIFT));
sendEvent(down);
}
down.recycle();
}
private void sendEvent(MotionEvent ev) {
if (!mTarget.verifyTouchDispatch(this, ev)) {
mInvalidated = true;
return;
}
int flags = ev.getEdgeFlags();
ev.setEdgeFlags(flags | TouchInteractionService.EDGE_NAV_BAR);
ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]);
@@ -78,6 +78,7 @@ import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.LatencyTrackerCompat;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -226,8 +227,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
private @InteractionType int mInteractionType = INTERACTION_NORMAL;
private final RecentsAnimationWrapper mRecentsAnimationWrapper =
new RecentsAnimationWrapper(this::createNewTouchProxyHandler);
private final RecentsAnimationWrapper mRecentsAnimationWrapper;
private final long mTouchTimeMs;
private long mLauncherFrameDrawnTime;
@@ -241,7 +241,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
WindowTransformSwipeHandler(int id, RunningTaskInfo runningTaskInfo, Context context,
long touchTimeMs, ActivityControlHelper<T> controller,
TouchInteractionLog touchInteractionLog) {
InputConsumerController inputConsumer, TouchInteractionLog touchInteractionLog) {
this.id = id;
mContext = context;
mRunningTaskInfo = runningTaskInfo;
@@ -251,6 +251,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
mActivityInitListener = mActivityControlHelper
.createActivityInitListener(this::onActivityInit);
mTouchInteractionLog = touchInteractionLog;
mRecentsAnimationWrapper = new RecentsAnimationWrapper(inputConsumer,
this::createNewTouchProxyHandler);
initStateCallbacks();
}
@@ -860,7 +862,6 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
}
mActivityInitListener.unregister();
mRecentsAnimationWrapper.unregisterInputConsumer();
mTaskSnapshot = null;
}
@@ -90,8 +90,6 @@ public class ClipAnimationHelper {
// Whether to boost the opening animation target layers, or the closing
private int mBoostModeTargetLayers = -1;
// Wether or not applyTransform has been called yet since prepareAnimation()
private boolean mIsFirstFrame = true;
private BiFunction<RemoteAnimationTargetCompat, Float, Float> mTaskAlphaCallback =
(t, a1) -> a1;
@@ -229,7 +229,9 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
}
};
private int mLoadPlanId = -1;
// Used to keep track of the last requested load plan id, so that we do not request to load the
// tasks again if we have already requested it and the task list has not changed
private int mRequestedLoadPlanId = -1;
// Only valid until the launcher state changes to NORMAL
private int mRunningTaskId = -1;
@@ -400,9 +402,6 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
final int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
if (mShowEmptyMessage) {
onAllTasksRemoved();
}
if (mTouchDownToStartHome) {
startHome();
}
@@ -413,7 +412,8 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
break;
case MotionEvent.ACTION_MOVE:
// Passing the touch slop will not allow dismiss to home
if (mTouchDownToStartHome && Math.hypot(mDownX - x, mDownY - y) > mTouchSlop) {
if (mTouchDownToStartHome &&
(isHandlingTouch() || Math.hypot(mDownX - x, mDownY - y) > mTouchSlop)) {
mTouchDownToStartHome = false;
}
break;
@@ -421,12 +421,17 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
// Touch down anywhere but the deadzone around the visible clear all button and
// between the task views will start home on touch up
if (!isHandlingTouch()) {
updateDeadZoneRects();
final boolean clearAllButtonDeadZoneConsumed = mClearAllButton.getAlpha() == 1
&& mClearAllButtonDeadZoneRect.contains(x, y);
if (!clearAllButtonDeadZoneConsumed
&& !mTaskViewDeadZoneRect.contains(x + getScrollX(), y)) {
if (mShowEmptyMessage) {
mTouchDownToStartHome = true;
} else {
updateDeadZoneRects();
final boolean clearAllButtonDeadZoneConsumed =
mClearAllButton.getAlpha() == 1
&& mClearAllButtonDeadZoneRect.contains(x, y);
if (!clearAllButtonDeadZoneConsumed
&& !mTaskViewDeadZoneRect.contains(x + getScrollX(), y)) {
mTouchDownToStartHome = true;
}
}
}
mDownX = x;
@@ -444,6 +449,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
mPendingAnimation.addEndListener((onEndListener) -> applyLoadPlan(loadPlan));
return;
}
TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null;
if (stack == null) {
removeAllViews();
@@ -612,8 +618,9 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
* and unloads the associated task data for tasks that are no longer visible.
*/
public void loadVisibleTaskData() {
if (!mOverviewStateEnabled) {
// Skip loading visible task data if we've already left the overview state
if (!mOverviewStateEnabled || mRequestedLoadPlanId == -1) {
// Skip loading visible task data if we've already left the overview state, or if the
// task list hasn't been loaded yet (the task views will not reflect the task list)
return;
}
@@ -666,16 +673,13 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
mHasVisibleTaskData.clear();
}
protected void onAllTasksRemoved() {
startHome();
}
protected abstract void startHome();
public void reset() {
mRunningTaskId = -1;
mRunningTaskTileHidden = false;
mIgnoreResetTaskId = -1;
mRequestedLoadPlanId = -1;
unloadVisibleTaskData();
setCurrentPage(0);
@@ -687,8 +691,8 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
* Reloads the view if anything in recents changed.
*/
public void reloadIfNeeded() {
if (!mModel.isLoadPlanValid(mLoadPlanId)) {
mLoadPlanId = mModel.loadTasks(mRunningTaskId, this::applyLoadPlan);
if (!mModel.isLoadPlanValid(mRequestedLoadPlanId)) {
mRequestedLoadPlanId = mModel.loadTasks(mRunningTaskId, this::applyLoadPlan);
}
}
@@ -749,8 +753,8 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
setCurrentPage(0);
// Load the tasks (if the loading is already
mLoadPlanId = mModel.loadTasks(runningTaskId, this::applyLoadPlan);
// Load the tasks
reloadIfNeeded();
}
public void showNextTask() {
@@ -973,7 +977,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
if (getTaskViewCount() == 0) {
removeView(mClearAllButton);
onAllTasksRemoved();
startHome();
} else {
snapToPageImmediately(pageToSnapTo);
}
@@ -1002,7 +1006,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
// Remove all the task views now
ActivityManagerWrapper.getInstance().removeAllRecentTasks();
removeAllViews();
onAllTasksRemoved();
startHome();
}
mPendingAnimation = null;
});
@@ -208,8 +208,8 @@ public class TaskMenuView extends AbstractFloatingView {
private void addMenuOption(TaskSystemShortcut menuOption, OnClickListener onClickListener) {
ViewGroup menuOptionView = (ViewGroup) mActivity.getLayoutInflater().inflate(
R.layout.task_view_menu_option, this, false);
menuOptionView.findViewById(R.id.icon).setBackgroundResource(menuOption.iconResId);
((TextView) menuOptionView.findViewById(R.id.text)).setText(menuOption.labelResId);
menuOption.setIconAndLabelFor(
menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
menuOptionView.setOnClickListener(onClickListener);
mOptionLayout.addView(menuOptionView);
}
@@ -387,8 +387,7 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
for (TaskSystemShortcut menuOption : TaskMenuView.MENU_OPTIONS) {
OnClickListener onClickListener = menuOption.getOnClickListener(activity, this);
if (onClickListener != null) {
info.addAction(new AccessibilityNodeInfo.AccessibilityAction(menuOption.labelResId,
context.getText(menuOption.labelResId)));
info.addAction(menuOption.createAccessibilityAction(context));
}
}
@@ -409,7 +408,7 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
}
for (TaskSystemShortcut menuOption : TaskMenuView.MENU_OPTIONS) {
if (action == menuOption.labelResId) {
if (menuOption.hasHandlerForAction(action)) {
OnClickListener onClickListener = menuOption.getOnClickListener(
fromContext(getContext()), this);
if (onClickListener != null) {
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2018 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.quickstep;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import org.junit.Rule;
import org.junit.rules.TestRule;
/**
* Base class for all instrumentation tests that deal with Quickstep.
*/
public abstract class AbstractQuickStepTest extends AbstractLauncherUiTest {
@Rule
public TestRule mQuickstepOnOffExecutor =
new QuickStepOnOffRule(mMainThreadExecutor, mLauncher);
}
@@ -0,0 +1,126 @@
/*
* Copyright (C) 2018 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.quickstep;
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS;
import static com.android.launcher3.tapl.TestHelpers.getHomeIntentInPackage;
import static com.android.launcher3.tapl.TestHelpers.getLauncherInMyProcess;
import static com.android.launcher3.util.rule.ShellCommandRule.disableHeadsUpNotification;
import static com.android.launcher3.util.rule.ShellCommandRule.getLauncherCommand;
import static com.android.quickstep.QuickStepOnOffRule.Mode.OFF;
import static org.junit.Assert.assertTrue;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.testcomponent.TestCommandReceiver;
import com.android.quickstep.QuickStepOnOffRule.QuickstepOnOff;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.junit.runners.model.Statement;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.Until;
@LargeTest
@RunWith(AndroidJUnit4.class)
/**
* TODO: Fix fallback when quickstep is enabled
*/
public class FallbackRecentsTest {
private final UiDevice mDevice;
private final LauncherInstrumentation mLauncher;
private final ActivityInfo mOtherLauncherActivity;
@Rule public final TestRule mDisableHeadsUpNotification = disableHeadsUpNotification();
@Rule public final TestRule mQuickstepOnOffExecutor;
@Rule public final TestRule mSetLauncherCommand;
public FallbackRecentsTest() {
Instrumentation instrumentation = getInstrumentation();
Context context = instrumentation.getContext();
mDevice = UiDevice.getInstance(instrumentation);
mLauncher = new LauncherInstrumentation(instrumentation);
mQuickstepOnOffExecutor = new QuickStepOnOffRule(new MainThreadExecutor(), mLauncher);
mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
getHomeIntentInPackage(context),
MATCH_DISABLED_COMPONENTS).get(0).activityInfo;
mSetLauncherCommand = (base, desc) -> new Statement() {
@Override
public void evaluate() throws Throwable {
TestCommandReceiver.callCommand(TestCommandReceiver.ENABLE_TEST_LAUNCHER);
UiDevice.getInstance(getInstrumentation()).executeShellCommand(
getLauncherCommand(mOtherLauncherActivity));
try {
base.evaluate();
} finally {
TestCommandReceiver.callCommand(TestCommandReceiver.DISABLE_TEST_LAUNCHER);
UiDevice.getInstance(getInstrumentation()).executeShellCommand(
getLauncherCommand(getLauncherInMyProcess()));
}
}
};
}
@QuickstepOnOff(mode = OFF)
@Test
public void goToOverviewFromHome() {
mDevice.pressHome();
assertTrue("Fallback Launcher not visible", mDevice.wait(Until.hasObject(By.pkg(
mOtherLauncherActivity.packageName)), WAIT_TIME_MS));
mLauncher.getBackground().switchToOverview();
}
@QuickstepOnOff(mode = OFF)
@Test
public void goToOverviewFromApp() {
startAppFast("com.android.settings");
mLauncher.getBackground().switchToOverview();
}
private void startAppFast(String packageName) {
final Instrumentation instrumentation = getInstrumentation();
final Intent intent = instrumentation.getContext().getPackageManager().
getLaunchIntentForPackage(packageName);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
instrumentation.getTargetContext().startActivity(intent);
assertTrue(packageName + " didn't start",
mDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), WAIT_TIME_MS));
}
}
@@ -0,0 +1,104 @@
/*
* Copyright (C) 2018 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.quickstep;
import static com.android.quickstep.QuickStepOnOffRule.Mode.BOTH;
import static com.android.quickstep.QuickStepOnOffRule.Mode.OFF;
import static com.android.quickstep.QuickStepOnOffRule.Mode.ON;
import androidx.test.InstrumentationRegistry;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.tapl.TestHelpers;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.Executor;
/**
* Test rule that allows executing a test with Quickstep on and then Quickstep off.
* The test should be annotated with @QuickstepOnOff.
*/
public class QuickStepOnOffRule implements TestRule {
public enum Mode {
ON, OFF, BOTH
}
// Annotation for tests that need to be run with quickstep enabled and disabled.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface QuickstepOnOff {
Mode mode() default BOTH;
}
private final Executor mMainThreadExecutor;
private final LauncherInstrumentation mLauncher;
public QuickStepOnOffRule(Executor mainThreadExecutor, LauncherInstrumentation launcher) {
mLauncher = launcher;
this.mMainThreadExecutor = mainThreadExecutor;
}
@Override
public Statement apply(Statement base, Description description) {
if (TestHelpers.isInLauncherProcess() &&
description.getAnnotation(QuickstepOnOff.class) != null) {
Mode mode = description.getAnnotation(QuickstepOnOff.class).mode();
return new Statement() {
@Override
public void evaluate() throws Throwable {
try {
if (mode == ON || mode == BOTH) {
evaluateWithQuickstepOn();
}
if (mode == OFF || mode == BOTH) {
evaluateWithQuickstepOff();
}
} finally {
overrideSwipeUpEnabled(null);
}
}
private void evaluateWithQuickstepOff() throws Throwable {
overrideSwipeUpEnabled(false);
base.evaluate();
}
private void evaluateWithQuickstepOn() throws Throwable {
overrideSwipeUpEnabled(true);
base.evaluate();
}
private void overrideSwipeUpEnabled(Boolean swipeUpEnabledOverride) {
mLauncher.overrideSwipeUpEnabled(swipeUpEnabledOverride);
mMainThreadExecutor.execute(() -> OverviewInteractionState.INSTANCE.get(
InstrumentationRegistry.getInstrumentation().getTargetContext()).
notifySwipeUpSettingChanged(mLauncher.isSwipeUpEnabled()));
}
};
} else {
return base;
}
}
}
+33
View File
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
**
** Copyright 2018, 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.
*/
-->
<!-- Used as the widget host view background when giving focus to a child via keyboard. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<shape android:shape="rectangle">
<stroke android:color="#fff" android:width="2dp" />
</shape>
</item>
<item android:state_focused="true">
<shape android:shape="rectangle">
<solid android:color="@color/focused_background" />
</shape>
</item>
</selector>
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:id="@+id/settings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_setting"
android:tint="@android:color/black"
android:padding="12dp"
android:background="?android:attr/selectableItemBackgroundBorderless" />
<View
android:id="@+id/divider"
android:layout_width="1dp"
android:layout_height="30dp"
android:layout_marginEnd="8dp"
android:background="?android:attr/listDivider" />
<!-- Note: seems we need focusable="false" and clickable="false" when moving to androidx -->
<Switch
android:id="@android:id/switch_widget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null" />
</LinearLayout>
+1 -2
View File
@@ -114,8 +114,7 @@
<string name="action_move_here" msgid="2170188780612570250">"आइटम यहां ले जाएं"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"होम स्क्रीन में आइटम जोड़ा गया"</string>
<string name="item_removed" msgid="851119963877842327">"आइटम निकाला गया"</string>
<!-- no translation found for undo (4151576204245173321) -->
<skip />
<string name="undo" msgid="4151576204245173321">"पहले जैसा करें"</string>
<string name="action_move" msgid="4339390619886385032">"आइटम ले जाएं"</string>
<string name="move_to_empty_cell" msgid="2833711483015685619">"पंक्ति <xliff:g id="NUMBER_0">%1$s</xliff:g> स्तंभ <xliff:g id="NUMBER_1">%2$s</xliff:g> पर ले जाएं"</string>
<string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g> स्थिति पर ले जाएं"</string>
+8 -8
View File
@@ -65,10 +65,10 @@
<string name="folder_hint_text" msgid="6617836969016293992">"תיקיה ללא שם"</string>
<string name="disabled_app_label" msgid="6673129024321402780">"<xliff:g id="APP_NAME">%1$s</xliff:g> מושבתת"</string>
<plurals name="badged_app_label" formatted="false" msgid="7948068486082879291">
<item quantity="two">לאפליקציה <xliff:g id="APP_NAME_2">%1$s</xliff:g> יש <xliff:g id="NOTIFICATION_COUNT_3">%2$d</xliff:g> הודעות</item>
<item quantity="many">לאפליקציה <xliff:g id="APP_NAME_2">%1$s</xliff:g> יש <xliff:g id="NOTIFICATION_COUNT_3">%2$d</xliff:g> הודעות</item>
<item quantity="other">לאפליקציה <xliff:g id="APP_NAME_2">%1$s</xliff:g> יש <xliff:g id="NOTIFICATION_COUNT_3">%2$d</xliff:g> הודעות</item>
<item quantity="one">לאפליקציה <xliff:g id="APP_NAME_0">%1$s</xliff:g> יש הודעה אחת (<xliff:g id="NOTIFICATION_COUNT_1">%2$d</xliff:g>)</item>
<item quantity="two">לאפליקציה <xliff:g id="APP_NAME_2">%1$s</xliff:g> יש <xliff:g id="NOTIFICATION_COUNT_3">%2$d</xliff:g> התראות</item>
<item quantity="many">לאפליקציה <xliff:g id="APP_NAME_2">%1$s</xliff:g> יש <xliff:g id="NOTIFICATION_COUNT_3">%2$d</xliff:g> התראות</item>
<item quantity="other">לאפליקציה <xliff:g id="APP_NAME_2">%1$s</xliff:g> יש <xliff:g id="NOTIFICATION_COUNT_3">%2$d</xliff:g> התראות</item>
<item quantity="one">לאפליקציה <xliff:g id="APP_NAME_0">%1$s</xliff:g> יש התראה אחת (<xliff:g id="NOTIFICATION_COUNT_1">%2$d</xliff:g>)</item>
</plurals>
<string name="default_scroll_format" msgid="7475544710230993317">"‏דף %1$d מתוך %2$d"</string>
<string name="workspace_scroll_format" msgid="8458889198184077399">"‏מסך דף הבית %1$d מתוך %2$d"</string>
@@ -85,11 +85,11 @@
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"הושבת על ידי מנהל המערכת שלך"</string>
<string name="allow_rotation_title" msgid="7728578836261442095">"אפשרות סיבוב של מסך דף הבית"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"כאשר הטלפון מסובב"</string>
<string name="icon_badging_title" msgid="874121399231955394">"סימני הודעות"</string>
<string name="icon_badging_title" msgid="874121399231955394">"סימני ההתראות"</string>
<string name="icon_badging_desc_on" msgid="2627952638544674079">"מופעלת"</string>
<string name="icon_badging_desc_off" msgid="5503319969924580241">"כבויה"</string>
<string name="title_missing_notification_access" msgid="7503287056163941064">"נדרשת גישה להודעות"</string>
<string name="msg_missing_notification_access" msgid="281113995110910548">"כדי להציג את סימני ההודעות, יש להפעיל הודעות מהאפליקציה <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="title_missing_notification_access" msgid="7503287056163941064">"נדרשת גישה להתראות"</string>
<string name="msg_missing_notification_access" msgid="281113995110910548">"כדי להציג את סימני ההתראות,יש להפעיל התראות מהאפליקציה <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="title_change_settings" msgid="1376365968844349552">"שנה את ההגדרות"</string>
<string name="icon_badging_service_title" msgid="2309733118428242174">"הצגה של סימן ההודעות"</string>
<string name="auto_add_shortcuts_label" msgid="8222286205987725611">"הוספת סמל במסך דף הבית"</string>
@@ -137,7 +137,7 @@
<string name="action_deep_shortcut" msgid="2864038805849372848">"קיצורי דרך"</string>
<string name="shortcuts_menu_with_notifications_description" msgid="2676582286544232849">"קיצורי דרך והודעות"</string>
<string name="action_dismiss_notification" msgid="5909461085055959187">"סגור"</string>
<string name="notification_dismissed" msgid="6002233469409822874">"ההודעה נסגרה"</string>
<string name="notification_dismissed" msgid="6002233469409822874">"ההתראה נסגרה"</string>
<string name="all_apps_personal_tab" msgid="4190252696685155002">"אישיות"</string>
<string name="all_apps_work_tab" msgid="4884822796154055118">"עבודה"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"פרופיל עבודה"</string>
+1 -1
View File
@@ -73,7 +73,7 @@
<string name="workspace_new_page" msgid="257366611030256142">"ပင်မမျက်နှာပြင် စာမျက်နှာသစ်"</string>
<string name="folder_opened" msgid="94695026776264709">"ဖွင့်ထားသောအကန့်, <xliff:g id="WIDTH">%1$d</xliff:g> နှင့် <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
<string name="folder_tap_to_close" msgid="4625795376335528256">"ဖိုင်တွဲကို ပိတ်ရန် တို့ပါ"</string>
<string name="folder_tap_to_rename" msgid="4017685068016979677">"အမည်ပြောင်းခြင်းကို သိမ်းဆည်းရန် တို့ပါ"</string>
<string name="folder_tap_to_rename" msgid="4017685068016979677">"အမည်ပြောင်းခြင်းကို သိမ်းရန် တို့ပါ"</string>
<string name="folder_closed" msgid="4100806530910930934">"ပိတ်ထားသောအကန့်"</string>
<string name="folder_renamed" msgid="1794088362165669656">"ပြောင်းလဲလိုက်သော အကန့်အမည် <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format" msgid="6629239338071103179">"အကန့်အမည်: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
* Copyright (C) 2018 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.
*/
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:key="feature_flags"
android:persistent="false">
</PreferenceScreen>
+6
View File
@@ -52,4 +52,10 @@
android:defaultValue=""
android:persistent="false" />
<PreferenceScreen
android:fragment="com.android.launcher3.config.FlagTogglerPreferenceFragment"
android:key="flag_toggler"
android:persistent="false"
android:title="Feature flags"/>
</PreferenceScreen>
@@ -40,6 +40,7 @@ import android.util.Patterns;
import com.android.launcher3.LauncherProvider.SqlArguments;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.Thunk;
import org.xmlpull.v1.XmlPullParser;
@@ -163,7 +164,7 @@ public class AutoInstallsLayout {
private final int mRowCount;
private final int mColumnCount;
private final long[] mTemp = new long[2];
private final int[] mTemp = new int[2];
@Thunk final ContentValues mValues;
protected final String mRootTag;
@@ -191,7 +192,7 @@ public class AutoInstallsLayout {
/**
* Loads the layout in the db and returns the number of entries added on the desktop.
*/
public int loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds) {
public int loadLayout(SQLiteDatabase db, IntArray screenIds) {
mDb = db;
try {
return parseLayout(mLayoutId, screenIds);
@@ -204,7 +205,7 @@ public class AutoInstallsLayout {
/**
* Parses the layout and returns the number of elements added on the homescreen.
*/
protected int parseLayout(int layoutId, ArrayList<Long> screenIds)
protected int parseLayout(int layoutId, IntArray screenIds)
throws XmlPullParserException, IOException {
XmlResourceParser parser = mSourceRes.getXml(layoutId);
beginDocument(parser, mRootTag);
@@ -227,14 +228,14 @@ public class AutoInstallsLayout {
* Parses container and screenId attribute from the current tag, and puts it in the out.
* @param out array of size 2.
*/
protected void parseContainerAndScreen(XmlResourceParser parser, long[] out) {
protected void parseContainerAndScreen(XmlResourceParser parser, int[] out) {
if (HOTSEAT_CONTAINER_NAME.equals(getAttributeValue(parser, ATTR_CONTAINER))) {
out[0] = Favorites.CONTAINER_HOTSEAT;
// Hack: hotseat items are stored using screen ids
out[1] = Long.parseLong(getAttributeValue(parser, ATTR_RANK));
out[1] = Integer.parseInt(getAttributeValue(parser, ATTR_RANK));
} else {
out[0] = Favorites.CONTAINER_DESKTOP;
out[1] = Long.parseLong(getAttributeValue(parser, ATTR_SCREEN));
out[1] = Integer.parseInt(getAttributeValue(parser, ATTR_SCREEN));
}
}
@@ -242,9 +243,7 @@ public class AutoInstallsLayout {
* Parses the current node and returns the number of elements added.
*/
protected int parseAndAddNode(
XmlResourceParser parser,
ArrayMap<String, TagParser> tagParserMap,
ArrayList<Long> screenIds)
XmlResourceParser parser, ArrayMap<String, TagParser> tagParserMap, IntArray screenIds)
throws XmlPullParserException, IOException {
if (TAG_INCLUDE.equals(parser.getName())) {
@@ -259,8 +258,8 @@ public class AutoInstallsLayout {
mValues.clear();
parseContainerAndScreen(parser, mTemp);
final long container = mTemp[0];
final long screenId = mTemp[1];
final int container = mTemp[0];
final int screenId = mTemp[1];
mValues.put(Favorites.CONTAINER, container);
mValues.put(Favorites.SCREEN, screenId);
@@ -275,7 +274,7 @@ public class AutoInstallsLayout {
if (LOGD) Log.d(TAG, "Ignoring unknown element tag: " + parser.getName());
return 0;
}
long newElementId = tagParser.parseAndAdd(parser);
int newElementId = tagParser.parseAndAdd(parser);
if (newElementId >= 0) {
// Keep track of the set of screens which need to be added to the db.
if (!screenIds.contains(screenId) &&
@@ -287,8 +286,8 @@ public class AutoInstallsLayout {
return 0;
}
protected long addShortcut(String title, Intent intent, int type) {
long id = mCallback.generateNewItemId();
protected int addShortcut(String title, Intent intent, int type) {
int id = mCallback.generateNewItemId();
mValues.put(Favorites.INTENT, intent.toUri(0));
mValues.put(Favorites.TITLE, title);
mValues.put(Favorites.ITEM_TYPE, type);
@@ -325,7 +324,7 @@ public class AutoInstallsLayout {
* Parses the tag and adds to the db
* @return the id of the row added or -1;
*/
long parseAndAdd(XmlResourceParser parser)
int parseAndAdd(XmlResourceParser parser)
throws XmlPullParserException, IOException;
}
@@ -335,7 +334,7 @@ public class AutoInstallsLayout {
protected class AppShortcutParser implements TagParser {
@Override
public long parseAndAdd(XmlResourceParser parser) {
public int parseAndAdd(XmlResourceParser parser) {
final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
@@ -372,7 +371,7 @@ public class AutoInstallsLayout {
/**
* Helper method to allow extending the parser capabilities
*/
protected long invalidPackageOrClass(XmlResourceParser parser) {
protected int invalidPackageOrClass(XmlResourceParser parser) {
Log.w(TAG, "Skipping invalid <favorite> with no component");
return -1;
}
@@ -384,7 +383,7 @@ public class AutoInstallsLayout {
protected class AutoInstallParser implements TagParser {
@Override
public long parseAndAdd(XmlResourceParser parser) {
public int parseAndAdd(XmlResourceParser parser) {
final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) {
@@ -415,7 +414,7 @@ public class AutoInstallsLayout {
}
@Override
public long parseAndAdd(XmlResourceParser parser) {
public int parseAndAdd(XmlResourceParser parser) {
final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0);
final int iconId = getAttributeResourceValue(parser, ATTR_ICON, 0);
@@ -471,7 +470,7 @@ public class AutoInstallsLayout {
protected class PendingWidgetParser implements TagParser {
@Override
public long parseAndAdd(XmlResourceParser parser)
public int parseAndAdd(XmlResourceParser parser)
throws XmlPullParserException, IOException {
final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
@@ -510,7 +509,7 @@ public class AutoInstallsLayout {
return verifyAndInsert(new ComponentName(packageName, className), extras);
}
protected long verifyAndInsert(ComponentName cn, Bundle extras) {
protected int verifyAndInsert(ComponentName cn, Bundle extras) {
mValues.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString());
mValues.put(Favorites.RESTORED,
LauncherAppWidgetInfo.FLAG_ID_NOT_VALID |
@@ -521,7 +520,7 @@ public class AutoInstallsLayout {
mValues.put(Favorites.INTENT, new Intent().putExtras(extras).toUri(0));
}
long insertedId = mCallback.insertAndCheck(mDb, mValues);
int insertedId = mCallback.insertAndCheck(mDb, mValues);
if (insertedId < 0) {
return -1;
} else {
@@ -542,7 +541,7 @@ public class AutoInstallsLayout {
}
@Override
public long parseAndAdd(XmlResourceParser parser)
public int parseAndAdd(XmlResourceParser parser)
throws XmlPullParserException, IOException {
final String title;
final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0);
@@ -557,14 +556,14 @@ public class AutoInstallsLayout {
mValues.put(Favorites.SPANX, 1);
mValues.put(Favorites.SPANY, 1);
mValues.put(Favorites._ID, mCallback.generateNewItemId());
long folderId = mCallback.insertAndCheck(mDb, mValues);
int folderId = mCallback.insertAndCheck(mDb, mValues);
if (folderId < 0) {
if (LOGD) Log.e(TAG, "Unable to add folder");
return -1;
}
final ContentValues myValues = new ContentValues(mValues);
ArrayList<Long> folderItems = new ArrayList<>();
IntArray folderItems = new IntArray();
int type;
int folderDepth = parser.getDepth();
@@ -580,7 +579,7 @@ public class AutoInstallsLayout {
TagParser tagParser = mFolderElements.get(parser.getName());
if (tagParser != null) {
final long id = tagParser.parseAndAdd(parser);
final int id = tagParser.parseAndAdd(parser);
if (id >= 0) {
folderItems.add(id);
rank++;
@@ -590,7 +589,7 @@ public class AutoInstallsLayout {
}
}
long addedId = folderId;
int addedId = folderId;
// We can only have folders with >= 2 items, so we need to remove the
// folder and clean up if less than 2 items were included, or some
@@ -675,9 +674,9 @@ public class AutoInstallsLayout {
}
public interface LayoutParserCallback {
long generateNewItemId();
int generateNewItemId();
long insertAndCheck(SQLiteDatabase db, ContentValues values);
int insertAndCheck(SQLiteDatabase db, ContentValues values);
}
@Thunk static void copyInteger(ContentValues from, ContentValues to, String key) {
+3 -3
View File
@@ -2063,7 +2063,7 @@ public class CellLayout extends ViewGroup {
private void commitTempPlacement() {
mTmpOccupied.copyTo(mOccupied);
long screenId = mLauncher.getWorkspace().getIdForScreen(this);
int screenId = mLauncher.getWorkspace().getIdForScreen(this);
int container = Favorites.CONTAINER_DESKTOP;
if (mContainerType == HOTSEAT) {
@@ -2688,8 +2688,8 @@ public class CellLayout extends ViewGroup {
// the CellLayout that was long clicked
public static final class CellInfo extends CellAndSpan {
public final View cell;
final long screenId;
final long container;
final int screenId;
final int container;
public CellInfo(View v, ItemInfo info) {
cellX = info.cellX;
@@ -76,13 +76,13 @@ public class DefaultLayoutParser extends AutoInstallsLayout {
}
@Override
protected void parseContainerAndScreen(XmlResourceParser parser, long[] out) {
protected void parseContainerAndScreen(XmlResourceParser parser, int[] out) {
out[0] = LauncherSettings.Favorites.CONTAINER_DESKTOP;
String strContainer = getAttributeValue(parser, ATTR_CONTAINER);
if (strContainer != null) {
out[0] = Long.valueOf(strContainer);
out[0] = Integer.parseInt(strContainer);
}
out[1] = Long.parseLong(getAttributeValue(parser, ATTR_SCREEN));
out[1] = Integer.parseInt(getAttributeValue(parser, ATTR_SCREEN));
}
/**
@@ -91,7 +91,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout {
public class AppShortcutWithUriParser extends AppShortcutParser {
@Override
protected long invalidPackageOrClass(XmlResourceParser parser) {
protected int invalidPackageOrClass(XmlResourceParser parser) {
final String uri = getAttributeValue(parser, ATTR_URI);
if (TextUtils.isEmpty(uri)) {
Log.e(TAG, "Skipping invalid <favorite> with no component or uri");
@@ -205,11 +205,11 @@ public class DefaultLayoutParser extends AutoInstallsLayout {
private final AppShortcutWithUriParser mChildParser = new AppShortcutWithUriParser();
@Override
public long parseAndAdd(XmlResourceParser parser) throws XmlPullParserException,
public int parseAndAdd(XmlResourceParser parser) throws XmlPullParserException,
IOException {
final int groupDepth = parser.getDepth();
int type;
long addedId = -1;
int addedId = -1;
while ((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > groupDepth) {
if (type != XmlPullParser.START_TAG || addedId > -1) {
@@ -233,7 +233,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout {
@Thunk class PartnerFolderParser implements TagParser {
@Override
public long parseAndAdd(XmlResourceParser parser) throws XmlPullParserException,
public int parseAndAdd(XmlResourceParser parser) throws XmlPullParserException,
IOException {
// Folder contents come from an external XML resource
final Partner partner = Partner.get(mPackageManager);
@@ -259,7 +259,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout {
@Thunk class MyFolderParser extends FolderParser {
@Override
public long parseAndAdd(XmlResourceParser parser) throws XmlPullParserException,
public int parseAndAdd(XmlResourceParser parser) throws XmlPullParserException,
IOException {
final int resId = getAttributeResourceValue(parser, ATTR_FOLDER_ITEMS, 0);
if (resId != 0) {
@@ -277,7 +277,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout {
protected class AppWidgetParser extends PendingWidgetParser {
@Override
protected long verifyAndInsert(ComponentName cn, Bundle extras) {
protected int verifyAndInsert(ComponentName cn, Bundle extras) {
try {
mPackageManager.getReceiverInfo(cn, 0);
} catch (Exception e) {
@@ -293,7 +293,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout {
}
final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
long insertedId = -1;
int insertedId = -1;
try {
int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
+5 -5
View File
@@ -34,7 +34,7 @@ public class ItemInfo {
/**
* The id in the settings database for this item
*/
public long id = NO_ID;
public int id = NO_ID;
/**
* One of {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION},
@@ -52,14 +52,14 @@ public class ItemInfo {
* will be {@link #NO_ID} (since it is not stored in the settings DB). For user folders
* it will be the id of the folder.
*/
public long container = NO_ID;
public int container = NO_ID;
/**
* Indicates the screen in which the shortcut appears if the container types is
* {@link LauncherSettings.Favorites#CONTAINER_DESKTOP}. (i.e., ignore if the container type is
* {@link LauncherSettings.Favorites#CONTAINER_HOTSEAT})
*/
public long screenId = -1;
public int screenId = -1;
/**
* Indicates the X position of the associated cell.
@@ -158,8 +158,8 @@ public class ItemInfo {
public void readFromValues(ContentValues values) {
itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
container = values.getAsLong(LauncherSettings.Favorites.CONTAINER);
screenId = values.getAsLong(LauncherSettings.Favorites.SCREEN);
container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER);
screenId = values.getAsInteger(LauncherSettings.Favorites.SCREEN);
cellX = values.getAsInteger(LauncherSettings.Favorites.CELLX);
cellY = values.getAsInteger(LauncherSettings.Favorites.CELLY);
spanX = values.getAsInteger(LauncherSettings.Favorites.SPANX);
+22 -18
View File
@@ -108,6 +108,7 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.ActivityResultInfo;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.MultiValueAlpha;
@@ -490,9 +491,9 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
* Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
* a configuration step, this allows the proper animations to run after other transitions.
*/
private long completeAdd(
private int completeAdd(
int requestCode, Intent intent, int appWidgetId, PendingRequestArgs info) {
long screenId = info.screenId;
int screenId = info.screenId;
if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
// When the screen id represents an actual screen (as opposed to a rank) we make sure
// that the drop page actually exists.
@@ -694,7 +695,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
* @param screenId the screen id to check
* @return the new screen, or screenId if it exists
*/
private long ensurePendingDropLayoutExists(long screenId) {
private int ensurePendingDropLayoutExists(int screenId) {
CellLayout dropLayout = mWorkspace.getScreenWithId(screenId);
if (dropLayout == null) {
// it's possible that the add screen was removed because it was
@@ -974,7 +975,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
*
* @param data The intent describing the shortcut.
*/
private void completeAddShortcut(Intent data, long container, long screenId, int cellX,
private void completeAddShortcut(Intent data, int container, int screenId, int cellX,
int cellY, PendingRequestArgs args) {
if (args.getRequestCode() != REQUEST_CREATE_SHORTCUT
|| args.getPendingIntent().getComponent() == null) {
@@ -1050,7 +1051,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
}
}
public FolderIcon findFolderIcon(final long folderIconId) {
public FolderIcon findFolderIcon(final int folderIconId) {
return (FolderIcon) mWorkspace.getHomescreenIconByItemId(folderIconId);
}
@@ -1413,7 +1414,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
}
}
public void addPendingItem(PendingAddItemInfo info, long container, long screenId,
public void addPendingItem(PendingAddItemInfo info, int container, int screenId,
int[] cell, int spanX, int spanY) {
info.container = container;
info.screenId = screenId;
@@ -1489,7 +1490,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
}
}
FolderIcon addFolder(CellLayout layout, long container, final long screenId, int cellX,
FolderIcon addFolder(CellLayout layout, int container, final int screenId, int cellX,
int cellY) {
final FolderInfo folderInfo = new FolderInfo();
folderInfo.title = getText(R.string.folder_name);
@@ -1650,7 +1651,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
/**
* Returns the CellLayout of the specified container at the specified screen.
*/
public CellLayout getCellLayout(long container, long screenId) {
public CellLayout getCellLayout(int container, int screenId) {
if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
if (mHotseat != null) {
return mHotseat.getLayout();
@@ -1750,14 +1751,15 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
}
@Override
public void bindScreens(ArrayList<Long> orderedScreenIds) {
public void bindScreens(IntArray orderedScreenIds) {
// Make sure the first screen is always at the start.
if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
if (FeatureFlags.QSB_ON_FIRST_SCREEN.get() &&
orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != 0) {
orderedScreenIds.remove(Workspace.FIRST_SCREEN_ID);
orderedScreenIds.removeValue(Workspace.FIRST_SCREEN_ID);
orderedScreenIds.add(0, Workspace.FIRST_SCREEN_ID);
LauncherModel.updateWorkspaceScreenOrder(this, orderedScreenIds);
} else if (!FeatureFlags.QSB_ON_FIRST_SCREEN && orderedScreenIds.isEmpty()) {
} else if (!FeatureFlags.QSB_ON_FIRST_SCREEN.get()
&& orderedScreenIds.isEmpty()) {
// If there are no screens, we need to have an empty screen
mWorkspace.addExtraEmptyScreen();
}
@@ -1769,11 +1771,11 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
mWorkspace.unlockWallpaperFromDefaultPageOnNextLayout();
}
private void bindAddScreens(ArrayList<Long> orderedScreenIds) {
private void bindAddScreens(IntArray orderedScreenIds) {
int count = orderedScreenIds.size();
for (int i = 0; i < count; i++) {
long screenId = orderedScreenIds.get(i);
if (!FeatureFlags.QSB_ON_FIRST_SCREEN || screenId != Workspace.FIRST_SCREEN_ID) {
int screenId = orderedScreenIds.get(i);
if (!FeatureFlags.QSB_ON_FIRST_SCREEN.get() || screenId != Workspace.FIRST_SCREEN_ID) {
// No need to bind the first screen, as its always bound.
mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId);
}
@@ -1792,7 +1794,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
}
@Override
public void bindAppsAdded(ArrayList<Long> newScreens, ArrayList<ItemInfo> addNotAnimated,
public void bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated,
ArrayList<ItemInfo> addAnimated) {
// Add the new screens
if (newScreens != null) {
@@ -1823,7 +1825,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
final Collection<Animator> bounceAnims = new ArrayList<>();
final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
Workspace workspace = mWorkspace;
long newItemsScreenId = -1;
int newItemsScreenId = -1;
int end = items.size();
for (int i = 0; i < end; i++) {
final ItemInfo item = items.get(i);
@@ -1896,7 +1898,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
AnimatorSet anim = new AnimatorSet();
anim.playTogether(bounceAnims);
long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
int currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newItemsScreenId);
final Runnable startBounceAnimRunnable = anim::start;
@@ -2283,6 +2285,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
writer.print(" mPendingRequestArgs=" + mPendingRequestArgs);
writer.println(" mPendingActivityResult=" + mPendingActivityResult);
writer.println(" mRotationHelper: " + mRotationHelper);
// Extra logging for b/116853349
mDragLayer.dumpAlpha(writer);
dumpMisc(writer);
try {
+14 -14
View File
@@ -16,7 +16,7 @@
package com.android.launcher3;
import static com.android.launcher3.SettingsActivity.NOTIFICATION_BADGING;
import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
import android.content.ComponentName;
import android.content.ContentProviderClient;
@@ -33,7 +33,7 @@ import com.android.launcher3.icons.IconCache;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.SettingsObserver;
import com.android.launcher3.util.SecureSettingsObserver;
public class LauncherAppState {
@@ -48,7 +48,7 @@ public class LauncherAppState {
private final IconCache mIconCache;
private final WidgetPreviewLoader mWidgetCache;
private final InvariantDeviceProfile mInvariantDeviceProfile;
private final SettingsObserver mNotificationBadgingObserver;
private final SecureSettingsObserver mNotificationBadgingObserver;
public static LauncherAppState getInstance(final Context context) {
return INSTANCE.get(context);
@@ -99,17 +99,17 @@ public class LauncherAppState {
mNotificationBadgingObserver = null;
} else {
// Register an observer to rebind the notification listener when badging is re-enabled.
mNotificationBadgingObserver = new SettingsObserver.Secure(
mContext.getContentResolver()) {
@Override
public void onSettingChanged(boolean isNotificationBadgingEnabled) {
if (isNotificationBadgingEnabled) {
NotificationListener.requestRebind(new ComponentName(
mContext, NotificationListener.class));
}
}
};
mNotificationBadgingObserver.register(NOTIFICATION_BADGING);
mNotificationBadgingObserver =
newNotificationSettingsObserver(mContext, this::onNotificationSettingsChanged);
mNotificationBadgingObserver.register();
mNotificationBadgingObserver.dispatchOnChange();
}
}
protected void onNotificationSettingsChanged(boolean isNotificationBadgingEnabled) {
if (isNotificationBadgingEnabled) {
NotificationListener.requestRebind(new ComponentName(
mContext, NotificationListener.class));
}
}
+30 -16
View File
@@ -55,6 +55,7 @@ import com.android.launcher3.provider.LauncherDbUtils;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.PackageUserKey;
@@ -143,15 +144,14 @@ public class LauncherModel extends BroadcastReceiver
public void clearPendingBinds();
public void startBinding();
public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons);
public void bindScreens(ArrayList<Long> orderedScreenIds);
public void bindScreens(IntArray orderedScreenIds);
public void finishFirstPageBind(ViewOnDrawExecutor executor);
public void finishBindingItems(int currentScreen);
public void bindAllApplications(ArrayList<AppInfo> apps);
public void bindAppsAddedOrUpdated(ArrayList<AppInfo> apps);
public void preAddApps();
public void bindAppsAdded(ArrayList<Long> newScreens,
ArrayList<ItemInfo> addNotAnimated,
ArrayList<ItemInfo> addAnimated);
public void bindAppsAdded(IntArray newScreens,
ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated);
public void bindPromiseAppProgressUpdated(PromiseAppInfo app);
public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated, UserHandle user);
public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
@@ -211,7 +211,12 @@ public class LauncherModel extends BroadcastReceiver
}
static void checkItemInfoLocked(
final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
final int itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
&& com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
android.util.Log.d("b/117332845",
"Checking item: " + android.util.Log.getStackTraceString(new Throwable()));
}
ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId);
if (modelItem != null && item != modelItem) {
// check all the data is consistent
@@ -250,7 +255,7 @@ public class LauncherModel extends BroadcastReceiver
static void checkItemInfo(final ItemInfo item) {
final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
final long itemId = item.id;
final int itemId = item.id;
Runnable r = new Runnable() {
public void run() {
synchronized (sBgDataModel) {
@@ -265,17 +270,16 @@ public class LauncherModel extends BroadcastReceiver
* Update the order of the workspace screens in the database. The array list contains
* a list of screen ids in the order that they should appear.
*/
public static void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
public static void updateWorkspaceScreenOrder(Context context, IntArray screens) {
final ContentResolver cr = context.getContentResolver();
final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
// Remove any negative screen ids -- these aren't persisted
Iterator<Long> iter = screensCopy.iterator();
while (iter.hasNext()) {
long id = iter.next();
if (id < 0) {
iter.remove();
// Create a copy with only non-negative values
final IntArray screensCopy = new IntArray();
for (int i = 0; i < screens.size(); i++) {
int id = screens.get(i);
if (id >= 0) {
screensCopy.add(id);
}
}
@@ -288,7 +292,7 @@ public class LauncherModel extends BroadcastReceiver
int count = screensCopy.size();
for (int i = 0; i < count; i++) {
ContentValues v = new ContentValues();
long screenId = screensCopy.get(i);
int screenId = screensCopy.get(i);
v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
@@ -447,6 +451,11 @@ public class LauncherModel extends BroadcastReceiver
* @return true if the page could be bound synchronously.
*/
public boolean startLoader(int synchronousBindPage) {
if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
&& com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
android.util.Log.d("b/117332845",
android.util.Log.getStackTraceString(new Throwable()));
}
// Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_LOADER_RUNNING);
synchronized (mLock) {
@@ -511,7 +520,7 @@ public class LauncherModel extends BroadcastReceiver
/**
* Loads the workspace screen ids in an ordered list.
*/
public static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
public static IntArray loadWorkspaceScreensDb(Context context) {
final ContentResolver contentResolver = context.getContentResolver();
final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
@@ -558,6 +567,11 @@ public class LauncherModel extends BroadcastReceiver
synchronized (mLock) {
// Everything loaded bind the data.
mModelLoaded = true;
if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
&& com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
android.util.Log.d("b/117332845",
android.util.Log.getStackTraceString(new Throwable()));
}
}
}
+39 -34
View File
@@ -58,6 +58,8 @@ import com.android.launcher3.model.DbDowngradeHelper;
import com.android.launcher3.provider.LauncherDbUtils;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
import com.android.launcher3.provider.RestoreDbTask;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.NoLocaleSQLiteHelper;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.Thunk;
@@ -67,9 +69,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Arrays;
public class LauncherProvider extends ContentProvider {
private static final String TAG = "LauncherProvider";
@@ -170,7 +170,7 @@ public class LauncherProvider extends ContentProvider {
return result;
}
@Thunk static long dbInsertAndCheck(DatabaseHelper helper,
@Thunk static int dbInsertAndCheck(DatabaseHelper helper,
SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
if (values == null) {
throw new RuntimeException("Error: attempting to insert null values");
@@ -179,7 +179,7 @@ public class LauncherProvider extends ContentProvider {
throw new RuntimeException("Error: attempting to add item without specifying an id");
}
helper.checkId(table, values);
return db.insert(table, nullColumnHack, values);
return (int) db.insert(table, nullColumnHack, values);
}
private void reloadLauncherIfExternal() {
@@ -205,7 +205,7 @@ public class LauncherProvider extends ContentProvider {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
addModifiedTime(initialValues);
final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
final int rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
if (rowId < 0) return null;
uri = ContentUris.withAppendedId(uri, rowId);
@@ -230,7 +230,7 @@ public class LauncherProvider extends ContentProvider {
private boolean initializeExternalAdd(ContentValues values) {
// 1. Ensure that externally added items have a valid item id
long id = mOpenHelper.generateNewItemId();
int id = mOpenHelper.generateNewItemId();
values.put(LauncherSettings.Favorites._ID, id);
// 2. In the case of an app widget, and if no app widget id is specified, we
@@ -263,7 +263,7 @@ public class LauncherProvider extends ContentProvider {
}
// Add screen id if not present
long screenId = values.getAsLong(LauncherSettings.Favorites.SCREEN);
int screenId = values.getAsInteger(LauncherSettings.Favorites.SCREEN);
SQLiteStatement stmp = null;
try {
stmp = mOpenHelper.getWritableDatabase().compileStatement(
@@ -369,17 +369,18 @@ public class LauncherProvider extends ContentProvider {
}
case LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS: {
Bundle result = new Bundle();
result.putSerializable(LauncherSettings.Settings.EXTRA_VALUE, deleteEmptyFolders());
result.putIntArray(LauncherSettings.Settings.EXTRA_VALUE, deleteEmptyFolders()
.toArray());
return result;
}
case LauncherSettings.Settings.METHOD_NEW_ITEM_ID: {
Bundle result = new Bundle();
result.putLong(LauncherSettings.Settings.EXTRA_VALUE, mOpenHelper.generateNewItemId());
result.putInt(LauncherSettings.Settings.EXTRA_VALUE, mOpenHelper.generateNewItemId());
return result;
}
case LauncherSettings.Settings.METHOD_NEW_SCREEN_ID: {
Bundle result = new Bundle();
result.putLong(LauncherSettings.Settings.EXTRA_VALUE, mOpenHelper.generateNewScreenId());
result.putInt(LauncherSettings.Settings.EXTRA_VALUE, mOpenHelper.generateNewScreenId());
return result;
}
case LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB: {
@@ -402,8 +403,8 @@ public class LauncherProvider extends ContentProvider {
* Deletes any empty folder from the DB.
* @return Ids of deleted folders.
*/
private ArrayList<Long> deleteEmptyFolders() {
ArrayList<Long> folderIds = new ArrayList<>();
private IntArray deleteEmptyFolders() {
IntArray folderIds = new IntArray();
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
// Select folders whose id do not match any container value.
@@ -542,8 +543,8 @@ public class LauncherProvider extends ContentProvider {
public static class DatabaseHelper extends NoLocaleSQLiteHelper implements LayoutParserCallback {
private final Handler mWidgetHostResetHandler;
private final Context mContext;
private long mMaxItemId = -1;
private long mMaxScreenId = -1;
private int mMaxItemId = -1;
private int mMaxScreenId = -1;
DatabaseHelper(Context context, Handler widgetHostResetHandler) {
this(context, widgetHostResetHandler, LauncherFiles.LAUNCHER_DB);
@@ -789,7 +790,7 @@ public class LauncherProvider extends ContentProvider {
convertShortcutsToLauncherActivities(db);
case 26:
// QSB was moved to the grid. Clear the first row on screen 0.
if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
if (FeatureFlags.QSB_ON_FIRST_SCREEN.get() &&
!LauncherDbUtils.prepareScreenZeroToHostQsb(mContext, db)) {
break;
}
@@ -844,7 +845,7 @@ public class LauncherProvider extends ContentProvider {
Log.e(TAG, "getAppWidgetIds not supported", e);
return;
}
final HashSet<Integer> validWidgets = new HashSet<>();
final IntSet validWidgets = new IntSet();
try (Cursor c = db.query(Favorites.TABLE_NAME,
new String[] {Favorites.APPWIDGET_ID },
"itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null, null, null)) {
@@ -899,7 +900,7 @@ public class LauncherProvider extends ContentProvider {
continue;
}
long id = c.getLong(idIndex);
int id = c.getInt(idIndex);
updateStmt.bindLong(1, id);
updateStmt.executeUpdateDelete();
}
@@ -914,15 +915,14 @@ public class LauncherProvider extends ContentProvider {
*/
public boolean recreateWorkspaceTable(SQLiteDatabase db) {
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
final ArrayList<Long> sortedIDs;
final IntArray sortedIDs;
try (Cursor c = db.query(WorkspaceScreens.TABLE_NAME,
new String[] {LauncherSettings.WorkspaceScreens._ID},
null, null, null, null,
LauncherSettings.WorkspaceScreens.SCREEN_RANK)) {
// Use LinkedHashSet so that ordering is preserved
sortedIDs = new ArrayList<>(
LauncherDbUtils.iterateCursor(c, 0, new LinkedHashSet<Long>()));
sortedIDs = LauncherDbUtils.getScreenIdsFromCursor(c);
}
db.execSQL("DROP TABLE IF EXISTS " + WorkspaceScreens.TABLE_NAME);
addWorkspacesTable(db, false);
@@ -937,7 +937,11 @@ public class LauncherProvider extends ContentProvider {
db.insertOrThrow(WorkspaceScreens.TABLE_NAME, null, values);
}
t.commit();
mMaxScreenId = sortedIDs.isEmpty() ? 0 : Collections.max(sortedIDs);
mMaxScreenId = 0;
for (int i = 0; i < sortedIDs.size(); i++) {
mMaxScreenId = Math.max(mMaxScreenId, sortedIDs.get(i));
}
} catch (SQLException ex) {
// Old version remains, which means we wipe old data
Log.e(TAG, ex.getMessage(), ex);
@@ -997,7 +1001,7 @@ public class LauncherProvider extends ContentProvider {
// constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
// after that point
@Override
public long generateNewItemId() {
public int generateNewItemId() {
if (mMaxItemId < 0) {
throw new RuntimeException("Error: max item id was not initialized");
}
@@ -1010,12 +1014,12 @@ public class LauncherProvider extends ContentProvider {
}
@Override
public long insertAndCheck(SQLiteDatabase db, ContentValues values) {
public int insertAndCheck(SQLiteDatabase db, ContentValues values) {
return dbInsertAndCheck(this, db, Favorites.TABLE_NAME, null, values);
}
public void checkId(String table, ContentValues values) {
long id = values.getAsLong(LauncherSettings.BaseLauncherColumns._ID);
int id = values.getAsInteger(LauncherSettings.BaseLauncherColumns._ID);
if (WorkspaceScreens.TABLE_NAME.equals(table)) {
mMaxScreenId = Math.max(id, mMaxScreenId);
} else {
@@ -1023,7 +1027,7 @@ public class LauncherProvider extends ContentProvider {
}
}
private long initializeMaxItemId(SQLiteDatabase db) {
private int initializeMaxItemId(SQLiteDatabase db) {
return getMaxId(db, Favorites.TABLE_NAME);
}
@@ -1032,7 +1036,7 @@ public class LauncherProvider extends ContentProvider {
// call the constructor from the worker thread; however, this doesn't extend until after the
// constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
// after that point
public long generateNewScreenId() {
public int generateNewScreenId() {
if (mMaxScreenId < 0) {
throw new RuntimeException("Error: max screen id was not initialized");
}
@@ -1040,20 +1044,21 @@ public class LauncherProvider extends ContentProvider {
return mMaxScreenId;
}
private long initializeMaxScreenId(SQLiteDatabase db) {
private int initializeMaxScreenId(SQLiteDatabase db) {
return getMaxId(db, WorkspaceScreens.TABLE_NAME);
}
@Thunk int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) {
ArrayList<Long> screenIds = new ArrayList<Long>();
IntArray screenIds = new IntArray();
// TODO: Use multiple loaders with fall-back and transaction.
int count = loader.loadLayout(db, screenIds);
// Add the screens specified by the items above
Collections.sort(screenIds);
int[] sortedScreenIds = screenIds.toArray();
Arrays.sort(sortedScreenIds);
int rank = 0;
ContentValues values = new ContentValues();
for (Long id : screenIds) {
for (int id : sortedScreenIds) {
values.clear();
values.put(LauncherSettings.WorkspaceScreens._ID, id);
values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank);
@@ -1075,12 +1080,12 @@ public class LauncherProvider extends ContentProvider {
/**
* @return the max _id in the provided table.
*/
@Thunk static long getMaxId(SQLiteDatabase db, String table) {
@Thunk static int getMaxId(SQLiteDatabase db, String table) {
Cursor c = db.rawQuery("SELECT MAX(_id) FROM " + table, null);
// get the result
long id = -1;
int id = -1;
if (c != null && c.moveToNext()) {
id = c.getLong(0);
id = c.getInt(0);
}
if (c != null) {
c.close();
@@ -128,7 +128,7 @@ public class LauncherSettings {
*
* @return The unique content URL for the specified row.
*/
public static Uri getContentUri(long id) {
public static Uri getContentUri(int id) {
return Uri.parse("content://" + LauncherProvider.AUTHORITY +
"/" + TABLE_NAME + "/" + id);
}
@@ -18,6 +18,7 @@ package com.android.launcher3;
import android.content.Context;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.IconShapeOverride;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.util.ResourceBasedOverride;
@@ -35,6 +36,7 @@ public class MainProcessInitializer implements ResourceBasedOverride {
protected void init(Context context) {
FileLog.setDir(context.getApplicationContext().getFilesDir());
FeatureFlags.initialize(context);
IconShapeOverride.apply(context);
SessionCommitReceiver.applyDefaultUserPrefs(context);
}
+23 -15
View File
@@ -18,6 +18,7 @@ package com.android.launcher3;
import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue;
import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
import android.annotation.TargetApi;
import android.app.Activity;
@@ -25,7 +26,6 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -43,10 +43,11 @@ import android.view.View;
import android.widget.Adapter;
import android.widget.ListView;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.IconShapeOverride;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.util.ListViewHighlighter;
import com.android.launcher3.util.SettingsObserver;
import com.android.launcher3.util.SecureSettingsObserver;
import com.android.launcher3.views.ButtonPreference;
import java.util.Objects;
@@ -57,9 +58,9 @@ import java.util.Objects;
public class SettingsActivity extends Activity
implements PreferenceFragment.OnPreferenceStartFragmentCallback {
private static final String FLAGS_PREFERENCE_KEY = "flag_toggler";
private static final String ICON_BADGING_PREFERENCE_KEY = "pref_icon_badging";
/** Hidden field Settings.Secure.NOTIFICATION_BADGING */
public static final String NOTIFICATION_BADGING = "notification_badging";
/** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners";
@@ -111,7 +112,7 @@ public class SettingsActivity extends Activity
*/
public static class LauncherSettingsFragment extends PreferenceFragment {
private IconBadgingObserver mIconBadgingObserver;
private SecureSettingsObserver mIconBadgingObserver;
private String mPreferenceKey;
private boolean mPreferenceHighlighted = false;
@@ -126,6 +127,12 @@ public class SettingsActivity extends Activity
getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY);
addPreferencesFromResource(R.xml.launcher_preferences);
// Only show flag toggler UI if this build variant implements that.
Preference flagToggler = findPreference(FLAGS_PREFERENCE_KEY);
if (flagToggler != null && !FeatureFlags.showFlagTogglerUi()) {
getPreferenceScreen().removePreference(flagToggler);
}
ContentResolver resolver = getActivity().getContentResolver();
ButtonPreference iconBadgingPref =
@@ -138,9 +145,14 @@ public class SettingsActivity extends Activity
getPreferenceScreen().removePreference(iconBadgingPref);
} else {
// Listen to system notification badge settings while this UI is active.
mIconBadgingObserver = new IconBadgingObserver(
iconBadgingPref, resolver, getFragmentManager());
mIconBadgingObserver.register(NOTIFICATION_BADGING, NOTIFICATION_ENABLED_LISTENERS);
mIconBadgingObserver = newNotificationSettingsObserver(
getActivity(), new IconBadgingObserver(iconBadgingPref, resolver));
mIconBadgingObserver.register();
// Also listen if notification permission changes
mIconBadgingObserver.getResolver().registerContentObserver(
Settings.Secure.getUriFor(NOTIFICATION_ENABLED_LISTENERS), false,
mIconBadgingObserver);
mIconBadgingObserver.dispatchOnChange();
}
Preference iconShapeOverride = findPreference(IconShapeOverride.KEY_PREFERENCE);
@@ -246,22 +258,18 @@ public class SettingsActivity extends Activity
* Content observer which listens for system badging setting changes,
* and updates the launcher badging setting subtext accordingly.
*/
private static class IconBadgingObserver extends SettingsObserver.Secure {
private static class IconBadgingObserver implements SecureSettingsObserver.OnChangeListener {
private final ButtonPreference mBadgingPref;
private final ContentResolver mResolver;
private final FragmentManager mFragmentManager;
public IconBadgingObserver(ButtonPreference badgingPref, ContentResolver resolver,
FragmentManager fragmentManager) {
super(resolver);
public IconBadgingObserver(ButtonPreference badgingPref, ContentResolver resolver) {
mBadgingPref = badgingPref;
mResolver = resolver;
mFragmentManager = fragmentManager;
}
@Override
public void onSettingChanged(boolean enabled) {
public void onSettingsChanged(boolean enabled) {
int summary = enabled ? R.string.icon_badging_desc_on : R.string.icon_badging_desc_off;
boolean serviceEnabled = true;
+8 -10
View File
@@ -52,6 +52,7 @@ import android.view.View;
import android.view.animation.Interpolator;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.IntArray;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
@@ -136,9 +137,13 @@ public final class Utilities {
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
public static final boolean IS_RUNNING_IN_TEST_HARNESS =
public static boolean IS_RUNNING_IN_TEST_HARNESS =
ActivityManager.isRunningInTestHarness();
public static void enableRunningInTestHarnessForTests() {
IS_RUNNING_IN_TEST_HARNESS = true;
}
public static boolean isPropertyEnabled(String propertyName) {
return Log.isLoggable(propertyName, Log.VERBOSE);
}
@@ -452,8 +457,8 @@ public final class Utilities {
size, metrics));
}
public static String createDbSelectionQuery(String columnName, Iterable<?> values) {
return String.format(Locale.ENGLISH, "%s IN (%s)", columnName, TextUtils.join(", ", values));
public static String createDbSelectionQuery(String columnName, IntArray values) {
return String.format(Locale.ENGLISH, "%s IN (%s)", columnName, values.toConcatString());
}
public static boolean isBootCompleted() {
@@ -510,13 +515,6 @@ public final class Utilities {
return spanned;
}
/**
* Replacement for Long.compare() which was added in API level 19.
*/
public static int longCompare(long lhs, long rhs) {
return lhs < rhs ? -1 : (lhs == rhs ? 0 : 1);
}
public static SharedPreferences getPrefs(Context context) {
return context.getSharedPreferences(
LauncherFiles.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE);
+43 -41
View File
@@ -28,7 +28,6 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.SuppressLint;
@@ -85,8 +84,10 @@ import com.android.launcher3.touch.WorkspaceTouchListener;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LongArrayMap;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.WallpaperOffsetInterpolator;
@@ -130,17 +131,17 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
private static final boolean MAP_RECURSE = true;
// The screen id used for the empty screen always present to the right.
public static final long EXTRA_EMPTY_SCREEN_ID = -201;
public static final int EXTRA_EMPTY_SCREEN_ID = -201;
// The is the first screen. It is always present, even if its empty.
public static final long FIRST_SCREEN_ID = 0;
public static final int FIRST_SCREEN_ID = 0;
private LayoutTransition mLayoutTransition;
@Thunk final WallpaperManager mWallpaperManager;
private ShortcutAndWidgetContainer mDragSourceInternal;
@Thunk final LongArrayMap<CellLayout> mWorkspaceScreens = new LongArrayMap<>();
@Thunk final ArrayList<Long> mScreenOrder = new ArrayList<>();
@Thunk final IntSparseArrayMap<CellLayout> mWorkspaceScreens = new IntSparseArrayMap<>();
@Thunk final IntArray mScreenOrder = new IntArray();
@Thunk Runnable mRemoveEmptyScreenRunnable;
@Thunk boolean mDeferRemoveExtraEmptyScreen = false;
@@ -227,7 +228,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
@Thunk int mLastReorderY = -1;
private SparseArray<Parcelable> mSavedStates;
private final ArrayList<Integer> mRestoredPages = new ArrayList<>();
private final IntArray mRestoredPages = new IntArray();
private float mCurrentScale;
private float mTransitionProgress;
@@ -479,7 +480,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
* @param qsb an existing qsb to recycle or null.
*/
public void bindAndInitFirstWorkspaceScreen(View qsb) {
if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
if (!FeatureFlags.QSB_ON_FIRST_SCREEN.get()) {
return;
}
// Add the first page
@@ -523,7 +524,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
enableLayoutTransitions();
}
public void insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) {
public void insertNewWorkspaceScreenBeforeEmptyScreen(int screenId) {
// Find the index to insert this view into. If the empty screen exists, then
// insert it before that.
int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
@@ -533,11 +534,11 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
insertNewWorkspaceScreen(screenId, insertIndex);
}
public void insertNewWorkspaceScreen(long screenId) {
public void insertNewWorkspaceScreen(int screenId) {
insertNewWorkspaceScreen(screenId, getChildCount());
}
public CellLayout insertNewWorkspaceScreen(long screenId, int insertIndex) {
public CellLayout insertNewWorkspaceScreen(int screenId, int insertIndex) {
if (mWorkspaceScreens.containsKey(screenId)) {
throw new RuntimeException("Screen id " + screenId + " already exists!");
}
@@ -604,7 +605,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
}
if (hasExtraEmptyScreen() || mScreenOrder.size() == 0) return;
long finalScreenId = mScreenOrder.get(mScreenOrder.size() - 1);
int finalScreenId = mScreenOrder.get(mScreenOrder.size() - 1);
CellLayout finalScreen = mWorkspaceScreens.get(finalScreenId);
@@ -612,7 +613,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
if (finalScreen.getShortcutsAndWidgets().getChildCount() == 0 &&
!finalScreen.isDropPending()) {
mWorkspaceScreens.remove(finalScreenId);
mScreenOrder.remove(finalScreenId);
mScreenOrder.removeValue(finalScreenId);
// if this is the last screen, convert it to the empty screen
mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, finalScreen);
@@ -678,7 +679,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
public void run() {
if (hasExtraEmptyScreen()) {
mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
mScreenOrder.removeValue(EXTRA_EMPTY_SCREEN_ID);
removeView(cl);
if (stripEmptyScreens) {
stripEmptyScreens();
@@ -710,7 +711,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && getChildCount() > 1;
}
public long commitExtraEmptyScreen() {
public int commitExtraEmptyScreen() {
if (mLauncher.isWorkspaceLoading()) {
// Invalid and dangerous operation if workspace is loading
return -1;
@@ -718,11 +719,11 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
mScreenOrder.removeValue(EXTRA_EMPTY_SCREEN_ID);
long newId = LauncherSettings.Settings.call(getContext().getContentResolver(),
int newId = LauncherSettings.Settings.call(getContext().getContentResolver(),
LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
.getLong(LauncherSettings.Settings.EXTRA_VALUE);
.getInt(LauncherSettings.Settings.EXTRA_VALUE);
mWorkspaceScreens.put(newId, cl);
mScreenOrder.add(newId);
@@ -732,11 +733,11 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
return newId;
}
public CellLayout getScreenWithId(long screenId) {
public CellLayout getScreenWithId(int screenId) {
return mWorkspaceScreens.get(screenId);
}
public long getIdForScreen(CellLayout layout) {
public int getIdForScreen(CellLayout layout) {
int index = mWorkspaceScreens.indexOfValue(layout);
if (index != -1) {
return mWorkspaceScreens.keyAt(index);
@@ -744,18 +745,18 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
return -1;
}
public int getPageIndexForScreenId(long screenId) {
public int getPageIndexForScreenId(int screenId) {
return indexOfChild(mWorkspaceScreens.get(screenId));
}
public long getScreenIdForPageIndex(int index) {
public int getScreenIdForPageIndex(int index) {
if (0 <= index && index < mScreenOrder.size()) {
return mScreenOrder.get(index);
}
return -1;
}
public ArrayList<Long> getScreenOrder() {
public IntArray getScreenOrder() {
return mScreenOrder;
}
@@ -772,13 +773,13 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
}
int currentPage = getNextPage();
ArrayList<Long> removeScreens = new ArrayList<>();
IntArray removeScreens = new IntArray();
int total = mWorkspaceScreens.size();
for (int i = 0; i < total; i++) {
long id = mWorkspaceScreens.keyAt(i);
int id = mWorkspaceScreens.keyAt(i);
CellLayout cl = mWorkspaceScreens.valueAt(i);
// FIRST_SCREEN_ID can never be removed.
if ((!FeatureFlags.QSB_ON_FIRST_SCREEN || id > FIRST_SCREEN_ID)
if ((!FeatureFlags.QSB_ON_FIRST_SCREEN.get() || id > FIRST_SCREEN_ID)
&& cl.getShortcutsAndWidgets().getChildCount() == 0) {
removeScreens.add(id);
}
@@ -791,10 +792,11 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
int minScreens = 1;
int pageShift = 0;
for (Long id: removeScreens) {
for (int i = 0; i < removeScreens.size(); i++) {
int id = removeScreens.get(i);
CellLayout cl = mWorkspaceScreens.get(id);
mWorkspaceScreens.remove(id);
mScreenOrder.remove(id);
mScreenOrder.removeValue(id);
if (getChildCount() > minScreens) {
if (indexOfChild(cl) < currentPage) {
@@ -843,7 +845,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
/**
* Adds the specified child in the specified screen based on the {@param info}
* See {@link #addInScreen(View, long, long, int, int, int, int)}.
* See {@link #addInScreen(View, int, int, int, int, int, int)}.
*/
public void addInScreen(View child, ItemInfo info) {
addInScreen(child, info.container, info.screenId, info.cellX, info.cellY,
@@ -861,7 +863,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
* @param spanX The number of cells spanned horizontally by the child.
* @param spanY The number of cells spanned vertically by the child.
*/
private void addInScreen(View child, long container, long screenId, int x, int y,
private void addInScreen(View child, int container, int screenId, int x, int y,
int spanX, int spanY) {
if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
if (getScreenWithId(screenId) == null) {
@@ -1675,7 +1677,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
}
}
long screenId = getIdForScreen(dropTargetLayout);
int screenId = getIdForScreen(dropTargetLayout);
if (screenId == EXTRA_EMPTY_SCREEN_ID) {
commitExtraEmptyScreen();
}
@@ -1740,7 +1742,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
return false;
}
boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
boolean createUserFolderIfNecessary(View newView, int container, CellLayout target,
int[] targetCell, float distance, boolean external, DragView dragView) {
if (distance > mMaxDistanceForFolderCreation) return false;
View v = target.getChildAt(targetCell[0], targetCell[1]);
@@ -1754,7 +1756,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
mCreateUserFolderOnDrop = false;
final long screenId = getIdForScreen(target);
final int screenId = getIdForScreen(target);
boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
@@ -1853,10 +1855,10 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
// Move internally
boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
long container = hasMovedIntoHotseat ?
int container = hasMovedIntoHotseat ?
LauncherSettings.Favorites.CONTAINER_HOTSEAT :
LauncherSettings.Favorites.CONTAINER_DESKTOP;
long screenId = (mTargetCell[0] < 0) ?
int screenId = (mTargetCell[0] < 0) ?
mDragInfo.screenId : getIdForScreen(dropTargetLayout);
int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
@@ -2530,10 +2532,10 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
spanY = mDragInfo.spanY;
}
final long container = mLauncher.isHotseatLayout(cellLayout) ?
final int container = mLauncher.isHotseatLayout(cellLayout) ?
LauncherSettings.Favorites.CONTAINER_HOTSEAT :
LauncherSettings.Favorites.CONTAINER_DESKTOP;
final long screenId = getIdForScreen(cellLayout);
final int screenId = getIdForScreen(cellLayout);
if (!mLauncher.isHotseatLayout(cellLayout)
&& screenId != getScreenIdForPageIndex(mCurrentPage)
&& !mLauncher.isInState(SPRING_LOADED)) {
@@ -3006,7 +3008,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
return childrenLayouts;
}
public View getHomescreenIconByItemId(final long id) {
public View getHomescreenIconByItemId(final int id) {
return getFirstMatch(new ItemOperator() {
@Override
@@ -3065,7 +3067,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
for (final CellLayout layoutParent: cellLayouts) {
final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
LongArrayMap<View> idToViewMap = new LongArrayMap<>();
IntSparseArrayMap<View> idToViewMap = new IntSparseArrayMap<>();
ArrayList<ItemInfo> items = new ArrayList<>();
for (int j = 0; j < layout.getChildCount(); j++) {
final View view = layout.getChildAt(j);
@@ -3153,7 +3155,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
void updateShortcuts(ArrayList<ShortcutInfo> shortcuts) {
int total = shortcuts.size();
final HashSet<ShortcutInfo> updates = new HashSet<>(total);
final HashSet<Long> folderIds = new HashSet<>();
final IntSet folderIds = new IntSet();
for (int i = 0; i < total; i++) {
ShortcutInfo s = shortcuts.get(i);
@@ -3193,7 +3195,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
public void updateIconBadges(final Set<PackageUserKey> updatedBadges) {
final PackageUserKey packageUserKey = new PackageUserKey(null, null);
final HashSet<Long> folderIds = new HashSet<>();
final IntSet folderIds = new IntSet();
mapOverItems(MAP_RECURSE, new ItemOperator() {
@Override
public boolean evaluate(ItemInfo info, View v) {
@@ -39,6 +39,7 @@ import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
@@ -159,7 +160,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme
beginAccessibleDrag(host, item);
} else if (action == ADD_TO_WORKSPACE) {
final int[] coordinates = new int[2];
final long screenId = findSpaceOnWorkspace(item, coordinates);
final int screenId = findSpaceOnWorkspace(item, coordinates);
mLauncher.getStateManager().goToState(NORMAL, true, new Runnable() {
@Override
@@ -191,7 +192,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme
folder.getInfo().remove(info, false);
final int[] coordinates = new int[2];
final long screenId = findSpaceOnWorkspace(item, coordinates);
final int screenId = findSpaceOnWorkspace(item, coordinates);
mLauncher.getModelWriter().moveItemInDatabase(info,
LauncherSettings.Favorites.CONTAINER_DESKTOP,
screenId, coordinates[0], coordinates[1]);
@@ -210,7 +211,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme
});
} else if (action == RESIZE) {
final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) item;
final ArrayList<Integer> actions = getSupportedResizeActions(host, info);
final IntArray actions = getSupportedResizeActions(host, info);
CharSequence[] labels = new CharSequence[actions.size()];
for (int i = 0; i < actions.size(); i++) {
labels[i] = mLauncher.getText(actions.get(i));
@@ -242,8 +243,8 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme
return false;
}
private ArrayList<Integer> getSupportedResizeActions(View host, LauncherAppWidgetInfo info) {
ArrayList<Integer> actions = new ArrayList<>();
private IntArray getSupportedResizeActions(View host, LauncherAppWidgetInfo info) {
IntArray actions = new IntArray();
AppWidgetProviderInfo providerInfo = ((LauncherAppWidgetHostView) host).getAppWidgetInfo();
if (providerInfo == null) {
@@ -392,10 +393,10 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme
/**
* Find empty space on the workspace and returns the screenId.
*/
protected long findSpaceOnWorkspace(ItemInfo info, int[] outCoordinates) {
protected int findSpaceOnWorkspace(ItemInfo info, int[] outCoordinates) {
Workspace workspace = mLauncher.getWorkspace();
ArrayList<Long> workspaceScreens = workspace.getScreenOrder();
long screenId;
IntArray workspaceScreens = workspace.getScreenOrder();
int screenId;
// First check if there is space on the current screen.
int screenIndex = workspace.getCurrentPage();
@@ -66,7 +66,7 @@ public class ShortcutMenuAccessibilityDelegate extends LauncherAccessibilityDele
}
final ShortcutInfo info = ((DeepShortcutView) host.getParent()).getFinalInfo();
final int[] coordinates = new int[2];
final long screenId = findSpaceOnWorkspace(item, coordinates);
final int screenId = findSpaceOnWorkspace(item, coordinates);
Runnable onComplete = new Runnable() {
@Override
public void run() {
@@ -25,16 +25,22 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.LinearLayout;
import com.android.launcher3.R;
import com.android.launcher3.anim.PropertySetter;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.R;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.systemui.plugins.AllAppsRow;
import com.android.systemui.plugins.PluginListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class FloatingHeaderView extends LinearLayout implements
ValueAnimator.AnimatorUpdateListener {
ValueAnimator.AnimatorUpdateListener, PluginListener<AllAppsRow> {
private final Rect mClip = new Rect(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
private final ValueAnimator mAnimator = ValueAnimator.ofInt(0, 0);
@@ -64,6 +70,9 @@ public class FloatingHeaderView extends LinearLayout implements
private AllAppsRecyclerView mMainRV;
private AllAppsRecyclerView mWorkRV;
private AllAppsRecyclerView mCurrentRV;
protected final Map<AllAppsRow, View> mPluginRows;
// Contains just the values of the above map so we can iterate without extracting a new list.
protected final List<View> mPluginRowViews;
private ViewGroup mParent;
private boolean mHeaderCollapsed;
private int mSnappedScrolledY;
@@ -82,6 +91,8 @@ public class FloatingHeaderView extends LinearLayout implements
public FloatingHeaderView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPluginRows = new HashMap<>();
mPluginRowViews = new ArrayList<>();
}
@Override
@@ -90,6 +101,38 @@ public class FloatingHeaderView extends LinearLayout implements
mTabLayout = findViewById(R.id.tabs);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
PluginManagerWrapper.INSTANCE.get(getContext()).addPluginListener(this,
AllAppsRow.class, true /* allowMultiple */);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
PluginManagerWrapper.INSTANCE.get(getContext()).removePluginListener(this);
}
@Override
public void onPluginConnected(AllAppsRow allAppsRowPlugin, Context context) {
mPluginRows.put(allAppsRowPlugin, null);
setupPluginRows();
allAppsRowPlugin.setOnHeightUpdatedListener(this::onPluginRowHeightUpdated);
}
protected void onPluginRowHeightUpdated() {
}
@Override
public void onPluginDisconnected(AllAppsRow plugin) {
View pluginRowView = mPluginRows.get(plugin);
removeView(pluginRowView);
mPluginRows.remove(plugin);
mPluginRowViews.remove(pluginRowView);
onPluginRowHeightUpdated();
}
public void setup(AllAppsContainerView.AdapterHolder[] mAH, boolean tabsHidden) {
mTabsHidden = tabsHidden;
mTabLayout.setVisibility(tabsHidden ? View.GONE : View.VISIBLE);
@@ -97,9 +140,24 @@ public class FloatingHeaderView extends LinearLayout implements
mWorkRV = setupRV(mWorkRV, mAH[AllAppsContainerView.AdapterHolder.WORK].recyclerView);
mParent = (ViewGroup) mMainRV.getParent();
setMainActive(mMainRVActive || mWorkRV == null);
setupPluginRows();
reset(false);
}
private void setupPluginRows() {
for (Map.Entry<AllAppsRow, View> rowPluginEntry : mPluginRows.entrySet()) {
if (rowPluginEntry.getValue() == null) {
View pluginRow = rowPluginEntry.getKey().setup(this);
addView(pluginRow, indexOfChild(mTabLayout));
rowPluginEntry.setValue(pluginRow);
mPluginRowViews.add(pluginRow);
}
}
for (View plugin : mPluginRowViews) {
plugin.setVisibility(mHeaderCollapsed ? GONE : VISIBLE);
}
}
private AllAppsRecyclerView setupRV(AllAppsRecyclerView old, AllAppsRecyclerView updated) {
if (old != updated && updated != null ) {
updated.addOnScrollListener(mOnScrollListener);
@@ -17,32 +17,29 @@
package com.android.launcher3.compat;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
import com.android.launcher3.util.LongArrayMap;
import android.util.LongSparseArray;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class UserManagerCompatVL extends UserManagerCompat {
private static final String USER_CREATION_TIME_KEY = "user_creation_time_";
protected final UserManager mUserManager;
private final PackageManager mPm;
private final Context mContext;
protected LongArrayMap<UserHandle> mUsers;
// Create a separate reverse map as LongArrayMap.indexOfValue checks if objects are same
protected LongSparseArray<UserHandle> mUsers;
// Create a separate reverse map as LongSparseArray.indexOfValue checks if objects are same
// and not {@link Object#equals}
protected ArrayMap<UserHandle, Long> mUserToSerialMap;
UserManagerCompatVL(Context context) {
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
mPm = context.getPackageManager();
mContext = context;
}
@Override
@@ -94,7 +91,7 @@ public class UserManagerCompatVL extends UserManagerCompat {
@Override
public void enableAndResetCache() {
synchronized (this) {
mUsers = new LongArrayMap<>();
mUsers = new LongSparseArray<>();
mUserToSerialMap = new ArrayMap<>();
List<UserHandle> users = mUserManager.getUserProfiles();
if (users != null) {
+143 -9
View File
@@ -16,17 +16,44 @@
package com.android.launcher3.config;
import static androidx.core.util.Preconditions.checkNotNull;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.GuardedBy;
import androidx.annotation.Keep;
import com.android.launcher3.Utilities;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* Defines a set of flags used to control various launcher behaviors.
*
* All the flags should be defined here with appropriate default values. To override a value,
* redefine it in {@link FeatureFlags}.
* <p>All the flags should be defined here with appropriate default values.
*
* This class is kept package-private to prevent direct access.
* <p>This class is kept package-private to prevent direct access.
*/
@Keep
abstract class BaseFlags {
BaseFlags() {}
private static final Object sLock = new Object();
@GuardedBy("sLock")
private static final List<TogglableFlag> sFlags = new ArrayList<>();
static final String FLAGS_PREF_NAME = "featureFlags";
BaseFlags() {
throw new UnsupportedOperationException("Don't instantiate BaseFlags");
}
public static boolean showFlagTogglerUi() {
return Utilities.IS_DEBUG_DEVICE;
}
public static final boolean IS_DOGFOOD_BUILD = false;
public static final String AUTHORITY = "com.android.launcher3.settings".intern();
@@ -34,15 +61,16 @@ abstract class BaseFlags {
// When enabled the promise icon is visible in all apps while installation an app.
public static final boolean LAUNCHER3_PROMISE_APPS_IN_ALL_APPS = false;
// Feature flag to enable moving the QSB on the 0th screen of the workspace.
public static final boolean QSB_ON_FIRST_SCREEN = true;
public static final TogglableFlag QSB_ON_FIRST_SCREEN = new TogglableFlag("QSB_ON_FIRST_SCREEN",
true,
"Enable moving the QSB on the 0th screen of the workspace");
public static final TogglableFlag EXAMPLE_FLAG = new TogglableFlag("EXAMPLE_FLAG", true,
"An example flag that doesn't do anything. Useful for testing");
//Feature flag to enable pulling down navigation shade from workspace.
public static final boolean PULL_DOWN_STATUS_BAR = true;
// When enabled the all-apps icon is not added to the hotseat.
public static final boolean NO_ALL_APPS_ICON = true;
// When true, custom widgets are loaded using CustomWidgetParser.
public static final boolean ENABLE_CUSTOM_WIDGETS = false;
@@ -55,4 +83,110 @@ abstract class BaseFlags {
// When true, overview shows screenshots in the orientation they were taken rather than
// trying to make them fit the orientation the device is in.
public static final boolean OVERVIEW_USE_SCREENSHOT_ORIENTATION = true;
public static void initialize(Context context) {
// Avoid the disk read for builds without the flags UI.
if (showFlagTogglerUi()) {
SharedPreferences sharedPreferences =
context.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE);
synchronized (sLock) {
for (TogglableFlag flag : sFlags) {
flag.currentValue = sharedPreferences.getBoolean(flag.key, flag.defaultValue);
}
}
} else {
synchronized (sLock) {
for (TogglableFlag flag : sFlags) {
flag.currentValue = flag.defaultValue;
}
}
}
}
static List<TogglableFlag> getTogglableFlags() {
// By Java Language Spec 12.4.2
// https://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4.2, the
// TogglableFlag instances on BaseFlags will be created before those on the FeatureFlags
// subclass. This code handles flags that are redeclared in FeatureFlags, ensuring the
// FeatureFlags one takes priority.
SortedMap<String, TogglableFlag> flagsByKey = new TreeMap<>();
synchronized (sLock) {
for (TogglableFlag flag : sFlags) {
flagsByKey.put(flag.key, flag);
}
}
return new ArrayList<>(flagsByKey.values());
}
public static final class TogglableFlag {
private final String key;
private final boolean defaultValue;
private final String description;
private boolean currentValue;
TogglableFlag(
String key,
boolean defaultValue,
String description) {
this.key = checkNotNull(key);
this.defaultValue = defaultValue;
this.description = checkNotNull(description);
synchronized (sLock) {
sFlags.add(this);
}
}
String getKey() {
return key;
}
boolean getDefaultValue() {
return defaultValue;
}
/** Returns the value of the flag at process start, including any overrides present. */
public boolean get() {
return currentValue;
}
String getDescription() {
return description;
}
@Override
public String toString() {
return "TogglableFlag{"
+ "key=" + key + ", "
+ "defaultValue=" + defaultValue + ", "
+ "description=" + description
+ "}";
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o instanceof TogglableFlag) {
TogglableFlag that = (TogglableFlag) o;
return (this.key.equals(that.getKey()))
&& (this.defaultValue == that.getDefaultValue())
&& (this.description.equals(that.getDescription()));
}
return false;
}
@Override
public int hashCode() {
int h$ = 1;
h$ *= 1000003;
h$ ^= key.hashCode();
h$ *= 1000003;
h$ ^= defaultValue ? 1231 : 1237;
h$ *= 1000003;
h$ ^= description.hashCode();
return h$;
}
}
}
@@ -0,0 +1,120 @@
/*
* Copyright (C) 2018 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.config;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Process;
import android.preference.PreferenceDataStore;
import android.preference.PreferenceFragment;
import android.preference.SwitchPreference;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.Toast;
import com.android.launcher3.R;
import com.android.launcher3.config.BaseFlags.TogglableFlag;
/**
* Dev-build only UI allowing developers to toggle flag settings. See {@link FeatureFlags}.
*/
public final class FlagTogglerPreferenceFragment extends PreferenceFragment {
private static final String TAG = "FlagTogglerPrefFrag";
private SharedPreferences mSharedPreferences;
private MenuItem saveButton;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.flag_preferences);
mSharedPreferences = getContext().getSharedPreferences(
FeatureFlags.FLAGS_PREF_NAME, Context.MODE_PRIVATE);
// For flag overrides we only want to store when the engineer chose to override the
// flag with a different value than the default. That way, when we flip flags in
// future, engineers will pick up the new value immediately. To accomplish this, we use a
// custom preference data store.
getPreferenceManager().setPreferenceDataStore(new PreferenceDataStore() {
@Override
public void putBoolean(String key, boolean value) {
for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
if (flag.getKey().equals(key)) {
if (value == flag.getDefaultValue()) {
mSharedPreferences.edit().remove(key).apply();
} else {
mSharedPreferences.edit().putBoolean(key, value).apply();
}
}
}
}
});
for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
SwitchPreference switchPreference = new SwitchPreference(getContext());
switchPreference.setKey(flag.getKey());
switchPreference.setDefaultValue(flag.getDefaultValue());
switchPreference.setChecked(getFlagStateFromSharedPrefs(flag));
switchPreference.setTitle(flag.getKey());
switchPreference.setSummaryOn(flag.getDefaultValue() ? "" : "overridden");
switchPreference.setSummaryOff(flag.getDefaultValue() ? "overridden" : "");
getPreferenceScreen().addPreference(switchPreference);
}
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
saveButton = menu.add("Apply");
saveButton.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item == saveButton) {
mSharedPreferences.edit().commit();
Log.e(TAG,
"Killing launcher process " + Process.myPid() + " to apply new flag values");
System.exit(0);
}
return super.onOptionsItemSelected(item);
}
@Override
public void onStop() {
boolean anyChanged = false;
for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
anyChanged = anyChanged ||
getFlagStateFromSharedPrefs(flag) != flag.get();
}
if (anyChanged) {
Toast.makeText(
getContext(),
"Flag won't be applied until you restart launcher",
Toast.LENGTH_LONG).show();
}
super.onStop();
}
private boolean getFlagStateFromSharedPrefs(TogglableFlag flag) {
return mSharedPreferences.getBoolean(flag.getKey(), flag.getDefaultValue());
}
}
@@ -65,7 +65,7 @@ public class FolderAdaptiveIcon extends AdaptiveIconDrawable {
}
public static FolderAdaptiveIcon createFolderAdaptiveIcon(
Launcher launcher, long folderId, Point dragViewSize) {
Launcher launcher, int folderId, Point dragViewSize) {
Preconditions.assertNonUiThread();
int margin = launcher.getResources()
.getDimensionPixelSize(R.dimen.blur_size_medium_outline);
@@ -0,0 +1,303 @@
package com.android.launcher3.icons;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
import com.android.launcher3.R;
import static android.graphics.Paint.DITHER_FLAG;
import static android.graphics.Paint.FILTER_BITMAP_FLAG;
import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR;
/**
* This class will be moved to androidx library. There shouldn't be any dependency outside
* this package.
*/
public class BaseIconFactory {
private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;
public static final boolean ATLEAST_OREO = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
private final Rect mOldBounds = new Rect();
private final Context mContext;
private final Canvas mCanvas;
private final PackageManager mPm;
private final ColorExtractor mColorExtractor;
private boolean mDisableColorExtractor;
private int mFillResIconDpi;
private int mIconBitmapSize;
private IconNormalizer mNormalizer;
private ShadowGenerator mShadowGenerator;
private Drawable mWrapperIcon;
private int mWrapperBackgroundColor;
protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
mContext = context.getApplicationContext();
mFillResIconDpi = fillResIconDpi;
mIconBitmapSize = iconBitmapSize;
mPm = mContext.getPackageManager();
mColorExtractor = new ColorExtractor();
mCanvas = new Canvas();
mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
}
protected void clear() {
mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
mDisableColorExtractor = false;
}
public ShadowGenerator getShadowGenerator() {
if (mShadowGenerator == null) {
mShadowGenerator = new ShadowGenerator(mContext);
}
return mShadowGenerator;
}
public IconNormalizer getNormalizer() {
if (mNormalizer == null) {
mNormalizer = new IconNormalizer(mContext);
}
return mNormalizer;
}
public BitmapInfo createIconBitmap(Intent.ShortcutIconResource iconRes) {
try {
Resources resources = mPm.getResourcesForApplication(iconRes.packageName);
if (resources != null) {
final int id = resources.getIdentifier(iconRes.resourceName, null, null);
// do not stamp old legacy shortcuts as the app may have already forgotten about it
return createBadgedIconBitmap(
resources.getDrawableForDensity(id, mFillResIconDpi),
Process.myUserHandle() /* only available on primary user */,
false /* do not apply legacy treatment */);
}
} catch (Exception e) {
// Icon not found.
}
return null;
}
public BitmapInfo createIconBitmap(Bitmap icon) {
if (mIconBitmapSize == icon.getWidth() && mIconBitmapSize == icon.getHeight()) {
return BitmapInfo.fromBitmap(icon);
}
return BitmapInfo.fromBitmap(
createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f));
}
public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
boolean shrinkNonAdaptiveIcons) {
return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, false, null);
}
public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
boolean shrinkNonAdaptiveIcons, boolean isInstantApp) {
return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, isInstantApp, null);
}
/**
* Creates bitmap using the source drawable and various parameters.
* The bitmap is visually normalized with other icons and has enough spacing to add shadow.
*
* @param icon source of the icon
* @param user info can be used for a badge
* @param shrinkNonAdaptiveIcons {@code true} if non adaptive icons should be treated
* @param isInstantApp info can be used for a badge
* @param scale returns the scale result from normalization
* @return a bitmap suitable for disaplaying as an icon at various system UIs.
*/
public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
boolean shrinkNonAdaptiveIcons, boolean isInstantApp, float[] scale) {
if (scale == null) {
scale = new float[1];
}
icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, null, scale);
Bitmap bitmap = createIconBitmap(icon, scale[0]);
if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
mCanvas.setBitmap(bitmap);
getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);
mCanvas.setBitmap(null);
}
final Bitmap result;
if (user != null && !Process.myUserHandle().equals(user)) {
BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap);
Drawable badged = mPm.getUserBadgedIcon(drawable, user);
if (badged instanceof BitmapDrawable) {
result = ((BitmapDrawable) badged).getBitmap();
} else {
result = createIconBitmap(badged, 1f);
}
} else if (isInstantApp) {
badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge));
result = bitmap;
} else {
result = bitmap;
}
return BitmapInfo.fromBitmap(result, mDisableColorExtractor ? null : mColorExtractor);
}
public Bitmap createScaledBitmapWithoutShadow(Drawable icon, boolean shrinkNonAdaptiveIcons) {
RectF iconBounds = new RectF();
float[] scale = new float[1];
icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, iconBounds, scale);
return createIconBitmap(icon,
Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)));
}
/**
* Sets the background color used for wrapped adaptive icon
*/
public void setWrapperBackgroundColor(int color) {
mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color;
}
/**
* Disables the dominant color extraction for all icons loaded.
*/
public void disableColorExtraction() {
mDisableColorExtractor = true;
}
private Drawable normalizeAndWrapToAdaptiveIcon(Drawable icon, boolean shrinkNonAdaptiveIcons,
RectF outIconBounds, float[] outScale) {
float scale = 1f;
if (shrinkNonAdaptiveIcons) {
boolean[] outShape = new boolean[1];
if (mWrapperIcon == null) {
mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper)
.mutate();
}
AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
dr.setBounds(0, 0, 1, 1);
scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
if (ATLEAST_OREO && !outShape[0] && !(icon instanceof AdaptiveIconDrawable)) {
FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
fsd.setDrawable(icon);
fsd.setScale(scale);
icon = dr;
scale = getNormalizer().getScale(icon, outIconBounds, null, null);
((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
}
} else {
scale = getNormalizer().getScale(icon, outIconBounds, null, null);
}
outScale[0] = scale;
return icon;
}
/**
* Adds the {@param badge} on top of {@param target} using the badge dimensions.
*/
public void badgeWithDrawable(Bitmap target, Drawable badge) {
mCanvas.setBitmap(target);
badgeWithDrawable(mCanvas, badge);
mCanvas.setBitmap(null);
}
/**
* Adds the {@param badge} on top of {@param target} using the badge dimensions.
*/
public void badgeWithDrawable(Canvas target, Drawable badge) {
int badgeSize = mContext.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize,
mIconBitmapSize, mIconBitmapSize);
badge.draw(target);
}
/**
* @param scale the scale to apply before drawing {@param icon} on the canvas
*/
private Bitmap createIconBitmap(Drawable icon, float scale) {
Bitmap bitmap = Bitmap.createBitmap(mIconBitmapSize, mIconBitmapSize,
Bitmap.Config.ARGB_8888);
mCanvas.setBitmap(bitmap);
mOldBounds.set(icon.getBounds());
if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
int offset = Math.max((int) Math.ceil(BLUR_FACTOR * mIconBitmapSize),
Math.round(mIconBitmapSize * (1 - scale) / 2 ));
icon.setBounds(offset, offset, mIconBitmapSize - offset, mIconBitmapSize - offset);
icon.draw(mCanvas);
} else {
if (icon instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
Bitmap b = bitmapDrawable.getBitmap();
if (bitmap != null && b.getDensity() == Bitmap.DENSITY_NONE) {
bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics());
}
}
int width = mIconBitmapSize;
int height = mIconBitmapSize;
int intrinsicWidth = icon.getIntrinsicWidth();
int intrinsicHeight = icon.getIntrinsicHeight();
if (intrinsicWidth > 0 && intrinsicHeight > 0) {
// Scale the icon proportionally to the icon dimensions
final float ratio = (float) intrinsicWidth / intrinsicHeight;
if (intrinsicWidth > intrinsicHeight) {
height = (int) (width / ratio);
} else if (intrinsicHeight > intrinsicWidth) {
width = (int) (height * ratio);
}
}
final int left = (mIconBitmapSize - width) / 2;
final int top = (mIconBitmapSize - height) / 2;
icon.setBounds(left, top, left + width, top + height);
mCanvas.save();
mCanvas.scale(scale, scale, mIconBitmapSize / 2, mIconBitmapSize / 2);
icon.draw(mCanvas);
mCanvas.restore();
}
icon.setBounds(mOldBounds);
mCanvas.setBitmap(null);
return bitmap;
}
/**
* An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
* This allows the badging to be done based on the action bitmap size rather than
* the scaled bitmap size.
*/
private static class FixedSizeBitmapDrawable extends BitmapDrawable {
public FixedSizeBitmapDrawable(Bitmap bitmap) {
super(null, bitmap);
}
@Override
public int getIntrinsicHeight() {
return getBitmap().getWidth();
}
@Override
public int getIntrinsicWidth() {
return getBitmap().getWidth();
}
}
}
@@ -29,6 +29,7 @@ import android.util.SparseBooleanArray;
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.BaseIconCache.IconDB;
import com.android.launcher3.util.IntArray;
import java.util.ArrayList;
import java.util.Collections;
@@ -205,7 +206,7 @@ public class IconCacheUpdateHandler {
public void finish() {
// Commit all deletes
ArrayList<Integer> deleteIds = new ArrayList<>();
IntArray deleteIds = new IntArray();
int count = mItemsToDelete.size();
for (int i = 0; i < count; i++) {
if (mItemsToDelete.valueAt(i)) {
@@ -16,28 +16,11 @@
package com.android.launcher3.icons;
import static android.graphics.Paint.DITHER_FLAG;
import static android.graphics.Paint.FILTER_BITMAP_FLAG;
import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.Intent.ShortcutIconResource;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.PaintDrawable;
import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
@@ -48,7 +31,6 @@ import com.android.launcher3.graphics.BitmapRenderer;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.shortcuts.DeepShortcutManager;
@@ -59,14 +41,14 @@ import com.android.launcher3.util.Themes;
import androidx.annotation.Nullable;
/**
* Helper methods for generating various launcher icons
* Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
* that are threadsafe.
*/
public class LauncherIcons implements AutoCloseable {
public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;
public static final Object sPoolSync = new Object();
private static final Object sPoolSync = new Object();
private static LauncherIcons sPool;
private LauncherIcons next;
/**
* Return a new Message instance from the global pool. Allows us to
@@ -81,7 +63,8 @@ public class LauncherIcons implements AutoCloseable {
return m;
}
}
return new LauncherIcons(context);
InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
return new LauncherIcons(context, idp.fillResIconDpi, idp.iconBitmapSize);
}
/**
@@ -90,8 +73,7 @@ public class LauncherIcons implements AutoCloseable {
public void recycle() {
synchronized (sPoolSync) {
// Clear any temporary state variables
mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
mDisableColorExtractor = false;
clear();
next = sPool;
sPool = this;
@@ -103,269 +85,41 @@ public class LauncherIcons implements AutoCloseable {
recycle();
}
private final Rect mOldBounds = new Rect();
private final Context mContext;
private final Canvas mCanvas;
private final PackageManager mPm;
private final ColorExtractor mColorExtractor;
private boolean mDisableColorExtractor;
private final int mFillResIconDpi;
private final int mIconBitmapSize;
private IconNormalizer mNormalizer;
private ShadowGenerator mShadowGenerator;
private Drawable mWrapperIcon;
private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
// sometimes we store linked lists of these things
private LauncherIcons next;
private LauncherIcons(Context context) {
private LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize) {
super(context, fillResIconDpi, iconBitmapSize);
mContext = context.getApplicationContext();
mPm = mContext.getPackageManager();
mColorExtractor = new ColorExtractor();
InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
mFillResIconDpi = idp.fillResIconDpi;
mIconBitmapSize = idp.iconBitmapSize;
mCanvas = new Canvas();
mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
mFillResIconDpi = fillResIconDpi;
mIconBitmapSize = iconBitmapSize;
}
public ShadowGenerator getShadowGenerator() {
if (mShadowGenerator == null) {
mShadowGenerator = new ShadowGenerator(mContext);
}
return mShadowGenerator;
public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
int iconAppTargetSdk) {
return createBadgedIconBitmap(icon, user, iconAppTargetSdk, false);
}
public IconNormalizer getNormalizer() {
if (mNormalizer == null) {
mNormalizer = new IconNormalizer(mContext);
}
return mNormalizer;
}
/**
* Returns a bitmap suitable for the all apps view. If the package or the resource do not
* exist, it returns null.
*/
public BitmapInfo createIconBitmap(ShortcutIconResource iconRes) {
try {
Resources resources = mPm.getResourcesForApplication(iconRes.packageName);
if (resources != null) {
final int id = resources.getIdentifier(iconRes.resourceName, null, null);
// do not stamp old legacy shortcuts as the app may have already forgotten about it
return createBadgedIconBitmap(
resources.getDrawableForDensity(id, mFillResIconDpi),
Process.myUserHandle() /* only available on primary user */,
0 /* do not apply legacy treatment */);
}
} catch (Exception e) {
// Icon not found.
}
return null;
}
/**
* Returns a bitmap which is of the appropriate size to be displayed as an icon
*/
public BitmapInfo createIconBitmap(Bitmap icon) {
if (mIconBitmapSize == icon.getWidth() && mIconBitmapSize == icon.getHeight()) {
return BitmapInfo.fromBitmap(icon);
}
return BitmapInfo.fromBitmap(
createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f));
}
/**
* Returns a bitmap suitable for displaying as an icon at various launcher UIs like all apps
* view or workspace. The icon is badged for {@param user}.
* The bitmap is also visually normalized with other icons.
*/
public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk) {
return createBadgedIconBitmap(icon, user, iconAppTargetSdk, false, null);
}
public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk,
boolean isInstantApp) {
public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
int iconAppTargetSdk, boolean isInstantApp) {
return createBadgedIconBitmap(icon, user, iconAppTargetSdk, isInstantApp, null);
}
/**
* Returns a bitmap suitable for displaying as an icon at various launcher UIs like all apps
* view or workspace. The icon is badged for {@param user}.
* The bitmap is also visually normalized with other icons.
*/
public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk,
boolean isInstantApp, float[] scale) {
if (scale == null) {
scale = new float[1];
}
icon = normalizeAndWrapToAdaptiveIcon(icon, iconAppTargetSdk, null, scale);
Bitmap bitmap = createIconBitmap(icon, scale[0]);
if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
mCanvas.setBitmap(bitmap);
getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);
mCanvas.setBitmap(null);
}
final Bitmap result;
if (user != null && !Process.myUserHandle().equals(user)) {
BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap);
Drawable badged = mPm.getUserBadgedIcon(drawable, user);
if (badged instanceof BitmapDrawable) {
result = ((BitmapDrawable) badged).getBitmap();
} else {
result = createIconBitmap(badged, 1f);
}
} else if (isInstantApp) {
badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge));
result = bitmap;
} else {
result = bitmap;
}
return BitmapInfo.fromBitmap(result, mDisableColorExtractor ? null : mColorExtractor);
public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
int iconAppTargetSdk, boolean isInstantApp, float[] scale) {
boolean shrinkNonAdaptiveIcons = Utilities.ATLEAST_P ||
(Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O);
return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, isInstantApp, scale);
}
/**
* Creates a normalized bitmap suitable for the all apps view. The bitmap is also visually
* normalized with other icons and has enough spacing to add shadow.
*/
public Bitmap createScaledBitmapWithoutShadow(Drawable icon, int iconAppTargetSdk) {
RectF iconBounds = new RectF();
float[] scale = new float[1];
icon = normalizeAndWrapToAdaptiveIcon(icon, iconAppTargetSdk, iconBounds, scale);
return createIconBitmap(icon,
Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)));
boolean shrinkNonAdaptiveIcons = Utilities.ATLEAST_P ||
(Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O);
return createScaledBitmapWithoutShadow(icon, shrinkNonAdaptiveIcons);
}
/**
* Sets the background color used for wrapped adaptive icon
*/
public void setWrapperBackgroundColor(int color) {
mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color;
}
/**
* Disables the dominant color extraction for all icons loaded through this session (until
* this instance is recycled).
*/
public void disableColorExtraction() {
mDisableColorExtractor = true;
}
private Drawable normalizeAndWrapToAdaptiveIcon(Drawable icon, int iconAppTargetSdk,
RectF outIconBounds, float[] outScale) {
float scale = 1f;
if ((Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) ||
Utilities.ATLEAST_P) {
boolean[] outShape = new boolean[1];
if (mWrapperIcon == null) {
mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper)
.mutate();
}
AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
dr.setBounds(0, 0, 1, 1);
scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
if (Utilities.ATLEAST_OREO && !outShape[0] && !(icon instanceof AdaptiveIconDrawable)) {
FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
fsd.setDrawable(icon);
fsd.setScale(scale);
icon = dr;
scale = getNormalizer().getScale(icon, outIconBounds, null, null);
((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
}
} else {
scale = getNormalizer().getScale(icon, outIconBounds, null, null);
}
outScale[0] = scale;
return icon;
}
/**
* Adds the {@param badge} on top of {@param target} using the badge dimensions.
*/
public void badgeWithDrawable(Bitmap target, Drawable badge) {
mCanvas.setBitmap(target);
badgeWithDrawable(mCanvas, badge);
mCanvas.setBitmap(null);
}
/**
* Adds the {@param badge} on top of {@param target} using the badge dimensions.
*/
private void badgeWithDrawable(Canvas target, Drawable badge) {
int badgeSize = mContext.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize,
mIconBitmapSize, mIconBitmapSize);
badge.draw(target);
}
/**
* @param scale the scale to apply before drawing {@param icon} on the canvas
*/
private Bitmap createIconBitmap(Drawable icon, float scale) {
int width = mIconBitmapSize;
int height = mIconBitmapSize;
if (icon instanceof PaintDrawable) {
PaintDrawable painter = (PaintDrawable) icon;
painter.setIntrinsicWidth(width);
painter.setIntrinsicHeight(height);
} else if (icon instanceof BitmapDrawable) {
// Ensure the bitmap has a density.
BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
Bitmap bitmap = bitmapDrawable.getBitmap();
if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) {
bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics());
}
}
int sourceWidth = icon.getIntrinsicWidth();
int sourceHeight = icon.getIntrinsicHeight();
if (sourceWidth > 0 && sourceHeight > 0) {
// Scale the icon proportionally to the icon dimensions
final float ratio = (float) sourceWidth / sourceHeight;
if (sourceWidth > sourceHeight) {
height = (int) (width / ratio);
} else if (sourceHeight > sourceWidth) {
width = (int) (height * ratio);
}
}
// no intrinsic size --> use default size
int textureWidth = mIconBitmapSize;
int textureHeight = mIconBitmapSize;
Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
Bitmap.Config.ARGB_8888);
mCanvas.setBitmap(bitmap);
final int left = (textureWidth-width) / 2;
final int top = (textureHeight-height) / 2;
mOldBounds.set(icon.getBounds());
if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
int offset = Math.max((int) Math.ceil(BLUR_FACTOR * textureWidth), Math.max(left, top));
int size = Math.max(width, height);
icon.setBounds(offset, offset, size - offset, size - offset);
} else {
icon.setBounds(left, top, left+width, top+height);
}
mCanvas.save();
mCanvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
icon.draw(mCanvas);
mCanvas.restore();
icon.setBounds(mOldBounds);
mCanvas.setBitmap(null);
return bitmap;
}
// below methods should also migrate to BaseIconFactory
public BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo) {
return createShortcutIcon(shortcutInfo, true /* badged */);
@@ -433,26 +187,4 @@ public class LauncherIcons implements AutoCloseable {
return pkgInfo;
}
}
/**
* An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
* This allows the badging to be done based on the action bitmap size rather than
* the scaled bitmap size.
*/
private static class FixedSizeBitmapDrawable extends BitmapDrawable {
public FixedSizeBitmapDrawable(Bitmap bitmap) {
super(null, bitmap);
}
@Override
public int getIntrinsicHeight() {
return getBitmap().getWidth();
}
@Override
public int getIntrinsicWidth() {
return getBitmap().getWidth();
}
}
}
@@ -34,6 +34,8 @@ import com.android.launcher3.LauncherSettings;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
import java.util.ArrayList;
import java.util.List;
@@ -59,12 +61,12 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask {
Context context = app.getContext();
final ArrayList<ItemInfo> addedItemsFinal = new ArrayList<>();
final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<>();
final IntArray addedWorkspaceScreensFinal = new IntArray();
// Get the list of workspace screens. We need to append to this list and
// can not use sBgWorkspaceScreens because loadWorkspace() may not have been
// called.
ArrayList<Long> workspaceScreens = LauncherModel.loadWorkspaceScreensDb(context);
IntArray workspaceScreens = LauncherModel.loadWorkspaceScreensDb(context);
synchronized(dataModel) {
List<ItemInfo> filteredItems = new ArrayList<>();
@@ -90,10 +92,9 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask {
for (ItemInfo item : filteredItems) {
// Find appropriate space for the item.
Pair<Long, int[]> coords = findSpaceForItem(app, dataModel, workspaceScreens,
int[] coords = findSpaceForItem(app, dataModel, workspaceScreens,
addedWorkspaceScreensFinal, item.spanX, item.spanY);
long screenId = coords.first;
int[] cordinates = coords.second;
int screenId = coords[0];
ItemInfo itemInfo;
if (item instanceof ShortcutInfo || item instanceof FolderInfo ||
@@ -108,7 +109,7 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask {
// Add the shortcut to the db
getModelWriter().addItemToDatabase(itemInfo,
LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId,
cordinates[0], cordinates[1]);
coords[1], coords[2]);
// Save the ShortcutInfo for binding in the workspace
addedItemsFinal.add(itemInfo);
@@ -126,7 +127,7 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask {
final ArrayList<ItemInfo> addNotAnimated = new ArrayList<>();
if (!addedItemsFinal.isEmpty()) {
ItemInfo info = addedItemsFinal.get(addedItemsFinal.size() - 1);
long lastScreenId = info.screenId;
int lastScreenId = info.screenId;
for (ItemInfo i : addedItemsFinal) {
if (i.screenId == lastScreenId) {
addAnimated.add(i);
@@ -142,7 +143,7 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask {
}
}
protected void updateScreens(Context context, ArrayList<Long> workspaceScreens) {
protected void updateScreens(Context context, IntArray workspaceScreens) {
LauncherModel.updateWorkspaceScreenOrder(context, workspaceScreens);
}
@@ -204,13 +205,10 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask {
/**
* Find a position on the screen for the given size or adds a new screen.
* @return screenId and the coordinates for the item.
* @return screenId and the coordinates for the item in an int array of size 3.
*/
protected Pair<Long, int[]> findSpaceForItem(
LauncherAppState app, BgDataModel dataModel,
ArrayList<Long> workspaceScreens,
ArrayList<Long> addedWorkspaceScreensFinal,
int spanX, int spanY) {
protected int[] findSpaceForItem( LauncherAppState app, BgDataModel dataModel,
IntArray workspaceScreens, IntArray addedWorkspaceScreensFinal, int spanX, int spanY) {
LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
// Use sBgItemsIdMap as all the items are already loaded.
@@ -228,7 +226,7 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask {
}
// Find appropriate space for the item.
long screenId = 0;
int screenId = 0;
int[] cordinates = new int[2];
boolean found = false;
@@ -258,7 +256,7 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask {
// Still no position found. Add a new screen to the end.
screenId = LauncherSettings.Settings.call(app.getContext().getContentResolver(),
LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
.getLong(LauncherSettings.Settings.EXTRA_VALUE);
.getInt(LauncherSettings.Settings.EXTRA_VALUE);
// Save the screen id for binding in the workspace
workspaceScreens.add(screenId);
@@ -270,7 +268,7 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask {
throw new RuntimeException("Can't find space to add the item");
}
}
return Pair.create(screenId, cordinates);
return new int[] {screenId, cordinates[0], cordinates[1]};
}
private boolean findNextAvailableIconSpaceInScreen(
@@ -36,7 +36,8 @@ import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.LongArrayMap;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.MultiHashMap;
import com.google.protobuf.nano.MessageNano;
@@ -62,7 +63,7 @@ public class BgDataModel {
* Map of all the ItemInfos (shortcuts, folders, and widgets) created by
* LauncherModel to their ids
*/
public final LongArrayMap<ItemInfo> itemsIdMap = new LongArrayMap<>();
public final IntSparseArrayMap<ItemInfo> itemsIdMap = new IntSparseArrayMap<>();
/**
* List of all the folders and shortcuts directly on the home screen (no widgets
@@ -78,12 +79,12 @@ public class BgDataModel {
/**
* Map of id to FolderInfos of all the folders created by LauncherModel
*/
public final LongArrayMap<FolderInfo> folders = new LongArrayMap<>();
public final IntSparseArrayMap<FolderInfo> folders = new IntSparseArrayMap<>();
/**
* Ordered list of workspace screens ids.
*/
public final ArrayList<Long> workspaceScreens = new ArrayList<>();
public final IntArray workspaceScreens = new IntArray();
/**
* Map of ShortcutKey to the number of times it is pinned.
@@ -132,7 +133,7 @@ public class BgDataModel {
writer.println(prefix + "Data Model:");
writer.print(prefix + " ---- workspace screens: ");
for (int i = 0; i < workspaceScreens.size(); i++) {
writer.print(" " + workspaceScreens.get(i).toString());
writer.print(" " + workspaceScreens.get(i));
}
writer.println();
writer.println(prefix + " ---- workspace items ");
@@ -169,7 +170,7 @@ public class BgDataModel {
// Add top parent nodes. (L1)
DumpTargetWrapper hotseat = new DumpTargetWrapper(ContainerType.HOTSEAT, 0);
LongArrayMap<DumpTargetWrapper> workspaces = new LongArrayMap<>();
IntSparseArrayMap<DumpTargetWrapper> workspaces = new IntSparseArrayMap<>();
for (int i = 0; i < workspaceScreens.size(); i++) {
workspaces.put(workspaceScreens.get(i),
new DumpTargetWrapper(ContainerType.WORKSPACE, i));
@@ -346,7 +347,7 @@ public class BgDataModel {
* Return an existing FolderInfo object if we have encountered this ID previously,
* or make a new one.
*/
public synchronized FolderInfo findOrMakeFolder(long id) {
public synchronized FolderInfo findOrMakeFolder(int id) {
// See if a placeholder was created for us already
FolderInfo folderInfo = folders.get(id);
if (folderInfo == null) {
@@ -11,8 +11,8 @@ import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Point;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
@@ -27,7 +27,9 @@ import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.LongArrayMap;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -59,7 +61,7 @@ public class GridSizeMigrationTask {
private final InvariantDeviceProfile mIdp;
private final ContentValues mTempValues = new ContentValues();
protected final ArrayList<Long> mEntryToRemove = new ArrayList<>();
protected final IntArray mEntryToRemove = new IntArray();
private final ArrayList<ContentProviderOperation> mUpdateOperations = new ArrayList<>();
protected final ArrayList<DbEntry> mCarryOver = new ArrayList<>();
private final HashSet<String> mValidPackages;
@@ -108,6 +110,7 @@ public class GridSizeMigrationTask {
/**
* Applied all the pending DB operations
*
* @return true if any DB operation was commited.
*/
private boolean applyOperations() throws Exception {
@@ -118,7 +121,7 @@ public class GridSizeMigrationTask {
if (!mEntryToRemove.isEmpty()) {
if (DEBUG) {
Log.d(TAG, "Removing items: " + TextUtils.join(", ", mEntryToRemove));
Log.d(TAG, "Removing items: " + mEntryToRemove.toConcatString());
}
mContext.getContentResolver().delete(LauncherSettings.Favorites.CONTENT_URI,
Utilities.createDbSelectionQuery(
@@ -134,6 +137,7 @@ public class GridSizeMigrationTask {
* entries is more than what can fit in the new hotseat, we drop the entries with least weight.
* For weight calculation {@see #WT_SHORTCUT}, {@see #WT_APPLICATION}
* & {@see #WT_FOLDER_FACTOR}.
*
* @return true if any DB change was made
*/
protected boolean migrateHotseat() throws Exception {
@@ -177,12 +181,13 @@ public class GridSizeMigrationTask {
* @return true if any DB change was made
*/
protected boolean migrateWorkspace() throws Exception {
ArrayList<Long> allScreens = LauncherModel.loadWorkspaceScreensDb(mContext);
IntArray allScreens = LauncherModel.loadWorkspaceScreensDb(mContext);
if (allScreens.isEmpty()) {
throw new Exception("Unable to get workspace screens");
}
for (long screenId : allScreens) {
for (int i = 0; i < allScreens.size(); i++) {
int screenId = allScreens.get(i);
if (DEBUG) {
Log.d(TAG, "Migrating " + screenId);
}
@@ -190,7 +195,7 @@ public class GridSizeMigrationTask {
}
if (!mCarryOver.isEmpty()) {
LongArrayMap<DbEntry> itemMap = new LongArrayMap<>();
IntSparseArrayMap<DbEntry> itemMap = new IntSparseArrayMap<>();
for (DbEntry e : mCarryOver) {
itemMap.put(e.id, e);
}
@@ -205,10 +210,10 @@ public class GridSizeMigrationTask {
new GridOccupancy(mTrgX, mTrgY), deepCopy(mCarryOver), 0, true);
placement.find();
if (placement.finalPlacedItems.size() > 0) {
long newScreenId = LauncherSettings.Settings.call(
int newScreenId = LauncherSettings.Settings.call(
mContext.getContentResolver(),
LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
.getLong(LauncherSettings.Settings.EXTRA_VALUE);
.getInt(LauncherSettings.Settings.EXTRA_VALUE);
allScreens.add(newScreenId);
for (DbEntry item : placement.finalPlacedItems) {
@@ -230,10 +235,11 @@ public class GridSizeMigrationTask {
int count = allScreens.size();
for (int i = 0; i < count; i++) {
ContentValues v = new ContentValues();
long screenId = allScreens.get(i);
int screenId = allScreens.get(i);
v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
mUpdateOperations.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
mUpdateOperations.add(ContentProviderOperation.newInsert(uri).withValues(
v).build());
}
}
return applyOperations();
@@ -249,9 +255,10 @@ public class GridSizeMigrationTask {
* 3) If all those items from the above list can be placed on this screen, place them
* (otherwise they are placed on a new screen).
*/
protected void migrateScreen(long screenId) {
protected void migrateScreen(int screenId) {
// If we are migrating the first screen, do not touch the first row.
int startY = (FeatureFlags.QSB_ON_FIRST_SCREEN && screenId == Workspace.FIRST_SCREEN_ID)
int startY =
(FeatureFlags.QSB_ON_FIRST_SCREEN.get() && screenId == Workspace.FIRST_SCREEN_ID)
? 1 : 0;
ArrayList<DbEntry> items = loadWorkspaceEntries(screenId);
@@ -276,9 +283,11 @@ public class GridSizeMigrationTask {
for (int y = mSrcY - 1; y >= startY; y--) {
// Use a deep copy when trying out a particular combination as it can change
// the underlying object.
ArrayList<DbEntry> itemsOnScreen = tryRemove(x, y, startY, deepCopy(items), outLoss);
ArrayList<DbEntry> itemsOnScreen = tryRemove(x, y, startY, deepCopy(items),
outLoss);
if ((outLoss[0] < removeWt) || ((outLoss[0] == removeWt) && (outLoss[1] < moveWt))) {
if ((outLoss[0] < removeWt) || ((outLoss[0] == removeWt) && (outLoss[1]
< moveWt))) {
removeWt = outLoss[0];
moveWt = outLoss[1];
removedCol = mShouldRemoveX ? x : removedCol;
@@ -302,7 +311,7 @@ public class GridSizeMigrationTask {
removedRow, removedCol, screenId));
}
LongArrayMap<DbEntry> itemMap = new LongArrayMap<>();
IntSparseArrayMap<DbEntry> itemMap = new IntSparseArrayMap<>();
for (DbEntry e : deepCopy(items)) {
itemMap.put(e.id, e);
}
@@ -359,6 +368,7 @@ public class GridSizeMigrationTask {
/**
* Tries the remove the provided row and column.
*
* @param items all the items on the screen under operation
* @param outLoss array of size 2. The first entry is filled with weight loss, and the second
* with the overall item movement.
@@ -434,6 +444,7 @@ public class GridSizeMigrationTask {
/**
* Recursively finds a placement for the provided items.
*
* @param index the position in {@link #itemsToPlace} to start looking at.
* @param weightLoss total weight loss upto this point
* @param moveCost total move cost upto this point
@@ -546,7 +557,8 @@ public class GridSizeMigrationTask {
for (int x = 0; x < mTrgX; x++) {
if (!occupied.cells[x][y]) {
int dist = ignoreMove ? 0 :
((me.cellX - x) * (me.cellX - x) + (me.cellY - y) * (me.cellY - y));
((me.cellX - x) * (me.cellX - x) + (me.cellY - y) * (me.cellY
- y));
if (dist < newDistance) {
newX = x;
newY = y;
@@ -613,9 +625,9 @@ public class GridSizeMigrationTask {
ArrayList<DbEntry> entries = new ArrayList<>();
while (c.moveToNext()) {
DbEntry entry = new DbEntry();
entry.id = c.getLong(indexId);
entry.id = c.getInt(indexId);
entry.itemType = c.getInt(indexItemType);
entry.screenId = c.getLong(indexScreen);
entry.screenId = c.getInt(indexScreen);
if (entry.screenId >= mSrcHotseatSize) {
mEntryToRemove.add(entry.id);
@@ -661,7 +673,7 @@ public class GridSizeMigrationTask {
/**
* Loads entries for a particular screen id.
*/
protected ArrayList<DbEntry> loadWorkspaceEntries(long screen) {
protected ArrayList<DbEntry> loadWorkspaceEntries(int screen) {
Cursor c = queryWorkspace(
new String[]{
Favorites._ID, // 0
@@ -689,7 +701,7 @@ public class GridSizeMigrationTask {
ArrayList<DbEntry> entries = new ArrayList<>();
while (c.moveToNext()) {
DbEntry entry = new DbEntry();
entry.id = c.getLong(indexId);
entry.id = c.getInt(indexId);
entry.itemType = c.getInt(indexItemType);
entry.cellX = c.getInt(indexCellX);
entry.cellY = c.getInt(indexCellY);
@@ -762,7 +774,7 @@ public class GridSizeMigrationTask {
/**
* @return the number of valid items in the folder.
*/
private int getFolderItemsCount(long folderId) {
private int getFolderItemsCount(int folderId) {
Cursor c = queryWorkspace(
new String[]{Favorites._ID, Favorites.INTENT},
Favorites.CONTAINER + " = " + folderId);
@@ -773,7 +785,7 @@ public class GridSizeMigrationTask {
verifyIntent(c.getString(1));
total++;
} catch (Exception e) {
mEntryToRemove.add(c.getLong(0));
mEntryToRemove.add(c.getInt(0));
}
}
c.close();
@@ -811,7 +823,8 @@ public class GridSizeMigrationTask {
public float weight;
public DbEntry() { }
public DbEntry() {
}
public DbEntry copy() {
DbEntry entry = new DbEntry();
@@ -883,6 +896,7 @@ public class GridSizeMigrationTask {
/**
* Migrates the workspace and hotseat in case their sizes changed.
*
* @return false if the migration failed.
*/
public static boolean migrateGridIfNeeded(Context context) {
@@ -892,7 +906,8 @@ public class GridSizeMigrationTask {
String gridSizeString = getPointString(idp.numColumns, idp.numRows);
if (gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, "")) &&
idp.numHotseatIcons == prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons)) {
idp.numHotseatIcons == prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
idp.numHotseatIcons)) {
// Skip if workspace and hotseat sizes have not changed.
return true;
}
@@ -903,7 +918,8 @@ public class GridSizeMigrationTask {
HashSet<String> validPackages = getValidPackages(context);
// Hotseat
int srcHotseatCount = prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons);
int srcHotseatCount = prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
idp.numHotseatIcons);
if (srcHotseatCount != idp.numHotseatIcons) {
// Migrate hotseat.
@@ -916,7 +932,8 @@ public class GridSizeMigrationTask {
Point sourceSize = parsePoint(prefs.getString(
KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString));
if (new MultiStepMigrationTask(validPackages, context).migrate(sourceSize, targetSize)) {
if (new MultiStepMigrationTask(validPackages, context).migrate(sourceSize,
targetSize)) {
dbChanged = true;
}
@@ -966,9 +983,11 @@ public class GridSizeMigrationTask {
/**
* Removes any broken item from the hotseat.
*
* @return a map with occupied hotseat position set to non-null value.
*/
public static LongArrayMap<Object> removeBrokenHotseatItems(Context context) throws Exception {
public static IntSparseArrayMap<Object> removeBrokenHotseatItems(Context context)
throws Exception {
GridSizeMigrationTask task = new GridSizeMigrationTask(
context, LauncherAppState.getIDP(context), getValidPackages(context),
Integer.MAX_VALUE, Integer.MAX_VALUE);
@@ -977,7 +996,7 @@ public class GridSizeMigrationTask {
ArrayList<DbEntry> items = task.loadHotseatEntries();
// Delete any entry marked for deletion by above load.
task.applyOperations();
LongArrayMap<Object> positions = new LongArrayMap<>();
IntSparseArrayMap<Object> positions = new IntSparseArrayMap<>();
for (DbEntry item : items) {
positions.put(item.screenId, item);
}
@@ -48,11 +48,11 @@ import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.LongArrayMap;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
import java.net.URISyntaxException;
import java.security.InvalidParameterException;
import java.util.ArrayList;
/**
* Extension of {@link Cursor} with utility methods for workspace loading.
@@ -68,9 +68,9 @@ public class LoaderCursor extends CursorWrapper {
private final IconCache mIconCache;
private final InvariantDeviceProfile mIDP;
private final ArrayList<Long> itemsToRemove = new ArrayList<>();
private final ArrayList<Long> restoredRows = new ArrayList<>();
private final LongArrayMap<GridOccupancy> occupied = new LongArrayMap<>();
private final IntArray itemsToRemove = new IntArray();
private final IntArray restoredRows = new IntArray();
private final IntSparseArrayMap<GridOccupancy> occupied = new IntSparseArrayMap<>();
private final int iconPackageIndex;
private final int iconResourceIndex;
@@ -90,8 +90,8 @@ public class LoaderCursor extends CursorWrapper {
// Properties loaded per iteration
public long serialNumber;
public UserHandle user;
public long id;
public long container;
public int id;
public int container;
public int itemType;
public int restoreFlag;
@@ -126,7 +126,7 @@ public class LoaderCursor extends CursorWrapper {
// Load common properties.
itemType = getInt(itemTypeIndex);
container = getInt(containerIndex);
id = getLong(idIndex);
id = getInt(idIndex);
serialNumber = getInt(profileIdIndex);
user = allUsers.get(serialNumber);
restoreFlag = getInt(restoredIndex);
@@ -293,7 +293,7 @@ public class LoaderCursor extends CursorWrapper {
*/
public ContentWriter updater() {
return new ContentWriter(mContext, new ContentWriter.CommitParams(
BaseColumns._ID + "= ?", new String[]{Long.toString(id)}));
BaseColumns._ID + "= ?", new String[]{Integer.toString(id)}));
}
/**
@@ -383,11 +383,11 @@ public class LoaderCursor extends CursorWrapper {
/**
* check & update map of what's occupied; used to discard overlapping/invalid items
*/
protected boolean checkItemPlacement(ItemInfo item, ArrayList<Long> workspaceScreens) {
long containerIndex = item.screenId;
protected boolean checkItemPlacement(ItemInfo item, IntArray workspaceScreens) {
int containerIndex = item.screenId;
if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
final GridOccupancy hotseatOccupancy =
occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
occupied.get(LauncherSettings.Favorites.CONTAINER_HOTSEAT);
if (item.screenId >= mIDP.numHotseatIcons) {
Log.e(TAG, "Error loading shortcut " + item
@@ -404,17 +404,17 @@ public class LoaderCursor extends CursorWrapper {
+ item.cellY + ") already occupied");
return false;
} else {
hotseatOccupancy.cells[(int) item.screenId][0] = true;
hotseatOccupancy.cells[item.screenId][0] = true;
return true;
}
} else {
final GridOccupancy occupancy = new GridOccupancy(mIDP.numHotseatIcons, 1);
occupancy.cells[(int) item.screenId][0] = true;
occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, occupancy);
occupancy.cells[item.screenId][0] = true;
occupied.put(LauncherSettings.Favorites.CONTAINER_HOTSEAT, occupancy);
return true;
}
} else if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
if (!workspaceScreens.contains((Long) item.screenId)) {
if (!workspaceScreens.contains(item.screenId)) {
// The item has an invalid screen id.
return false;
}
@@ -440,7 +440,8 @@ public class LoaderCursor extends CursorWrapper {
if (item.screenId == Workspace.FIRST_SCREEN_ID) {
// Mark the first row as occupied (if the feature is enabled)
// in order to account for the QSB.
screen.markCells(0, 0, countX + 1, 1, FeatureFlags.QSB_ON_FIRST_SCREEN);
screen.markCells(0, 0, countX + 1, 1,
FeatureFlags.QSB_ON_FIRST_SCREEN.get());
}
occupied.put(item.screenId, screen);
}
@@ -29,9 +29,10 @@ import com.android.launcher3.LauncherModel.Callbacks;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.PagedView;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.LooperIdleLock;
import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.ViewOnDrawExecutor;
@@ -52,7 +53,7 @@ import java.util.concurrent.Executor;
public class LoaderResults {
private static final String TAG = "LoaderResults";
private static final long INVALID_SCREEN_ID = -1L;
private static final int INVALID_SCREEN_ID = -1;
private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
private final Executor mUiExecutor;
@@ -92,7 +93,7 @@ public class LoaderResults {
// Save a copy of all the bg-thread collections
ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
final ArrayList<Long> orderedScreenIds = new ArrayList<>();
final IntArray orderedScreenIds = new IntArray();
synchronized (mBgDataModel) {
workspaceItems.addAll(mBgDataModel.workspaceItems);
@@ -112,7 +113,7 @@ public class LoaderResults {
currentScreen = currScreen;
}
final boolean validFirstPage = currentScreen >= 0;
final long currentScreenId =
final int currentScreenId =
validFirstPage ? orderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID;
// Separate the items that are on the current screen, and all the other remaining items
@@ -209,7 +210,7 @@ public class LoaderResults {
/** Filters the set of items who are directly or indirectly (via another container) on the
* specified screen. */
public static <T extends ItemInfo> void filterCurrentWorkspaceItems(long currentScreenId,
public static <T extends ItemInfo> void filterCurrentWorkspaceItems(int currentScreenId,
ArrayList<T> allWorkspaceItems,
ArrayList<T> currentScreenItems,
ArrayList<T> otherScreenItems) {
@@ -225,13 +226,10 @@ public class LoaderResults {
// Order the set of items by their containers first, this allows use to walk through the
// list sequentially, build up a list of containers that are in the specified screen,
// as well as all items in those containers.
Set<Long> itemsOnScreen = new HashSet<>();
Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
@Override
public int compare(ItemInfo lhs, ItemInfo rhs) {
return Utilities.longCompare(lhs.container, rhs.container);
}
});
IntSet itemsOnScreen = new IntSet();
Collections.sort(allWorkspaceItems,
(lhs, rhs) -> Integer.compare(lhs.container, rhs.container));
for (T info : allWorkspaceItems) {
if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
if (info.screenId == currentScreenId) {
@@ -267,15 +265,15 @@ public class LoaderResults {
// Within containers, order by their spatial position in that container
switch ((int) lhs.container) {
case LauncherSettings.Favorites.CONTAINER_DESKTOP: {
long lr = (lhs.screenId * screenCellCount +
int lr = (lhs.screenId * screenCellCount +
lhs.cellY * screenCols + lhs.cellX);
long rr = (rhs.screenId * screenCellCount +
int rr = (rhs.screenId * screenCellCount +
rhs.cellY * screenCols + rhs.cellX);
return Utilities.longCompare(lr, rr);
return Integer.compare(lr, rr);
}
case LauncherSettings.Favorites.CONTAINER_HOTSEAT: {
// We currently use the screen id as the rank
return Utilities.longCompare(lhs.screenId, rhs.screenId);
return Integer.compare(lhs.screenId, rhs.screenId);
}
default:
if (FeatureFlags.IS_DOGFOOD_BUILD) {
@@ -286,7 +284,7 @@ public class LoaderResults {
}
} else {
// Between containers, order by hotseat, desktop
return Utilities.longCompare(lhs.container, rhs.container);
return Integer.compare(lhs.container, rhs.container);
}
}
});
@@ -296,6 +294,11 @@ public class LoaderResults {
final ArrayList<LauncherAppWidgetInfo> appWidgets,
final Executor executor) {
if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
&& com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
android.util.Log.d("b/117332845",
android.util.Log.getStackTraceString(new Throwable()));
}
// Bind the workspace items
int N = workspaceItems.size();
for (int i = 0; i < N; i += ITEMS_CHUNK) {
@@ -70,6 +70,7 @@ import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.LooperIdleLock;
import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.PackageManagerHelper;
@@ -124,6 +125,11 @@ public class LoaderTask implements Runnable {
mPackageInstaller = PackageInstallerCompat.getInstance(mApp.getContext());
mAppWidgetManager = AppWidgetManagerCompat.getInstance(mApp.getContext());
mIconCache = mApp.getIconCache();
if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
&& com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
android.util.Log.d("b/117332845",
android.util.Log.getStackTraceString(new Throwable()));
}
}
protected synchronized void waitForIdle() {
@@ -150,7 +156,7 @@ public class LoaderTask implements Runnable {
allItems.addAll(mBgDataModel.workspaceItems);
allItems.addAll(mBgDataModel.appWidgets);
}
long firstScreen = mBgDataModel.workspaceScreens.isEmpty()
int firstScreen = mBgDataModel.workspaceScreens.isEmpty()
? -1 // In this case, we can still look at the items in the hotseat.
: mBgDataModel.workspaceScreens.get(0);
filterCurrentWorkspaceItems(firstScreen, allItems, firstScreenItems,
@@ -714,11 +720,11 @@ public class LoaderTask implements Runnable {
// Remove dead items
if (c.commitDeleted()) {
// Remove any empty folder
ArrayList<Long> deletedFolderIds = (ArrayList<Long>) LauncherSettings.Settings
int[] deletedFolderIds = LauncherSettings.Settings
.call(contentResolver,
LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS)
.getSerializable(LauncherSettings.Settings.EXTRA_VALUE);
for (long folderId : deletedFolderIds) {
.getIntArray(LauncherSettings.Settings.EXTRA_VALUE);
for (int folderId : deletedFolderIds) {
mBgDataModel.workspaceItems.remove(mBgDataModel.folders.get(folderId));
mBgDataModel.folders.remove(folderId);
mBgDataModel.itemsIdMap.remove(folderId);
@@ -773,18 +779,18 @@ public class LoaderTask implements Runnable {
}
// Remove any empty screens
ArrayList<Long> unusedScreens = new ArrayList<>(mBgDataModel.workspaceScreens);
IntArray unusedScreens = mBgDataModel.workspaceScreens.clone();
for (ItemInfo item: mBgDataModel.itemsIdMap) {
long screenId = item.screenId;
int screenId = item.screenId;
if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
unusedScreens.contains(screenId)) {
unusedScreens.remove(screenId);
unusedScreens.removeValue(screenId);
}
}
// If there are any empty screens remove them, and update.
if (unusedScreens.size() != 0) {
mBgDataModel.workspaceScreens.removeAll(unusedScreens);
mBgDataModel.workspaceScreens.removeAllValues(unusedScreens);
LauncherModel.updateWorkspaceScreenOrder(context, mBgDataModel.workspaceScreens);
}
}
@@ -79,7 +79,7 @@ public class ModelWriter {
}
private void updateItemInfoProps(
ItemInfo item, long container, long screenId, int cellX, int cellY) {
ItemInfo item, int container, int screenId, int cellX, int cellY) {
item.container = container;
item.cellX = cellX;
item.cellY = cellY;
@@ -98,7 +98,7 @@ public class ModelWriter {
* <container, screen, cellX, cellY>
*/
public void addOrMoveItemInDatabase(ItemInfo item,
long container, long screenId, int cellX, int cellY) {
int container, int screenId, int cellX, int cellY) {
if (item.container == ItemInfo.NO_ID) {
// From all apps
addItemToDatabase(item, container, screenId, cellX, cellY);
@@ -108,7 +108,12 @@ public class ModelWriter {
}
}
private void checkItemInfoLocked(long itemId, ItemInfo item, StackTraceElement[] stackTrace) {
private void checkItemInfoLocked(int itemId, ItemInfo item, StackTraceElement[] stackTrace) {
if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
&& com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
android.util.Log.d("b/117332845",
"Checking item: " + android.util.Log.getStackTraceString(new Throwable()));
}
ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId);
if (modelItem != null && item != modelItem) {
// check all the data is consistent
@@ -149,7 +154,7 @@ public class ModelWriter {
* Move an item in the DB to a new <container, screen, cellX, cellY>
*/
public void moveItemInDatabase(final ItemInfo item,
long container, long screenId, int cellX, int cellY) {
int container, int screenId, int cellX, int cellY) {
updateItemInfoProps(item, container, screenId, cellX, cellY);
final ContentWriter writer = new ContentWriter(mContext)
@@ -166,7 +171,7 @@ public class ModelWriter {
* Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
* cellX, cellY have already been updated on the ItemInfos.
*/
public void moveItemsInDatabase(final ArrayList<ItemInfo> items, long container, int screen) {
public void moveItemsInDatabase(final ArrayList<ItemInfo> items, int container, int screen) {
ArrayList<ContentValues> contentValues = new ArrayList<>();
int count = items.size();
@@ -190,7 +195,7 @@ public class ModelWriter {
* Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
*/
public void modifyItemInDatabase(final ItemInfo item,
long container, long screenId, int cellX, int cellY, int spanX, int spanY) {
int container, int screenId, int cellX, int cellY, int spanX, int spanY) {
updateItemInfoProps(item, container, screenId, cellX, cellY);
item.spanX = spanX;
item.spanY = spanY;
@@ -221,14 +226,14 @@ public class ModelWriter {
* cellY fields of the item. Also assigns an ID to the item.
*/
public void addItemToDatabase(final ItemInfo item,
long container, long screenId, int cellX, int cellY) {
int container, int screenId, int cellX, int cellY) {
updateItemInfoProps(item, container, screenId, cellX, cellY);
final ContentWriter writer = new ContentWriter(mContext);
final ContentResolver cr = mContext.getContentResolver();
item.onAddToDatabase(writer);
item.id = Settings.call(cr, Settings.METHOD_NEW_ITEM_ID).getLong(Settings.EXTRA_VALUE);
item.id = Settings.call(cr, Settings.METHOD_NEW_ITEM_ID).getInt(Settings.EXTRA_VALUE);
writer.put(Favorites._ID, item.id);
ModelVerifier verifier = new ModelVerifier();
@@ -355,9 +360,14 @@ public class ModelWriter {
private class UpdateItemRunnable extends UpdateItemBaseRunnable {
private final ItemInfo mItem;
private final ContentWriter mWriter;
private final long mItemId;
private final int mItemId;
UpdateItemRunnable(ItemInfo item, ContentWriter writer) {
if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
&& com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
android.util.Log.d("b/117332845",
android.util.Log.getStackTraceString(new Throwable()));
}
mItem = item;
mWriter = writer;
mItemId = item.id;
@@ -386,7 +396,7 @@ public class ModelWriter {
int count = mItems.size();
for (int i = 0; i < count; i++) {
ItemInfo item = mItems.get(i);
final long itemId = item.id;
final int itemId = item.id;
final Uri uri = Favorites.getContentUri(itemId);
ContentValues values = mValues.get(i);
@@ -409,7 +419,7 @@ public class ModelWriter {
mStackTrace = new Throwable().getStackTrace();
}
protected void updateItemArrays(ItemInfo item, long itemId) {
protected void updateItemArrays(ItemInfo item, int itemId) {
// Lock on mBgLock *after* the db operation
synchronized (mBgDataModel) {
checkItemInfoLocked(itemId, item, mStackTrace);
@@ -44,8 +44,8 @@ import com.android.launcher3.logging.FileLog;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LongArrayMap;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
@@ -171,7 +171,7 @@ public class PackageUpdatedTask extends BaseModelUpdateTask {
}
}
final LongArrayMap<Boolean> removedShortcuts = new LongArrayMap<>();
final IntSparseArrayMap<Boolean> removedShortcuts = new IntSparseArrayMap<>();
// Update shortcut infos
if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
@@ -16,7 +16,7 @@
package com.android.launcher3.notification;
import static com.android.launcher3.SettingsActivity.NOTIFICATION_BADGING;
import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
import android.annotation.TargetApi;
import android.app.Notification;
@@ -28,13 +28,13 @@ import android.os.Message;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.SettingsObserver;
import com.android.launcher3.util.SecureSettingsObserver;
import java.util.ArrayList;
import java.util.Arrays;
@@ -42,7 +42,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import androidx.annotation.Nullable;
@@ -78,7 +77,7 @@ public class NotificationListener extends NotificationListenerService {
/** The last notification key that was dismissed from launcher UI */
private String mLastKeyDismissedByLauncher;
private SettingsObserver mNotificationBadgingObserver;
private SecureSettingsObserver mNotificationBadgingObserver;
private final Handler.Callback mWorkerCallback = new Handler.Callback() {
@Override
@@ -195,19 +194,20 @@ public class NotificationListener extends NotificationListenerService {
super.onListenerConnected();
sIsConnected = true;
mNotificationBadgingObserver = new SettingsObserver.Secure(getContentResolver()) {
@Override
public void onSettingChanged(boolean isNotificationBadgingEnabled) {
if (!isNotificationBadgingEnabled && sIsConnected) {
requestUnbind();
}
}
};
mNotificationBadgingObserver.register(NOTIFICATION_BADGING);
mNotificationBadgingObserver =
newNotificationSettingsObserver(this, this::onNotificationBadgingChanged);
mNotificationBadgingObserver.register();
mNotificationBadgingObserver.dispatchOnChange();
onNotificationFullRefresh();
}
private void onNotificationBadgingChanged(boolean isNotificationBadgingEnabled) {
if (!isNotificationBadgingEnabled && sIsConnected) {
requestUnbind();
}
}
private void onNotificationFullRefresh() {
mWorkerHandler.obtainMessage(MSG_NOTIFICATION_FULL_REFRESH).sendToTarget();
}
@@ -346,7 +346,7 @@ public class NotificationListener extends NotificationListenerService {
private List<StatusBarNotification> filterNotifications(
StatusBarNotification[] notifications) {
if (notifications == null) return null;
Set<Integer> removedNotifications = new ArraySet<>();
IntSet removedNotifications = new IntSet();
for (int i = 0; i < notifications.length; i++) {
if (shouldBeFilteredOut(notifications[i])) {
removedNotifications.add(i);
@@ -391,13 +391,10 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource,
if (view instanceof DeepShortcutView) {
// Expanded system shortcut, with both icon and text shown on white background.
final DeepShortcutView shortcutView = (DeepShortcutView) view;
shortcutView.getIconView().setBackgroundResource(info.iconResId);
shortcutView.getBubbleText().setText(info.labelResId);
info.setIconAndLabelFor(shortcutView.getIconView(), shortcutView.getBubbleText());
} else if (view instanceof ImageView) {
// Only the system shortcut icon shows on a gray background header.
final ImageView shortcutIcon = (ImageView) view;
shortcutIcon.setImageResource(info.iconResId);
shortcutIcon.setContentDescription(getContext().getText(info.labelResId));
info.setIconAndContentDescriptionFor((ImageView) view);
}
view.setTag(info);
view.setOnClickListener(info.getOnClickListener(mLauncher,
@@ -3,10 +3,15 @@ package com.android.launcher3.popup;
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
@@ -23,18 +28,82 @@ import com.android.launcher3.widget.WidgetsBottomSheet;
import java.util.List;
/**
* Represents a system shortcut for a given app. The shortcut should have a static label and
* icon, and an onClickListener that depends on the item that the shortcut services.
* Represents a system shortcut for a given app. The shortcut should have a label and icon, and an
* onClickListener that depends on the item that the shortcut services.
*
* Example system shortcuts, defined as inner classes, include Widgets and AppInfo.
*/
public abstract class SystemShortcut<T extends BaseDraggingActivity> extends ItemInfo {
public final int iconResId;
public final int labelResId;
private final int mIconResId;
private final int mLabelResId;
private final Drawable mIcon;
private final CharSequence mLabel;
private final CharSequence mContentDescription;
private final int mAccessibilityActionId;
public SystemShortcut(int iconResId, int labelResId) {
this.iconResId = iconResId;
this.labelResId = labelResId;
mIconResId = iconResId;
mLabelResId = labelResId;
mAccessibilityActionId = labelResId;
mIcon = null;
mLabel = null;
mContentDescription = null;
}
public SystemShortcut(Drawable icon, CharSequence label, CharSequence contentDescription,
int accessibilityActionId) {
mIcon = icon;
mLabel = label;
mContentDescription = contentDescription;
mAccessibilityActionId = accessibilityActionId;
mIconResId = 0;
mLabelResId = 0;
}
public SystemShortcut(SystemShortcut other) {
mIconResId = other.mIconResId;
mLabelResId = other.mLabelResId;
mIcon = other.mIcon;
mLabel = other.mLabel;
mContentDescription = other.mContentDescription;
mAccessibilityActionId = other.mAccessibilityActionId;
}
public void setIconAndLabelFor(View iconView, TextView labelView) {
if (mIcon != null) {
iconView.setBackground(mIcon);
} else {
iconView.setBackgroundResource(mIconResId);
}
if (mLabel != null) {
labelView.setText(mLabel);
} else {
labelView.setText(mLabelResId);
}
}
public void setIconAndContentDescriptionFor(ImageView view) {
if (mIcon != null) {
view.setImageDrawable(mIcon);
} else {
view.setImageResource(mIconResId);
}
view.setContentDescription(getContentDescription(view.getContext()));
}
private CharSequence getContentDescription(Context context) {
return mContentDescription != null ? mContentDescription : context.getText(mLabelResId);
}
public AccessibilityNodeInfo.AccessibilityAction createAccessibilityAction(Context context) {
return new AccessibilityNodeInfo.AccessibilityAction(mAccessibilityActionId,
getContentDescription(context));
}
public boolean hasHandlerForAction(int action) {
return mAccessibilityActionId == action;
}
public abstract View.OnClickListener getOnClickListener(T activity, ItemInfo itemInfo);
@@ -32,8 +32,9 @@ import android.net.Uri;
import android.os.Process;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.LongSparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
import com.android.launcher3.DefaultLayoutParser;
import com.android.launcher3.LauncherAppState;
@@ -50,7 +51,9 @@ import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.GridSizeMigrationTask;
import com.android.launcher3.util.LongArrayMap;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashSet;
@@ -85,7 +88,7 @@ public class ImportDataTask {
}
public boolean importWorkspace() throws Exception {
ArrayList<Long> allScreens = LauncherDbUtils.getScreenIdsFromCursor(
IntArray allScreens = LauncherDbUtils.getScreenIdsFromCursor(
mContext.getContentResolver().query(mOtherScreensUri, null, null, null,
LauncherSettings.WorkspaceScreens.SCREEN_RANK));
FileLog.d(TAG, "Importing DB from " + mOtherFavoritesUri);
@@ -102,12 +105,12 @@ public class ImportDataTask {
// Build screen update
ArrayList<ContentProviderOperation> screenOps = new ArrayList<>();
int count = allScreens.size();
LongSparseArray<Long> screenIdMap = new LongSparseArray<>(count);
SparseIntArray screenIdMap = new SparseIntArray(count);
for (int i = 0; i < count; i++) {
ContentValues v = new ContentValues();
v.put(LauncherSettings.WorkspaceScreens._ID, i);
v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
screenIdMap.put(allScreens.get(i), (long) i);
screenIdMap.put(allScreens.get(i), i);
screenOps.add(ContentProviderOperation.newInsert(
LauncherSettings.WorkspaceScreens.CONTENT_URI).withValues(v).build());
}
@@ -128,16 +131,16 @@ public class ImportDataTask {
* 3) In the end fills any holes in hotseat with items from default hotseat layout.
*/
private void importWorkspaceItems(
long firsetScreenId, LongSparseArray<Long> screenIdMap) throws Exception {
int firstScreenId, SparseIntArray screenIdMap) throws Exception {
String profileId = Long.toString(UserManagerCompat.getInstance(mContext)
.getSerialNumberForUser(Process.myUserHandle()));
boolean createEmptyRowOnFirstScreen;
if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
if (FeatureFlags.QSB_ON_FIRST_SCREEN.get()) {
try (Cursor c = mContext.getContentResolver().query(mOtherFavoritesUri, null,
// get items on the first row of the first screen
"profileId = ? AND container = -100 AND screen = ? AND cellY = 0",
new String[]{profileId, Long.toString(firsetScreenId)},
new String[]{profileId, Integer.toString(firstScreenId)},
null)) {
// First row of first screen is not empty
createEmptyRowOnFirstScreen = c.moveToNext();
@@ -190,7 +193,7 @@ public class ImportDataTask {
int type = c.getInt(itemTypeIndex);
int container = c.getInt(containerIndex);
long screen = c.getLong(screenIndex);
int screen = c.getInt(screenIndex);
int cellX = c.getInt(cellXIndex);
int cellY = c.getInt(cellYIndex);
@@ -199,7 +202,7 @@ public class ImportDataTask {
switch (container) {
case Favorites.CONTAINER_DESKTOP: {
Long newScreenId = screenIdMap.get(screen);
Integer newScreenId = screenIdMap.get(screen);
if (newScreenId == null) {
FileLog.d(TAG, String.format("Skipping item %d, type %d not on a valid screen %d", id, type, screen));
continue;
@@ -306,15 +309,15 @@ public class ImportDataTask {
insertOperations.clear();
}
LongArrayMap<Object> hotseatItems = GridSizeMigrationTask.removeBrokenHotseatItems(mContext);
IntSparseArrayMap<Object> hotseatItems = GridSizeMigrationTask.removeBrokenHotseatItems(mContext);
int myHotseatCount = LauncherAppState.getIDP(mContext).numHotseatIcons;
if (hotseatItems.size() < myHotseatCount) {
// Insufficient hotseat items. Add a few more.
HotseatParserCallback parserCallback = new HotseatParserCallback(
hotseatTargetApps, hotseatItems, insertOperations, maxId + 1, myHotseatCount);
new HotseatLayoutParser(mContext,
parserCallback).loadLayout(null, new ArrayList<Long>());
mHotseatSize = (int) hotseatItems.keyAt(hotseatItems.size() - 1) + 1;
parserCallback).loadLayout(null, new IntArray());
mHotseatSize = hotseatItems.keyAt(hotseatItems.size() - 1) + 1;
if (!insertOperations.isEmpty()) {
mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY,
@@ -405,13 +408,13 @@ public class ImportDataTask {
*/
private static class HotseatParserCallback implements LayoutParserCallback {
private final HashSet<String> mExistingApps;
private final LongArrayMap<Object> mExistingItems;
private final IntSparseArrayMap<Object> mExistingItems;
private final ArrayList<ContentProviderOperation> mOutOps;
private final int mRequiredSize;
private int mStartItemId;
HotseatParserCallback(
HashSet<String> existingApps, LongArrayMap<Object> existingItems,
HashSet<String> existingApps, IntSparseArrayMap<Object> existingItems,
ArrayList<ContentProviderOperation> outOps, int startItemId, int requiredSize) {
mExistingApps = existingApps;
mExistingItems = existingItems;
@@ -421,12 +424,12 @@ public class ImportDataTask {
}
@Override
public long generateNewItemId() {
public int generateNewItemId() {
return mStartItemId++;
}
@Override
public long insertAndCheck(SQLiteDatabase db, ContentValues values) {
public int insertAndCheck(SQLiteDatabase db, ContentValues values) {
if (mExistingItems.size() >= mRequiredSize) {
// No need to add more items.
return 0;
@@ -445,7 +448,7 @@ public class ImportDataTask {
mExistingApps.add(pkg);
// find next vacant spot.
long screen = 0;
int screen = 0;
while (mExistingItems.get(screen) != null) {
screen++;
}
@@ -26,6 +26,7 @@ import android.util.Log;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherSettings.WorkspaceScreens;
import com.android.launcher3.util.IntArray;
import java.util.ArrayList;
import java.util.Collection;
@@ -47,7 +48,7 @@ public class LauncherDbUtils {
public static boolean prepareScreenZeroToHostQsb(Context context, SQLiteDatabase db) {
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
// Get the existing screens
ArrayList<Long> screenIds = getScreenIdsFromCursor(db.query(WorkspaceScreens.TABLE_NAME,
IntArray screenIds = getScreenIdsFromCursor(db.query(WorkspaceScreens.TABLE_NAME,
null, null, null, null, null, WorkspaceScreens.SCREEN_RANK));
if (screenIds.isEmpty()) {
@@ -57,10 +58,10 @@ public class LauncherDbUtils {
}
if (screenIds.get(0) != 0) {
// First screen is not 0, we need to rename screens
if (screenIds.indexOf(0L) > -1) {
if (screenIds.contains(0)) {
// There is already a screen 0. First rename it to a different screen.
long newScreenId = 1;
while (screenIds.indexOf(newScreenId) > -1) newScreenId++;
int newScreenId = 1;
while (screenIds.contains(newScreenId)) newScreenId++;
renameScreen(db, 0, newScreenId);
}
@@ -86,8 +87,8 @@ public class LauncherDbUtils {
}
}
private static void renameScreen(SQLiteDatabase db, long oldScreen, long newScreen) {
String[] whereParams = new String[] { Long.toString(oldScreen) };
private static void renameScreen(SQLiteDatabase db, int oldScreen, int newScreen) {
String[] whereParams = new String[] { Integer.toString(oldScreen) };
ContentValues values = new ContentValues();
values.put(WorkspaceScreens._ID, newScreen);
@@ -101,19 +102,18 @@ public class LauncherDbUtils {
/**
* Parses the cursor containing workspace screens table and returns the list of screen IDs
*/
public static ArrayList<Long> getScreenIdsFromCursor(Cursor sc) {
public static IntArray getScreenIdsFromCursor(Cursor sc) {
try {
return iterateCursor(sc,
sc.getColumnIndexOrThrow(WorkspaceScreens._ID),
new ArrayList<Long>());
sc.getColumnIndexOrThrow(WorkspaceScreens._ID), new IntArray());
} finally {
sc.close();
}
}
public static <T extends Collection<Long>> T iterateCursor(Cursor c, int columnIndex, T out) {
public static IntArray iterateCursor(Cursor c, int columnIndex, IntArray out) {
while (c.moveToNext()) {
out.add(c.getLong(columnIndex));
out.add(c.getInt(columnIndex));
}
return out;
}
@@ -27,7 +27,7 @@ import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.model.GridSizeMigrationTask;
import com.android.launcher3.util.LongArrayMap;
import com.android.launcher3.util.IntSparseArrayMap;
import java.util.ArrayList;
@@ -39,8 +39,8 @@ public class LossyScreenMigrationTask extends GridSizeMigrationTask {
private final SQLiteDatabase mDb;
private final LongArrayMap<DbEntry> mOriginalItems;
private final LongArrayMap<DbEntry> mUpdates;
private final IntSparseArrayMap<DbEntry> mOriginalItems;
private final IntSparseArrayMap<DbEntry> mUpdates;
protected LossyScreenMigrationTask(
Context context, InvariantDeviceProfile idp, SQLiteDatabase db) {
@@ -50,8 +50,8 @@ public class LossyScreenMigrationTask extends GridSizeMigrationTask {
new Point(idp.numColumns, idp.numRows));
mDb = db;
mOriginalItems = new LongArrayMap<>();
mUpdates = new LongArrayMap<>();
mOriginalItems = new IntSparseArrayMap<>();
mUpdates = new IntSparseArrayMap<>();
}
@Override
@@ -65,7 +65,7 @@ public class LossyScreenMigrationTask extends GridSizeMigrationTask {
}
@Override
protected ArrayList<DbEntry> loadWorkspaceEntries(long screen) {
protected ArrayList<DbEntry> loadWorkspaceEntries(int screen) {
ArrayList<DbEntry> result = super.loadWorkspaceEntries(screen);
for (DbEntry entry : result) {
mOriginalItems.put(entry.id, entry.copy());
@@ -90,7 +90,7 @@ public class LossyScreenMigrationTask extends GridSizeMigrationTask {
tempValues.clear();
update.addToContentValues(tempValues);
mDb.update(Favorites.TABLE_NAME, tempValues, "_id = ?",
new String[] {Long.toString(update.id)});
new String[] {Integer.toString(update.id)});
}
}
@@ -92,7 +92,7 @@ public class RestoreDbTask {
new String[]{Integer.toString(Favorites.ITEM_TYPE_APPWIDGET)});
long myProfileId = helper.getDefaultUserSerial();
if (Utilities.longCompare(oldProfileId, myProfileId) != 0) {
if (myProfileId != oldProfileId) {
FileLog.d(TAG, "Changing primary user id from " + oldProfileId + " to " + myProfileId);
migrateProfileId(db, myProfileId);
}
@@ -213,7 +213,7 @@ public class QsbContainerView extends FrameLayout {
}
public boolean isQsbEnabled() {
return FeatureFlags.QSB_ON_FIRST_SCREEN;
return FeatureFlags.QSB_ON_FIRST_SCREEN.get();
}
protected Bundle createBindOptions() {
@@ -16,7 +16,6 @@
package com.android.launcher3.qsb;
import android.appwidget.AppWidgetHostView;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
@@ -26,17 +25,20 @@ import android.widget.RemoteViews;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.widget.NavigableAppWidgetHostView;
/**
* Appwidget host view with QSB specific logic.
*/
public class QsbWidgetHostView extends AppWidgetHostView {
public class QsbWidgetHostView extends NavigableAppWidgetHostView {
@ViewDebug.ExportedProperty(category = "launcher")
private int mPreviousOrientation;
public QsbWidgetHostView(Context context) {
super(context);
setFocusable(true);
setBackgroundResource(R.drawable.qsb_host_view_focus_bg);
}
@Override
@@ -89,4 +91,9 @@ public class QsbWidgetHostView extends AppWidgetHostView {
Launcher.getLauncher(v2.getContext()).startSearch("", false, null, true));
return v;
}
@Override
protected boolean shouldAllowDirectClick() {
return true;
}
}
@@ -82,7 +82,7 @@ public class TouchEventTranslator {
public void dispatchDownEvents(MotionEvent ev) {
for(int i = 0; i < ev.getPointerCount() && i < mDownEvents.size(); i++) {
int pid = ev.getPointerId(i);
put(pid, ev.getX(i), 0, mDownEvents.get(i).timeStamp, ev);
put(pid, i, ev.getX(i), 0, mDownEvents.get(i).timeStamp, ev);
}
}
@@ -103,7 +103,7 @@ public class TouchEventTranslator {
}
generateEvent(ev.getAction(), ev);
} else {
put(pid, x, y, ev);
put(pid, index, x, y, ev);
}
break;
case MotionEvent.ACTION_MOVE:
@@ -128,11 +128,11 @@ public class TouchEventTranslator {
}
}
private TouchEventTranslator put(int id, float x, float y, MotionEvent ev) {
return put(id, x, y, ev.getEventTime(), ev);
private TouchEventTranslator put(int id, int index, float x, float y, MotionEvent ev) {
return put(id, index, x, y, ev.getEventTime(), ev);
}
private TouchEventTranslator put(int id, float x, float y, long ms, MotionEvent ev) {
private TouchEventTranslator put(int id, int index, float x, float y, long ms, MotionEvent ev) {
checkFingerExistence(id, false);
boolean isInitialDown = (mFingers.size() == 0);
@@ -155,7 +155,7 @@ public class TouchEventTranslator {
} else {
action = MotionEvent.ACTION_POINTER_DOWN;
// Set the id of the changed pointer.
action |= id << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
action |= index << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
}
generateEvent(action, ms, ev);
return this;
@@ -209,7 +209,7 @@ public class TouchEventTranslator {
public void printSamples(String msg, MotionEvent ev) {
System.out.printf("%s %s", msg, MotionEvent.actionToString(ev.getActionMasked()));
final int pointerCount = ev.getPointerCount();
System.out.printf("#%d/%d", ev.getPointerId(ev.getActionIndex()), pointerCount);
System.out.printf("#%d/%d", ev.getActionIndex(), pointerCount);
System.out.printf(" t=%d:", ev.getEventTime());
for (int p = 0; p < pointerCount; p++) {
System.out.printf(" id=%d: (%f,%f)",
@@ -242,6 +242,10 @@ public class TouchEventTranslator {
if (DEBUG) {
printSamples(TAG + " generateEvent", event);
}
if (event.getPointerId(event.getActionIndex()) < 0) {
printSamples(TAG + "generateEvent", event);
throw new IllegalStateException(event.getActionIndex() + " not found in MotionEvent");
}
mListener.accept(event);
event.recycle();
}
@@ -0,0 +1,236 @@
/*
* Copyright (C) 2018 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 java.util.Arrays;
/**
* Copy of the platform hidden implementation of android.util.IntArray.
* Implements a growing array of int primitives.
*/
public class IntArray implements Cloneable {
private static final int MIN_CAPACITY_INCREMENT = 12;
private static final int[] EMPTY_INT = new int[0];
/* package private */ int[] mValues;
/* package private */ int mSize;
private IntArray(int[] array, int size) {
mValues = array;
mSize = size;
}
/**
* Creates an empty IntArray with the default initial capacity.
*/
public IntArray() {
this(10);
}
/**
* Creates an empty IntArray with the specified initial capacity.
*/
public IntArray(int initialCapacity) {
if (initialCapacity == 0) {
mValues = EMPTY_INT;
} else {
mValues = new int[initialCapacity];
}
mSize = 0;
}
/**
* Creates an IntArray wrapping the given primitive int array.
*/
public static IntArray wrap(int... array) {
return new IntArray(array, array.length);
}
/**
* Appends the specified value to the end of this array.
*/
public void add(int value) {
add(mSize, value);
}
/**
* Inserts a value at the specified position in this array. If the specified index is equal to
* the length of the array, the value is added at the end.
*
* @throws IndexOutOfBoundsException when index &lt; 0 || index &gt; size()
*/
public void add(int index, int value) {
ensureCapacity(1);
int rightSegment = mSize - index;
mSize++;
checkBounds(mSize, index);
if (rightSegment != 0) {
// Move by 1 all values from the right of 'index'
System.arraycopy(mValues, index, mValues, index + 1, rightSegment);
}
mValues[index] = value;
}
/**
* Adds the values in the specified array to this array.
*/
public void addAll(IntArray values) {
final int count = values.mSize;
ensureCapacity(count);
System.arraycopy(values.mValues, 0, mValues, mSize, count);
mSize += count;
}
/**
* Ensures capacity to append at least <code>count</code> values.
*/
private void ensureCapacity(int count) {
final int currentSize = mSize;
final int minCapacity = currentSize + count;
if (minCapacity >= mValues.length) {
final int targetCap = currentSize + (currentSize < (MIN_CAPACITY_INCREMENT / 2) ?
MIN_CAPACITY_INCREMENT : currentSize >> 1);
final int newCapacity = targetCap > minCapacity ? targetCap : minCapacity;
final int[] newValues = new int[newCapacity];
System.arraycopy(mValues, 0, newValues, 0, currentSize);
mValues = newValues;
}
}
/**
* Removes all values from this array.
*/
public void clear() {
mSize = 0;
}
@Override
public IntArray clone() {
return wrap(toArray());
}
/**
* Returns the value at the specified position in this array.
*/
public int get(int index) {
checkBounds(mSize, index);
return mValues[index];
}
/**
* Sets the value at the specified position in this array.
*/
public void set(int index, int value) {
checkBounds(mSize, index);
mValues[index] = value;
}
/**
* Returns the index of the first occurrence of the specified value in this
* array, or -1 if this array does not contain the value.
*/
public int indexOf(int value) {
final int n = mSize;
for (int i = 0; i < n; i++) {
if (mValues[i] == value) {
return i;
}
}
return -1;
}
public boolean contains(int value) {
return indexOf(value) >= 0;
}
public boolean isEmpty() {
return mSize == 0;
}
/**
* Removes the value at the specified index from this array.
*/
public void removeIndex(int index) {
checkBounds(mSize, index);
System.arraycopy(mValues, index + 1, mValues, index, mSize - index - 1);
mSize--;
}
/**
* Removes the values if it exists
*/
public void removeValue(int value) {
int index = indexOf(value);
if (index >= 0) {
removeIndex(index);
}
}
/**
* Removes the values if it exists
*/
public void removeAllValues(IntArray values) {
for (int i = 0; i < values.mSize; i++) {
removeValue(values.mValues[i]);
}
}
/**
* Returns the number of values in this array.
*/
public int size() {
return mSize;
}
/**
* Returns a new array with the contents of this IntArray.
*/
public int[] toArray() {
return mSize == 0 ? EMPTY_INT : Arrays.copyOf(mValues, mSize);
}
/**
* Returns a comma separate list of all values.
*/
public String toConcatString() {
StringBuilder b = new StringBuilder();
for (int i = 0; i < mSize ; i++) {
if (i > 0) {
b.append(", ");
}
b.append(mValues[i]);
}
return b.toString();
}
/**
* Throws {@link ArrayIndexOutOfBoundsException} if the index is out of bounds.
*
* @param len length of the array. Must be non-negative
* @param index the index to check
* @throws ArrayIndexOutOfBoundsException if the {@code index} is out of bounds of the array
*/
private static void checkBounds(int len, int index) {
if (index < 0 || len <= index) {
throw new ArrayIndexOutOfBoundsException("length=" + len + "; index=" + index);
}
}
}
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2018 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 java.util.Arrays;
/**
* A wrapper over IntArray implementing a growing set of int primitives.
*/
public class IntSet {
final IntArray mArray = new IntArray();
/**
* Appends the specified value to the set if it does not exist.
*/
public void add(int value) {
int index = Arrays.binarySearch(mArray.mValues, 0, mArray.mSize, value);
if (index < 0) {
mArray.add(-index - 1, value);
}
}
public boolean contains(int value) {
return Arrays.binarySearch(mArray.mValues, 0, mArray.mSize, value) >= 0;
}
public boolean isEmpty() {
return mArray.isEmpty();
}
/**
* Returns the number of values in this set.
*/
public int size() {
return mArray.size();
}
}
@@ -16,16 +16,16 @@
package com.android.launcher3.util;
import android.util.LongSparseArray;
import android.util.SparseArray;
import java.util.Iterator;
/**
* Extension of {@link LongSparseArray} with some utility methods.
* Extension of {@link SparseArray} with some utility methods.
*/
public class LongArrayMap<E> extends LongSparseArray<E> implements Iterable<E> {
public class IntSparseArrayMap<E> extends SparseArray<E> implements Iterable<E> {
public boolean containsKey(long key) {
public boolean containsKey(int key) {
return indexOfKey(key) >= 0;
}
@@ -34,8 +34,8 @@ public class LongArrayMap<E> extends LongSparseArray<E> implements Iterable<E> {
}
@Override
public LongArrayMap<E> clone() {
return (LongArrayMap<E>) super.clone();
public IntSparseArrayMap<E> clone() {
return (IntSparseArrayMap<E>) super.clone();
}
@Override
@@ -105,7 +105,7 @@ public interface ItemInfoMatcher {
keys.contains(ShortcutKey.fromItemInfo(info));
}
static ItemInfoMatcher ofItemIds(LongArrayMap<Boolean> ids, Boolean matchDefault) {
static ItemInfoMatcher ofItemIds(IntSparseArrayMap<Boolean> ids, Boolean matchDefault) {
return (info, cn) -> ids.get(info.id, matchDefault);
}
}
@@ -75,7 +75,7 @@ public class ListViewHighlighter implements OnScrollListener, RecyclerListener,
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
highlightIfVisible(firstVisibleItem, firstVisibleItem + visibleItemCount);
highlightIfVisible(firstVisibleItem, firstVisibleItem + visibleItemCount - 1);
}
private boolean highlightIfVisible(int start, int end) {
@@ -19,6 +19,8 @@ package com.android.launcher3.util;
import android.util.Property;
import android.view.View;
import java.util.Arrays;
/**
* Utility class to handle separating a single value as a factor of multiple values
*/
@@ -55,6 +57,11 @@ public class MultiValueAlpha {
}
}
@Override
public String toString() {
return Arrays.toString(mMyProperties);
}
public AlphaProperty getProperty(int index) {
return mMyProperties[index];
}
@@ -97,5 +104,10 @@ public class MultiValueAlpha {
public float getValue() {
return mValue;
}
@Override
public String toString() {
return Float.toString(mValue);
}
}
}
@@ -0,0 +1,82 @@
/*
* Copyright (C) 2017 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.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
import android.provider.Settings;
/**
* Utility class to listen for secure settings changes
*/
public class SecureSettingsObserver extends ContentObserver {
/** Hidden field Settings.Secure.NOTIFICATION_BADGING */
public static final String NOTIFICATION_BADGING = "notification_badging";
private final ContentResolver mResolver;
private final String mKeySetting;
private final int mDefaultValue;
private final OnChangeListener mOnChangeListener;
public SecureSettingsObserver(ContentResolver resolver, OnChangeListener listener,
String keySetting, int defaultValue) {
super(new Handler());
mResolver = resolver;
mOnChangeListener = listener;
mKeySetting = keySetting;
mDefaultValue = defaultValue;
}
@Override
public void onChange(boolean selfChange) {
mOnChangeListener.onSettingsChanged(getValue());
}
public boolean getValue() {
return Settings.Secure.getInt(mResolver, mKeySetting, mDefaultValue) == 1;
}
public void register() {
mResolver.registerContentObserver(Settings.Secure.getUriFor(mKeySetting), false, this);
}
public ContentResolver getResolver() {
return mResolver;
}
public void dispatchOnChange() {
onChange(true);
}
public void unregister() {
mResolver.unregisterContentObserver(this);
}
public interface OnChangeListener {
void onSettingsChanged(boolean isEnabled);
}
public static SecureSettingsObserver newNotificationSettingsObserver(Context context,
OnChangeListener listener) {
return new SecureSettingsObserver(
context.getContentResolver(), listener, NOTIFICATION_BADGING, 1);
}
}
@@ -1,100 +0,0 @@
/*
* Copyright (C) 2017 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.content.ContentResolver;
import android.database.ContentObserver;
import android.os.Handler;
import android.provider.Settings;
public interface SettingsObserver {
/**
* Registers the content observer to call {@link #onSettingChanged(boolean)} when any of the
* passed settings change. The value passed to onSettingChanged() is based on the key setting.
*/
void register(String keySetting, String ... dependentSettings);
void unregister();
void onSettingChanged(boolean keySettingEnabled);
abstract class Secure extends ContentObserver implements SettingsObserver {
private ContentResolver mResolver;
private String mKeySetting;
public Secure(ContentResolver resolver) {
super(new Handler());
mResolver = resolver;
}
@Override
public void register(String keySetting, String ... dependentSettings) {
mKeySetting = keySetting;
mResolver.registerContentObserver(
Settings.Secure.getUriFor(mKeySetting), false, this);
for (String setting : dependentSettings) {
mResolver.registerContentObserver(
Settings.Secure.getUriFor(setting), false, this);
}
onChange(true);
}
@Override
public void unregister() {
mResolver.unregisterContentObserver(this);
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
onSettingChanged(Settings.Secure.getInt(mResolver, mKeySetting, 1) == 1);
}
}
abstract class System extends ContentObserver implements SettingsObserver {
private ContentResolver mResolver;
private String mKeySetting;
public System(ContentResolver resolver) {
super(new Handler());
mResolver = resolver;
}
@Override
public void register(String keySetting, String ... dependentSettings) {
mKeySetting = keySetting;
mResolver.registerContentObserver(
Settings.System.getUriFor(mKeySetting), false, this);
for (String setting : dependentSettings) {
mResolver.registerContentObserver(
Settings.System.getUriFor(setting), false, this);
}
onChange(true);
}
@Override
public void unregister() {
mResolver.unregisterContentObserver(this);
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
onSettingChanged(Settings.System.getInt(mResolver, mKeySetting, 1) == 1);
}
}
}
@@ -16,6 +16,10 @@
package com.android.launcher3.views;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
import android.content.Context;
@@ -35,6 +39,7 @@ import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.TouchController;
import java.io.PrintWriter;
import java.util.ArrayList;
/**
@@ -79,6 +84,9 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext>
protected TouchController mActiveController;
private TouchCompleteListener mTouchCompleteListener;
// Object controlling the current touch interaction
private Object mCurrentTouchOwner;
public BaseDragLayer(Context context, AttributeSet attrs, int alphaChannelCount) {
super(context, attrs);
mActivity = (T) ActivityContext.lookupContext(context);
@@ -94,7 +102,7 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext>
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
if (action == ACTION_UP || action == ACTION_CANCEL) {
if (mTouchCompleteListener != null) {
mTouchCompleteListener.onTouchComplete();
}
@@ -177,7 +185,7 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext>
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
if (action == ACTION_UP || action == ACTION_CANCEL) {
if (mTouchCompleteListener != null) {
mTouchCompleteListener.onTouchComplete();
}
@@ -192,6 +200,37 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext>
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return verifyTouchDispatch(this, ev) && super.dispatchTouchEvent(ev);
}
/**
* Returns true if the {@param caller} is allowed to dispatch {@param ev} on this view,
* false otherwise.
*/
public boolean verifyTouchDispatch(Object caller, MotionEvent ev) {
int action = ev.getAction();
if (action == ACTION_DOWN) {
if (mCurrentTouchOwner != null) {
// Another touch in progress.
ev.setAction(ACTION_CANCEL);
super.dispatchTouchEvent(ev);
ev.setAction(action);
}
mCurrentTouchOwner = caller;
return true;
}
if (mCurrentTouchOwner != caller) {
// Someone else is controlling the touch
return false;
}
if (action == ACTION_UP || action == ACTION_CANCEL) {
mCurrentTouchOwner = null;
}
return true;
}
/**
* Determine the rect of the descendant in this DragLayer's coordinates
*
@@ -319,6 +358,10 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext>
return mMultiValueAlpha.getProperty(index);
}
public void dumpAlpha(PrintWriter writer) {
writer.println(" dragLayerAlpha : " + mMultiValueAlpha );
}
public static class LayoutParams extends InsettableFrameLayout.LayoutParams {
public int x, y;
public boolean customPosition = false;
@@ -16,16 +16,13 @@
package com.android.launcher3.widget;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Handler;
import android.os.SystemClock;
import android.util.SparseBooleanArray;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -49,12 +46,10 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener;
import java.util.ArrayList;
/**
* {@inheritDoc}
*/
public class LauncherAppWidgetHostView extends AppWidgetHostView
public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
implements TouchCompleteListener, View.OnLongClickListener {
// Related to the auto-advancing of widgets
@@ -75,9 +70,6 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView
private float mSlop;
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mChildrenFocused;
private boolean mIsScrollable;
private boolean mIsAttachedToWindow;
private boolean mIsAutoAdvanceRegistered;
@@ -267,98 +259,6 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView
}
}
@Override
public int getDescendantFocusability() {
return mChildrenFocused ? ViewGroup.FOCUS_BEFORE_DESCENDANTS
: ViewGroup.FOCUS_BLOCK_DESCENDANTS;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (mChildrenFocused && event.getKeyCode() == KeyEvent.KEYCODE_ESCAPE
&& event.getAction() == KeyEvent.ACTION_UP) {
mChildrenFocused = false;
requestFocus();
return true;
}
return super.dispatchKeyEvent(event);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (!mChildrenFocused && keyCode == KeyEvent.KEYCODE_ENTER) {
event.startTracking();
return true;
}
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (event.isTracking()) {
if (!mChildrenFocused && keyCode == KeyEvent.KEYCODE_ENTER) {
mChildrenFocused = true;
ArrayList<View> focusableChildren = getFocusables(FOCUS_FORWARD);
focusableChildren.remove(this);
int childrenCount = focusableChildren.size();
switch (childrenCount) {
case 0:
mChildrenFocused = false;
break;
case 1: {
if (getTag() instanceof ItemInfo) {
ItemInfo item = (ItemInfo) getTag();
if (item.spanX == 1 && item.spanY == 1) {
focusableChildren.get(0).performClick();
mChildrenFocused = false;
return true;
}
}
// continue;
}
default:
focusableChildren.get(0).requestFocus();
return true;
}
}
}
return super.onKeyUp(keyCode, event);
}
@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
if (gainFocus) {
mChildrenFocused = false;
dispatchChildFocus(false);
}
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
}
@Override
public void requestChildFocus(View child, View focused) {
super.requestChildFocus(child, focused);
dispatchChildFocus(mChildrenFocused && focused != null);
if (focused != null) {
focused.setFocusableInTouchMode(false);
}
}
@Override
public void clearChildFocus(View child) {
super.clearChildFocus(child);
dispatchChildFocus(false);
}
@Override
public boolean dispatchUnhandledMove(View focused, int direction) {
return mChildrenFocused;
}
private void dispatchChildFocus(boolean childIsFocused) {
// The host view's background changes when selected, to indicate the focus is inside.
setSelected(childIsFocused);
}
public void switchToErrorView() {
// Update the widget with 0 Layout id, to reset the view to error view.
updateAppWidget(new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0));
@@ -502,4 +402,13 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView
mLauncher.removeItem(this, info, false /* deleteFromDb */);
mLauncher.bindAppWidget(info);
}
@Override
protected boolean shouldAllowDirectClick() {
if (getTag() instanceof ItemInfo) {
ItemInfo item = (ItemInfo) getTag();
return item.spanX == 1 && item.spanY == 1;
}
return false;
}
}
@@ -0,0 +1,136 @@
/*
* Copyright (C) 2018 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.widget;
import android.appwidget.AppWidgetHostView;
import android.content.Context;
import android.graphics.Rect;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import java.util.ArrayList;
/**
* Extension of AppWidgetHostView with support for controlled keyboard navigation.
*/
public abstract class NavigableAppWidgetHostView extends AppWidgetHostView {
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mChildrenFocused;
public NavigableAppWidgetHostView(Context context) {
super(context);
}
@Override
public int getDescendantFocusability() {
return mChildrenFocused ? ViewGroup.FOCUS_BEFORE_DESCENDANTS
: ViewGroup.FOCUS_BLOCK_DESCENDANTS;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (mChildrenFocused && event.getKeyCode() == KeyEvent.KEYCODE_ESCAPE
&& event.getAction() == KeyEvent.ACTION_UP) {
mChildrenFocused = false;
requestFocus();
return true;
}
return super.dispatchKeyEvent(event);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (!mChildrenFocused && keyCode == KeyEvent.KEYCODE_ENTER) {
event.startTracking();
return true;
}
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (event.isTracking()) {
if (!mChildrenFocused && keyCode == KeyEvent.KEYCODE_ENTER) {
mChildrenFocused = true;
ArrayList<View> focusableChildren = getFocusables(FOCUS_FORWARD);
focusableChildren.remove(this);
int childrenCount = focusableChildren.size();
switch (childrenCount) {
case 0:
mChildrenFocused = false;
break;
case 1: {
if (shouldAllowDirectClick()) {
focusableChildren.get(0).performClick();
mChildrenFocused = false;
return true;
}
// continue;
}
default:
focusableChildren.get(0).requestFocus();
return true;
}
}
}
return super.onKeyUp(keyCode, event);
}
/**
* For a widget with only a single interactive element, return true if whole widget should act
* as a single interactive element, and clicking 'enter' should activate the child element
* directly. Otherwise clicking 'enter' will only move the focus inside the widget.
*/
protected abstract boolean shouldAllowDirectClick();
@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
if (gainFocus) {
mChildrenFocused = false;
dispatchChildFocus(false);
}
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
}
@Override
public void requestChildFocus(View child, View focused) {
super.requestChildFocus(child, focused);
dispatchChildFocus(mChildrenFocused && focused != null);
if (focused != null) {
focused.setFocusableInTouchMode(false);
}
}
@Override
public void clearChildFocus(View child) {
super.clearChildFocus(child);
dispatchChildFocus(false);
}
@Override
public boolean dispatchUnhandledMove(View focused, int direction) {
return mChildrenFocused;
}
private void dispatchChildFocus(boolean childIsFocused) {
// The host view's background changes when selected, to indicate the focus is inside.
setSelected(childIsFocused);
}
}
@@ -16,10 +16,13 @@
package com.android.launcher3.config;
import android.content.Context;
/**
* Defines a set of flags used to control various launcher behaviors
*/
public final class FeatureFlags extends BaseFlags {
private FeatureFlags() {}
private FeatureFlags() {
// Prevent instantiation
}
}
+3
View File
@@ -0,0 +1,3 @@
This directory contains plugin interfaces that launcher listens for and plugins implement. In other words, these are the hooks that specify what plugins launcher currently supports.
Details about how to create a new plugin interface, or to use existing interfaces to write a plugin can be found at go/gnl/plugins.
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2018 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.systemui.plugins;
import android.view.View;
import android.view.ViewGroup;
import com.android.systemui.plugins.annotations.ProvidesInterface;
/**
* Implement this plugin interface to add a row of views to the top of the all apps drawer.
*/
@ProvidesInterface(action = AllAppsRow.ACTION, version = AllAppsRow.VERSION)
public interface AllAppsRow extends Plugin {
String ACTION = "com.android.systemui.action.PLUGIN_ALL_APPS_ACTIONS";
int VERSION = 1;
/**
* Setup the row and return the parent view.
* @param parent The ViewGroup to which launcher will add this row.
*/
View setup(ViewGroup parent);
/**
* @return The height to reserve in all apps for your views.
*/
int getExpectedHeight();
/**
* Update launcher whenever {@link #getExpectedHeight()} changes.
*/
void setOnHeightUpdatedListener(OnHeightUpdatedListener onHeightUpdatedListener);
interface OnHeightUpdatedListener {
void onHeightUpdated();
}
}
@@ -0,0 +1,40 @@
/*
* Copyright (C) 2018 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.uioverrides.plugins;
import android.content.Context;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
public class PluginManagerWrapper {
public static final MainThreadInitializedObject<PluginManagerWrapper> INSTANCE =
new MainThreadInitializedObject<>(PluginManagerWrapper::new);
private PluginManagerWrapper(Context c) {
}
public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass) {
}
public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass,
boolean allowMultiple) {
}
public void removePluginListener(PluginListener<? extends Plugin> listener) {
}
}
+2 -1
View File
@@ -27,7 +27,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
LOCAL_SRC_FILES := $(call all-java-files-under, tapl) \
../quickstep/src/com/android/quickstep/SwipeUpSetting.java \
../src/com/android/launcher3/TestProtocol.java
../src/com/android/launcher3/util/SecureSettingsObserver.java \
../src/com/android/launcher3/TestProtocol.java \
LOCAL_SDK_VERSION := current
LOCAL_MODULE := ub-launcher-aosp-tapl
+28
View File
@@ -67,5 +67,33 @@
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<provider
android:name="com.android.launcher3.testcomponent.TestCommandReceiver"
android:authorities="${packageName}.commands"
android:exported="true" />
<activity
android:name="com.android.launcher3.testcomponent.TestLauncherActivity"
android:launchMode="singleTask"
android:clearTaskOnLaunch="true"
android:label="Test launcher"
android:stateNotNeeded="true"
android:theme="@android:style/Theme.DeviceDefault.Light"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="unspecified"
android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
android:resizeableActivity="true"
android:taskAffinity=""
android:process=":testLauncherProcess"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.MONKEY"/>
<category android:name="android.intent.category.LAUNCHER_APP" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -23,7 +23,8 @@ import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.LongArrayMap;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
import org.junit.Before;
import org.junit.Test;
@@ -43,15 +44,15 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
private final ComponentName mComponent1 = new ComponentName("a", "b");
private final ComponentName mComponent2 = new ComponentName("b", "b");
private ArrayList<Long> existingScreens;
private ArrayList<Long> newScreens;
private LongArrayMap<GridOccupancy> screenOccupancy;
private IntArray existingScreens;
private IntArray newScreens;
private IntSparseArrayMap<GridOccupancy> screenOccupancy;
@Before
public void initData() throws Exception {
existingScreens = new ArrayList<>();
screenOccupancy = new LongArrayMap<>();
newScreens = new ArrayList<>();
existingScreens = new IntArray();
screenOccupancy = new IntSparseArrayMap<>();
newScreens = new IntArray();
idp.numColumns = 5;
idp.numRows = 5;
@@ -65,7 +66,7 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
return new AddWorkspaceItemsTask(list) {
@Override
protected void updateScreens(Context context, ArrayList<Long> workspaceScreens) { }
protected void updateScreens(Context context, IntArray workspaceScreens) { }
};
}
@@ -77,18 +78,18 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
// Second screen has 2 holes of sizes 3x2 and 2x3
setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
Pair<Long, int[]> spaceFound = newTask()
int[] spaceFound = newTask()
.findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 1, 1);
assertEquals(2L, (long) spaceFound.first);
assertTrue(screenOccupancy.get(spaceFound.first)
.isRegionVacant(spaceFound.second[0], spaceFound.second[1], 1, 1));
assertEquals(2, spaceFound[0]);
assertTrue(screenOccupancy.get(spaceFound[0])
.isRegionVacant(spaceFound[1], spaceFound[2], 1, 1));
// Find a larger space
spaceFound = newTask()
.findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 2, 3);
assertEquals(2L, (long) spaceFound.first);
assertTrue(screenOccupancy.get(spaceFound.first)
.isRegionVacant(spaceFound.second[0], spaceFound.second[1], 2, 3));
assertEquals(2, spaceFound[0]);
assertTrue(screenOccupancy.get(spaceFound[0])
.isRegionVacant(spaceFound[1], spaceFound[2], 2, 3));
}
@Test
@@ -97,11 +98,11 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
commitScreensToDb();
ArrayList<Long> oldScreens = new ArrayList<>(existingScreens);
Pair<Long, int[]> spaceFound = newTask()
IntArray oldScreens = existingScreens.clone();
int[] spaceFound = newTask()
.findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 3, 3);
assertFalse(oldScreens.contains(spaceFound.first));
assertTrue(newScreens.contains(spaceFound.first));
assertFalse(oldScreens.contains(spaceFound[0]));
assertTrue(newScreens.contains(spaceFound[0]));
}
@Test
@@ -135,7 +136,7 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
// only info2 should be added because info was already added to the workspace
// in setupWorkspaceWithHoles()
verify(callbacks).bindAppsAdded(any(ArrayList.class), notAnimated.capture(),
verify(callbacks).bindAppsAdded(any(IntArray.class), notAnimated.capture(),
animated.capture());
assertTrue(notAnimated.getValue().isEmpty());
@@ -143,7 +144,7 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
assertTrue(animated.getValue().contains(info2));
}
private int setupWorkspaceWithHoles(int startId, long screenId, Rect... holes) {
private int setupWorkspaceWithHoles(int startId, int screenId, Rect... holes) {
GridOccupancy occupancy = new GridOccupancy(idp.numColumns, idp.numRows);
occupancy.markCells(0, 0, idp.numColumns, idp.numRows, true);
for (Rect r : holes) {
@@ -183,7 +184,7 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
int count = existingScreens.size();
for (int i = 0; i < count; i++) {
ContentValues v = new ContentValues();
long screenId = existingScreens.get(i);
int screenId = existingScreens.get(i);
v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
@@ -53,7 +53,7 @@ public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase {
// Verify that only the app icons of app1 (id 1 & 2) are updated. Custom shortcut (id 7)
// is not updated
verifyUpdate(1L, 2L);
verifyUpdate(1, 2);
// Verify that only app1 var updated in allAppsList
assertFalse(allAppsList.data.isEmpty());
@@ -80,11 +80,11 @@ public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase {
// app3 has only restored apps (id 5, 6) and shortcuts (id 9). Verify that only apps were
// were updated
verifyUpdate(5L, 6L);
verifyUpdate(5, 6);
}
private void verifyUpdate(Long... idsUpdated) {
HashSet<Long> updates = new HashSet<>(Arrays.asList(idsUpdated));
private void verifyUpdate(Integer... idsUpdated) {
HashSet<Integer> updates = new HashSet<>(Arrays.asList(idsUpdated));
for (ItemInfo info : bgDataModel.itemsIdMap) {
if (updates.contains(info.id)) {
assertEquals(NEW_LABEL_PREFIX + info.id, info.title);
@@ -17,6 +17,7 @@ import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.model.GridSizeMigrationTask.MultiStepMigrationTask;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.TestLauncherProvider;
import org.junit.Before;
@@ -24,7 +25,6 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
@@ -43,8 +43,8 @@ public class GridSizeMigrationTaskTest {
new ProviderTestRule.Builder(TestLauncherProvider.class, LauncherProvider.AUTHORITY)
.build();
private static final long DESKTOP = LauncherSettings.Favorites.CONTAINER_DESKTOP;
private static final long HOTSEAT = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
private static final int DESKTOP = LauncherSettings.Favorites.CONTAINER_DESKTOP;
private static final int HOTSEAT = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
private static final int APPLICATION = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
private static final int SHORTCUT = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
@@ -75,7 +75,7 @@ public class GridSizeMigrationTaskTest {
@Test
public void testHotseatMigration_apps_dropped() throws Exception {
long[] hotseatItems = {
int[] hotseatItems = {
addItem(APPLICATION, 0, HOTSEAT, 0, 0),
addItem(SHORTCUT, 1, HOTSEAT, 0, 0),
-1,
@@ -92,7 +92,7 @@ public class GridSizeMigrationTaskTest {
@Test
public void testHotseatMigration_shortcuts_dropped() throws Exception {
long[] hotseatItems = {
int[] hotseatItems = {
addItem(APPLICATION, 0, HOTSEAT, 0, 0),
addItem(30, 1, HOTSEAT, 0, 0),
-1,
@@ -107,11 +107,11 @@ public class GridSizeMigrationTaskTest {
verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
}
private void verifyHotseat(long... sortedIds) {
private void verifyHotseat(int... sortedIds) {
int screenId = 0;
int total = 0;
for (long id : sortedIds) {
for (int id : sortedIds) {
Cursor c = mProviderRule.getResolver().query(LauncherSettings.Favorites.CONTENT_URI,
new String[]{LauncherSettings.Favorites._ID},
"container=-101 and screen=" + screenId, null, null, null);
@@ -139,7 +139,7 @@ public class GridSizeMigrationTaskTest {
@Test
public void testWorkspace_empty_row_column_removed() throws Exception {
long[][][] ids = createGrid(new int[][][]{{
int[][][] ids = createGrid(new int[][][]{{
{ 0, 0, -1, 1},
{ 3, 1, -1, 4},
{ -1, -1, -1, -1},
@@ -150,7 +150,7 @@ public class GridSizeMigrationTaskTest {
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Column 2 and row 2 got removed.
verifyWorkspace(new long[][][] {{
verifyWorkspace(new int[][][] {{
{ids[0][0][0], ids[0][0][1], ids[0][0][3]},
{ids[0][1][0], ids[0][1][1], ids[0][1][3]},
{ids[0][3][0], ids[0][3][1], ids[0][3][3]},
@@ -159,7 +159,7 @@ public class GridSizeMigrationTaskTest {
@Test
public void testWorkspace_new_screen_created() throws Exception {
long[][][] ids = createGrid(new int[][][]{{
int[][][] ids = createGrid(new int[][][]{{
{ 0, 0, 0, 1},
{ 3, 1, 0, 4},
{ -1, -1, -1, -1},
@@ -170,7 +170,7 @@ public class GridSizeMigrationTaskTest {
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Items in the second column get moved to new screen
verifyWorkspace(new long[][][] {{
verifyWorkspace(new int[][][] {{
{ids[0][0][0], ids[0][0][1], ids[0][0][3]},
{ids[0][1][0], ids[0][1][1], ids[0][1][3]},
{ids[0][3][0], ids[0][3][1], ids[0][3][3]},
@@ -181,7 +181,7 @@ public class GridSizeMigrationTaskTest {
@Test
public void testWorkspace_items_merged_in_next_screen() throws Exception {
long[][][] ids = createGrid(new int[][][]{{
int[][][] ids = createGrid(new int[][][]{{
{ 0, 0, 0, 1},
{ 3, 1, 0, 4},
{ -1, -1, -1, -1},
@@ -196,7 +196,7 @@ public class GridSizeMigrationTaskTest {
// Items in the second column of the first screen should get placed on the 3rd
// row of the second screen
verifyWorkspace(new long[][][] {{
verifyWorkspace(new int[][][] {{
{ids[0][0][0], ids[0][0][1], ids[0][0][3]},
{ids[0][1][0], ids[0][1][1], ids[0][1][3]},
{ids[0][3][0], ids[0][3][1], ids[0][3][3]},
@@ -211,7 +211,7 @@ public class GridSizeMigrationTaskTest {
public void testWorkspace_items_not_merged_in_next_screen() throws Exception {
// First screen has 2 items that need to be moved, but second screen has only one
// empty space after migration (top-left corner)
long[][][] ids = createGrid(new int[][][]{{
int[][][] ids = createGrid(new int[][][]{{
{ 0, 0, 0, 1},
{ 3, 1, 0, 4},
{ -1, -1, -1, -1},
@@ -227,7 +227,7 @@ public class GridSizeMigrationTaskTest {
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Items in the second column of the first screen should get placed on a new screen.
verifyWorkspace(new long[][][] {{
verifyWorkspace(new int[][][] {{
{ids[0][0][0], ids[0][0][1], ids[0][0][3]},
{ids[0][1][0], ids[0][1][1], ids[0][1][3]},
{ids[0][3][0], ids[0][3][1], ids[0][3][3]},
@@ -244,7 +244,7 @@ public class GridSizeMigrationTaskTest {
public void testWorkspace_first_row_blocked() throws Exception {
// The first screen has one item on the 4th column which needs moving, as the first row
// will be kept empty.
long[][][] ids = createGrid(new int[][][]{{
int[][][] ids = createGrid(new int[][][]{{
{ -1, -1, -1, -1},
{ 3, 1, 7, 0},
{ 8, 7, 7, -1},
@@ -255,7 +255,7 @@ public class GridSizeMigrationTaskTest {
new Point(4, 4), new Point(3, 4)).migrateWorkspace();
// Items in the second column of the first screen should get placed on a new screen.
verifyWorkspace(new long[][][] {{
verifyWorkspace(new int[][][] {{
{ -1, -1, -1},
{ids[0][1][0], ids[0][1][1], ids[0][1][2]},
{ids[0][2][0], ids[0][2][1], ids[0][2][2]},
@@ -268,7 +268,7 @@ public class GridSizeMigrationTaskTest {
@Test
public void testWorkspace_items_moved_to_empty_first_row() throws Exception {
// Items will get moved to the next screen to keep the first screen empty.
long[][][] ids = createGrid(new int[][][]{{
int[][][] ids = createGrid(new int[][][]{{
{ -1, -1, -1, -1},
{ 0, 1, 0, 0},
{ 8, 7, 7, -1},
@@ -279,7 +279,7 @@ public class GridSizeMigrationTaskTest {
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Items in the second column of the first screen should get placed on a new screen.
verifyWorkspace(new long[][][] {{
verifyWorkspace(new int[][][] {{
{ -1, -1, -1},
{ids[0][2][0], ids[0][2][1], ids[0][2][2]},
{ids[0][3][0], ids[0][3][1], ids[0][3][2]},
@@ -289,7 +289,7 @@ public class GridSizeMigrationTaskTest {
}});
}
private long[][][] createGrid(int[][][] typeArray) throws Exception {
private int[][][] createGrid(int[][][] typeArray) throws Exception {
return createGrid(typeArray, 1);
}
@@ -300,14 +300,14 @@ public class GridSizeMigrationTaskTest {
* two represent the workspace grid.
* @return the same grid representation where each entry is the corresponding item id.
*/
private long[][][] createGrid(int[][][] typeArray, long startScreen) throws Exception {
private int[][][] createGrid(int[][][] typeArray, int startScreen) throws Exception {
LauncherSettings.Settings.call(mProviderRule.getResolver(),
LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
long[][][] ids = new long[typeArray.length][][];
int[][][] ids = new int[typeArray.length][][];
for (int i = 0; i < typeArray.length; i++) {
// Add screen to DB
long screenId = startScreen + i;
int screenId = startScreen + i;
// Keep the screen id counter up to date
LauncherSettings.Settings.call(mProviderRule.getResolver(),
@@ -318,9 +318,9 @@ public class GridSizeMigrationTaskTest {
v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
mProviderRule.getResolver().insert(LauncherSettings.WorkspaceScreens.CONTENT_URI, v);
ids[i] = new long[typeArray[i].length][];
ids[i] = new int[typeArray[i].length][];
for (int y = 0; y < typeArray[i].length; y++) {
ids[i][y] = new long[typeArray[i][y].length];
ids[i][y] = new int[typeArray[i][y].length];
for (int x = 0; x < typeArray[i][y].length; x++) {
if (typeArray[i][y][x] < 0) {
// Empty cell
@@ -339,16 +339,16 @@ public class GridSizeMigrationTaskTest {
* @param ids A 3d array where the first dimension represents the screen, and the rest two
* represent the workspace grid.
*/
private void verifyWorkspace(long[][][] ids) {
ArrayList<Long> allScreens = LauncherModel.loadWorkspaceScreensDb(mContext);
private void verifyWorkspace(int[][][] ids) {
IntArray allScreens = LauncherModel.loadWorkspaceScreensDb(mContext);
assertEquals(ids.length, allScreens.size());
int total = 0;
for (int i = 0; i < ids.length; i++) {
long screenId = allScreens.get(i);
int screenId = allScreens.get(i);
for (int y = 0; y < ids[i].length; y++) {
for (int x = 0; x < ids[i][y].length; x++) {
long id = ids[i][y][x];
int id = ids[i][y][x];
Cursor c = mProviderRule.getResolver().query(
LauncherSettings.Favorites.CONTENT_URI,
@@ -382,10 +382,10 @@ public class GridSizeMigrationTaskTest {
* @param type {@link #APPLICATION} or {@link #SHORTCUT} or >= 2 for
* folder (where the type represents the number of items in the folder).
*/
private long addItem(int type, long screen, long container, int x, int y) throws Exception {
long id = LauncherSettings.Settings.call(mProviderRule.getResolver(),
private int addItem(int type, int screen, int container, int x, int y) throws Exception {
int id = LauncherSettings.Settings.call(mProviderRule.getResolver(),
LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
.getLong(LauncherSettings.Settings.EXTRA_VALUE);
.getInt(LauncherSettings.Settings.EXTRA_VALUE);
ContentValues values = new ContentValues();
values.put(LauncherSettings.Favorites._ID, id);
@@ -18,6 +18,7 @@ import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.util.IntArray;
import org.junit.Before;
import org.junit.Test;
@@ -149,83 +150,83 @@ public class LoaderCursorTest {
@Test
public void checkItemPlacement_wrongWorkspaceScreen() {
ArrayList<Long> workspaceScreens = new ArrayList<>(Arrays.asList(1L, 3L));
IntArray workspaceScreens = IntArray.wrap(1, 3);
mIDP.numRows = 4;
mIDP.numColumns = 4;
mIDP.numHotseatIcons = 3;
// Item on unknown screen are not placed
assertFalse(mLoaderCursor.checkItemPlacement(
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 4L), workspaceScreens));
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 4), workspaceScreens));
assertFalse(mLoaderCursor.checkItemPlacement(
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 5L), workspaceScreens));
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 5), workspaceScreens));
assertFalse(mLoaderCursor.checkItemPlacement(
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2L), workspaceScreens));
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2), workspaceScreens));
assertTrue(mLoaderCursor.checkItemPlacement(
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens));
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1), workspaceScreens));
assertTrue(mLoaderCursor.checkItemPlacement(
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 3L), workspaceScreens));
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 3), workspaceScreens));
}
@Test
public void checkItemPlacement_outsideBounds() {
ArrayList<Long> workspaceScreens = new ArrayList<>(Arrays.asList(1L, 2L));
IntArray workspaceScreens = IntArray.wrap(1, 2);
mIDP.numRows = 4;
mIDP.numColumns = 4;
mIDP.numHotseatIcons = 3;
// Item outside screen bounds are not placed
assertFalse(mLoaderCursor.checkItemPlacement(
newItemInfo(4, 4, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens));
newItemInfo(4, 4, 1, 1, CONTAINER_DESKTOP, 1), workspaceScreens));
}
@Test
public void checkItemPlacement_overlappingItems() {
ArrayList<Long> workspaceScreens = new ArrayList<>(Arrays.asList(1L, 2L));
IntArray workspaceScreens = IntArray.wrap(1, 2);
mIDP.numRows = 4;
mIDP.numColumns = 4;
mIDP.numHotseatIcons = 3;
// Overlapping items are not placed
assertTrue(mLoaderCursor.checkItemPlacement(
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens));
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1), workspaceScreens));
assertFalse(mLoaderCursor.checkItemPlacement(
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens));
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1), workspaceScreens));
assertTrue(mLoaderCursor.checkItemPlacement(
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2L), workspaceScreens));
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2), workspaceScreens));
assertFalse(mLoaderCursor.checkItemPlacement(
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2L), workspaceScreens));
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2), workspaceScreens));
assertTrue(mLoaderCursor.checkItemPlacement(
newItemInfo(1, 1, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens));
newItemInfo(1, 1, 1, 1, CONTAINER_DESKTOP, 1), workspaceScreens));
assertTrue(mLoaderCursor.checkItemPlacement(
newItemInfo(2, 2, 2, 2, CONTAINER_DESKTOP, 1L), workspaceScreens));
newItemInfo(2, 2, 2, 2, CONTAINER_DESKTOP, 1), workspaceScreens));
assertFalse(mLoaderCursor.checkItemPlacement(
newItemInfo(3, 2, 1, 2, CONTAINER_DESKTOP, 1L), workspaceScreens));
newItemInfo(3, 2, 1, 2, CONTAINER_DESKTOP, 1), workspaceScreens));
}
@Test
public void checkItemPlacement_hotseat() {
ArrayList<Long> workspaceScreens = new ArrayList<>();
IntArray workspaceScreens = new IntArray();
mIDP.numRows = 4;
mIDP.numColumns = 4;
mIDP.numHotseatIcons = 3;
// Hotseat items are only placed based on screenId
assertTrue(mLoaderCursor.checkItemPlacement(
newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 1L), workspaceScreens));
newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 1), workspaceScreens));
assertTrue(mLoaderCursor.checkItemPlacement(
newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 2L), workspaceScreens));
newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 2), workspaceScreens));
assertFalse(mLoaderCursor.checkItemPlacement(
newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 3L), workspaceScreens));
newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 3), workspaceScreens));
}
private ItemInfo newItemInfo(int cellX, int cellY, int spanX, int spanY,
long container, long screenId) {
int container, int screenId) {
ItemInfo info = new ItemInfo();
info.cellX = cellX;
info.cellY = cellY;
@@ -48,18 +48,18 @@ public class PackageInstallStateChangedTaskTest extends BaseModelUpdateTaskTestC
public void testSessionUpdate_shortcuts_updated() throws Exception {
executeTaskForTest(newTask("app3", 30));
verifyProgressUpdate(30, 5L, 6L, 7L);
verifyProgressUpdate(30, 5, 6, 7);
}
@Test
public void testSessionUpdate_widgets_updated() throws Exception {
executeTaskForTest(newTask("app4", 30));
verifyProgressUpdate(30, 8L, 9L);
verifyProgressUpdate(30, 8, 9);
}
private void verifyProgressUpdate(int progress, Long... idsUpdated) {
HashSet<Long> updates = new HashSet<>(Arrays.asList(idsUpdated));
private void verifyProgressUpdate(int progress, Integer... idsUpdated) {
HashSet<Integer> updates = new HashSet<>(Arrays.asList(idsUpdated));
for (ItemInfo info : bgDataModel.itemsIdMap) {
if (info instanceof ShortcutInfo) {
assertEquals(updates.contains(info.id) ? progress: 0,
@@ -0,0 +1,96 @@
/*
* Copyright (C) 2018 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.testcomponent;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
import static android.content.pm.PackageManager.DONT_KILL_APP;
import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import androidx.test.InstrumentationRegistry;
/**
* Content provider to receive commands from tests
*/
public class TestCommandReceiver extends ContentProvider {
public static final String ENABLE_TEST_LAUNCHER = "enable-test-launcher";
public static final String DISABLE_TEST_LAUNCHER = "disable-test-launcher";
@Override
public boolean onCreate() {
return true;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("unimplemented mock method");
}
@Override
public String getType(Uri uri) {
throw new UnsupportedOperationException("unimplemented mock method");
}
@Override
public Uri insert(Uri uri, ContentValues values) {
throw new UnsupportedOperationException("unimplemented mock method");
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
throw new UnsupportedOperationException("unimplemented mock method");
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("unimplemented mock method");
}
@Override
public Bundle call(String method, String arg, Bundle extras) {
switch (method) {
case ENABLE_TEST_LAUNCHER: {
getContext().getPackageManager().setComponentEnabledSetting(
new ComponentName(getContext(), TestLauncherActivity.class),
COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
return null;
}
case DISABLE_TEST_LAUNCHER: {
getContext().getPackageManager().setComponentEnabledSetting(
new ComponentName(getContext(), TestLauncherActivity.class),
COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP);
return null;
}
}
return super.call(method, arg, extras);
}
public static Bundle callCommand(String command) {
Instrumentation inst = InstrumentationRegistry.getInstrumentation();
Uri uri = Uri.parse("content://" + inst.getContext().getPackageName() + ".commands");
return inst.getTargetContext().getContentResolver().call(uri, command, null, null);
}
}
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2018 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.testcomponent;
import static android.content.Intent.ACTION_MAIN;
import static android.content.Intent.CATEGORY_LAUNCHER;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED;
import android.app.LauncherActivity;
import android.content.Intent;
public class TestLauncherActivity extends LauncherActivity {
@Override
protected Intent getTargetIntent() {
return new Intent(ACTION_MAIN, null)
.addCategory(CATEGORY_LAUNCHER)
.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
}
}
@@ -15,26 +15,21 @@
*/
package com.android.launcher3.ui;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.fail;
import android.app.Instrumentation;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.LauncherActivityInfo;
import android.graphics.Point;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import android.view.MotionEvent;
import android.view.Surface;
import androidx.test.InstrumentationRegistry;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.Direction;
import androidx.test.uiautomator.UiDevice;
@@ -43,23 +38,28 @@ import androidx.test.uiautomator.Until;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherState;
import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.R;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.testcomponent.AppWidgetNoConfig;
import com.android.launcher3.testcomponent.AppWidgetWithConfig;
import com.android.launcher3.tapl.TestHelpers;
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.rule.LauncherActivityRule;
import com.android.launcher3.util.rule.ShellCommandRule;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.runners.model.Statement;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -76,39 +76,88 @@ public abstract class AbstractLauncherUiTest {
public static final long SHORT_UI_TIMEOUT= 300;
public static final long DEFAULT_UI_TIMEOUT = 10000;
public static final long DEFAULT_WORKER_TIMEOUT_SECS = 5;
protected MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
protected final UiDevice mDevice;
protected final LauncherInstrumentation mLauncher;
protected Context mTargetContext;
protected String mTargetPackage;
protected final boolean mIsInLauncherProcess;
private static final String TAG = "AbstractLauncherUiTest";
protected AbstractLauncherUiTest() {
final Instrumentation instrumentation = getInstrumentation();
mDevice = UiDevice.getInstance(instrumentation);
try {
mDevice.setOrientationNatural();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
if (TestHelpers.isInLauncherProcess()) Utilities.enableRunningInTestHarnessForTests();
mLauncher = new LauncherInstrumentation(instrumentation);
mIsInLauncherProcess = instrumentation.getTargetContext().getPackageName().equals(
mDevice.getLauncherPackageName());
}
@Rule
public LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
@Rule public ShellCommandRule mDefaultLauncherRule =
TestHelpers.isInLauncherProcess() ? ShellCommandRule.setDefaultLauncher() : null;
@Rule public ShellCommandRule mDisableHeadsUpNotification =
ShellCommandRule.disableHeadsUpNotification();
// Annotation for tests that need to be run in portrait and landscape modes.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
protected @interface PortraitLandscape {
}
@Rule
public TestRule mPortraitLandscapeExecutor =
(base, description) -> false && description.getAnnotation(PortraitLandscape.class)
!= null ? new Statement() {
@Override
public void evaluate() throws Throwable {
try {
// Create launcher activity if necessary and bring it to the front.
mDevice.pressHome();
waitForLauncherCondition("Launcher activity wasn't created",
launcher -> launcher != null);
executeOnLauncher(launcher ->
launcher.getRotationHelper().forceAllowRotationForTesting(true));
evaluateInPortrait();
evaluateInLandscape();
} finally {
mDevice.setOrientationNatural();
executeOnLauncher(launcher ->
launcher.getRotationHelper().forceAllowRotationForTesting(false));
mLauncher.setExpectedRotation(Surface.ROTATION_0);
}
}
private void evaluateInPortrait() throws Throwable {
mDevice.setOrientationNatural();
mLauncher.setExpectedRotation(Surface.ROTATION_0);
base.evaluate();
}
private void evaluateInLandscape() throws Throwable {
mDevice.setOrientationLeft();
mLauncher.setExpectedRotation(Surface.ROTATION_90);
base.evaluate();
}
} : base;
@Before
public void setUp() throws Exception {
mTargetContext = InstrumentationRegistry.getTargetContext();
mTargetPackage = mTargetContext.getPackageName();
mDevice.executeShellCommand("settings put global heads_up_notifications_enabled 0");
}
@After
public void tearDown() throws Exception {
mDevice.executeShellCommand("settings put global heads_up_notifications_enabled 1");
// Limits UI tests affecting tests running after them.
waitForModelLoaded();
}
protected void lockRotation(boolean naturalOrientation) throws RemoteException {
@@ -119,34 +168,6 @@ public abstract class AbstractLauncherUiTest {
}
}
protected Instrumentation getInstrumentation() {
return InstrumentationRegistry.getInstrumentation();
}
/**
* Opens all apps and returns the recycler view
*/
protected UiObject2 openAllApps() {
mDevice.waitForIdle();
UiObject2 hotseat = mDevice.wait(
Until.findObject(getSelectorForId(R.id.hotseat)), 2500);
Point start = hotseat.getVisibleCenter();
int endY = (int) (mDevice.getDisplayHeight() * 0.1f);
// 100 px/step
mDevice.swipe(start.x, start.y, start.x, endY, (start.y - endY) / 100);
return findViewById(R.id.apps_list_view);
}
/**
* Opens widget tray and returns the recycler view.
*/
protected UiObject2 openWidgetsTray() {
mDevice.pressMenu(); // Enter overview mode.
mDevice.wait(Until.findObject(
By.text(mTargetContext.getString(R.string.widget_button_text))), DEFAULT_UI_TIMEOUT).click();
return findViewById(R.id.widgets_list_view);
}
/**
* Scrolls the {@param container} until it finds an object matching {@param condition}.
* @return the matching object.
@@ -168,73 +189,6 @@ public abstract class AbstractLauncherUiTest {
}
}
/**
* Drags an icon to the center of homescreen.
* @param icon object that is either app icon or shortcut icon
*/
protected void dragToWorkspace(UiObject2 icon, boolean expectedToShowShortcuts) {
Point center = icon.getVisibleCenter();
// Action Down
sendPointer(MotionEvent.ACTION_DOWN, center);
UiObject2 dragLayer = findViewById(R.id.drag_layer);
if (expectedToShowShortcuts) {
// Make sure shortcuts show up, and then move a bit to hide them.
assertNotNull(findViewById(R.id.deep_shortcuts_container));
Point moveLocation = new Point(center);
int distanceToMove = mTargetContext.getResources().getDimensionPixelSize(
R.dimen.deep_shortcuts_start_drag_threshold) + 50;
if (moveLocation.y - distanceToMove >= dragLayer.getVisibleBounds().top) {
moveLocation.y -= distanceToMove;
} else {
moveLocation.y += distanceToMove;
}
movePointer(center, moveLocation);
assertNull(findViewById(R.id.deep_shortcuts_container));
}
// Wait until Remove/Delete target is visible
assertNotNull(findViewById(R.id.delete_target_text));
Point moveLocation = dragLayer.getVisibleCenter();
// Move to center
movePointer(center, moveLocation);
sendPointer(MotionEvent.ACTION_UP, center);
// Wait until remove target is gone.
mDevice.wait(Until.gone(getSelectorForId(R.id.delete_target_text)), DEFAULT_UI_TIMEOUT);
}
private void movePointer(Point from, Point to) {
while(!from.equals(to)) {
from.x = getNextMoveValue(to.x, from.x);
from.y = getNextMoveValue(to.y, from.y);
sendPointer(MotionEvent.ACTION_MOVE, from);
}
}
private int getNextMoveValue(int targetValue, int oldValue) {
if (targetValue - oldValue > 10) {
return oldValue + 10;
} else if (targetValue - oldValue < -10) {
return oldValue - 10;
} else {
return targetValue;
}
}
protected void sendPointer(int action, Point point) {
MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(), action, point.x, point.y, 0);
getInstrumentation().sendPointerSync(event);
event.recycle();
}
/**
* Removes all icons from homescreen and hotseat.
*/
@@ -247,6 +201,11 @@ public abstract class AbstractLauncherUiTest {
}
protected void resetLoaderState() {
if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
&& com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
android.util.Log.d("b/117332845",
"START " + android.util.Log.getStackTraceString(new Throwable()));
}
try {
mMainThreadExecutor.execute(new Runnable() {
@Override
@@ -257,6 +216,19 @@ public abstract class AbstractLauncherUiTest {
} catch (Throwable t) {
throw new IllegalArgumentException(t);
}
waitForModelLoaded();
if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
&& com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
android.util.Log.d("b/117332845",
"FINISH " + android.util.Log.getStackTraceString(new Throwable()));
}
}
protected void waitForModelLoaded() {
waitForLauncherCondition("Launcher model didn't load", launcher -> {
final LauncherModel model = LauncherAppState.getInstance(mTargetContext).getModel();
return model.getCallback() == null || model.isModelLoaded();
});
}
/**
@@ -271,7 +243,7 @@ public abstract class AbstractLauncherUiTest {
}
protected <T> T getFromLauncher(Function<Launcher, T> f) {
if (!mIsInLauncherProcess) return null;
if (!TestHelpers.isInLauncherProcess()) return null;
return getOnUiThread(() -> f.apply(mActivityMonitor.getActivity()));
}
@@ -284,53 +256,23 @@ public abstract class AbstractLauncherUiTest {
// Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call expecting
// the results of that gesture because the wait can hide flakeness.
protected boolean waitForState(LauncherState state) {
return waitForLauncherCondition(launcher -> launcher.getStateManager().getState() == state);
protected void waitForState(String message, LauncherState state) {
waitForLauncherCondition(message,
launcher -> launcher.getStateManager().getState() == state);
}
// Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
// flakiness.
protected boolean waitForLauncherCondition(Function<Launcher, Boolean> condition) {
return waitForLauncherCondition(condition, DEFAULT_ACTIVITY_TIMEOUT);
protected void waitForLauncherCondition(String message, Function<Launcher, Boolean> condition) {
waitForLauncherCondition(message, condition, DEFAULT_ACTIVITY_TIMEOUT);
}
// Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
// flakiness.
protected boolean waitForLauncherCondition(
Function<Launcher, Boolean> condition, long timeout) {
if (!mIsInLauncherProcess) return true;
return Wait.atMost(() -> getFromLauncher(condition), timeout);
}
/**
* Finds a widget provider which can fit on the home screen.
* @param hasConfigureScreen if true, a provider with a config screen is returned.
*/
protected LauncherAppWidgetProviderInfo findWidgetProvider(final boolean hasConfigureScreen) {
LauncherAppWidgetProviderInfo info =
getOnUiThread(new Callable<LauncherAppWidgetProviderInfo>() {
@Override
public LauncherAppWidgetProviderInfo call() throws Exception {
ComponentName cn = new ComponentName(getInstrumentation().getContext(),
hasConfigureScreen ? AppWidgetWithConfig.class : AppWidgetNoConfig.class);
Log.d(TAG, "findWidgetProvider componentName=" + cn.flattenToString());
return AppWidgetManagerCompat.getInstance(mTargetContext)
.findProvider(cn, Process.myUserHandle());
}
});
if (info == null) {
throw new IllegalArgumentException("No valid widget provider");
}
return info;
}
protected UiObject2 findViewById(int id) {
return mDevice.wait(Until.findObject(getSelectorForId(id)), DEFAULT_UI_TIMEOUT);
}
protected BySelector getSelectorForId(int id) {
String name = mTargetContext.getResources().getResourceEntryName(id);
return By.res(mTargetPackage, name);
protected void waitForLauncherCondition(
String message, Function<Launcher, Boolean> condition, long timeout) {
if (!TestHelpers.isInLauncherProcess()) return;
Wait.atMost(message, () -> getFromLauncher(condition), timeout);
}
protected LauncherActivityInfo getSettingsApp() {

Some files were not shown because too many files have changed in this diff Show More