Merge "dump method should accept --proto and --debug flag regardless of the order Bug:69006241" into ub-launcher3-master
diff --git a/Android.mk b/Android.mk
index 4cc5e42..0dc7ff1 100644
--- a/Android.mk
+++ b/Android.mk
@@ -17,6 +17,14 @@
LOCAL_PATH := $(call my-dir)
#
+# Prebuilt Java Libraries
+#
+include $(CLEAR_VARS)
+LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
+ libSharedSystemUI:quickstep/libs/sysui_shared.jar
+include $(BUILD_MULTI_PREBUILT)
+
+#
# Build rule for Launcher3 app.
#
include $(CLEAR_VARS)
@@ -30,7 +38,7 @@
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
- $(call all-java-files-under, src_config) \
+ $(call all-java-files-under, src_ui_overrides) \
$(call all-java-files-under, src_flags) \
$(call all-proto-files-under, protos) \
$(call all-proto-files-under, proto_overrides)
@@ -75,7 +83,7 @@
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
- $(call all-java-files-under, src_config) \
+ $(call all-java-files-under, src_ui_overrides) \
$(call all-java-files-under, go/src_flags) \
$(call all-proto-files-under, protos) \
$(call all-proto-files-under, proto_overrides)
@@ -112,22 +120,57 @@
include $(BUILD_PACKAGE)
#
-# Launcher proto buffer jar used for development
+# Build rule for Quickstep app.
#
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(call all-proto-files-under, protos) $(call all-proto-files-under, proto_overrides)
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-v4 \
+ android-support-v7-recyclerview \
+ android-support-dynamic-animation \
+ libSharedSystemUI
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-java-files-under, quickstep/src) \
+ $(call all-java-files-under, src_flags) \
+ $(call all-proto-files-under, protos) \
+ $(call all-proto-files-under, proto_overrides)
+
+LOCAL_RESOURCE_DIR := \
+ $(LOCAL_PATH)/quickstep/res \
+ $(LOCAL_PATH)/res \
+ prebuilts/sdk/current/support/v7/recyclerview/res \
+
+LOCAL_PROGUARD_ENABLED := disabled
LOCAL_PROTOC_OPTIMIZE_TYPE := nano
LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/ --proto_path=$(LOCAL_PATH)/proto_overrides/
LOCAL_PROTO_JAVA_OUTPUT_PARAMS := enum_style=java
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE := launcher_proto_lib
-LOCAL_IS_HOST_MODULE := true
-LOCAL_STATIC_JAVA_LIBRARIES := host-libprotobuf-java-nano
+LOCAL_AAPT_FLAGS := \
+ --auto-add-overlay \
+ --extra-packages android.support.v7.recyclerview \
-include $(BUILD_HOST_JAVA_LIBRARY)
+LOCAL_SDK_VERSION := system_current
+LOCAL_MIN_SDK_VERSION := 26
+LOCAL_PACKAGE_NAME := Launcher3QuickStep
+LOCAL_PRIVILEGED_MODULE := true
+LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3
+
+LOCAL_FULL_LIBS_MANIFEST_FILES := \
+ $(LOCAL_PATH)/AndroidManifest.xml \
+ $(LOCAL_PATH)/AndroidManifest-common.xml
+
+LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml
+LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
+
+include $(BUILD_PACKAGE)
+
+
+
# ==================================================
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 23bddf6..6ef7828 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -112,53 +112,5 @@
android:writePermission="com.android.launcher3.permission.WRITE_SETTINGS"
android:readPermission="com.android.launcher3.permission.READ_SETTINGS" />
- <!-- ENABLE_FOR_TESTING
-
- <activity
- android:name="com.android.launcher3.testing.LauncherExtension"
- android:launchMode="singleTask"
- android:clearTaskOnLaunch="true"
- android:stateNotNeeded="true"
- android:theme="@style/Theme"
- android:windowSoftInputMode="adjustPan"
- android:screenOrientation="nosensor"
- >
- <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"/>
- </intent-filter>
- </activity>
-
- <activity
- android:name="com.android.launcher3.testing.MemoryDumpActivity"
- android:theme="@android:style/Theme.NoDisplay"
- android:label="* HPROF"
- android:excludeFromRecents="true"
- android:icon="@drawable/ic_launcher_home"
- >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
-
- <activity
- android:name="com.android.launcher3.testing.ToggleWeightWatcher"
- android:label="Show Mem"
- android:icon="@drawable/ic_launcher_home">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
-
- <service android:name="com.android.launcher3.testing.MemoryTracker" />
-
- -->
-
</application>
</manifest>
diff --git a/build.gradle b/build.gradle
index 2376146..61c05e5 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,7 +4,7 @@
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.3.1'
+ classpath 'com.android.tools.build:gradle:2.3.3'
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.0'
}
}
@@ -40,7 +40,20 @@
applicationId 'com.android.launcher3'
testApplicationId 'com.android.launcher3.tests'
}
+
+ quickstep {
+ applicationId 'com.android.launcher3'
+ testApplicationId 'com.android.launcher3.tests'
+ }
}
+
+ // Disable release builds for now
+ android.variantFilter { variant ->
+ if (variant.buildType.name.endsWith('release')) {
+ variant.setIgnore(true);
+ }
+ }
+
sourceSets {
main {
res.srcDirs = ['res']
@@ -52,31 +65,34 @@
}
}
+ debug {
+ manifest.srcFile "AndroidManifest.xml"
+ }
+
androidTest {
res.srcDirs = ['tests/res']
java.srcDirs = ['tests/src']
manifest.srcFile "tests/AndroidManifest-common.xml"
}
- aosp {
- java.srcDirs = ['src_flags']
- manifest.srcFile "AndroidManifest.xml"
+ androidTestDebug {
+ manifest.srcFile "tests/AndroidManifest.xml"
}
- aospAndroidTest {
- manifest.srcFile "tests/AndroidManifest.xml"
+ aosp {
+ java.srcDirs = ['src_flags', "src_ui_overrides"]
}
l3go {
res.srcDirs = ['go/res']
- java.srcDirs = ['go/src_flags']
- // Note: we are using the Launcher3 manifest here because the gradle manifest-merger uses
- // different attributes than the build system.
- manifest.srcFile "AndroidManifest.xml"
+ java.srcDirs = ['go/src_flags', "src_ui_overrides"]
+ manifest.srcFile "go/AndroidManifest.xml"
}
- l3goAndroidTest {
- manifest.srcFile "tests/AndroidManifest.xml"
+ quickstep {
+ res.srcDirs = ['quickstep/res']
+ java.srcDirs = ['src_flags', 'quickstep/src']
+ manifest.srcFile "quickstep/AndroidManifest.xml"
}
}
}
@@ -93,11 +109,14 @@
compile "com.android.support:recyclerview-v7:${SUPPORT_LIBS_VERSION}"
compile 'com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-7'
+ quickstepCompile fileTree(dir: "quickstep/libs", include: 'sysui_shared.jar')
+
testCompile 'junit:junit:4.12'
androidTestCompile "org.mockito:mockito-core:1.9.5"
androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
- androidTestCompile 'com.android.support.test:runner:0.5'
+ androidTestCompile 'com.android.support.test:runner:1.0.0'
+ androidTestCompile 'com.android.support.test:rules:1.0.0'
androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
androidTestCompile "com.android.support:support-annotations:${SUPPORT_LIBS_VERSION}"
}
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 0bbec18..de74fce 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -132,7 +132,9 @@
// not using the HOME_INTENT
CANCEL = 3; // Indicates that a confirmation screen was cancelled
CONFIRM = 4; // Indicates thata confirmation screen was accepted
+ STOP = 5; // Indicates onStop() was called (screen time out, power off)
}
+
optional Type type = 1;
optional Touch touch = 2;
optional Direction dir = 3;
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
new file mode 100644
index 0000000..08c740c4
--- /dev/null
+++ b/quickstep/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.launcher3" >
+
+ <uses-sdk android:targetSdkVersion="23" android:minSdkVersion="21"/>
+
+ <application
+ android:backupAgent="com.android.launcher3.LauncherBackupAgent"
+ android:fullBackupOnly="true"
+ android:fullBackupContent="@xml/backupscheme"
+ android:hardwareAccelerated="true"
+ android:icon="@drawable/ic_launcher_home"
+ android:label="@string/derived_app_name"
+ android:theme="@style/LauncherTheme"
+ android:largeHeap="@bool/config_largeHeap"
+ android:restoreAnyVersion="true"
+ android:supportsRtl="true" >
+
+ <service android:name="com.android.quickstep.TouchInteractionService"
+ android:exported="true" />
+
+ <!-- STOPSHIP: Change exported to false once all the integration is complete.
+ It is set to true so that the activity can be started from command line -->
+ <activity android:name="com.android.quickstep.RecentsActivity"
+ android:exported="true" />
+ </application>
+
+</manifest>
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
new file mode 100644
index 0000000..9069698
--- /dev/null
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/res/drawable/task_thumbnail_background.xml b/quickstep/res/drawable/task_thumbnail_background.xml
new file mode 100644
index 0000000..603380e
--- /dev/null
+++ b/quickstep/res/drawable/task_thumbnail_background.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+ <solid android:color="#FF000000" />
+ <corners android:radius="2dp" />
+</shape>
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
new file mode 100644
index 0000000..22f014a
--- /dev/null
+++ b/quickstep/res/layout/overview_panel.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<com.android.quickstep.RecentsView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:theme="@style/HomeScreenElementTheme"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:alpha="0.0"
+ android:visibility="invisible" />
\ No newline at end of file
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
new file mode 100644
index 0000000..839d934
--- /dev/null
+++ b/quickstep/res/layout/task.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<com.android.quickstep.TaskView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <com.android.quickstep.TaskThumbnailView
+ android:id="@+id/snapshot"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginTop="@dimen/task_thumbnail_top_margin"
+ android:scaleType="matrix"
+ android:background="@drawable/task_thumbnail_background"
+ android:elevation="4dp" />
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/task_thumbnail_icon_size"
+ android:layout_height="@dimen/task_thumbnail_icon_size"
+ android:layout_gravity="top|center_horizontal"
+ android:elevation="5dp"/>
+</com.android.quickstep.TaskView>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
new file mode 100644
index 0000000..4f85957
--- /dev/null
+++ b/quickstep/res/values/dimens.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+
+ <dimen name="task_thumbnail_top_margin">24dp</dimen>
+ <dimen name="task_thumbnail_icon_size">48dp</dimen>
+
+ <dimen name="quickstep_fling_threshold_velocity">500dp</dimen>
+ <dimen name="quickstep_fling_min_velocity">250dp</dimen>
+
+</resources>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
new file mode 100644
index 0000000..ef61226
--- /dev/null
+++ b/quickstep/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* 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.
+*/
+-->
+<resources>
+
+ <!-- Application name -->
+ <string name="derived_app_name" translatable="false">Quickstep</string>
+</resources>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
new file mode 100644
index 0000000..1176034
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -0,0 +1,60 @@
+/*
+ * 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.uioverrides;
+
+import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
+
+import android.view.View;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.quickstep.RecentsView;
+
+/**
+ * Definition for overview state
+ */
+public class OverviewState extends LauncherState {
+
+ private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_MULTI_PAGE;
+
+ public OverviewState(int id) {
+ super(id, ContainerType.WORKSPACE, OVERVIEW_TRANSITION_MS, 1f, STATE_FLAGS);
+ }
+
+ @Override
+ public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
+ // TODO: Find a better transition
+ return new float[] {0f, 0};
+ }
+
+ @Override
+ public void onStateEnabled(Launcher launcher) {
+ RecentsView rv = launcher.getOverviewPanel();
+ rv.setOverviewStateEnabled(true);
+ }
+
+ @Override
+ public void onStateDisabled(Launcher launcher) {
+ RecentsView rv = launcher.getOverviewPanel();
+ rv.setOverviewStateEnabled(false);
+ }
+
+ @Override
+ public View getFinalFocus(Launcher launcher) {
+ return launcher.getOverviewPanel();
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
new file mode 100644
index 0000000..da1eff9
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -0,0 +1,53 @@
+/*
+ * 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.uioverrides;
+
+import static com.android.launcher3.WorkspaceStateTransitionAnimation.NO_ANIM_PROPERTY_SETTER;
+
+import android.animation.AnimatorSet;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
+import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.WorkspaceStateTransitionAnimation.AnimatedPropertySetter;
+import com.android.launcher3.WorkspaceStateTransitionAnimation.PropertySetter;
+import com.android.launcher3.anim.AnimationLayerSet;
+
+public class RecentsViewStateController implements StateHandler {
+
+ private final Launcher mLauncher;
+
+ public RecentsViewStateController(Launcher launcher) {
+ mLauncher = launcher;
+ }
+
+ @Override
+ public void setState(LauncherState state) {
+ setState(state, NO_ANIM_PROPERTY_SETTER);
+ }
+
+ @Override
+ public void setStateWithAnimation(LauncherState toState, AnimationLayerSet layerViews,
+ AnimatorSet anim, AnimationConfig config) {
+ setState(toState, new AnimatedPropertySetter(config.duration, layerViews, anim));
+ }
+
+ private void setState(LauncherState state, PropertySetter setter) {
+ setter.setViewAlpha(null, mLauncher.getOverviewPanel(),
+ state == LauncherState.OVERVIEW ? 1 : 0);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
new file mode 100644
index 0000000..20abdc7
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -0,0 +1,71 @@
+/*
+ * 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.uioverrides;
+
+import android.content.Intent;
+import android.view.View.AccessibilityDelegate;
+import android.widget.PopupMenu;
+import android.widget.Toast;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.R;
+import com.android.launcher3.VerticalSwipeController;
+import com.android.launcher3.util.TouchController;
+import com.android.launcher3.widget.WidgetsFullSheet;
+
+public class UiFactory {
+
+ public static TouchController[] createTouchControllers(Launcher launcher) {
+ return new TouchController[] {new VerticalSwipeController(launcher)};
+ }
+
+ public static AccessibilityDelegate newPageIndicatorAccessibilityDelegate() {
+ return null;
+ }
+
+ public static StateHandler[] getStateHandler(Launcher launcher) {
+ return new StateHandler[] {
+ launcher.getAllAppsController(), launcher.getWorkspace(),
+ new RecentsViewStateController(launcher)};
+ }
+
+ public static void onWorkspaceLongPress(Launcher launcher) {
+ PopupMenu menu = new PopupMenu(launcher, launcher.getWorkspace().getPageIndicator());
+ menu.getMenu().add(R.string.wallpaper_button_text).setOnMenuItemClickListener((i) -> {
+ launcher.onClickWallpaperPicker(null);
+ return true;
+ });
+ menu.getMenu().add(R.string.widget_button_text).setOnMenuItemClickListener((i) -> {
+ if (launcher.getPackageManager().isSafeMode()) {
+ Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
+ } else {
+ WidgetsFullSheet.show(launcher, true /* animated */);
+ }
+ return true;
+ });
+ if (launcher.hasSettings()) {
+ menu.getMenu().add(R.string.settings_button_text).setOnMenuItemClickListener((i) -> {
+ launcher.startActivity(new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
+ .setPackage(launcher.getPackageName())
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ return true;
+ });
+ }
+ menu.show();
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/AnimatedFloat.java b/quickstep/src/com/android/quickstep/AnimatedFloat.java
new file mode 100644
index 0000000..1f6781e
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/AnimatedFloat.java
@@ -0,0 +1,79 @@
+/*
+ * 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.quickstep;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.util.FloatProperty;
+
+/**
+ * A mutable float which allows animating the value
+ */
+public class AnimatedFloat {
+
+ public static FloatProperty<AnimatedFloat> VALUE = new FloatProperty<AnimatedFloat>("value") {
+ @Override
+ public void setValue(AnimatedFloat obj, float v) {
+ obj.updateValue(v);
+ }
+
+ @Override
+ public Float get(AnimatedFloat obj) {
+ return obj.value;
+ }
+ };
+
+ private final Runnable mUpdateCallback;
+ private ObjectAnimator mValueAnimator;
+
+ public float value;
+
+ public AnimatedFloat(Runnable updateCallback) {
+ mUpdateCallback = updateCallback;
+ }
+
+ public ObjectAnimator animateToValue(float v) {
+ if (mValueAnimator != null) {
+ mValueAnimator.cancel();
+ }
+ mValueAnimator = ObjectAnimator.ofFloat(this, VALUE, v);
+ mValueAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ if (mValueAnimator == animator) {
+ mValueAnimator = null;
+ }
+ }
+ });
+ return mValueAnimator;
+ }
+
+ /**
+ * Changes the value and calls the callback.
+ * Note that the value can be directly accessed as well to avoid notifying the callback.
+ */
+ public void updateValue(float v) {
+ if (Float.compare(v, value) != 0) {
+ value = v;
+ mUpdateCallback.run();
+ }
+ }
+
+ public ObjectAnimator getCurrentAnimation() {
+ return mValueAnimator;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/MotionEventQueue.java b/quickstep/src/com/android/quickstep/MotionEventQueue.java
new file mode 100644
index 0000000..e3c3a1b
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/MotionEventQueue.java
@@ -0,0 +1,93 @@
+/*
+ * 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.quickstep;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_MOVE;
+
+import android.view.Choreographer;
+import android.view.MotionEvent;
+
+import com.android.systemui.shared.system.ChoreographerCompat;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * Helper class for batching input events
+ */
+public class MotionEventQueue implements Runnable {
+
+ // We use two arrays and swap the current index when one array is being consumed
+ private final EventArray[] mArrays = new EventArray[] {new EventArray(), new EventArray()};
+ private int mCurrentIndex = 0;
+
+ private final Choreographer mChoreographer;
+ private final Consumer<MotionEvent> mConsumer;
+
+ public MotionEventQueue(Choreographer choreographer, Consumer<MotionEvent> consumer) {
+ mChoreographer = choreographer;
+ mConsumer = consumer;
+ }
+
+ public void queue(MotionEvent event) {
+ synchronized (mArrays) {
+ EventArray array = mArrays[mCurrentIndex];
+ if (array.isEmpty()) {
+ ChoreographerCompat.postInputFrame(mChoreographer, this);
+ }
+
+ int eventAction = event.getAction();
+ if (eventAction == ACTION_MOVE && array.lastEventAction == ACTION_MOVE) {
+ // Replace and recycle the last event
+ array.set(array.size() - 1, event).recycle();
+ } else {
+ array.add(event);
+ array.lastEventAction = eventAction;
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ EventArray array = swapAndGetCurrentArray();
+ int size = array.size();
+ for (int i = 0; i < size; i++) {
+ MotionEvent event = array.get(i);
+ mConsumer.accept(event);
+ event.recycle();
+ }
+ array.clear();
+ array.lastEventAction = ACTION_CANCEL;
+ }
+
+ private EventArray swapAndGetCurrentArray() {
+ synchronized (mArrays) {
+ EventArray current = mArrays[mCurrentIndex];
+ mCurrentIndex = mCurrentIndex ^ 1;
+ return current;
+ }
+ }
+
+ private static class EventArray extends ArrayList<MotionEvent> {
+
+ public int lastEventAction = ACTION_CANCEL;
+
+ public EventArray() {
+ super(4);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/MultiStateCallback.java b/quickstep/src/com/android/quickstep/MultiStateCallback.java
new file mode 100644
index 0000000..cca2729
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/MultiStateCallback.java
@@ -0,0 +1,58 @@
+/*
+ * 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.quickstep;
+
+import android.util.SparseArray;
+
+/**
+ * Utility class to help manage multiple callbacks based on different states.
+ */
+public class MultiStateCallback {
+
+ private final SparseArray<Runnable> mCallbacks = new SparseArray<>();
+
+ private int mState = 0;
+
+ /**
+ * Adds the provided state flags to the global state and executes any callbacks as a result.
+ * @param stateFlag
+ */
+ public void setState(int stateFlag) {
+ mState = mState | stateFlag;
+
+ int count = mCallbacks.size();
+ for (int i = 0; i < count; i++) {
+ int state = mCallbacks.keyAt(i);
+
+ if ((mState & state) == state) {
+ Runnable callback = mCallbacks.valueAt(i);
+ if (callback != null) {
+ // Set the callback to null, so that it does not run again.
+ mCallbacks.setValueAt(i, null);
+ callback.run();
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the callbacks to be run when the provided states are enabled.
+ * The callback is only run once.
+ */
+ public void addCallback(int stateMask, Runnable callback) {
+ mCallbacks.put(stateMask, callback);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
new file mode 100644
index 0000000..e03a2ff
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
@@ -0,0 +1,320 @@
+/*
+ * 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.quickstep;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.RectEvaluator;
+import android.annotation.TargetApi;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.support.annotation.UiThread;
+import android.view.View;
+import android.view.ViewTreeObserver.OnPreDrawListener;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.states.InternalStateHandler;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.WindowManagerWrapper;
+
+@TargetApi(Build.VERSION_CODES.O)
+public class NavBarSwipeInteractionHandler extends InternalStateHandler {
+
+ private static final int STATE_LAUNCHER_READY = 1 << 0;
+ private static final int STATE_RECENTS_DELAY_COMPLETE = 1 << 1;
+ private static final int STATE_LOAD_PLAN_READY = 1 << 2;
+ private static final int STATE_RECENTS_FULLY_VISIBLE = 1 << 3;
+ private static final int STATE_ACTIVITY_MULTIPLIER_COMPLETE = 1 << 4;
+ private static final int STATE_SCALED_SNAPSHOT_RECENTS = 1 << 5;
+ private static final int STATE_SCALED_SNAPSHOT_APP = 1 << 6;
+
+ private static final long RECENTS_VIEW_VISIBILITY_DELAY = 120;
+ private static final long RECENTS_VIEW_VISIBILITY_DURATION = 150;
+ private static final long DEFAULT_SWIPE_DURATION = 200;
+
+ // Ideal velocity for a smooth transition
+ private static final float PIXEL_PER_MS = 2f;
+
+ private static final float MIN_PROGRESS_FOR_OVERVIEW = 0.5f;
+
+ private final Rect mStableInsets = new Rect();
+ private final Rect mSourceRect = new Rect();
+ private final Rect mTargetRect = new Rect();
+ private final Rect mCurrentRect = new Rect();
+ private final RectEvaluator mRectEvaluator = new RectEvaluator(mCurrentRect);
+
+ // Shift in the range of [0, 1].
+ // 0 => preview snapShot is completely visible, and hotseat is completely translated down
+ // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
+ // visible.
+ private final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
+
+ // Activity multiplier in the range of [0, 1]. When the activity becomes visible, this is
+ // animated to 1, so allow for a smooth transition.
+ private final AnimatedFloat mActivityMultiplier = new AnimatedFloat(this::updateFinalShift);
+
+ private final int mRunningTaskId;
+ private final Context mContext;
+
+ private final MultiStateCallback mStateCallback;
+
+ private Launcher mLauncher;
+ private SnapshotDragView mDragView;
+ private RecentsView mRecentsView;
+ private Hotseat mHotseat;
+ private RecentsTaskLoadPlan mLoadPlan;
+
+ private boolean mLauncherReady;
+ private boolean mTouchEndHandled;
+ private float mCurrentDisplacement;
+
+ private Bitmap mTaskSnapshot;
+
+ NavBarSwipeInteractionHandler(RunningTaskInfo runningTaskInfo, Context context) {
+ mRunningTaskId = runningTaskInfo.id;
+ mContext = context;
+ WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
+
+ // Build the state callback
+ mStateCallback = new MultiStateCallback();
+ mStateCallback.addCallback(STATE_LAUNCHER_READY, this::onLauncherReady);
+ mStateCallback.addCallback(STATE_LOAD_PLAN_READY | STATE_RECENTS_DELAY_COMPLETE,
+ this::setTaskPlanToUi);
+ mStateCallback.addCallback(STATE_SCALED_SNAPSHOT_APP, this::resumeLastTask);
+ mStateCallback.addCallback(STATE_RECENTS_FULLY_VISIBLE | STATE_SCALED_SNAPSHOT_RECENTS
+ | STATE_ACTIVITY_MULTIPLIER_COMPLETE,
+ this::onAnimationToLauncherComplete);
+ mStateCallback.addCallback(STATE_LAUNCHER_READY | STATE_SCALED_SNAPSHOT_APP,
+ this::cleanupLauncher);
+ }
+
+ private void onLauncherReady() {
+ mLauncherReady = true;
+ executeFrameUpdate();
+
+ // Wait for some time before loading recents so that the first frame is fast
+ new Handler().postDelayed(() -> mStateCallback.setState(STATE_RECENTS_DELAY_COMPLETE),
+ RECENTS_VIEW_VISIBILITY_DELAY);
+
+ long duration = Math.min(DEFAULT_SWIPE_DURATION,
+ Math.max((long) (-mCurrentDisplacement / PIXEL_PER_MS), 0));
+ if (mCurrentShift.getCurrentAnimation() != null) {
+ ObjectAnimator anim = mCurrentShift.getCurrentAnimation();
+ long theirDuration = anim.getDuration() - anim.getCurrentPlayTime();
+
+ // TODO: Find a better heuristic
+ duration = (duration + theirDuration) / 2;
+ }
+ ObjectAnimator anim = mActivityMultiplier.animateToValue(1)
+ .setDuration(duration);
+ anim.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE);
+ }
+ });
+ anim.start();
+ }
+
+ public void setTaskSnapshot(Bitmap taskSnapshot) {
+ mTaskSnapshot = taskSnapshot;
+ }
+
+ @Override
+ public void onLauncherResume() {
+ mDragView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ mDragView.getViewTreeObserver().removeOnPreDrawListener(this);
+ mStateCallback.setState(STATE_LAUNCHER_READY);
+ return true;
+ }
+ });
+ }
+
+ @Override
+ protected void init(Launcher launcher, boolean alreadyOnHome) {
+ AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome);
+ launcher.getStateManager().goToState(LauncherState.OVERVIEW, alreadyOnHome);
+
+ mLauncher = launcher;
+ mDragView = new SnapshotDragView(mLauncher, mTaskSnapshot);
+ mLauncher.getDragLayer().addView(mDragView);
+ mDragView.setPivotX(0);
+ mDragView.setPivotY(0);
+ mRecentsView = mLauncher.getOverviewPanel();
+ mHotseat = mLauncher.getHotseat();
+
+ // Optimization
+ mLauncher.getAppsView().setVisibility(View.GONE);
+ mRecentsView.setVisibility(View.GONE);
+ }
+
+ @UiThread
+ public void updateDisplacement(float displacement) {
+ mCurrentDisplacement = displacement;
+ executeFrameUpdate();
+ }
+
+ private void executeFrameUpdate() {
+ if (mLauncherReady) {
+ final float displacement = -mCurrentDisplacement;
+ int hotseatHeight = mHotseat.getHeight();
+ float translation = Utilities.boundToRange(displacement, 0, hotseatHeight);
+ float shift = hotseatHeight == 0 ? 0 : translation / hotseatHeight;
+ mCurrentShift.updateValue(shift);
+ }
+ }
+
+ @UiThread
+ private void updateFinalShift() {
+ if (!mLauncherReady) {
+ return;
+ }
+
+ if (mTargetRect.isEmpty()) {
+ DragLayer dl = mLauncher.getDragLayer();
+ mSourceRect.set(0, 0, dl.getWidth(), dl.getHeight());
+ Rect targetPadding = RecentsView.getPadding(mLauncher);
+ Rect insets = dl.getInsets();
+ mTargetRect.set(
+ targetPadding.left + insets.left,
+ targetPadding.top + insets.top,
+ mSourceRect.right - targetPadding.right - insets.right,
+ mSourceRect.bottom - targetPadding.bottom - insets.bottom);
+ mTargetRect.top += mLauncher.getResources()
+ .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
+ }
+
+ float shift = mCurrentShift.value * mActivityMultiplier.value;
+ int hotseatHeight = mHotseat.getHeight();
+
+ mHotseat.setTranslationY((1 - shift) * hotseatHeight);
+
+ mRectEvaluator.evaluate(shift, mSourceRect, mTargetRect);
+
+ float scale = (float) mCurrentRect.width() / mSourceRect.width();
+ mDragView.setTranslationX(mCurrentRect.left - mStableInsets.left * scale * shift);
+ mDragView.setTranslationY(mCurrentRect.top - mStableInsets.top * scale * shift);
+ mDragView.setScaleX(scale);
+ mDragView.setScaleY(scale);
+ mDragView.getViewBounds().setClipTop((int) (mStableInsets.top * shift));
+ mDragView.getViewBounds().setClipBottom((int) (mStableInsets.bottom * shift));
+ }
+
+ @UiThread
+ public void setRecentsTaskLoadPlan(RecentsTaskLoadPlan loadPlan) {
+ mLoadPlan = loadPlan;
+ mStateCallback.setState(STATE_LOAD_PLAN_READY);
+ }
+
+ private void setTaskPlanToUi() {
+ mRecentsView.update(mLoadPlan);
+ mRecentsView.setVisibility(View.VISIBLE);
+
+ // Animate alpha
+ mRecentsView.setAlpha(0);
+ mRecentsView.animate().alpha(1).setDuration(RECENTS_VIEW_VISIBILITY_DURATION)
+ .withEndAction(() -> mStateCallback.setState(STATE_RECENTS_FULLY_VISIBLE));
+ }
+
+ @UiThread
+ public void endTouch(float endVelocity) {
+ if (mTouchEndHandled) {
+ return;
+ }
+ mTouchEndHandled = true;
+
+ Resources res = mContext.getResources();
+ float flingThreshold = res.getDimension(R.dimen.quickstep_fling_threshold_velocity);
+ boolean isFling = Math.abs(endVelocity) > flingThreshold;
+
+ long duration = DEFAULT_SWIPE_DURATION;
+ final float endShift;
+ if (!isFling) {
+ endShift = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? 1 : 0;
+ } else {
+ endShift = endVelocity < 0 ? 1 : 0;
+ float minFlingVelocity = res.getDimension(R.dimen.quickstep_fling_min_velocity);
+ if (Math.abs(endVelocity) > minFlingVelocity && mLauncherReady) {
+ float distanceToTravel = (endShift - mCurrentShift.value) * mHotseat.getHeight();
+
+ // we want the page's snap velocity to approximately match the velocity at
+ // which the user flings, so we scale the duration by a value near to the
+ // derivative of the scroll interpolator at zero, ie. 5.
+ duration = 5 * Math.round(1000 * Math.abs(distanceToTravel / endVelocity));
+ }
+ }
+
+ ObjectAnimator anim = mCurrentShift.animateToValue(endShift).setDuration(duration);
+ anim.setInterpolator(Interpolators.SCROLL);
+ anim.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ mStateCallback.setState((Float.compare(mCurrentShift.value, 0) == 0)
+ ? STATE_SCALED_SNAPSHOT_APP : STATE_SCALED_SNAPSHOT_RECENTS);
+ }
+ });
+ anim.start();
+ }
+
+ @UiThread
+ private void resumeLastTask() {
+ TaskKey key = null;
+ if (mLoadPlan != null) {
+ Task task = mLoadPlan.getTaskStack().findTaskWithId(mRunningTaskId);
+ if (task != null) {
+ key = task.key;
+ }
+ }
+
+ if (key == null) {
+ // TODO: We need a better way for this
+ key = new TaskKey(mRunningTaskId, 0, null, UserHandle.myUserId(), 0);
+ }
+
+ ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
+ ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(key, opts, null, null);
+ }
+
+ private void cleanupLauncher() {
+ // TODO: These should be done as part of ActivityOptions#OnAnimationStarted
+ mHotseat.setTranslationY(0);
+ mLauncher.setOnResumeCallback(() -> mDragView.close(false));
+ }
+
+ private void onAnimationToLauncherComplete() {
+ mDragView.close(false);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
new file mode 100644
index 0000000..f92d773
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -0,0 +1,48 @@
+/*
+ * 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.quickstep;
+
+import android.app.ListActivity;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.support.annotation.Nullable;
+import android.widget.ArrayAdapter;
+
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.recents.model.Task;
+
+/**
+ * A simple activity to show the recently launched tasks
+ */
+public class RecentsActivity extends ListActivity {
+
+ private ArrayAdapter<Task> mAdapter;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(this);
+ plan.preloadPlan(new PreloadOptions(), new RecentsTaskLoader(this, 1, 1, 0), -1,
+ UserHandle.myUserId());
+
+ mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
+ mAdapter.addAll(plan.getTaskStack().getTasks());
+ setListAdapter(mAdapter);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/RecentsView.java
new file mode 100644
index 0000000..675f456
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsView.java
@@ -0,0 +1,173 @@
+/*
+ * 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.quickstep;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.PagedView;
+import com.android.launcher3.R;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.WindowManagerWrapper;
+
+import java.util.ArrayList;
+
+/**
+ * A list of recent tasks.
+ */
+public class RecentsView extends PagedView {
+
+ private boolean mOverviewStateEnabled;
+ private boolean mTaskStackListenerRegistered;
+
+ private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
+ @Override
+ public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
+ for (int i = 0; i < getChildCount(); i++) {
+ final TaskView taskView = (TaskView) getChildAt(i);
+ if (taskView.getTask().key.id == taskId) {
+ taskView.getThumbnail().setThumbnail(snapshot);
+ return;
+ }
+ }
+ }
+ };
+
+ public RecentsView(Context context) {
+ this(context, null);
+ }
+
+ public RecentsView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ setWillNotDraw(false);
+ setPageSpacing((int) getResources().getDimension(R.dimen.recents_page_spacing));
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ Rect padding = getPadding(Launcher.getLauncher(getContext()));
+ setPadding(padding.left, padding.top, padding.right, padding.bottom);
+ }
+
+ @Override
+ protected void onWindowVisibilityChanged(int visibility) {
+ super.onWindowVisibilityChanged(visibility);
+ updateTaskStackListenerState();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ updateTaskStackListenerState();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ updateTaskStackListenerState();
+ }
+
+ public void setOverviewStateEnabled(boolean enabled) {
+ mOverviewStateEnabled = enabled;
+ updateTaskStackListenerState();
+ }
+
+ public void update(RecentsTaskLoadPlan loadPlan) {
+ final RecentsTaskLoader loader = TouchInteractionService.getRecentsTaskLoader();
+ setCurrentPage(0);
+ TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null;
+ if (stack == null) {
+ removeAllViews();
+ return;
+ }
+
+ // Ensure there are as many views as there are tasks in the stack (adding and trimming as
+ // necessary)
+ final LayoutInflater inflater = LayoutInflater.from(getContext());
+ final ArrayList<Task> tasks = stack.getTasks();
+ for (int i = getChildCount(); i < tasks.size(); i++) {
+ final TaskView taskView = (TaskView) inflater.inflate(R.layout.task, this, false);
+ addView(taskView);
+ }
+ while (getChildCount() > tasks.size()) {
+ final TaskView taskView = (TaskView) getChildAt(getChildCount() - 1);
+ removeView(taskView);
+ loader.unloadTaskData(taskView.getTask());
+ }
+
+ // Rebind all task views
+ for (int i = tasks.size() - 1; i >= 0; i--) {
+ final Task task = tasks.get(i);
+ final TaskView taskView = (TaskView) getChildAt(tasks.size() - i - 1);
+ taskView.bind(task);
+ loader.loadTaskData(task);
+ }
+ }
+
+ public void launchTaskWithId(int taskId) {
+ for (int i = 0; i < getChildCount(); i++) {
+ final TaskView taskView = (TaskView) getChildAt(i);
+ if (taskView.getTask().key.id == taskId) {
+ taskView.launchTask(false /* animate */);
+ return;
+ }
+ }
+ }
+
+ private void updateTaskStackListenerState() {
+ boolean registerStackListener = mOverviewStateEnabled && isAttachedToWindow()
+ && getWindowVisibility() == VISIBLE;
+ if (registerStackListener != mTaskStackListenerRegistered) {
+ if (registerStackListener) {
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
+ } else {
+ ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
+ }
+ mTaskStackListenerRegistered = registerStackListener;
+ }
+ }
+
+ public static Rect getPadding(Launcher launcher) {
+ DeviceProfile profile = launcher.getDeviceProfile();
+ Rect stableInsets = new Rect();
+ WindowManagerWrapper.getInstance().getStableInsets(stableInsets);
+ Rect padding = profile.getWorkspacePadding(null);
+ float taskWidth = profile.getCurrentWidth() - stableInsets.left - stableInsets.right;
+ float taskHeight = profile.getCurrentHeight() - stableInsets.top - stableInsets.bottom;
+ float overviewHeight = profile.availableHeightPx - padding.top - padding.bottom
+ - stableInsets.top;
+ float overviewWidth = taskWidth * overviewHeight / taskHeight;
+ padding.left = padding.right = (int) ((profile.availableWidthPx - overviewWidth) / 2);
+ return padding;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/SimpleTaskView.java b/quickstep/src/com/android/quickstep/SimpleTaskView.java
new file mode 100644
index 0000000..8425fa3
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/SimpleTaskView.java
@@ -0,0 +1,52 @@
+/*
+ * 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.quickstep;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.WindowManager;
+
+/**
+ * A simple view which keeps its size proportional to the display size
+ */
+public class SimpleTaskView extends View {
+
+ private static final Point sTempPoint = new Point();
+
+ public SimpleTaskView(Context context) {
+ super(context);
+ }
+
+ public SimpleTaskView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public SimpleTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ getContext().getSystemService(WindowManager.class)
+ .getDefaultDisplay().getRealSize(sTempPoint);
+
+ int width = (int) ((float) height * sTempPoint.x / sTempPoint.y);
+ setMeasuredDimension(width, height);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/SnapshotDragView.java b/quickstep/src/com/android/quickstep/SnapshotDragView.java
new file mode 100644
index 0000000..2ef3942
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/SnapshotDragView.java
@@ -0,0 +1,92 @@
+/*
+ * 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.quickstep;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.view.MotionEvent;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.systemui.shared.recents.view.AnimateableViewBounds;
+
+/**
+ * Floating view which shows the task snapshot allowing it to be dragged and placed.
+ */
+public class SnapshotDragView extends AbstractFloatingView implements Insettable {
+
+ private final Launcher mLauncher;
+ private final Bitmap mSnapshot;
+ private final AnimateableViewBounds mViewBounds;
+
+ public SnapshotDragView(Launcher launcher, Bitmap snapshot) {
+ super(launcher, null);
+ mLauncher = launcher;
+ mSnapshot = snapshot;
+ mViewBounds = new AnimateableViewBounds(this, 0);
+ setWillNotDraw(false);
+ setClipToOutline(true);
+ setOutlineProvider(mViewBounds);
+ }
+
+ AnimateableViewBounds getViewBounds() {
+ return mViewBounds;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (mSnapshot != null) {
+ setMeasuredDimension(mSnapshot.getWidth(), mSnapshot.getHeight());
+ } else {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (mSnapshot != null) {
+ canvas.drawBitmap(mSnapshot, 0, 0, null);
+ }
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ return false;
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ // We dont suupport animate.
+ mLauncher.getDragLayer().removeView(this);
+ }
+
+ @Override
+ public void logActionCommand(int command) {
+ // We should probably log the weather
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_QUICKSTEP_PREVIEW) != 0;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/TaskThumbnailView.java
new file mode 100644
index 0000000..6e8bbeb
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailView.java
@@ -0,0 +1,179 @@
+/*
+ * 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.quickstep;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LightingColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
+/**
+ * A task in the Recents view.
+ */
+public class TaskThumbnailView extends FrameLayout {
+
+ private ThumbnailData mThumbnailData;
+
+ private Rect mThumbnailRect = new Rect();
+ private float mThumbnailScale;
+
+ private Matrix mMatrix = new Matrix();
+ private Paint mDrawPaint = new Paint();
+ protected Paint mBgFillPaint = new Paint();
+ protected BitmapShader mBitmapShader;
+
+ private float mDimAlpha = 1f;
+ private LightingColorFilter mLightingColorFilter = new LightingColorFilter(Color.WHITE, 0);
+
+ public TaskThumbnailView(Context context) {
+ this(context, null);
+ }
+
+ public TaskThumbnailView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ setWillNotDraw(false);
+ setClipToOutline(true);
+ }
+
+ /**
+ * Updates this thumbnail.
+ */
+ public void setThumbnail(ThumbnailData thumbnailData) {
+ if (thumbnailData != null && thumbnailData.thumbnail != null) {
+ Bitmap bm = thumbnailData.thumbnail;
+ bm.prepareToDraw();
+ mThumbnailScale = thumbnailData.scale;
+ mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+ mDrawPaint.setShader(mBitmapShader);
+ mThumbnailRect.set(0, 0,
+ bm.getWidth() - thumbnailData.insets.left - thumbnailData.insets.right,
+ bm.getHeight() - thumbnailData.insets.top - thumbnailData.insets.bottom);
+ mThumbnailData = thumbnailData;
+ updateThumbnailMatrix();
+ updateThumbnailPaintFilter();
+ } else {
+ mBitmapShader = null;
+ mDrawPaint.setShader(null);
+ mThumbnailRect.setEmpty();
+ mThumbnailData = null;
+ }
+ }
+
+ /**
+ * Sets the alpha of the dim layer on top of this view.
+ */
+ public void setDimAlpha(float dimAlpha) {
+ mDimAlpha = dimAlpha;
+ updateThumbnailPaintFilter();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ int viewWidth = getMeasuredWidth();
+ int viewHeight = getMeasuredHeight();
+ int thumbnailWidth = Math.min(viewWidth,
+ (int) (mThumbnailRect.width() * mThumbnailScale));
+ int thumbnailHeight = Math.min(viewHeight,
+ (int) (mThumbnailRect.height() * mThumbnailScale));
+
+ if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) {
+ // Draw the background, there will be some small overdraw with the thumbnail
+ if (thumbnailWidth < viewWidth) {
+ // Portrait thumbnail on a landscape task view
+ canvas.drawRect(Math.max(0, thumbnailWidth), 0, viewWidth, viewHeight,
+ mBgFillPaint);
+ }
+ if (thumbnailHeight < viewHeight) {
+ // Landscape thumbnail on a portrait task view
+ canvas.drawRect(0, Math.max(0, thumbnailHeight), viewWidth, viewHeight,
+ mBgFillPaint);
+ }
+
+ // Draw the thumbnail
+ canvas.drawRect(0, 0, thumbnailWidth, thumbnailHeight, mDrawPaint);
+ } else {
+ canvas.drawRect(0, 0, viewWidth, viewHeight, mBgFillPaint);
+ }
+ }
+
+ private void updateThumbnailPaintFilter() {
+ int mul = (int) (mDimAlpha * 255);
+ if (mBitmapShader != null) {
+ mLightingColorFilter = new LightingColorFilter(Color.argb(255, mul, mul, mul), 0);
+ mDrawPaint.setColorFilter(mLightingColorFilter);
+ mDrawPaint.setColor(0xFFffffff);
+ mBgFillPaint.setColorFilter(mLightingColorFilter);
+ } else {
+ mDrawPaint.setColorFilter(null);
+ mDrawPaint.setColor(Color.argb(255, mul, mul, mul));
+ }
+ invalidate();
+ }
+
+ private void updateThumbnailMatrix() {
+ mThumbnailScale = 1f;
+ if (mBitmapShader != null && mThumbnailData != null) {
+ if (getMeasuredWidth() == 0) {
+ // If we haven't measured , skip the thumbnail drawing and only draw the background
+ // color
+ mThumbnailScale = 0f;
+ } else {
+ float invThumbnailScale = 1f / mThumbnailScale;
+ final Configuration configuration =
+ getContext().getApplicationContext().getResources().getConfiguration();
+ final DeviceProfile profile = Launcher.getLauncher(getContext()).getDeviceProfile();
+ if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
+ if (mThumbnailData.orientation == Configuration.ORIENTATION_PORTRAIT) {
+ // If we are in the same orientation as the screenshot, just scale it to the
+ // width of the task view
+ mThumbnailScale = (float) getMeasuredWidth() / mThumbnailRect.width();
+ } else {
+ // Scale the landscape thumbnail up to app size, then scale that to the task
+ // view size to match other portrait screenshots
+ mThumbnailScale = invThumbnailScale *
+ ((float) getMeasuredWidth() / profile.getCurrentWidth());
+ }
+ } else {
+ // Otherwise, scale the screenshot to fit 1:1 in the current orientation
+ mThumbnailScale = invThumbnailScale;
+ }
+ }
+ mMatrix.setTranslate(-mThumbnailData.insets.left, -mThumbnailData.insets.top);
+ mMatrix.postScale(mThumbnailScale, mThumbnailScale);
+ mBitmapShader.setLocalMatrix(mMatrix);
+ }
+ invalidate();
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/TaskView.java b/quickstep/src/com/android/quickstep/TaskView.java
new file mode 100644
index 0000000..029afd6
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TaskView.java
@@ -0,0 +1,136 @@
+/*
+ * 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.quickstep;
+
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.launcher3.R;
+import com.android.launcher3.uioverrides.OverviewState;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task.TaskCallbacks;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.shared.recents.view.RecentsTransition;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A task in the Recents view.
+ */
+public class TaskView extends FrameLayout implements TaskCallbacks {
+
+ private Task mTask;
+ private TaskThumbnailView mSnapshotView;
+ private ImageView mIconView;
+
+ public TaskView(Context context) {
+ this(context, null);
+ }
+
+ public TaskView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ setOnClickListener((view) -> {
+ launchTask(true /* animate */);
+ });
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mSnapshotView = findViewById(R.id.snapshot);
+ mIconView = findViewById(R.id.icon);
+ }
+
+ /**
+ * Updates this task view to the given {@param task}.
+ */
+ public void bind(Task task) {
+ if (mTask != null) {
+ mTask.removeCallback(this);
+ }
+ mTask = task;
+ task.addCallback(this);
+ }
+
+ public Task getTask() {
+ return mTask;
+ }
+
+ public TaskThumbnailView getThumbnail() {
+ return mSnapshotView;
+ }
+
+ public void launchTask(boolean animate) {
+ if (mTask != null) {
+ final ActivityOptions opts;
+ if (animate) {
+ // Calculate the bounds of the thumbnail to animate from
+ final Rect bounds = new Rect();
+ final int[] pos = new int[2];
+ mSnapshotView.getLocationInWindow(pos);
+ bounds.set(pos[0], pos[1],
+ pos[0] + mSnapshotView.getWidth(),
+ pos[1] + mSnapshotView.getHeight());
+ AppTransitionAnimationSpecsFuture animFuture =
+ new AppTransitionAnimationSpecsFuture(getHandler()) {
+ @Override
+ public List<AppTransitionAnimationSpecCompat> composeSpecs() {
+ ArrayList<AppTransitionAnimationSpecCompat> specs =
+ new ArrayList<>();
+ specs.add(new AppTransitionAnimationSpecCompat(mTask.key.id, null,
+ bounds));
+ return specs;
+ }
+ };
+ opts = RecentsTransition.createAspectScaleAnimation(
+ getContext(), getHandler(), true /* scaleUp */, animFuture, null);
+ } else {
+ opts = ActivityOptions.makeCustomAnimation(getContext(), 0, 0);
+ }
+ ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(mTask.key,
+ opts, null, null);
+ }
+ }
+
+ @Override
+ public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) {
+ mSnapshotView.setThumbnail(thumbnailData);
+ mIconView.setImageDrawable(task.icon);
+ }
+
+ @Override
+ public void onTaskDataUnloaded() {
+ mSnapshotView.setThumbnail(null);
+ mIconView.setImageDrawable(null);
+ }
+
+ @Override
+ public void onTaskWindowingModeChanged() {
+ // Do nothing
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
new file mode 100644
index 0000000..ff26fb3
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -0,0 +1,269 @@
+/*
+ * 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.quickstep;
+
+import static android.view.MotionEvent.INVALID_POINTER_ID;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityOptions;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.Choreographer;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+
+import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.R;
+import com.android.launcher3.util.TraceHelper;
+import com.android.systemui.shared.recents.IOverviewProxy;
+import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.BackgroundExecutor;
+
+/**
+ * Service connected by system-UI for handling touch interaction.
+ */
+public class TouchInteractionService extends Service {
+
+ private static final String TAG = "TouchInteractionService";
+
+ private static RecentsTaskLoader sRecentsTaskLoader;
+
+ private final IBinder mMyBinder = new IOverviewProxy.Stub() {
+
+ @Override
+ public void onMotionEvent(MotionEvent ev) {
+ mEventQueue.queue(ev);
+ }
+
+ @Override
+ public void onBind(ISystemUiProxy iSystemUiProxy) throws RemoteException {
+ mISystemUiProxy = iSystemUiProxy;
+ }
+ };
+
+ private ActivityManagerWrapper mAM;
+ private RunningTaskInfo mRunningTask;
+ private Intent mHomeIntent;
+ private ComponentName mLauncher;
+ private MotionEventQueue mEventQueue;
+ private MainThreadExecutor mMainThreadExecutor;
+
+ private int mDisplayRotation;
+ private final Point mDisplaySize = new Point();
+ private final PointF mDownPos = new PointF();
+ private final PointF mLastPos = new PointF();
+ private int mActivePointerId = INVALID_POINTER_ID;
+ private VelocityTracker mVelocityTracker;
+ private int mTouchSlop;
+ private NavBarSwipeInteractionHandler mInteractionHandler;
+
+ private ISystemUiProxy mISystemUiProxy;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mAM = ActivityManagerWrapper.getInstance();
+
+ mHomeIntent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .setPackage(getPackageName())
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ ResolveInfo info = getPackageManager().resolveActivity(mHomeIntent, 0);
+ mLauncher = new ComponentName(getPackageName(), info.activityInfo.name);
+ mHomeIntent.setComponent(mLauncher);
+
+ Resources res = getResources();
+ if (sRecentsTaskLoader == null) {
+ sRecentsTaskLoader = new RecentsTaskLoader(this,
+ res.getInteger(R.integer.config_recentsMaxThumbnailCacheSize),
+ res.getInteger(R.integer.config_recentsMaxIconCacheSize), 0);
+ sRecentsTaskLoader.startLoader(this);
+ }
+
+ mMainThreadExecutor = new MainThreadExecutor();
+ mEventQueue = new MotionEventQueue(Choreographer.getInstance(), this::handleMotionEvent);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.d(TAG, "Touch service connected");
+ return mMyBinder;
+ }
+
+ public static RecentsTaskLoader getRecentsTaskLoader() {
+ return sRecentsTaskLoader;
+ }
+
+ private void handleMotionEvent(MotionEvent ev) {
+ if (ev.getActionMasked() != MotionEvent.ACTION_DOWN && mVelocityTracker == null) {
+ return;
+ }
+ switch (ev.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN: {
+ mActivePointerId = ev.getPointerId(0);
+ mDownPos.set(ev.getX(), ev.getY());
+ mLastPos.set(mDownPos);
+ mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
+ Display display = getSystemService(WindowManager.class).getDefaultDisplay();
+ display.getRealSize(mDisplaySize);
+ mDisplayRotation = display.getRotation();
+
+ mRunningTask = mAM.getRunningTask();
+ if (mRunningTask == null || mRunningTask.topActivity.equals(mLauncher)) {
+ // TODO: We could drive all-apps in this case. For now just ignore swipe.
+ break;
+ }
+
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ } else {
+ mVelocityTracker.clear();
+ }
+ mVelocityTracker.addMovement(ev);
+ if (mInteractionHandler != null) {
+ mInteractionHandler.endTouch(0);
+ mInteractionHandler = null;
+ }
+ break;
+ }
+ case MotionEvent.ACTION_POINTER_UP: {
+ int ptrIdx = ev.getActionIndex();
+ int ptrId = ev.getPointerId(ptrIdx);
+ if (ptrId == mActivePointerId) {
+ final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
+ mDownPos.set(
+ ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
+ ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
+ mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
+ mActivePointerId = ev.getPointerId(newPointerIdx);
+ mVelocityTracker.clear();
+ }
+ break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (pointerIndex == INVALID_POINTER_ID) {
+ break;
+ }
+ mVelocityTracker.addMovement(ev);
+ mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+
+ float displacement = ev.getY(pointerIndex) - mDownPos.y;
+ if (mInteractionHandler == null) {
+ if (Math.abs(displacement) >= mTouchSlop) {
+ startTouchTracking();
+ }
+ } else {
+ // Move
+ mInteractionHandler.updateDisplacement(displacement);
+ }
+ break;
+ }
+ case MotionEvent.ACTION_CANCEL:
+ // TODO: Should be different than ACTION_UP
+ case MotionEvent.ACTION_UP: {
+
+ endInteraction();
+ break;
+ }
+ }
+ }
+
+ private void startTouchTracking() {
+ // Create the shared handler
+ final NavBarSwipeInteractionHandler handler =
+ new NavBarSwipeInteractionHandler(mRunningTask, this);
+
+ // Preload and start the recents activity on a background thread
+ final Context context = this;
+ final RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(context);
+ final int taskId = mRunningTask.id;
+
+ BackgroundExecutor.get().submit(() -> {
+ // Get the snap shot before
+ handler.setTaskSnapshot(getCurrentTaskSnapshot());
+
+ // Start the launcher activity with our custom handler
+ Intent homeIntent = handler.addToIntent(new Intent(mHomeIntent));
+ startActivity(homeIntent, ActivityOptions.makeCustomAnimation(this, 0, 0).toBundle());
+
+ /*
+ ActivityManagerWrapper.getInstance().startRecentsActivity(null, options,
+ ActivityOptions.makeCustomAnimation(this, 0, 0), UserHandle.myUserId(),
+ null, null);
+ */
+
+ // Preload the plan
+ RecentsTaskLoader loader = TouchInteractionService.getRecentsTaskLoader();
+ PreloadOptions opts = new PreloadOptions();
+ opts.loadTitles = false;
+ loadPlan.preloadPlan(opts, loader, taskId, UserHandle.myUserId());
+ // Set the load plan on UI thread
+ mMainThreadExecutor.execute(() -> handler.setRecentsTaskLoadPlan(loadPlan));
+ });
+ mInteractionHandler = handler;
+ }
+
+ private void endInteraction() {
+ if (mInteractionHandler != null) {
+ mVelocityTracker.computeCurrentVelocity(1000,
+ ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
+
+ mInteractionHandler.endTouch(mVelocityTracker.getYVelocity(mActivePointerId));
+ mInteractionHandler = null;
+ }
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+
+ private Bitmap getCurrentTaskSnapshot() {
+ if (mISystemUiProxy == null) {
+ Log.e(TAG, "Never received systemUIProxy");
+ return null;
+ }
+
+ TraceHelper.beginSection("TaskSnapshot");
+ // TODO: We are using some hardcoded layers for now, to best approximate the activity layers
+ try {
+ return mISystemUiProxy.screenshot(new Rect(), mDisplaySize.x, mDisplaySize.y, 0, 100000,
+ false, mDisplayRotation).toBitmap();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error capturing snapshot", e);
+ return null;
+ } finally {
+ TraceHelper.endSection("TaskSnapshot");
+ }
+ }
+}
diff --git a/res/color/all_apps_tab_text.xml b/res/color/all_apps_tab_text.xml
new file mode 100644
index 0000000..f0c6310
--- /dev/null
+++ b/res/color/all_apps_tab_text.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="?android:attr/colorAccent" android:state_selected="true"/>
+ <item android:color="?android:attr/textColorTertiary"/>
+</selector>
\ No newline at end of file
diff --git a/res/drawable/all_apps_handle_landscape.xml b/res/drawable/all_apps_handle_landscape.xml
new file mode 100644
index 0000000..23826ab
--- /dev/null
+++ b/res/drawable/all_apps_handle_landscape.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="13dp"
+ android:height="13dp"
+ android:viewportWidth="13.0"
+ android:viewportHeight="13.0" >
+
+ <path
+ android:pathData="M2 8.5L6.5 4L11 8.5"
+ android:strokeColor="?attr/workspaceAmbientShadowColor"
+ android:strokeWidth="4"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round" />
+
+ <path
+ android:pathData="M2 8.5L6.5 4L11 8.5"
+ android:strokeColor="?attr/workspaceTextColor"
+ android:strokeWidth="2"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round" />
+
+</vector>
diff --git a/res/drawable/all_apps_handle_portrait.xml b/res/drawable/all_apps_handle_portrait.xml
new file mode 100644
index 0000000..75aa448
--- /dev/null
+++ b/res/drawable/all_apps_handle_portrait.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="16dp"
+ android:height="13dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="13.0" >
+
+ <path
+ android:pathData="M2 6h12"
+ android:strokeColor="?attr/workspaceAmbientShadowColor"
+ android:strokeWidth="4"
+ android:strokeLineCap="round" />
+
+ <path
+ android:pathData="M2 6h12"
+ android:strokeColor="?attr/workspaceTextColor"
+ android:strokeWidth="2"
+ android:strokeLineCap="round" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/bg_notification_content.xml b/res/drawable/bg_notification_content.xml
new file mode 100644
index 0000000..cf129eb
--- /dev/null
+++ b/res/drawable/bg_notification_content.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="?attr/popupColorTertiary" />
+
+ <item android:height="3dp" android:top="0dp">
+ <shape>
+ <gradient
+ android:angle="270"
+ android:endColor="@android:color/transparent"
+ android:startColor="#33000000"
+ android:type="linear" />
+ </shape>
+ </item>
+</layer-list>
diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml
index 0c7999e..4ea32b4 100644
--- a/res/layout-land/launcher.xml
+++ b/res/layout-land/launcher.xml
@@ -42,6 +42,19 @@
android:layout_gravity="center"
launcher:pageIndicator="@id/page_indicator" />
+ <com.android.launcher3.pageindicators.PageIndicatorLandscape
+ android:id="@+id/page_indicator"
+ android:theme="@style/HomeScreenElementTheme"
+ android:layout_width="@dimen/dynamic_grid_min_page_indicator_size"
+ android:layout_height="@dimen/dynamic_grid_min_page_indicator_size"
+ android:layout_gravity="bottom|left">
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/all_apps_handle_landscape"
+ android:layout_gravity="center" />
+ </com.android.launcher3.pageindicators.PageIndicatorLandscape>
+
<include layout="@layout/gradient_bg" />
<!-- DO NOT CHANGE THE ID -->
@@ -66,13 +79,6 @@
android:layout_height="match_parent"
android:visibility="invisible" />
- <com.android.launcher3.pageindicators.PageIndicatorCaretLandscape
- android:id="@+id/page_indicator"
- android:theme="@style/HomeScreenElementTheme"
- android:layout_width="@dimen/dynamic_grid_min_page_indicator_size"
- android:layout_height="@dimen/dynamic_grid_min_page_indicator_size"
- android:layout_gravity="bottom|left"/>
-
</com.android.launcher3.dragndrop.DragLayer>
</com.android.launcher3.LauncherRootView>
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 6f837ae..832aaef 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -27,25 +27,54 @@
android:focusableInTouchMode="true"
android:saveEnabled="false" >
- <!-- DO NOT CHANGE THE ID -->
- <com.android.launcher3.allapps.AllAppsRecyclerView
- android:id="@+id/apps_list_view"
+ <include layout="@layout/all_apps_rv_layout" />
+
+ <include layout="@layout/all_apps_fast_scroller" />
+
+ <RelativeLayout
+ android:id="@+id/all_apps_header"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_below="@id/search_container_all_apps"
- android:layout_gravity="center_horizontal|top"
- android:clipToPadding="false"
- android:descendantFocusability="afterDescendants"
- android:focusable="true"
- android:overScrollMode="never" />
+ android:layout_height="wrap_content"
+ android:clickable="true"
+ android:paddingTop="30dp"
+ android:layout_below="@id/search_container_all_apps" >
+
+ <com.android.launcher3.allapps.PredictionRowView
+ android:id="@+id/header_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ <com.android.launcher3.views.SlidingTabStrip
+ android:id="@+id/tabs"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/all_apps_header_tab_height"
+ android:layout_below="@id/header_content"
+ android:orientation="horizontal">
+ <Button
+ android:id="@+id/tab_personal"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:text="@string/all_apps_personal_tab"
+ android:textColor="@color/all_apps_tab_text"
+ android:background="?android:attr/selectableItemBackground"/>
+ <Button
+ android:id="@+id/tab_work"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:text="@string/all_apps_work_tab"
+ android:textColor="@color/all_apps_tab_text"
+ android:background="?android:attr/selectableItemBackground"/>
+ </com.android.launcher3.views.SlidingTabStrip>
+
+ </RelativeLayout>
<!-- Note: we are reusing/repurposing a system attribute for search layout, because of a
platform bug, which prevents using custom attributes in <include> tag -->
<include
android:id="@id/search_container_all_apps"
- layout="?android:attr/keyboardLayout" />
-
- <include layout="@layout/all_apps_fast_scroller" />
+ layout="?android:attr/keyboardLayout"/>
<View
android:id="@+id/nav_bar_bg"
diff --git a/res/layout/all_apps_rv_layout.xml b/res/layout/all_apps_rv_layout.xml
new file mode 100644
index 0000000..3c19f8c
--- /dev/null
+++ b/res/layout/all_apps_rv_layout.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<com.android.launcher3.allapps.AllAppsRecyclerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/apps_list_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_below="@id/search_container_all_apps"
+ android:clipToPadding="false"
+ android:descendantFocusability="afterDescendants"
+ android:focusable="true"
+ android:overScrollMode="never" />
diff --git a/res/layout/all_apps_tabs.xml b/res/layout/all_apps_tabs.xml
new file mode 100644
index 0000000..fa1d591
--- /dev/null
+++ b/res/layout/all_apps_tabs.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<com.android.launcher3.allapps.InterceptingViewPager
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/all_apps_tabs_view_pager"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_below="@id/search_container_all_apps"
+ android:layout_gravity="center_horizontal|top"
+ android:layout_marginTop="@dimen/all_apps_header_tab_height"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:descendantFocusability="afterDescendants"
+ android:paddingTop="30dp">
+
+ <include layout="@layout/all_apps_rv_layout" />
+
+ <include layout="@layout/all_apps_rv_layout" />
+
+</com.android.launcher3.allapps.InterceptingViewPager>
\ No newline at end of file
diff --git a/res/layout/deep_shortcut.xml b/res/layout/deep_shortcut.xml
index 4a2ad42..4a3db1f 100644
--- a/res/layout/deep_shortcut.xml
+++ b/res/layout/deep_shortcut.xml
@@ -35,6 +35,7 @@
android:textColor="?android:attr/textColorPrimary"
android:fontFamily="sans-serif"
launcher:layoutHorizontal="true"
+ launcher:deferShadowGeneration="true"
launcher:iconDisplay="shortcut_popup"
launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size" />
diff --git a/res/layout/notification.xml b/res/layout/notification.xml
deleted file mode 100644
index 1eebb43..0000000
--- a/res/layout/notification.xml
+++ /dev/null
@@ -1,97 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<com.android.launcher3.notification.NotificationItemView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/notification_view"
- android:layout_width="@dimen/bg_popup_item_width"
- android:layout_height="wrap_content"
- android:theme="@style/PopupItem"
- android:elevation="@dimen/deep_shortcuts_elevation">
-
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:clipChildren="false">
-
- <View
- android:id="@+id/gutter_top"
- android:layout_width="match_parent"
- android:layout_height="4dp"
- android:theme="@style/PopupGutter"
- android:visibility="gone" />
-
- <FrameLayout
- android:id="@+id/header"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_header_height"
- android:paddingStart="@dimen/notification_padding_start"
- android:paddingEnd="@dimen/notification_padding_end"
- android:background="?attr/popupColorPrimary"
- android:elevation="@dimen/notification_elevation"
- android:layout_below="@id/gutter_top" >
- <TextView
- android:id="@+id/notification_text"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_gravity="start"
- android:gravity="center_vertical"
- android:text="@string/notifications_header"
- android:textSize="@dimen/notification_header_text_size"
- android:textColor="?android:attr/textColorPrimary" />
- <TextView
- android:id="@+id/notification_count"
- android:layout_width="@dimen/notification_icon_size"
- android:layout_height="match_parent"
- android:layout_gravity="end"
- android:gravity="center"
- android:textSize="@dimen/notification_header_count_text_size"
- android:fontFamily="sans-serif-medium"
- android:textColor="?android:attr/textColorPrimary" />
- </FrameLayout>
-
- <include layout="@layout/notification_main"
- android:id="@+id/main_view"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_main_height"
- android:layout_below="@id/header" />
-
- <View
- android:id="@+id/divider"
- android:layout_width="match_parent"
- android:layout_height="@dimen/popup_item_divider_height"
- android:background="?attr/popupColorTertiary"
- android:layout_below="@id/main_view"
- android:visibility="gone" />
-
- <include layout="@layout/notification_footer"
- android:id="@+id/footer"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_footer_height"
- android:layout_below="@id/divider" />
-
- <View
- android:id="@+id/gutter_bottom"
- android:layout_width="match_parent"
- android:layout_height="4dp"
- android:theme="@style/PopupGutter"
- android:visibility="gone"
- android:layout_below="@id/footer" />
-
- </RelativeLayout>
-
-</com.android.launcher3.notification.NotificationItemView>
diff --git a/res/layout/notification_content.xml b/res/layout/notification_content.xml
new file mode 100644
index 0000000..d01be01
--- /dev/null
+++ b/res/layout/notification_content.xml
@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<merge
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <!-- header -->
+ <FrameLayout
+ android:id="@+id/header"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_header_height"
+ android:paddingEnd="@dimen/notification_padding_end"
+ android:paddingStart="@dimen/notification_padding_start">
+ <TextView
+ android:id="@+id/notification_text"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:gravity="center_vertical"
+ android:text="@string/notifications_header"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="@dimen/notification_header_text_size" />
+ <TextView
+ android:id="@+id/notification_count"
+ android:layout_width="@dimen/notification_icon_size"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
+ android:fontFamily="sans-serif-medium"
+ android:gravity="center"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="@dimen/notification_header_count_text_size" />
+ </FrameLayout>
+
+ <!-- Main view -->
+ <com.android.launcher3.notification.NotificationMainView
+ android:id="@+id/main_view"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_main_height"
+ android:background="@drawable/bg_notification_content"
+ android:focusable="true" >
+
+ <LinearLayout
+ android:id="@+id/text_and_background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?attr/popupColorPrimary"
+ android:gravity="center_vertical"
+ android:orientation="vertical"
+ android:paddingBottom="14dp"
+ android:paddingEnd="@dimen/notification_main_text_padding_end"
+ android:paddingStart="@dimen/notification_padding_start">
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:fontFamily="sans-serif"
+ android:lines="1"
+ android:textAlignment="viewStart"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="@dimen/notification_main_title_size" />
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:fontFamily="sans-serif"
+ android:lines="1"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textSize="@dimen/notification_main_text_size" />
+ </LinearLayout>
+
+ <View
+ android:id="@+id/popup_item_icon"
+ android:layout_width="@dimen/notification_icon_size"
+ android:layout_height="@dimen/notification_icon_size"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginBottom="7dp"
+ android:layout_marginEnd="@dimen/notification_padding_end" />
+
+ </com.android.launcher3.notification.NotificationMainView>
+
+ <!-- Divider -->
+ <View
+ android:id="@+id/divider"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/popup_item_divider_height"
+ android:layout_below="@id/main_view"
+ android:background="?attr/popupColorTertiary" />
+
+ <!-- Footer -->
+ <com.android.launcher3.notification.NotificationFooterLayout
+ android:id="@+id/footer"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_footer_height"
+ android:layout_gravity="center_vertical"
+ android:clipChildren="false">
+
+ <LinearLayout
+ android:id="@+id/icon_row"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:gravity="end|center_vertical"
+ android:orientation="horizontal"
+ android:padding="@dimen/notification_footer_icon_row_padding"/>
+
+ <View
+ android:id="@+id/overflow"
+ android:layout_width="@dimen/horizontal_ellipsis_size"
+ android:layout_height="@dimen/horizontal_ellipsis_size"
+ android:layout_gravity="start|center_vertical"
+ android:layout_marginStart="@dimen/horizontal_ellipsis_offset"
+ android:background="@drawable/horizontal_ellipsis" />
+
+ </com.android.launcher3.notification.NotificationFooterLayout>
+</merge>
\ No newline at end of file
diff --git a/res/layout/notification_footer.xml b/res/layout/notification_footer.xml
deleted file mode 100644
index 86280e0..0000000
--- a/res/layout/notification_footer.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-
-<com.android.launcher3.notification.NotificationFooterLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:elevation="@dimen/notification_elevation"
- android:clipChildren="false"
- android:layout_gravity="center_vertical"
- android:background="?attr/popupColorPrimary">
-
- <LinearLayout
- android:id="@+id/icon_row"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal"
- android:gravity="end|center_vertical"
- android:padding="@dimen/notification_footer_icon_row_padding"
- android:clipToPadding="false"
- android:clipChildren="false"/>
-
- <View
- android:id="@+id/overflow"
- android:layout_width="@dimen/horizontal_ellipsis_size"
- android:layout_height="@dimen/horizontal_ellipsis_size"
- android:background="@drawable/horizontal_ellipsis"
- android:layout_marginStart="@dimen/horizontal_ellipsis_offset"
- android:layout_gravity="start|center_vertical" />
-
-</com.android.launcher3.notification.NotificationFooterLayout>
-
diff --git a/res/layout/notification_gutter.xml b/res/layout/notification_gutter.xml
new file mode 100644
index 0000000..10e7f7d
--- /dev/null
+++ b/res/layout/notification_gutter.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<View
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="4dp"
+ android:layout_marginTop="4dp"
+ android:background="@drawable/bg_notification_content" />
\ No newline at end of file
diff --git a/res/layout/notification_main.xml b/res/layout/notification_main.xml
deleted file mode 100644
index f94face..0000000
--- a/res/layout/notification_main.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-
-<com.android.launcher3.notification.NotificationMainView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:focusable="true"
- android:elevation="@dimen/notification_elevation" >
-
- <LinearLayout
- android:id="@+id/text_and_background"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:gravity="center_vertical"
- android:background="?attr/popupColorPrimary"
- android:paddingStart="@dimen/notification_padding_start"
- android:paddingEnd="@dimen/notification_main_text_padding_end"
- android:paddingBottom="14dp">
- <TextView
- android:id="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAlignment="viewStart"
- android:fontFamily="sans-serif"
- android:textSize="@dimen/notification_main_title_size"
- android:textColor="?android:attr/textColorPrimary"
- android:lines="1"
- android:ellipsize="end" />
-
- <TextView
- android:id="@+id/text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:fontFamily="sans-serif"
- android:textSize="@dimen/notification_main_text_size"
- android:textColor="?android:attr/textColorSecondary"
- android:lines="1"
- android:ellipsize="end" />
- </LinearLayout>
-
- <View
- android:id="@+id/popup_item_icon"
- android:layout_width="@dimen/notification_icon_size"
- android:layout_height="@dimen/notification_icon_size"
- android:layout_marginEnd="@dimen/notification_padding_end"
- android:layout_marginBottom="7dp"
- android:layout_gravity="center_vertical|end" />
-
-</com.android.launcher3.notification.NotificationMainView>
-
diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml
index d1ac56c..c795b81 100644
--- a/res/layout/overview_panel.xml
+++ b/res/layout/overview_panel.xml
@@ -14,11 +14,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<LinearLayout
+<com.android.launcher3.uioverrides.OverviewPanel
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:theme="@style/HomeScreenElementTheme"
- launcher:layout_ignoreInsets="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"
@@ -73,4 +71,4 @@
android:textColor="?attr/workspaceTextColor"
android:textSize="12sp" />
-</LinearLayout>
\ No newline at end of file
+</com.android.launcher3.uioverrides.OverviewPanel>
\ No newline at end of file
diff --git a/res/layout/page_indicator.xml b/res/layout/page_indicator.xml
index 92f52d6..2df511b 100644
--- a/res/layout/page_indicator.xml
+++ b/res/layout/page_indicator.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
-<com.android.launcher3.pageindicators.PageIndicatorLineCaret
+<com.android.launcher3.pageindicators.PageIndicatorLine
xmlns:android="http://schemas.android.com/apk/res/android"
android:theme="@style/HomeScreenElementTheme"
android:layout_width="match_parent"
@@ -23,6 +23,7 @@
android:id="@+id/all_apps_handle"
android:layout_width="48dp"
android:layout_height="@dimen/dynamic_grid_min_page_indicator_size"
+ android:src="@drawable/all_apps_handle_portrait"
android:layout_gravity="top|center"
android:scaleType="centerInside"/>
-</com.android.launcher3.pageindicators.PageIndicatorLineCaret>
+</com.android.launcher3.pageindicators.PageIndicatorLine>
diff --git a/res/layout/popup_container.xml b/res/layout/popup_container.xml
index 67db4a5..c737407 100644
--- a/res/layout/popup_container.xml
+++ b/res/layout/popup_container.xml
@@ -19,11 +19,8 @@
android:id="@+id/deep_shortcuts_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
+ android:background="?attr/popupColorPrimary"
android:clipToPadding="false"
android:clipChildren="false"
android:elevation="@dimen/deep_shortcuts_elevation"
- android:orientation="vertical">
-
-</com.android.launcher3.popup.PopupContainerWithArrow>
\ No newline at end of file
+ android:orientation="vertical" />
\ No newline at end of file
diff --git a/res/layout/shortcuts_item.xml b/res/layout/shortcuts_item.xml
deleted file mode 100644
index 7cd996d..0000000
--- a/res/layout/shortcuts_item.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<com.android.launcher3.shortcuts.ShortcutsItemView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/shortcuts_view"
- android:layout_width="@dimen/bg_popup_item_width"
- android:layout_height="wrap_content"
- android:elevation="@dimen/deep_shortcuts_elevation">
-
- <LinearLayout
- android:id="@+id/content"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <!-- The shortcuts header is added at runtime when necessary. -->
-
- <LinearLayout
- android:id="@+id/shortcuts"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical" />
- </LinearLayout>
-
-</com.android.launcher3.shortcuts.ShortcutsItemView>
diff --git a/res/layout/system_shortcut.xml b/res/layout/system_shortcut.xml
index 04f3d02..1888e22 100644
--- a/res/layout/system_shortcut.xml
+++ b/res/layout/system_shortcut.xml
@@ -34,6 +34,7 @@
android:fontFamily="sans-serif"
launcher:iconDisplay="shortcut_popup"
launcher:layoutHorizontal="true"
+ launcher:deferShadowGeneration="true"
android:focusable="false" />
<View
diff --git a/res/layout/system_shortcut_icons.xml b/res/layout/system_shortcut_icons.xml
index 34d63e7..4daf469 100644
--- a/res/layout/system_shortcut_icons.xml
+++ b/res/layout/system_shortcut_icons.xml
@@ -22,6 +22,4 @@
android:orientation="horizontal"
android:gravity="end|center_vertical"
android:background="?attr/popupColorSecondary"
- android:elevation="1dp"
- android:outlineProvider="none" />
- <!-- We have elevation so this is drawn on top, but no outline provider to remove shadow -->
+ android:clipToPadding="true" />
diff --git a/res/layout/zzz_weight_watcher.xml b/res/layout/zzz_weight_watcher.xml
deleted file mode 100644
index 07fd39e..0000000
--- a/res/layout/zzz_weight_watcher.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<com.android.launcher3.testing.WeightWatcher xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
diff --git a/res/values/config.xml b/res/values/config.xml
index c0bf360..0ff0d75 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -1,8 +1,4 @@
<resources>
-<!-- Dynamic Grid -->
- <!-- Out of 100, the percent of space the overview bar should try and take vertically. -->
- <integer name="config_dynamic_grid_overview_icon_zone_percentage">22</integer>
-
<!-- Miscellaneous -->
<bool name="config_largeHeap">false</bool>
<bool name="is_tablet">false</bool>
@@ -50,8 +46,6 @@
<!-- Out of 100, the percent to shrink the workspace during spring loaded mode. -->
<integer name="config_workspaceSpringLoadShrinkPercentage">90</integer>
- <!-- Out of 100, the percent to shrink the workspace during overview mode. -->
- <integer name="config_workspaceOverviewShrinkPercentage">70</integer>
<!-- This constant stores the ratio of the all apps button drawable which
is used for internal (baked-in) padding -->
@@ -140,4 +134,8 @@
<item type="id" name="search_container_hotseat" />
<item type="id" name="search_container_all_apps" />
+<!-- Recents -->
+ <integer name="config_recentsMaxThumbnailCacheSize">6</integer>
+ <integer name="config_recentsMaxIconCacheSize">12</integer>
+
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 481199e..efe5043 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -15,7 +15,10 @@
-->
<resources>
-<!-- Dynamic Grid -->
+
+ <dimen name="click_shadow_elevation">4dp</dimen>
+
+ <!-- Dynamic Grid -->
<dimen name="dynamic_grid_edge_margin">8dp</dimen>
<dimen name="dynamic_grid_min_page_indicator_size">32dp</dimen>
<dimen name="dynamic_grid_page_indicator_line_height">1dp</dimen>
@@ -86,10 +89,9 @@
<dimen name="all_apps_empty_search_bg_top_offset">144dp</dimen>
<dimen name="all_apps_background_canvas_width">700dp</dimen>
<dimen name="all_apps_background_canvas_height">475dp</dimen>
- <dimen name="all_apps_caret_stroke_width">2dp</dimen>
- <dimen name="all_apps_caret_shadow_spread">1dp</dimen>
- <dimen name="all_apps_caret_size">13dp</dimen>
<dimen name="all_apps_caret_workspace_offset">18dp</dimen>
+ <dimen name="all_apps_header_tab_height">50dp</dimen>
+ <dimen name="all_apps_tabs_indicator_height">2dp</dimen>
<!-- Search bar in All Apps -->
<dimen name="all_apps_header_max_elevation">3dp</dimen>
@@ -179,6 +181,7 @@
<dimen name="deep_shortcut_drag_handle_size">16dp</dimen>
<dimen name="popup_padding_start">10dp</dimen>
<dimen name="popup_padding_end">16dp</dimen>
+ <dimen name="popup_vertical_padding">4dp</dimen>
<dimen name="popup_arrow_width">10dp</dimen>
<dimen name="popup_arrow_height">8dp</dimen>
<dimen name="popup_arrow_vertical_offset">-2dp</dimen>
@@ -226,10 +229,12 @@
<dimen name="notification_footer_icon_size">18dp</dimen>
<!-- notification_icon_size + notification_padding_end + 16dp padding between icon and text -->
<dimen name="notification_main_text_padding_end">52dp</dimen>
- <dimen name="notification_elevation">2dp</dimen>
<dimen name="horizontal_ellipsis_size">18dp</dimen>
<!-- arrow_horizontal_offset_start - (ellipsis_size - arrow_width) / 2 -->
<dimen name="horizontal_ellipsis_offset">19dp</dimen>
<dimen name="popup_item_divider_height">0.5dp</dimen>
<dimen name="swipe_helper_falsing_threshold">70dp</dimen>
+
+<!-- Recents -->
+ <dimen name="recents_page_spacing">10dp</dimen>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e9b00f6..fdd4d8d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -318,4 +318,10 @@
<!-- Accessibility confirmation for notification being dismissed. -->
<string name="notification_dismissed">Notification dismissed</string>
+ <!-- Label of tab to indicate personal apps -->
+ <string name="all_apps_personal_tab">Personal</string>
+
+ <!-- Label of tab to indicate work apps -->
+ <string name="all_apps_work_tab">Work</string>
+
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 8129e81..8cc4743 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -136,13 +136,6 @@
<style name="PopupItem">
<item name="android:colorControlHighlight">?attr/popupColorTertiary</item>
</style>
- <style name="PopupGutter">
- <item name="android:backgroundTintMode">multiply</item>
- <item name="android:backgroundTint">?attr/popupColorSecondary</item>
- <item name="android:background">@drawable/gutter_horizontal</item>
- <item name="android:elevation">@dimen/notification_elevation</item>
- <item name="android:outlineProvider">none</item>
- </style>
<!-- Drop targets -->
<style name="DropTargetButtonBase">
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index c582fc5..30c1c54 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -15,7 +15,7 @@
limitations under the License.
-->
-<profiles xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
+<profiles xmlns:launcher="http://schemas.android.com/apk/res-auto" >
<profile
launcher:name="Super Short Stubby"
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 62e0fb1..26024e5 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -20,7 +20,6 @@
import android.content.Context;
import android.support.annotation.IntDef;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
@@ -42,7 +41,8 @@
TYPE_ACTION_POPUP,
TYPE_WIDGETS_BOTTOM_SHEET,
TYPE_WIDGET_RESIZE_FRAME,
- TYPE_WIDGETS_FULL_SHEET
+ TYPE_WIDGETS_FULL_SHEET,
+ TYPE_QUICKSTEP_PREVIEW
})
@Retention(RetentionPolicy.SOURCE)
public @interface FloatingViewType {}
@@ -51,9 +51,11 @@
public static final int TYPE_WIDGETS_BOTTOM_SHEET = 1 << 2;
public static final int TYPE_WIDGET_RESIZE_FRAME = 1 << 3;
public static final int TYPE_WIDGETS_FULL_SHEET = 1 << 4;
+ public static final int TYPE_QUICKSTEP_PREVIEW = 1 << 5;
public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
- | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET;
+ | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
+ | TYPE_QUICKSTEP_PREVIEW;
protected boolean mIsOpen;
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index 8ac8570..5eb6cc7 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -154,7 +154,7 @@
for (int i = data.size() - 1; i >= 0; i--) {
AppInfo info = data.get(i);
if (matcher.matches(info, info.componentName)) {
- info.isDisabled = op.apply(info.isDisabled);
+ info.runtimeStatusFlags = op.apply(info.runtimeStatusFlags);
modified.add(info);
}
}
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index 7d2f753..9796d18 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -21,9 +21,12 @@
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
+import android.os.Build;
+import android.os.Process;
import android.os.UserHandle;
import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageManagerHelper;
@@ -32,10 +35,6 @@
*/
public class AppInfo extends ItemInfoWithIcon {
- public static final int FLAG_SYSTEM_UNKNOWN = 0;
- public static final int FLAG_SYSTEM_YES = 1 << 0;
- public static final int FLAG_SYSTEM_NO = 1 << 1;
-
/**
* The intent used to start the application.
*/
@@ -43,16 +42,6 @@
public ComponentName componentName;
- /**
- * {@see ShortcutInfo#isDisabled}
- */
- public int isDisabled = ShortcutInfo.DEFAULT;
-
- /**
- * Stores if the app is a system app or not.
- */
- public int isSystemApp;
-
public AppInfo() {
itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
}
@@ -73,18 +62,12 @@
this.componentName = info.getComponentName();
this.container = ItemInfo.NO_ID;
this.user = user;
- if (PackageManagerHelper.isAppSuspended(info.getApplicationInfo())) {
- isDisabled |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
- }
- if (quietModeEnabled) {
- isDisabled |= ShortcutInfo.FLAG_DISABLED_QUIET_USER;
- }
-
intent = makeLaunchIntent(info);
- isSystemApp = (info.getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 0
- ? FLAG_SYSTEM_NO : FLAG_SYSTEM_YES;
-
+ if (quietModeEnabled) {
+ runtimeStatusFlags |= FLAG_DISABLED_QUIET_USER;
+ }
+ updateRuntimeFlagsForActivityTarget(this, info);
}
public AppInfo(AppInfo info) {
@@ -92,8 +75,6 @@
componentName = info.componentName;
title = Utilities.trim(info.title);
intent = new Intent(info.intent);
- isDisabled = info.isDisabled;
- isSystemApp = info.isSystemApp;
}
@Override
@@ -120,8 +101,20 @@
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
}
- @Override
- public boolean isDisabled() {
- return isDisabled != 0;
+ public static void updateRuntimeFlagsForActivityTarget(
+ ItemInfoWithIcon info, LauncherActivityInfo lai) {
+ ApplicationInfo appInfo = lai.getApplicationInfo();
+ if (PackageManagerHelper.isAppSuspended(appInfo)) {
+ info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
+ }
+ info.runtimeStatusFlags |= (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0
+ ? FLAG_SYSTEM_NO : FLAG_SYSTEM_YES;
+
+ if (FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.ATLEAST_OREO
+ && appInfo.targetSdkVersion >= Build.VERSION_CODES.O
+ && Process.myUserHandle().equals(lai.getUser())) {
+ // The icon for a non-primary user is badged, hence it's not exactly an adaptive icon.
+ info.runtimeStatusFlags |= FLAG_ADAPTIVE_ICON;
+ }
}
}
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index afb83be..b315980 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -61,9 +61,13 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- ViewGroup parent = (ViewGroup) getParent();
+ bindFastScrollbar();
+ }
+
+ public void bindFastScrollbar() {
+ ViewGroup parent = (ViewGroup) getParent().getParent();
mScrollbar = parent.findViewById(R.id.fast_scroller);
- mScrollbar.setRecyclerView(this, (TextView) parent.findViewById(R.id.fast_scroller_popup));
+ mScrollbar.setRecyclerView(this, parent.findViewById(R.id.fast_scroller_popup));
}
/**
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index cd72fba..a590504 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -178,6 +178,16 @@
}
+ /**
+ * Resets the view so it can be recycled.
+ */
+ public void reset() {
+ mBadgeInfo = null;
+ mBadgePalette = null;
+ mBadgeScale = 0f;
+ mForceHideBadge = false;
+ }
+
public void applyFromShortcutInfo(ShortcutInfo info) {
applyFromShortcutInfo(info, false);
}
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 1a1c319..cfb55cc 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -16,7 +16,7 @@
package com.android.launcher3;
-import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_NEXT_FRAME;
+import static com.android.launcher3.LauncherState.NORMAL;
import android.animation.AnimatorSet;
import android.animation.FloatArrayEvaluator;
@@ -37,10 +37,9 @@
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.LinearInterpolator;
import android.widget.TextView;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
@@ -54,6 +53,7 @@
public abstract class ButtonDropTarget extends TextView
implements DropTarget, DragController.DragListener, OnClickListener {
+ private static final int[] sTempCords = new int[2];
private static final int DRAG_VIEW_DROP_DURATION = 285;
private final boolean mHideParentOnDisable;
@@ -201,6 +201,10 @@
protected abstract boolean supportsDrop(ItemInfo info);
+ public boolean supportsAccessibilityDrop(ItemInfo info) {
+ return supportsDrop(info);
+ }
+
@Override
public boolean isDropEnabled() {
return mActive && (mAccessibleDrag ||
@@ -231,19 +235,22 @@
public void run() {
completeDrop(d);
mDropTargetBar.onDragEnd();
- mLauncher.exitSpringLoadedDragMode(SPRING_LOADED_EXIT_NEXT_FRAME);
+ mLauncher.getStateManager().goToState(NORMAL);
}
};
dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f,
DRAG_VIEW_DROP_DURATION,
- new DecelerateInterpolator(2),
- new LinearInterpolator(), onAnimationEndRunnable,
+ Interpolators.DEACCEL_2, Interpolators.LINEAR, onAnimationEndRunnable,
DragLayer.ANIMATION_END_DISAPPEAR, null);
}
+ public abstract int getAccessibilityAction();
+
@Override
public void prepareAccessibilityDrop() { }
+ public abstract void onAccessibilityDrop(View view, ItemInfo item);
+
public abstract void completeDrop(DragObject d);
@Override
@@ -251,9 +258,9 @@
super.getHitRect(outRect);
outRect.bottom += mBottomDragPadding;
- int[] coords = new int[2];
- mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, coords);
- outRect.offsetTo(coords[0], coords[1]);
+ sTempCords[0] = sTempCords[1] = 0;
+ mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, sTempCords);
+ outRect.offsetTo(sTempCords[0], sTempCords[1]);
}
public Rect getIconRect(DragObject dragObject) {
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 3643971..79a34a0 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -44,15 +44,15 @@
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.DecelerateInterpolator;
+
import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
import com.android.launcher3.accessibility.FolderAccessibilityHelper;
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.folder.PreviewBackground;
import com.android.launcher3.graphics.DragPreviewProvider;
import com.android.launcher3.util.CellAndSpan;
@@ -60,6 +60,7 @@
import com.android.launcher3.util.ParcelableSparseArray;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.Thunk;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -224,7 +225,7 @@
mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * grid.iconSizePx);
// Initialize the data structures used for the drag visualization.
- mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
+ mEaseOutInterpolator = Interpolators.DEACCEL_2_5; // Quint ease out
mDragCell[0] = mDragCell[1] = -1;
for (int i = 0; i < mDragOutlines.length; i++) {
mDragOutlines[i] = new Rect(-1, -1, -1, -1);
@@ -342,7 +343,7 @@
// the home screen mode, however, once in overview mode stylus button press should be
// enabled to allow rearranging the different home screens. So check what mode
// the workspace is in, and only perform stylus button presses while in overview mode.
- if (mLauncher.mWorkspace.isInOverviewMode()
+ if (mLauncher.isInState(LauncherState.OVERVIEW)
&& mStylusEventHelper.onMotionEvent(ev)) {
return true;
}
@@ -384,16 +385,7 @@
@Override
public void setPressedIcon(BubbleTextView icon, Bitmap background) {
- if (icon == null || background == null) {
- mTouchFeedbackView.setBitmap(null);
- mTouchFeedbackView.animate().cancel();
- } else {
- if (mTouchFeedbackView.setBitmap(background)) {
- mTouchFeedbackView.alignWithIconView(icon, mShortcutsAndWidgets,
- null /* clipAgainstView */);
- mTouchFeedbackView.animateShadow();
- }
- }
+ mTouchFeedbackView.setPressedIcon(icon, background);
}
void setIsDragOverlapping(boolean isDragOverlapping) {
diff --git a/src/com/android/launcher3/ClickShadowView.java b/src/com/android/launcher3/ClickShadowView.java
index aad1112..5391b4d 100644
--- a/src/com/android/launcher3/ClickShadowView.java
+++ b/src/com/android/launcher3/ClickShadowView.java
@@ -16,15 +16,28 @@
package com.android.launcher3;
+import static com.android.launcher3.FastBitmapDrawable.CLICK_FEEDBACK_DURATION;
+import static com.android.launcher3.FastBitmapDrawable.CLICK_FEEDBACK_INTERPOLATOR;
+import static com.android.launcher3.LauncherAnimUtils.ELEVATION;
+import static com.android.launcher3.graphics.HolographicOutlineHelper.ADAPTIVE_ICON_SHADOW_BITMAP;
+
+import android.animation.ObjectAnimator;
+import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Rect;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.util.Property;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
public class ClickShadowView extends View {
@@ -32,6 +45,8 @@
private static final int SHADOW_LOW_ALPHA = 30;
private static final int SHADOW_HIGH_ALPHA = 60;
+ private static float sAdaptiveIconScaleFactor = 1f;
+
private final Paint mPaint;
@ViewDebug.ExportedProperty(category = "launcher")
@@ -40,6 +55,10 @@
private final float mShadowPadding;
private Bitmap mBitmap;
+ private ObjectAnimator mAnim;
+
+ private Drawable mAdaptiveIcon;
+ private ViewOutlineProvider mOutlineProvider;
public ClickShadowView(Context context) {
super(context);
@@ -50,6 +69,10 @@
mShadowOffset = getResources().getDimension(R.dimen.click_shadow_high_shift);
}
+ public static void setAdaptiveIconScaleFactor(float factor) {
+ sAdaptiveIconScaleFactor = factor;
+ }
+
/**
* @return extra space required by the view to show the shadow.
*/
@@ -57,11 +80,64 @@
return (int) (SHADOW_SIZE_FACTOR * mShadowPadding);
}
+ public void setPressedIcon(BubbleTextView icon, Bitmap background) {
+ if (icon == null) {
+ setBitmap(null);
+ cancelAnim();
+ return;
+ }
+ if (background == null) {
+ if (mBitmap == ADAPTIVE_ICON_SHADOW_BITMAP) {
+ // clear animation shadow
+ }
+ setBitmap(null);
+ cancelAnim();
+ icon.setOutlineProvider(null);
+ } else if (setBitmap(background)) {
+ if (mBitmap == ADAPTIVE_ICON_SHADOW_BITMAP) {
+ setupAdaptiveShadow(icon);
+ cancelAnim();
+ startAnim(icon, ELEVATION,
+ getResources().getDimension(R.dimen.click_shadow_elevation));
+ } else {
+ alignWithIconView(icon);
+ startAnim(this, ALPHA, 1);
+ }
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.O)
+ private void setupAdaptiveShadow(final BubbleTextView view) {
+ if (mAdaptiveIcon == null) {
+ mAdaptiveIcon = new AdaptiveIconDrawable(null, null);
+ mOutlineProvider = new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ mAdaptiveIcon.getOutline(outline);
+ }
+ };
+ }
+
+ int iconWidth = view.getRight() - view.getLeft();
+ int iconHSpace = iconWidth - view.getCompoundPaddingRight() - view.getCompoundPaddingLeft();
+ int drawableWidth = view.getIcon().getBounds().width();
+
+ Rect bounds = new Rect();
+ bounds.left = view.getCompoundPaddingLeft() + (iconHSpace - drawableWidth) / 2;
+ bounds.right = bounds.left + drawableWidth;
+ bounds.top = view.getPaddingTop();
+ bounds.bottom = bounds.top + view.getIcon().getBounds().height();
+ Utilities.scaleRectAboutCenter(bounds, sAdaptiveIconScaleFactor);
+
+ mAdaptiveIcon.setBounds(bounds);
+ view.setOutlineProvider(mOutlineProvider);
+ }
+
/**
* Applies the new bitmap.
* @return true if the view was invalidated.
*/
- public boolean setBitmap(Bitmap b) {
+ private boolean setBitmap(Bitmap b) {
if (b != mBitmap){
mBitmap = b;
invalidate();
@@ -80,48 +156,51 @@
}
}
- public void animateShadow() {
- setAlpha(0);
- animate().alpha(1)
- .setDuration(FastBitmapDrawable.CLICK_FEEDBACK_DURATION)
- .setInterpolator(FastBitmapDrawable.CLICK_FEEDBACK_INTERPOLATOR)
- .start();
+ private void cancelAnim() {
+ if (mAnim != null) {
+ mAnim.cancel();
+ mAnim.setCurrentPlayTime(0);
+ mAnim = null;
+ }
+ }
+
+ private void startAnim(View target, Property<View, Float> property, float endValue) {
+ cancelAnim();
+ property.set(target, 0f);
+ mAnim = ObjectAnimator.ofFloat(target, property, endValue);
+ mAnim.setDuration(CLICK_FEEDBACK_DURATION)
+ .setInterpolator(CLICK_FEEDBACK_INTERPOLATOR);
+ mAnim.start();
}
/**
* Aligns the shadow with {@param view}
- * @param viewParent immediate parent of {@param view}. It must be a sibling of this view.
+ * Note: {@param view} must be a descendant of my parent.
*/
- public void alignWithIconView(BubbleTextView view, ViewGroup viewParent, View clipAgainstView) {
- float leftShift = view.getLeft() + viewParent.getLeft() - getLeft();
- float topShift = view.getTop() + viewParent.getTop() - getTop();
+ private void alignWithIconView(BubbleTextView view) {
+ int[] coords = new int[] {0, 0};
+ Utilities.getDescendantCoordRelativeToAncestor(
+ (ViewGroup) view.getParent(), (View) getParent(), coords, false);
+
+ float leftShift = view.getLeft() + coords[0] - getLeft();
+ float topShift = view.getTop() + coords[1] - getTop();
int iconWidth = view.getRight() - view.getLeft();
int iconHeight = view.getBottom() - view.getTop();
int iconHSpace = iconWidth - view.getCompoundPaddingRight() - view.getCompoundPaddingLeft();
float drawableWidth = view.getIcon().getBounds().width();
- if (clipAgainstView != null) {
- // Set the bounds to clip against
- int[] coords = new int[] {0, 0};
- Utilities.getDescendantCoordRelativeToAncestor(clipAgainstView, (View) getParent(),
- coords, false);
- int clipLeft = (int) Math.max(0, coords[0] - leftShift - mShadowPadding);
- int clipTop = (int) Math.max(0, coords[1] - topShift - mShadowPadding) ;
- setClipBounds(new Rect(clipLeft, clipTop, clipLeft + iconWidth, clipTop + iconHeight));
- } else {
- // Reset the clip bounds
- setClipBounds(null);
- }
+ // Set the bounds to clip against
+ int clipLeft = (int) Math.max(0, coords[0] - leftShift - mShadowPadding);
+ int clipTop = (int) Math.max(0, coords[1] - topShift - mShadowPadding) ;
+ setClipBounds(new Rect(clipLeft, clipTop, clipLeft + iconWidth, clipTop + iconHeight));
setTranslationX(leftShift
- + viewParent.getTranslationX()
+ view.getCompoundPaddingLeft() * view.getScaleX()
+ (iconHSpace - drawableWidth) * view.getScaleX() / 2 /* drawable gap */
+ iconWidth * (1 - view.getScaleX()) / 2 /* gap due to scale */
- mShadowPadding /* extra shadow size */
);
setTranslationY(topShift
- + viewParent.getTranslationY()
+ view.getPaddingTop() * view.getScaleY() /* drawable gap */
+ view.getHeight() * (1 - view.getScaleY()) / 2 /* gap due to scale */
- mShadowPadding /* extra shadow size */
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index fdd4f34..c12ea57 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -21,6 +21,7 @@
import android.util.AttributeSet;
import android.view.View;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.folder.Folder;
@@ -49,14 +50,22 @@
setTextBasedOnDragSource(dragObject.dragInfo);
}
- /** @return true for items that should have a "Remove" action in accessibility. */
- public static boolean supportsAccessibleDrop(ItemInfo info) {
+ /**
+ * @return true for items that should have a "Remove" action in accessibility.
+ */
+ @Override
+ public boolean supportsAccessibilityDrop(ItemInfo info) {
return (info instanceof ShortcutInfo)
|| (info instanceof LauncherAppWidgetInfo)
|| (info instanceof FolderInfo);
}
@Override
+ public int getAccessibilityAction() {
+ return LauncherAccessibilityDelegate.REMOVE;
+ }
+
+ @Override
protected boolean supportsDrop(ItemInfo info) {
return true;
}
@@ -77,19 +86,21 @@
public void completeDrop(DragObject d) {
ItemInfo item = d.dragInfo;
if ((d.dragSource instanceof Workspace) || (d.dragSource instanceof Folder)) {
- removeWorkspaceOrFolderItem(mLauncher, item, null);
+ onAccessibilityDrop(null, item);
}
}
/**
* Removes the item from the workspace. If the view is not null, it also removes the view.
*/
- public static void removeWorkspaceOrFolderItem(Launcher launcher, ItemInfo item, View view) {
+ @Override
+ public void onAccessibilityDrop(View view, ItemInfo item) {
// Remove the item from launcher and the db, we can ignore the containerInfo in this call
// because we already remove the drag view from the folder (if the drag originated from
// a folder) in Folder.beginDrag()
- launcher.removeItem(view, item, true /* deleteFromDb */);
- launcher.getWorkspace().stripEmptyScreens();
- launcher.getDragLayer().announceForAccessibility(launcher.getString(R.string.item_removed));
+ mLauncher.removeItem(view, item, true /* deleteFromDb */);
+ mLauncher.getWorkspace().stripEmptyScreens();
+ mLauncher.getDragLayer()
+ .announceForAccessibility(getContext().getString(R.string.item_removed));
}
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 8f7e882..c6226f4 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -27,11 +27,11 @@
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.View;
-import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.FrameLayout;
import com.android.launcher3.CellLayout.ContainerType;
+import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.badge.BadgeRenderer;
import java.util.ArrayList;
@@ -65,12 +65,6 @@
private static final float TALL_DEVICE_ASPECT_RATIO_THRESHOLD = 2.0f;
- // Overview mode
- private final int overviewModeMinIconZoneHeightPx;
- private final int overviewModeMaxIconZoneHeightPx;
- private final int overviewModeBarItemWidthPx;
- private final int overviewModeBarSpacerWidthPx;
- private final float overviewModeIconZoneRatio;
// Workspace
private final int desiredWorkspaceLeftRightMarginPx;
@@ -196,16 +190,6 @@
res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
topWorkspacePadding =
res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_top_padding);
- overviewModeMinIconZoneHeightPx =
- res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
- overviewModeMaxIconZoneHeightPx =
- res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
- overviewModeBarItemWidthPx =
- res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
- overviewModeBarSpacerWidthPx =
- res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
- overviewModeIconZoneRatio =
- res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
iconDrawablePaddingOriginalPx =
res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size);
@@ -570,13 +554,6 @@
}
}
- int getOverviewModeButtonBarHeight() {
- int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
- return Utilities.boundToRange(zoneHeight,
- overviewModeMinIconZoneHeightPx,
- overviewModeMaxIconZoneHeightPx);
- }
-
public static int calculateCellWidth(int width, int countX) {
return width / countX;
}
@@ -597,16 +574,6 @@
return isVerticalBarLayout() || isLargeTablet;
}
- private int getVisibleChildCount(ViewGroup parent) {
- int visibleChildren = 0;
- for (int i = 0; i < parent.getChildCount(); i++) {
- if (parent.getChildAt(i).getVisibility() != View.GONE) {
- visibleChildren++;
- }
- }
- return visibleChildren;
- }
-
public void layout(Launcher launcher, boolean notifyListeners) {
FrameLayout.LayoutParams lp;
boolean hasVerticalBarLayout = isVerticalBarLayout();
@@ -701,25 +668,10 @@
pageIndicator.setLayoutParams(lp);
}
- // Layout the Overview Mode
- ViewGroup overviewMode = launcher.getOverviewPanel();
- if (overviewMode != null) {
- int visibleChildCount = getVisibleChildCount(overviewMode);
- int totalItemWidth = visibleChildCount * overviewModeBarItemWidthPx;
- int maxWidth = totalItemWidth + (visibleChildCount - 1) * overviewModeBarSpacerWidthPx;
-
- lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
- lp.width = Math.min(availableWidthPx, maxWidth);
- lp.height = getOverviewModeButtonBarHeight();
- lp.bottomMargin = mInsets.bottom;
- overviewMode.setLayoutParams(lp);
- }
-
// Layout the AllAppsRecyclerView
- View view = launcher.findViewById(R.id.apps_list_view);
+ AllAppsContainerView appsView = launcher.findViewById(R.id.apps_view);
int paddingLeftRight = desiredWorkspaceLeftRightMarginPx + cellLayoutPaddingLeftRightPx;
- view.setPadding(paddingLeftRight, view.getPaddingTop(), paddingLeftRight,
- view.getPaddingBottom());
+ appsView.setRecyclerViewSidePadding(paddingLeftRight, paddingLeftRight);
if (notifyListeners) {
for (int i = mListeners.size() - 1; i >= 0; i--) {
@@ -728,13 +680,13 @@
}
}
- private int getCurrentWidth() {
+ public int getCurrentWidth() {
return isLandscape
? Math.max(widthPx, heightPx)
: Math.min(widthPx, heightPx);
}
- private int getCurrentHeight() {
+ public int getCurrentHeight() {
return isLandscape
? Math.min(widthPx, heightPx)
: Math.max(widthPx, heightPx);
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index 29a1349..3eca5cd 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -16,6 +16,9 @@
package com.android.launcher3;
+import static com.android.launcher3.AlphaUpdateListener.updateVisibility;
+import static com.android.launcher3.Utilities.isAccessibilityEnabled;
+
import android.animation.TimeInterpolator;
import android.content.Context;
import android.util.AttributeSet;
@@ -23,29 +26,27 @@
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
-import android.view.accessibility.AccessibilityManager;
-import android.view.animation.AccelerateInterpolator;
import android.widget.LinearLayout;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
+import java.util.ArrayList;
+
/*
* The top bar containing various drop targets: Delete/App Info/Uninstall.
*/
public class DropTargetBar extends LinearLayout implements DragController.DragListener {
protected static final int DEFAULT_DRAG_FADE_DURATION = 175;
- protected static final TimeInterpolator DEFAULT_INTERPOLATOR = new AccelerateInterpolator();
+ protected static final TimeInterpolator DEFAULT_INTERPOLATOR = Interpolators.ACCEL;
private final Runnable mFadeAnimationEndRunnable = new Runnable() {
@Override
public void run() {
- AccessibilityManager am = (AccessibilityManager)
- getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
- boolean accessibilityEnabled = am.isEnabled();
- AlphaUpdateListener.updateVisibility(DropTargetBar.this, accessibilityEnabled);
+ updateVisibility(DropTargetBar.this, isAccessibilityEnabled(getContext()));
}
};
@@ -55,6 +56,7 @@
@ViewDebug.ExportedProperty(category = "launcher")
protected boolean mVisible = false;
+ private ButtonDropTarget[] mDropTargets;
private ViewPropertyAnimator mCurrentAnimation;
public DropTargetBar(Context context, AttributeSet attrs) {
@@ -75,7 +77,27 @@
public void setup(DragController dragController) {
dragController.addDragListener(this);
- setupButtonDropTarget(this, dragController);
+ ArrayList<ButtonDropTarget> outList = new ArrayList<>();
+ findDropTargets(this, outList);
+
+ mDropTargets = new ButtonDropTarget[outList.size()];
+ for (int i = 0; i < mDropTargets.length; i++) {
+ mDropTargets[i] = outList.get(i);
+ mDropTargets[i].setDropTargetBar(this);
+ dragController.addDragListener(mDropTargets[i]);
+ dragController.addDropTarget(mDropTargets[i]);
+ }
+ }
+
+ private static void findDropTargets(View view, ArrayList<ButtonDropTarget> outTargets) {
+ if (view instanceof ButtonDropTarget) {
+ outTargets.add((ButtonDropTarget) view);
+ } else if (view instanceof ViewGroup) {
+ ViewGroup vg = (ViewGroup) view;
+ for (int i = vg.getChildCount() - 1; i >= 0; i--) {
+ findDropTargets(vg.getChildAt(i), outTargets);
+ }
+ }
}
@Override
@@ -130,20 +152,6 @@
return result;
}
- private void setupButtonDropTarget(View view, DragController dragController) {
- if (view instanceof ButtonDropTarget) {
- ButtonDropTarget bdt = (ButtonDropTarget) view;
- bdt.setDropTargetBar(this);
- dragController.addDragListener(bdt);
- dragController.addDropTarget(bdt);
- } else if (view instanceof ViewGroup) {
- ViewGroup vg = (ViewGroup) view;
- for (int i = vg.getChildCount() - 1; i >= 0; i--) {
- setupButtonDropTarget(vg.getChildAt(i), dragController);
- }
- }
- }
-
private void animateToVisibility(boolean isVisible) {
if (mVisible != isVisible) {
mVisible = isVisible;
@@ -190,4 +198,8 @@
mDeferOnDragEnd = false;
}
}
+
+ public ButtonDropTarget[] getDropTargets() {
+ return mDropTargets;
+ }
}
diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java
index f78cde5..289242f 100644
--- a/src/com/android/launcher3/InfoDropTarget.java
+++ b/src/com/android/launcher3/InfoDropTarget.java
@@ -26,6 +26,7 @@
import android.util.Log;
import android.widget.Toast;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.util.Themes;
@@ -49,8 +50,8 @@
}
@Override
- protected ComponentName performDropAction(DragObject d) {
- return performDropAction(mLauncher, d.dragInfo, null, null);
+ protected ComponentName performDropAction(ItemInfo item) {
+ return performDropAction(mLauncher, item, null, null);
}
/**
@@ -96,13 +97,15 @@
}
@Override
- protected boolean supportsDrop(ItemInfo info) {
- return supportsDrop(getContext(), info);
+ public int getAccessibilityAction() {
+ return LauncherAccessibilityDelegate.INFO;
}
- public static boolean supportsDrop(Context context, ItemInfo info) {
+ @Override
+ protected boolean supportsDrop(ItemInfo info) {
// Only show the App Info drop target if developer settings are enabled.
- boolean developmentSettingsEnabled = Settings.Global.getInt(context.getContentResolver(),
+ boolean developmentSettingsEnabled = Settings.Global.getInt(
+ getContext().getContentResolver(),
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1;
if (!developmentSettingsEnabled) {
return false;
diff --git a/src/com/android/launcher3/InsettableFrameLayout.java b/src/com/android/launcher3/InsettableFrameLayout.java
index be76490..60f5ca2 100644
--- a/src/com/android/launcher3/InsettableFrameLayout.java
+++ b/src/com/android/launcher3/InsettableFrameLayout.java
@@ -9,8 +9,7 @@
import android.view.ViewGroup;
import android.widget.FrameLayout;
-public class InsettableFrameLayout extends FrameLayout implements
- ViewGroup.OnHierarchyChangeListener, Insettable {
+public class InsettableFrameLayout extends FrameLayout implements Insettable {
@ViewDebug.ExportedProperty(category = "launcher")
protected Rect mInsets = new Rect();
@@ -21,7 +20,6 @@
public InsettableFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
- setOnHierarchyChangeListener(this);
}
public void setFrameLayoutChildInsets(View child, Rect newInsets, Rect oldInsets) {
@@ -95,12 +93,8 @@
}
@Override
- public void onChildViewAdded(View parent, View child) {
+ public void onViewAdded(View child) {
+ super.onViewAdded(child);
setFrameLayoutChildInsets(child, mInsets, new Rect());
}
-
- @Override
- public void onChildViewRemoved(View parent, View child) {
- }
-
}
diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java
index 1e020e2..fea4dda 100644
--- a/src/com/android/launcher3/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/ItemInfoWithIcon.java
@@ -33,11 +33,75 @@
*/
public boolean usingLowResIcon;
+ /**
+ * Indicates that the icon is disabled due to safe mode restrictions.
+ */
+ public static final int FLAG_DISABLED_SAFEMODE = 1 << 0;
+
+ /**
+ * Indicates that the icon is disabled as the app is not available.
+ */
+ public static final int FLAG_DISABLED_NOT_AVAILABLE = 1 << 1;
+
+ /**
+ * Indicates that the icon is disabled as the app is suspended
+ */
+ public static final int FLAG_DISABLED_SUSPENDED = 1 << 2;
+
+ /**
+ * Indicates that the icon is disabled as the user is in quiet mode.
+ */
+ public static final int FLAG_DISABLED_QUIET_USER = 1 << 3;
+
+ /**
+ * Indicates that the icon is disabled as the publisher has disabled the actual shortcut.
+ */
+ public static final int FLAG_DISABLED_BY_PUBLISHER = 1 << 4;
+
+ /**
+ * Indicates that the icon is disabled as the user partition is currently locked.
+ */
+ public static final int FLAG_DISABLED_LOCKED_USER = 1 << 5;
+
+ public static final int FLAG_DISABLED_MASK = FLAG_DISABLED_SAFEMODE |
+ FLAG_DISABLED_NOT_AVAILABLE | FLAG_DISABLED_SUSPENDED |
+ FLAG_DISABLED_QUIET_USER | FLAG_DISABLED_BY_PUBLISHER | FLAG_DISABLED_LOCKED_USER;
+
+ /**
+ * The item points to a system app.
+ */
+ public static final int FLAG_SYSTEM_YES = 1 << 6;
+
+ /**
+ * The item points to a non system app.
+ */
+ public static final int FLAG_SYSTEM_NO = 1 << 7;
+
+ public static final int FLAG_SYSTEM_MASK = FLAG_SYSTEM_YES | FLAG_SYSTEM_NO;
+
+ /**
+ * Flag indicating that the icon is an {@link android.graphics.drawable.AdaptiveIconDrawable}
+ * that can be optimized in various way.
+ */
+ public static final int FLAG_ADAPTIVE_ICON = 1 << 8;
+
+ /**
+ * Status associated with the system state of the underlying item. This is calculated every
+ * time a new info is created and not persisted on the disk.
+ */
+ public int runtimeStatusFlags = 0;
+
protected ItemInfoWithIcon() { }
protected ItemInfoWithIcon(ItemInfoWithIcon info) {
super(info);
iconBitmap = info.iconBitmap;
usingLowResIcon = info.usingLowResIcon;
+ runtimeStatusFlags = info.runtimeStatusFlags;
+ }
+
+ @Override
+ public boolean isDisabled() {
+ return (runtimeStatusFlags & FLAG_DISABLED_MASK) != 0;
}
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 24e7083..b7986da 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -16,8 +16,18 @@
package com.android.launcher3;
-import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_NEXT_FRAME;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_BY_PUBLISHER;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_QUIET_USER;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import static com.android.launcher3.util.RunnableWithId.RUNNABLE_ID_BIND_APPS;
import static com.android.launcher3.util.RunnableWithId.RUNNABLE_ID_BIND_WIDGETS;
@@ -47,16 +57,15 @@
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
-import android.os.Handler;
import android.os.Parcelable;
import android.os.Process;
import android.os.StrictMode;
@@ -79,9 +88,7 @@
import android.view.View;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
-import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
import android.view.animation.OvershootInterpolator;
import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
@@ -101,7 +108,6 @@
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
-import com.android.launcher3.dragndrop.PinItemDragListener;
import com.android.launcher3.dynamicui.WallpaperColorInfo;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
@@ -112,10 +118,12 @@
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.pageindicators.PageIndicator;
-import com.android.launcher3.popup.BaseActionPopup;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.states.AllAppsState;
+import com.android.launcher3.states.InternalStateHandler;
+import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -130,7 +138,6 @@
import com.android.launcher3.util.PendingRequestArgs;
import com.android.launcher3.util.RunnableWithId;
import com.android.launcher3.util.SystemUiController;
-import com.android.launcher3.util.TestingUtils;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.TraceHelper;
@@ -159,7 +166,6 @@
public class Launcher extends BaseActivity
implements LauncherExterns, View.OnClickListener, OnLongClickListener,
LauncherModel.Callbacks, View.OnTouchListener, LauncherProviderChangeListener,
- AccessibilityManager.AccessibilityStateChangeListener,
WallpaperColorInfo.OnThemeChangeListener {
public static final String TAG = "Launcher";
static final boolean LOGD = false;
@@ -186,11 +192,6 @@
*/
protected static final int REQUEST_LAST = 100;
- private static final int SOFT_INPUT_MODE_DEFAULT =
- WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
- private static final int SOFT_INPUT_MODE_ALL_APPS =
- WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
-
// The Intent extra that defines whether to ignore the launch animation
static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
"com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
@@ -206,13 +207,7 @@
// Type: SparseArray<Parcelable>
private static final String RUNTIME_STATE_WIDGET_PANEL = "launcher.widget_panel";
- static final String APPS_VIEW_SHOWN = "launcher.apps_view_shown";
-
- /** The different states that Launcher can be in. */
- enum State { WORKSPACE, WORKSPACE_SPRING_LOADED, APPS}
-
- @Thunk State mState = State.WORKSPACE;
- @Thunk LauncherStateTransitionAnimation mStateTransitionAnimation;
+ private LauncherStateManager mStateManager;
private boolean mIsSafeModeEnabled;
@@ -228,15 +223,12 @@
@Thunk DragLayer mDragLayer;
private DragController mDragController;
- public View mWeightWatcher;
-
private AppWidgetManagerCompat mAppWidgetManager;
private LauncherAppWidgetHost mAppWidgetHost;
private final int[] mTmpAddItemCellCoordinates = new int[2];
@Thunk Hotseat mHotseat;
- private ViewGroup mOverviewPanel;
private View mAllAppsButton;
@@ -246,6 +238,9 @@
@Thunk AllAppsContainerView mAppsView;
AllAppsTransitionController mAllAppsController;
+ // UI and state for the overview panel
+ private ViewGroup mOverviewPanel;
+
// We need to store the orientation Launcher was created with, due to a bug (b/64916689)
// that results in widgets being inflated in the wrong orientation.
private int mOrientation;
@@ -266,7 +261,6 @@
private ModelWriter mModelWriter;
private IconCache mIconCache;
private LauncherAccessibilityDelegate mAccessibilityDelegate;
- private final Handler mHandler = new Handler();
private boolean mHasFocus = false;
private ObjectAnimator mScrimAnimator;
@@ -274,21 +268,12 @@
private PopupDataProvider mPopupDataProvider;
- // Determines how long to wait after a rotation before restoring the screen orientation to
- // match the sensor state.
- private static final int RESTORE_SCREEN_ORIENTATION_DELAY = 500;
-
private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<>();
// We only want to get the SharedPreferences once since it does an FS stat each time we get
// it from the context.
private SharedPreferences mSharedPrefs;
- // Exiting spring loaded mode happens with a delay. This runnable object triggers the
- // state transition. If another state transition happened during this delay,
- // simply unregister this runnable.
- private Runnable mExitSpringLoadedModeRunnable;
-
// Activity result which needs to be processed after workspace has loaded.
private ActivityResultInfo mPendingActivityResult;
/**
@@ -301,15 +286,7 @@
public ViewGroupFocusHelper mFocusHandler;
private boolean mRotationEnabled = false;
-
- @Thunk void setOrientation() {
- if (mRotationEnabled) {
- unlockScreenOrientation(true);
- } else {
- setRequestedOrientation(
- ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
- }
- }
+ private boolean mAppLaunchSuccess;
private RotationPrefChangeHandler mRotationPrefChangeHandler;
@@ -363,7 +340,7 @@
mDragController = new DragController(this);
mAllAppsController = new AllAppsTransitionController(this);
- mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, mAllAppsController);
+ mStateManager = new LauncherStateManager(this);
mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
@@ -382,9 +359,26 @@
mPopupDataProvider = new PopupDataProvider(this);
- ((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
- .addAccessibilityStateChangeListener(this);
+ mRotationEnabled = getResources().getBoolean(R.bool.allow_rotation);
+ // In case we are on a device with locked rotation, we should look at preferences to check
+ // if the user has specifically allowed rotation.
+ if (!mRotationEnabled) {
+ mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext());
+ mRotationPrefChangeHandler = new RotationPrefChangeHandler();
+ mSharedPrefs.registerOnSharedPreferenceChangeListener(mRotationPrefChangeHandler);
+ }
+ boolean internalStateHandled = InternalStateHandler.handleCreate(this, getIntent());
+ if (internalStateHandled) {
+ // Temporarily enable the rotation
+ mRotationEnabled = true;
+
+ if (savedInstanceState != null) {
+ // InternalStateHandler has already set the appropriate state.
+ // We dont need to do anything.
+ savedInstanceState.remove(RUNTIME_STATE);
+ }
+ }
restoreState(savedInstanceState);
// We only load the page synchronously if the user rotates (or triggers a
@@ -393,10 +387,13 @@
if (savedInstanceState != null) {
currentScreen = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN, currentScreen);
}
+
if (!mModel.startLoader(currentScreen)) {
- // If we are not binding synchronously, show a fade in animation when
- // the first page bind completes.
- mDragLayer.setAlpha(0);
+ if (!internalStateHandled) {
+ // If we are not binding synchronously, show a fade in animation when
+ // the first page bind completes.
+ mDragLayer.setAlpha(0);
+ }
} else {
// Pages bound synchronously.
mWorkspace.setCurrentPage(currentScreen);
@@ -408,23 +405,10 @@
mDefaultKeySsb = new SpannableStringBuilder();
Selection.setSelection(mDefaultKeySsb, 0);
- mRotationEnabled = getResources().getBoolean(R.bool.allow_rotation);
- // In case we are on a device with locked rotation, we should look at preferences to check
- // if the user has specifically allowed rotation.
- if (!mRotationEnabled) {
- mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext());
- mRotationPrefChangeHandler = new RotationPrefChangeHandler();
- mSharedPrefs.registerOnSharedPreferenceChangeListener(mRotationPrefChangeHandler);
- }
-
- if (PinItemDragListener.handleDragRequest(this, getIntent())) {
- // Temporarily enable the rotation
- mRotationEnabled = true;
- }
-
// On large interfaces, or on devices that a user has specifically enabled screen rotation,
// we want the screen to auto-rotate based on the current orientation
- setOrientation();
+ setRequestedOrientation(mRotationEnabled
+ ? SCREEN_ORIENTATION_UNSPECIFIED : SCREEN_ORIENTATION_NOSENSOR);
setContentView(mLauncherView);
@@ -450,6 +434,10 @@
recreate();
}
+ public LauncherStateManager getStateManager() {
+ return mStateManager;
+ }
+
protected void overrideTheme(boolean isDark, boolean supportsDarkText) {
if (isDark) {
setTheme(R.style.LauncherThemeDark);
@@ -581,7 +569,7 @@
Runnable exitSpringLoaded = new Runnable() {
@Override
public void run() {
- exitSpringLoadedDragMode(SPRING_LOADED_EXIT_DELAY);
+ mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
}
};
@@ -601,11 +589,11 @@
}
return;
} else if (requestCode == REQUEST_PICK_WALLPAPER) {
- if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) {
+ if (resultCode == RESULT_OK && isInState(OVERVIEW)) {
// User could have free-scrolled between pages before picking a wallpaper; make sure
// we move to the closest one now.
mWorkspace.setCurrentPage(mWorkspace.getPageNearestToCenterOfScreen());
- showWorkspace(false);
+ mStateManager.goToState(NORMAL, false);
}
return;
}
@@ -633,7 +621,7 @@
final Runnable onComplete = new Runnable() {
@Override
public void run() {
- exitSpringLoadedDragMode(SPRING_LOADED_EXIT_NEXT_FRAME);
+ getStateManager().goToState(NORMAL);
}
};
@@ -761,7 +749,7 @@
@Override
public void run() {
completeAddAppWidget(appWidgetId, requestArgs, layout, null);
- exitSpringLoadedDragMode(SPRING_LOADED_EXIT_DELAY);
+ mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
}
};
} else if (resultCode == RESULT_CANCELED) {
@@ -786,11 +774,12 @@
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onStop();
}
+ mAppWidgetHost.setListenIfResumed(false);
- if (Utilities.ATLEAST_NOUGAT_MR1) {
- mAppWidgetHost.stopListening();
+ if (!mAppLaunchSuccess) {
+ getUserEventDispatcher().logActionCommand(Action.Command.STOP,
+ mStateManager.getState().containerType);
}
-
NotificationListener.removeNotificationsChangedListener();
}
@@ -802,10 +791,7 @@
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onStart();
}
-
- if (Utilities.ATLEAST_NOUGAT_MR1) {
- mAppWidgetHost.startListening();
- }
+ mAppWidgetHost.setListenIfResumed(true);
if (!isWorkspaceLoading()) {
NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
@@ -837,6 +823,7 @@
super.onResume();
TraceHelper.partitionSection("ON_RESUME", "superCall");
+ mAppLaunchSuccess = false;
getUserEventDispatcher().resetElapsedSessionMillis();
mPaused = false;
if (mOnResumeNeedsLoad) {
@@ -926,7 +913,7 @@
}
}
- protected boolean hasSettings() {
+ public boolean hasSettings() {
if (mLauncherCallbacks != null) {
return mLauncherCallbacks.hasSettings();
} else {
@@ -992,26 +979,6 @@
return handled;
}
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_MENU) {
- // Ignore the menu key if we are currently dragging or are on the custom content screen
- if (!mDragController.isDragging()) {
- // Close any open floating view
- AbstractFloatingView.closeAllOpenViews(this);
-
- // Show the overview mode if we are on the workspace
- if (mState == State.WORKSPACE && !mWorkspace.isInOverviewMode() &&
- !mWorkspace.isSwitchingState()) {
- mOverviewPanel.requestFocus();
- showOverviewMode(true, true /* requestButtonFocus */);
- }
- }
- return true;
- }
- return super.onKeyUp(keyCode, event);
- }
-
private String getTypedText() {
return mDefaultKeySsb.toString();
}
@@ -1023,6 +990,10 @@
Selection.setSelection(mDefaultKeySsb, 0);
}
+ public boolean isInState(LauncherState state) {
+ return mStateManager.getState() == state;
+ }
+
/**
* Restores the previous state, if it exists.
*
@@ -1033,12 +1004,11 @@
return;
}
- int stateOrdinal = savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal());
- State[] stateValues = State.values();
- State state = (stateOrdinal >= 0 && stateOrdinal < stateValues.length)
- ? stateValues[stateOrdinal] : State.WORKSPACE;
- if (state == State.APPS) {
- showAppsView(false /* animated */);
+ int stateOrdinal = savedState.getInt(RUNTIME_STATE, NORMAL.ordinal);
+ LauncherState[] stateValues = LauncherState.values();
+ LauncherState state = stateValues[stateOrdinal];
+ if (!state.doNotRestore) {
+ mStateManager.goToState(state, false /* animated */);
}
PendingRequestArgs requestArgs = savedState.getParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS);
@@ -1063,6 +1033,7 @@
mFocusHandler = mDragLayer.getFocusIndicatorHelper();
mWorkspace = mDragLayer.findViewById(R.id.workspace);
mWorkspace.initParentViews(mDragLayer);
+ mOverviewPanel = findViewById(R.id.overview_panel);
mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
@@ -1077,9 +1048,6 @@
mHotseat.setOnLongClickListener(this);
}
- // Setup the overview panel
- setupOverviewPanel();
-
// Setup the workspace
mWorkspace.setHapticFeedbackEnabled(false);
mWorkspace.setOnLongClickListener(this);
@@ -1098,51 +1066,9 @@
// Setup the drag controller (drop targets have to be added in reverse order in priority)
mDragController.setMoveTarget(mWorkspace);
- mDragController.addDropTarget(mWorkspace);
mDropTargetBar.setup(mDragController);
mAllAppsController.setupViews(mAppsView, mHotseat, mWorkspace);
-
- if (TestingUtils.MEMORY_DUMP_ENABLED) {
- TestingUtils.addWeightWatcher(this);
- }
- }
-
- private void setupOverviewPanel() {
- mOverviewPanel = findViewById(R.id.overview_panel);
-
- // Bind wallpaper button actions
- View wallpaperButton = findViewById(R.id.wallpaper_button);
- new OverviewButtonClickListener(ControlType.WALLPAPER_BUTTON) {
- @Override
- public void handleViewClick(View view) {
- onClickWallpaperPicker(view);
- }
- }.attachTo(wallpaperButton);
-
- // Bind widget button actions
- new OverviewButtonClickListener(ControlType.WIDGETS_BUTTON) {
- @Override
- public void handleViewClick(View view) {
- onClickAddWidgetButton(view);
- }
- }.attachTo(findViewById(R.id.widget_button));
-
- // Bind settings actions
- View settingsButton = findViewById(R.id.settings_button);
- boolean hasSettings = hasSettings();
- if (hasSettings) {
- new OverviewButtonClickListener(ControlType.SETTINGS_BUTTON) {
- @Override
- public void handleViewClick(View view) {
- onClickSettingsButton(view);
- }
- }.attachTo(settingsButton);
- } else {
- settingsButton.setVisibility(View.GONE);
- }
-
- mOverviewPanel.setAlpha(0f);
}
/**
@@ -1316,10 +1242,7 @@
// Reset AllApps to its initial state only if we are not in the middle of
// processing a multi-step drop
if (mAppsView != null && mPendingRequestArgs == null) {
- if (!showWorkspace(false)) {
- // If we are already on the workspace, then manually reset all apps
- mAppsView.reset();
- }
+ mStateManager.goToState(NORMAL);
}
mShouldFadeInScrim = true;
} else if (Intent.ACTION_USER_PRESENT.equals(action)) {
@@ -1337,9 +1260,9 @@
mWorkspace.updateIconBadges(updatedBadges);
mAppsView.updateIconBadges(updatedBadges);
- BaseActionPopup popup = BaseActionPopup.getOpen(Launcher.this);
- if (popup instanceof PopupContainerWithArrow) {
- ((PopupContainerWithArrow) popup).updateNotificationHeader(updatedBadges);
+ PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(Launcher.this);
+ if (popup != null) {
+ popup.updateNotificationHeader(updatedBadges);
}
}
};
@@ -1367,6 +1290,10 @@
}
}
+ public AllAppsTransitionController getAllAppsController() {
+ return mAllAppsController;
+ }
+
public DragLayer getDragLayer() {
return mDragLayer;
}
@@ -1383,8 +1310,8 @@
return mHotseat;
}
- public ViewGroup getOverviewPanel() {
- return mOverviewPanel;
+ public <T extends ViewGroup> T getOverviewPanel() {
+ return (T) mOverviewPanel;
}
public DropTargetBar getDropTargetBar() {
@@ -1419,68 +1346,50 @@
!= Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
// Check this condition before handling isActionMain, as this will get reset.
- boolean shouldMoveToDefaultScreen = alreadyOnHome &&
- mState == State.WORKSPACE && AbstractFloatingView.getTopOpenView(this) == null;
-
+ boolean shouldMoveToDefaultScreen = alreadyOnHome && isInState(NORMAL)
+ && AbstractFloatingView.getTopOpenView(this) == null;
boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction());
+ boolean internalStateHandled = InternalStateHandler
+ .handleNewIntent(this, intent, alreadyOnHome);
+
if (isActionMain) {
- if (mWorkspace == null) {
- // Can be cases where mWorkspace is null, this prevents a NPE
- return;
- }
+ if (!internalStateHandled) {
+ // Note: There should be at most one log per method call. This is enforced
+ // implicitly by using if-else statements.
+ UserEventDispatcher ued = getUserEventDispatcher();
+ AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(this);
+ if (topOpenView != null) {
+ topOpenView.logActionCommand(Action.Command.HOME_INTENT);
+ } else if (alreadyOnHome) {
+ Target target = newContainerTarget(mStateManager.getState().containerType);
+ target.pageIndex = mWorkspace.getCurrentPage();
+ ued.logActionCommand(Action.Command.HOME_INTENT, target);
+ }
- // Note: There should be at most one log per method call. This is enforced implicitly
- // by using if-else statements.
- UserEventDispatcher ued = getUserEventDispatcher();
- AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(this);
- if (topOpenView != null) {
- topOpenView.logActionCommand(Action.Command.HOME_INTENT);
- } else if (alreadyOnHome) {
- Target target = newContainerTarget(mWorkspace.getState().containerType);
- target.pageIndex = mWorkspace.getCurrentPage();
- ued.logActionCommand(Action.Command.HOME_INTENT, target);
- }
+ // In all these cases, only animate if we're already on home
+ AbstractFloatingView.closeAllOpenViews(this, alreadyOnHome);
- // In all these cases, only animate if we're already on home
- AbstractFloatingView.closeAllOpenViews(this, alreadyOnHome);
- showWorkspace(alreadyOnHome /* animated */);
+ mStateManager.goToState(NORMAL, alreadyOnHome /* animated */);
+
+ // Reset the apps view
+ if (!alreadyOnHome && mAppsView != null) {
+ mAppsView.reset();
+ }
+
+ if (shouldMoveToDefaultScreen && !mWorkspace.isTouchActive()) {
+ mWorkspace.post(mWorkspace::moveToDefaultScreen);
+ }
+ }
final View v = getWindow().peekDecorView();
if (v != null && v.getWindowToken() != null) {
UiThreadHelper.hideKeyboardAsync(this, v.getWindowToken());
}
- // Reset the apps view
- if (!alreadyOnHome && mAppsView != null) {
- mAppsView.reset();
- }
-
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onHomeIntent();
}
}
- PinItemDragListener.handleDragRequest(this, intent);
-
- if (mLauncherCallbacks != null) {
- mLauncherCallbacks.onNewIntent(intent);
- }
-
- // Defer moving to the default screen until after we callback to the LauncherCallbacks
- // as slow logic in the callbacks eat into the time the scroller expects for the snapToPage
- // animation.
- if (isActionMain) {
- if (shouldMoveToDefaultScreen && !mWorkspace.isTouchActive()) {
-
- mWorkspace.post(new Runnable() {
- @Override
- public void run() {
- if (mWorkspace != null) {
- mWorkspace.moveToDefaultScreen();
- }
- }
- });
- }
- }
TraceHelper.endSection("NEW_INTENT");
}
@@ -1499,7 +1408,7 @@
outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getNextPage());
}
- outState.putInt(RUNTIME_STATE, mState.ordinal());
+ outState.putInt(RUNTIME_STATE, mStateManager.getState().ordinal);
AbstractFloatingView widgets = AbstractFloatingView
@@ -1557,10 +1466,6 @@
mAppWidgetHost = null;
TextKeyListener.getInstance().release();
-
- ((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
- .removeAccessibilityStateChangeListener(this);
-
WallpaperColorInfo.getInstance(this).setOnThemeChangeListener(null);
LauncherAnimUtils.onDestroyActivity();
@@ -1620,7 +1525,7 @@
}
// We need to show the workspace after starting the search
- showWorkspace(true);
+ mStateManager.goToState(NORMAL);
}
/**
@@ -1712,7 +1617,7 @@
@Override
public void run() {
// Exit spring loaded mode if necessary after adding the widget
- exitSpringLoadedDragMode(SPRING_LOADED_EXIT_DELAY);
+ mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
}
};
completeAddAppWidget(appWidgetId, info, boundWidget, addFlowHandler.getProviderInfo(this));
@@ -1893,12 +1798,9 @@
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
if (topView != null) {
topView.onBackPressed();
- } else if (isAppsViewVisible()) {
- ued.logActionCommand(Action.Command.BACK, ContainerType.ALLAPPS);
- showWorkspace(true);
- } else if (mWorkspace.isInOverviewMode()) {
- ued.logActionCommand(Action.Command.BACK, ContainerType.OVERVIEW);
- showWorkspace(true);
+ } else if (!isInState(NORMAL)) {
+ ued.logActionCommand(Action.Command.BACK, mStateManager.getState().containerType);
+ mStateManager.goToState(NORMAL);
} else {
// Back button is a no-op here, but give at least some feedback for the button press
mWorkspace.showOutlinesTemporarily();
@@ -1922,23 +1824,23 @@
}
if (v instanceof Workspace) {
- if (mWorkspace.isInOverviewMode()) {
+ if (isInState(OVERVIEW)) {
getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Type.TOUCH,
LauncherLogProto.Action.Direction.NONE,
LauncherLogProto.ContainerType.OVERVIEW, mWorkspace.getCurrentPage());
- showWorkspace(true);
+ mStateManager.goToState(NORMAL);
}
return;
}
if (v instanceof CellLayout) {
- if (mWorkspace.isInOverviewMode()) {
+ if (isInState(OVERVIEW)) {
int page = mWorkspace.indexOfChild(v);
getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Type.TOUCH,
LauncherLogProto.Action.Direction.NONE,
LauncherLogProto.ContainerType.OVERVIEW, page);
mWorkspace.snapToPageFromOverView(page);
- showWorkspace(true);
+ mStateManager.goToState(NORMAL);
}
return;
}
@@ -2010,12 +1912,12 @@
*/
protected void onClickAllAppsButton(View v) {
if (LOGD) Log.d(TAG, "onClickAllAppsButton");
- if (!isAppsViewVisible()) {
+ if (!isInState(ALL_APPS)) {
getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
ControlType.ALL_APPS_BUTTON);
- showAppsView(true /* animated */);
+ mStateManager.goToState(ALL_APPS);
} else {
- showWorkspace(true);
+ mStateManager.goToState(NORMAL);
}
}
@@ -2066,10 +1968,10 @@
// Open shortcut
final ShortcutInfo shortcut = (ShortcutInfo) tag;
- if (shortcut.isDisabled != 0) {
- if ((shortcut.isDisabled &
- ~ShortcutInfo.FLAG_DISABLED_SUSPENDED &
- ~ShortcutInfo.FLAG_DISABLED_QUIET_USER) == 0) {
+ if (shortcut.isDisabled()) {
+ if ((shortcut.runtimeStatusFlags &
+ ~FLAG_DISABLED_SUSPENDED &
+ ~FLAG_DISABLED_QUIET_USER) == 0) {
// If the app is only disabled because of the above flags, launch activity anyway.
// Framework will tell the user why the app is suspended.
} else {
@@ -2080,10 +1982,10 @@
}
// Otherwise just use a generic error message.
int error = R.string.activity_not_available;
- if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) {
+ if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_SAFEMODE) != 0) {
error = R.string.safemode_shortcut_error;
- } else if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_BY_PUBLISHER) != 0 ||
- (shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_LOCKED_USER) != 0) {
+ } else if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_BY_PUBLISHER) != 0 ||
+ (shortcut.runtimeStatusFlags & FLAG_DISABLED_LOCKED_USER) != 0) {
error = R.string.shortcut_not_available;
}
Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
@@ -2119,7 +2021,6 @@
throw new IllegalArgumentException("Input must have a valid intent");
}
startActivitySafely(v, intent, item);
- getUserEventDispatcher().logAppLaunch(v, intent); // TODO for discovered apps b/35802115
}
/**
@@ -2141,19 +2042,6 @@
}
/**
- * Event handler for the (Add) Widgets button that appears after a long press
- * on the home screen.
- */
- public void onClickAddWidgetButton(View view) {
- if (LOGD) Log.d(TAG, "onClickAddWidgetButton");
- if (mIsSafeModeEnabled) {
- Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
- } else {
- WidgetsFullSheet.show(this, true /* animated */);
- }
- }
-
- /**
* Event handler for the wallpaper picker button that appears after a long press
* on the home screen.
*/
@@ -2175,35 +2063,22 @@
intent.setPackage(pickerPackage);
}
- intent.setSourceBounds(getViewBounds(v));
+ final Bundle launchOptions;
+ if (v != null) {
+ intent.setSourceBounds(getViewBounds(v));
+ // If there is no target package, use the default intent chooser animation
+ launchOptions = hasTargetPackage ? getActivityLaunchOptions(v) : null;
+ } else {
+ launchOptions = null;
+ }
try {
- startActivityForResult(intent, REQUEST_PICK_WALLPAPER,
- // If there is no target package, use the default intent chooser animation
- hasTargetPackage ? getActivityLaunchOptions(v) : null);
+ startActivityForResult(intent, REQUEST_PICK_WALLPAPER, launchOptions);
} catch (ActivityNotFoundException e) {
setWaitingForResult(null);
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
}
}
- /**
- * Event handler for a click on the settings button that appears after a long press
- * on the home screen.
- */
- public void onClickSettingsButton(View v) {
- if (LOGD) Log.d(TAG, "onClickSettingsButton");
- Intent intent = new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
- .setPackage(getPackageName());
- intent.setSourceBounds(getViewBounds(v));
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(intent, getActivityLaunchOptions(v));
- }
-
- @Override
- public void onAccessibilityStateChanged(boolean enabled) {
- mDragLayer.onAccessibilityStateChanged(enabled);
- }
-
private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info) {
try {
StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy();
@@ -2280,9 +2155,10 @@
}
public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
+ mAppLaunchSuccess = false;
if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
- return false;
+ return mAppLaunchSuccess;
}
// Only launch using the new animation if the shortcut has not opted out (this is a
// private contract between launcher and may be ignored in the future).
@@ -2322,12 +2198,13 @@
btv.setStayPressed(true);
setOnResumeCallback(btv);
}
- return true;
+ mAppLaunchSuccess = true;
+ getUserEventDispatcher().logAppLaunch(v, intent); // TODO for discovered apps b/35802115
} catch (ActivityNotFoundException|SecurityException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e);
}
- return false;
+ return mAppLaunchSuccess;
}
@Override
@@ -2340,18 +2217,18 @@
public boolean onLongClick(View v) {
if (!isDraggingEnabled()) return false;
if (isWorkspaceLocked()) return false;
- if (mState != State.WORKSPACE) return false;
+ if (!isInState(NORMAL) && !isInState(OVERVIEW)) return false;
boolean ignoreLongPressToOverview =
mDeviceProfile.shouldIgnoreLongPressToOverview(mLastDispatchTouchEventX);
if (v instanceof Workspace) {
- if (!mWorkspace.isInOverviewMode()) {
+ if (!isInState(OVERVIEW)) {
if (!mWorkspace.isTouchActive() && !ignoreLongPressToOverview) {
getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
Action.Direction.NONE, ContainerType.WORKSPACE,
mWorkspace.getCurrentPage());
- showOverviewMode(true);
+ UiFactory.onWorkspaceLongPress(this);
mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
return true;
@@ -2377,7 +2254,7 @@
if (!mDragController.isDragging()) {
if (itemUnderLongClick == null) {
// User long pressed on empty space
- if (mWorkspace.isInOverviewMode()) {
+ if (mWorkspace.isPageRearrangeEnabled()) {
mWorkspace.startReordering(v);
getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
Action.Direction.NONE, ContainerType.OVERVIEW);
@@ -2388,7 +2265,7 @@
getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
Action.Direction.NONE, ContainerType.WORKSPACE,
mWorkspace.getCurrentPage());
- showOverviewMode(true);
+ UiFactory.onWorkspaceLongPress(this);
}
mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
@@ -2427,17 +2304,6 @@
}
}
- /**
- * For overridden classes.
- */
- public boolean isAllAppsVisible() {
- return isAppsViewVisible();
- }
-
- public boolean isAppsViewVisible() {
- return mState == State.APPS;
- }
-
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
@@ -2454,184 +2320,16 @@
}
}
- public boolean showWorkspace(boolean animated) {
- return showWorkspace(animated, null);
- }
-
- public boolean showWorkspace(boolean animated, Runnable onCompleteRunnable) {
- boolean changed = mState != State.WORKSPACE ||
- mWorkspace.getState() != LauncherState.NORMAL;
- if (changed || mAllAppsController.isTransitioning()) {
- mWorkspace.setVisibility(View.VISIBLE);
- mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
- LauncherState.NORMAL, animated, onCompleteRunnable);
-
- // Set focus to the AppsCustomize button
- if (mAllAppsButton != null) {
- mAllAppsButton.requestFocus();
- }
- }
-
- // Change the state *after* we've called all the transition code
- setState(State.WORKSPACE);
-
- if (changed) {
- // Send an accessibility event to announce the context change
- getWindow().getDecorView()
- .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- }
- return changed;
- }
-
- /**
- * Shows the overview button.
- */
- public void showOverviewMode(boolean animated) {
- showOverviewMode(animated, false);
- }
-
- /**
- * Shows the overview button, and if {@param requestButtonFocus} is set, will force the focus
- * onto one of the overview panel buttons.
- */
- void showOverviewMode(boolean animated, boolean requestButtonFocus) {
- Runnable postAnimRunnable = null;
- if (requestButtonFocus) {
- postAnimRunnable = new Runnable() {
- @Override
- public void run() {
- // Hitting the menu button when in touch mode does not trigger touch mode to
- // be disabled, so if requested, force focus on one of the overview panel
- // buttons.
- mOverviewPanel.requestFocusFromTouch();
- }
- };
- }
- mWorkspace.setVisibility(View.VISIBLE);
- mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
- LauncherState.OVERVIEW, animated, postAnimRunnable);
- setState(State.WORKSPACE);
-
- // If animated from long press, then don't allow any of the controller in the drag
- // layer to intercept any remaining touch.
- mWorkspace.requestDisallowInterceptTouchEvent(animated);
- }
-
- private void setState(State state) {
- this.mState = state;
- updateSoftInputMode();
- }
-
- private void updateSoftInputMode() {
- if (FeatureFlags.LAUNCHER3_UPDATE_SOFT_INPUT_MODE) {
- final int mode;
- if (isAppsViewVisible()) {
- mode = SOFT_INPUT_MODE_ALL_APPS;
- } else {
- mode = SOFT_INPUT_MODE_DEFAULT;
- }
- getWindow().setSoftInputMode(mode);
- }
- }
-
- /**
- * Shows the apps view.
- *
- * @return whether the current from and to state allowed this operation
- */
- // TODO: calling method should use the return value so that when {@code false} is returned
- // the workspace transition doesn't fall into invalid state.
- public boolean showAppsView(boolean animated) {
- markAppsViewShown();
-
- if (!(mState == State.WORKSPACE ||
- (mState == State.APPS && mAllAppsController.isTransitioning()))) {
- return false;
- }
-
- // This is a safe and supported transition to bypass spring_loaded mode.
- if (mExitSpringLoadedModeRunnable != null) {
- mHandler.removeCallbacks(mExitSpringLoadedModeRunnable);
- mExitSpringLoadedModeRunnable = null;
- }
-
- mStateTransitionAnimation.startAnimationToAllApps(animated);
-
- // Change the state *after* we've called all the transition code
- setState(State.APPS);
- AbstractFloatingView.closeAllOpenViews(this);
-
- // Send an accessibility event to announce the context change
- getWindow().getDecorView()
- .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- return true;
- }
-
- public void enterSpringLoadedDragMode() {
- if (LOGD) Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s", mState.name()));
- if (isStateSpringLoaded()) {
- return;
- }
-
- // Lock the orientation:
- if (mRotationEnabled) {
- setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
- }
-
- // Prevent any Un/InstallShortcutReceivers from updating the db while we are
- // in spring loaded mode
- InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_DRAG_AND_DROP);
-
- mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
- LauncherState.SPRING_LOADED, true /* animated */,
- null /* onCompleteRunnable */);
- setState(State.WORKSPACE_SPRING_LOADED);
- }
-
- public void exitSpringLoadedDragMode(int delay) {
- exitSpringLoadedDragMode(delay, null);
- }
-
- public void exitSpringLoadedDragMode(int delay, final Runnable onCompleteRunnable) {
- if (!isStateSpringLoaded()) return;
-
- // Unlock rotation lock
- unlockScreenOrientation(false);
-
- // Re-enable any Un/InstallShortcutReceiver and now process any queued items
- InstallShortcutReceiver.disableAndFlushInstallQueue(
- InstallShortcutReceiver.FLAG_DRAG_AND_DROP, this);
-
- if (mExitSpringLoadedModeRunnable != null) {
- mHandler.removeCallbacks(mExitSpringLoadedModeRunnable);
- }
- mExitSpringLoadedModeRunnable = new Runnable() {
- @Override
- public void run() {
- showWorkspace(true, onCompleteRunnable);
- mExitSpringLoadedModeRunnable = null;
- }
- };
- mHandler.postDelayed(mExitSpringLoadedModeRunnable, delay);
- }
-
- boolean isStateSpringLoaded() {
- return mState == State.WORKSPACE_SPRING_LOADED;
- }
-
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
final boolean result = super.dispatchPopulateAccessibilityEvent(event);
final List<CharSequence> text = event.getText();
text.clear();
// Populate event with a fake title based on the current state.
- if (mState == State.APPS) {
- text.add(getString(R.string.all_apps_button_label));
- } else if (mWorkspace != null) {
- text.add(mWorkspace.getCurrentPageDescription());
- } else {
- text.add(getString(R.string.all_apps_home_button_label));
- }
+ // TODO: When can workspace be null?
+ text.add(mWorkspace == null
+ ? getString(R.string.all_apps_home_button_label)
+ : mStateManager.getState().getDescription(this));
return result;
}
@@ -3182,7 +2880,7 @@
if (mAppsView != null) {
Executor pendingExecutor = getPendingExecutor();
- if (pendingExecutor != null && mState != State.APPS) {
+ if (pendingExecutor != null && !isInState(ALL_APPS)) {
// Wait until the fade in animation has finished before setting all apps list.
pendingExecutor.execute(r);
return;
@@ -3367,30 +3065,14 @@
mModel.refreshAndBindWidgetsAndShortcuts(packageUser);
}
- public void unlockScreenOrientation(boolean immediate) {
- if (mRotationEnabled) {
- if (immediate) {
- setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
- } else {
- mHandler.postDelayed(new Runnable() {
- public void run() {
- setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
- }
- }, RESTORE_SCREEN_ORIENTATION_DELAY);
- }
- }
- }
-
- private void markAppsViewShown() {
- if (mSharedPrefs.getBoolean(APPS_VIEW_SHOWN, false)) {
- return;
- }
- mSharedPrefs.edit().putBoolean(APPS_VIEW_SHOWN, true).apply();
+ public boolean isRotationEnabled () {
+ return mRotationEnabled;
}
private boolean shouldShowDiscoveryBounce() {
- UserManagerCompat um = UserManagerCompat.getInstance(this);
- return mState == State.WORKSPACE && !mSharedPrefs.getBoolean(APPS_VIEW_SHOWN, false) && !um.isDemoUser();
+ return isInState(NORMAL)
+ && !mSharedPrefs.getBoolean(AllAppsState.APPS_VIEW_SHOWN, false)
+ && !UserManagerCompat.getInstance(this).isDemoUser();
}
/**
@@ -3448,7 +3130,7 @@
List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
ArrayList<KeyboardShortcutInfo> shortcutInfos = new ArrayList<>();
- if (mState == State.WORKSPACE) {
+ if (isInState(NORMAL)) {
shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.all_apps_button_label),
KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON));
}
@@ -3474,8 +3156,8 @@
if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
switch (keyCode) {
case KeyEvent.KEYCODE_A:
- if (mState == State.WORKSPACE) {
- showAppsView(true);
+ if (isInState(NORMAL)) {
+ getStateManager().goToState(ALL_APPS);
return true;
}
break;
@@ -3486,7 +3168,7 @@
&& mAccessibilityDelegate.performAction(focusedView,
(ItemInfo) focusedView.getTag(),
LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) {
- BaseActionPopup.getOpen(this).requestFocus();
+ PopupContainerWithArrow.getOpen(this).requestFocus();
return true;
}
break;
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 3585110..9869fdf 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -38,7 +38,6 @@
public static final int OVERVIEW_TRANSITION_MS = 250;
public static final int SPRING_LOADED_TRANSITION_MS = 150;
public static final int SPRING_LOADED_EXIT_DELAY = 500;
- public static final int SPRING_LOADED_EXIT_NEXT_FRAME = 0;
static WeakHashMap<Animator, Object> sAnimators = new WeakHashMap<Animator, Object>();
static Animator.AnimatorListener sEndAnimListener = new Animator.AnimatorListener() {
@@ -165,4 +164,17 @@
view.setScaleY(scale);
}
};
+
+ public static final Property<View, Float> ELEVATION =
+ new Property<View, Float>(Float.class, "elevation") {
+ @Override
+ public Float get(View view) {
+ return view.getElevation();
+ }
+
+ @Override
+ public void set(View view, Float elevation) {
+ view.setElevation(elevation);
+ }
+ };
}
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index dfb30fd..0136c70 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -27,12 +27,10 @@
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.util.ConfigMonitor;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.SettingsObserver;
-import com.android.launcher3.util.TestingUtils;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
@@ -88,10 +86,6 @@
Preconditions.assertUIThread();
mContext = context;
- if (TestingUtils.MEMORY_DUMP_ENABLED) {
- TestingUtils.startTrackingMemory(mContext);
- }
-
mInvariantDeviceProfile = new InvariantDeviceProfile(mContext);
mIconCache = new IconCache(mContext, mInvariantDeviceProfile);
mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index 70440fa..9aa74b3 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -16,7 +16,8 @@
package com.android.launcher3;
-import android.app.Activity;
+import static android.app.Activity.RESULT_CANCELED;
+
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
@@ -41,12 +42,17 @@
*/
public class LauncherAppWidgetHost extends AppWidgetHost {
+ private static final int FLAG_LISTENING = 1;
+ private static final int FLAG_RESUMED = 1 << 1;
+ private static final int FLAG_LISTEN_IF_RESUMED = 1 << 2;
+
public static final int APPWIDGET_HOST_ID = 1024;
private final ArrayList<ProviderChangedListener> mProviderChangeListeners = new ArrayList<>();
private final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
private final Context mContext;
+ private int mFlags = FLAG_RESUMED;
public LauncherAppWidgetHost(Context context) {
super(context, APPWIDGET_HOST_ID);
@@ -66,7 +72,7 @@
if (FeatureFlags.GO_DISABLE_WIDGETS) {
return;
}
-
+ mFlags |= FLAG_LISTENING;
try {
super.startListening();
} catch (Exception e) {
@@ -85,10 +91,59 @@
if (FeatureFlags.GO_DISABLE_WIDGETS) {
return;
}
-
+ mFlags &= ~FLAG_LISTENING;
super.stopListening();
}
+ /**
+ * Updates the resumed state of the host.
+ * When a host is not resumed, it defers calls to startListening until host is resumed again.
+ * But if the host was already listening, it will not call stopListening.
+ *
+ * @see #setListenIfResumed(boolean)
+ */
+ public void setResumed(boolean isResumed) {
+ if (isResumed == ((mFlags & FLAG_RESUMED) != 0)) {
+ return;
+ }
+ if (isResumed) {
+ mFlags |= FLAG_RESUMED;
+ // Start listening if we were supposed to start listening on resume
+ if ((mFlags & FLAG_LISTEN_IF_RESUMED) != 0 && (mFlags & FLAG_LISTENING) == 0) {
+ startListening();
+ }
+ } else {
+ mFlags &= ~FLAG_RESUMED;
+ }
+ }
+
+ /**
+ * Updates the listening state of the host. If the host is not resumed, startListening is
+ * deferred until next resume.
+ *
+ * @see #setResumed(boolean)
+ */
+ public void setListenIfResumed(boolean listenIfResumed) {
+ if (!Utilities.ATLEAST_NOUGAT_MR1) {
+ return;
+ }
+ if (listenIfResumed == ((mFlags & FLAG_LISTEN_IF_RESUMED) != 0)) {
+ return;
+ }
+ if (listenIfResumed) {
+ mFlags |= FLAG_LISTEN_IF_RESUMED;
+ if ((mFlags & FLAG_RESUMED) != 0) {
+ // If we are resumed, start listening immediately. Note we do not check for
+ // duplicate calls before calling startListening as startListening is safe to call
+ // multiple times.
+ startListening();
+ }
+ } else {
+ mFlags &= ~FLAG_LISTEN_IF_RESUMED;
+ stopListening();
+ }
+ }
+
@Override
public int allocateAppWidgetId() {
if (FeatureFlags.GO_DISABLE_WIDGETS) {
@@ -203,12 +258,7 @@
}
private void sendActionCancelled(final BaseActivity activity, final int requestCode) {
- new Handler().post(new Runnable() {
- @Override
- public void run() {
- activity.onActivityResult(requestCode, Activity.RESULT_CANCELED, null);
- }
- });
+ new Handler().post(() -> activity.onActivityResult(requestCode, RESULT_CANCELED, null));
}
/**
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
index 78d753a..928258f 100644
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ b/src/com/android/launcher3/LauncherCallbacks.java
@@ -45,7 +45,6 @@
void onPause();
void onDestroy();
void onSaveInstanceState(Bundle outState);
- void onNewIntent(Intent intent);
void onActivityResult(int requestCode, int resultCode, Intent data);
void onRequestPermissionsResult(int requestCode, String[] permissions,
int[] grantResults);
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 25a698b..98568e4 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -44,7 +44,6 @@
import android.os.Handler;
import android.os.Message;
import android.os.Process;
-import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
@@ -86,7 +85,7 @@
*/
public static final int SCHEMA_VERSION = 27;
- public static final String AUTHORITY = (BuildConfig.APPLICATION_ID + ".settings").intern();
+ public static final String AUTHORITY = FeatureFlags.AUTHORITY;
static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 8ddc491..d6cd8a3 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -17,55 +17,118 @@
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
+import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
-import static com.android.launcher3.LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
-import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
-import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_TRANSITION_MS;
-import static com.android.launcher3.StateFlags.FLAG_DISABLE_ACCESSIBILITY;
-import static com.android.launcher3.StateFlags.FLAG_HIDE_HOTSEAT;
-import static com.android.launcher3.StateFlags.FLAG_MULTI_PAGE;
-import static com.android.launcher3.StateFlags.FLAG_SHOW_SCRIM;
+import android.view.View;
+import com.android.launcher3.states.AllAppsState;
+import com.android.launcher3.states.SpringLoadedState;
+import com.android.launcher3.uioverrides.OverviewState;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-interface StateFlags {
- int FLAG_SHOW_SCRIM = 1 << 0;
- int FLAG_MULTI_PAGE = 1 << 1;
- int FLAG_HIDE_HOTSEAT = 1 << 2;
- int FLAG_DISABLE_ACCESSIBILITY = 1 << 3;
-}
+import java.util.Arrays;
+
/**
- * Various states for launcher
+ * Base state for various states used for the Launcher
*/
-public enum LauncherState {
+public class LauncherState {
- NORMAL (ContainerType.WORKSPACE, 0, 0),
- ALL_APPS (ContainerType.ALLAPPS, ALL_APPS_TRANSITION_MS, FLAG_DISABLE_ACCESSIBILITY),
- SPRING_LOADED (ContainerType.WORKSPACE, SPRING_LOADED_TRANSITION_MS,
- FLAG_SHOW_SCRIM | FLAG_MULTI_PAGE | FLAG_DISABLE_ACCESSIBILITY),
- OVERVIEW (ContainerType.OVERVIEW, OVERVIEW_TRANSITION_MS,
- FLAG_SHOW_SCRIM | FLAG_MULTI_PAGE | FLAG_HIDE_HOTSEAT);
+ protected static final int FLAG_SHOW_SCRIM = 1 << 0;
+ protected static final int FLAG_MULTI_PAGE = 1 << 1;
+ protected static final int FLAG_DISABLE_ACCESSIBILITY = 1 << 2;
+ protected static final int FLAG_DO_NOT_RESTORE = 1 << 3;
+ private static final LauncherState[] sAllStates = new LauncherState[4];
+
+ public static final LauncherState NORMAL = new LauncherState(0, ContainerType.WORKSPACE,
+ 0, 1f, FLAG_DO_NOT_RESTORE);
+
+ public static final LauncherState ALL_APPS = new AllAppsState(1);
+
+ public static final LauncherState SPRING_LOADED = new SpringLoadedState(2);
+
+ public static final LauncherState OVERVIEW = new OverviewState(3);
+
+ public final int ordinal;
+
+ /**
+ * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher}
+ */
public final int containerType;
+ /**
+ * True if the state can be persisted across activity restarts.
+ */
+ public final boolean doNotRestore;
+
+ /**
+ * True if workspace has multiple pages visible.
+ */
public final boolean hasMultipleVisiblePages;
+
+ /**
+ * Accessibility flag for workspace and its pages.
+ * @see android.view.View#setImportantForAccessibility(int)
+ */
public final int workspaceAccessibilityFlag;
- // Properties related to state transition animation.
+ /**
+ * Properties related to state transition animation
+ *
+ * @see WorkspaceStateTransitionAnimation
+ */
public final boolean hasScrim;
- public final boolean hideHotseat;
public final int transitionDuration;
- LauncherState(int containerType, int transitionDuration, int flags) {
+ /**
+ * Fraction shift in the vertical translation UI and related properties
+ *
+ * @see com.android.launcher3.allapps.AllAppsTransitionController
+ */
+ public final float verticalProgress;
+
+ public LauncherState(int id, int containerType, int transitionDuration, float verticalProgress,
+ int flags) {
this.containerType = containerType;
this.transitionDuration = transitionDuration;
this.hasScrim = (flags & FLAG_SHOW_SCRIM) != 0;
this.hasMultipleVisiblePages = (flags & FLAG_MULTI_PAGE) != 0;
- this.hideHotseat = (flags & FLAG_HIDE_HOTSEAT) != 0;
this.workspaceAccessibilityFlag = (flags & FLAG_DISABLE_ACCESSIBILITY) != 0
? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
: IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+ this.doNotRestore = (flags & FLAG_DO_NOT_RESTORE) != 0;
+
+ this.verticalProgress = verticalProgress;
+
+ this.ordinal = id;
+ sAllStates[id] = this;
+ }
+
+ public static LauncherState[] values() {
+ return Arrays.copyOf(sAllStates, sAllStates.length);
+ }
+
+ public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
+ return new float[] {1, 0};
+ }
+
+ public void onStateEnabled(Launcher launcher) {
+ dispatchWindowStateChanged(launcher);
+ }
+
+ public void onStateDisabled(Launcher launcher) { }
+
+ public View getFinalFocus(Launcher launcher) {
+ return launcher.getWorkspace();
+ }
+
+ public String getDescription(Launcher launcher) {
+ return launcher.getWorkspace().getCurrentPageDescription();
+ }
+
+ protected static void dispatchWindowStateChanged(Launcher launcher) {
+ launcher.getWindow().getDecorView().sendAccessibilityEvent(TYPE_WINDOW_STATE_CHANGED);
}
}
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
new file mode 100644
index 0000000..1e6016b
--- /dev/null
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2015 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;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.View;
+
+import com.android.launcher3.anim.AnimationLayerSet;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.uioverrides.UiFactory;
+
+/**
+ * TODO: figure out what kind of tests we can write for this
+ *
+ * Things to test when changing the following class.
+ * - Home from workspace
+ * - from center screen
+ * - from other screens
+ * - Home from all apps
+ * - from center screen
+ * - from other screens
+ * - Back from all apps
+ * - from center screen
+ * - from other screens
+ * - Launch app from workspace and quit
+ * - with back
+ * - with home
+ * - Launch app from all apps and quit
+ * - with back
+ * - with home
+ * - Go to a screen that's not the default, then all
+ * apps, and launch and app, and go back
+ * - with back
+ * -with home
+ * - On workspace, long press power and go back
+ * - with back
+ * - with home
+ * - On all apps, long press power and go back
+ * - with back
+ * - with home
+ * - On workspace, power off
+ * - On all apps, power off
+ * - Launch an app and turn off the screen while in that app
+ * - Go back with home key
+ * - Go back with back key TODO: make this not go to workspace
+ * - From all apps
+ * - From workspace
+ * - Enter and exit car mode (becuase it causes an extra configuration changed)
+ * - From all apps
+ * - From the center workspace
+ * - From another workspace
+ */
+public class LauncherStateManager {
+
+ public static final String TAG = "StateManager";
+
+ private final AnimationConfig mConfig = new AnimationConfig();
+ private final Handler mUiHandler;
+ private final Launcher mLauncher;
+
+ private StateHandler[] mStateHandlers;
+ private LauncherState mState = NORMAL;
+
+ private StateListener mStateListener;
+
+ public LauncherStateManager(Launcher l) {
+ mUiHandler = new Handler(Looper.getMainLooper());
+ mLauncher = l;
+ }
+
+ public LauncherState getState() {
+ return mState;
+ }
+
+ private StateHandler[] getStateHandlers() {
+ if (mStateHandlers == null) {
+ mStateHandlers = UiFactory.getStateHandler(mLauncher);
+ }
+ return mStateHandlers;
+ }
+
+ public void setStateListener(StateListener stateListener) {
+ mStateListener = stateListener;
+ }
+
+ /**
+ * @see #goToState(LauncherState, boolean, Runnable)
+ */
+ public void goToState(LauncherState state) {
+ goToState(state, true, 0, null);
+ }
+
+ /**
+ * @see #goToState(LauncherState, boolean, Runnable)
+ */
+ public void goToState(LauncherState state, boolean animated) {
+ goToState(state, animated, 0, null);
+ }
+
+ /**
+ * Changes the Launcher state to the provided state.
+ *
+ * @param animated false if the state should change immediately without any animation,
+ * true otherwise
+ * @paras onCompleteRunnable any action to perform at the end of the transition, of null.
+ */
+ public void goToState(LauncherState state, boolean animated, Runnable onCompleteRunnable) {
+ goToState(state, animated, 0, onCompleteRunnable);
+ }
+
+ /**
+ * Changes the Launcher state to the provided state after the given delay.
+ */
+ public void goToState(LauncherState state, long delay, Runnable onCompleteRunnable) {
+ goToState(state, true, delay, onCompleteRunnable);
+ }
+
+ /**
+ * Changes the Launcher state to the provided state after the given delay.
+ */
+ public void goToState(LauncherState state, long delay) {
+ goToState(state, true, delay, null);
+ }
+
+ private void goToState(LauncherState state, boolean animated, long delay,
+ Runnable onCompleteRunnable) {
+ if (mLauncher.isInState(state) && mConfig.mCurrentAnimation == null) {
+
+ // Run any queued runnable
+ if (onCompleteRunnable != null) {
+ onCompleteRunnable.run();
+ }
+ return;
+ }
+
+ // Cancel the current animation
+ mConfig.reset();
+
+ if (!animated) {
+ setState(state);
+ for (StateHandler handler : getStateHandlers()) {
+ handler.setState(state);
+ }
+ if (mStateListener != null) {
+ mStateListener.onStateSetImmediately(state);
+ }
+ mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
+
+ // Run any queued runnable
+ if (onCompleteRunnable != null) {
+ onCompleteRunnable.run();
+ }
+ return;
+ }
+
+ // Since state NORMAL can be reached from multiple states, just assume that the
+ // transition plays in reverse and use the same duration as previous state.
+ mConfig.duration = state == NORMAL ? mState.transitionDuration : state.transitionDuration;
+
+ AnimatorSet animation = createAnimationToNewWorkspaceInternal(state, onCompleteRunnable);
+ Runnable runnable = new StartAnimRunnable(animation, state.getFinalFocus(mLauncher));
+ if (delay > 0) {
+ mUiHandler.postDelayed(runnable, delay);
+ } else {
+ mUiHandler.post(runnable);
+ }
+ }
+
+ /**
+ * Creates a {@link AnimatorPlaybackController} that can be used for a controlled
+ * state transition.
+ * @param state the final state for the transition.
+ * @param duration intended duration for normal playback. Use higher duration for better
+ * accuracy.
+ */
+ public AnimatorPlaybackController createAnimationToNewWorkspace(
+ LauncherState state, long duration) {
+ mConfig.reset();
+ mConfig.userControlled = true;
+ mConfig.duration = duration;
+ return AnimatorPlaybackController.wrap(
+ createAnimationToNewWorkspaceInternal(state, null), duration);
+ }
+
+ protected AnimatorSet createAnimationToNewWorkspaceInternal(final LauncherState state,
+ final Runnable onCompleteRunnable) {
+ final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
+ final AnimationLayerSet layerViews = new AnimationLayerSet();
+
+ for (StateHandler handler : getStateHandlers()) {
+ handler.setStateWithAnimation(state, layerViews, animation, mConfig);
+ }
+ animation.addListener(layerViews);
+ animation.addListener(new AnimationSuccessListener() {
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ // Change the internal state only when the transition actually starts
+ setState(state);
+ if (mStateListener != null) {
+ mStateListener.onStateTransitionStart(state);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (mStateListener != null) {
+ mStateListener.onStateTransitionComplete(mState);
+ }
+ }
+
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ // Run any queued runnables
+ if (onCompleteRunnable != null) {
+ onCompleteRunnable.run();
+ }
+
+ mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
+ }
+ });
+ mConfig.setAnimation(animation);
+ return mConfig.mCurrentAnimation;
+ }
+
+ private void setState(LauncherState state) {
+ mState.onStateDisabled(mLauncher);
+ mState = state;
+ mState.onStateEnabled(mLauncher);
+ mLauncher.getAppWidgetHost().setResumed(state == LauncherState.NORMAL);
+ }
+
+ /**
+ * Cancels the current animation.
+ */
+ public void cancelAnimation() {
+ mConfig.reset();
+ }
+
+ private class StartAnimRunnable implements Runnable {
+
+ private final AnimatorSet mAnim;
+ private final View mViewToFocus;
+
+ public StartAnimRunnable(AnimatorSet anim, View viewToFocus) {
+ mAnim = anim;
+ mViewToFocus = viewToFocus;
+ }
+
+ @Override
+ public void run() {
+ if (mConfig.mCurrentAnimation != mAnim) {
+ return;
+ }
+ if (mViewToFocus != null) {
+ mViewToFocus.requestFocus();
+ }
+ mAnim.start();
+ }
+ }
+
+ public static class AnimationConfig extends AnimatorListenerAdapter {
+ public long duration;
+ public boolean userControlled;
+
+ private AnimatorSet mCurrentAnimation;
+
+ public void reset() {
+ duration = 0;
+ userControlled = false;
+
+ if (mCurrentAnimation != null) {
+ mCurrentAnimation.setDuration(0);
+ mCurrentAnimation.cancel();
+ mCurrentAnimation = null;
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mCurrentAnimation == animation) {
+ mCurrentAnimation = null;
+ }
+ }
+
+ public void setAnimation(AnimatorSet animation) {
+ mCurrentAnimation = animation;
+ mCurrentAnimation.addListener(this);
+ }
+ }
+
+ public interface StateHandler {
+
+ /**
+ * Updates the UI to {@param state} without any animations
+ */
+ void setState(LauncherState state);
+
+ /**
+ * Sets the UI to {@param state} by animating any changes.
+ */
+ void setStateWithAnimation(LauncherState toState, AnimationLayerSet layerViews,
+ AnimatorSet anim, AnimationConfig config);
+ }
+
+ public interface StateListener {
+
+ /**
+ * Called when the state is set without an animation.
+ */
+ void onStateSetImmediately(LauncherState state);
+
+ void onStateTransitionStart(LauncherState toState);
+ void onStateTransitionComplete(LauncherState finalState);
+ }
+}
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
deleted file mode 100644
index f07f1bf..0000000
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ /dev/null
@@ -1,332 +0,0 @@
-/*
- * Copyright (C) 2015 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;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.util.Log;
-import android.view.View;
-
-import com.android.launcher3.allapps.AllAppsContainerView;
-import com.android.launcher3.allapps.AllAppsTransitionController;
-import com.android.launcher3.anim.AnimationLayerSet;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.Thunk;
-
-/**
- * TODO: figure out what kind of tests we can write for this
- *
- * Things to test when changing the following class.
- * - Home from workspace
- * - from center screen
- * - from other screens
- * - Home from all apps
- * - from center screen
- * - from other screens
- * - Back from all apps
- * - from center screen
- * - from other screens
- * - Launch app from workspace and quit
- * - with back
- * - with home
- * - Launch app from all apps and quit
- * - with back
- * - with home
- * - Go to a screen that's not the default, then all
- * apps, and launch and app, and go back
- * - with back
- * -with home
- * - On workspace, long press power and go back
- * - with back
- * - with home
- * - On all apps, long press power and go back
- * - with back
- * - with home
- * - On workspace, power off
- * - On all apps, power off
- * - Launch an app and turn off the screen while in that app
- * - Go back with home key
- * - Go back with back key TODO: make this not go to workspace
- * - From all apps
- * - From workspace
- * - Enter and exit car mode (becuase it causes an extra configuration changed)
- * - From all apps
- * - From the center workspace
- * - From another workspace
- */
-public class LauncherStateTransitionAnimation {
-
- public static final String TAG = "LSTAnimation";
-
- private final AnimationConfig mConfig = new AnimationConfig();
- @Thunk Launcher mLauncher;
- @Thunk AnimatorSet mCurrentAnimation;
- AllAppsTransitionController mAllAppsController;
-
- public LauncherStateTransitionAnimation(Launcher l, AllAppsTransitionController allAppsController) {
- mLauncher = l;
- mAllAppsController = allAppsController;
- }
-
- /**
- * Starts an animation to the apps view.
- */
- public void startAnimationToAllApps(boolean animated) {
- final AllAppsContainerView toView = mLauncher.getAppsView();
-
- // If for some reason our views aren't initialized, don't animate
- animated = animated && (toView != null);
-
- final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
-
- final AnimationLayerSet layerViews = new AnimationLayerSet();
-
- // Cancel the current animation
- cancelAnimation();
-
- if (!animated) {
- mLauncher.getWorkspace().setState(LauncherState.ALL_APPS);
-
- mAllAppsController.finishPullUp();
- toView.setTranslationX(0.0f);
- toView.setTranslationY(0.0f);
- toView.setScaleX(1.0f);
- toView.setScaleY(1.0f);
- toView.setAlpha(1.0f);
- toView.setVisibility(View.VISIBLE);
-
- mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
- return;
- }
-
- if (!FeatureFlags.LAUNCHER3_PHYSICS) {
- // We are animating the content view alpha, so ensure we have a layer for it.
- layerViews.addView(toView);
- }
-
- animation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- cleanupAnimation();
- mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
- }
- });
-
- mConfig.reset();
- mAllAppsController.animateToAllApps(animation, mConfig);
- mLauncher.getWorkspace().setStateWithAnimation(LauncherState.ALL_APPS,
- layerViews, animation, mConfig);
-
- Runnable startAnimRunnable = new StartAnimRunnable(animation, toView);
- mCurrentAnimation = animation;
- mCurrentAnimation.addListener(layerViews);
- if (mConfig.shouldPost) {
- toView.post(startAnimRunnable);
- } else {
- startAnimRunnable.run();
- }
- }
-
- /**
- * Starts an animation to the workspace from the current overlay view.
- */
- public void startAnimationToWorkspace(final Launcher.State fromState,
- final LauncherState fromWorkspaceState, final LauncherState toWorkspaceState,
- final boolean animated, final Runnable onCompleteRunnable) {
- if (toWorkspaceState != LauncherState.NORMAL &&
- toWorkspaceState != LauncherState.SPRING_LOADED &&
- toWorkspaceState != LauncherState.OVERVIEW) {
- Log.e(TAG, "Unexpected call to startAnimationToWorkspace");
- }
-
- if (fromState == Launcher.State.APPS || mAllAppsController.isTransitioning()) {
- startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState,
- animated, onCompleteRunnable);
- } else {
- startAnimationToNewWorkspaceState(fromWorkspaceState, toWorkspaceState,
- animated, onCompleteRunnable);
- }
- }
-
- /**
- * Starts an animation to the workspace from the apps view.
- */
- private void startAnimationToWorkspaceFromAllApps(final LauncherState fromWorkspaceState,
- final LauncherState toWorkspaceState, boolean animated,
- final Runnable onCompleteRunnable) {
- final AllAppsContainerView fromView = mLauncher.getAppsView();
- // If for some reason our views aren't initialized, don't animate
- animated = animated & (fromView != null);
-
- final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
- final AnimationLayerSet layerViews = new AnimationLayerSet();
-
- // Cancel the current animation
- cancelAnimation();
-
- if (!animated) {
- if (fromWorkspaceState == LauncherState.ALL_APPS) {
- mAllAppsController.finishPullDown();
- }
- fromView.setVisibility(View.GONE);
- mLauncher.getWorkspace().setState(toWorkspaceState);
- mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
-
- // Run any queued runnables
- if (onCompleteRunnable != null) {
- onCompleteRunnable.run();
- }
- return;
- }
-
- animation.addListener(new AnimatorListenerAdapter() {
- boolean canceled = false;
- @Override
- public void onAnimationCancel(Animator animation) {
- canceled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (canceled) return;
- // Run any queued runnables
- if (onCompleteRunnable != null) {
- onCompleteRunnable.run();
- }
-
- cleanupAnimation();
- mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
- }
-
- });
-
- mConfig.reset();
- mAllAppsController.animateToWorkspace(animation, mConfig);
- mLauncher.getWorkspace().setStateWithAnimation(toWorkspaceState, layerViews, animation,
- mConfig);
-
- Runnable startAnimRunnable = new StartAnimRunnable(animation, mLauncher.getWorkspace());
- mCurrentAnimation = animation;
- mCurrentAnimation.addListener(layerViews);
- if (mConfig.shouldPost) {
- fromView.post(startAnimRunnable);
- } else {
- startAnimRunnable.run();
- }
- }
-
- /**
- * Starts an animation to the workspace from another workspace state, e.g. normal to overview.
- */
- private void startAnimationToNewWorkspaceState(final LauncherState fromWorkspaceState,
- final LauncherState toWorkspaceState, final boolean animated,
- final Runnable onCompleteRunnable) {
- final View fromWorkspace = mLauncher.getWorkspace();
- // Cancel the current animation
- cancelAnimation();
-
- mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
-
- if (!animated) {
- mLauncher.getWorkspace().setState(toWorkspaceState);
- // Run any queued runnables
- if (onCompleteRunnable != null) {
- onCompleteRunnable.run();
- }
- return;
- }
-
- final AnimationLayerSet layerViews = new AnimationLayerSet();
- final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
- mConfig.reset();
- mLauncher.getWorkspace().setStateWithAnimation(toWorkspaceState,
- layerViews, animation, mConfig);
-
- animation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- // Run any queued runnables
- if (onCompleteRunnable != null) {
- onCompleteRunnable.run();
- }
-
- // This can hold unnecessary references to views.
- cleanupAnimation();
- }
- });
- animation.addListener(layerViews);
- fromWorkspace.post(new StartAnimRunnable(animation, null));
- mCurrentAnimation = animation;
- }
-
- /**
- * Cancels the current animation.
- */
- private void cancelAnimation() {
- if (mCurrentAnimation != null) {
- mCurrentAnimation.setDuration(0);
- mCurrentAnimation.cancel();
- mCurrentAnimation = null;
- }
- }
-
- @Thunk void cleanupAnimation() {
- mCurrentAnimation = null;
- }
-
- private class StartAnimRunnable implements Runnable {
-
- private final AnimatorSet mAnim;
- private final View mViewToFocus;
-
- public StartAnimRunnable(AnimatorSet anim, View viewToFocus) {
- mAnim = anim;
- mViewToFocus = viewToFocus;
- }
-
- @Override
- public void run() {
- if (mCurrentAnimation != mAnim) {
- return;
- }
- if (mViewToFocus != null) {
- mViewToFocus.requestFocus();
- }
- mAnim.start();
- }
- }
-
- public static class AnimationConfig {
- public boolean shouldPost;
-
- private long mOverriddenDuration = -1;
-
- public void reset() {
- shouldPost = false;
- mOverriddenDuration = -1;
- }
-
- public void overrideDuration(long duration) {
- mOverriddenDuration = duration;
- }
-
- public long getDuration(long defaultDuration) {
- return mOverriddenDuration >= 0 ? mOverriddenDuration : defaultDuration;
- }
- }
-}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 87f3dda..6c22474 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -46,10 +46,10 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.Interpolator;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.pageindicators.PageIndicator;
import com.android.launcher3.touch.OverScroll;
-import com.android.launcher3.util.Themes;
import com.android.launcher3.util.Thunk;
import java.util.ArrayList;
@@ -87,15 +87,12 @@
public static final int INVALID_RESTORE_PAGE = -1001;
private boolean mFreeScroll = false;
- private int mFreeScrollMinScrollX = -1;
- private int mFreeScrollMaxScrollX = -1;
protected int mFlingThresholdVelocity;
protected int mMinFlingVelocity;
protected int mMinSnapVelocity;
protected boolean mFirstLayout = true;
- private int mNormalChildHeight;
@ViewDebug.ExportedProperty(category = "launcher")
protected int mCurrentPage;
@@ -137,7 +134,6 @@
protected int mTouchSlop;
private int mMaximumVelocity;
protected boolean mAllowOverScroll = true;
- protected int[] mTempVisiblePagesRange = new int[2];
protected static final int INVALID_POINTER = -1;
@@ -169,8 +165,6 @@
@Thunk static int REORDERING_REORDER_REPOSITION_DURATION = 300;
private static int REORDERING_SIDE_PAGE_HOVER_TIMEOUT = 80;
- private float mMinScale = 1f;
- private boolean mUseMinScale = false;
@Thunk View mDragView;
private Runnable mSidePageHoverRunnable;
@Thunk int mSidePageHoverIndex = -1;
@@ -218,7 +212,7 @@
*/
protected void init() {
mScroller = new LauncherScroller(getContext());
- setDefaultInterpolator(new ScrollInterpolator());
+ setDefaultInterpolator(Interpolators.SCROLL);
mCurrentPage = 0;
final ViewConfiguration configuration = ViewConfiguration.get(getContext());
@@ -276,12 +270,6 @@
}
}
- public void setMinScale(float f) {
- mMinScale = f;
- mUseMinScale = true;
- requestLayout();
- }
-
@Override
public void setScaleX(float scaleX) {
super.setScaleX(scaleX);
@@ -380,16 +368,8 @@
}
private int validateNewPage(int newPage) {
- int validatedPage = newPage;
- // When in free scroll mode, we need to clamp to the free scroll page range.
- if (mFreeScroll) {
- getFreeScrollPageRange(mTempVisiblePagesRange);
- validatedPage = Math.max(mTempVisiblePagesRange[0],
- Math.min(newPage, mTempVisiblePagesRange[1]));
- }
// Ensure that it is clamped by the actual set of children in all cases
- validatedPage = Utilities.boundToRange(validatedPage, 0, getPageCount() - 1);
- return validatedPage;
+ return Utilities.boundToRange(newPage, 0, getPageCount() - 1);
}
/**
@@ -491,13 +471,11 @@
if (mFreeScroll) {
// If the scroller is trying to move to a location beyond the maximum allowed
// in the free scroll mode, we make sure to end the scroll operation.
- if (!mScroller.isFinished() &&
- (x > mFreeScrollMaxScrollX || x < mFreeScrollMinScrollX)) {
+ if (!mScroller.isFinished() && (x > mMaxScrollX || x < 0)) {
forceFinishScroller(false);
}
- x = Math.min(x, mFreeScrollMaxScrollX);
- x = Math.max(x, mFreeScrollMinScrollX);
+ x = Utilities.boundToRange(x, 0, mMaxScrollX);
}
mUnboundedScrollX = x;
@@ -610,56 +588,9 @@
computeScrollHelper();
}
- public static class LayoutParams extends ViewGroup.LayoutParams {
- public boolean isFullScreenPage = false;
-
- // If true, the start edge of the page snaps to the start edge of the viewport.
- public boolean matchStartEdge = false;
-
- /**
- * {@inheritDoc}
- */
- public LayoutParams(int width, int height) {
- super(width, height);
- }
-
- public LayoutParams(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public LayoutParams(ViewGroup.LayoutParams source) {
- super(source);
- }
- }
-
- @Override
- public LayoutParams generateLayoutParams(AttributeSet attrs) {
- return new LayoutParams(getContext(), attrs);
- }
-
- @Override
- protected LayoutParams generateDefaultLayoutParams() {
- return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
- }
-
- @Override
- protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
- return new LayoutParams(p);
- }
-
- @Override
- protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
- return p instanceof LayoutParams;
- }
-
- public void addFullScreenPage(View page) {
- LayoutParams lp = generateDefaultLayoutParams();
- lp.isFullScreenPage = true;
- super.addView(page, 0, lp);
- }
-
public int getNormalChildHeight() {
- return mNormalChildHeight;
+ return getViewportHeight() - getPaddingTop() - getPaddingBottom()
+ - mInsets.top - mInsets.bottom;
}
@Override
@@ -675,22 +606,7 @@
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
- // NOTE: We multiply by 2f to account for the fact that depending on the offset of the
- // viewport, we can be at most one and a half screens offset once we scale down
- DisplayMetrics dm = getResources().getDisplayMetrics();
- int maxSize = Math.max(dm.widthPixels + mInsets.left + mInsets.right,
- dm.heightPixels + mInsets.top + mInsets.bottom);
- int parentWidthSize = (int) (2f * maxSize);
- int parentHeightSize = (int) (2f * maxSize);
- int scaledWidthSize, scaledHeightSize;
- if (mUseMinScale) {
- scaledWidthSize = (int) (parentWidthSize / mMinScale);
- scaledHeightSize = (int) (parentHeightSize / mMinScale);
- } else {
- scaledWidthSize = widthSize;
- scaledHeightSize = heightSize;
- }
mViewport.set(0, 0, widthSize, heightSize);
if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
@@ -704,71 +620,19 @@
return;
}
- /* Allow the height to be set as WRAP_CONTENT. This allows the particular case
- * of the All apps view on XLarge displays to not take up more space then it needs. Width
- * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect
- * each page to have the same width.
- */
- final int verticalPadding = getPaddingTop() + getPaddingBottom();
- final int horizontalPadding = getPaddingLeft() + getPaddingRight();
-
- int referenceChildWidth = 0;
// The children are given the same width and height as the workspace
// unless they were set to WRAP_CONTENT
if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize);
- if (DEBUG) Log.d(TAG, "PagedView.scaledSize: " + scaledWidthSize + ", " + scaledHeightSize);
- if (DEBUG) Log.d(TAG, "PagedView.parentSize: " + parentWidthSize + ", " + parentHeightSize);
- if (DEBUG) Log.d(TAG, "PagedView.horizontalPadding: " + horizontalPadding);
- if (DEBUG) Log.d(TAG, "PagedView.verticalPadding: " + verticalPadding);
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- // disallowing padding in paged view (just pass 0)
- final View child = getPageAt(i);
- if (child.getVisibility() != GONE) {
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- int childWidthMode;
- int childHeightMode;
- int childWidth;
- int childHeight;
+ int myWidthSpec = MeasureSpec.makeMeasureSpec(
+ getViewportWidth() - mInsets.left - mInsets.right, MeasureSpec.EXACTLY);
+ int myHeightSpec = MeasureSpec.makeMeasureSpec(
+ getViewportHeight() - mInsets.top - mInsets.bottom, MeasureSpec.EXACTLY);
- if (!lp.isFullScreenPage) {
- if (lp.width == LayoutParams.WRAP_CONTENT) {
- childWidthMode = MeasureSpec.AT_MOST;
- } else {
- childWidthMode = MeasureSpec.EXACTLY;
- }
-
- if (lp.height == LayoutParams.WRAP_CONTENT) {
- childHeightMode = MeasureSpec.AT_MOST;
- } else {
- childHeightMode = MeasureSpec.EXACTLY;
- }
-
- childWidth = getViewportWidth() - horizontalPadding
- - mInsets.left - mInsets.right;
- childHeight = getViewportHeight() - verticalPadding
- - mInsets.top - mInsets.bottom;
- mNormalChildHeight = childHeight;
- } else {
- childWidthMode = MeasureSpec.EXACTLY;
- childHeightMode = MeasureSpec.EXACTLY;
-
- childWidth = getViewportWidth();
- childHeight = getViewportHeight();
- }
- if (referenceChildWidth == 0) {
- referenceChildWidth = childWidth;
- }
-
- final int childWidthMeasureSpec =
- MeasureSpec.makeMeasureSpec(childWidth, childWidthMode);
- final int childHeightMeasureSpec =
- MeasureSpec.makeMeasureSpec(childHeight, childHeightMode);
- child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- }
- }
- setMeasuredDimension(scaledWidthSize, scaledHeightSize);
+ // measureChildren takes accounts for content padding, we only need to care about extra
+ // space due to insets.
+ measureChildren(myWidthSpec, myHeightSpec);
+ setMeasuredDimension(widthSize, heightSize);
}
@SuppressLint("DrawAllocation")
@@ -793,10 +657,7 @@
int verticalPadding = getPaddingTop() + getPaddingBottom();
- LayoutParams lp = (LayoutParams) getChildAt(startIndex).getLayoutParams();
- LayoutParams nextLp;
-
- int childLeft = offsetX + (lp.isFullScreenPage ? 0 : getPaddingLeft());
+ int childLeft = offsetX + getPaddingLeft();
if (mPageScrolls == null || childCount != mChildCountOnLastLayout) {
mPageScrolls = new int[childCount];
}
@@ -804,14 +665,9 @@
for (int i = startIndex; i != endIndex; i += delta) {
final View child = getPageAt(i);
if (child.getVisibility() != View.GONE) {
- lp = (LayoutParams) child.getLayoutParams();
- int childTop;
- if (lp.isFullScreenPage) {
- childTop = offsetY;
- } else {
- childTop = offsetY + getPaddingTop() + mInsets.top;
- childTop += (getViewportHeight() - mInsets.top - mInsets.bottom - verticalPadding - child.getMeasuredHeight()) / 2;
- }
+ int childTop = offsetY + getPaddingTop() + mInsets.top;
+ childTop += (getViewportHeight() - mInsets.top - mInsets.bottom - verticalPadding
+ - child.getMeasuredHeight()) / 2;
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
@@ -820,26 +676,10 @@
child.layout(childLeft, childTop,
childLeft + child.getMeasuredWidth(), childTop + childHeight);
- int scrollOffsetLeft = lp.isFullScreenPage ? 0 : getPaddingLeft();
+ int scrollOffsetLeft = getPaddingLeft();
mPageScrolls[i] = childLeft - scrollOffsetLeft - offsetX;
- int pageGap = mPageSpacing;
- int next = i + delta;
- if (next != endIndex) {
- nextLp = (LayoutParams) getPageAt(next).getLayoutParams();
- } else {
- nextLp = null;
- }
-
- // Prevent full screen pages from showing in the viewport
- // when they are not the current page.
- if (lp.isFullScreenPage) {
- pageGap = getPaddingLeft();
- } else if (nextLp != null && nextLp.isFullScreenPage) {
- pageGap = getPaddingRight();
- }
-
- childLeft += childWidth + pageGap + getChildGap();
+ childLeft += childWidth + mPageSpacing + getChildGap();
}
}
@@ -886,7 +726,7 @@
return 0;
}
- @Thunk void updateMaxScrollX() {
+ private void updateMaxScrollX() {
mMaxScrollX = computeMaxScrollX();
}
@@ -915,13 +755,11 @@
// This ensures that when children are added, they get the correct transforms / alphas
// in accordance with any scroll effects.
- updateFreescrollBounds();
invalidate();
}
@Override
public void onChildViewRemoved(View parent, View child) {
- updateFreescrollBounds();
mCurrentPage = validateNewPage(mCurrentPage);
invalidate();
}
@@ -974,11 +812,6 @@
return offset;
}
- protected void getFreeScrollPageRange(int[] range) {
- range[0] = 0;
- range[1] = Math.max(0, getChildCount() - 1);
- }
-
@Override
public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
int page = indexToPage(indexOfChild(child));
@@ -1309,12 +1142,7 @@
} else {
View child = getChildAt(index);
- int scrollOffset = 0;
- LayoutParams lp = (LayoutParams) child.getLayoutParams();
- if (!lp.isFullScreenPage) {
- scrollOffset = mIsRtl ? getPaddingRight() : getPaddingLeft();
- }
-
+ int scrollOffset = scrollOffset = mIsRtl ? getPaddingRight() : getPaddingLeft();
int baselineX = mPageScrolls[index] + scrollOffset + getViewportOffsetX();
return (int) (child.getX() - baselineX);
}
@@ -1341,38 +1169,20 @@
/**
* return true if freescroll has been enabled, false otherwise
*/
- public boolean enableFreeScroll() {
+ protected void enableFreeScroll() {
setEnableFreeScroll(true);
- return true;
}
- public void disableFreeScroll() {
+ protected void disableFreeScroll() {
setEnableFreeScroll(false);
}
- void updateFreescrollBounds() {
- getFreeScrollPageRange(mTempVisiblePagesRange);
- if (mIsRtl) {
- mFreeScrollMinScrollX = getScrollForPage(mTempVisiblePagesRange[1]);
- mFreeScrollMaxScrollX = getScrollForPage(mTempVisiblePagesRange[0]);
- } else {
- mFreeScrollMinScrollX = getScrollForPage(mTempVisiblePagesRange[0]);
- mFreeScrollMaxScrollX = getScrollForPage(mTempVisiblePagesRange[1]);
- }
- }
-
private void setEnableFreeScroll(boolean freeScroll) {
boolean wasFreeScroll = mFreeScroll;
mFreeScroll = freeScroll;
if (mFreeScroll) {
- updateFreescrollBounds();
- getFreeScrollPageRange(mTempVisiblePagesRange);
- if (getCurrentPage() < mTempVisiblePagesRange[0]) {
- setCurrentPage(mTempVisiblePagesRange[0]);
- } else if (getCurrentPage() > mTempVisiblePagesRange[1]) {
- setCurrentPage(mTempVisiblePagesRange[1]);
- }
+ setCurrentPage(getNextPage());
} else if (wasFreeScroll) {
snapToPage(getNextPage());
}
@@ -1388,12 +1198,12 @@
if (mDragView != null) {
int dragX = (int) (mDragView.getLeft() + (mDragView.getMeasuredWidth() / 2)
+ mDragView.getTranslationX());
- getFreeScrollPageRange(mTempVisiblePagesRange);
int minDistance = Integer.MAX_VALUE;
int minIndex = indexOfChild(mDragView);
- for (int i = mTempVisiblePagesRange[0]; i <= mTempVisiblePagesRange[1]; i++) {
+ int maxPageNo = getChildCount() - 1;
+ for (int i = 0; i <= maxPageNo; i++) {
View page = getPageAt(i);
- int pageX = (int) (page.getLeft() + page.getMeasuredWidth() / 2);
+ int pageX = (page.getLeft() + page.getMeasuredWidth() / 2);
int d = Math.abs(dragX - pageX);
if (d < minDistance) {
minIndex = i;
@@ -1488,11 +1298,7 @@
final int pageUnderPointIndex = getNearestHoverOverPageIndex();
// Do not allow any page to be moved to 0th position.
if (pageUnderPointIndex > 0 && pageUnderPointIndex != indexOfChild(mDragView)) {
- mTempVisiblePagesRange[0] = 0;
- mTempVisiblePagesRange[1] = getPageCount() - 1;
- getFreeScrollPageRange(mTempVisiblePagesRange);
- if (mTempVisiblePagesRange[0] <= pageUnderPointIndex &&
- pageUnderPointIndex <= mTempVisiblePagesRange[1] &&
+ if (0 <= pageUnderPointIndex && pageUnderPointIndex <= getPageCount() - 1 &&
pageUnderPointIndex != mSidePageHoverIndex && mScroller.isFinished()) {
mSidePageHoverIndex = pageUnderPointIndex;
mSidePageHoverRunnable = new Runnable() {
@@ -1809,16 +1615,6 @@
return PAGE_SNAP_ANIMATION_DURATION;
}
- public static class ScrollInterpolator implements Interpolator {
- public ScrollInterpolator() {
- }
-
- public float getInterpolation(float t) {
- t -= 1.0f;
- return t*t*t*t*t + 1;
- }
- }
-
// We want the duration of the page snap animation to be influenced by the distance that
// the screen has to travel, however, we don't want this duration to be effected in a
// purely linear fashion. Instead, we use this method to moderate the effect that the distance
@@ -2026,18 +1822,14 @@
// Do not allow the first page to be moved around
if (mTouchState != TOUCH_STATE_REST || dragViewIndex <= 0) return false;
- mTempVisiblePagesRange[0] = 0;
- mTempVisiblePagesRange[1] = getPageCount() - 1;
- getFreeScrollPageRange(mTempVisiblePagesRange);
- mReorderingStarted = true;
-
// Check if we are within the reordering range
- if (mTempVisiblePagesRange[0] <= dragViewIndex &&
- dragViewIndex <= mTempVisiblePagesRange[1]) {
+ if (0 <= dragViewIndex && dragViewIndex <= getPageCount() - 1) {
// Find the drag view under the pointer
mDragView = getChildAt(dragViewIndex);
mDragView.animate().scaleX(1.15f).scaleY(1.15f).setDuration(100).start();
mDragViewBaselineLeft = mDragView.getLeft();
+ mReorderingStarted = true;
+
snapToPage(getPageNearestToCenterOfScreen());
disableFreeScroll();
onStartReordering();
diff --git a/src/com/android/launcher3/PinchAnimationManager.java b/src/com/android/launcher3/PinchAnimationManager.java
deleted file mode 100644
index bbe8e89..0000000
--- a/src/com/android/launcher3/PinchAnimationManager.java
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Copyright (C) 2016 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;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.util.Log;
-import android.view.View;
-import android.view.animation.LinearInterpolator;
-
-import com.android.launcher3.anim.AnimationLayerSet;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-
-/**
- * Manages the animations that play as the user pinches to/from overview mode.
- *
- * It will look like this pinching in:
- * - Workspace scales down
- * - At some threshold 1, hotseat and QSB fade out (full animation)
- * - At a later threshold 2, panel buttons fade in and scrim fades in
- * - At a final threshold 3, snap to overview
- *
- * Pinching out:
- * - Workspace scales up
- * - At threshold 1, panel buttons fade out
- * - At threshold 2, hotseat and QSB fade in and scrim fades out
- * - At threshold 3, snap to workspace
- *
- * @see PinchToOverviewListener
- * @see PinchThresholdManager
- */
-public class PinchAnimationManager {
- private static final String TAG = "PinchAnimationManager";
-
- private static final int THRESHOLD_ANIM_DURATION = 150;
- private static final LinearInterpolator INTERPOLATOR = new LinearInterpolator();
-
- private static final int INDEX_HOTSEAT = 0;
- private static final int INDEX_OVERVIEW_PANEL_BUTTONS = 1;
- private static final int INDEX_SCRIM = 2;
-
- private final Animator[] mAnimators = new Animator[3];
-
- private Launcher mLauncher;
- private Workspace mWorkspace;
-
- private float mOverviewScale;
- private float mOverviewTranslationY;
- private int mNormalOverviewTransitionDuration;
- private boolean mIsAnimating;
-
- public PinchAnimationManager(Launcher launcher) {
- mLauncher = launcher;
- mWorkspace = launcher.mWorkspace;
-
- mOverviewScale = mWorkspace.getOverviewModeShrinkFactor();
- mOverviewTranslationY = mWorkspace.getOverviewModeTranslationY();
- mNormalOverviewTransitionDuration = LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
- }
-
- public int getNormalOverviewTransitionDuration() {
- return mNormalOverviewTransitionDuration;
- }
-
- /**
- * Interpolate from {@param currentProgress} to {@param toProgress}, calling
- * {@link #setAnimationProgress(float)} throughout the duration. If duration is -1,
- * the default overview transition duration is used.
- */
- public void animateToProgress(float currentProgress, float toProgress, int duration,
- final PinchThresholdManager thresholdManager) {
- if (duration == -1) {
- duration = mNormalOverviewTransitionDuration;
- }
- ValueAnimator animator = ValueAnimator.ofFloat(currentProgress, toProgress);
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float pinchProgress = (Float) animation.getAnimatedValue();
- setAnimationProgress(pinchProgress);
- thresholdManager.updateAndAnimatePassedThreshold(pinchProgress,
- PinchAnimationManager.this);
- }
- }
- );
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mIsAnimating = false;
- thresholdManager.reset();
- mWorkspace.onEndStateTransition();
- }
- });
- animator.setDuration(duration).start();
- mIsAnimating = true;
- }
-
- public boolean isAnimating() {
- return mIsAnimating;
- }
-
- /**
- * Animates to the specified progress. This should be called repeatedly throughout the pinch
- * gesture to run animations that interpolate throughout the gesture.
- * @param interpolatedProgress The progress from 0 to 1, where 0 is overview and 1 is workspace.
- */
- public void setAnimationProgress(float interpolatedProgress) {
- float interpolatedScale = interpolatedProgress * (1f - mOverviewScale) + mOverviewScale;
- float interpolatedTranslationY = (1f - interpolatedProgress) * mOverviewTranslationY;
- mWorkspace.setScaleX(interpolatedScale);
- mWorkspace.setScaleY(interpolatedScale);
- mWorkspace.setTranslationY(interpolatedTranslationY);
- int alpha = (int) ((1f - interpolatedProgress) * 255);
- setOverviewPanelsAlpha(alpha, 0);
- }
-
- /**
- * Animates certain properties based on which threshold was passed, and in what direction. The
- * starting state must also be taken into account because the thresholds mean different things
- * when going from workspace to overview and vice versa.
- * @param threshold One of {@link PinchThresholdManager#THRESHOLD_ONE},
- * {@link PinchThresholdManager#THRESHOLD_TWO}, or
- * {@link PinchThresholdManager#THRESHOLD_THREE}
- * @param startState {@link LauncherState#NORMAL} or {@link LauncherState#OVERVIEW}.
- * @param goingTowards {@link LauncherState#NORMAL} or {@link LauncherState#OVERVIEW}.
- * Note that this doesn't have to be the opposite of startState;
- */
- public void animateThreshold(float threshold, LauncherState startState,
- LauncherState goingTowards) {
- if (threshold == PinchThresholdManager.THRESHOLD_ONE) {
- if (startState == OVERVIEW) {
- animateOverviewPanelButtons(goingTowards == OVERVIEW);
- } else if (startState == NORMAL) {
- animateHotseatAndQsb(goingTowards == NORMAL);
- }
- } else if (threshold == PinchThresholdManager.THRESHOLD_TWO) {
- if (startState == OVERVIEW) {
- animateHotseatAndQsb(goingTowards == NORMAL);
- animateScrim(goingTowards == OVERVIEW);
- } else if (startState == NORMAL) {
- animateOverviewPanelButtons(goingTowards == OVERVIEW);
- animateScrim(goingTowards == OVERVIEW);
- }
- } else if (threshold == PinchThresholdManager.THRESHOLD_THREE) {
- // Passing threshold 3 ends the pinch and snaps to the new state.
- if (startState == OVERVIEW && goingTowards == NORMAL) {
- mLauncher.getUserEventDispatcher().logActionOnContainer(
- Action.Touch.PINCH, Action.Direction.NONE,
- ContainerType.OVERVIEW, mWorkspace.getCurrentPage());
- mLauncher.showWorkspace(true);
- mWorkspace.snapToPage(mWorkspace.getCurrentPage());
- } else if (startState == NORMAL && goingTowards == OVERVIEW) {
- mLauncher.getUserEventDispatcher().logActionOnContainer(
- Action.Touch.PINCH, Action.Direction.NONE,
- ContainerType.WORKSPACE, mWorkspace.getCurrentPage());
- mLauncher.showOverviewMode(true);
- }
- } else {
- Log.e(TAG, "Received unknown threshold to animate: " + threshold);
- }
- }
-
- private void setOverviewPanelsAlpha(int alpha, int duration) {
- int childCount = mWorkspace.getChildCount();
- for (int i = 0; i < childCount; i++) {
- final CellLayout cl = (CellLayout) mWorkspace.getChildAt(i);
- if (duration == 0) {
- cl.getScrimBackground().setAlpha(alpha);
- } else {
- ObjectAnimator.ofInt(cl.getScrimBackground(),
- LauncherAnimUtils.DRAWABLE_ALPHA, alpha).setDuration(duration).start();
- }
- }
- }
-
- private void animateHotseatAndQsb(boolean show) {
- startAnimator(INDEX_HOTSEAT,
- mWorkspace.createHotseatAlphaAnimator(show ? 1 : 0), THRESHOLD_ANIM_DURATION);
- }
-
- private void animateOverviewPanelButtons(boolean show) {
- animateShowHideView(INDEX_OVERVIEW_PANEL_BUTTONS, mLauncher.getOverviewPanel(), show);
- }
-
- private void animateScrim(boolean show) {
- int endValue = show ? mWorkspace.getStateTransitionAnimation().mWorkspaceScrimAlpha : 0;
- startAnimator(INDEX_SCRIM, ObjectAnimator.ofInt(
- mLauncher.getDragLayer().getScrim(), LauncherAnimUtils.DRAWABLE_ALPHA, endValue),
- mNormalOverviewTransitionDuration);
- }
-
- private void animateShowHideView(int index, final View view, boolean show) {
- Animator animator = ObjectAnimator.ofFloat(view, View.ALPHA, show ? 1 : 0);
- animator.addListener(new AnimationLayerSet(view));
- if (show) {
- view.setVisibility(View.VISIBLE);
- } else {
- animator.addListener(new AnimatorListenerAdapter() {
- private boolean mCancelled = false;
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mCancelled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (!mCancelled) {
- view.setVisibility(View.INVISIBLE);
- }
- }
- });
- }
- startAnimator(index, animator, THRESHOLD_ANIM_DURATION);
- }
-
- private void startAnimator(int index, Animator animator, long duration) {
- if (mAnimators[index] != null) {
- mAnimators[index].cancel();
- }
- mAnimators[index] = animator;
- mAnimators[index].setInterpolator(INTERPOLATOR);
- mAnimators[index].setDuration(duration).start();
- }
-}
diff --git a/src/com/android/launcher3/PinchThresholdManager.java b/src/com/android/launcher3/PinchThresholdManager.java
deleted file mode 100644
index 8cbc33d..0000000
--- a/src/com/android/launcher3/PinchThresholdManager.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2016 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;
-
-/**
- * Keeps track of when thresholds are passed during a pinch gesture,
- * used to inform {@link PinchAnimationManager} throughout.
- *
- * @see PinchToOverviewListener
- * @see PinchAnimationManager
- */
-public class PinchThresholdManager {
- public static final float THRESHOLD_ZERO = 0.0f;
- public static final float THRESHOLD_ONE = 0.40f;
- public static final float THRESHOLD_TWO = 0.70f;
- public static final float THRESHOLD_THREE = 0.95f;
-
- private Workspace mWorkspace;
-
- private float mPassedThreshold = THRESHOLD_ZERO;
-
- public PinchThresholdManager(Workspace workspace) {
- mWorkspace = workspace;
- }
-
- /**
- * Uses the pinch progress to determine whether a threshold has been passed,
- * and asks the {@param animationManager} to animate if so.
- * @param progress From 0 to 1, where 0 is overview and 1 is workspace.
- * @param animationManager Animates the threshold change if one is passed.
- * @return The last passed threshold, one of
- * {@link PinchThresholdManager#THRESHOLD_ZERO},
- * {@link PinchThresholdManager#THRESHOLD_ONE},
- * {@link PinchThresholdManager#THRESHOLD_TWO}, or
- * {@link PinchThresholdManager#THRESHOLD_THREE}
- */
- public float updateAndAnimatePassedThreshold(float progress,
- PinchAnimationManager animationManager) {
- if (!mWorkspace.isInOverviewMode()) {
- // Invert the progress, because going from workspace to overview is 1 to 0.
- progress = 1f - progress;
- }
-
- float previousPassedThreshold = mPassedThreshold;
-
- if (progress < THRESHOLD_ONE) {
- mPassedThreshold = THRESHOLD_ZERO;
- } else if (progress < THRESHOLD_TWO) {
- mPassedThreshold = THRESHOLD_ONE;
- } else if (progress < THRESHOLD_THREE) {
- mPassedThreshold = THRESHOLD_TWO;
- } else {
- mPassedThreshold = THRESHOLD_THREE;
- }
-
- if (mPassedThreshold != previousPassedThreshold) {
- LauncherState fromState = mWorkspace.isInOverviewMode() ? LauncherState.OVERVIEW
- : LauncherState.NORMAL;
- LauncherState toState = mWorkspace.isInOverviewMode() ? LauncherState.NORMAL
- : LauncherState.OVERVIEW;
- float thresholdToAnimate = mPassedThreshold;
- if (mPassedThreshold < previousPassedThreshold) {
- // User reversed pinch, so heading back to the state that they started from.
- toState = fromState;
- thresholdToAnimate = previousPassedThreshold;
- }
- animationManager.animateThreshold(thresholdToAnimate, fromState, toState);
- }
- return mPassedThreshold;
- }
-
- public float getPassedThreshold() {
- return mPassedThreshold;
- }
-
- public void reset() {
- mPassedThreshold = THRESHOLD_ZERO;
- }
-}
diff --git a/src/com/android/launcher3/PinchToOverviewListener.java b/src/com/android/launcher3/PinchToOverviewListener.java
deleted file mode 100644
index 2a5899c..0000000
--- a/src/com/android/launcher3/PinchToOverviewListener.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Copyright (C) 2016 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;
-
-import android.animation.TimeInterpolator;
-import android.content.Context;
-import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
-
-import com.android.launcher3.util.TouchController;
-
-/**
- * Detects pinches and animates the Workspace to/from overview mode.
- *
- * Usage: Pass MotionEvents to onInterceptTouchEvent() and onTouchEvent(). This class will handle
- * the pinch detection, and use {@link PinchAnimationManager} to handle the animations.
- *
- * @see PinchThresholdManager
- * @see PinchAnimationManager
- */
-public class PinchToOverviewListener extends ScaleGestureDetector.SimpleOnScaleGestureListener
- implements TouchController {
- private static final float OVERVIEW_PROGRESS = 0f;
- private static final float WORKSPACE_PROGRESS = 1f;
- /**
- * The velocity threshold at which a pinch will be completed instead of canceled,
- * even if the first threshold has not been passed. Measured in progress / millisecond
- */
- private static final float FLING_VELOCITY = 0.003f;
-
- private ScaleGestureDetector mPinchDetector;
- private Launcher mLauncher;
- private Workspace mWorkspace = null;
- private boolean mPinchStarted = false;
- private float mPreviousProgress;
- private float mProgressDelta;
- private long mPreviousTimeMillis;
- private long mTimeDelta;
- private boolean mPinchCanceled = false;
- private TimeInterpolator mInterpolator;
-
- private PinchThresholdManager mThresholdManager;
- private PinchAnimationManager mAnimationManager;
-
- public PinchToOverviewListener(Launcher launcher) {
- mLauncher = launcher;
- mPinchDetector = new ScaleGestureDetector((Context) mLauncher, this);
- }
-
- public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
- mPinchDetector.onTouchEvent(ev);
- return mPinchStarted;
- }
-
- public boolean onControllerTouchEvent(MotionEvent ev) {
- if (mPinchStarted) {
- if (ev.getPointerCount() > 2) {
- // Using more than two fingers causes weird behavior, so just cancel the pinch.
- cancelPinch(mPreviousProgress, -1);
- } else {
- return mPinchDetector.onTouchEvent(ev);
- }
- }
- return false;
- }
-
- @Override
- public boolean onScaleBegin(ScaleGestureDetector detector) {
- if (mLauncher.mState != Launcher.State.WORKSPACE) {
- // Don't listen for the pinch gesture if on all apps, widget picker, -1, etc.
- return false;
- }
- if (mAnimationManager != null && mAnimationManager.isAnimating()) {
- // Don't listen for the pinch gesture if we are already animating from a previous one.
- return false;
- }
- if (mLauncher.isWorkspaceLocked()) {
- // Don't listen for the pinch gesture if the workspace isn't ready.
- return false;
- }
- if (mWorkspace == null) {
- mWorkspace = mLauncher.getWorkspace();
- mThresholdManager = new PinchThresholdManager(mWorkspace);
- mAnimationManager = new PinchAnimationManager(mLauncher);
- }
- if (mWorkspace.isSwitchingState() || mWorkspace.mScrollInteractionBegan) {
- // Don't listen for the pinch gesture while switching state, as it will cause a jump
- // once the state switching animation is complete.
- return false;
- }
- if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
- // Don't listen for the pinch gesture if a floating view is open.
- return false;
- }
-
- mPreviousProgress = mWorkspace.isInOverviewMode() ? OVERVIEW_PROGRESS : WORKSPACE_PROGRESS;
- mPreviousTimeMillis = System.currentTimeMillis();
- mInterpolator = mWorkspace.isInOverviewMode() ? new LogDecelerateInterpolator(100, 0)
- : new LogAccelerateInterpolator(100, 0);
- mPinchStarted = true;
- mWorkspace.onPrepareStateTransition(true);
- return true;
- }
-
- @Override
- public void onScaleEnd(ScaleGestureDetector detector) {
- super.onScaleEnd(detector);
-
- float progressVelocity = mProgressDelta / mTimeDelta;
- float passedThreshold = mThresholdManager.getPassedThreshold();
- boolean isFling = mWorkspace.isInOverviewMode() && progressVelocity >= FLING_VELOCITY
- || !mWorkspace.isInOverviewMode() && progressVelocity <= -FLING_VELOCITY;
- boolean shouldCancelPinch = !isFling && passedThreshold < PinchThresholdManager.THRESHOLD_ONE;
- // If we are going towards overview, mPreviousProgress is how much further we need to
- // go, since it is going from 1 to 0. If we are going to workspace, we want
- // 1 - mPreviousProgress.
- float remainingProgress = mPreviousProgress;
- if (mWorkspace.isInOverviewMode() || shouldCancelPinch) {
- remainingProgress = 1f - mPreviousProgress;
- }
- int duration = computeDuration(remainingProgress, progressVelocity);
- if (shouldCancelPinch) {
- cancelPinch(mPreviousProgress, duration);
- } else if (passedThreshold < PinchThresholdManager.THRESHOLD_THREE) {
- float toProgress = mWorkspace.isInOverviewMode() ?
- WORKSPACE_PROGRESS : OVERVIEW_PROGRESS;
- mAnimationManager.animateToProgress(mPreviousProgress, toProgress, duration,
- mThresholdManager);
- } else {
- mThresholdManager.reset();
- mWorkspace.onEndStateTransition();
- }
- mPinchStarted = false;
- mPinchCanceled = false;
- }
-
- /**
- * Compute the amount of time required to complete the transition based on the current pinch
- * speed. If this time is too long, instead return the normal duration, ignoring the speed.
- */
- private int computeDuration(float remainingProgress, float progressVelocity) {
- float progressSpeed = Math.abs(progressVelocity);
- int remainingMillis = (int) (remainingProgress / progressSpeed);
- return Math.min(remainingMillis, mAnimationManager.getNormalOverviewTransitionDuration());
- }
-
- /**
- * Cancels the current pinch, returning back to where the pinch started (either workspace or
- * overview). If duration is -1, the default overview transition duration is used.
- */
- private void cancelPinch(float currentProgress, int duration) {
- if (mPinchCanceled) return;
- mPinchCanceled = true;
- float toProgress = mWorkspace.isInOverviewMode() ? OVERVIEW_PROGRESS : WORKSPACE_PROGRESS;
- mAnimationManager.animateToProgress(currentProgress, toProgress, duration,
- mThresholdManager);
- mPinchStarted = false;
- }
-
- @Override
- public boolean onScale(ScaleGestureDetector detector) {
- if (mThresholdManager.getPassedThreshold() == PinchThresholdManager.THRESHOLD_THREE) {
- // We completed the pinch, so stop listening to further movement until user lets go.
- return true;
- }
- if (mLauncher.getDragController().isDragging()) {
- mLauncher.getDragController().cancelDrag();
- }
-
- float pinchDist = detector.getCurrentSpan() - detector.getPreviousSpan();
- if (pinchDist < 0 && mWorkspace.isInOverviewMode() ||
- pinchDist > 0 && !mWorkspace.isInOverviewMode()) {
- // Pinching the wrong way, so ignore.
- return false;
- }
- // Pinch distance must equal the workspace width before switching states.
- int pinchDistanceToCompleteTransition = mWorkspace.getWidth();
- float overviewScale = mWorkspace.getOverviewModeShrinkFactor();
- float initialWorkspaceScale = mWorkspace.isInOverviewMode() ? overviewScale : 1f;
- float pinchScale = initialWorkspaceScale + pinchDist / pinchDistanceToCompleteTransition;
- // Bound the scale between the overview scale and the normal workspace scale (1f).
- pinchScale = Math.max(overviewScale, Math.min(pinchScale, 1f));
- // Progress ranges from 0 to 1, where 0 corresponds to the overview scale and 1
- // corresponds to the normal workspace scale (1f).
- float progress = (pinchScale - overviewScale) / (1f - overviewScale);
- float interpolatedProgress = mInterpolator.getInterpolation(progress);
-
- mAnimationManager.setAnimationProgress(interpolatedProgress);
- float passedThreshold = mThresholdManager.updateAndAnimatePassedThreshold(
- interpolatedProgress, mAnimationManager);
- if (passedThreshold == PinchThresholdManager.THRESHOLD_THREE) {
- return true;
- }
-
- mProgressDelta = interpolatedProgress - mPreviousProgress;
- mPreviousProgress = interpolatedProgress;
- mTimeDelta = System.currentTimeMillis() - mPreviousTimeMillis;
- mPreviousTimeMillis = System.currentTimeMillis();
- return false;
- }
-}
\ No newline at end of file
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index adf008b..ec608ca 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -65,13 +65,6 @@
public static final int FLAG_SUPPORTS_WEB_UI = 16; //0B10000;
/**
- * Indicates if it represents a common type mentioned in {@link CommonAppTypeParser}.
- * Upto 15 different types supported.
- */
- @Deprecated
- public static final int FLAG_RESTORED_APP_TYPE = 0B0011110000;
-
- /**
* The intent used to start the application.
*/
public Intent intent;
@@ -83,42 +76,6 @@
public Intent.ShortcutIconResource iconResource;
/**
- * Indicates that the icon is disabled due to safe mode restrictions.
- */
- public static final int FLAG_DISABLED_SAFEMODE = 1 << 0;
-
- /**
- * Indicates that the icon is disabled as the app is not available.
- */
- public static final int FLAG_DISABLED_NOT_AVAILABLE = 1 << 1;
-
- /**
- * Indicates that the icon is disabled as the app is suspended
- */
- public static final int FLAG_DISABLED_SUSPENDED = 1 << 2;
-
- /**
- * Indicates that the icon is disabled as the user is in quiet mode.
- */
- public static final int FLAG_DISABLED_QUIET_USER = 1 << 3;
-
- /**
- * Indicates that the icon is disabled as the publisher has disabled the actual shortcut.
- */
- public static final int FLAG_DISABLED_BY_PUBLISHER = 1 << 4;
-
- /**
- * Indicates that the icon is disabled as the user partition is currently locked.
- */
- public static final int FLAG_DISABLED_LOCKED_USER = 1 << 5;
-
- /**
- * Could be disabled, if the the app is installed but unavailable (eg. in safe mode or when
- * sd-card is not available).
- */
- public int isDisabled = DEFAULT;
-
- /**
* A message to display when the user tries to start a disabled shortcut.
* This is currently only used for deep shortcuts.
*/
@@ -142,7 +99,6 @@
iconResource = info.iconResource;
status = info.status;
mInstallProgress = info.mInstallProgress;
- isDisabled = info.isDisabled;
}
/** TODO: Remove this. It's only called by ApplicationInfo.makeShortcut. */
@@ -150,7 +106,6 @@
super(info);
title = Utilities.trim(info.title);
intent = new Intent(info.intent);
- isDisabled = info.isDisabled;
}
/**
@@ -219,9 +174,9 @@
contentDescription = UserManagerCompat.getInstance(context)
.getBadgedLabelForUser(label, user);
if (shortcutInfo.isEnabled()) {
- isDisabled &= ~FLAG_DISABLED_BY_PUBLISHER;
+ runtimeStatusFlags &= ~FLAG_DISABLED_BY_PUBLISHER;
} else {
- isDisabled |= FLAG_DISABLED_BY_PUBLISHER;
+ runtimeStatusFlags |= FLAG_DISABLED_BY_PUBLISHER;
}
disabledMessage = shortcutInfo.getDisabledMessage();
}
@@ -233,11 +188,6 @@
}
@Override
- public boolean isDisabled() {
- return isDisabled != 0;
- }
-
- @Override
public ComponentName getTargetComponent() {
ComponentName cn = super.getTargetComponent();
if (cn == null && (itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index 4393819..68a441a 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -1,5 +1,8 @@
package com.android.launcher3;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_MASK;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_NO;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -10,22 +13,28 @@
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.android.launcher3.Launcher.OnResumeCallback;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import java.net.URISyntaxException;
-public class UninstallDropTarget extends ButtonDropTarget {
+public class UninstallDropTarget extends ButtonDropTarget implements OnAlarmListener {
private static final String TAG = "UninstallDropTarget";
- private static Boolean sUninstallDisabled;
+
+ private static final long CACHE_EXPIRE_TIMEOUT = 5000;
+ private final ArrayMap<UserHandle, Boolean> mUninstallDisabledCache = new ArrayMap<>(1);
+
+ private final Alarm mCacheExpireAlarm;
public UninstallDropTarget(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -33,6 +42,9 @@
public UninstallDropTarget(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+
+ mCacheExpireAlarm = new Alarm();
+ mCacheExpireAlarm.setOnAlarmListener(this);
}
@Override
@@ -48,34 +60,45 @@
}
@Override
- protected boolean supportsDrop(ItemInfo info) {
- return supportsDrop(getContext(), info);
+ public void onAlarm(Alarm alarm) {
+ mUninstallDisabledCache.clear();
}
- public static boolean supportsDrop(Context context, ItemInfo info) {
- if (sUninstallDisabled == null) {
- UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
- Bundle restrictions = userManager.getUserRestrictions();
- sUninstallDisabled = restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false)
+ @Override
+ public int getAccessibilityAction() {
+ return LauncherAccessibilityDelegate.UNINSTALL;
+ }
+
+ @Override
+ protected boolean supportsDrop(ItemInfo info) {
+ Boolean uninstallDisabled = mUninstallDisabledCache.get(info.user);
+ if (uninstallDisabled == null) {
+ UserManager userManager =
+ (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+ Bundle restrictions = userManager.getUserRestrictions(info.user);
+ uninstallDisabled = restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false)
|| restrictions.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS, false);
+ mUninstallDisabledCache.put(info.user, uninstallDisabled);
}
- if (sUninstallDisabled) {
+ // Cancel any pending alarm and set cache expiry after some time
+ mCacheExpireAlarm.setAlarm(CACHE_EXPIRE_TIMEOUT);
+ if (uninstallDisabled) {
return false;
}
- if (info instanceof AppInfo) {
- AppInfo appInfo = (AppInfo) info;
- if (appInfo.isSystemApp != AppInfo.FLAG_SYSTEM_UNKNOWN) {
- return (appInfo.isSystemApp & AppInfo.FLAG_SYSTEM_NO) != 0;
+ if (info instanceof ItemInfoWithIcon) {
+ ItemInfoWithIcon iconInfo = (ItemInfoWithIcon) info;
+ if ((iconInfo.runtimeStatusFlags & FLAG_SYSTEM_MASK) != 0) {
+ return (iconInfo.runtimeStatusFlags & FLAG_SYSTEM_NO) != 0;
}
}
- return getUninstallTarget(context, info) != null;
+ return getUninstallTarget(info) != null;
}
/**
* @return the component name that should be uninstalled or null.
*/
- private static ComponentName getUninstallTarget(Context context, ItemInfo item) {
+ private ComponentName getUninstallTarget(ItemInfo item) {
Intent intent = null;
UserHandle user = null;
if (item != null &&
@@ -84,7 +107,7 @@
user = item.user;
}
if (intent != null) {
- LauncherActivityInfo info = LauncherAppsCompat.getInstance(context)
+ LauncherActivityInfo info = LauncherAppsCompat.getInstance(mLauncher)
.resolveActivity(intent, user);
if (info != null
&& (info.getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
@@ -103,7 +126,7 @@
@Override
public void completeDrop(final DragObject d) {
- ComponentName target = performDropAction(d);
+ ComponentName target = performDropAction(d.dragInfo);
if (d.dragSource instanceof DeferredOnComplete) {
DeferredOnComplete deferred = (DeferredOnComplete) d.dragSource;
if (target != null) {
@@ -119,27 +142,19 @@
* Performs the drop action and returns the target component for the dragObject or null if
* the action was not performed.
*/
- protected ComponentName performDropAction(DragObject d) {
- return performDropAction(mLauncher, d.dragInfo);
- }
-
- /**
- * Performs the drop action and returns the target component for the dragObject or null if
- * the action was not performed.
- */
- private static ComponentName performDropAction(Context context, ItemInfo info) {
- ComponentName cn = getUninstallTarget(context, info);
+ protected ComponentName performDropAction(ItemInfo info) {
+ ComponentName cn = getUninstallTarget(info);
if (cn == null) {
// System applications cannot be installed. For now, show a toast explaining that.
// We may give them the option of disabling apps this way.
- Toast.makeText(context, R.string.uninstall_system_app_text, Toast.LENGTH_SHORT).show();
+ Toast.makeText(mLauncher, R.string.uninstall_system_app_text, Toast.LENGTH_SHORT).show();
return null;
}
try {
- Intent i = Intent.parseUri(context.getString(R.string.delete_package_intent), 0)
+ Intent i = Intent.parseUri(mLauncher.getString(R.string.delete_package_intent), 0)
.setData(Uri.fromParts("package", cn.getPackageName(), cn.getClassName()))
.putExtra(Intent.EXTRA_USER, info.user);
- context.startActivity(i);
+ mLauncher.startActivity(i);
return cn;
} catch (URISyntaxException e) {
Log.e(TAG, "Failed to parse intent to start uninstall activity for item=" + info);
@@ -147,8 +162,9 @@
}
}
- public static boolean startUninstallActivity(Launcher launcher, ItemInfo info) {
- return performDropAction(launcher, info) != null;
+ @Override
+ public void onAccessibilityDrop(View view, ItemInfo item) {
+ performDropAction(item);
}
/**
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 7167778..e6bc770 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -614,6 +614,12 @@
return c == null || c.isEmpty();
}
+ public static boolean isAccessibilityEnabled(Context context) {
+ AccessibilityManager accessibilityManager = (AccessibilityManager)
+ context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ return accessibilityManager.isEnabled();
+ }
+
public static void sendCustomAccessibilityEvent(View target, int type, String text) {
AccessibilityManager accessibilityManager = (AccessibilityManager)
target.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
diff --git a/src/com/android/launcher3/VerticalSwipeController.java b/src/com/android/launcher3/VerticalSwipeController.java
new file mode 100644
index 0000000..b3dc176
--- /dev/null
+++ b/src/com/android/launcher3/VerticalSwipeController.java
@@ -0,0 +1,282 @@
+/*
+ * 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;
+
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.anim.SpringAnimationHandler.Y_DIRECTION;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.support.animation.SpringAnimation;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.SpringAnimationHandler;
+import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.util.TouchController;
+
+import java.util.ArrayList;
+
+/**
+ * Handles vertical touch gesture on the DragLayer
+ */
+public class VerticalSwipeController extends AnimatorListenerAdapter
+ implements TouchController, SwipeDetector.Listener {
+
+ private static final String TAG = "VerticalSwipeController";
+
+ private static final float RECATCH_REJECTION_FRACTION = .0875f;
+ private static final int SINGLE_FRAME_MS = 16;
+
+ // Progress after which the transition is assumed to be a success in case user does not fling
+ private static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
+
+ private final Launcher mLauncher;
+ private final SwipeDetector mDetector;
+
+ private boolean mNoIntercept;
+ private int mStartContainerType;
+
+ private AnimatorPlaybackController mCurrentAnimation;
+ private LauncherState mToState;
+
+ private float mStartProgress;
+ // Ratio of transition process [0, 1] to drag displacement (px)
+ private float mProgressMultiplier;
+
+ private SpringAnimationHandler[] mSpringHandlers;
+
+ public VerticalSwipeController(Launcher l) {
+ mLauncher = l;
+ mDetector = new SwipeDetector(l, this, SwipeDetector.VERTICAL);
+ }
+
+ private boolean canInterceptTouch(MotionEvent ev) {
+ if (!mLauncher.isInState(NORMAL) && !mLauncher.isInState(ALL_APPS)) {
+ // Don't listen for the swipe gesture if we are already in some other state.
+ return false;
+ }
+ if (mCurrentAnimation != null) {
+ // If we are already animating from a previous state, we can intercept.
+ return true;
+ }
+ if (mLauncher.isInState(ALL_APPS) && !mLauncher.getAppsView().shouldContainerScroll(ev)) {
+ return false;
+ }
+ if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ if (mCurrentAnimation != null && animation == mCurrentAnimation.getTarget()) {
+ Log.e(TAG, "Who dare cancel the animation when I am in control", new Exception());
+ mDetector.finishedScrolling();
+ mCurrentAnimation = null;
+ }
+ }
+
+ private void initSprings() {
+ AllAppsContainerView appsView = mLauncher.getAppsView();
+
+ SpringAnimationHandler handler = appsView.getSpringAnimationHandler();
+ if (handler == null) {
+ mSpringHandlers = new SpringAnimationHandler[0];
+ return;
+ }
+
+ ArrayList<SpringAnimationHandler> handlers = new ArrayList<>();
+ handlers.add(handler);
+
+ SpringAnimation searchSpring = appsView.getSearchUiManager().getSpringForFling();
+ if (searchSpring != null) {
+ SpringAnimationHandler searchHandler =
+ new SpringAnimationHandler(Y_DIRECTION, handler.getFactory());
+ searchHandler.add(searchSpring, true /* setDefaultValues */);
+ handlers.add(searchHandler);
+ }
+
+ mSpringHandlers = handlers.toArray(new SpringAnimationHandler[handlers.size()]);
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mNoIntercept = !canInterceptTouch(ev);
+ if (mNoIntercept) {
+ return false;
+ }
+
+ // Now figure out which direction scroll events the controller will start
+ // calling the callbacks.
+ final int directionsToDetectScroll;
+ boolean ignoreSlopWhenSettling = false;
+
+ if (mCurrentAnimation != null) {
+ if (mCurrentAnimation.getProgressFraction() > 1 - RECATCH_REJECTION_FRACTION) {
+ directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+ } else if (mCurrentAnimation.getProgressFraction() < RECATCH_REJECTION_FRACTION ) {
+ directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
+ } else {
+ directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+ ignoreSlopWhenSettling = true;
+ }
+ } else {
+ if (mLauncher.isInState(ALL_APPS)) {
+ directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
+ mStartContainerType = ContainerType.ALLAPPS;
+ } else {
+ directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+ mStartContainerType = mLauncher.getDragLayer().isEventOverHotseat(ev) ?
+ ContainerType.HOTSEAT : ContainerType.WORKSPACE;
+ }
+ }
+
+ mDetector.setDetectableScrollConditions(
+ directionsToDetectScroll, ignoreSlopWhenSettling);
+
+ if (mSpringHandlers == null) {
+ initSprings();
+ }
+ }
+
+ if (mNoIntercept) {
+ return false;
+ }
+
+ onControllerTouchEvent(ev);
+ return mDetector.isDraggingOrSettling();
+ }
+
+ @Override
+ public boolean onControllerTouchEvent(MotionEvent ev) {
+ for (SpringAnimationHandler h : mSpringHandlers) {
+ h.addMovement(ev);
+ }
+ return mDetector.onTouchEvent(ev);
+ }
+
+ @Override
+ public void onDragStart(boolean start) {
+ if (mCurrentAnimation == null) {
+ float range = getShiftRange();
+ long maxAccuracy = (long) (2 * range);
+
+ // Build current animation
+ mToState = mLauncher.isInState(ALL_APPS) ? NORMAL : ALL_APPS;
+ mCurrentAnimation = mLauncher.getStateManager()
+ .createAnimationToNewWorkspace(mToState, maxAccuracy);
+ mCurrentAnimation.getTarget().addListener(this);
+ mStartProgress = 0;
+ mProgressMultiplier = (mLauncher.isInState(ALL_APPS) ? 1 : -1) / range;
+ mCurrentAnimation.dispatchOnStart();
+ } else {
+ mCurrentAnimation.pause();
+ mStartProgress = mCurrentAnimation.getProgressFraction();
+ }
+
+ for (SpringAnimationHandler h : mSpringHandlers) {
+ h.skipToEnd();
+ }
+ }
+
+ private float getShiftRange() {
+ return mLauncher.mAllAppsController.getShiftRange();
+ }
+
+ @Override
+ public boolean onDrag(float displacement, float velocity) {
+ float deltaProgress = mProgressMultiplier * displacement;
+ mCurrentAnimation.setPlayFraction(deltaProgress + mStartProgress);
+ return true;
+ }
+
+ @Override
+ public void onDragEnd(float velocity, boolean fling) {
+ final long animationDuration;
+ final int logAction;
+ final LauncherState targetState;
+ final float progress = mCurrentAnimation.getProgressFraction();
+
+ if (fling) {
+ logAction = Touch.FLING;
+ if (velocity < 0) {
+ targetState = ALL_APPS;
+ animationDuration = SwipeDetector.calculateDuration(velocity,
+ mToState == ALL_APPS ? (1 - progress) : progress);
+ } else {
+ targetState = NORMAL;
+ animationDuration = SwipeDetector.calculateDuration(velocity,
+ mToState == ALL_APPS ? progress : (1 - progress));
+ }
+ // snap to top or bottom using the release velocity
+ } else {
+ logAction = Touch.SWIPE;
+ if (progress > SUCCESS_TRANSITION_PROGRESS) {
+ targetState = mToState;
+ animationDuration = SwipeDetector.calculateDuration(velocity, 1 - progress);
+ } else {
+ targetState = mToState == ALL_APPS ? NORMAL : ALL_APPS;
+ animationDuration = SwipeDetector.calculateDuration(velocity, progress);
+ }
+ }
+
+ if (fling && targetState == ALL_APPS) {
+ for (SpringAnimationHandler h : mSpringHandlers) {
+ // The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
+ h.animateToFinalPosition(0 /* pos */, 1 /* startValue */);
+ }
+ }
+
+ mCurrentAnimation.setEndAction(new Runnable() {
+ @Override
+ public void run() {
+ if (targetState == mToState) {
+ // Transition complete. log the action
+ mLauncher.getUserEventDispatcher().logActionOnContainer(logAction,
+ mToState == ALL_APPS ? Direction.UP : Direction.DOWN,
+ mStartContainerType, mLauncher.getWorkspace().getCurrentPage());
+ } else {
+ mLauncher.getStateManager().goToState(
+ mToState == ALL_APPS ? NORMAL : ALL_APPS, false);
+ }
+ mDetector.finishedScrolling();
+ mCurrentAnimation = null;
+ }
+ });
+
+ float nextFrameProgress = Utilities.boundToRange(
+ progress + velocity * SINGLE_FRAME_MS / getShiftRange(), 0f, 1f);
+
+ ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
+ anim.setFloatValues(nextFrameProgress, targetState == mToState ? 1f : 0f);
+ anim.setDuration(animationDuration);
+ anim.setInterpolator(scrollInterpolatorForVelocity(velocity));
+ anim.start();
+ }
+}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 213eaad..0db5a16 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -16,10 +16,13 @@
package com.android.launcher3;
-import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_NEXT_FRAME;
-import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
+import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_TRANSITION_MS;
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.SPRING_LOADED;
+import static com.android.launcher3.Utilities.isAccessibilityEnabled;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -51,22 +54,17 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewDebug;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Interpolator;
import android.widget.Toast;
import com.android.launcher3.Launcher.LauncherOverlay;
import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
-import com.android.launcher3.LauncherStateTransitionAnimation.AnimationConfig;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
-import com.android.launcher3.accessibility.OverviewAccessibilityDelegate;
import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate;
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
import com.android.launcher3.anim.AnimationLayerSet;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.badge.FolderBadgeInfo;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.config.FeatureFlags;
@@ -82,6 +80,7 @@
import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
+import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -105,7 +104,7 @@
public class Workspace extends PagedView
implements DropTarget, DragSource, View.OnTouchListener,
DragController.DragListener, ViewGroup.OnHierarchyChangeListener,
- Insettable {
+ Insettable, LauncherStateManager.StateHandler {
private static final String TAG = "Launcher.Workspace";
/** The value that {@link #mTransitionProgress} must be greater than for
@@ -173,16 +172,11 @@
@Thunk final Launcher mLauncher;
@Thunk DragController mDragController;
- // These are temporary variables to prevent having to allocate a new object just to
- // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
- private static final Rect sTempRect = new Rect();
-
private final int[] mTempXY = new int[2];
@Thunk float[] mDragViewVisualCenter = new float[2];
private final float[] mTempTouchCoordinates = new float[2];
private SpringLoadedDragController mSpringLoadedDragController;
- private final float mOverviewModeShrinkFactor;
// Direction used for moving the workspace and hotseat UI
public enum Direction {
@@ -209,8 +203,6 @@
*/
private final float[] mHotseatAlpha = new float[] {1, 1, 1};
- @ViewDebug.ExportedProperty(category = "launcher")
- private LauncherState mState = LauncherState.NORMAL;
private boolean mIsSwitchingState = false;
boolean mChildrenLayersEnabled = true;
@@ -271,6 +263,8 @@
boolean mOverlayShown = false;
private boolean mForceDrawAdjacentPages = false;
+ private boolean mPageRearrangeEnabled = false;
+
// Total over scrollX in the overlay direction.
private float mOverlayTranslation;
@@ -301,14 +295,11 @@
mLauncher = Launcher.getLauncher(context);
mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this);
- final Resources res = getResources();
DeviceProfile grid = mLauncher.getDeviceProfile();
mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
mWallpaperManager = WallpaperManager.getInstance(context);
mWallpaperOffset = new WallpaperOffsetInterpolator(this);
- mOverviewModeShrinkFactor =
- res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100f;
setOnHierarchyChangeListener(this);
setHapticFeedbackEnabled(false);
@@ -416,7 +407,7 @@
}
// Always enter the spring loaded mode
- mLauncher.enterSpringLoadedDragMode();
+ mLauncher.getStateManager().goToState(SPRING_LOADED);
}
public void deferRemoveExtraEmptyScreen() {
@@ -448,9 +439,7 @@
setClipChildren(false);
setClipToPadding(false);
- setMinScale(mOverviewModeShrinkFactor);
setupLayoutTransition();
-
mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx);
// Set the wallpaper dimensions when Launcher starts up
@@ -460,7 +449,7 @@
@Override
public void initParentViews(View parent) {
super.initParentViews(parent);
- mPageIndicator.setAccessibilityDelegate(new OverviewAccessibilityDelegate());
+ mPageIndicator.setAccessibilityDelegate(UiFactory.newPageIndicatorAccessibilityDelegate());
}
private void setupLayoutTransition() {
@@ -578,6 +567,8 @@
mWorkspaceScreens.put(screenId, newScreen);
mScreenOrder.add(insertIndex, screenId);
addView(newScreen, insertIndex);
+ mStateTransitionAnimation.applyChildState(
+ mLauncher.getStateManager().getState(), newScreen, insertIndex);
if (mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
newScreen.enableAccessibleDrag(true, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
@@ -1126,7 +1117,7 @@
enableHwLayersOnVisiblePages();
}
- private void showPageIndicatorAtCurrentScroll() {
+ public void showPageIndicatorAtCurrentScroll() {
if (mPageIndicator != null) {
mPageIndicator.setScroll(getScrollX(), computeMaxScrollX());
}
@@ -1164,8 +1155,6 @@
super.shouldFlingForVelocity(velocityX);
}
- private final Interpolator mAlphaInterpolator = new DecelerateInterpolator(3f);
-
/**
* The overlay scroll is being controlled locally, just update our overlay effect
*/
@@ -1190,7 +1179,7 @@
scroll = Math.max(scroll - offset, 0);
scroll = Math.min(1, scroll / (1 - offset));
- float alpha = 1 - mAlphaInterpolator.getInterpolation(scroll);
+ float alpha = 1 - Interpolators.DEACCEL_3.getInterpolation(scroll);
float transX = mLauncher.getDragLayer().getMeasuredWidth() * scroll;
transX *= 1 - slip;
@@ -1282,7 +1271,7 @@
}
});
- final boolean accessibilityEnabled = isAccessibilityEnabled();
+ final boolean accessibilityEnabled = isAccessibilityEnabled(mLauncher);
animator.addUpdateListener(
new AlphaUpdateListener(mLauncher.getHotseat(), accessibilityEnabled));
animator.addUpdateListener(
@@ -1291,12 +1280,6 @@
}
}
- protected boolean isAccessibilityEnabled() {
- AccessibilityManager am = (AccessibilityManager)
- mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
- return am.isEnabled();
- }
-
@Override
protected void notifyPageSwitchListener(int prevPage) {
super.notifyPageSwitchListener(prevPage);
@@ -1351,7 +1334,7 @@
@Override
public void announceForAccessibility(CharSequence text) {
// Don't announce if apps is on top of us.
- if (!mLauncher.isAppsViewVisible()) {
+ if (!mLauncher.isInState(ALL_APPS)) {
super.announceForAccessibility(text);
}
}
@@ -1418,18 +1401,18 @@
return super.getDescendantFocusability();
}
- public boolean workspaceInModalState() {
- return mState != LauncherState.NORMAL;
+ private boolean workspaceInModalState() {
+ return !mLauncher.isInState(NORMAL);
}
/** Returns whether a drag should be allowed to be started from the current workspace state. */
public boolean workspaceIconsCanBeDragged() {
- return mState == LauncherState.NORMAL || mState == LauncherState.SPRING_LOADED;
+ return mLauncher.isInState(NORMAL) || mLauncher.isInState(SPRING_LOADED);
}
private void updateChildrenLayersEnabled() {
- boolean small = mState == LauncherState.OVERVIEW || mIsSwitchingState;
- boolean enableChildrenLayers = small || isPageInTransition();
+ boolean enableChildrenLayers =
+ isPageRearrangeEnabled() || mIsSwitchingState || isPageInTransition();
if (enableChildrenLayers != mChildrenLayersEnabled) {
mChildrenLayersEnabled = enableChildrenLayers;
@@ -1547,110 +1530,73 @@
enableLayoutTransitions();
}
- public boolean isInOverviewMode() {
- return mState == LauncherState.OVERVIEW;
- }
-
public void snapToPageFromOverView(int whichPage) {
- snapToPage(whichPage, OVERVIEW_TRANSITION_MS, new ZoomInInterpolator());
+ snapToPage(whichPage, OVERVIEW_TRANSITION_MS, Interpolators.ZOOM_IN);
}
- int getOverviewModeTranslationY() {
- DeviceProfile grid = mLauncher.getDeviceProfile();
- int overviewButtonBarHeight = grid.getOverviewModeButtonBarHeight();
+ private void onStartStateTransition(LauncherState state) {
+ mIsSwitchingState = true;
+ mTransitionProgress = 0;
- int scaledHeight = (int) (mOverviewModeShrinkFactor * getNormalChildHeight());
- Rect workspacePadding = grid.getWorkspacePadding(sTempRect);
- int workspaceTop = mInsets.top + workspacePadding.top;
- int workspaceBottom = getViewportHeight() - mInsets.bottom - workspacePadding.bottom;
- int overviewTop = mInsets.top;
- int overviewBottom = getViewportHeight() - mInsets.bottom - overviewButtonBarHeight;
- int workspaceOffsetTopEdge = workspaceTop + ((workspaceBottom - workspaceTop) - scaledHeight) / 2;
- int overviewOffsetTopEdge = overviewTop + (overviewBottom - overviewTop - scaledHeight) / 2;
- return -workspaceOffsetTopEdge + overviewOffsetTopEdge;
+ updateChildrenLayersEnabled();
}
- float getSpringLoadedTranslationY() {
- DeviceProfile grid = mLauncher.getDeviceProfile();
- if (grid.isVerticalBarLayout() || getChildCount() == 0) {
- return 0;
- }
+ private void onEndStateTransition() {
+ mIsSwitchingState = false;
+ mForceDrawAdjacentPages = false;
+ mTransitionProgress = 1;
- float scaledHeight = grid.workspaceSpringLoadShrinkFactor * getNormalChildHeight();
- float shrunkTop = mInsets.top + grid.dropTargetBarSizePx;
- float shrunkBottom = getViewportHeight() - mInsets.bottom
- - grid.getWorkspacePadding(sTempRect).bottom
- - grid.workspaceSpringLoadedBottomSpace;
- float totalShrunkSpace = shrunkBottom - shrunkTop;
-
- float desiredCellTop = shrunkTop + (totalShrunkSpace - scaledHeight) / 2;
-
- float halfHeight = getHeight() / 2;
- float myCenter = getTop() + halfHeight;
- float cellTopFromCenter = halfHeight - getChildAt(0).getTop();
- float actualCellTop = myCenter - cellTopFromCenter * grid.workspaceSpringLoadShrinkFactor;
- return (desiredCellTop - actualCellTop) / grid.workspaceSpringLoadShrinkFactor;
+ updateChildrenLayersEnabled();
+ updateAccessibilityFlags();
}
- float getOverviewModeShrinkFactor() {
- return mOverviewModeShrinkFactor;
- }
-
-
/**
* Sets the current workspace {@link LauncherState} and updates the UI without any animations
*/
+ @Override
public void setState(LauncherState toState) {
- // Update the current state
- mState = toState;
- mStateTransitionAnimation.setState(mState);
-
- updateAccessibilityFlags();
- onPrepareStateTransition(mState.hasMultipleVisiblePages);
- onStartStateTransition();
+ onStartStateTransition(toState);
+ mStateTransitionAnimation.setState(toState);
onEndStateTransition();
}
/**
* Sets the current workspace {@link LauncherState}, then animates the UI
*/
+ @Override
public void setStateWithAnimation(LauncherState toState, AnimationLayerSet layerViews,
AnimatorSet anim, AnimationConfig config) {
- final LauncherState fromState = mState;
+ StateTransitionListener listener = new StateTransitionListener(toState);
+ mStateTransitionAnimation.setStateWithAnimation(toState, anim, layerViews, config);
- // Update the current state
- mState = toState;
- mStateTransitionAnimation.setStateWithAnimation(
- fromState, toState, anim, layerViews, config);
+ // Invalidate the pages now, so that we have the visible pages before the
+ // animation is started
+ if (toState.hasMultipleVisiblePages) {
+ mForceDrawAdjacentPages = true;
+ }
+ invalidate(); // This will call dispatchDraw(), which calls getVisiblePages().
- updateAccessibilityFlags();
- onPrepareStateTransition(mState.hasMultipleVisiblePages);
-
- StateTransitionListener listener = new StateTransitionListener();
ValueAnimator stepAnimator = ValueAnimator.ofFloat(0, 1);
stepAnimator.addUpdateListener(listener);
-
+ stepAnimator.setDuration(config.duration);
anim.play(stepAnimator);
anim.addListener(listener);
}
- public LauncherState getState() {
- return mState;
- }
-
public void updateAccessibilityFlags() {
// TODO: Update the accessibility flags appropriately when dragging.
+ int accessibilityFlag = mLauncher.getStateManager().getState().workspaceAccessibilityFlag;
if (!mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
int total = getPageCount();
for (int i = 0; i < total; i++) {
- updateAccessibilityFlags((CellLayout) getPageAt(i), i);
+ updateAccessibilityFlags(accessibilityFlag, (CellLayout) getPageAt(i), i);
}
- setImportantForAccessibility(mState.workspaceAccessibilityFlag);
+ setImportantForAccessibility(accessibilityFlag);
}
}
- private void updateAccessibilityFlags(CellLayout page, int pageNo) {
- if (mState == LauncherState.OVERVIEW) {
+ private void updateAccessibilityFlags(int accessibilityFlag, CellLayout page, int pageNo) {
+ if (isPageRearrangeEnabled()) {
page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
page.getShortcutsAndWidgets().setImportantForAccessibility(
IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
@@ -1665,50 +1611,25 @@
}
} else {
page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
- page.getShortcutsAndWidgets()
- .setImportantForAccessibility(mState.workspaceAccessibilityFlag);
+ page.getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
page.setContentDescription(null);
page.setAccessibilityDelegate(null);
}
}
- public void onPrepareStateTransition(boolean multiplePagesVisible) {
- mIsSwitchingState = true;
- mTransitionProgress = 0;
-
- if (multiplePagesVisible) {
- mForceDrawAdjacentPages = true;
+ public void setPageRearrangeEnabled(boolean isEnabled) {
+ if (mPageRearrangeEnabled != isEnabled) {
+ mPageRearrangeEnabled = isEnabled;
+ if (isEnabled) {
+ enableFreeScroll();
+ } else {
+ disableFreeScroll();
+ }
}
- invalidate(); // This will call dispatchDraw(), which calls getVisiblePages().
-
- updateChildrenLayersEnabled();
}
- private void onStartStateTransition() {
- if (mState == LauncherState.SPRING_LOADED) {
- // Show the page indicator at the same time as the rest of the transition.
- showPageIndicatorAtCurrentScroll();
- }
- getPageIndicator().setShouldAutoHide(mState != LauncherState.SPRING_LOADED);
- }
-
- public void onEndStateTransition() {
- mIsSwitchingState = false;
- updateChildrenLayersEnabled();
- mForceDrawAdjacentPages = false;
- mTransitionProgress = 1;
-
- if (mState == LauncherState.OVERVIEW) {
- enableFreeScroll();
- } else {
- disableFreeScroll();
- }
-
- ViewGroup overviewPanel = mLauncher.getOverviewPanel();
- if (isAccessibilityEnabled() && overviewPanel.getVisibility() == View.VISIBLE) {
- overviewPanel.getChildAt(0).performAccessibilityAction(
- AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
- }
+ public boolean isPageRearrangeEnabled() {
+ return mPageRearrangeEnabled;
}
public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) {
@@ -1807,8 +1728,8 @@
}
private boolean transitionStateShouldAllowDrop() {
- return ((!isSwitchingState() || mTransitionProgress > ALLOW_DROP_TRANSITION_PROGRESS) &&
- (mState == LauncherState.NORMAL || mState == LauncherState.SPRING_LOADED));
+ return (!isSwitchingState() || mTransitionProgress > ALLOW_DROP_TRANSITION_PROGRESS) &&
+ workspaceIconsCanBeDragged();
}
/**
@@ -2079,7 +2000,7 @@
dropTargetLayout, mTargetCell, distance, false, d.dragView) ||
addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
distance, d, false)) {
- mLauncher.exitSpringLoadedDragMode(SPRING_LOADED_EXIT_DELAY);
+ mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
return;
}
@@ -2194,7 +2115,7 @@
// spring-loaded mode so the page meets the icon where it was picked up.
mLauncher.getDragController().animateDragViewToOriginalPosition(
onCompleteRunnable, cell, SPRING_LOADED_TRANSITION_MS);
- mLauncher.exitSpringLoadedDragMode(SPRING_LOADED_EXIT_NEXT_FRAME);
+ mLauncher.getStateManager().goToState(NORMAL);
mLauncher.getDropTargetBar().onDragEnd();
parent.onDropChild(cell);
return;
@@ -2217,8 +2138,8 @@
}
parent.onDropChild(cell);
- mLauncher.exitSpringLoadedDragMode(
- SPRING_LOADED_EXIT_DELAY, onCompleteRunnable);
+ mLauncher.getStateManager().goToState(
+ NORMAL, SPRING_LOADED_EXIT_DELAY, onCompleteRunnable);
}
if (d.stateAnnouncer != null && !droppedOnOriginalCell) {
@@ -2750,7 +2671,7 @@
final long screenId = getIdForScreen(cellLayout);
if (!mLauncher.isHotseatLayout(cellLayout)
&& screenId != getScreenIdForPageIndex(mCurrentPage)
- && mState != LauncherState.SPRING_LOADED) {
+ && !mLauncher.isInState(SPRING_LOADED)) {
snapToPage(getPageIndexForScreenId(screenId));
}
@@ -2825,7 +2746,7 @@
animationStyle, finalView, true);
} else {
// This is for other drag/drop cases, like dragging from All Apps
- mLauncher.exitSpringLoadedDragMode(SPRING_LOADED_EXIT_DELAY);
+ mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
View view;
@@ -3026,10 +2947,6 @@
}
}
- public WorkspaceStateTransitionAnimation getStateTransitionAnimation() {
- return mStateTransitionAnimation;
- }
-
/**
* Return the current CellInfo describing our current drag; this method exists
* so that Launcher can sync this object with the correct info when the activity is created/
@@ -3567,16 +3484,6 @@
}
}
- @Override
- public boolean enableFreeScroll() {
- if (getState() == LauncherState.OVERVIEW) {
- return super.enableFreeScroll();
- } else {
- Log.w(TAG, "enableFreeScroll called but not in overview: state=" + getState());
- return false;
- }
- }
-
/**
* Used as a workaround to ensure that the AppWidgetService receives the
* PACKAGE_ADDED broadcast before updating widgets.
@@ -3633,6 +3540,13 @@
private class StateTransitionListener extends AnimatorListenerAdapter
implements AnimatorUpdateListener {
+
+ private final LauncherState mToState;
+
+ StateTransitionListener(LauncherState toState) {
+ mToState = toState;
+ }
+
@Override
public void onAnimationUpdate(ValueAnimator anim) {
mTransitionProgress = anim.getAnimatedFraction();
@@ -3640,8 +3554,7 @@
@Override
public void onAnimationStart(Animator animation) {
- mTransitionProgress = 0;
- onStartStateTransition();
+ onStartStateTransition(mToState);
}
@Override
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 8e215b0..8edec40 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -30,10 +30,10 @@
import android.util.Property;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
-import android.view.animation.DecelerateInterpolator;
-import com.android.launcher3.LauncherStateTransitionAnimation.AnimationConfig;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.anim.AnimationLayerSet;
+import com.android.launcher3.anim.Interpolators;
/**
* A convenience class to update a view's visibility state after an alpha animation.
@@ -86,65 +86,17 @@
}
/**
- * This interpolator emulates the rate at which the perceived scale of an object changes
- * as its distance from a camera increases. When this interpolator is applied to a scale
- * animation on a view, it evokes the sense that the object is shrinking due to moving away
- * from the camera.
- */
-class ZInterpolator implements TimeInterpolator {
- private float focalLength;
-
- public ZInterpolator(float foc) {
- focalLength = foc;
- }
-
- public float getInterpolation(float input) {
- return (1.0f - focalLength / (focalLength + input)) /
- (1.0f - focalLength / (focalLength + 1.0f));
- }
-}
-
-/**
- * The exact reverse of ZInterpolator.
- */
-class InverseZInterpolator implements TimeInterpolator {
- private ZInterpolator zInterpolator;
- public InverseZInterpolator(float foc) {
- zInterpolator = new ZInterpolator(foc);
- }
- public float getInterpolation(float input) {
- return 1 - zInterpolator.getInterpolation(1 - input);
- }
-}
-
-/**
- * InverseZInterpolator compounded with an ease-out.
- */
-class ZoomInInterpolator implements TimeInterpolator {
- private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f);
- private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f);
-
- public float getInterpolation(float input) {
- return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input));
- }
-}
-
-/**
* Manages the animations between each of the workspace states.
*/
public class WorkspaceStateTransitionAnimation {
- private static final PropertySetter NO_ANIM_PROPERTY_SETTER = new PropertySetter();
-
- private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
+ public static final PropertySetter NO_ANIM_PROPERTY_SETTER = new PropertySetter();
public final int mWorkspaceScrimAlpha;
private final Launcher mLauncher;
private final Workspace mWorkspace;
- private final float mSpringLoadedShrinkFactor;
- private final float mOverviewModeShrinkFactor;
private final boolean mWorkspaceFadeInAdjacentScreens;
private float mNewScale;
@@ -155,9 +107,6 @@
DeviceProfile grid = mLauncher.getDeviceProfile();
Resources res = launcher.getResources();
- mSpringLoadedShrinkFactor = mLauncher.getDeviceProfile().workspaceSpringLoadShrinkFactor;
- mOverviewModeShrinkFactor =
- res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100f;
mWorkspaceScrimAlpha = res.getInteger(R.integer.config_workspaceScrimAlpha);
mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
}
@@ -166,12 +115,10 @@
setWorkspaceProperty(toState, NO_ANIM_PROPERTY_SETTER);
}
- public void setStateWithAnimation(LauncherState fromState, LauncherState toState,
- AnimatorSet anim, AnimationLayerSet layerViews, AnimationConfig config) {
- long duration = config.getDuration(toState == LauncherState.NORMAL
- ? fromState.transitionDuration : toState.transitionDuration);
+ public void setStateWithAnimation(LauncherState toState, AnimatorSet anim,
+ AnimationLayerSet layerViews, AnimationConfig config) {
AnimatedPropertySetter propertySetter =
- new AnimatedPropertySetter(duration, layerViews, anim);
+ new AnimatedPropertySetter(config.duration, layerViews, anim);
setWorkspaceProperty(toState, propertySetter);
}
@@ -183,57 +130,45 @@
* Starts a transition animation for the workspace.
*/
private void setWorkspaceProperty(LauncherState state, PropertySetter propertySetter) {
- // Update the workspace state
- int finalBackgroundAlpha = state.hasScrim ? 255 : 0;
-
- final float finalWorkspaceTranslationY;
- switch (state) {
- case OVERVIEW:
- mNewScale = mOverviewModeShrinkFactor;
- finalWorkspaceTranslationY = mWorkspace.getOverviewModeTranslationY();
- break;
- case SPRING_LOADED:
- mNewScale = mSpringLoadedShrinkFactor;
- finalWorkspaceTranslationY = mWorkspace.getSpringLoadedTranslationY();
- break;
- default:
- mNewScale = 1f;
- finalWorkspaceTranslationY = 0;
- }
+ float[] scaleAndTranslationY = state.getWorkspaceScaleAndTranslation(mLauncher);
+ mNewScale = scaleAndTranslationY[0];
+ final float finalWorkspaceTranslationY = scaleAndTranslationY[1];
int toPage = mWorkspace.getPageNearestToCenterOfScreen();
final int childCount = mWorkspace.getChildCount();
for (int i = 0; i < childCount; i++) {
- final CellLayout cl = (CellLayout) mWorkspace.getChildAt(i);
- propertySetter.setInt(cl.getScrimBackground(),
- DRAWABLE_ALPHA, finalBackgroundAlpha, mZoomInInterpolator);
-
- // Only animate the page alpha when we actually fade pages
- if (mWorkspaceFadeInAdjacentScreens) {
- float finalAlpha = state == LauncherState.NORMAL && i != toPage ? 0 : 1f;
- propertySetter.setFloat(cl.getShortcutsAndWidgets(), View.ALPHA,
- finalAlpha, mZoomInInterpolator);
- }
+ applyChildState(state, (CellLayout) mWorkspace.getChildAt(i), i, toPage,
+ propertySetter);
}
- float finalHotseatAlpha = state.hideHotseat ? 0f : 1f;
-
- // This is true when transitioning between:
- // - Overview <-> Workspace
- propertySetter.setViewAlpha(null, mLauncher.getOverviewPanel(), 1 - finalHotseatAlpha);
- propertySetter.setViewAlpha(mWorkspace.createHotseatAlphaAnimator(finalHotseatAlpha),
- mLauncher.getHotseat(), finalHotseatAlpha);
-
- propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, mZoomInInterpolator);
+ propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, Interpolators.ZOOM_IN);
propertySetter.setFloat(mWorkspace, View.TRANSLATION_Y,
- finalWorkspaceTranslationY, mZoomInInterpolator);
+ finalWorkspaceTranslationY, Interpolators.ZOOM_IN);
// Set scrim
propertySetter.setInt(mLauncher.getDragLayer().getScrim(), DRAWABLE_ALPHA,
- state.hasScrim ? mWorkspaceScrimAlpha : 0, new DecelerateInterpolator(1.5f));
+ state.hasScrim ? mWorkspaceScrimAlpha : 0, Interpolators.DEACCEL_1_5);
}
- private static class PropertySetter {
+ public void applyChildState(LauncherState state, CellLayout cl, int childIndex) {
+ applyChildState(state, cl, childIndex, mWorkspace.getPageNearestToCenterOfScreen(),
+ NO_ANIM_PROPERTY_SETTER);
+ }
+
+ private void applyChildState(LauncherState state, CellLayout cl, int childIndex,
+ int centerPage, PropertySetter propertySetter) {
+ propertySetter.setInt(cl.getScrimBackground(),
+ DRAWABLE_ALPHA, state.hasScrim ? 255 : 0, Interpolators.ZOOM_IN);
+
+ // Only animate the page alpha when we actually fade pages
+ if (mWorkspaceFadeInAdjacentScreens) {
+ float finalAlpha = state == LauncherState.NORMAL && childIndex != centerPage ? 0 : 1f;
+ propertySetter.setFloat(cl.getShortcutsAndWidgets(), View.ALPHA,
+ finalAlpha, Interpolators.ZOOM_IN);
+ }
+ }
+
+ public static class PropertySetter {
public void setViewAlpha(Animator anim, View view, float alpha) {
if (anim != null) {
@@ -261,13 +196,14 @@
}
}
- private static class AnimatedPropertySetter extends PropertySetter {
+ public static class AnimatedPropertySetter extends PropertySetter {
private final long mDuration;
private final AnimationLayerSet mLayerViews;
private final AnimatorSet mStateAnimator;
- AnimatedPropertySetter(long duration, AnimationLayerSet layerView, AnimatorSet anim) {
+ public AnimatedPropertySetter(
+ long duration, AnimationLayerSet layerView, AnimatorSet anim) {
mDuration = duration;
mLayerViews = layerView;
mStateAnimator = anim;
@@ -311,7 +247,7 @@
}
private TimeInterpolator getFadeInterpolator(float finalAlpha) {
- return finalAlpha == 0 ? new DecelerateInterpolator(2) : null;
+ return finalAlpha == 0 ? Interpolators.DEACCEL_2 : null;
}
}
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 583492e..512db72 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -1,5 +1,7 @@
package com.android.launcher3.accessibility;
+import static com.android.launcher3.LauncherState.NORMAL;
+
import android.app.AlertDialog;
import android.appwidget.AppWidgetProviderInfo;
import android.content.DialogInterface;
@@ -17,6 +19,7 @@
import com.android.launcher3.AppInfo;
import com.android.launcher3.AppWidgetResizeFrame;
import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.ButtonDropTarget;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeleteDropTarget;
import com.android.launcher3.DropTarget.DragObject;
@@ -27,6 +30,7 @@
import com.android.launcher3.LauncherAppWidgetHostView;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutInfo;
@@ -45,9 +49,9 @@
private static final String TAG = "LauncherAccessibilityDelegate";
- protected static final int REMOVE = R.id.action_remove;
- protected static final int INFO = R.id.action_info;
- protected static final int UNINSTALL = R.id.action_uninstall;
+ public static final int REMOVE = R.id.action_remove;
+ public static final int INFO = R.id.action_info;
+ public static final int UNINSTALL = R.id.action_uninstall;
protected static final int ADD_TO_WORKSPACE = R.id.action_add_to_workspace;
protected static final int MOVE = R.id.action_move;
protected static final int MOVE_TO_WORKSPACE = R.id.action_move_to_workspace;
@@ -108,14 +112,10 @@
info.addAction(mActions.get(DEEP_SHORTCUTS));
}
- if (DeleteDropTarget.supportsAccessibleDrop(item)) {
- info.addAction(mActions.get(REMOVE));
- }
- if (UninstallDropTarget.supportsDrop(host.getContext(), item)) {
- info.addAction(mActions.get(UNINSTALL));
- }
- if (InfoDropTarget.supportsDrop(host.getContext(), item)) {
- info.addAction(mActions.get(INFO));
+ for (ButtonDropTarget target : mLauncher.getDropTargetBar().getDropTargets()) {
+ if (target.supportsAccessibilityDrop(item)) {
+ info.addAction(mActions.get(target.getAccessibilityAction()));
+ }
}
// Do not add move actions for keyboard request as this uses virtual nodes.
@@ -148,27 +148,19 @@
}
public boolean performAction(final View host, final ItemInfo item, int action) {
- if (action == REMOVE) {
- DeleteDropTarget.removeWorkspaceOrFolderItem(mLauncher, item, host);
- return true;
- } else if (action == INFO) {
- InfoDropTarget.startDetailsActivityForInfo(item, mLauncher, null, null);
- return true;
- } else if (action == UNINSTALL) {
- return UninstallDropTarget.startUninstallActivity(mLauncher, item);
- } else if (action == MOVE) {
+ if (action == MOVE) {
beginAccessibleDrag(host, item);
} else if (action == ADD_TO_WORKSPACE) {
final int[] coordinates = new int[2];
final long screenId = findSpaceOnWorkspace(item, coordinates);
- mLauncher.showWorkspace(true, new Runnable() {
+ mLauncher.getStateManager().goToState(NORMAL, true, new Runnable() {
@Override
public void run() {
if (item instanceof AppInfo) {
ShortcutInfo info = ((AppInfo) item).makeShortcut();
mLauncher.getModelWriter().addItemToDatabase(info,
- LauncherSettings.Favorites.CONTAINER_DESKTOP,
+ Favorites.CONTAINER_DESKTOP,
screenId, coordinates[0], coordinates[1]);
ArrayList<ItemInfo> itemList = new ArrayList<>();
@@ -178,7 +170,7 @@
PendingAddItemInfo info = (PendingAddItemInfo) item;
Workspace workspace = mLauncher.getWorkspace();
workspace.snapToPage(workspace.getPageIndexForScreenId(screenId));
- mLauncher.addPendingItem(info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
+ mLauncher.addPendingItem(info, Favorites.CONTAINER_DESKTOP,
screenId, coordinates, info.spanX, info.spanY);
}
announceConfirmation(R.string.item_added_to_workspace);
@@ -231,6 +223,13 @@
return true;
} else if (action == DEEP_SHORTCUTS) {
return PopupContainerWithArrow.showForIcon((BubbleTextView) host) != null;
+ } else {
+ for (ButtonDropTarget dropTarget : mLauncher.getDropTargetBar().getDropTargets()) {
+ if (action == dropTarget.getAccessibilityAction()) {
+ dropTarget.onAccessibilityDrop(host, item);
+ return true;
+ }
+ }
}
return false;
}
diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
index 5b7353a..cfb0520 100644
--- a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
@@ -16,6 +16,8 @@
package com.android.launcher3.accessibility;
+import static com.android.launcher3.LauncherState.NORMAL;
+
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -79,9 +81,7 @@
}
};
- if (!mLauncher.showWorkspace(true, onComplete)) {
- onComplete.run();
- }
+ mLauncher.getStateManager().goToState(NORMAL, true, onComplete);
return true;
} else if (action == DISMISS_NOTIFICATION) {
if (!(host instanceof NotificationMainView)) {
diff --git a/src/com/android/launcher3/allapps/AllAppsCaretController.java b/src/com/android/launcher3/allapps/AllAppsCaretController.java
deleted file mode 100644
index 583b49f..0000000
--- a/src/com/android/launcher3/allapps/AllAppsCaretController.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2016 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.allapps;
-
-import android.animation.ObjectAnimator;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.pageindicators.CaretDrawable;
-import com.android.launcher3.touch.SwipeDetector;
-
-public class AllAppsCaretController {
- // Determines when the caret should flip. Should be accessed via getThreshold()
- private static final float CARET_THRESHOLD = 0.015f;
- private static final float CARET_THRESHOLD_LAND = 0.5f;
- // The velocity at which the caret will peak (i.e. exhibit a 90 degree bend)
- private static final float PEAK_VELOCITY = SwipeDetector.RELEASE_VELOCITY_PX_MS * .7f;
-
- private Launcher mLauncher;
-
- private ObjectAnimator mCaretAnimator;
- private CaretDrawable mCaretDrawable;
- private float mLastCaretProgress;
- private boolean mThresholdCrossed;
-
- public AllAppsCaretController(CaretDrawable caret, Launcher launcher) {
- mLauncher = launcher;
- mCaretDrawable = caret;
-
- final long caretAnimationDuration = launcher.getResources().getInteger(
- R.integer.config_caretAnimationDuration);
- final Interpolator caretInterpolator = AnimationUtils.loadInterpolator(launcher,
- android.R.interpolator.fast_out_slow_in);
-
- // We will set values later
- mCaretAnimator = ObjectAnimator.ofFloat(mCaretDrawable, "caretProgress", 0);
- mCaretAnimator.setDuration(caretAnimationDuration);
- mCaretAnimator.setInterpolator(caretInterpolator);
- }
-
- /**
- * Updates the state of the caret based on the progress of the {@link AllAppsContainerView}, as
- * defined by the {@link AllAppsTransitionController}. Uses the container's velocity to adjust
- * angle of caret.
- *
- * @param containerProgress The progress of the container in the range [0..1]
- * @param velocity The velocity of the container
- * @param dragging {@code true} if the container is being dragged
- */
- public void updateCaret(float containerProgress, float velocity, boolean dragging) {
- // If we're in portrait and the shift is not 0 or 1, adjust the caret based on velocity
- if (getThreshold() < containerProgress && containerProgress < 1 - getThreshold() &&
- !mLauncher.useVerticalBarLayout()) {
- mThresholdCrossed = true;
-
- // How fast are we moving as a percentage of the peak velocity?
- final float pctOfFlingVelocity = Math.max(-1, Math.min(velocity / PEAK_VELOCITY, 1));
-
- mCaretDrawable.setCaretProgress(pctOfFlingVelocity);
-
- // Set the last caret progress to this progress to prevent animator cancellation
- mLastCaretProgress = pctOfFlingVelocity;
- // Animate to neutral. This is necessary so the caret doesn't "freeze" when the
- // container stops moving (e.g., during a drag or when the threshold is reached).
- animateCaretToProgress(CaretDrawable.PROGRESS_CARET_NEUTRAL);
- } else if (!dragging) {
- // Otherwise, if we're not dragging, match the caret to the appropriate state
- if (containerProgress <= getThreshold()) { // All Apps is up
- animateCaretToProgress(CaretDrawable.PROGRESS_CARET_POINTING_DOWN);
- } else if (containerProgress >= 1 - getThreshold()) { // All Apps is down
- animateCaretToProgress(CaretDrawable.PROGRESS_CARET_POINTING_UP);
- }
- }
- }
-
- private void animateCaretToProgress(float progress) {
- // If the new progress is the same as the last progress we animated to, terminate early
- if (Float.compare(mLastCaretProgress, progress) == 0) {
- return;
- }
-
- if (mCaretAnimator.isRunning()) {
- mCaretAnimator.cancel(); // Stop the animator in its tracks
- }
-
- // Update the progress and start the animation
- mLastCaretProgress = progress;
- mCaretAnimator.setFloatValues(progress);
- mCaretAnimator.start();
- }
-
- private float getThreshold() {
- // In landscape, just return the landscape threshold.
- if (mLauncher.useVerticalBarLayout()) {
- return CARET_THRESHOLD_LAND;
- }
-
- // Before the threshold is crossed, it is reported as zero. This makes the caret immediately
- // responsive when a drag begins. After the threshold is crossed, we return the constant
- // value. This means the caret will start its state-based adjustment sooner. That is, we
- // won't have to wait until the panel is completely settled to begin animation.
- return mThresholdCrossed ? CARET_THRESHOLD : 0f;
- }
-
- public void onDragStart() {
- mThresholdCrossed = false;
- }
-}
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 4de31f4..efd7b97 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -18,11 +18,18 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
+import android.os.Process;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.util.AttributeSet;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -38,6 +45,7 @@
import com.android.launcher3.Insettable;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
import com.android.launcher3.PromiseAppInfo;
import com.android.launcher3.R;
import com.android.launcher3.anim.SpringAnimationHandler;
@@ -46,9 +54,14 @@
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.keyboard.FocusedItemDecorator;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.ComponentKeyMapper;
+import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.TransformingTouchDelegate;
+import com.android.launcher3.views.SlidingTabStrip;
+import java.util.HashMap;
import java.util.List;
import java.util.Set;
@@ -62,23 +75,26 @@
protected final Rect mBasePadding = new Rect();
private final Launcher mLauncher;
- private final AlphabeticalAppsList mApps;
- private final AllAppsGridAdapter mAdapter;
- private final LinearLayoutManager mLayoutManager;
+ private final AdapterHolder[] mAH;
private final ClickShadowView mTouchFeedbackView;
+ private final ItemInfoMatcher mPersonalMatcher = ItemInfoMatcher.ofUser(Process.myUserHandle());
+ private final ItemInfoMatcher mWorkMatcher = ItemInfoMatcher.not(mPersonalMatcher);
- private AllAppsRecyclerView mAppsRecyclerView;
private SearchUiManager mSearchUiManager;
private View mSearchContainer;
+ private InterceptingViewPager mViewPager;
+ private ViewGroup mHeader;
+ private FloatingHeaderHandler mFloatingHeaderHandler;
private SpannableStringBuilder mSearchQueryBuilder = null;
private int mNumAppsPerRow;
private int mNumPredictedAppsPerRow;
- private SpringAnimationHandler mSpringAnimationHandler;
-
private TransformingTouchDelegate mTouchDelegate;
+ private boolean mUsingTabs;
+
+ private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>();
public AllAppsContainerView(Context context) {
this(context, null);
@@ -92,11 +108,7 @@
super(context, attrs, defStyleAttr);
mLauncher = Launcher.getLauncher(context);
- mApps = new AlphabeticalAppsList(context);
- mAdapter = new AllAppsGridAdapter(mLauncher, mApps, mLauncher, this);
- mSpringAnimationHandler = mAdapter.getSpringAnimationHandler();
- mApps.setAdapter(mAdapter);
- mLayoutManager = mAdapter.getLayoutManager();
+
mSearchQueryBuilder = new SpannableStringBuilder();
Selection.setSelection(mSearchQueryBuilder, 0);
@@ -106,6 +118,10 @@
int size = mLauncher.getDeviceProfile().allAppsIconSizePx
+ mTouchFeedbackView.getExtraSize();
addView(mTouchFeedbackView, size, size);
+
+ mAH = new AdapterHolder[2];
+ mAH[AdapterHolder.MAIN] = new AdapterHolder();
+ mAH[AdapterHolder.WORK] = new AdapterHolder();
}
@Override
@@ -115,7 +131,17 @@
DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
grid.addLauncherLayoutChangedListener(this);
- mTouchDelegate = new TransformingTouchDelegate(mAppsRecyclerView);
+ applyTouchDelegate();
+ }
+
+ private void applyTouchDelegate() {
+ RecyclerView rv = getActiveRecyclerView();
+ mTouchDelegate = new TransformingTouchDelegate(rv);
+ mTouchDelegate.setBounds(
+ rv.getLeft() - mBasePadding.left,
+ rv.getTop() - mBasePadding.top,
+ rv.getRight() + mBasePadding.right,
+ rv.getBottom() + mBasePadding.bottom);
setTouchDelegate(mTouchDelegate);
}
@@ -147,59 +173,81 @@
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- mTouchDelegate.setBounds(
- mAppsRecyclerView.getLeft() - mBasePadding.left,
- mAppsRecyclerView.getTop() - mBasePadding.top,
- mAppsRecyclerView.getRight() + mBasePadding.right,
- mAppsRecyclerView.getBottom() + mBasePadding.bottom);
+ applyTouchDelegate();
}
@Override
public void setPressedIcon(BubbleTextView icon, Bitmap background) {
- if (icon == null || background == null) {
- mTouchFeedbackView.setBitmap(null);
- mTouchFeedbackView.animate().cancel();
- } else if (mTouchFeedbackView.setBitmap(background)) {
- View rv = findViewById(R.id.apps_list_view);
- mTouchFeedbackView.alignWithIconView(icon, (ViewGroup) icon.getParent(), rv);
- mTouchFeedbackView.animateShadow();
- }
+ mTouchFeedbackView.setPressedIcon(icon, background);
}
/**
* Sets the current set of apps.
*/
public void setApps(List<AppInfo> apps) {
- mApps.setApps(apps);
+ boolean hasWorkProfileApp = hasWorkProfileApp(apps);
+ if (mUsingTabs != hasWorkProfileApp) {
+ rebindAdapters(hasWorkProfileApp);
+ }
+ mComponentToAppMap.clear();
+ addOrUpdateApps(apps);
}
/**
* Adds or updates existing apps in the list
*/
public void addOrUpdateApps(List<AppInfo> apps) {
- mApps.addOrUpdateApps(apps);
- mSearchUiManager.refreshSearchResult();
- }
-
- public void updatePromiseAppProgress(PromiseAppInfo app) {
- int childCount = mAppsRecyclerView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = mAppsRecyclerView.getChildAt(i);
- if (child instanceof BubbleTextView && child.getTag() == app) {
- BubbleTextView bubbleTextView = (BubbleTextView) child;
- bubbleTextView.applyProgressLevel(app.level);
- }
+ for (AppInfo app : apps) {
+ mComponentToAppMap.put(app.toComponentKey(), app);
}
+ onAppsUpdated();
+ mSearchUiManager.refreshSearchResult();
}
/**
* Removes some apps from the list.
*/
public void removeApps(List<AppInfo> apps) {
- mApps.removeApps(apps);
+ for (AppInfo app : apps) {
+ mComponentToAppMap.remove(app.toComponentKey());
+ }
+ onAppsUpdated();
mSearchUiManager.refreshSearchResult();
}
+ private void onAppsUpdated() {
+ for (int i = 0; i < getNumOfAdapters(); i++) {
+ mAH[i].appsList.onAppsUpdated();
+ }
+ }
+
+ private int getNumOfAdapters() {
+ return mUsingTabs ? mAH.length : 1;
+ }
+
+ public void updatePromiseAppProgress(PromiseAppInfo app) {
+ for (int i = 0; i < mAH.length; i++) {
+ updatePromiseAppProgress(app, mAH[i].recyclerView);
+ }
+ if (mFloatingHeaderHandler != null) {
+ updatePromiseAppProgress(app, mFloatingHeaderHandler.getContentView());
+ }
+ }
+
+ private void updatePromiseAppProgress(PromiseAppInfo app, ViewGroup parent) {
+ if (parent == null) {
+ return;
+ }
+ int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = parent.getChildAt(i);
+ if (child instanceof BubbleTextView && child.getTag() == app) {
+ BubbleTextView bubbleTextView = (BubbleTextView) child;
+ bubbleTextView.applyProgressLevel(app.level);
+ }
+ }
+ }
+
/**
* Returns whether the view itself will handle the touch event or not.
*/
@@ -209,15 +257,31 @@
if (mLauncher.getDragLayer().isEventOverView(mSearchContainer, ev)) {
return true;
}
- return mAppsRecyclerView.shouldContainerScroll(ev, mLauncher.getDragLayer());
+ if (mUsingTabs && mLauncher.getDragLayer().isEventOverView(mHeader, ev)) {
+ return true;
+ }
+ AllAppsRecyclerView rv = getActiveRecyclerView();
+ return rv == null || rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
+ }
+
+ public AllAppsRecyclerView getActiveRecyclerView() {
+ if (!mUsingTabs || mViewPager.getCurrentItem() == 0) {
+ return mAH[AdapterHolder.MAIN].recyclerView;
+ } else {
+ return mAH[AdapterHolder.WORK].recyclerView;
+ }
}
/**
* Resets the state of AllApps.
*/
public void reset() {
+ for (int i = 0; i < mAH.length; i++) {
+ if (mAH[i].recyclerView != null) {
+ mAH[i].recyclerView.scrollToTop();
+ }
+ }
// Reset the search bar and base recycler view after transitioning home
- mAppsRecyclerView.scrollToTop();
mSearchUiManager.reset();
}
@@ -230,33 +294,18 @@
setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
- if (hasFocus) {
- mAppsRecyclerView.requestFocus();
+ if (hasFocus && getActiveRecyclerView() != null) {
+ getActiveRecyclerView().requestFocus();
}
}
});
- // Load the all apps recycler view
- mAppsRecyclerView = findViewById(R.id.apps_list_view);
- mAppsRecyclerView.setApps(mApps);
- mAppsRecyclerView.setLayoutManager(mLayoutManager);
- mAppsRecyclerView.setAdapter(mAdapter);
- mAppsRecyclerView.setHasFixedSize(true);
- // No animations will occur when changes occur to the items in this RecyclerView.
- mAppsRecyclerView.setItemAnimator(null);
- if (FeatureFlags.LAUNCHER3_PHYSICS) {
- mAppsRecyclerView.setSpringAnimationHandler(mSpringAnimationHandler);
- }
+ mHeader = findViewById(R.id.all_apps_header);
+ rebindAdapters(mUsingTabs);
mSearchContainer = findViewById(R.id.search_container_all_apps);
mSearchUiManager = (SearchUiManager) mSearchContainer;
- mSearchUiManager.initialize(mApps, mAppsRecyclerView);
-
-
- FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(mAppsRecyclerView);
- mAppsRecyclerView.addItemDecoration(focusedItemDecorator);
- mAppsRecyclerView.preMeasureViews(mAdapter);
- mAdapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
+ mSearchUiManager.initialize(this);
onLauncherLayoutChanged();
}
@@ -275,10 +324,9 @@
mNumPredictedAppsPerRow != grid.inv.numColumns) {
mNumAppsPerRow = grid.inv.numColumns;
mNumPredictedAppsPerRow = grid.inv.numColumns;
-
- mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
- mAdapter.setNumAppsPerRow(mNumAppsPerRow);
- mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
+ for (int i = 0; i < mAH.length; i++) {
+ mAH[i].applyNumsPerRow();
+ }
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@@ -292,7 +340,7 @@
@Override
public boolean onLongClick(final View v) {
// When we have exited all apps or are in transition, disregard long clicks
- if (!mLauncher.isAppsViewVisible() ||
+ if (!mLauncher.isInState(LauncherState.ALL_APPS) ||
mLauncher.getWorkspace().isSwitchingState()) return false;
// Return if global dragging is not enabled or we are already dragging
if (!mLauncher.isDraggingEnabled()) return false;
@@ -331,10 +379,10 @@
@Override
public void setInsets(Rect insets) {
DeviceProfile grid = mLauncher.getDeviceProfile();
- mAppsRecyclerView.setPadding(
- mAppsRecyclerView.getPaddingLeft(), mAppsRecyclerView.getPaddingTop(),
- mAppsRecyclerView.getPaddingRight(), insets.bottom);
-
+ for (int i = 0; i < mAH.length; i++) {
+ mAH[i].padding.bottom = insets.bottom;
+ mAH[i].applyPadding();
+ }
if (grid.isVerticalBarLayout()) {
ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
mlp.leftMargin = insets.left;
@@ -351,20 +399,303 @@
public void updateIconBadges(Set<PackageUserKey> updatedBadges) {
final PackageUserKey packageUserKey = new PackageUserKey(null, null);
- final int n = mAppsRecyclerView.getChildCount();
- for (int i = 0; i < n; i++) {
- View child = mAppsRecyclerView.getChildAt(i);
- if (!(child instanceof BubbleTextView) || !(child.getTag() instanceof ItemInfo)) {
- continue;
- }
- ItemInfo info = (ItemInfo) child.getTag();
- if (packageUserKey.updateFromItemInfo(info) && updatedBadges.contains(packageUserKey)) {
- ((BubbleTextView) child).applyBadgeState(info, true /* animate */);
+ for (int j = 0; j < mAH.length; j++) {
+ if (mAH[j].recyclerView != null) {
+ final int n = mAH[j].recyclerView.getChildCount();
+ for (int i = 0; i < n; i++) {
+ View child = mAH[j].recyclerView.getChildAt(i);
+ if (!(child instanceof BubbleTextView) || !(child.getTag() instanceof ItemInfo)) {
+ continue;
+ }
+ ItemInfo info = (ItemInfo) child.getTag();
+ if (packageUserKey.updateFromItemInfo(info) && updatedBadges.contains(packageUserKey)) {
+ ((BubbleTextView) child).applyBadgeState(info, true /* animate */);
+ }
+ }
}
}
}
public SpringAnimationHandler getSpringAnimationHandler() {
- return mSpringAnimationHandler;
+ return mUsingTabs ? null : mAH[AdapterHolder.MAIN].animationHandler;
}
+
+ private void rebindAdapters(boolean showTabs) {
+ if (showTabs != mUsingTabs) {
+ replaceRVContainer(showTabs);
+ }
+ mUsingTabs = showTabs;
+
+ if (mUsingTabs) {
+ mAH[AdapterHolder.MAIN].setup(mViewPager.getChildAt(0), mPersonalMatcher);
+ mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher);
+ setupWorkProfileTabs();
+ setupHeader();
+ mHeader.setVisibility(View.VISIBLE);
+ } else {
+ mHeader.setVisibility(View.GONE);
+ mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null);
+ }
+
+ applyTouchDelegate();
+ }
+
+ private boolean hasWorkProfileApp(List<AppInfo> apps) {
+ if (FeatureFlags.ALL_APPS_TABS_ENABLED) {
+ for (AppInfo app : apps) {
+ if (mWorkMatcher.matches(app, null)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void replaceRVContainer(boolean showTabs) {
+ for (int i = 0; i < mAH.length; i++) {
+ if (mAH[i].recyclerView != null) {
+ mAH[i].recyclerView.setLayoutManager(null);
+ }
+ }
+ View oldView = getRecyclerViewContainer();
+ int index = indexOfChild(oldView);
+ removeView(oldView);
+ int layout = showTabs ? R.layout.all_apps_tabs : R.layout.all_apps_rv_layout;
+ View newView = LayoutInflater.from(getContext()).inflate(layout, this, false);
+ addView(newView, index);
+ mViewPager = showTabs ? (InterceptingViewPager) newView : null;
+ }
+
+ public View getRecyclerViewContainer() {
+ return mViewPager != null ? mViewPager : findViewById(R.id.apps_list_view);
+ }
+
+ private void setupWorkProfileTabs() {
+ final SlidingTabStrip tabs = findViewById(R.id.tabs);
+ mViewPager.setAdapter(new TabsPagerAdapter());
+ mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
+
+ boolean mVisible = true;
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ tabs.updateIndicatorPosition(position, positionOffset);
+ if (positionOffset == 0 && !mVisible || positionOffset > 0 && mVisible) {
+ mVisible = positionOffset == 0;
+ for (int i = 0; i < mAH.length; i++) {
+ if (mAH[i].recyclerView != null) {
+ mAH[i].recyclerView.getScrollbar().setAlpha(mVisible ? 1 : 0);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onPageSelected(int pos) {
+ tabs.updateTabTextColor(pos);
+ mFloatingHeaderHandler.setMainActive(pos == 0);
+ applyTouchDelegate();
+ if (mAH[pos].recyclerView != null) {
+ mAH[pos].recyclerView.bindFastScrollbar();
+ }
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ }
+ });
+
+ findViewById(R.id.tab_personal)
+ .setOnClickListener((View view) -> mViewPager.setCurrentItem(0));
+ findViewById(R.id.tab_work)
+ .setOnClickListener((View view) -> mViewPager.setCurrentItem(1));
+ }
+
+ public void setPredictedApps(List<ComponentKeyMapper<AppInfo>> apps) {
+ if (mUsingTabs) {
+ mFloatingHeaderHandler.getContentView().setPredictedApps(apps);
+ }
+ mAH[AdapterHolder.MAIN].appsList.setPredictedApps(apps);
+ }
+
+ public AppInfo findApp(ComponentKeyMapper<AppInfo> mapper) {
+ return mapper.getItem(mComponentToAppMap);
+ }
+
+ public AlphabeticalAppsList getApps() {
+ return mAH[AdapterHolder.MAIN].appsList;
+ }
+
+ public boolean isUsingTabs() {
+ return mUsingTabs;
+ }
+
+ public FloatingHeaderHandler getFloatingHeaderHandler() {
+ return mFloatingHeaderHandler;
+ }
+
+ private void setupHeader() {
+ int contentHeight = mLauncher.getDeviceProfile().allAppsCellHeightPx;
+ RecyclerView mainRV = mAH[AdapterHolder.MAIN].recyclerView;
+ RecyclerView workRV = mAH[AdapterHolder.WORK] != null
+ ? mAH[AdapterHolder.WORK].recyclerView : null;
+ mFloatingHeaderHandler = new FloatingHeaderHandler(mHeader, mainRV, workRV, contentHeight);
+ mFloatingHeaderHandler.getContentView().setNumAppsPerRow(mNumPredictedAppsPerRow);
+ mFloatingHeaderHandler.getContentView().setComponentToAppMap(mComponentToAppMap);
+ for (int i = 0; i < mAH.length; i++) {
+ mAH[i].paddingTopForTabs = contentHeight;
+ mAH[i].applyPadding();
+ }
+ }
+
+ public void setLastSearchQuery(String query) {
+ for (int i = 0; i < mAH.length; i++) {
+ mAH[i].adapter.setLastSearchQuery(query);
+ }
+ }
+
+ public void onSearchResultsChanged() {
+ for (int i = 0; i < mAH.length; i++) {
+ mAH[i].recyclerView.onSearchResultsChanged();
+ }
+ }
+
+ public void setRecyclerViewPaddingTop(int top) {
+ for (int i = 0; i < mAH.length; i++) {
+ mAH[i].padding.top = top;
+ mAH[i].applyPadding();
+ }
+ }
+
+ public void setRecyclerViewSidePadding(int left, int right) {
+ for (int i = 0; i < mAH.length; i++) {
+ mAH[i].padding.left = left;
+ mAH[i].padding.right = right;
+ mAH[i].applyPadding();
+ }
+ }
+
+ public void setRecyclerViewVerticalFadingEdgeEnabled(boolean enabled) {
+ for (int i = 0; i < mAH.length; i++) {
+ mAH[i].applyVerticalFadingEdgeEnabled(enabled);
+ }
+ }
+
+ public void addElevationController(RecyclerView.OnScrollListener scrollListener) {
+ if (!mUsingTabs) {
+ mAH[AdapterHolder.MAIN].recyclerView.addOnScrollListener(scrollListener);
+ }
+ }
+
+ public List<AppInfo> getPredictedApps() {
+ if (mUsingTabs) {
+ return mFloatingHeaderHandler.getContentView().getPredictedApps();
+ } else {
+ return mAH[AdapterHolder.MAIN].appsList.getPredictedApps();
+ }
+ }
+
+ public class AdapterHolder {
+ public static final int MAIN = 0;
+ public static final int WORK = 1;
+
+ final AllAppsGridAdapter adapter;
+ final LinearLayoutManager layoutManager;
+ final SpringAnimationHandler animationHandler;
+ final AlphabeticalAppsList appsList;
+ final Rect padding = new Rect();
+ int paddingTopForTabs;
+ AllAppsRecyclerView recyclerView;
+ boolean verticalFadingEdge;
+
+ AdapterHolder() {
+ appsList = new AlphabeticalAppsList(mLauncher, mComponentToAppMap);
+ adapter = new AllAppsGridAdapter(mLauncher, appsList, mLauncher,
+ AllAppsContainerView.this, true);
+ appsList.setAdapter(adapter);
+ animationHandler = adapter.getSpringAnimationHandler();
+ layoutManager = adapter.getLayoutManager();
+ }
+
+ void setup(@NonNull View rv, @Nullable ItemInfoMatcher matcher) {
+ appsList.updateItemFilter(matcher);
+ recyclerView = (AllAppsRecyclerView) rv;
+ recyclerView.setApps(appsList, mUsingTabs);
+ recyclerView.setLayoutManager(layoutManager);
+ recyclerView.setAdapter(adapter);
+ recyclerView.setHasFixedSize(true);
+ // No animations will occur when changes occur to the items in this RecyclerView.
+ recyclerView.setItemAnimator(null);
+ if (FeatureFlags.LAUNCHER3_PHYSICS && animationHandler != null) {
+ recyclerView.setSpringAnimationHandler(animationHandler);
+ }
+ FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(recyclerView);
+ recyclerView.addItemDecoration(focusedItemDecorator);
+ recyclerView.preMeasureViews(adapter);
+ adapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
+ applyVerticalFadingEdgeEnabled(verticalFadingEdge);
+ applyPadding();
+ applyNumsPerRow();
+ }
+
+ void applyPadding() {
+ if (recyclerView != null) {
+ int paddingTop = mUsingTabs ? paddingTopForTabs : padding.top;
+ recyclerView.setPadding(padding.left, paddingTop, padding.right, padding.bottom);
+ }
+ }
+
+ void applyNumsPerRow() {
+ if (mNumAppsPerRow > 0) {
+ if (recyclerView != null) {
+ recyclerView.setNumAppsPerRow(mLauncher.getDeviceProfile(), mNumAppsPerRow);
+ }
+ adapter.setNumAppsPerRow(mNumAppsPerRow);
+ appsList.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
+ if (mUsingTabs && mFloatingHeaderHandler != null) {
+ mFloatingHeaderHandler.getContentView()
+ .setNumAppsPerRow(mNumPredictedAppsPerRow);
+ }
+ }
+ }
+
+ public void applyVerticalFadingEdgeEnabled(boolean enabled) {
+ verticalFadingEdge = enabled;
+ mAH[AdapterHolder.MAIN].recyclerView.setVerticalFadingEdgeEnabled(!mUsingTabs
+ && verticalFadingEdge);
+ }
+ }
+
+ private class TabsPagerAdapter extends PagerAdapter {
+ @Override
+ public int getCount() {
+ return 2;
+ }
+
+ @Override
+ public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
+ return view == object;
+ }
+
+ @NonNull
+ @Override
+ public Object instantiateItem(@NonNull ViewGroup container, int position) {
+ if (position == 0) {
+ return mAH[AdapterHolder.MAIN].recyclerView;
+ } else {
+ return mAH[AdapterHolder.WORK].recyclerView;
+ }
+ }
+
+ @Nullable
+ @Override
+ public CharSequence getPageTitle(int position) {
+ if (position == 0) {
+ return getResources().getString(R.string.all_apps_personal_tab);
+ } else {
+ return getResources().getString(R.string.all_apps_work_tab);
+ }
+ }
+ }
+
}
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index f7ce8c1..ac8d367 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -206,10 +206,10 @@
// The intent to send off to the market app, updated each time the search query changes.
private Intent mMarketSearchIntent;
- private SpringAnimationHandler<ViewHolder> mSpringAnimationHandler;
+ private final SpringAnimationHandler<ViewHolder> mSpringAnimationHandler;
public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps, View.OnClickListener
- iconClickListener, View.OnLongClickListener iconLongClickListener) {
+ iconClickListener, View.OnLongClickListener iconLongClickListener, boolean springAnim) {
Resources res = launcher.getResources();
mLauncher = launcher;
mApps = apps;
@@ -220,9 +220,11 @@
mLayoutInflater = LayoutInflater.from(launcher);
mIconClickListener = iconClickListener;
mIconLongClickListener = iconLongClickListener;
- if (FeatureFlags.LAUNCHER3_PHYSICS) {
+ if (FeatureFlags.LAUNCHER3_PHYSICS && springAnim) {
mSpringAnimationHandler = new SpringAnimationHandler<>(
SpringAnimationHandler.Y_DIRECTION, new AllAppsSpringAnimationFactory());
+ } else {
+ mSpringAnimationHandler = null;
}
}
@@ -336,6 +338,7 @@
case VIEW_TYPE_PREDICTION_ICON:
AppInfo info = mApps.getAdapterItems().get(position).appInfo;
BubbleTextView icon = (BubbleTextView) holder.itemView;
+ icon.reset();
icon.applyFromApplicationInfo(info);
break;
case VIEW_TYPE_DISCOVERY_ITEM:
@@ -376,7 +379,7 @@
@Override
public void onViewAttachedToWindow(ViewHolder holder) {
int type = holder.getItemViewType();
- if (FeatureFlags.LAUNCHER3_PHYSICS && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) {
+ if (mSpringAnimationHandler != null && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) {
mSpringAnimationHandler.add(holder.itemView, holder);
}
}
@@ -384,7 +387,7 @@
@Override
public void onViewDetachedFromWindow(ViewHolder holder) {
int type = holder.getItemViewType();
- if (FeatureFlags.LAUNCHER3_PHYSICS && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) {
+ if (mSpringAnimationHandler != null && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) {
mSpringAnimationHandler.remove(holder.itemView);
}
}
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 494cd5a..09357f7 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -31,6 +31,7 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.anim.SpringAnimationHandler;
import com.android.launcher3.config.FeatureFlags;
@@ -40,6 +41,7 @@
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.views.RecyclerViewFastScroller;
import java.util.List;
@@ -51,6 +53,7 @@
private AlphabeticalAppsList mApps;
private AllAppsFastScrollHelper mFastScrollHelper;
private int mNumAppsPerRow;
+ private int mUserProfileTabContentHeight;
// The specific view heights that we use to calculate scroll
private SparseIntArray mViewHeights = new SparseIntArray();
@@ -63,7 +66,6 @@
private SpringAnimationHandler mSpringAnimationHandler;
private OverScrollHelper mOverScrollHelper;
private SwipeDetector mPullDetector;
-
private float mContentTranslationY = 0;
public static final Property<AllAppsRecyclerView, Float> CONTENT_TRANS_Y =
new Property<AllAppsRecyclerView, Float>(Float.class, "appsRecyclerViewContentTransY") {
@@ -122,9 +124,11 @@
/**
* Sets the list of apps in this view, used to determine the fastscroll position.
*/
- public void setApps(AlphabeticalAppsList apps) {
+ public void setApps(AlphabeticalAppsList apps, boolean usingTabs) {
mApps = apps;
mFastScrollHelper = new AllAppsFastScrollHelper(this, apps);
+ mUserProfileTabContentHeight = usingTabs
+ ? Launcher.getLauncher(getContext()).getDeviceProfile().allAppsCellHeightPx : 0;;
}
public AlphabeticalAppsList getApps() {
@@ -136,7 +140,6 @@
*/
public void setNumAppsPerRow(DeviceProfile grid, int numAppsPerRow) {
mNumAppsPerRow = numAppsPerRow;
-
RecyclerView.RecycledViewPool pool = getRecycledViewPool();
int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH, 1);
@@ -169,7 +172,6 @@
AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET);
putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH);
-
if (FeatureFlags.DISCOVERY_ENABLED) {
putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
AllAppsGridAdapter.VIEW_TYPE_APPS_LOADING_DIVIDER);
@@ -485,8 +487,23 @@
*/
@Override
protected int getAvailableScrollHeight() {
- return getPaddingTop() + getCurrentScrollY(mApps.getAdapterItems().size(), 0)
- - getHeight() + getPaddingBottom();
+ return getPaddingTop() + getCurrentScrollY(getAdapter().getItemCount(), 0)
+ - getHeight() + getPaddingBottom() + mUserProfileTabContentHeight;
+ }
+
+ public int getScrollBarTop() {
+ return super.getScrollBarTop() + mUserProfileTabContentHeight;
+ }
+
+ /**
+ * Returns the height of the fast scroll bar
+ */
+ public int getScrollbarTrackHeight() {
+ return super.getScrollbarTrackHeight() + mUserProfileTabContentHeight;
+ }
+
+ public RecyclerViewFastScroller getScrollbar() {
+ return mScrollbar;
}
/**
@@ -587,7 +604,7 @@
private void reset(boolean shouldSpring) {
float y = getContentTranslationY();
if (Float.compare(y, 0) != 0) {
- if (FeatureFlags.LAUNCHER3_PHYSICS && shouldSpring) {
+ if (mSpringAnimationHandler != null && shouldSpring) {
// We calculate our own velocity to give the springs the desired effect.
float velocity = y / getDampedOverScroll(getHeight()) * MAX_RELEASE_VELOCITY;
// We want to negate the velocity because we are moving to 0 from -1 due to the
@@ -614,4 +631,5 @@
return OverScroll.dampedScroll(y, getHeight());
}
}
+
}
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 5642b4c..eb26704 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -1,36 +1,31 @@
package com.android.launcher3.allapps;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
-import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
-import android.support.animation.SpringAnimation;
-import android.support.v4.view.animation.FastOutSlowInInterpolator;
-import android.view.MotionEvent;
+import android.util.Property;
import android.view.View;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.Animation;
import android.view.animation.Interpolator;
-import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Hotseat;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.LauncherStateTransitionAnimation.AnimationConfig;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
-import com.android.launcher3.anim.SpringAnimationHandler;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.anim.AnimationLayerSet;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.graphics.GradientView;
-import com.android.launcher3.touch.SwipeDetector;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.Themes;
-import com.android.launcher3.util.TouchController;
/**
* Handles AllApps view transition.
@@ -42,29 +37,33 @@
* If release velocity < THRES1, snap according to either top or bottom depending on whether it's
* closer to top or closer to the page indicator.
*/
-public class AllAppsTransitionController implements TouchController, SwipeDetector.Listener,
- SearchUiManager.OnScrollRangeChangeListener {
+public class AllAppsTransitionController
+ implements SearchUiManager.OnScrollRangeChangeListener, LauncherStateManager.StateHandler {
- private final Interpolator mWorkspaceAccelnterpolator = new AccelerateInterpolator(2f);
- private final Interpolator mHotseatAccelInterpolator = new AccelerateInterpolator(1.5f);
- private final Interpolator mFastOutSlowInInterpolator = new FastOutSlowInInterpolator();
- private final SwipeDetector.ScrollInterpolator mScrollInterpolator
- = new SwipeDetector.ScrollInterpolator();
+ private static final Property<AllAppsTransitionController, Float> PROGRESS =
+ new Property<AllAppsTransitionController, Float>(Float.class, "progress") {
+
+ @Override
+ public Float get(AllAppsTransitionController controller) {
+ return controller.mProgress;
+ }
+
+ @Override
+ public void set(AllAppsTransitionController controller, Float progress) {
+ controller.setProgress(progress);
+ }
+ };
+
+ private final Interpolator mWorkspaceAccelnterpolator = Interpolators.ACCEL_2;
+ private final Interpolator mHotseatAccelInterpolator = Interpolators.ACCEL_1_5;
private static final float PARALLAX_COEFFICIENT = .125f;
- private static final int SINGLE_FRAME_MS = 16;
private AllAppsContainerView mAppsView;
private Workspace mWorkspace;
private Hotseat mHotseat;
- private AllAppsCaretController mCaretController;
-
- private float mStatusBarHeight;
-
private final Launcher mLauncher;
- private final SwipeDetector mDetector;
- private final ArgbEvaluator mEvaluator;
private final boolean mIsDarkTheme;
// Animation in this class is controlled by a single variable {@link mProgress}.
@@ -73,193 +72,31 @@
// When {@link mProgress} is 0, all apps container is pulled up.
// When {@link mProgress} is 1, all apps container is pulled down.
- private float mShiftStart; // [0, mShiftRange]
private float mShiftRange; // changes depending on the orientation
private float mProgress; // [0, 1], mShiftRange * mProgress = shiftCurrent
- // Velocity of the container. Unit is in px/ms.
- private float mContainerVelocity;
-
private static final float DEFAULT_SHIFT_RANGE = 10;
- private static final float RECATCH_REJECTION_FRACTION = .0875f;
-
- private long mAnimationDuration;
-
- private AnimatorSet mCurrentAnimation;
- private boolean mNoIntercept;
- private boolean mTouchEventStartedOnHotseat;
-
- // Used in discovery bounce animation to provide the transition without workspace changing.
private boolean mIsTranslateWithoutWorkspace = false;
private Animator mDiscoBounceAnimation;
private GradientView mGradientView;
- private SpringAnimation mSearchSpring;
- private SpringAnimationHandler mSpringAnimationHandler;
-
public AllAppsTransitionController(Launcher l) {
mLauncher = l;
- mDetector = new SwipeDetector(l, this, SwipeDetector.VERTICAL);
mShiftRange = DEFAULT_SHIFT_RANGE;
mProgress = 1f;
- mEvaluator = new ArgbEvaluator();
mIsDarkTheme = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark);
}
- @Override
- public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- mNoIntercept = false;
- mTouchEventStartedOnHotseat = mLauncher.getDragLayer().isEventOverHotseat(ev);
- if (!mLauncher.isAllAppsVisible() && mLauncher.getWorkspace().workspaceInModalState()) {
- mNoIntercept = true;
- } else if (mLauncher.isAllAppsVisible() &&
- !mAppsView.shouldContainerScroll(ev)) {
- mNoIntercept = true;
- } else if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
- mNoIntercept = true;
- } else {
- // Now figure out which direction scroll events the controller will start
- // calling the callbacks.
- int directionsToDetectScroll = 0;
- boolean ignoreSlopWhenSettling = false;
-
- if (mDetector.isIdleState()) {
- if (mLauncher.isAllAppsVisible()) {
- directionsToDetectScroll |= SwipeDetector.DIRECTION_NEGATIVE;
- } else {
- directionsToDetectScroll |= SwipeDetector.DIRECTION_POSITIVE;
- }
- } else {
- if (isInDisallowRecatchBottomZone()) {
- directionsToDetectScroll |= SwipeDetector.DIRECTION_POSITIVE;
- } else if (isInDisallowRecatchTopZone()) {
- directionsToDetectScroll |= SwipeDetector.DIRECTION_NEGATIVE;
- } else {
- directionsToDetectScroll |= SwipeDetector.DIRECTION_BOTH;
- ignoreSlopWhenSettling = true;
- }
- }
- mDetector.setDetectableScrollConditions(directionsToDetectScroll,
- ignoreSlopWhenSettling);
- }
- }
-
- if (mNoIntercept) {
- return false;
- }
- mDetector.onTouchEvent(ev);
- if (mDetector.isSettlingState() && (isInDisallowRecatchBottomZone() || isInDisallowRecatchTopZone())) {
- return false;
- }
- return mDetector.isDraggingOrSettling();
+ public float getShiftRange() {
+ return mShiftRange;
}
- @Override
- public boolean onControllerTouchEvent(MotionEvent ev) {
- if (hasSpringAnimationHandler()) {
- mSpringAnimationHandler.addMovement(ev);
- }
- return mDetector.onTouchEvent(ev);
- }
-
- private boolean isInDisallowRecatchTopZone() {
- return mProgress < RECATCH_REJECTION_FRACTION;
- }
-
- private boolean isInDisallowRecatchBottomZone() {
- return mProgress > 1 - RECATCH_REJECTION_FRACTION;
- }
-
- @Override
- public void onDragStart(boolean start) {
- mCaretController.onDragStart();
- cancelAnimation();
- mCurrentAnimation = LauncherAnimUtils.createAnimatorSet();
- mShiftStart = mAppsView.getTranslationY();
- preparePull(start);
- if (hasSpringAnimationHandler()) {
- mSpringAnimationHandler.skipToEnd();
- }
- }
-
- @Override
- public boolean onDrag(float displacement, float velocity) {
- if (mAppsView == null) {
- return false; // early termination.
- }
-
- mContainerVelocity = velocity;
-
- float shift = Math.min(Math.max(0, mShiftStart + displacement), mShiftRange);
- setProgress(shift / mShiftRange);
-
- return true;
- }
-
- @Override
- public void onDragEnd(float velocity, boolean fling) {
- if (mAppsView == null) {
- return; // early termination.
- }
-
- final int containerType = mTouchEventStartedOnHotseat
- ? ContainerType.HOTSEAT : ContainerType.WORKSPACE;
-
- if (fling) {
- if (velocity < 0) {
- calculateDuration(velocity, mAppsView.getTranslationY());
-
- if (!mLauncher.isAllAppsVisible()) {
- mLauncher.getUserEventDispatcher().logActionOnContainer(
- Action.Touch.FLING,
- Action.Direction.UP,
- containerType);
- }
- mLauncher.showAppsView(true /* animated */);
- if (hasSpringAnimationHandler()) {
- mSpringAnimationHandler.add(mSearchSpring, true /* setDefaultValues */);
- // The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
- mSpringAnimationHandler.animateToFinalPosition(0 /* pos */, 1 /* startValue */);
- }
- } else {
- calculateDuration(velocity, Math.abs(mShiftRange - mAppsView.getTranslationY()));
- mLauncher.showWorkspace(true);
- }
- // snap to top or bottom using the release velocity
- } else {
- if (mAppsView.getTranslationY() > mShiftRange / 2) {
- calculateDuration(velocity, Math.abs(mShiftRange - mAppsView.getTranslationY()));
- mLauncher.showWorkspace(true);
- } else {
- calculateDuration(velocity, Math.abs(mAppsView.getTranslationY()));
- if (!mLauncher.isAllAppsVisible()) {
- mLauncher.getUserEventDispatcher().logActionOnContainer(
- Action.Touch.SWIPE,
- Action.Direction.UP,
- containerType);
- }
- mLauncher.showAppsView(true /* animated */);
- }
- }
- }
-
- public boolean isTransitioning() {
- return mDetector.isDraggingOrSettling();
- }
-
- /**
- * @param start {@code true} if start of new drag.
- */
- public void preparePull(boolean start) {
- if (start) {
- // Initialize values that should not change until #onDragEnd
- mStatusBarHeight = mLauncher.getDragLayer().getInsets().top;
- mHotseat.setVisibility(View.VISIBLE);
- mAppsView.setVisibility(View.VISIBLE);
- }
+ private void onProgressAnimationStart() {
+ // Initialize values that should not change until #onDragEnd
+ mHotseat.setVisibility(View.VISIBLE);
+ mAppsView.setVisibility(View.VISIBLE);
}
private void updateLightStatusBar(float shift) {
@@ -283,10 +120,15 @@
}
/**
- * @param progress value between 0 and 1, 0 shows all apps and 1 shows workspace
+ * Note this method should not be called outside this class. This is public because it is used
+ * in xml-based animations which also handle updating the appropriate UI.
+ *
+ * @param progress value between 0 and 1, 0 shows all apps and 1 shows workspace
+ *
+ * @see #setState(LauncherState)
+ * @see #setStateWithAnimation(LauncherState, AnimationLayerSet, AnimatorSet, AnimationConfig)
*/
public void setProgress(float progress) {
- float shiftPrevious = mProgress * mShiftRange;
mProgress = progress;
float shiftCurrent = progress * mShiftRange;
@@ -314,12 +156,6 @@
mWorkspace.setWorkspaceYTranslationAndAlpha(
PARALLAX_COEFFICIENT * (-mShiftRange + shiftCurrent), workspaceAlpha);
- if (!mDetector.isDraggingState()) {
- mContainerVelocity = mDetector.computeVelocity(shiftCurrent - shiftPrevious,
- System.currentTimeMillis());
- }
-
- mCaretController.updateCaret(progress, mContainerVelocity, mDetector.isDraggingState());
updateLightStatusBar(shiftCurrent);
}
@@ -327,78 +163,47 @@
return mProgress;
}
- private void calculateDuration(float velocity, float disp) {
- mAnimationDuration = SwipeDetector.calculateDuration(velocity, disp / mShiftRange);
+ /**
+ * Sets the vertical transition progress to {@param state} and updates all the dependent UI
+ * accordingly.
+ */
+ @Override
+ public void setState(LauncherState state) {
+ setProgress(state.verticalProgress);
+ onProgressAnimationEnd();
}
- public void animateToAllApps(AnimatorSet animationOut, AnimationConfig outConfig) {
- outConfig.shouldPost = true;
- if (animationOut == null) {
+ /**
+ * Creates an animation which updates the vertical transition progress and updates all the
+ * dependent UI using various animation events
+ */
+ @Override
+ public void setStateWithAnimation(LauncherState toState, AnimationLayerSet layerViews,
+ AnimatorSet animationOut, AnimationConfig config) {
+ if (Float.compare(mProgress, toState.verticalProgress) == 0) {
+ // Fail fast
+ onProgressAnimationEnd();
return;
}
- Interpolator interpolator;
- if (mDetector.isIdleState()) {
- preparePull(true);
- mAnimationDuration = LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
- mShiftStart = mAppsView.getTranslationY();
- interpolator = mFastOutSlowInInterpolator;
- } else {
- mScrollInterpolator.setVelocityAtZero(Math.abs(mContainerVelocity));
- interpolator = mScrollInterpolator;
- float nextFrameProgress = mProgress + mContainerVelocity * SINGLE_FRAME_MS / mShiftRange;
- if (nextFrameProgress >= 0f) {
- mProgress = nextFrameProgress;
- }
- outConfig.shouldPost = false;
- }
- outConfig.overrideDuration(mAnimationDuration);
- ObjectAnimator driftAndAlpha = ObjectAnimator.ofFloat(this, "progress",
- mProgress, 0f);
- driftAndAlpha.setDuration(mAnimationDuration);
- driftAndAlpha.setInterpolator(interpolator);
- animationOut.play(driftAndAlpha);
-
- animationOut.addListener(new AnimatorListenerAdapter() {
- // Spring values used when the user has not flung all apps.
- private final float MAX_RELEASE_VELOCITY = 10000;
- // The delay (as a % of the animation duration) to start the springs.
- private final float DELAY = 0.3f;
-
- boolean canceled = false;
-
+ Interpolator interpolator = config.userControlled ? LINEAR : FAST_OUT_SLOW_IN;
+ ObjectAnimator anim = ObjectAnimator.ofFloat(
+ this, PROGRESS, mProgress, toState.verticalProgress);
+ anim.setDuration(config.duration);
+ anim.setInterpolator(interpolator);
+ anim.addListener(new AnimationSuccessListener() {
@Override
- public void onAnimationCancel(Animator animation) {
- canceled = true;
+ public void onAnimationSuccess(Animator animator) {
+ onProgressAnimationEnd();
}
@Override
public void onAnimationStart(Animator animation) {
- // Add springs for cases where the user has not flung.
- // ie. clicking on the caret, releasing all apps so it snaps up.
- mAppsView.postDelayed(new Runnable() {
- @Override
- public void run() {
- if (!canceled && !mSpringAnimationHandler.isRunning()) {
- float velocity = mProgress * MAX_RELEASE_VELOCITY;
- mSpringAnimationHandler.animateToPositionWithVelocity(0, 1, velocity);
- }
- }
- }, (long) (mAnimationDuration * DELAY));
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (canceled) {
- return;
- } else {
- finishPullUp();
- cleanUpAnimation();
- mDetector.finishedScrolling();
- }
+ onProgressAnimationStart();
}
});
- mCurrentAnimation = animationOut;
+
+ animationOut.play(anim);
}
public void showDiscoveryBounce() {
@@ -412,12 +217,12 @@
@Override
public void onAnimationStart(Animator animator) {
mIsTranslateWithoutWorkspace = true;
- preparePull(true);
+ onProgressAnimationStart();
}
@Override
public void onAnimationEnd(Animator animator) {
- finishPullDown();
+ onProgressAnimationEnd();
mDiscoBounceAnimation = null;
mIsTranslateWithoutWorkspace = false;
}
@@ -434,83 +239,6 @@
});
}
- public void animateToWorkspace(AnimatorSet animationOut, AnimationConfig outconfig) {
- outconfig.shouldPost = true;
- if (animationOut == null) {
- return;
- }
- Interpolator interpolator;
- if (mDetector.isIdleState()) {
- preparePull(true);
- mAnimationDuration = LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
- mShiftStart = mAppsView.getTranslationY();
- interpolator = mFastOutSlowInInterpolator;
- } else {
- mScrollInterpolator.setVelocityAtZero(Math.abs(mContainerVelocity));
- interpolator = mScrollInterpolator;
- float nextFrameProgress = mProgress + mContainerVelocity * SINGLE_FRAME_MS / mShiftRange;
- if (nextFrameProgress <= 1f) {
- mProgress = nextFrameProgress;
- }
- outconfig.shouldPost = false;
- }
-
- outconfig.overrideDuration(mAnimationDuration);
- ObjectAnimator driftAndAlpha = ObjectAnimator.ofFloat(this, "progress",
- mProgress, 1f);
- driftAndAlpha.setDuration(mAnimationDuration);
- driftAndAlpha.setInterpolator(interpolator);
- animationOut.play(driftAndAlpha);
-
- animationOut.addListener(new AnimatorListenerAdapter() {
- boolean canceled = false;
-
- @Override
- public void onAnimationCancel(Animator animation) {
- canceled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (canceled) {
- return;
- } else {
- finishPullDown();
- cleanUpAnimation();
- mDetector.finishedScrolling();
- }
- }
- });
- mCurrentAnimation = animationOut;
- }
-
- public void finishPullUp() {
- mHotseat.setVisibility(View.INVISIBLE);
- if (hasSpringAnimationHandler()) {
- mSpringAnimationHandler.remove(mSearchSpring);
- mSpringAnimationHandler.reset();
- }
- setProgress(0f);
- }
-
- public void finishPullDown() {
- mAppsView.setVisibility(View.INVISIBLE);
- mHotseat.setVisibility(View.VISIBLE);
- mAppsView.reset();
- if (hasSpringAnimationHandler()) {
- mSpringAnimationHandler.reset();
- }
- setProgress(1f);
- }
-
- private void cancelAnimation() {
- if (mCurrentAnimation != null) {
- mCurrentAnimation.cancel();
- mCurrentAnimation = null;
- }
- cancelDiscoveryAnimation();
- }
-
public void cancelDiscoveryAnimation() {
if (mDiscoBounceAnimation == null) {
return;
@@ -519,24 +247,12 @@
mDiscoBounceAnimation = null;
}
- private void cleanUpAnimation() {
- mCurrentAnimation = null;
- }
-
public void setupViews(AllAppsContainerView appsView, Hotseat hotseat, Workspace workspace) {
mAppsView = appsView;
mHotseat = hotseat;
mWorkspace = workspace;
mHotseat.bringToFront();
- mCaretController = new AllAppsCaretController(
- mWorkspace.getPageIndicator().getCaretDrawable(), mLauncher);
mAppsView.getSearchUiManager().addOnScrollRangeChangeListener(this);
- mSpringAnimationHandler = mAppsView.getSpringAnimationHandler();
- mSearchSpring = mAppsView.getSearchUiManager().getSpringForFling();
- }
-
- private boolean hasSpringAnimationHandler() {
- return FeatureFlags.LAUNCHER3_PHYSICS && mSpringAnimationHandler != null;
}
@Override
@@ -544,4 +260,19 @@
mShiftRange = scrollRange;
setProgress(mProgress);
}
+
+ /**
+ * Set the final view states based on the progress.
+ * TODO: This logic should go in {@link LauncherState}
+ */
+ private void onProgressAnimationEnd() {
+ if (Float.compare(mProgress, 1f) == 0) {
+ mAppsView.setVisibility(View.INVISIBLE);
+ mHotseat.setVisibility(View.VISIBLE);
+ mAppsView.reset();
+ } else if (Float.compare(mProgress, 0f) == 0) {
+ mHotseat.setVisibility(View.INVISIBLE);
+ mAppsView.setVisibility(View.VISIBLE);
+ }
+ }
}
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index f0b650b..f9dde2f 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -30,6 +30,7 @@
import com.android.launcher3.discovery.AppDiscoveryUpdateState;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ComponentKeyMapper;
+import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LabelComparator;
import java.util.ArrayList;
@@ -165,7 +166,7 @@
// The set of apps from the system not including predictions
private final List<AppInfo> mApps = new ArrayList<>();
- private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>();
+ private final HashMap<ComponentKey, AppInfo> mComponentToAppMap;
// The set of filtered apps with the current filter
private final List<AppInfo> mFilteredApps = new ArrayList<>();
@@ -188,13 +189,20 @@
private int mNumAppsPerRow;
private int mNumPredictedAppsPerRow;
private int mNumAppRowsInAdapter;
+ private ItemInfoMatcher mItemFilter;
- public AlphabeticalAppsList(Context context) {
+ public AlphabeticalAppsList(Context context, HashMap<ComponentKey, AppInfo> componentToAppMap) {
+ mComponentToAppMap = componentToAppMap;
mLauncher = Launcher.getLauncher(context);
mIndexer = new AlphabeticIndexCompat(context);
mAppNameComparator = new AppInfoComparator(context);
}
+ public void updateItemFilter(ItemInfoMatcher itemFilter) {
+ this.mItemFilter = itemFilter;
+ onAppsUpdated();
+ }
+
/**
* Sets the number of apps per row.
*/
@@ -374,40 +382,18 @@
}
/**
- * Sets the current set of apps.
- */
- public void setApps(List<AppInfo> apps) {
- mComponentToAppMap.clear();
- addOrUpdateApps(apps);
- }
-
- /**
- * Adds or updates existing apps in the list
- */
- public void addOrUpdateApps(List<AppInfo> apps) {
- for (AppInfo app : apps) {
- mComponentToAppMap.put(app.toComponentKey(), app);
- }
- onAppsUpdated();
- }
-
- /**
- * Removes some apps from the list.
- */
- public void removeApps(List<AppInfo> apps) {
- for (AppInfo app : apps) {
- mComponentToAppMap.remove(app.toComponentKey());
- }
- onAppsUpdated();
- }
-
- /**
* Updates internals when the set of apps are updated.
*/
- private void onAppsUpdated() {
+ void onAppsUpdated() {
// Sort the list of apps
mApps.clear();
- mApps.addAll(mComponentToAppMap.values());
+
+ for (AppInfo app : mComponentToAppMap.values()) {
+ if (mItemFilter == null || mItemFilter.matches(app, null)) {
+ mApps.add(app);
+ }
+ }
+
Collections.sort(mApps, mAppNameComparator);
// As a special case for some languages (currently only Simplified Chinese), we may need to
@@ -474,42 +460,45 @@
mFastScrollerSections.clear();
mAdapterItems.clear();
- if (DEBUG_PREDICTIONS) {
- if (mPredictedAppComponents.isEmpty() && !mApps.isEmpty()) {
- mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName,
- Process.myUserHandle())));
- mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName,
- Process.myUserHandle())));
- mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName,
- Process.myUserHandle())));
- mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName,
- Process.myUserHandle())));
- }
- }
-
- // Process the predicted app components
- mPredictedApps.clear();
- if (mPredictedAppComponents != null && !mPredictedAppComponents.isEmpty() && !hasFilter()) {
- mPredictedApps.addAll(processPredictedAppComponents(mPredictedAppComponents));
-
- if (!mPredictedApps.isEmpty()) {
- // Add a section for the predictions
- lastFastScrollerSectionInfo = new FastScrollSectionInfo("");
- mFastScrollerSections.add(lastFastScrollerSectionInfo);
-
- // Add the predicted app items
- for (AppInfo info : mPredictedApps) {
- AdapterItem appItem = AdapterItem.asPredictedApp(position++, "", info,
- appIndex++);
- if (lastFastScrollerSectionInfo.fastScrollToItem == null) {
- lastFastScrollerSectionInfo.fastScrollToItem = appItem;
- }
- mAdapterItems.add(appItem);
- mFilteredApps.add(info);
+ if (!FeatureFlags.ALL_APPS_TABS_ENABLED) {
+ if (DEBUG_PREDICTIONS) {
+ if (mPredictedAppComponents.isEmpty() && !mApps.isEmpty()) {
+ mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName,
+ Process.myUserHandle())));
+ mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName,
+ Process.myUserHandle())));
+ mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName,
+ Process.myUserHandle())));
+ mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName,
+ Process.myUserHandle())));
}
-
- mAdapterItems.add(AdapterItem.asPredictionDivider(position++));
}
+
+ // Process the predicted app components
+ mPredictedApps.clear();
+ if (mPredictedAppComponents != null && !mPredictedAppComponents.isEmpty() && !hasFilter()) {
+ mPredictedApps.addAll(processPredictedAppComponents(mPredictedAppComponents));
+
+ if (!mPredictedApps.isEmpty()) {
+ // Add a section for the predictions
+ lastFastScrollerSectionInfo = new FastScrollSectionInfo("");
+ mFastScrollerSections.add(lastFastScrollerSectionInfo);
+
+ // Add the predicted app items
+ for (AppInfo info : mPredictedApps) {
+ AdapterItem appItem = AdapterItem.asPredictedApp(position++, "", info,
+ appIndex++);
+ if (lastFastScrollerSectionInfo.fastScrollToItem == null) {
+ lastFastScrollerSectionInfo.fastScrollToItem = appItem;
+ }
+ mAdapterItems.add(appItem);
+ mFilteredApps.add(info);
+ }
+
+ mAdapterItems.add(AdapterItem.asPredictionDivider(position++));
+ }
+ }
+
}
// Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
@@ -626,7 +615,6 @@
if (mSearchResults == null) {
return mApps;
}
-
ArrayList<AppInfo> result = new ArrayList<>();
for (ComponentKey key : mSearchResults) {
AppInfo match = mComponentToAppMap.get(key);
@@ -648,10 +636,6 @@
return result;
}
- public AppInfo findApp(ComponentKeyMapper<AppInfo> mapper) {
- return mapper.getItem(mComponentToAppMap);
- }
-
/**
* Returns the cached section name for the given title, recomputing and updating the cache if
* the title has no cached section name.
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderHandler.java b/src/com/android/launcher3/allapps/FloatingHeaderHandler.java
new file mode 100644
index 0000000..984966b
--- /dev/null
+++ b/src/com/android/launcher3/allapps/FloatingHeaderHandler.java
@@ -0,0 +1,139 @@
+/*
+ * 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.allapps;
+
+import android.graphics.Rect;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+import com.android.launcher3.R;
+
+public class FloatingHeaderHandler extends RecyclerView.OnScrollListener {
+
+ private final int mMaxTranslation;
+ private final View mHeaderView;
+ private final PredictionRowView mContentView;
+ private final RecyclerView mMainRV;
+ private final RecyclerView mWorkRV;
+ private final Rect mClip = new Rect(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
+
+ private boolean mHeaderHidden;
+ private int mSnappedScrolledY;
+ private int mTranslationY;
+ private int mMainScrolledY;
+ private int mWorkScrolledY;
+ private boolean mMainRVActive;
+
+ public FloatingHeaderHandler(@NonNull View header, @NonNull RecyclerView personalRV,
+ @Nullable RecyclerView workRV, int contentHeight) {
+ mHeaderView = header;
+ mContentView = mHeaderView.findViewById(R.id.header_content);
+ mContentView.getLayoutParams().height = contentHeight;
+ mMaxTranslation = contentHeight;
+ mMainRV = personalRV;
+ mMainRV.addOnScrollListener(this);
+ mWorkRV = workRV;
+ if (workRV != null) {
+ workRV.addOnScrollListener(this);
+ }
+ setMainActive(true);
+ }
+
+ public void setMainActive(boolean active) {
+ mMainRVActive = active;
+ mSnappedScrolledY = getCurrentScroll() - mMaxTranslation;
+ setExpanded(true);
+ }
+
+ public View getHeaderView() {
+ return mHeaderView;
+ }
+
+ public PredictionRowView getContentView() {
+ return mContentView;
+ }
+
+ @Override
+ public void onScrolled(RecyclerView rv, int dx, int dy) {
+ boolean isMainRV = rv == mMainRV;
+ if (isMainRV != mMainRVActive) {
+ return;
+ }
+
+ int current = isMainRV
+ ? (mMainScrolledY -= dy)
+ : (mWorkScrolledY -= dy);
+
+ if (dy == 0) {
+ setExpanded(true);
+ } else {
+ moved(current);
+ apply();
+ }
+ }
+
+ private void moved(final int currentScrollY) {
+ if (mHeaderHidden) {
+ if (currentScrollY <= mSnappedScrolledY) {
+ mSnappedScrolledY = currentScrollY;
+ } else {
+ mHeaderHidden = false;
+ }
+ mTranslationY = currentScrollY;
+ } else {
+ mTranslationY = currentScrollY - mSnappedScrolledY - mMaxTranslation;
+
+ // update state vars
+ if (mTranslationY >= 0) { // expanded: must not move down further
+ mTranslationY = 0;
+ mSnappedScrolledY = currentScrollY - mMaxTranslation;
+ } else if (mTranslationY <= -mMaxTranslation) { // hide or stay hidden
+ mHeaderHidden = true;
+ mSnappedScrolledY = currentScrollY;
+ }
+ }
+ }
+
+ private void apply() {
+ mTranslationY = Math.max(mTranslationY, -mMaxTranslation);
+ mHeaderView.setTranslationY(mTranslationY);
+ mClip.top = mMaxTranslation + mTranslationY;
+ mMainRV.setClipBounds(mClip);
+ if (mWorkRV != null) {
+ mWorkRV.setClipBounds(mClip);
+ }
+ }
+
+ private void setExpanded(boolean expand) {
+ int translateTo = expand ? 0 : -mMaxTranslation;
+ mTranslationY = translateTo;
+ apply();
+
+ mHeaderHidden = !expand;
+ mSnappedScrolledY = expand ? getCurrentScroll() - mMaxTranslation : getCurrentScroll();
+ }
+
+ public boolean isExpanded() {
+ return !mHeaderHidden;
+ }
+
+ private int getCurrentScroll() {
+ return mMainRVActive ? mMainScrolledY : mWorkScrolledY;
+ }
+
+}
diff --git a/src/com/android/launcher3/allapps/InterceptingViewPager.java b/src/com/android/launcher3/allapps/InterceptingViewPager.java
new file mode 100644
index 0000000..3524ca9
--- /dev/null
+++ b/src/com/android/launcher3/allapps/InterceptingViewPager.java
@@ -0,0 +1,91 @@
+/*
+ * 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.allapps;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import com.android.launcher3.touch.SwipeDetector;
+
+import static android.view.MotionEvent.INVALID_POINTER_ID;
+
+
+public class InterceptingViewPager extends ViewPager {
+
+
+ private final PointF mDownPos = new PointF();
+ private final PointF mLastPos = new PointF();
+ private final int mSlop;
+ private int mActivePointerId = INVALID_POINTER_ID;
+
+ public InterceptingViewPager(@NonNull Context context) {
+ super(context);
+ mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ }
+
+ public InterceptingViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ boolean result = super.onInterceptTouchEvent(ev);
+ if (!result) {
+ switch (ev.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mActivePointerId = ev.getPointerId(0);
+ mDownPos.set(ev.getX(), ev.getY());
+ mLastPos.set(mDownPos);
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ int ptrIdx = ev.getActionIndex();
+ int ptrId = ev.getPointerId(ptrIdx);
+ if (ptrId == mActivePointerId) {
+ final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
+ mDownPos.set(
+ ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
+ ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
+ mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
+ mActivePointerId = ev.getPointerId(newPointerIdx);
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (pointerIndex == INVALID_POINTER_ID) {
+ break;
+ }
+ float deltaX = ev.getX() - mDownPos.x;
+ float deltaY = ev.getY() - mDownPos.y;
+ if (Math.abs(deltaX) > mSlop && Math.abs(deltaX) > Math.abs(deltaY)) {
+ return true;
+ }
+ mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+ break;
+ default:
+ break;
+ }
+ }
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/PredictionRowView.java b/src/com/android/launcher3/allapps/PredictionRowView.java
new file mode 100644
index 0000000..5551f07
--- /dev/null
+++ b/src/com/android/launcher3/allapps/PredictionRowView.java
@@ -0,0 +1,139 @@
+/*
+ * 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.allapps;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.LinearLayout;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.ComponentKeyMapper;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+public class PredictionRowView extends LinearLayout {
+
+ private static final String TAG = "PredictionRowView";
+
+ private HashMap<ComponentKey, AppInfo> mComponentToAppMap;
+ private int mNumPredictedAppsPerRow;
+ // The set of predicted app component names
+ private final List<ComponentKeyMapper<AppInfo>> mPredictedAppComponents = new ArrayList<>();
+ // The set of predicted apps resolved from the component names and the current set of apps
+ private final List<AppInfo> mPredictedApps = new ArrayList<>();
+
+ public PredictionRowView(@NonNull Context context) {
+ this(context, null);
+ }
+
+ public PredictionRowView(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ setOrientation(LinearLayout.HORIZONTAL);
+ }
+
+ public void setComponentToAppMap(HashMap<ComponentKey, AppInfo> componentToAppMap) {
+ this.mComponentToAppMap = componentToAppMap;
+ }
+
+ /**
+ * Sets the number of apps per row.
+ */
+ public void setNumAppsPerRow(int numPredictedAppsPerRow) {
+ mNumPredictedAppsPerRow = numPredictedAppsPerRow;
+ }
+
+ public void onAppsUpdated() {
+ // TODO
+ }
+
+ /**
+ * Returns the predicted apps.
+ */
+ public List<AppInfo> getPredictedApps() {
+ return mPredictedApps;
+ }
+
+ /**
+ * Sets the current set of predicted apps.
+ *
+ * This can be called before we get the full set of applications, we should merge the results
+ * only in onAppsUpdated() which is idempotent.
+ *
+ * If the number of predicted apps is the same as the previous list of predicted apps,
+ * we can optimize by swapping them in place.
+ */
+ public void setPredictedApps(List<ComponentKeyMapper<AppInfo>> apps) {
+ mPredictedAppComponents.clear();
+ mPredictedAppComponents.addAll(apps);
+
+ List<AppInfo> newPredictedApps = processPredictedAppComponents(apps);
+ // We only need to do work if any of the visible predicted apps have changed.
+ if (!newPredictedApps.equals(mPredictedApps)) {
+ if (newPredictedApps.size() == mPredictedApps.size()) {
+ swapInNewPredictedApps(newPredictedApps);
+ } else {
+ // We need to update the appIndex of all the items.
+ onAppsUpdated();
+ }
+ }
+ }
+
+ private List<AppInfo> processPredictedAppComponents(
+ List<ComponentKeyMapper<AppInfo>> components) {
+ if (mComponentToAppMap.isEmpty()) {
+ // Apps have not been bound yet.
+ return Collections.emptyList();
+ }
+
+ List<AppInfo> predictedApps = new ArrayList<>();
+ for (ComponentKeyMapper<AppInfo> mapper : components) {
+ AppInfo info = mapper.getItem(mComponentToAppMap);
+ if (info != null) {
+ predictedApps.add(info);
+ } else {
+ if (FeatureFlags.IS_DOGFOOD_BUILD) {
+ Log.e(TAG, "Predicted app not found: " + mapper);
+ }
+ }
+ // Stop at the number of predicted apps
+ if (predictedApps.size() == mNumPredictedAppsPerRow) {
+ break;
+ }
+ }
+ return predictedApps;
+ }
+
+ /**
+ * Swaps out the old predicted apps with the new predicted apps, in place. This optimization
+ * allows us to skip an entire relayout that would otherwise be called by notifyDataSetChanged.
+ *
+ * Note: This should only be called if the # of predicted apps is the same.
+ * This method assumes that predicted apps are the first items in the adapter.
+ */
+ private void swapInNewPredictedApps(List<AppInfo> apps) {
+ // TODO
+ }
+
+}
diff --git a/src/com/android/launcher3/allapps/SearchUiManager.java b/src/com/android/launcher3/allapps/SearchUiManager.java
index 34230e0..f562b6a 100644
--- a/src/com/android/launcher3/allapps/SearchUiManager.java
+++ b/src/com/android/launcher3/allapps/SearchUiManager.java
@@ -27,7 +27,7 @@
/**
* Initializes the search manager.
*/
- void initialize(AlphabeticalAppsList appsList, AllAppsRecyclerView recyclerView);
+ void initialize(AllAppsContainerView containerView);
/**
* A {@link SpringAnimation} that will be used when the user flings.
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index ddf6e58..e65a2c4 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -31,19 +31,19 @@
import android.view.KeyEvent;
import android.view.View;
import android.widget.FrameLayout;
+
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsGridAdapter;
-import com.android.launcher3.allapps.AllAppsRecyclerView;
+import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AlphabeticalAppsList;
import com.android.launcher3.allapps.SearchUiManager;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.discovery.AppDiscoveryItem;
import com.android.launcher3.discovery.AppDiscoveryUpdateState;
import com.android.launcher3.graphics.TintedDrawableSpan;
import com.android.launcher3.util.ComponentKey;
+
import java.util.ArrayList;
/**
@@ -60,11 +60,9 @@
private ExtendedEditText mSearchInput;
private AlphabeticalAppsList mApps;
- private AllAppsRecyclerView mAppsRecyclerView;
- private AllAppsGridAdapter mAdapter;
private View mDivider;
private HeaderElevationController mElevationController;
-
+ private AllAppsContainerView mAppsView;
private SpringAnimation mSpring;
public AppsSearchContainerLayout(Context context) {
@@ -124,14 +122,12 @@
@Override
- public void initialize(
- AlphabeticalAppsList appsList, AllAppsRecyclerView recyclerView) {
- mApps = appsList;
- mAppsRecyclerView = recyclerView;
- mAppsRecyclerView.addOnScrollListener(mElevationController);
- mAdapter = (AllAppsGridAdapter) mAppsRecyclerView.getAdapter();
+ public void initialize(AllAppsContainerView appsView) {
+ mApps = appsView.getApps();
+ mAppsView = appsView;
+ appsView.addElevationController(mElevationController);
mSearchBarController.initialize(
- new DefaultAppSearchAlgorithm(appsList.getApps()), mSearchInput, mLauncher, this);
+ new DefaultAppSearchAlgorithm(mApps.getApps()), mSearchInput, mLauncher, this);
}
@Override
@@ -174,7 +170,7 @@
if (apps != null) {
mApps.setOrderedFilter(apps);
notifyResultChanged();
- mAdapter.setLastSearchQuery(query);
+ mAppsView.setLastSearchQuery(query);
}
}
@@ -201,7 +197,7 @@
private void notifyResultChanged() {
mElevationController.reset();
- mAppsRecyclerView.onSearchResultsChanged();
+ mAppsView.onSearchResultsChanged();
}
@Override
diff --git a/src/com/android/launcher3/anim/AnimationSuccessListener.java b/src/com/android/launcher3/anim/AnimationSuccessListener.java
new file mode 100644
index 0000000..9448632
--- /dev/null
+++ b/src/com/android/launcher3/anim/AnimationSuccessListener.java
@@ -0,0 +1,42 @@
+/*
+ * 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.anim;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+
+/**
+ * Extension of {@link AnimatorListenerAdapter} for listening for non-cancelled animations
+ */
+public abstract class AnimationSuccessListener extends AnimatorListenerAdapter {
+
+ protected boolean mCancelled = false;
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!mCancelled) {
+ onAnimationSuccess(animation);
+ }
+ }
+
+ public abstract void onAnimationSuccess(Animator animator);
+}
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
new file mode 100644
index 0000000..826a20e
--- /dev/null
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -0,0 +1,216 @@
+/*
+ * 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.anim;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators
+ * and durations.
+ *
+ * Note: The implementation does not support start delays on child animations or
+ * sequential playbacks.
+ */
+public abstract class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateListener {
+
+ public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration) {
+
+ /**
+ * TODO: use {@link AnimatorSet#setCurrentPlayTime(long)} once b/68382377 is fixed.
+ */
+ return new AnimatorPlaybackControllerVL(anim, duration);
+ }
+
+ private final ValueAnimator mAnimationPlayer;
+ private final long mDuration;
+
+ protected final AnimatorSet mAnim;
+
+ protected float mCurrentFraction;
+ private Runnable mEndAction;
+
+ protected AnimatorPlaybackController(AnimatorSet anim, long duration) {
+ mAnim = anim;
+ mDuration = duration;
+
+ mAnimationPlayer = ValueAnimator.ofFloat(0, 1);
+ mAnimationPlayer.setInterpolator(Interpolators.LINEAR);
+ mAnimationPlayer.addListener(new OnAnimationEndDispatcher());
+ mAnimationPlayer.addUpdateListener(this);
+ }
+
+ public AnimatorSet getTarget() {
+ return mAnim;
+ }
+
+ /**
+ * Starts playing the animation forward from current position.
+ */
+ public void start() {
+ mAnimationPlayer.setFloatValues(mCurrentFraction, 1);
+ mAnimationPlayer.setDuration(clampDuration(1 - mCurrentFraction));
+ mAnimationPlayer.start();
+ }
+
+ /**
+ * Starts playing the animation backwards from current position
+ */
+ public void reverse() {
+ mAnimationPlayer.setFloatValues(mCurrentFraction, 0);
+ mAnimationPlayer.setDuration(clampDuration(mCurrentFraction));
+ mAnimationPlayer.start();
+ }
+
+ /**
+ * Pauses the currently playing animation.
+ */
+ public void pause() {
+ mAnimationPlayer.cancel();
+ }
+
+ /**
+ * Returns the underlying animation used for controlling the set.
+ */
+ public ValueAnimator getAnimationPlayer() {
+ return mAnimationPlayer;
+ }
+
+ /**
+ * Sets the current animation position and updates all the child animators accordingly.
+ */
+ public abstract void setPlayFraction(float fraction);
+
+ public float getProgressFraction() {
+ return mCurrentFraction;
+ }
+
+ /**
+ * Sets the action to be called when the animation is completed. Also clears any
+ * previously set action.
+ */
+ public void setEndAction(Runnable runnable) {
+ mEndAction = runnable;
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ setPlayFraction((float) valueAnimator.getAnimatedValue());
+ }
+
+ protected long clampDuration(float fraction) {
+ float playPos = mDuration * fraction;
+ if (playPos <= 0) {
+ return 0;
+ } else {
+ return Math.min((long) playPos, mDuration);
+ }
+ }
+
+ public void dispatchOnStart() {
+ dispatchOnStartRecursively(mAnim);
+ }
+
+ private void dispatchOnStartRecursively(Animator animator) {
+ for (AnimatorListener l : nonNullList(animator.getListeners())) {
+ l.onAnimationStart(animator);
+ }
+
+ if (animator instanceof AnimatorSet) {
+ for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
+ dispatchOnStartRecursively(anim);
+ }
+ }
+ }
+
+ public static class AnimatorPlaybackControllerVL extends AnimatorPlaybackController {
+
+ private final ValueAnimator[] mChildAnimations;
+
+ private AnimatorPlaybackControllerVL(AnimatorSet anim, long duration) {
+ super(anim, duration);
+
+ // Build animation list
+ ArrayList<ValueAnimator> childAnims = new ArrayList<>();
+ getAnimationsRecur(mAnim, childAnims);
+ mChildAnimations = childAnims.toArray(new ValueAnimator[childAnims.size()]);
+ }
+
+ private void getAnimationsRecur(AnimatorSet anim, ArrayList<ValueAnimator> out) {
+ long forceDuration = anim.getDuration();
+ for (Animator child : anim.getChildAnimations()) {
+ if (forceDuration > 0) {
+ child.setDuration(forceDuration);
+ }
+ if (child instanceof ValueAnimator) {
+ out.add((ValueAnimator) child);
+ } else if (child instanceof AnimatorSet) {
+ getAnimationsRecur((AnimatorSet) child, out);
+ } else {
+ throw new RuntimeException("Unknown animation type " + child);
+ }
+ }
+ }
+
+ @Override
+ public void setPlayFraction(float fraction) {
+ mCurrentFraction = fraction;
+ long playPos = clampDuration(fraction);
+ for (ValueAnimator anim : mChildAnimations) {
+ anim.setCurrentPlayTime(Math.min(playPos, anim.getDuration()));
+ }
+ }
+
+ }
+
+ private class OnAnimationEndDispatcher extends AnimationSuccessListener {
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mCancelled = false;
+ }
+
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ dispatchOnEndRecursively(mAnim);
+ if (mEndAction != null) {
+ mEndAction.run();
+ }
+ }
+
+ private void dispatchOnEndRecursively(Animator animator) {
+ for (AnimatorListener l : nonNullList(animator.getListeners())) {
+ l.onAnimationEnd(animator);
+ }
+
+ if (animator instanceof AnimatorSet) {
+ for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
+ dispatchOnEndRecursively(anim);
+ }
+ }
+ }
+ }
+
+ private static <T> List<T> nonNullList(ArrayList<T> list) {
+ return list == null ? Collections.<T>emptyList() : list;
+ }
+}
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
new file mode 100644
index 0000000..8826e64
--- /dev/null
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -0,0 +1,90 @@
+/*
+ * 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.anim;
+
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.PathInterpolator;
+
+
+/**
+ * Common interpolators used in Launcher
+ */
+public class Interpolators {
+
+ public static final Interpolator LINEAR = new LinearInterpolator();
+
+ public static final Interpolator ACCEL = new AccelerateInterpolator();
+ public static final Interpolator ACCEL_1_5 = new AccelerateInterpolator(1.5f);
+ public static final Interpolator ACCEL_2 = new AccelerateInterpolator(2);
+
+ public static final Interpolator DEACCEL = new DecelerateInterpolator();
+ public static final Interpolator DEACCEL_1_5 = new DecelerateInterpolator(1.5f);
+ public static final Interpolator DEACCEL_2 = new DecelerateInterpolator(2);
+ public static final Interpolator DEACCEL_2_5 = new DecelerateInterpolator(2.5f);
+ public static final Interpolator DEACCEL_3 = new DecelerateInterpolator(3f);
+
+ public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+
+ /**
+ * Inversion of zInterpolate, compounded with an ease-out.
+ */
+ public static final Interpolator ZOOM_IN = new Interpolator() {
+
+ private static final float FOCAL_LENGTH = 0.35f;
+
+ @Override
+ public float getInterpolation(float v) {
+ return DEACCEL_3.getInterpolation(1 - zInterpolate(1 - v));
+ }
+
+ /**
+ * This interpolator emulates the rate at which the perceived scale of an object changes
+ * as its distance from a camera increases. When this interpolator is applied to a scale
+ * animation on a view, it evokes the sense that the object is shrinking due to moving away
+ * from the camera.
+ */
+ private float zInterpolate(float input) {
+ return (1.0f - FOCAL_LENGTH / (FOCAL_LENGTH + input)) /
+ (1.0f - FOCAL_LENGTH / (FOCAL_LENGTH + 1.0f));
+ }
+ };
+
+ public static final Interpolator SCROLL = new Interpolator() {
+ @Override
+ public float getInterpolation(float t) {
+ t -= 1.0f;
+ return t*t*t*t*t + 1;
+ }
+ };
+
+ public static final Interpolator SCROLL_CUBIC = new Interpolator() {
+ @Override
+ public float getInterpolation(float t) {
+ t -= 1.0f;
+ return t*t*t + 1;
+ }
+ };
+
+ private static final float FAST_FLING_PX_MS = 10;
+
+ public static Interpolator scrollInterpolatorForVelocity(float velocity) {
+ return Math.abs(velocity) > FAST_FLING_PX_MS ? SCROLL : SCROLL_CUBIC;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/anim/RevealOutlineAnimation.java b/src/com/android/launcher3/anim/RevealOutlineAnimation.java
index 51d00d9..1312da9 100644
--- a/src/com/android/launcher3/anim/RevealOutlineAnimation.java
+++ b/src/com/android/launcher3/anim/RevealOutlineAnimation.java
@@ -28,10 +28,6 @@
/** Sets the progress, from 0 to 1, of the reveal animation. */
abstract void setProgress(float progress);
- public ValueAnimator createRevealAnimator(final View revealView) {
- return createRevealAnimator(revealView, false);
- }
-
public ValueAnimator createRevealAnimator(final View revealView, boolean isReversed) {
ValueAnimator va =
isReversed ? ValueAnimator.ofFloat(1f, 0f) : ValueAnimator.ofFloat(0f, 1f);
@@ -39,8 +35,13 @@
va.addListener(new AnimatorListenerAdapter() {
private boolean mWasCanceled = false;
+ private boolean mIsClippedToOutline;
+ private ViewOutlineProvider mOldOutlineProvider;
public void onAnimationStart(Animator animation) {
+ mIsClippedToOutline = revealView.getClipToOutline();
+ mOldOutlineProvider = revealView.getOutlineProvider();
+
revealView.setOutlineProvider(RevealOutlineAnimation.this);
revealView.setClipToOutline(true);
if (shouldRemoveElevationDuringAnimation()) {
@@ -55,8 +56,8 @@
public void onAnimationEnd(Animator animation) {
if (!mWasCanceled) {
- revealView.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
- revealView.setClipToOutline(false);
+ revealView.setOutlineProvider(mOldOutlineProvider);
+ revealView.setClipToOutline(mIsClippedToOutline);
if (shouldRemoveElevationDuringAnimation()) {
revealView.setTranslationZ(0);
}
diff --git a/src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java b/src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java
index d01b26c..9c09477 100644
--- a/src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java
+++ b/src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java
@@ -18,11 +18,6 @@
import android.graphics.Rect;
-import com.android.launcher3.popup.PopupContainerWithArrow;
-
-import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_BOTTOM_CORNERS;
-import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_TOP_CORNERS;
-
/**
* A {@link RevealOutlineAnimation} that provides an outline that interpolates between two radii
* and two {@link Rect}s.
@@ -37,21 +32,12 @@
private final Rect mStartRect;
private final Rect mEndRect;
- private final @PopupContainerWithArrow.RoundedCornerFlags int mRoundedCorners;
-
public RoundedRectRevealOutlineProvider(float startRadius, float endRadius, Rect startRect,
Rect endRect) {
- this(startRadius, endRadius, startRect, endRect,
- ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS);
- }
-
- public RoundedRectRevealOutlineProvider(float startRadius, float endRadius, Rect startRect,
- Rect endRect, int roundedCorners) {
mStartRadius = startRadius;
mEndRadius = endRadius;
mStartRect = startRect;
mEndRect = endRect;
- mRoundedCorners = roundedCorners;
}
@Override
@@ -65,13 +51,7 @@
mOutline.left = (int) ((1 - progress) * mStartRect.left + progress * mEndRect.left);
mOutline.top = (int) ((1 - progress) * mStartRect.top + progress * mEndRect.top);
- if ((mRoundedCorners & ROUNDED_TOP_CORNERS) == 0) {
- mOutline.top -= mOutlineRadius;
- }
mOutline.right = (int) ((1 - progress) * mStartRect.right + progress * mEndRect.right);
mOutline.bottom = (int) ((1 - progress) * mStartRect.bottom + progress * mEndRect.bottom);
- if ((mRoundedCorners & ROUNDED_BOTTOM_CORNERS) == 0) {
- mOutline.bottom += mOutlineRadius;
- }
}
}
diff --git a/src/com/android/launcher3/anim/SpringAnimationHandler.java b/src/com/android/launcher3/anim/SpringAnimationHandler.java
index eec3a48..29a2430 100644
--- a/src/com/android/launcher3/anim/SpringAnimationHandler.java
+++ b/src/com/android/launcher3/anim/SpringAnimationHandler.java
@@ -83,6 +83,10 @@
mAnimations.add(spring);
}
+ public AnimationFactory<T> getFactory() {
+ return mAnimationFactory;
+ }
+
/**
* Adds a new or recycled animation to the list of springs handled by this class.
*
diff --git a/src/com/android/launcher3/badge/BadgeRenderer.java b/src/com/android/launcher3/badge/BadgeRenderer.java
index c2cc215..6ce334e 100644
--- a/src/com/android/launcher3/badge/BadgeRenderer.java
+++ b/src/com/android/launcher3/badge/BadgeRenderer.java
@@ -26,6 +26,7 @@
import android.graphics.Rect;
import android.graphics.Shader;
import android.support.annotation.Nullable;
+import android.util.Log;
import android.util.SparseArray;
import com.android.launcher3.R;
@@ -38,6 +39,8 @@
*/
public class BadgeRenderer {
+ private static final String TAG = "BadgeRenderer";
+
private static final boolean DOTS_ONLY = true;
// The badge sizes are defined as percentages of the app icon size.
@@ -95,6 +98,10 @@
*/
public void draw(Canvas canvas, IconPalette palette, @Nullable BadgeInfo badgeInfo,
Rect iconBounds, float badgeScale, Point spaceForOffset) {
+ if (palette == null || iconBounds == null || spaceForOffset == null) {
+ Log.e(TAG, "Invalid null argument(s) passed in call to draw.");
+ return;
+ }
mTextPaint.setColor(palette.textColor);
IconDrawer iconDrawer = badgeInfo != null && badgeInfo.isIconLarge()
? mLargeIconDrawer : mSmallIconDrawer;
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 8a1bc63..1924710 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -29,14 +29,12 @@
BaseFlags() {}
public static final boolean IS_DOGFOOD_BUILD = false;
+ public static final String AUTHORITY = "com.android.launcher3.settings".intern();
// Custom flags go below this
public static boolean LAUNCHER3_DISABLE_ICON_NORMALIZATION = false;
- public static boolean LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW = false;
// When enabled allows to use any point on the fast scrollbar to start dragging.
public static final boolean LAUNCHER3_DIRECT_SCROLL = true;
- // When enabled while all-apps open, the soft input will be set to adjust resize .
- public static final boolean LAUNCHER3_UPDATE_SOFT_INPUT_MODE = false;
// 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;
// When enabled allows use of physics based motions in the Launcher.
@@ -60,4 +58,7 @@
// Features to control Launcher3Go behavior
public static final boolean GO_DISABLE_WIDGETS = false;
+
+ // When enabled shows a work profile tab in all apps
+ public static final boolean ALL_APPS_TABS_ENABLED = false;
}
diff --git a/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java
index 06493b2..ed5cfeb 100644
--- a/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java
+++ b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java
@@ -41,7 +41,6 @@
this.intent = item.isInstantApp ? item.launchIntent : item.installIntent;
this.title = item.title;
this.iconBitmap = item.bitmap;
- this.isDisabled = ShortcutInfo.DEFAULT;
this.usingLowResIcon = false;
this.isInstantApp = item.isInstantApp;
this.isRecent = item.isRecent;
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index c843e72..db199c1 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -147,11 +147,12 @@
// Start home and pass the draw request params
PinItemDragListener listener = new PinItemDragListener(mRequest, bounds,
img.getBitmap().getWidth(), img.getWidth());
- Intent homeIntent = new Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_HOME)
- .setPackage(getPackageName())
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .putExtra(PinItemDragListener.EXTRA_PIN_ITEM_DRAG_LISTENER, listener);
+
+ Intent homeIntent = listener.addToIntent(
+ new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .setPackage(getPackageName())
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
if (!getResources().getBoolean(R.bool.allow_rotation) &&
!Utilities.isAllowRotationPrefEnabled(this) &&
diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
index 96aaee0..4629dad 100644
--- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
@@ -16,22 +16,25 @@
package com.android.launcher3.dragndrop;
+import static com.android.launcher3.LauncherState.NORMAL;
+
import android.content.ClipDescription;
import android.content.Intent;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
-import android.os.Parcel;
import android.os.SystemClock;
import android.util.Log;
import android.view.DragEvent;
import android.view.View;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
+import com.android.launcher3.states.InternalStateHandler;
import com.android.launcher3.widget.PendingItemDragHelper;
import java.util.UUID;
@@ -39,7 +42,7 @@
/**
* {@link DragSource} for handling drop from a different window.
*/
-public abstract class BaseItemDragListener implements
+public abstract class BaseItemDragListener extends InternalStateHandler implements
View.OnDragListener, DragSource, DragOptions.PreDragCondition {
private static final String TAG = "BaseItemDragListener";
@@ -67,25 +70,16 @@
mId = UUID.randomUUID().toString();
}
- protected BaseItemDragListener(Parcel parcel) {
- mPreviewRect = Rect.CREATOR.createFromParcel(parcel);
- mPreviewBitmapWidth = parcel.readInt();
- mPreviewViewWidth = parcel.readInt();
- mId = parcel.readString();
- }
-
- protected void writeToParcel(Parcel parcel, int i) {
- mPreviewRect.writeToParcel(parcel, i);
- parcel.writeInt(mPreviewBitmapWidth);
- parcel.writeInt(mPreviewViewWidth);
- parcel.writeString(mId);
- }
-
public String getMimeType() {
return MIME_TYPE_PREFIX + mId;
}
- public void setLauncher(Launcher launcher) {
+ @Override
+ public void init(Launcher launcher, boolean alreadyOnHome) {
+ AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome);
+ launcher.getStateManager().goToState(NORMAL, alreadyOnHome /* animated */);
+ launcher.getDragLayer().setOnDragListener(this);
+
mLauncher = launcher;
mDragController = launcher.getDragController();
}
@@ -182,4 +176,7 @@
mLauncher.getDragLayer().setOnDragListener(null);
}
}
+
+ @Override
+ public void onLauncherResume() { }
}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 10bd67d..818cea7 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -17,6 +17,7 @@
package com.android.launcher3.dragndrop;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+import static com.android.launcher3.LauncherState.NORMAL;
import android.content.ComponentName;
import android.content.res.Resources;
@@ -263,7 +264,7 @@
if (!accepted) {
// If it was not accepted, cleanup the state. If it was accepted, it is the
// responsibility of the drop target to cleanup the state.
- mLauncher.exitSpringLoadedDragMode(SPRING_LOADED_EXIT_DELAY);
+ mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
mDragObject.deferDragViewCleanupPostAnimation = false;
}
@@ -604,29 +605,32 @@
}
private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
- final Rect r = mRectTemp;
+ mDragObject.x = x;
+ mDragObject.y = y;
+ final Rect r = mRectTemp;
final ArrayList<DropTarget> dropTargets = mDropTargets;
final int count = dropTargets.size();
- for (int i=count-1; i>=0; i--) {
+ for (int i = count - 1; i >= 0; i--) {
DropTarget target = dropTargets.get(i);
if (!target.isDropEnabled())
continue;
target.getHitRectRelativeToDragLayer(r);
-
- mDragObject.x = x;
- mDragObject.y = y;
if (r.contains(x, y)) {
-
dropCoordinates[0] = x;
dropCoordinates[1] = y;
mLauncher.getDragLayer().mapCoordInSelfToDescendant((View) target, dropCoordinates);
-
return target;
}
}
- return null;
+ // Pass all unhandled drag to workspace. Workspace finds the correct
+ // cell layout to drop to in the existing drag/drop logic.
+ dropCoordinates[0] = x;
+ dropCoordinates[1] = y;
+ mLauncher.getDragLayer().mapCoordInSelfToDescendant(mLauncher.getWorkspace(),
+ dropCoordinates);
+ return mLauncher.getWorkspace();
}
public void setWindowToken(IBinder token) {
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index bc5aafc..9f9822c 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -33,7 +33,6 @@
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
-import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.TextView;
@@ -43,15 +42,15 @@
import com.android.launcher3.DropTargetBar;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.Launcher;
-import com.android.launcher3.PinchToOverviewListener;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsTransitionController;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
+import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.TouchController;
@@ -74,7 +73,7 @@
// Variables relating to animation of views after drop
private ValueAnimator mDropAnim = null;
- private final TimeInterpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
+ private final TimeInterpolator mCubicEaseOutInterpolator = Interpolators.DEACCEL_1_5;
@Thunk DragView mDropView = null;
@Thunk int mAnchorViewInitialScrollX = 0;
@Thunk View mAnchorView = null;
@@ -91,12 +90,10 @@
private final ViewGroupFocusHelper mFocusIndicatorHelper;
private final PageCutOutScrimDrawable mPageCutOutScrim;
- // Related to pinch-to-go-to-overview gesture.
- private PinchToOverviewListener mPinchListener = null;
-
// Handles all apps pull up interaction
private AllAppsTransitionController mAllAppsController;
+ protected TouchController[] mControllers;
private TouchController mActiveController;
/**
* Used to create a new DragLayer from XML.
@@ -121,10 +118,7 @@
mLauncher = launcher;
mDragController = dragController;
mAllAppsController = allAppsTransitionController;
-
- boolean isAccessibilityEnabled = ((AccessibilityManager) mLauncher.getSystemService(
- Context.ACCESSIBILITY_SERVICE)).isEnabled();
- onAccessibilityStateChanged(isAccessibilityEnabled);
+ mControllers = UiFactory.createTouchControllers(mLauncher);
}
public ViewGroupFocusHelper getFocusIndicatorHelper() {
@@ -141,11 +135,6 @@
return super.verifyDrawable(who) || who == mPageCutOutScrim;
}
- public void onAccessibilityStateChanged(boolean isAccessibilityEnabled) {
- mPinchListener = FeatureFlags.LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW || isAccessibilityEnabled
- ? null : new PinchToOverviewListener(mLauncher);
- }
-
public boolean isEventOverHotseat(MotionEvent ev) {
return isEventOverView(mLauncher.getHotseat(), ev);
}
@@ -191,15 +180,11 @@
return true;
}
- if (mAllAppsController.onControllerInterceptTouchEvent(ev)) {
- mActiveController = mAllAppsController;
- return true;
- }
-
- if (mPinchListener != null && mPinchListener.onControllerInterceptTouchEvent(ev)) {
- // Stop listening for scrolling etc. (onTouchEvent() handles the rest of the pinch.)
- mActiveController = mPinchListener;
- return true;
+ for (TouchController controller : mControllers) {
+ if (controller.onControllerInterceptTouchEvent(ev)) {
+ mActiveController = controller;
+ return true;
+ }
}
return false;
}
@@ -718,13 +703,14 @@
}
@Override
- public void onChildViewAdded(View parent, View child) {
- super.onChildViewAdded(parent, child);
+ public void onViewAdded(View child) {
+ super.onViewAdded(child);
updateChildIndices();
}
@Override
- public void onChildViewRemoved(View parent, View child) {
+ public void onViewRemoved(View child) {
+ super.onViewRemoved(child);
updateChildIndices();
}
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 17fad84..7c89df3 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -43,7 +43,6 @@
import android.support.animation.SpringAnimation;
import android.support.animation.SpringForce;
import android.view.View;
-import android.view.animation.DecelerateInterpolator;
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.ItemInfo;
@@ -54,6 +53,7 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.ShortcutConfigActivityInfo;
import com.android.launcher3.config.FeatureFlags;
@@ -472,7 +472,7 @@
public void crossFade(int duration) {
ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
va.setDuration(duration);
- va.setInterpolator(new DecelerateInterpolator(1.5f));
+ va.setInterpolator(Interpolators.DEACCEL_1_5);
va.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
index b9d97ac..924bb4c 100644
--- a/src/com/android/launcher3/dragndrop/PinItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
@@ -18,23 +18,18 @@
import android.annotation.TargetApi;
import android.appwidget.AppWidgetManager;
-import android.content.Intent;
import android.content.pm.LauncherApps.PinItemRequest;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.view.DragEvent;
import android.view.View;
import android.widget.RemoteViews;
import com.android.launcher3.DragSource;
import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.PendingAddItemInfo;
-import com.android.launcher3.Utilities;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
@@ -46,9 +41,7 @@
* in the source window and is passed on to the Launcher activity as an Intent extra.
*/
@TargetApi(Build.VERSION_CODES.O)
-public class PinItemDragListener extends BaseItemDragListener implements Parcelable {
-
- public static final String EXTRA_PIN_ITEM_DRAG_LISTENER = "pin_item_drag_listener";
+public class PinItemDragListener extends BaseItemDragListener {
private final PinItemRequest mRequest;
@@ -58,22 +51,6 @@
mRequest = request;
}
- private PinItemDragListener(Parcel parcel) {
- super(parcel);
- mRequest = PinItemRequest.CREATOR.createFromParcel(parcel);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int i) {
- super.writeToParcel(parcel, i);
- mRequest.writeToParcel(parcel, i);
- }
-
@Override
protected boolean onDragStart(DragEvent event) {
if (!mRequest.isValid()) {
@@ -126,33 +103,4 @@
}
return null;
}
-
- public static boolean handleDragRequest(Launcher launcher, Intent intent) {
- if (!Utilities.ATLEAST_OREO) {
- return false;
- }
- if (intent == null || !Intent.ACTION_MAIN.equals(intent.getAction())) {
- return false;
- }
- Parcelable dragExtra = intent.getParcelableExtra(EXTRA_PIN_ITEM_DRAG_LISTENER);
- if (dragExtra instanceof PinItemDragListener) {
- PinItemDragListener dragListener = (PinItemDragListener) dragExtra;
- dragListener.setLauncher(launcher);
-
- launcher.getDragLayer().setOnDragListener(dragListener);
- return true;
- }
- return false;
- }
-
- public static final Parcelable.Creator<PinItemDragListener> CREATOR =
- new Parcelable.Creator<PinItemDragListener>() {
- public PinItemDragListener createFromParcel(Parcel source) {
- return new PinItemDragListener(source);
- }
-
- public PinItemDragListener[] newArray(int size) {
- return new PinItemDragListener[size];
- }
- };
}
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index fcc4f0e..2168001 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -17,6 +17,7 @@
package com.android.launcher3.folder;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+import static com.android.launcher3.LauncherState.NORMAL;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -1243,7 +1244,7 @@
mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, mLauncher.getModelWriter());
}
- mLauncher.exitSpringLoadedDragMode(SPRING_LOADED_EXIT_DELAY);
+ mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
if (d.stateAnnouncer != null) {
d.stateAnnouncer.completeAction(R.string.item_moved);
}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 900781c..5983029 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -16,6 +16,9 @@
package com.android.launcher3.folder;
+import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
+import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION;
+
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.content.Context;
@@ -33,8 +36,6 @@
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import com.android.launcher3.Alarm;
@@ -56,6 +57,7 @@
import com.android.launcher3.StylusEventHelper;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.badge.BadgeRenderer;
import com.android.launcher3.badge.FolderBadgeInfo;
import com.android.launcher3.dragndrop.BaseItemDragListener;
@@ -68,9 +70,6 @@
import java.util.ArrayList;
import java.util.List;
-import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
-import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION;
-
/**
* An icon that can appear on in the workspace representing an {@link Folder}.
*/
@@ -350,7 +349,7 @@
float finalScale = scale * scaleRelativeToDragLayer;
dragLayer.animateView(animateView, from, to, finalAlpha,
1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION,
- new DecelerateInterpolator(2), new AccelerateInterpolator(2),
+ Interpolators.DEACCEL_2, Interpolators.ACCEL_2,
null, DragLayer.ANIMATION_END_DISAPPEAR, null);
mFolder.hideItem(item);
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index f4ac0a1..9e5bc4f 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -27,7 +27,6 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewDebug;
-import android.view.animation.DecelerateInterpolator;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
@@ -43,6 +42,7 @@
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace.ItemOperator;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
import com.android.launcher3.pageindicators.PageIndicator;
import com.android.launcher3.util.Thunk;
@@ -511,7 +511,7 @@
int scroll = getScrollForPage(getNextPage()) + hint;
int delta = scroll - getScrollX();
if (delta != 0) {
- mScroller.setInterpolator(new DecelerateInterpolator());
+ mScroller.setInterpolator(Interpolators.DEACCEL);
mScroller.startScroll(getScrollX(), 0, delta, 0, Folder.SCROLL_HINT_DURATION);
invalidate();
}
diff --git a/src/com/android/launcher3/graphics/GradientView.java b/src/com/android/launcher3/graphics/GradientView.java
index 5455b43..bacb063 100644
--- a/src/com/android/launcher3/graphics/GradientView.java
+++ b/src/com/android/launcher3/graphics/GradientView.java
@@ -29,12 +29,12 @@
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View;
-import android.view.animation.AccelerateInterpolator;
import android.view.animation.Interpolator;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.dynamicui.WallpaperColorInfo;
import com.android.launcher3.util.Themes;
@@ -64,7 +64,7 @@
private final int mMaskHeight, mMaskWidth;
private final int mAlphaColors;
private final Paint mDebugPaint = DEBUG ? new Paint() : null;
- private final Interpolator mAccelerator = new AccelerateInterpolator();
+ private final Interpolator mAccelerator = Interpolators.ACCEL;
private final float mAlphaStart;
private final WallpaperColorInfo mWallpaperColorInfo;
private final int mScrimColor;
diff --git a/src/com/android/launcher3/graphics/HolographicOutlineHelper.java b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
index 9e67f56..fdf2d67 100644
--- a/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
+++ b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
@@ -16,6 +16,8 @@
package com.android.launcher3.graphics;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_ADAPTIVE_ICON;
+
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BlurMaskFilter;
@@ -29,6 +31,7 @@
import android.util.SparseArray;
import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.R;
/**
@@ -37,6 +40,12 @@
*/
public class HolographicOutlineHelper {
+ /**
+ * Bitmap used as shadow for Adaptive icons
+ */
+ public static final Bitmap ADAPTIVE_ICON_SHADOW_BITMAP =
+ Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
+
private static HolographicOutlineHelper sInstance;
private final Canvas mCanvas = new Canvas();
@@ -63,6 +72,11 @@
}
public Bitmap createMediumDropShadow(BubbleTextView view) {
+ if (view.getTag() instanceof ItemInfoWithIcon &&
+ ((((ItemInfoWithIcon) view.getTag()).runtimeStatusFlags & FLAG_ADAPTIVE_ICON)
+ != 0)) {
+ return ADAPTIVE_ICON_SHADOW_BITMAP;
+ }
Drawable drawable = view.getIcon();
if (drawable == null) {
return null;
@@ -119,7 +133,7 @@
}
public void recycleShadowBitmap(Bitmap bitmap) {
- if (bitmap != null) {
+ if (bitmap != null && bitmap != ADAPTIVE_ICON_SHADOW_BITMAP) {
mBitmapCache.put((bitmap.getWidth() << 16) | bitmap.getHeight(), bitmap);
}
}
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
index d471af6..1eddb2c 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -72,7 +72,7 @@
PackageManager packageManager = context.getPackageManager();
// the resource
try {
- Resources resources = packageManager.getResourcesForApplication(iconRes.resourceName);
+ Resources resources = packageManager.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
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index 06dc7ac..6d486ee 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -30,9 +30,9 @@
import android.graphics.Rect;
import android.util.Property;
import android.util.SparseArray;
-import android.view.animation.LinearInterpolator;
import com.android.launcher3.FastBitmapDrawable;
+import com.android.launcher3.anim.Interpolators;
import java.lang.ref.WeakReference;
@@ -226,7 +226,7 @@
mCurrentAnim = ObjectAnimator.ofFloat(this, INTERNAL_STATE, finalProgress);
mCurrentAnim.setDuration(
(long) ((finalProgress - mInternalStateProgress) * DURATION_SCALE));
- mCurrentAnim.setInterpolator(new LinearInterpolator());
+ mCurrentAnim.setInterpolator(Interpolators.LINEAR);
if (isFinish) {
mCurrentAnim.addListener(new AnimatorListenerAdapter() {
@Override
@@ -237,7 +237,6 @@
}
mCurrentAnim.start();
}
-
}
/**
diff --git a/src/com/android/launcher3/keyboard/CustomActionsPopup.java b/src/com/android/launcher3/keyboard/CustomActionsPopup.java
index 150522e..938955c 100644
--- a/src/com/android/launcher3/keyboard/CustomActionsPopup.java
+++ b/src/com/android/launcher3/keyboard/CustomActionsPopup.java
@@ -27,7 +27,7 @@
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
-import com.android.launcher3.popup.BaseActionPopup;
+import com.android.launcher3.popup.PopupContainerWithArrow;
import java.util.ArrayList;
import java.util.Collections;
@@ -46,7 +46,7 @@
public CustomActionsPopup(Launcher launcher, View icon) {
mLauncher = launcher;
mIcon = icon;
- BaseActionPopup container = BaseActionPopup.getOpen(launcher);
+ PopupContainerWithArrow container = PopupContainerWithArrow.getOpen(launcher);
if (container != null) {
mDelegate = container.getAccessibilityDelegate();
} else {
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index 81333b1..00ee009 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -91,7 +91,8 @@
return getFieldName(t.controlType, ControlType.class);
case Target.Type.CONTAINER:
String str = getFieldName(t.containerType, ContainerType.class);
- if (t.containerType == ContainerType.WORKSPACE) {
+ if (t.containerType == ContainerType.WORKSPACE ||
+ t.containerType == ContainerType.HOTSEAT) {
str += " id=" + t.pageIndex;
} else if (t.containerType == ContainerType.FOLDER) {
str += " grid(" + t.gridX + "," + t.gridY+ ")";
@@ -105,13 +106,16 @@
private static String getItemStr(Target t) {
String typeStr = getFieldName(t.itemType, ItemType.class);
if (t.packageNameHash != 0) {
- typeStr += ", packageHash=" + t.packageNameHash + ", predictiveRank=" + t.predictedRank;
+ typeStr += ", packageHash=" + t.packageNameHash;
}
if (t.componentHash != 0) {
- typeStr += ", componentHash=" + t.componentHash + ", predictiveRank=" + t.predictedRank;
+ typeStr += ", componentHash=" + t.componentHash;
}
if (t.intentHash != 0) {
- typeStr += ", intentHash=" + t.intentHash + ", predictiveRank=" + t.predictedRank;
+ typeStr += ", intentHash=" + t.intentHash;
+ }
+ if (t.packageNameHash != 0 || t.componentHash != 0 || t.intentHash != 0) {
+ typeStr += ", predictiveRank=" + t.predictedRank;
}
return typeStr + ", grid(" + t.gridX + "," + t.gridY + "), span(" + t.spanX + "," + t.spanY
+ "), pageIdx=" + t.pageIndex;
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 1a6dade..243dbea 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -232,6 +232,10 @@
event.action.dir = dir;
event.srcTarget[0].pageIndex = pageIndex;
dispatchUserEvent(event, null);
+
+ if (action == Action.Touch.SWIPE) {
+ resetElapsedContainerMillis();
+ }
}
public void logActionOnItem(int action, int dir, int itemType) {
@@ -323,6 +327,7 @@
ev.actionDurationMillis);
log += "\n isInLandscapeMode " + ev.isInLandscapeMode;
log += "\n isInMultiWindowMode " + ev.isInMultiWindowMode;
+ log += "\n";
Log.d(TAG, log);
}
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index bc7da9b..ccef9b7 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -32,6 +32,7 @@
import android.util.Log;
import android.util.LongSparseArray;
+import com.android.launcher3.AppInfo;
import com.android.launcher3.IconCache;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
@@ -48,7 +49,6 @@
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.LongArrayMap;
-import com.android.launcher3.util.PackageManagerHelper;
import java.net.URISyntaxException;
import java.security.InvalidParameterException;
@@ -202,7 +202,6 @@
return TextUtils.isEmpty(title) ? "" : Utilities.trim(title);
}
-
/**
* Make an ShortcutInfo object for a restored application or shortcut item that points
* to a package that is not yet installed on the system.
@@ -274,8 +273,8 @@
info.iconBitmap = icon != null ? icon : info.iconBitmap;
}
- if (lai != null && PackageManagerHelper.isAppSuspended(lai.getApplicationInfo())) {
- info.isDisabled = ShortcutInfo.FLAG_DISABLED_SUSPENDED;
+ if (lai != null) {
+ AppInfo.updateRuntimeFlagsForActivityTarget(info, lai);
}
// from the db
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 5386fb4..310416f 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -25,10 +25,9 @@
import android.content.pm.LauncherActivityInfo;
import android.content.pm.PackageInstaller;
import android.graphics.Bitmap;
+import android.graphics.drawable.AdaptiveIconDrawable;
import android.os.Handler;
import android.os.Process;
-import android.os.SystemClock;
-import android.os.Trace;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
@@ -37,6 +36,7 @@
import com.android.launcher3.AllAppsList;
import com.android.launcher3.AppInfo;
+import com.android.launcher3.ClickShadowView;
import com.android.launcher3.FolderInfo;
import com.android.launcher3.IconCache;
import com.android.launcher3.InstallShortcutReceiver;
@@ -54,6 +54,7 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIconPreviewVerifier;
+import com.android.launcher3.graphics.IconNormalizer;
import com.android.launcher3.graphics.LauncherIcons;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.provider.ImportDataTask;
@@ -76,6 +77,9 @@
import java.util.Map;
import java.util.concurrent.CancellationException;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
/**
@@ -144,7 +148,9 @@
TraceHelper.beginSection(TAG);
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
- TraceHelper.partitionSection(TAG, "step 1.1: loading workspace");
+ TraceHelper.partitionSection(TAG, "step 1.1: loading UI resources");
+ loadUiResources();
+ TraceHelper.partitionSection(TAG, "step 1.2: loading workspace");
loadWorkspace();
verifyNotStopped();
@@ -207,6 +213,14 @@
this.notify();
}
+ public void loadUiResources() {
+ if (Utilities.ATLEAST_OREO) {
+ ClickShadowView.setAdaptiveIconScaleFactor(
+ IconNormalizer.getInstance(mApp.getContext()).getScale(
+ new AdaptiveIconDrawable(null, null), null, null, null));
+ }
+ }
+
private void loadWorkspace() {
final Context context = mApp.getContext();
final ContentResolver contentResolver = context.getContentResolver();
@@ -466,13 +480,13 @@
true /* badged */, fallbackIconProvider);
if (pmHelper.isAppSuspended(
pinnedShortcut.getPackage(), info.user)) {
- info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
+ info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
}
intent = info.intent;
} else {
// Create a shortcut info in disabled mode for now.
info = c.loadSimpleShortcut();
- info.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
+ info.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
}
} else { // item type == ITEM_TYPE_SHORTCUT
info = c.loadSimpleShortcut();
@@ -480,7 +494,7 @@
// Shortcuts are only available on the primary profile
if (!TextUtils.isEmpty(targetPkg)
&& pmHelper.isAppSuspended(targetPkg, c.user)) {
- disabledState |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
+ disabledState |= FLAG_DISABLED_SUSPENDED;
}
// App shortcuts that used to be automatically added to Launcher
@@ -503,9 +517,9 @@
info.rank = c.getInt(rankIndex);
info.spanX = 1;
info.spanY = 1;
- info.isDisabled |= disabledState;
+ info.runtimeStatusFlags |= disabledState;
if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
- info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
+ info.runtimeStatusFlags |= FLAG_DISABLED_SAFEMODE;
}
if (c.restoreFlag != 0 && !TextUtils.isEmpty(targetPkg)) {
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 53a9862..470dadf 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -244,9 +244,9 @@
infoUpdated = true;
}
- int oldDisabledFlags = si.isDisabled;
- si.isDisabled = flagOp.apply(si.isDisabled);
- if (si.isDisabled != oldDisabledFlags) {
+ int oldRuntimeFlags = si.runtimeStatusFlags;
+ si.runtimeStatusFlags = flagOp.apply(si.runtimeStatusFlags);
+ if (si.runtimeStatusFlags != oldRuntimeFlags) {
shortcutUpdated = true;
}
}
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 8170f9a..2e9ac72 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.model;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
+
import android.content.Context;
import android.os.UserHandle;
@@ -87,12 +89,12 @@
removedKeys.add(key);
continue;
}
- si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
+ si.runtimeStatusFlags &= ~FLAG_DISABLED_LOCKED_USER;
si.updateFromDeepShortcutInfo(shortcut, context);
si.iconBitmap = LauncherIcons.createShortcutIcon(shortcut, context,
si.iconBitmap);
} else {
- si.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
+ si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
}
updatedShortcutInfos.add(si);
}
diff --git a/src/com/android/launcher3/notification/Interpolators.java b/src/com/android/launcher3/notification/Interpolators.java
deleted file mode 100644
index 5c3b22a..0000000
--- a/src/com/android/launcher3/notification/Interpolators.java
+++ /dev/null
@@ -1,37 +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.notification;
-
-import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
-
-/**
- * Utility class to receive interpolators from.
- *
- * This class was copied from com.android.systemui.
- */
-public class Interpolators {
- public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
- public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
- public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
-
- /**
- * Interpolator to be used when animating a move based on a click. Pair with enough duration.
- */
- public static final Interpolator TOUCH_RESPONSE =
- new PathInterpolator(0.3f, 0f, 0.1f, 1f);
-}
diff --git a/src/com/android/launcher3/notification/NotificationFooterLayout.java b/src/com/android/launcher3/notification/NotificationFooterLayout.java
index 3cf3ff6..1216a27 100644
--- a/src/com/android/launcher3/notification/NotificationFooterLayout.java
+++ b/src/com/android/launcher3/notification/NotificationFooterLayout.java
@@ -23,22 +23,18 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
-import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
-import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.anim.PropertyResetListener;
-import com.android.launcher3.popup.BaseActionPopup;
-import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.util.Themes;
import java.util.ArrayList;
import java.util.Iterator;
@@ -61,11 +57,12 @@
private final List<NotificationInfo> mNotifications = new ArrayList<>();
private final List<NotificationInfo> mOverflowNotifications = new ArrayList<>();
private final boolean mRtl;
+ private final int mBackgroundColor;
FrameLayout.LayoutParams mIconLayoutParams;
private View mOverflowEllipsis;
private LinearLayout mIconRow;
- private int mBackgroundColor;
+ private NotificationItemView mContainer;
public NotificationFooterLayout(Context context) {
this(context, null, 0);
@@ -93,14 +90,19 @@
int availableIconRowSpace = footerWidth - paddingEnd - ellipsisSpace
- iconSize * MAX_FOOTER_NOTIFICATIONS;
mIconLayoutParams.setMarginStart(availableIconRowSpace / MAX_FOOTER_NOTIFICATIONS);
+
+ mBackgroundColor = Themes.getAttrColor(context, R.attr.popupColorPrimary);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mOverflowEllipsis = findViewById(R.id.overflow);
- mIconRow = (LinearLayout) findViewById(R.id.icon_row);
- mBackgroundColor = ((ColorDrawable) getBackground()).getColor();
+ mIconRow = findViewById(R.id.icon_row);
+ }
+
+ void setContainer(NotificationItemView container) {
+ mContainer = container;
}
/**
@@ -198,25 +200,8 @@
updateOverflowEllipsisVisibility();
if (mIconRow.getChildCount() == 0) {
// There are no more icons in the footer, so hide it.
- BaseActionPopup popup = BaseActionPopup.getOpen(
- Launcher.getLauncher(getContext()));
- if (popup instanceof PopupContainerWithArrow) {
- final int newHeight = getResources().getDimensionPixelSize(
- R.dimen.notification_empty_footer_height);
- Animator collapseFooter = ((PopupContainerWithArrow) popup)
- .reduceNotificationViewHeight(getHeight() - newHeight,
- getResources().getInteger(R.integer.config_removeNotificationViewDuration));
- collapseFooter.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- ((ViewGroup) getParent()).findViewById(R.id.divider).setVisibility(GONE);
- // Keep view around because gutter is aligned to it, but remove height to
- // both hide the view and keep calculations correct for last dismissal.
- getLayoutParams().height = newHeight;
- requestLayout();
- }
- });
- collapseFooter.start();
+ if (mContainer != null) {
+ mContainer.removeFooter();
}
}
}
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
index ab94c32..5bbd19c 100644
--- a/src/com/android/launcher3/notification/NotificationItemView.java
+++ b/src/com/android/launcher3/notification/NotificationItemView.java
@@ -16,117 +16,102 @@
package com.android.launcher3.notification;
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
import android.app.Notification;
import android.content.Context;
import android.graphics.Rect;
import android.support.annotation.Nullable;
-import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
-import android.widget.FrameLayout;
+import android.view.ViewGroup.MarginLayoutParams;
import android.widget.TextView;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
-import com.android.launcher3.anim.PropertyResetListener;
-import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.graphics.IconPalette;
-import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
-import com.android.launcher3.popup.PopupItemView;
+import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.touch.SwipeDetector;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.Themes;
import java.util.List;
+import static com.android.launcher3.touch.SwipeDetector.HORIZONTAL;
+
/**
- * A {@link FrameLayout} that contains a header, main view and a footer.
- * The main view contains the icon and text (title + subtext) of the first notification.
- * The footer contains: A list of just the icons of all the notifications past the first one.
- * @see NotificationFooterLayout
+ * Utility class to manage notification UI
*/
-public class NotificationItemView extends PopupItemView implements LogContainerProvider {
+public class NotificationItemView {
private static final Rect sTempRect = new Rect();
- private TextView mHeaderText;
- private TextView mHeaderCount;
- private NotificationMainView mMainView;
- private NotificationFooterLayout mFooter;
- private SwipeDetector mSwipeDetector;
+ private final Context mContext;
+ private final PopupContainerWithArrow mContainer;
+
+ private final TextView mHeaderText;
+ private final TextView mHeaderCount;
+ private final NotificationMainView mMainView;
+ private final NotificationFooterLayout mFooter;
+ private final SwipeDetector mSwipeDetector;
+ private final View mIconView;
+
+ private final View mHeader;
+ private final View mDivider;
+
+ private View mGutter;
+
+ private boolean mIgnoreTouch = false;
private boolean mAnimatingNextIcon;
private int mNotificationHeaderTextColor = Notification.COLOR_DEFAULT;
- public NotificationItemView(Context context) {
- this(context, null, 0);
- }
+ public NotificationItemView(PopupContainerWithArrow container) {
+ mContainer = container;
+ mContext = container.getContext();
- public NotificationItemView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
+ mHeaderText = container.findViewById(R.id.notification_text);
+ mHeaderCount = container.findViewById(R.id.notification_count);
+ mMainView = container.findViewById(R.id.main_view);
+ mFooter = container.findViewById(R.id.footer);
+ mIconView = container.findViewById(R.id.popup_item_icon);
- public NotificationItemView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
+ mHeader = container.findViewById(R.id.header);
+ mDivider = container.findViewById(R.id.divider);
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mHeaderText = findViewById(R.id.notification_text);
- mHeaderCount = findViewById(R.id.notification_count);
- mMainView = findViewById(R.id.main_view);
- mFooter = findViewById(R.id.footer);
-
- mSwipeDetector = new SwipeDetector(getContext(), mMainView, SwipeDetector.HORIZONTAL);
+ mSwipeDetector = new SwipeDetector(mContext, mMainView, HORIZONTAL);
mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false);
mMainView.setSwipeDetector(mSwipeDetector);
+ mFooter.setContainer(this);
}
- public NotificationMainView getMainView() {
- return mMainView;
+ public void addGutter() {
+ if (mGutter == null) {
+ mGutter = mContainer.inflateAndAdd(R.layout.notification_gutter);
+ }
}
- /**
- * This method is used to calculate the height to remove when dismissing the last notification.
- * We subtract the height of the footer in this case since the footer should be gone or in the
- * process of being removed.
- * @return The height of the entire notification item, minus the footer if it still exists.
- */
- public int getHeightMinusFooter() {
- if (mFooter.getParent() == null) {
- return getHeight();
+ public void removeFooter() {
+ if (mContainer.indexOfChild(mFooter) >= 0) {
+ mContainer.removeView(mFooter);
+ mContainer.removeView(mDivider);
}
- int excessFooterHeight = mFooter.getHeight() - getResources().getDimensionPixelSize(
- R.dimen.notification_empty_footer_height);
- return getHeight() - excessFooterHeight;
}
- public Animator animateHeightRemoval(int heightToRemove, boolean shouldRemoveFromTop) {
- AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
+ public void inverseGutterMargin() {
+ MarginLayoutParams lp = (MarginLayoutParams) mGutter.getLayoutParams();
+ int top = lp.topMargin;
+ lp.topMargin = lp.bottomMargin;
+ lp.bottomMargin = top;
+ }
- Rect startRect = new Rect(mPillRect);
- Rect endRect = new Rect(mPillRect);
- if (shouldRemoveFromTop) {
- endRect.top += heightToRemove;
- } else {
- endRect.bottom -= heightToRemove;
- }
- anim.play(new RoundedRectRevealOutlineProvider(getBackgroundRadius(), getBackgroundRadius(),
- startRect, endRect, mRoundedCorners).createRevealAnimator(this, false));
+ public void removeAllViews() {
+ mContainer.removeView(mMainView);
+ mContainer.removeView(mHeader);
- View bottomGutter = findViewById(R.id.gutter_bottom);
- if (bottomGutter != null && bottomGutter.getVisibility() == VISIBLE) {
- Animator translateGutter = ObjectAnimator.ofFloat(bottomGutter, TRANSLATION_Y,
- -heightToRemove);
- translateGutter.addListener(new PropertyResetListener<>(TRANSLATION_Y, 0f));
- anim.play(translateGutter);
+ if (mContainer.indexOfChild(mFooter) >= 0) {
+ mContainer.removeView(mFooter);
+ mContainer.removeView(mDivider);
}
- return anim;
+ if (mGutter != null) {
+ mContainer.removeView(mGutter);
+ }
}
public void updateHeader(int notificationCount, @Nullable IconPalette palette) {
@@ -134,32 +119,44 @@
if (palette != null) {
if (mNotificationHeaderTextColor == Notification.COLOR_DEFAULT) {
mNotificationHeaderTextColor =
- IconPalette.resolveContrastColor(getContext(), palette.dominantColor,
- Themes.getAttrColor(getContext(), R.attr.popupColorPrimary));
+ IconPalette.resolveContrastColor(mContext, palette.dominantColor,
+ Themes.getAttrColor(mContext, R.attr.popupColorPrimary));
}
mHeaderText.setTextColor(mNotificationHeaderTextColor);
mHeaderCount.setTextColor(mNotificationHeaderTextColor);
}
}
- @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ sTempRect.set(mMainView.getLeft(), mMainView.getTop(),
+ mMainView.getRight(), mMainView.getBottom());
+ mIgnoreTouch = !sTempRect.contains((int) ev.getX(), (int) ev.getY());
+ if (!mIgnoreTouch) {
+ mContainer.getParent().requestDisallowInterceptTouchEvent(true);
+ }
+ }
+ if (mIgnoreTouch) {
+ return false;
+ }
if (mMainView.getNotificationInfo() == null) {
// The notification hasn't been populated yet.
return false;
}
- getParent().requestDisallowInterceptTouchEvent(true);
+
mSwipeDetector.onTouchEvent(ev);
return mSwipeDetector.isDraggingOrSettling();
}
- @Override
public boolean onTouchEvent(MotionEvent ev) {
+ if (mIgnoreTouch) {
+ return false;
+ }
if (mMainView.getNotificationInfo() == null) {
// The notification hasn't been populated yet.
return false;
}
- return mSwipeDetector.onTouchEvent(ev) || super.onTouchEvent(ev);
+ return mSwipeDetector.onTouchEvent(ev);
}
public void applyNotificationInfos(final List<NotificationInfo> notificationInfos) {
@@ -168,7 +165,7 @@
}
NotificationInfo mainNotification = notificationInfos.get(0);
- mMainView.applyNotificationInfo(mainNotification, mIconView);
+ mMainView.applyNotificationInfo(mainNotification, false);
for (int i = 1; i < notificationInfos.size(); i++) {
mFooter.addNotificationInfo(notificationInfos.get(i));
@@ -182,29 +179,18 @@
if (dismissedMainNotification && !mAnimatingNextIcon) {
// Animate the next icon into place as the new main notification.
mAnimatingNextIcon = true;
- mMainView.setVisibility(INVISIBLE);
- mMainView.setTranslationX(0);
+ mMainView.setContentVisibility(View.INVISIBLE);
+ mMainView.setContentTranslation(0);
mIconView.getGlobalVisibleRect(sTempRect);
- mFooter.animateFirstNotificationTo(sTempRect,
- new NotificationFooterLayout.IconAnimationEndListener() {
- @Override
- public void onIconAnimationEnd(NotificationInfo newMainNotification) {
- if (newMainNotification != null) {
- mMainView.applyNotificationInfo(newMainNotification, mIconView, true);
- mMainView.setVisibility(VISIBLE);
- }
- mAnimatingNextIcon = false;
+ mFooter.animateFirstNotificationTo(sTempRect, (newMainNotification) -> {
+ if (newMainNotification != null) {
+ mMainView.applyNotificationInfo(newMainNotification, true);
+ mMainView.setContentVisibility(View.VISIBLE);
}
+ mAnimatingNextIcon = false;
});
} else {
mFooter.trimNotifications(notificationKeys);
}
}
-
- @Override
- public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
- LauncherLogProto.Target targetParent) {
- target.itemType = LauncherLogProto.ItemType.NOTIFICATION;
- targetParent.containerType = LauncherLogProto.ContainerType.DEEPSHORTCUTS;
- }
}
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index 5aff28d..33caded 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -16,22 +16,28 @@
package com.android.launcher3.notification;
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+
+import android.animation.Animator;
import android.animation.ObjectAnimator;
+import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.RippleDrawable;
+import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.FloatProperty;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewPropertyAnimator;
import android.widget.FrameLayout;
import android.widget.TextView;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.touch.OverScroll;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -41,13 +47,33 @@
* A {@link android.widget.FrameLayout} that contains a single notification,
* e.g. icon + title + text.
*/
+@TargetApi(Build.VERSION_CODES.N)
public class NotificationMainView extends FrameLayout implements SwipeDetector.Listener {
+ private static FloatProperty<NotificationMainView> CONTENT_TRANSLATION =
+ new FloatProperty<NotificationMainView>("contentTranslation") {
+ @Override
+ public void setValue(NotificationMainView view, float v) {
+ view.setContentTranslation(v);
+ }
+
+ @Override
+ public Float get(NotificationMainView view) {
+ return view.mTextAndBackground.getTranslationX();
+ }
+ };
+
+ // This is used only to track the notification view, so that it can be properly logged.
+ public static final ItemInfo NOTIFICATION_ITEM_INFO = new ItemInfo();
+
+ private final ObjectAnimator mContentTranslateAnimator;
+
private NotificationInfo mNotificationInfo;
private ViewGroup mTextAndBackground;
private int mBackgroundColor;
private TextView mTitleView;
private TextView mTextView;
+ private View mIconView;
private SwipeDetector mSwipeDetector;
@@ -61,25 +87,24 @@
public NotificationMainView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+
+ mContentTranslateAnimator = ObjectAnimator.ofFloat(this, CONTENT_TRANSLATION, 0);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mTextAndBackground = (ViewGroup) findViewById(R.id.text_and_background);
+ mTextAndBackground = findViewById(R.id.text_and_background);
ColorDrawable colorBackground = (ColorDrawable) mTextAndBackground.getBackground();
mBackgroundColor = colorBackground.getColor();
RippleDrawable rippleBackground = new RippleDrawable(ColorStateList.valueOf(
Themes.getAttrColor(getContext(), android.R.attr.colorControlHighlight)),
colorBackground, null);
mTextAndBackground.setBackground(rippleBackground);
- mTitleView = (TextView) mTextAndBackground.findViewById(R.id.title);
- mTextView = (TextView) mTextAndBackground.findViewById(R.id.text);
- }
-
- public void applyNotificationInfo(NotificationInfo mainNotification, View iconView) {
- applyNotificationInfo(mainNotification, iconView, false);
+ mTitleView = mTextAndBackground.findViewById(R.id.title);
+ mTextView = mTextAndBackground.findViewById(R.id.text);
+ mIconView = findViewById(R.id.popup_item_icon);
}
public void setSwipeDetector(SwipeDetector swipeDetector) {
@@ -89,8 +114,7 @@
/**
* Sets the content of this view, animating it after a new icon shifts up if necessary.
*/
- public void applyNotificationInfo(NotificationInfo mainNotification, View iconView,
- boolean animate) {
+ public void applyNotificationInfo(NotificationInfo mainNotification, boolean animate) {
mNotificationInfo = mainNotification;
CharSequence title = mNotificationInfo.title;
CharSequence text = mNotificationInfo.text;
@@ -102,20 +126,30 @@
mTitleView.setText(TextUtils.isEmpty(title) ? text.toString() : title.toString());
mTextView.setVisibility(GONE);
}
- iconView.setBackground(mNotificationInfo.getIconForBackground(getContext(),
+ mIconView.setBackground(mNotificationInfo.getIconForBackground(getContext(),
mBackgroundColor));
if (mNotificationInfo.intent != null) {
setOnClickListener(mNotificationInfo);
}
- setTranslationX(0);
+ setContentTranslation(0);
// Add a dummy ItemInfo so that logging populates the correct container and item types
// instead of DEFAULT_CONTAINERTYPE and DEFAULT_ITEMTYPE, respectively.
- setTag(new ItemInfo());
+ setTag(NOTIFICATION_ITEM_INFO);
if (animate) {
ObjectAnimator.ofFloat(mTextAndBackground, ALPHA, 0, 1).setDuration(150).start();
}
}
+ public void setContentTranslation(float translation) {
+ mTextAndBackground.setTranslationX(translation);
+ mIconView.setTranslationX(translation);
+ }
+
+ public void setContentVisibility(int visibility) {
+ mTextAndBackground.setVisibility(visibility);
+ mIconView.setVisibility(visibility);
+ }
+
public NotificationInfo getNotificationInfo() {
return mNotificationInfo;
}
@@ -142,9 +176,9 @@
@Override
public boolean onDrag(float displacement, float velocity) {
- setTranslationX(canChildBeDismissed()
+ setContentTranslation(canChildBeDismissed()
? displacement : OverScroll.dampedScroll(displacement, getWidth()));
- animate().cancel();
+ mContentTranslateAnimator.cancel();
return true;
}
@@ -152,6 +186,7 @@
public void onDragEnd(float velocity, boolean fling) {
final boolean willExit;
final float endTranslation;
+ final float startTranslation = mTextAndBackground.getTranslationX();
if (!canChildBeDismissed()) {
willExit = false;
@@ -159,31 +194,30 @@
} else if (fling) {
willExit = true;
endTranslation = velocity < 0 ? - getWidth() : getWidth();
- } else if (Math.abs(getTranslationX()) > getWidth() / 2) {
+ } else if (Math.abs(startTranslation) > getWidth() / 2) {
willExit = true;
- endTranslation = (getTranslationX() < 0 ? -getWidth() : getWidth());
+ endTranslation = (startTranslation < 0 ? -getWidth() : getWidth());
} else {
willExit = false;
endTranslation = 0;
}
- SwipeDetector.ScrollInterpolator interpolator = new SwipeDetector.ScrollInterpolator();
- interpolator.setVelocityAtZero(velocity);
-
long duration = SwipeDetector.calculateDuration(velocity,
- (endTranslation - getTranslationX()) / getWidth());
- animate()
- .setDuration(duration)
- .setInterpolator(interpolator)
- .translationX(endTranslation)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- mSwipeDetector.finishedScrolling();
- if (willExit) {
- onChildDismissed();
- }
- }
- }).start();
+ (endTranslation - startTranslation) / getWidth());
+
+ mContentTranslateAnimator.removeAllListeners();
+ mContentTranslateAnimator.setDuration(duration)
+ .setInterpolator(scrollInterpolatorForVelocity(velocity));
+ mContentTranslateAnimator.setFloatValues(startTranslation, endTranslation);
+ mContentTranslateAnimator.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ mSwipeDetector.finishedScrolling();
+ if (willExit) {
+ onChildDismissed();
+ }
+ }
+ });
+ mContentTranslateAnimator.start();
}
}
diff --git a/src/com/android/launcher3/pageindicators/CaretDrawable.java b/src/com/android/launcher3/pageindicators/CaretDrawable.java
deleted file mode 100644
index 5ade497..0000000
--- a/src/com/android/launcher3/pageindicators/CaretDrawable.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2016 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.pageindicators;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.PixelFormat;
-import android.graphics.drawable.Drawable;
-
-import com.android.launcher3.R;
-import com.android.launcher3.util.Themes;
-
-public class CaretDrawable extends Drawable {
- public static final float PROGRESS_CARET_POINTING_UP = -1f;
- public static final float PROGRESS_CARET_POINTING_DOWN = 1f;
- public static final float PROGRESS_CARET_NEUTRAL = 0;
-
- private float mCaretProgress = PROGRESS_CARET_NEUTRAL;
-
- private Paint mShadowPaint = new Paint();
- private Paint mCaretPaint = new Paint();
- private Path mPath = new Path();
- private final int mCaretSizePx;
- private final boolean mUseShadow;
-
- public CaretDrawable(Context context) {
- final Resources res = context.getResources();
-
- final int strokeWidth = res.getDimensionPixelSize(R.dimen.all_apps_caret_stroke_width);
- final int shadowSpread = res.getDimensionPixelSize(R.dimen.all_apps_caret_shadow_spread);
-
- mCaretPaint.setColor(Themes.getAttrColor(context, R.attr.workspaceTextColor));
- mCaretPaint.setAntiAlias(true);
- mCaretPaint.setStrokeWidth(strokeWidth);
- mCaretPaint.setStyle(Paint.Style.STROKE);
- mCaretPaint.setStrokeCap(Paint.Cap.ROUND);
- mCaretPaint.setStrokeJoin(Paint.Join.ROUND);
-
- mShadowPaint.setColor(res.getColor(R.color.default_shadow_color_no_alpha));
- mShadowPaint.setAlpha(Themes.getAlpha(context, android.R.attr.spotShadowAlpha));
- mShadowPaint.setAntiAlias(true);
- mShadowPaint.setStrokeWidth(strokeWidth + (shadowSpread * 2));
- mShadowPaint.setStyle(Paint.Style.STROKE);
- mShadowPaint.setStrokeCap(Paint.Cap.ROUND);
- mShadowPaint.setStrokeJoin(Paint.Join.ROUND);
-
- mUseShadow = !Themes.getAttrBoolean(context, R.attr.isWorkspaceDarkText);
- mCaretSizePx = res.getDimensionPixelSize(R.dimen.all_apps_caret_size);
- }
-
- @Override
- public int getIntrinsicHeight() {
- return mCaretSizePx;
- }
-
- @Override
- public int getIntrinsicWidth() {
- return mCaretSizePx;
- }
-
- @Override
- public void draw(Canvas canvas) {
- // Assumes caret paint is more important than shadow paint
- if (Float.compare(mCaretPaint.getAlpha(), 0f) == 0) {
- return;
- }
-
- // Assumes shadow stroke width is larger
- final float width = getBounds().width() - mShadowPaint.getStrokeWidth();
- final float height = getBounds().height() - mShadowPaint.getStrokeWidth();
- final float left = getBounds().left + (mShadowPaint.getStrokeWidth() / 2);
- final float top = getBounds().top + (mShadowPaint.getStrokeWidth() / 2);
-
- // When the bounds are square, this will result in a caret with a right angle
- final float verticalInset = (height / 4);
- final float caretHeight = (height - (verticalInset * 2));
-
- mPath.reset();
- mPath.moveTo(left, top + caretHeight * (1 - getNormalizedCaretProgress()));
- mPath.lineTo(left + (width / 2), top + caretHeight * getNormalizedCaretProgress());
- mPath.lineTo(left + width, top + caretHeight * (1 - getNormalizedCaretProgress()));
- if (mUseShadow) {
- canvas.drawPath(mPath, mShadowPaint);
- }
- canvas.drawPath(mPath, mCaretPaint);
- }
-
- /**
- * Sets the caret progress
- *
- * @param progress The progress ({@value #PROGRESS_CARET_POINTING_UP} for pointing up,
- * {@value #PROGRESS_CARET_POINTING_DOWN} for pointing down, {@value #PROGRESS_CARET_NEUTRAL}
- * for neutral)
- */
- public void setCaretProgress(float progress) {
- mCaretProgress = progress;
- invalidateSelf();
- }
-
- /**
- * Returns the caret progress
- *
- * @return The progress
- */
- public float getCaretProgress() {
- return mCaretProgress;
- }
-
- /**
- * Returns the caret progress normalized to [0..1]
- *
- * @return The normalized progress
- */
- public float getNormalizedCaretProgress() {
- return (mCaretProgress - PROGRESS_CARET_POINTING_UP) /
- (PROGRESS_CARET_POINTING_DOWN - PROGRESS_CARET_POINTING_UP);
- }
-
- @Override
- public int getOpacity() {
- return PixelFormat.TRANSLUCENT;
- }
-
- @Override
- public void setAlpha(int alpha) {
- mCaretPaint.setAlpha(alpha);
- mShadowPaint.setAlpha(alpha);
- invalidateSelf();
- }
-
- @Override
- public void setColorFilter(ColorFilter cf) {
- // no-op
- }
-}
diff --git a/src/com/android/launcher3/pageindicators/PageIndicator.java b/src/com/android/launcher3/pageindicators/PageIndicator.java
index d6ef5b4..be6bcc5 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicator.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicator.java
@@ -16,7 +16,6 @@
package com.android.launcher3.pageindicators;
import android.content.Context;
-import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.FrameLayout;
@@ -24,7 +23,6 @@
* Base class for a page indicator.
*/
public abstract class PageIndicator extends FrameLayout {
- private CaretDrawable mCaretDrawable;
protected int mNumPages = 1;
@@ -52,28 +50,7 @@
onPageCountChanged();
}
- public CaretDrawable getCaretDrawable() {
- return mCaretDrawable;
- }
-
- public void setCaretDrawable(CaretDrawable caretDrawable) {
- if (mCaretDrawable != null) {
- mCaretDrawable.setCallback(null);
- }
-
- mCaretDrawable = caretDrawable;
-
- if (mCaretDrawable != null) {
- mCaretDrawable.setCallback(this);
- }
- }
-
protected void onPageCountChanged() {}
public void setShouldAutoHide(boolean shouldAutoHide) {}
-
- @Override
- protected boolean verifyDrawable(Drawable who) {
- return super.verifyDrawable(who) || who == getCaretDrawable();
- }
}
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorCaretLandscape.java b/src/com/android/launcher3/pageindicators/PageIndicatorCaretLandscape.java
deleted file mode 100644
index 911be93..0000000
--- a/src/com/android/launcher3/pageindicators/PageIndicatorCaretLandscape.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2016 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.pageindicators;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-
-/**
- * Simply draws the caret drawable bottom-right aligned in the view. This ensures that we can have
- * a view with as large an area as we want (for touching) while maintaining a caret of size
- * all_apps_caret_size. Used only for the landscape layout.
- */
-public class PageIndicatorCaretLandscape extends PageIndicator {
- // all apps pull up handle drawable.
-
- public PageIndicatorCaretLandscape(Context context) {
- this(context, null);
- }
-
- public PageIndicatorCaretLandscape(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public PageIndicatorCaretLandscape(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- int caretSize = context.getResources().getDimensionPixelSize(R.dimen.all_apps_caret_size);
- CaretDrawable caretDrawable = new CaretDrawable(context);
- caretDrawable.setBounds(0, 0, caretSize, caretSize);
- setCaretDrawable(caretDrawable);
-
- Launcher l = Launcher.getLauncher(context);
- setOnClickListener(l);
- setOnFocusChangeListener(l.mFocusHandler);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- Rect drawableBounds = getCaretDrawable().getBounds();
- int count = canvas.save();
- canvas.translate((getWidth() - drawableBounds.width()) / 2,
- getHeight() - drawableBounds.height());
- getCaretDrawable().draw(canvas);
- canvas.restoreToCount(count);
- }
-}
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorLandscape.java b/src/com/android/launcher3/pageindicators/PageIndicatorLandscape.java
new file mode 100644
index 0000000..7325235
--- /dev/null
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorLandscape.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 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.pageindicators;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.launcher3.Launcher;
+
+/**
+ * Simply draws the caret drawable bottom-right aligned in the view. This ensures that we can have
+ * a view with as large an area as we want (for touching) while maintaining a caret of size
+ * all_apps_caret_size. Used only for the landscape layout.
+ */
+public class PageIndicatorLandscape extends PageIndicator {
+ // all apps pull up handle drawable.
+
+ public PageIndicatorLandscape(Context context) {
+ this(context, null);
+ }
+
+ public PageIndicatorLandscape(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PageIndicatorLandscape(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ Launcher l = Launcher.getLauncher(context);
+ setOnClickListener(l);
+ setOnFocusChangeListener(l.mFocusHandler);
+ }
+}
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java b/src/com/android/launcher3/pageindicators/PageIndicatorLine.java
similarity index 84%
rename from src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java
rename to src/com/android/launcher3/pageindicators/PageIndicatorLine.java
index 5eedd92..09a06b0 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorLine.java
@@ -26,7 +26,7 @@
*
* The fraction is 1 / number of pages and the position is based on the progress of the page scroll.
*/
-public class PageIndicatorLineCaret extends PageIndicator {
+public class PageIndicatorLine extends PageIndicator {
private static final int LINE_ANIMATE_DURATION = ViewConfiguration.getScrollBarFadeDuration();
private static final int LINE_FADE_DELAY = ViewConfiguration.getScrollDefaultDelay();
@@ -56,43 +56,43 @@
private final int mLineHeight;
private ImageView mAllAppsHandle;
- private static final Property<PageIndicatorLineCaret, Integer> PAINT_ALPHA
- = new Property<PageIndicatorLineCaret, Integer>(Integer.class, "paint_alpha") {
+ private static final Property<PageIndicatorLine, Integer> PAINT_ALPHA
+ = new Property<PageIndicatorLine, Integer>(Integer.class, "paint_alpha") {
@Override
- public Integer get(PageIndicatorLineCaret obj) {
+ public Integer get(PageIndicatorLine obj) {
return obj.mLinePaint.getAlpha();
}
@Override
- public void set(PageIndicatorLineCaret obj, Integer alpha) {
+ public void set(PageIndicatorLine obj, Integer alpha) {
obj.mLinePaint.setAlpha(alpha);
obj.invalidate();
}
};
- private static final Property<PageIndicatorLineCaret, Float> NUM_PAGES
- = new Property<PageIndicatorLineCaret, Float>(Float.class, "num_pages") {
+ private static final Property<PageIndicatorLine, Float> NUM_PAGES
+ = new Property<PageIndicatorLine, Float>(Float.class, "num_pages") {
@Override
- public Float get(PageIndicatorLineCaret obj) {
+ public Float get(PageIndicatorLine obj) {
return obj.mNumPagesFloat;
}
@Override
- public void set(PageIndicatorLineCaret obj, Float numPages) {
+ public void set(PageIndicatorLine obj, Float numPages) {
obj.mNumPagesFloat = numPages;
obj.invalidate();
}
};
- private static final Property<PageIndicatorLineCaret, Integer> TOTAL_SCROLL
- = new Property<PageIndicatorLineCaret, Integer>(Integer.class, "total_scroll") {
+ private static final Property<PageIndicatorLine, Integer> TOTAL_SCROLL
+ = new Property<PageIndicatorLine, Integer>(Integer.class, "total_scroll") {
@Override
- public Integer get(PageIndicatorLineCaret obj) {
+ public Integer get(PageIndicatorLine obj) {
return obj.mTotalScroll;
}
@Override
- public void set(PageIndicatorLineCaret obj, Integer totalScroll) {
+ public void set(PageIndicatorLine obj, Integer totalScroll) {
obj.mTotalScroll = totalScroll;
obj.invalidate();
}
@@ -105,15 +105,15 @@
}
};
- public PageIndicatorLineCaret(Context context) {
+ public PageIndicatorLine(Context context) {
this(context, null);
}
- public PageIndicatorLineCaret(Context context, AttributeSet attrs) {
+ public PageIndicatorLine(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
- public PageIndicatorLineCaret(Context context, AttributeSet attrs, int defStyle) {
+ public PageIndicatorLine(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
Resources res = context.getResources();
@@ -122,7 +122,6 @@
mLauncher = Launcher.getLauncher(context);
mLineHeight = res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_line_height);
- setCaretDrawable(new CaretDrawable(context));
boolean darkText = WallpaperColorInfo.getInstance(context).supportsDarkText();
mActiveAlpha = darkText ? BLACK_ALPHA : WHITE_ALPHA;
@@ -132,8 +131,7 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mAllAppsHandle = (ImageView) findViewById(R.id.all_apps_handle);
- mAllAppsHandle.setImageDrawable(getCaretDrawable());
+ mAllAppsHandle = findViewById(R.id.all_apps_handle);
mAllAppsHandle.setOnClickListener(mLauncher);
mAllAppsHandle.setOnFocusChangeListener(mLauncher.mFocusHandler);
mLauncher.setAllAppsButton(mAllAppsHandle);
diff --git a/src/com/android/launcher3/popup/BaseActionPopup.java b/src/com/android/launcher3/popup/BaseActionPopup.java
deleted file mode 100644
index 7ffe2ef..0000000
--- a/src/com/android/launcher3/popup/BaseActionPopup.java
+++ /dev/null
@@ -1,599 +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.popup;
-
-import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.CornerPathEffect;
-import android.graphics.Outline;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.drawable.ShapeDrawable;
-import android.os.Build;
-import android.support.annotation.IntDef;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.widget.TextView;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
-import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
-import com.android.launcher3.anim.PropertyListBuilder;
-import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
-import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.graphics.TriangleShape;
-import com.android.launcher3.logging.LoggerUtils;
-import com.android.launcher3.notification.NotificationItemView;
-import com.android.launcher3.shortcuts.DeepShortcutView;
-import com.android.launcher3.shortcuts.ShortcutsItemView;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.util.Themes;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Set;
-
-/**
- * Base popup container for showing shortcuts to deep links within apps.
- */
-@TargetApi(Build.VERSION_CODES.N)
-public class BaseActionPopup<V extends TextView> extends AbstractFloatingView {
-
- public static final int ROUNDED_TOP_CORNERS = 1 << 0;
- public static final int ROUNDED_BOTTOM_CORNERS = 1 << 1;
-
- @IntDef(flag = true, value = {
- ROUNDED_TOP_CORNERS,
- ROUNDED_BOTTOM_CORNERS
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface RoundedCornerFlags {}
-
- protected final Launcher mLauncher;
- protected final LauncherAccessibilityDelegate mAccessibilityDelegate;
- private final boolean mIsRtl;
-
- public ShortcutsItemView mShortcutsItemView;
-
- protected V mOriginalIcon;
- private final Rect mTempRect = new Rect();
- private PointF mInterceptTouchDown = new PointF();
- private boolean mIsLeftAligned;
- protected boolean mIsAboveIcon;
- protected View mArrow;
- private int mGravity;
-
- protected Animator mOpenCloseAnimator;
- protected boolean mDeferContainerRemoval;
- private final Rect mStartRect = new Rect();
- private final Rect mEndRect = new Rect();
-
- public BaseActionPopup(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- mLauncher = Launcher.getLauncher(context);
-
- mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher);
- mIsRtl = Utilities.isRtl(getResources());
- }
-
- public BaseActionPopup(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public BaseActionPopup(Context context) {
- this(context, null, 0);
- }
-
- public LauncherAccessibilityDelegate getAccessibilityDelegate() {
- return mAccessibilityDelegate;
- }
-
- protected PopupItemView getItemViewAt(int index) {
- if (!mIsAboveIcon) {
- // Opening down, so arrow is the first view.
- index++;
- }
- return (PopupItemView) getChildAt(index);
- }
-
- protected int getItemCount() {
- // All children except the arrow are items.
- return getChildCount() - 1;
- }
-
- protected void animateOpen() {
- setVisibility(View.VISIBLE);
- mIsOpen = true;
-
- final AnimatorSet openAnim = LauncherAnimUtils.createAnimatorSet();
- final Resources res = getResources();
- final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration);
- final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
-
- // Rectangular reveal.
- int itemsTotalHeight = 0;
- for (int i = 0; i < getItemCount(); i++) {
- itemsTotalHeight += getItemViewAt(i).getMeasuredHeight();
- }
- Point startPoint = computeAnimStartPoint(itemsTotalHeight);
- int top = mIsAboveIcon ? getPaddingTop() : startPoint.y;
- float radius = getItemViewAt(0).getBackgroundRadius();
- mStartRect.set(startPoint.x, startPoint.y, startPoint.x, startPoint.y);
- mEndRect.set(0, top, getMeasuredWidth(), top + itemsTotalHeight);
- final ValueAnimator revealAnim = new RoundedRectRevealOutlineProvider
- (radius, radius, mStartRect, mEndRect).createRevealAnimator(this, false);
- revealAnim.setDuration(revealDuration);
- revealAnim.setInterpolator(revealInterpolator);
-
- Animator fadeIn = ObjectAnimator.ofFloat(this, ALPHA, 0, 1);
- fadeIn.setDuration(revealDuration);
- fadeIn.setInterpolator(revealInterpolator);
- openAnim.play(fadeIn);
-
- // Animate the arrow.
- mArrow.setScaleX(0);
- mArrow.setScaleY(0);
- Animator arrowScale = createArrowScaleAnim(1).setDuration(res.getInteger(
- R.integer.config_popupArrowOpenDuration));
-
- openAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mOpenCloseAnimator = null;
- Utilities.sendCustomAccessibilityEvent(
- BaseActionPopup.this,
- AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
- getContext().getString(R.string.action_deep_shortcut));
- }
- });
-
- mOpenCloseAnimator = openAnim;
- openAnim.playSequentially(revealAnim, arrowScale);
- openAnim.start();
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
- enforceContainedWithinScreen(l, r);
- }
-
- private void enforceContainedWithinScreen(int left, int right) {
- DragLayer dragLayer = mLauncher.getDragLayer();
- if (getTranslationX() + left < 0 ||
- getTranslationX() + right > dragLayer.getWidth()) {
- // If we are still off screen, center horizontally too.
- mGravity |= Gravity.CENTER_HORIZONTAL;
- }
-
- if (Gravity.isHorizontal(mGravity)) {
- setX(dragLayer.getWidth() / 2 - getMeasuredWidth() / 2);
- }
- if (Gravity.isVertical(mGravity)) {
- setY(dragLayer.getHeight() / 2 - getMeasuredHeight() / 2);
- }
- }
-
- /**
- * Returns the point at which the center of the arrow merges with the first popup item.
- */
- private Point computeAnimStartPoint(int itemsTotalHeight) {
- int arrowCenterX = getResources().getDimensionPixelSize(mIsLeftAligned ^ mIsRtl ?
- R.dimen.popup_arrow_horizontal_center_start:
- R.dimen.popup_arrow_horizontal_center_end);
- if (!mIsLeftAligned) {
- arrowCenterX = getMeasuredWidth() - arrowCenterX;
- }
- int arrowHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom()
- - itemsTotalHeight;
- // The y-coordinate of edge between the arrow and the first popup item.
- int arrowEdge = getPaddingTop() + (mIsAboveIcon ? itemsTotalHeight : arrowHeight);
- return new Point(arrowCenterX, arrowEdge);
- }
-
- /**
- * Orients this container above or below the given icon, aligning with the left or right.
- *
- * These are the preferred orientations, in order (RTL prefers right-aligned over left):
- * - Above and left-aligned
- * - Above and right-aligned
- * - Below and left-aligned
- * - Below and right-aligned
- *
- * So we always align left if there is enough horizontal space
- * and align above if there is enough vertical space.
- */
- protected void orientAboutIcon(int arrowHeight) {
- int width = getMeasuredWidth();
- int height = getMeasuredHeight() + arrowHeight;
-
- DragLayer dragLayer = mLauncher.getDragLayer();
- dragLayer.getDescendantRectRelativeToSelf(mOriginalIcon, mTempRect);
- Rect insets = dragLayer.getInsets();
-
- // Align left (right in RTL) if there is room.
- int leftAlignedX = mTempRect.left + mOriginalIcon.getPaddingLeft();
- int rightAlignedX = mTempRect.right - width - mOriginalIcon.getPaddingRight();
- int x = leftAlignedX;
- boolean canBeLeftAligned = leftAlignedX + width + insets.left
- < dragLayer.getRight() - insets.right;
- boolean canBeRightAligned = rightAlignedX > dragLayer.getLeft() + insets.left;
- if (!canBeLeftAligned || (mIsRtl && canBeRightAligned)) {
- x = rightAlignedX;
- }
- mIsLeftAligned = x == leftAlignedX;
- if (mIsRtl) {
- x -= dragLayer.getWidth() - width;
- }
-
- // Offset x so that the arrow and shortcut icons are center-aligned with the original icon.
- int iconWidth = mOriginalIcon.getWidth()
- - mOriginalIcon.getTotalPaddingLeft() - mOriginalIcon.getTotalPaddingRight();
- iconWidth *= mOriginalIcon.getScaleX();
- Resources resources = getResources();
- int xOffset;
- if (isAlignedWithStart()) {
- // Aligning with the shortcut icon.
- int shortcutIconWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcut_icon_size);
- int shortcutPaddingStart = resources.getDimensionPixelSize(
- R.dimen.popup_padding_start);
- xOffset = iconWidth / 2 - shortcutIconWidth / 2 - shortcutPaddingStart;
- } else {
- // Aligning with the drag handle.
- int shortcutDragHandleWidth = resources.getDimensionPixelSize(
- R.dimen.deep_shortcut_drag_handle_size);
- int shortcutPaddingEnd = resources.getDimensionPixelSize(
- R.dimen.popup_padding_end);
- xOffset = iconWidth / 2 - shortcutDragHandleWidth / 2 - shortcutPaddingEnd;
- }
- x += mIsLeftAligned ? xOffset : -xOffset;
-
- // Open above icon if there is room.
- int iconHeight = getIconHeightForPopupPlacement();
- int y = mTempRect.top + mOriginalIcon.getPaddingTop() - height;
- mIsAboveIcon = y > dragLayer.getTop() + insets.top;
- if (!mIsAboveIcon) {
- y = mTempRect.top + mOriginalIcon.getPaddingTop() + iconHeight;
- }
-
- // Insets are added later, so subtract them now.
- if (mIsRtl) {
- x += insets.right;
- } else {
- x -= insets.left;
- }
- y -= insets.top;
-
- mGravity = 0;
- if (y + height > dragLayer.getBottom() - insets.bottom) {
- // The container is opening off the screen, so just center it in the drag layer instead.
- mGravity = Gravity.CENTER_VERTICAL;
- // Put the container next to the icon, preferring the right side in ltr (left in rtl).
- int rightSide = leftAlignedX + iconWidth - insets.left;
- int leftSide = rightAlignedX - iconWidth - insets.left;
- if (!mIsRtl) {
- if (rightSide + width < dragLayer.getRight()) {
- x = rightSide;
- mIsLeftAligned = true;
- } else {
- x = leftSide;
- mIsLeftAligned = false;
- }
- } else {
- if (leftSide > dragLayer.getLeft()) {
- x = leftSide;
- mIsLeftAligned = false;
- } else {
- x = rightSide;
- mIsLeftAligned = true;
- }
- }
- mIsAboveIcon = true;
- }
-
- setX(x);
- setY(y);
- }
-
- protected int getIconHeightForPopupPlacement() {
- return mOriginalIcon.getHeight();
- }
-
- protected boolean isAlignedWithStart() {
- return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl;
- }
-
- /**
- * Adds an arrow view pointing at the original icon.
- * @param horizontalOffset the horizontal offset of the arrow, so that it
- * points at the center of the original icon
- */
- protected View addArrowView(int horizontalOffset, int verticalOffset, int width, int height) {
- LayoutParams layoutParams = new LayoutParams(width, height);
- if (mIsLeftAligned) {
- layoutParams.gravity = Gravity.LEFT;
- layoutParams.leftMargin = horizontalOffset;
- } else {
- layoutParams.gravity = Gravity.RIGHT;
- layoutParams.rightMargin = horizontalOffset;
- }
- if (mIsAboveIcon) {
- layoutParams.topMargin = verticalOffset;
- } else {
- layoutParams.bottomMargin = verticalOffset;
- }
-
- View arrowView = new View(getContext());
- if (Gravity.isVertical(mGravity)) {
- // This is only true if there wasn't room for the container next to the icon,
- // so we centered it instead. In that case we don't want to show the arrow.
- arrowView.setVisibility(INVISIBLE);
- } else {
- ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
- width, height, !mIsAboveIcon));
- Paint arrowPaint = arrowDrawable.getPaint();
- arrowPaint.setColor(Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary));
- // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
- int radius = getResources().getDimensionPixelSize(R.dimen.popup_arrow_corner_radius);
- arrowPaint.setPathEffect(new CornerPathEffect(radius));
- arrowView.setBackground(arrowDrawable);
- arrowView.setElevation(getElevation());
- }
- addView(arrowView, mIsAboveIcon ? getChildCount() : 0, layoutParams);
- return arrowView;
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- mInterceptTouchDown.set(ev.getX(), ev.getY());
- return false;
- }
- // Stop sending touch events to deep shortcut views if user moved beyond touch slop.
- return Math.hypot(mInterceptTouchDown.x - ev.getX(), mInterceptTouchDown.y - ev.getY())
- > ViewConfiguration.get(getContext()).getScaledTouchSlop();
- }
-
- protected ObjectAnimator createArrowScaleAnim(float scale) {
- return LauncherAnimUtils.ofPropertyValuesHolder(
- mArrow, new PropertyListBuilder().scale(scale).build());
- }
-
- @Override
- protected void handleClose(boolean animate) {
- if (animate) {
- animateClose();
- } else {
- closeComplete();
- }
- }
-
- protected void animateClose() {
- if (!mIsOpen) {
- return;
- }
- mEndRect.setEmpty();
- if (mOpenCloseAnimator != null) {
- Outline outline = new Outline();
- getOutlineProvider().getOutline(this, outline);
- outline.getRect(mEndRect);
- mOpenCloseAnimator.cancel();
- }
- mIsOpen = false;
-
- final AnimatorSet closeAnim = LauncherAnimUtils.createAnimatorSet();
- prepareCloseAnimator(closeAnim);
-
- closeAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mOpenCloseAnimator = null;
- if (mDeferContainerRemoval) {
- setVisibility(INVISIBLE);
- } else {
- closeComplete();
- }
- }
- });
- mOpenCloseAnimator = closeAnim;
- closeAnim.start();
- }
-
- protected void prepareCloseAnimator(AnimatorSet closeAnim) {
- final Resources res = getResources();
- final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
-
- // Rectangular reveal (reversed).
- int itemsTotalHeight = 0;
- for (int i = 0; i < getItemCount(); i++) {
- itemsTotalHeight += getItemViewAt(i).getMeasuredHeight();
- }
- Point startPoint = computeAnimStartPoint(itemsTotalHeight);
- int top = mIsAboveIcon ? getPaddingTop() : startPoint.y;
- float radius = getItemViewAt(0).getBackgroundRadius();
- mStartRect.set(startPoint.x, startPoint.y, startPoint.x, startPoint.y);
- if (mEndRect.isEmpty()) {
- mEndRect.set(0, top, getMeasuredWidth(), top + itemsTotalHeight);
- }
- final ValueAnimator revealAnim = new RoundedRectRevealOutlineProvider(
- radius, radius, mStartRect, mEndRect).createRevealAnimator(this, true);
- revealAnim.setInterpolator(revealInterpolator);
- closeAnim.play(revealAnim);
-
- Animator fadeOut = ObjectAnimator.ofFloat(this, ALPHA, 0);
- fadeOut.setInterpolator(revealInterpolator);
- closeAnim.play(fadeOut);
- closeAnim.setDuration((long) res.getInteger(R.integer.config_popupOpenCloseDuration));
- }
-
- /**
- * Closes the folder without animation.
- */
- protected void closeComplete() {
- if (mOpenCloseAnimator != null) {
- mOpenCloseAnimator.cancel();
- mOpenCloseAnimator = null;
- }
- mIsOpen = false;
- mDeferContainerRemoval = false;
- mLauncher.getDragLayer().removeView(this);
- }
-
- @Override
- protected boolean isOfType(int type) {
- return (type & TYPE_ACTION_POPUP) != 0;
- }
-
- /**
- * Returns a DeepShortcutsContainer which is already open or null
- */
- public static BaseActionPopup getOpen(Launcher launcher) {
- return getOpenView(launcher, TYPE_ACTION_POPUP);
- }
-
- @Override
- public void logActionCommand(int command) {
- mLauncher.getUserEventDispatcher().logActionCommand(
- command, mOriginalIcon, ContainerType.DEEPSHORTCUTS);
- }
-
- @Override
- public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- DragLayer dl = mLauncher.getDragLayer();
- if (!dl.isEventOverView(this, ev)) {
- mLauncher.getUserEventDispatcher().logActionTapOutside(
- LoggerUtils.newContainerTarget(ContainerType.DEEPSHORTCUTS));
- close(true);
-
- // We let touches on the original icon go through so that users can launch
- // the app with one tap if they don't find a shortcut they want.
- return mOriginalIcon == null || !dl.isEventOverView(mOriginalIcon, ev);
- }
- }
- return false;
- }
-
- public void populateAndShow(V originalIcon, PopupPopulator.Item[] itemsToPopulate) {
- setVisibility(View.INVISIBLE);
- mLauncher.getDragLayer().addView(this);
-
- final Resources resources = getResources();
- final int arrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width);
- final int arrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height);
- final int arrowVerticalOffset = resources.getDimensionPixelSize(
- R.dimen.popup_arrow_vertical_offset);
-
- mOriginalIcon = originalIcon;
-
- // Add dummy views first, and populate with real info when ready.
- addDummyViews(itemsToPopulate);
-
- measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- orientAboutIcon(arrowHeight + arrowVerticalOffset);
-
- boolean reverseOrder = mIsAboveIcon;
- if (reverseOrder) {
- removeAllViews();
- mShortcutsItemView = null;
- itemsToPopulate = PopupPopulator.reverseItems(itemsToPopulate);
- addDummyViews(itemsToPopulate);
-
- measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- orientAboutIcon(arrowHeight + arrowVerticalOffset);
- }
-
- // Add the arrow.
- final int arrowHorizontalOffset = resources.getDimensionPixelSize(isAlignedWithStart() ?
- R.dimen.popup_arrow_horizontal_offset_start :
- R.dimen.popup_arrow_horizontal_offset_end);
- mArrow = addArrowView(arrowHorizontalOffset, arrowVerticalOffset, arrowWidth, arrowHeight);
- mArrow.setPivotX(arrowWidth / 2);
- mArrow.setPivotY(mIsAboveIcon ? 0 : arrowHeight);
-
- measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- animateOpen();
- }
-
- protected void addDummyViews(PopupPopulator.Item[] itemTypesToPopulate) {
- final LayoutInflater inflater = mLauncher.getLayoutInflater();
- int shortcutsItemRoundedCorners = ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS;
- int numItems = itemTypesToPopulate.length;
- for (int i = 0; i < numItems; i++) {
- PopupPopulator.Item itemTypeToPopulate = itemTypesToPopulate[i];
- PopupPopulator.Item prevItemTypeToPopulate =
- i > 0 ? itemTypesToPopulate[i - 1] : null;
- PopupPopulator.Item nextItemTypeToPopulate =
- i < numItems - 1 ? itemTypesToPopulate[i + 1] : null;
- final View item = inflater.inflate(itemTypeToPopulate.layoutId, this, false);
-
- boolean shouldUnroundTopCorners = prevItemTypeToPopulate != null
- && itemTypeToPopulate.isShortcut ^ prevItemTypeToPopulate.isShortcut;
- boolean shouldUnroundBottomCorners = nextItemTypeToPopulate != null
- && itemTypeToPopulate.isShortcut ^ nextItemTypeToPopulate.isShortcut;
-
- onViewInflated(item, itemTypeToPopulate,
- shouldUnroundTopCorners, shouldUnroundBottomCorners);
-
- if (itemTypeToPopulate.isShortcut) {
- if (mShortcutsItemView == null) {
- mShortcutsItemView = (ShortcutsItemView) inflater.inflate(
- R.layout.shortcuts_item, this, false);
- addView(mShortcutsItemView);
- if (shouldUnroundTopCorners) {
- shortcutsItemRoundedCorners &= ~ROUNDED_TOP_CORNERS;
- }
- }
- mShortcutsItemView.addShortcutView(item, itemTypeToPopulate);
- if (shouldUnroundBottomCorners) {
- shortcutsItemRoundedCorners &= ~ROUNDED_BOTTOM_CORNERS;
- }
- } else {
- addView(item);
- }
- }
- int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary);
- mShortcutsItemView.setBackgroundWithCorners(backgroundColor, shortcutsItemRoundedCorners);
- }
-
- protected void onViewInflated(View view, PopupPopulator.Item itemType,
- boolean shouldUnroundTopCorners, boolean shouldUnroundBottomCorners) {
-
- }
-}
\ No newline at end of file
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 4435afb..3dc58a1 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -16,26 +16,39 @@
package com.android.launcher3.popup;
-import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
+import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.CornerPathEffect;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.drawable.ShapeDrawable;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
+import android.view.Gravity;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.widget.ImageView;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
@@ -45,42 +58,103 @@
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.R;
-import com.android.launcher3.anim.PropertyResetListener;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
+import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.badge.BadgeInfo;
import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.graphics.TriangleShape;
+import com.android.launcher3.logging.LoggerUtils;
+import com.android.launcher3.notification.NotificationInfo;
import com.android.launcher3.notification.NotificationItemView;
import com.android.launcher3.notification.NotificationKeyData;
-import com.android.launcher3.popup.PopupPopulator.Item;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.DeepShortcutView;
-import com.android.launcher3.shortcuts.ShortcutsItemView;
+import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Themes;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import static com.android.launcher3.notification.NotificationMainView.NOTIFICATION_ITEM_INFO;
+import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
+import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+
/**
* A container for shortcuts to deep links and notifications associated with an app.
*/
@TargetApi(Build.VERSION_CODES.N)
-public class PopupContainerWithArrow extends BaseActionPopup<BubbleTextView> implements DragSource,
- DragController.DragListener {
+public class PopupContainerWithArrow extends AbstractFloatingView implements DragSource,
+ DragController.DragListener, View.OnLongClickListener,
+ View.OnTouchListener {
+
+ private final List<DeepShortcutView> mShortcuts = new ArrayList<>();
+ private final PointF mInterceptTouchDown = new PointF();
+ private final Rect mTempRect = new Rect();
+ private final Point mIconLastTouchPos = new Point();
private final int mStartDragThreshold;
+ private final LayoutInflater mInflater;
+ private final float mOutlineRadius;
+ private final Launcher mLauncher;
+ private final LauncherAccessibilityDelegate mAccessibilityDelegate;
+ private final boolean mIsRtl;
+ private final int mArrayOffset;
+ private final View mArrow;
+
+ private BubbleTextView mOriginalIcon;
private NotificationItemView mNotificationItemView;
- private AnimatorSet mReduceHeightAnimatorSet;
+
+ private ViewGroup mSystemShortcutContainer;
+
+ private boolean mIsLeftAligned;
+ protected boolean mIsAboveIcon;
private int mNumNotifications;
+ private int mGravity;
+
+ protected Animator mOpenCloseAnimator;
+ protected boolean mDeferContainerRemoval;
+ private final Rect mStartRect = new Rect();
+ private final Rect mEndRect = new Rect();
public PopupContainerWithArrow(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mStartDragThreshold = getResources().getDimensionPixelSize(
R.dimen.deep_shortcuts_start_drag_threshold);
+ mInflater = LayoutInflater.from(context);
+ mOutlineRadius = getResources().getDimension(R.dimen.bg_round_rect_radius);
+ mLauncher = Launcher.getLauncher(context);
+ mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher);
+ mIsRtl = Utilities.isRtl(getResources());
+
+ setClipToOutline(true);
+ setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mOutlineRadius);
+ }
+ });
+
+ // Initialize arrow view
+ final Resources resources = getResources();
+ final int arrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width);
+ final int arrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height);
+ mArrow = new View(context);
+ mArrow.setLayoutParams(new DragLayer.LayoutParams(arrowWidth, arrowHeight));
+ mArrayOffset = resources.getDimensionPixelSize(R.dimen.popup_arrow_vertical_offset);
}
public PopupContainerWithArrow(Context context, AttributeSet attrs) {
@@ -91,6 +165,75 @@
this(context, null, 0);
}
+ public LauncherAccessibilityDelegate getAccessibilityDelegate() {
+ return mAccessibilityDelegate;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mInterceptTouchDown.set(ev.getX(), ev.getY());
+ }
+ if (mNotificationItemView != null
+ && mNotificationItemView.onInterceptTouchEvent(ev)) {
+ return true;
+ }
+ // Stop sending touch events to deep shortcut views if user moved beyond touch slop.
+ return Math.hypot(mInterceptTouchDown.x - ev.getX(), mInterceptTouchDown.y - ev.getY())
+ > ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (mNotificationItemView != null) {
+ return mNotificationItemView.onTouchEvent(ev);
+ }
+ return super.onTouchEvent(ev);
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_ACTION_POPUP) != 0;
+ }
+
+ @Override
+ public void logActionCommand(int command) {
+ mLauncher.getUserEventDispatcher().logActionCommand(
+ command, mOriginalIcon, ContainerType.DEEPSHORTCUTS);
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ DragLayer dl = mLauncher.getDragLayer();
+ if (!dl.isEventOverView(this, ev)) {
+ mLauncher.getUserEventDispatcher().logActionTapOutside(
+ LoggerUtils.newContainerTarget(ContainerType.DEEPSHORTCUTS));
+ close(true);
+
+ // We let touches on the original icon go through so that users can launch
+ // the app with one tap if they don't find a shortcut they want.
+ return mOriginalIcon == null || !dl.isEventOverView(mOriginalIcon, ev);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ if (animate) {
+ animateClose();
+ } else {
+ closeComplete();
+ }
+ }
+
+ public <T extends View> T inflateAndAdd(int resId) {
+ View view = mInflater.inflate(resId, this, false);
+ addView(view);
+ return (T) view;
+ }
+
/**
* Shows the notifications and deep shortcuts associated with {@param icon}.
* @return the container if shown or null.
@@ -124,188 +267,420 @@
private void populateAndShow(final BubbleTextView originalIcon, final List<String> shortcutIds,
final List<NotificationKeyData> notificationKeys, List<SystemShortcut> systemShortcuts) {
mNumNotifications = notificationKeys.size();
- PopupPopulator.Item[] itemsToPopulate = PopupPopulator
- .getItemsToPopulate(shortcutIds, notificationKeys, systemShortcuts);
- populateAndShow(originalIcon, itemsToPopulate);
- ItemInfo originalItemInfo = (ItemInfo) originalIcon.getTag();
- List<DeepShortcutView> shortcutViews = mShortcutsItemView == null
- ? Collections.EMPTY_LIST
- : mShortcutsItemView.getDeepShortcutViews(mIsAboveIcon);
- List<View> systemShortcutViews = mShortcutsItemView == null
- ? Collections.EMPTY_LIST
- : mShortcutsItemView.getSystemShortcutViews(mIsAboveIcon);
- if (mNotificationItemView != null) {
+ setVisibility(View.INVISIBLE);
+ mLauncher.getDragLayer().addView(this);
+
+ mOriginalIcon = originalIcon;
+
+ // Add views
+ if (mNumNotifications > 0) {
+ // Add notification entries
+ View.inflate(getContext(), R.layout.notification_content, this);
+ mNotificationItemView = new NotificationItemView(this);
+ if (mNumNotifications == 1) {
+ mNotificationItemView.removeFooter();
+ }
updateNotificationHeader();
}
+ int viewsToFlip = getChildCount();
+ mSystemShortcutContainer = this;
- int numShortcuts = shortcutViews.size() + systemShortcutViews.size();
- int numNotifications = notificationKeys.size();
- if (numNotifications == 0) {
+ if (!shortcutIds.isEmpty()) {
+ if (mNotificationItemView != null) {
+ mNotificationItemView.addGutter();
+ }
+
+ for (int i = shortcutIds.size(); i > 0; i--) {
+ mShortcuts.add(inflateAndAdd(R.layout.deep_shortcut));
+ }
+ updateHiddenShortcuts();
+
+ if (!systemShortcuts.isEmpty()) {
+ mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons);
+ for (SystemShortcut shortcut : systemShortcuts) {
+ View view = mInflater.inflate(R.layout.system_shortcut_icon_only,
+ mSystemShortcutContainer, false);
+ mSystemShortcutContainer.addView(view);
+ initializeSystemShortcut(view, shortcut);
+ }
+ }
+ } else if (!systemShortcuts.isEmpty()) {
+ if (mNotificationItemView != null) {
+ mNotificationItemView.addGutter();
+ }
+
+ for (SystemShortcut shortcut : systemShortcuts) {
+ initializeSystemShortcut(inflateAndAdd(R.layout.system_shortcut), shortcut);
+ }
+ }
+ orientAboutIcon();
+
+ boolean reverseOrder = mIsAboveIcon;
+ if (reverseOrder) {
+ int count = getChildCount();
+ ArrayList<View> allViews = new ArrayList<>(count);
+ for (int i = 0; i < count; i++) {
+ if (i == viewsToFlip) {
+ Collections.reverse(allViews);
+ }
+ allViews.add(getChildAt(i));
+ }
+ Collections.reverse(allViews);
+ removeAllViews();
+ for (int i = 0; i < count; i++) {
+ addView(allViews.get(i));
+ }
+ if (mNotificationItemView != null) {
+ mNotificationItemView.inverseGutterMargin();
+ }
+
+ orientAboutIcon();
+ }
+ updateDividers();
+
+ // Add the arrow.
+ final int arrowHorizontalOffset = getResources().getDimensionPixelSize(isAlignedWithStart()
+ ? R.dimen.popup_arrow_horizontal_offset_start
+ : R.dimen.popup_arrow_horizontal_offset_end);
+ mLauncher.getDragLayer().addView(mArrow);
+ DragLayer.LayoutParams arrowLp = (DragLayer.LayoutParams) mArrow.getLayoutParams();
+ if (mIsLeftAligned) {
+ mArrow.setX(getX() + arrowHorizontalOffset);
+ } else {
+ mArrow.setX(getX() + getMeasuredWidth() - arrowHorizontalOffset);
+ }
+
+ if (Gravity.isVertical(mGravity)) {
+ // This is only true if there wasn't room for the container next to the icon,
+ // so we centered it instead. In that case we don't want to show the arrow.
+ mArrow.setVisibility(INVISIBLE);
+ } else {
+ ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
+ arrowLp.width, arrowLp.height, !mIsAboveIcon));
+ Paint arrowPaint = arrowDrawable.getPaint();
+ arrowPaint.setColor(Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary));
+ // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
+ int radius = getResources().getDimensionPixelSize(R.dimen.popup_arrow_corner_radius);
+ arrowPaint.setPathEffect(new CornerPathEffect(radius));
+ mArrow.setBackground(arrowDrawable);
+ mArrow.setElevation(getElevation());
+ }
+
+ mArrow.setPivotX(arrowLp.width / 2);
+ mArrow.setPivotY(mIsAboveIcon ? 0 : arrowLp.height);
+
+ animateOpen();
+
+ ItemInfo originalItemInfo = (ItemInfo) originalIcon.getTag();
+ int numShortcuts = mShortcuts.size() + systemShortcuts.size();
+ if (mNumNotifications == 0) {
setContentDescription(getContext().getString(R.string.shortcuts_menu_description,
numShortcuts, originalIcon.getContentDescription().toString()));
} else {
setContentDescription(getContext().getString(
R.string.shortcuts_menu_with_notifications_description, numShortcuts,
- numNotifications, originalIcon.getContentDescription().toString()));
+ mNumNotifications, originalIcon.getContentDescription().toString()));
}
mLauncher.getDragController().addDragListener(this);
mOriginalIcon.forceHideBadge(true);
+ // All views are added. Animate layout from now on.
+ setLayoutTransition(new LayoutTransition());
+
// Load the shortcuts on a background thread and update the container as it animates.
final Looper workerLooper = LauncherModel.getWorkerLooper();
new Handler(workerLooper).postAtFrontOfQueue(PopupPopulator.createUpdateRunnable(
mLauncher, originalItemInfo, new Handler(Looper.getMainLooper()),
- this, shortcutIds, shortcutViews, notificationKeys, mNotificationItemView,
- systemShortcuts, systemShortcutViews));
+ this, shortcutIds, mShortcuts, notificationKeys));
}
- @Override
- protected void addDummyViews(Item[] itemTypesToPopulate) {
- mNotificationItemView = null;
- super.addDummyViews(itemTypesToPopulate);
- if (mNumNotifications > 0) {
- mShortcutsItemView.hideShortcuts(mIsAboveIcon, MAX_SHORTCUTS_IF_NOTIFICATIONS);
- }
+ protected boolean isAlignedWithStart() {
+ return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl;
}
- @Override
- protected void onViewInflated(View view, Item itemType,
- boolean shouldUnroundTopCorners, boolean shouldUnroundBottomCorners) {
- if (itemType == PopupPopulator.Item.NOTIFICATION) {
- mNotificationItemView = (NotificationItemView) view;
- boolean notificationFooterHasIcons = mNumNotifications > 1;
- int footerHeight = getResources().getDimensionPixelSize(
- notificationFooterHasIcons ? R.dimen.notification_footer_height
- : R.dimen.notification_empty_footer_height);
- view.findViewById(R.id.footer).getLayoutParams().height = footerHeight;
- if (notificationFooterHasIcons) {
- mNotificationItemView.findViewById(R.id.divider).setVisibility(VISIBLE);
- }
+ /**
+ * Orients this container above or below the given icon, aligning with the left or right.
+ *
+ * These are the preferred orientations, in order (RTL prefers right-aligned over left):
+ * - Above and left-aligned
+ * - Above and right-aligned
+ * - Below and left-aligned
+ * - Below and right-aligned
+ *
+ * So we always align left if there is enough horizontal space
+ * and align above if there is enough vertical space.
+ */
+ protected void orientAboutIcon() {
+ measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ int width = getMeasuredWidth();
+ int extraVerticalSpace = mArrow.getLayoutParams().height + mArrayOffset
+ + getResources().getDimensionPixelSize(R.dimen.popup_vertical_padding);
+ int height = getMeasuredHeight() + extraVerticalSpace;
- int roundedCorners = ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS;
- if (shouldUnroundTopCorners) {
- roundedCorners &= ~ROUNDED_TOP_CORNERS;
- mNotificationItemView.findViewById(R.id.gutter_top).setVisibility(VISIBLE);
- }
- if (shouldUnroundBottomCorners) {
- roundedCorners &= ~ROUNDED_BOTTOM_CORNERS;
- mNotificationItemView.findViewById(R.id.gutter_bottom).setVisibility(VISIBLE);
- }
- int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorTertiary);
- mNotificationItemView.setBackgroundWithCorners(backgroundColor, roundedCorners);
+ DragLayer dragLayer = mLauncher.getDragLayer();
+ dragLayer.getDescendantRectRelativeToSelf(mOriginalIcon, mTempRect);
+ Rect insets = dragLayer.getInsets();
- mNotificationItemView.getMainView().setAccessibilityDelegate(mAccessibilityDelegate);
- } else if (itemType == PopupPopulator.Item.SHORTCUT) {
- view.setAccessibilityDelegate(mAccessibilityDelegate);
+ // Align left (right in RTL) if there is room.
+ int leftAlignedX = mTempRect.left + mOriginalIcon.getPaddingLeft();
+ int rightAlignedX = mTempRect.right - width - mOriginalIcon.getPaddingRight();
+ int x = leftAlignedX;
+ boolean canBeLeftAligned = leftAlignedX + width + insets.left
+ < dragLayer.getRight() - insets.right;
+ boolean canBeRightAligned = rightAlignedX > dragLayer.getLeft() + insets.left;
+ if (!canBeLeftAligned || (mIsRtl && canBeRightAligned)) {
+ x = rightAlignedX;
+ }
+ mIsLeftAligned = x == leftAlignedX;
+ if (mIsRtl) {
+ x -= dragLayer.getWidth() - width;
}
- if (itemType != PopupPopulator.Item.SYSTEM_SHORTCUT_ICON && itemType.isShortcut
- && mNumNotifications > 0) {
- int prevHeight = view.getLayoutParams().height;
- // Condense shortcuts height when there are notifications.
- view.getLayoutParams().height = getResources().getDimensionPixelSize(
- R.dimen.bg_popup_item_condensed_height);
- if (view instanceof DeepShortcutView) {
- float iconScale = (float) view.getLayoutParams().height / prevHeight;
- ((DeepShortcutView) view).getIconView().setScaleX(iconScale);
- ((DeepShortcutView) view).getIconView().setScaleY(iconScale);
- }
+ // Offset x so that the arrow and shortcut icons are center-aligned with the original icon.
+ int iconWidth = mOriginalIcon.getWidth()
+ - mOriginalIcon.getTotalPaddingLeft() - mOriginalIcon.getTotalPaddingRight();
+ iconWidth *= mOriginalIcon.getScaleX();
+ Resources resources = getResources();
+ int xOffset;
+ if (isAlignedWithStart()) {
+ // Aligning with the shortcut icon.
+ int shortcutIconWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcut_icon_size);
+ int shortcutPaddingStart = resources.getDimensionPixelSize(
+ R.dimen.popup_padding_start);
+ xOffset = iconWidth / 2 - shortcutIconWidth / 2 - shortcutPaddingStart;
+ } else {
+ // Aligning with the drag handle.
+ int shortcutDragHandleWidth = resources.getDimensionPixelSize(
+ R.dimen.deep_shortcut_drag_handle_size);
+ int shortcutPaddingEnd = resources.getDimensionPixelSize(
+ R.dimen.popup_padding_end);
+ xOffset = iconWidth / 2 - shortcutDragHandleWidth / 2 - shortcutPaddingEnd;
}
- }
+ x += mIsLeftAligned ? xOffset : -xOffset;
- private void addDummyViews(PopupPopulator.Item[] itemTypesToPopulate, int numNotifications) {
- final Resources res = getResources();
- final LayoutInflater inflater = mLauncher.getLayoutInflater();
+ // Open above icon if there is room.
+ int iconHeight = getIconHeightForPopupPlacement();
+ int y = mTempRect.top + mOriginalIcon.getPaddingTop() - height;
+ mIsAboveIcon = y > dragLayer.getTop() + insets.top;
+ if (!mIsAboveIcon) {
+ y = mTempRect.top + mOriginalIcon.getPaddingTop() + iconHeight + extraVerticalSpace;
+ }
- int shortcutsItemRoundedCorners = ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS;
- int numItems = itemTypesToPopulate.length;
- for (int i = 0; i < numItems; i++) {
- PopupPopulator.Item itemTypeToPopulate = itemTypesToPopulate[i];
- PopupPopulator.Item prevItemTypeToPopulate =
- i > 0 ? itemTypesToPopulate[i - 1] : null;
- PopupPopulator.Item nextItemTypeToPopulate =
- i < numItems - 1 ? itemTypesToPopulate[i + 1] : null;
- final View item = inflater.inflate(itemTypeToPopulate.layoutId, this, false);
+ // Insets are added later, so subtract them now.
+ if (mIsRtl) {
+ x += insets.right;
+ } else {
+ x -= insets.left;
+ }
+ y -= insets.top;
- boolean shouldUnroundTopCorners = prevItemTypeToPopulate != null
- && itemTypeToPopulate.isShortcut ^ prevItemTypeToPopulate.isShortcut;
- boolean shouldUnroundBottomCorners = nextItemTypeToPopulate != null
- && itemTypeToPopulate.isShortcut ^ nextItemTypeToPopulate.isShortcut;
-
- if (itemTypeToPopulate == PopupPopulator.Item.NOTIFICATION) {
- mNotificationItemView = (NotificationItemView) item;
- boolean notificationFooterHasIcons = numNotifications > 1;
- int footerHeight = res.getDimensionPixelSize(
- notificationFooterHasIcons ? R.dimen.notification_footer_height
- : R.dimen.notification_empty_footer_height);
- item.findViewById(R.id.footer).getLayoutParams().height = footerHeight;
- if (notificationFooterHasIcons) {
- mNotificationItemView.findViewById(R.id.divider).setVisibility(VISIBLE);
- }
-
- int roundedCorners = ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS;
- if (shouldUnroundTopCorners) {
- roundedCorners &= ~ROUNDED_TOP_CORNERS;
- mNotificationItemView.findViewById(R.id.gutter_top).setVisibility(VISIBLE);
- }
- if (shouldUnroundBottomCorners) {
- roundedCorners &= ~ROUNDED_BOTTOM_CORNERS;
- mNotificationItemView.findViewById(R.id.gutter_bottom).setVisibility(VISIBLE);
- }
- int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorTertiary);
- mNotificationItemView.setBackgroundWithCorners(backgroundColor, roundedCorners);
-
- mNotificationItemView.getMainView().setAccessibilityDelegate(mAccessibilityDelegate);
- } else if (itemTypeToPopulate == PopupPopulator.Item.SHORTCUT) {
- item.setAccessibilityDelegate(mAccessibilityDelegate);
- }
-
- if (itemTypeToPopulate.isShortcut) {
- if (mShortcutsItemView == null) {
- mShortcutsItemView = (ShortcutsItemView) inflater.inflate(
- R.layout.shortcuts_item, this, false);
- addView(mShortcutsItemView);
- if (shouldUnroundTopCorners) {
- shortcutsItemRoundedCorners &= ~ROUNDED_TOP_CORNERS;
- }
- }
- if (itemTypeToPopulate != PopupPopulator.Item.SYSTEM_SHORTCUT_ICON
- && numNotifications > 0) {
- int prevHeight = item.getLayoutParams().height;
- // Condense shortcuts height when there are notifications.
- item.getLayoutParams().height = res.getDimensionPixelSize(
- R.dimen.bg_popup_item_condensed_height);
- if (item instanceof DeepShortcutView) {
- float iconScale = (float) item.getLayoutParams().height / prevHeight;
- ((DeepShortcutView) item).getIconView().setScaleX(iconScale);
- ((DeepShortcutView) item).getIconView().setScaleY(iconScale);
- }
- }
- mShortcutsItemView.addShortcutView(item, itemTypeToPopulate);
- if (shouldUnroundBottomCorners) {
- shortcutsItemRoundedCorners &= ~ROUNDED_BOTTOM_CORNERS;
+ mGravity = 0;
+ if (y + height > dragLayer.getBottom() - insets.bottom) {
+ // The container is opening off the screen, so just center it in the drag layer instead.
+ mGravity = Gravity.CENTER_VERTICAL;
+ // Put the container next to the icon, preferring the right side in ltr (left in rtl).
+ int rightSide = leftAlignedX + iconWidth - insets.left;
+ int leftSide = rightAlignedX - iconWidth - insets.left;
+ if (!mIsRtl) {
+ if (rightSide + width < dragLayer.getRight()) {
+ x = rightSide;
+ mIsLeftAligned = true;
+ } else {
+ x = leftSide;
+ mIsLeftAligned = false;
}
} else {
- addView(item);
+ if (leftSide > dragLayer.getLeft()) {
+ x = leftSide;
+ mIsLeftAligned = false;
+ } else {
+ x = rightSide;
+ mIsLeftAligned = true;
+ }
}
+ mIsAboveIcon = true;
}
- int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary);
- mShortcutsItemView.setBackgroundWithCorners(backgroundColor, shortcutsItemRoundedCorners);
- if (numNotifications > 0) {
- mShortcutsItemView.hideShortcuts(mIsAboveIcon, MAX_SHORTCUTS_IF_NOTIFICATIONS);
+
+ setX(x);
+ if (Gravity.isVertical(mGravity)) {
+ return;
+ }
+
+ DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+ DragLayer.LayoutParams arrowLp = (DragLayer.LayoutParams) mArrow.getLayoutParams();
+ if (mIsAboveIcon) {
+ arrowLp.gravity = lp.gravity = Gravity.BOTTOM;
+ lp.bottomMargin =
+ mLauncher.getDragLayer().getHeight() - y - getMeasuredHeight() - insets.top;
+ arrowLp.bottomMargin = lp.bottomMargin - arrowLp.height - mArrayOffset - insets.bottom;
+ } else {
+ arrowLp.gravity = lp.gravity = Gravity.TOP;
+ lp.topMargin = y + insets.top;
+ arrowLp.topMargin = lp.topMargin - insets.top - arrowLp.height - mArrayOffset;
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+
+ // enforce contained is within screen
+ DragLayer dragLayer = mLauncher.getDragLayer();
+ if (getTranslationX() + l < 0 ||
+ getTranslationX() + r > dragLayer.getWidth()) {
+ // If we are still off screen, center horizontally too.
+ mGravity |= Gravity.CENTER_HORIZONTAL;
+ }
+
+ if (Gravity.isHorizontal(mGravity)) {
+ setX(dragLayer.getWidth() / 2 - getMeasuredWidth() / 2);
+ mArrow.setVisibility(INVISIBLE);
+ }
+ if (Gravity.isVertical(mGravity)) {
+ setY(dragLayer.getHeight() / 2 - getMeasuredHeight() / 2);
+ }
+ }
+
+ protected void animateOpen() {
+ setVisibility(View.VISIBLE);
+ mIsOpen = true;
+
+ final AnimatorSet openAnim = LauncherAnimUtils.createAnimatorSet();
+ final Resources res = getResources();
+ final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration);
+ final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
+
+ // Rectangular reveal.
+ final ValueAnimator revealAnim = createOpenCloseOutlineProvider()
+ .createRevealAnimator(this, false);
+ revealAnim.setDuration(revealDuration);
+ revealAnim.setInterpolator(revealInterpolator);
+
+ Animator fadeIn = ObjectAnimator.ofFloat(this, ALPHA, 0, 1);
+ fadeIn.setDuration(revealDuration);
+ fadeIn.setInterpolator(revealInterpolator);
+ openAnim.play(fadeIn);
+
+ // Animate the arrow.
+ mArrow.setScaleX(0);
+ mArrow.setScaleY(0);
+ Animator arrowScale = ObjectAnimator.ofFloat(mArrow, LauncherAnimUtils.SCALE_PROPERTY, 1)
+ .setDuration(res.getInteger(R.integer.config_popupArrowOpenDuration));
+
+ openAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mOpenCloseAnimator = null;
+ Utilities.sendCustomAccessibilityEvent(
+ PopupContainerWithArrow.this,
+ AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
+ getContext().getString(R.string.action_deep_shortcut));
+ }
+ });
+
+ mOpenCloseAnimator = openAnim;
+ openAnim.playSequentially(revealAnim, arrowScale);
+ openAnim.start();
+ }
+
+ public void applyNotificationInfos(List<NotificationInfo> notificationInfos) {
+ mNotificationItemView.applyNotificationInfos(notificationInfos);
+ }
+
+ private void updateHiddenShortcuts() {
+ int allowedCount = mNotificationItemView != null
+ ? MAX_SHORTCUTS_IF_NOTIFICATIONS : MAX_SHORTCUTS;
+ int originalHeight = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height);
+ int itemHeight = mNotificationItemView != null ?
+ getResources().getDimensionPixelSize(R.dimen.bg_popup_item_condensed_height)
+ : originalHeight;
+ float iconScale = ((float) itemHeight) / originalHeight;
+
+ int total = mShortcuts.size();
+ for (int i = 0; i < total; i++) {
+ DeepShortcutView view = mShortcuts.get(i);
+ view.setVisibility(i >= allowedCount ? GONE : VISIBLE);
+ view.getLayoutParams().height = itemHeight;
+ view.getIconView().setScaleX(iconScale);
+ view.getIconView().setScaleY(iconScale);
+ }
+ }
+
+ private void updateDividers() {
+ int count = getChildCount();
+ DeepShortcutView lastView = null;
+ for (int i = 0; i < count; i++) {
+ View view = getChildAt(i);
+ if (view.getVisibility() == VISIBLE && view instanceof DeepShortcutView) {
+ if (lastView != null) {
+ lastView.setDividerVisibility(VISIBLE);
+ }
+ lastView = (DeepShortcutView) view;
+ lastView.setDividerVisibility(INVISIBLE);
+ }
}
}
@Override
protected void onWidgetsBound() {
- if (mShortcutsItemView != null) {
- mShortcutsItemView.enableWidgetsIfExist(mOriginalIcon);
+ ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
+ SystemShortcut widgetInfo = new SystemShortcut.Widgets();
+ View.OnClickListener onClickListener = widgetInfo.getOnClickListener(mLauncher, itemInfo);
+ View widgetsView = null;
+ int count = mSystemShortcutContainer.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View systemShortcutView = mSystemShortcutContainer.getChildAt(i);
+ if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) {
+ widgetsView = systemShortcutView;
+ break;
+ }
+ }
+
+ if (onClickListener != null && widgetsView == null) {
+ // We didn't have any widgets cached but now there are some, so enable the shortcut.
+ if (mSystemShortcutContainer != this) {
+ View view = mInflater.inflate(R.layout.system_shortcut_icon_only,
+ mSystemShortcutContainer, false);
+ mSystemShortcutContainer.addView(view);
+ initializeSystemShortcut(view, widgetInfo);
+ } else {
+ // If using the expanded system shortcut (as opposed to just the icon), we need to
+ // reopen the container to ensure measurements etc. all work out. While this could
+ // be quite janky, in practice the user would typically see a small flicker as the
+ // animation restarts partway through, and this is a very rare edge case anyway.
+ ((PopupContainerWithArrow) getParent()).close(false);
+ PopupContainerWithArrow.showForIcon(mOriginalIcon);
+ }
+ } else if (onClickListener == null && widgetsView != null) {
+ // No widgets exist, but we previously added the shortcut so remove it.
+ if (mSystemShortcutContainer != this) {
+ mSystemShortcutContainer.removeView(widgetsView);
+ } else {
+ ((PopupContainerWithArrow) getParent()).close(false);
+ PopupContainerWithArrow.showForIcon(mOriginalIcon);
+ }
}
}
- @Override
+ private void initializeSystemShortcut(View view, SystemShortcut info) {
+ 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);
+ } 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));
+ }
+ view.setTag(info);
+ view.setOnClickListener(info.getOnClickListener(mLauncher,
+ (ItemInfo) mOriginalIcon.getTag()));
+ }
+
protected int getIconHeightForPopupPlacement() {
return mOriginalIcon.getIcon() != null
? mOriginalIcon.getIcon().getBounds().height()
@@ -383,108 +758,15 @@
ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag();
BadgeInfo badgeInfo = updatedBadges.get(PackageUserKey.fromItemInfo(originalInfo));
if (badgeInfo == null || badgeInfo.getNotificationKeys().size() == 0) {
- // There are no more notifications, so create an animation to remove
- // the notifications view and expand the shortcuts view (if possible).
- AnimatorSet removeNotification = LauncherAnimUtils.createAnimatorSet();
- int hiddenShortcutsHeight = 0;
- if (mShortcutsItemView != null) {
- hiddenShortcutsHeight = mShortcutsItemView.getHiddenShortcutsHeight();
- int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary);
- // With notifications gone, all corners of shortcuts item should be rounded.
- mShortcutsItemView.setBackgroundWithCorners(backgroundColor,
- ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS);
- removeNotification.play(mShortcutsItemView.showAllShortcuts(mIsAboveIcon));
- }
- final int duration = getResources().getInteger(
- R.integer.config_removeNotificationViewDuration);
- removeNotification.play(adjustItemHeights(mNotificationItemView.getHeightMinusFooter(),
- hiddenShortcutsHeight, duration));
- Animator fade = ObjectAnimator.ofFloat(mNotificationItemView, ALPHA, 0)
- .setDuration(duration);
- fade.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- removeView(mNotificationItemView);
- mNotificationItemView = null;
- if (getItemCount() == 0) {
- close(false);
- }
- }
- });
- removeNotification.play(fade);
- final long arrowScaleDuration = getResources().getInteger(
- R.integer.config_popupArrowOpenDuration);
- Animator hideArrow = createArrowScaleAnim(0).setDuration(arrowScaleDuration);
- hideArrow.setStartDelay(0);
- Animator showArrow = createArrowScaleAnim(1).setDuration(arrowScaleDuration);
- showArrow.setStartDelay((long) (duration - arrowScaleDuration * 1.5));
- removeNotification.playSequentially(hideArrow, showArrow);
- removeNotification.start();
- return;
+ // No more notifications, remove the notification views and expand all shortcuts.
+ mNotificationItemView.removeAllViews();
+ mNotificationItemView = null;
+ updateHiddenShortcuts();
+ updateDividers();
+ } else {
+ mNotificationItemView.trimNotifications(
+ NotificationKeyData.extractKeysOnly(badgeInfo.getNotificationKeys()));
}
- mNotificationItemView.trimNotifications(NotificationKeyData.extractKeysOnly(
- badgeInfo.getNotificationKeys()));
- }
-
- public Animator reduceNotificationViewHeight(int heightToRemove, int duration) {
- return adjustItemHeights(heightToRemove, 0, duration);
- }
-
- /**
- * Animates the height of the notification item and the translationY of other items accordingly.
- */
- public Animator adjustItemHeights(int notificationHeightToRemove, int shortcutHeightToAdd,
- int duration) {
- if (mReduceHeightAnimatorSet != null) {
- mReduceHeightAnimatorSet.cancel();
- }
- final int translateYBy = mIsAboveIcon ? notificationHeightToRemove - shortcutHeightToAdd
- : -notificationHeightToRemove;
- mReduceHeightAnimatorSet = LauncherAnimUtils.createAnimatorSet();
- boolean removingNotification =
- notificationHeightToRemove == mNotificationItemView.getHeightMinusFooter();
- boolean shouldRemoveNotificationHeightFromTop = mIsAboveIcon && removingNotification;
- mReduceHeightAnimatorSet.play(mNotificationItemView.animateHeightRemoval(
- notificationHeightToRemove, shouldRemoveNotificationHeightFromTop));
- PropertyResetListener<View, Float> resetTranslationYListener
- = new PropertyResetListener<>(TRANSLATION_Y, 0f);
- boolean itemIsAfterShortcuts = false;
- for (int i = 0; i < getItemCount(); i++) {
- final PopupItemView itemView = getItemViewAt(i);
- if (itemIsAfterShortcuts) {
- // Every item after the shortcuts item needs to adjust for the new height.
- itemView.setTranslationY(itemView.getTranslationY() - shortcutHeightToAdd);
- }
- if (itemView == mNotificationItemView && (!mIsAboveIcon || removingNotification)) {
- // The notification view is already in the right place.
- continue;
- }
- ValueAnimator translateItem = ObjectAnimator.ofFloat(itemView, TRANSLATION_Y,
- itemView.getTranslationY() + translateYBy).setDuration(duration);
- translateItem.addListener(resetTranslationYListener);
- mReduceHeightAnimatorSet.play(translateItem);
- if (itemView == mShortcutsItemView) {
- itemIsAfterShortcuts = true;
- }
- }
- if (mIsAboveIcon) {
- // We also need to adjust the arrow position to account for the new shortcuts height.
- mArrow.setTranslationY(mArrow.getTranslationY() - shortcutHeightToAdd);
- }
- mReduceHeightAnimatorSet.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mIsAboveIcon) {
- // All the items, including the notification item, translated down, but the
- // container itself did not. This means the items would jump back to their
- // original translation unless we update the container's translationY here.
- setTranslationY(getTranslationY() + translateYBy);
- mArrow.setTranslationY(0);
- }
- mReduceHeightAnimatorSet = null;
- }
- });
- return mReduceHeightAnimatorSet;
}
@Override
@@ -515,25 +797,146 @@
@Override
public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
- target.itemType = ItemType.DEEPSHORTCUT;
+ if (info == NOTIFICATION_ITEM_INFO) {
+ target.itemType = ItemType.NOTIFICATION;
+ } else {
+ target.itemType = ItemType.DEEPSHORTCUT;
+ target.rank = info.rank;
+ }
targetParent.containerType = ContainerType.DEEPSHORTCUTS;
}
- @Override
- protected void prepareCloseAnimator(AnimatorSet closeAnim) {
+ protected void animateClose() {
+ if (!mIsOpen) {
+ return;
+ }
+ mEndRect.setEmpty();
+ if (mOpenCloseAnimator != null) {
+ Outline outline = new Outline();
+ getOutlineProvider().getOutline(this, outline);
+ outline.getRect(mEndRect);
+ mOpenCloseAnimator.cancel();
+ }
+ mIsOpen = false;
+
+ final AnimatorSet closeAnim = LauncherAnimUtils.createAnimatorSet();
+ // Hide the arrow
+ closeAnim.play(ObjectAnimator.ofFloat(mArrow, LauncherAnimUtils.SCALE_PROPERTY, 0));
+ closeAnim.play(ObjectAnimator.ofFloat(mArrow, ALPHA, 0));
+
// Animate original icon's text back in.
closeAnim.play(mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */));
-
mOriginalIcon.forceHideBadge(false);
- super.prepareCloseAnimator(closeAnim);
+
+ final Resources res = getResources();
+ final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
+
+ // Rectangular reveal (reversed).
+ final ValueAnimator revealAnim = createOpenCloseOutlineProvider()
+ .createRevealAnimator(this, true);
+ revealAnim.setInterpolator(revealInterpolator);
+ closeAnim.play(revealAnim);
+
+ Animator fadeOut = ObjectAnimator.ofFloat(this, ALPHA, 0);
+ fadeOut.setInterpolator(revealInterpolator);
+ closeAnim.play(fadeOut);
+ closeAnim.setDuration((long) res.getInteger(R.integer.config_popupOpenCloseDuration));
+
+ closeAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mOpenCloseAnimator = null;
+ if (mDeferContainerRemoval) {
+ setVisibility(INVISIBLE);
+ } else {
+ closeComplete();
+ }
+ }
+ });
+ mOpenCloseAnimator = closeAnim;
+ closeAnim.start();
}
- @Override
- protected void closeComplete() {
+ private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
+ int arrowCenterX = getResources().getDimensionPixelSize(mIsLeftAligned ^ mIsRtl ?
+ R.dimen.popup_arrow_horizontal_center_start:
+ R.dimen.popup_arrow_horizontal_center_end);
+ if (!mIsLeftAligned) {
+ arrowCenterX = getMeasuredWidth() - arrowCenterX;
+ }
+ int arrowCenterY = mIsAboveIcon ? getMeasuredHeight() : 0;
+
+ mStartRect.set(arrowCenterX, arrowCenterY, arrowCenterX, arrowCenterY);
+ if (mEndRect.isEmpty()) {
+ mEndRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
+ }
+
+ return new RoundedRectRevealOutlineProvider
+ (mOutlineRadius, mOutlineRadius, mStartRect, mEndRect);
+ }
+
+ /**
+ * Closes the popup without animation.
+ */
+ private void closeComplete() {
mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible());
mOriginalIcon.forceHideBadge(false);
mLauncher.getDragController().removeDragListener(this);
- super.closeComplete();
+ if (mOpenCloseAnimator != null) {
+ mOpenCloseAnimator.cancel();
+ mOpenCloseAnimator = null;
+ }
+ mIsOpen = false;
+ mDeferContainerRemoval = false;
+ mLauncher.getDragLayer().removeView(this);
+ mLauncher.getDragLayer().removeView(mArrow);
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent ev) {
+ // Touched a shortcut, update where it was touched so we can drag from there on long click.
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
+ break;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ // Return early if not the correct view
+ if (!(v.getParent() instanceof DeepShortcutView)) return false;
+ // Return early if global dragging is not enabled
+ if (!mLauncher.isDraggingEnabled()) return false;
+ // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
+ if (mLauncher.getDragController().isDragging()) return false;
+
+ // Long clicked on a shortcut.
+ DeepShortcutView sv = (DeepShortcutView) v.getParent();
+ sv.setWillDrawIcon(false);
+
+ // Move the icon to align with the center-top of the touch point
+ Point iconShift = new Point();
+ iconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
+ iconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
+
+ DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
+ this, sv.getFinalInfo(),
+ new ShortcutDragPreviewProvider(sv.getIconView(), iconShift), new DragOptions());
+ dv.animateShift(-iconShift.x, -iconShift.y);
+
+ // TODO: support dragging from within folder without having to close it
+ AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
+ return false;
+ }
+
+ /**
+ * Returns a PopupContainerWithArrow which is already open or null
+ */
+ public static PopupContainerWithArrow getOpen(Launcher launcher) {
+ return getOpenView(launcher, TYPE_ACTION_POPUP);
}
}
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 070ac39..abc186b 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -148,9 +148,9 @@
}
private void trimNotifications(Map<PackageUserKey, BadgeInfo> updatedBadges) {
- BaseActionPopup openContainer = BaseActionPopup.getOpen(mLauncher);
- if (openContainer instanceof PopupContainerWithArrow) {
- ((PopupContainerWithArrow) openContainer).trimNotifications(updatedBadges);
+ PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher);
+ if (openContainer != null) {
+ openContainer.trimNotifications(updatedBadges);
}
}
diff --git a/src/com/android/launcher3/popup/PopupItemView.java b/src/com/android/launcher3/popup/PopupItemView.java
deleted file mode 100644
index 75c3f26..0000000
--- a/src/com/android/launcher3/popup/PopupItemView.java
+++ /dev/null
@@ -1,145 +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.popup;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.drawable.ShapeDrawable;
-import android.graphics.drawable.shapes.RoundRectShape;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.popup.BaseActionPopup.RoundedCornerFlags;
-
-import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_BOTTOM_CORNERS;
-import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_TOP_CORNERS;
-
-/**
- * An abstract {@link FrameLayout} that contains content for {@link PopupContainerWithArrow}.
- */
-public abstract class PopupItemView extends FrameLayout {
-
- protected final Rect mPillRect;
- protected @RoundedCornerFlags int mRoundedCorners;
- protected final boolean mIsRtl;
- protected View mIconView;
-
- private final Paint mBackgroundClipPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
- private final Matrix mMatrix = new Matrix();
- private Bitmap mRoundedCornerBitmap;
-
- public PopupItemView(Context context) {
- this(context, null, 0);
- }
-
- public PopupItemView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public PopupItemView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- mPillRect = new Rect();
-
- // Initialize corner clipping Bitmap and Paint.
- int radius = (int) getBackgroundRadius();
- mRoundedCornerBitmap = Bitmap.createBitmap(radius, radius, Bitmap.Config.ALPHA_8);
- Canvas canvas = new Canvas();
- canvas.setBitmap(mRoundedCornerBitmap);
- canvas.drawArc(0, 0, radius*2, radius*2, 180, 90, true, mBackgroundClipPaint);
- mBackgroundClipPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
-
- mIsRtl = Utilities.isRtl(getResources());
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mIconView = findViewById(R.id.popup_item_icon);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- mPillRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
- }
-
- @Override
- protected void dispatchDraw(Canvas canvas) {
- if (mRoundedCorners == 0) {
- super.dispatchDraw(canvas);
- return;
- }
-
- int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null);
- super.dispatchDraw(canvas);
-
- // Clip children to this item's rounded corners.
- int cornerWidth = mRoundedCornerBitmap.getWidth();
- int cornerHeight = mRoundedCornerBitmap.getHeight();
- int cornerCenterX = Math.round(cornerWidth / 2f);
- int cornerCenterY = Math.round(cornerHeight / 2f);
- if ((mRoundedCorners & ROUNDED_TOP_CORNERS) != 0) {
- // Clip top left corner.
- mMatrix.reset();
- canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
- // Clip top right corner.
- mMatrix.setRotate(90, cornerCenterX, cornerCenterY);
- mMatrix.postTranslate(canvas.getWidth() - cornerWidth, 0);
- canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
- }
- if ((mRoundedCorners & ROUNDED_BOTTOM_CORNERS) != 0) {
- // Clip bottom right corner.
- mMatrix.setRotate(180, cornerCenterX, cornerCenterY);
- mMatrix.postTranslate(canvas.getWidth() - cornerWidth, canvas.getHeight() - cornerHeight);
- canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
- // Clip bottom left corner.
- mMatrix.setRotate(270, cornerCenterX, cornerCenterY);
- mMatrix.postTranslate(0, canvas.getHeight() - cornerHeight);
- canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
- }
-
- canvas.restoreToCount(saveCount);
- }
-
- /**
- * Creates a round rect drawable (with the specified corners unrounded)
- * and sets it as this View's background.
- */
- public void setBackgroundWithCorners(int color, @RoundedCornerFlags int roundedCorners) {
- mRoundedCorners = roundedCorners;
- float rTop = (roundedCorners & ROUNDED_TOP_CORNERS) == 0 ? 0 : getBackgroundRadius();
- float rBot = (roundedCorners & ROUNDED_BOTTOM_CORNERS) == 0 ? 0 : getBackgroundRadius();
- float[] radii = new float[] {rTop, rTop, rTop, rTop, rBot, rBot, rBot, rBot};
- ShapeDrawable roundRectBackground = new ShapeDrawable(new RoundRectShape(radii, null, null));
- roundRectBackground.getPaint().setColor(color);
- setBackground(roundRectBackground);
- }
-
- protected float getBackgroundRadius() {
- return getResources().getDimensionPixelSize(R.dimen.bg_round_rect_radius);
- }
-}
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index 0dc1ca0..6c83d12 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -17,23 +17,17 @@
package com.android.launcher3.popup;
import android.content.ComponentName;
-import android.content.Context;
import android.os.Handler;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
-import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
-import android.view.View;
-import android.widget.ImageView;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.graphics.LauncherIcons;
import com.android.launcher3.notification.NotificationInfo;
-import com.android.launcher3.notification.NotificationItemView;
import com.android.launcher3.notification.NotificationKeyData;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.DeepShortcutView;
@@ -56,55 +50,6 @@
@VisibleForTesting static final int NUM_DYNAMIC = 2;
public static final int MAX_SHORTCUTS_IF_NOTIFICATIONS = 2;
- public enum Item {
- SHORTCUT(R.layout.deep_shortcut, true),
- NOTIFICATION(R.layout.notification, false),
- SYSTEM_SHORTCUT(R.layout.system_shortcut, true),
- SYSTEM_SHORTCUT_ICON(R.layout.system_shortcut_icon_only, true);
-
- public final int layoutId;
- public final boolean isShortcut;
-
- Item(int layoutId, boolean isShortcut) {
- this.layoutId = layoutId;
- this.isShortcut = isShortcut;
- }
- }
-
- public static @NonNull Item[] getItemsToPopulate(@NonNull List<String> shortcutIds,
- @NonNull List<NotificationKeyData> notificationKeys,
- @NonNull List<SystemShortcut> systemShortcuts) {
- boolean hasNotifications = notificationKeys.size() > 0;
- int numNotificationItems = hasNotifications ? 1 : 0;
- int numShortcuts = shortcutIds.size();
- int numItems = Math.min(MAX_SHORTCUTS, numShortcuts) + numNotificationItems
- + systemShortcuts.size();
- Item[] items = new Item[numItems];
- for (int i = 0; i < numItems; i++) {
- items[i] = Item.SHORTCUT;
- }
- if (hasNotifications) {
- // The notification layout is always first.
- items[0] = Item.NOTIFICATION;
- }
- // The system shortcuts are always last.
- boolean iconsOnly = !shortcutIds.isEmpty();
- for (int i = 0; i < systemShortcuts.size(); i++) {
- items[numItems - 1 - i] = iconsOnly ? Item.SYSTEM_SHORTCUT_ICON : Item.SYSTEM_SHORTCUT;
- }
- return items;
- }
-
- public static Item[] reverseItems(Item[] items) {
- if (items == null) return null;
- int numItems = items.length;
- Item[] reversedArray = new Item[numItems];
- for (int i = 0; i < numItems; i++) {
- reversedArray[i] = items[numItems - i - 1];
- }
- return reversedArray;
- }
-
/**
* Sorts shortcuts in rank order, with manifest shortcuts coming before dynamic shortcuts.
*/
@@ -179,137 +124,42 @@
public static Runnable createUpdateRunnable(final Launcher launcher, final ItemInfo originalInfo,
final Handler uiHandler, final PopupContainerWithArrow container,
final List<String> shortcutIds, final List<DeepShortcutView> shortcutViews,
- final List<NotificationKeyData> notificationKeys,
- final NotificationItemView notificationView, final List<SystemShortcut> systemShortcuts,
- final List<View> systemShortcutViews) {
+ final List<NotificationKeyData> notificationKeys) {
final ComponentName activity = originalInfo.getTargetComponent();
final UserHandle user = originalInfo.user;
- return new Runnable() {
- @Override
- public void run() {
- if (notificationView != null) {
- List<StatusBarNotification> notifications = launcher.getPopupDataProvider()
- .getStatusBarNotificationsForKeys(notificationKeys);
- List<NotificationInfo> infos = new ArrayList<>(notifications.size());
- for (int i = 0; i < notifications.size(); i++) {
- StatusBarNotification notification = notifications.get(i);
- infos.add(new NotificationInfo(launcher, notification));
- }
- uiHandler.post(new UpdateNotificationChild(notificationView, infos));
+ return () -> {
+ if (!notificationKeys.isEmpty()) {
+ List<StatusBarNotification> notifications = launcher.getPopupDataProvider()
+ .getStatusBarNotificationsForKeys(notificationKeys);
+ List<NotificationInfo> infos = new ArrayList<>(notifications.size());
+ for (int i = 0; i < notifications.size(); i++) {
+ StatusBarNotification notification = notifications.get(i);
+ infos.add(new NotificationInfo(launcher, notification));
}
-
- List<ShortcutInfoCompat> shortcuts = DeepShortcutManager.getInstance(launcher)
- .queryForShortcutsContainer(activity, shortcutIds, user);
- String shortcutIdToDeDupe = notificationKeys.isEmpty() ? null
- : notificationKeys.get(0).shortcutId;
- shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts, shortcutIdToDeDupe);
- for (int i = 0; i < shortcuts.size() && i < shortcutViews.size(); i++) {
- final ShortcutInfoCompat shortcut = shortcuts.get(i);
- ShortcutInfo si = new ShortcutInfo(shortcut, launcher);
- // Use unbadged icon for the menu.
- si.iconBitmap = LauncherIcons.createShortcutIcon(
- shortcut, launcher, false /* badged */);
- si.rank = i;
- uiHandler.post(new UpdateShortcutChild(container, shortcutViews.get(i),
- si, shortcut));
- }
-
- // This ensures that mLauncher.getWidgetsForPackageUser()
- // doesn't return null (it puts all the widgets in memory).
- for (int i = 0; i < systemShortcuts.size(); i++) {
- final SystemShortcut systemShortcut = systemShortcuts.get(i);
- uiHandler.post(new UpdateSystemShortcutChild(container,
- systemShortcutViews.get(i), systemShortcut, launcher, originalInfo));
- }
- uiHandler.post(new Runnable() {
- @Override
- public void run() {
- launcher.refreshAndBindWidgetsForPackageUser(
- PackageUserKey.fromItemInfo(originalInfo));
- }
- });
+ uiHandler.post(() -> container.applyNotificationInfos(infos));
}
+
+ List<ShortcutInfoCompat> shortcuts = DeepShortcutManager.getInstance(launcher)
+ .queryForShortcutsContainer(activity, shortcutIds, user);
+ String shortcutIdToDeDupe = notificationKeys.isEmpty() ? null
+ : notificationKeys.get(0).shortcutId;
+ shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts, shortcutIdToDeDupe);
+ for (int i = 0; i < shortcuts.size() && i < shortcutViews.size(); i++) {
+ final ShortcutInfoCompat shortcut = shortcuts.get(i);
+ final ShortcutInfo si = new ShortcutInfo(shortcut, launcher);
+ // Use unbadged icon for the menu.
+ si.iconBitmap = LauncherIcons.createShortcutIcon(
+ shortcut, launcher, false /* badged */);
+ si.rank = i;
+
+ final DeepShortcutView view = shortcutViews.get(i);
+ uiHandler.post(() -> view.applyShortcutInfo(si, shortcut, container));
+ }
+
+ // This ensures that mLauncher.getWidgetsForPackageUser()
+ // doesn't return null (it puts all the widgets in memory).
+ uiHandler.post(() -> launcher.refreshAndBindWidgetsForPackageUser(
+ PackageUserKey.fromItemInfo(originalInfo)));
};
}
-
- /** Updates the shortcut child of this container based on the given shortcut info. */
- private static class UpdateShortcutChild implements Runnable {
- private final PopupContainerWithArrow mContainer;
- private final DeepShortcutView mShortcutChild;
- private final ShortcutInfo mShortcutChildInfo;
- private final ShortcutInfoCompat mDetail;
-
- public UpdateShortcutChild(PopupContainerWithArrow container, DeepShortcutView shortcutChild,
- ShortcutInfo shortcutChildInfo, ShortcutInfoCompat detail) {
- mContainer = container;
- mShortcutChild = shortcutChild;
- mShortcutChildInfo = shortcutChildInfo;
- mDetail = detail;
- }
-
- @Override
- public void run() {
- mShortcutChild.applyShortcutInfo(mShortcutChildInfo, mDetail,
- mContainer.mShortcutsItemView);
- }
- }
-
- /** Updates the notification child based on the given notification info. */
- private static class UpdateNotificationChild implements Runnable {
- private NotificationItemView mNotificationView;
- private List<NotificationInfo> mNotificationInfos;
-
- public UpdateNotificationChild(NotificationItemView notificationView,
- List<NotificationInfo> notificationInfos) {
- mNotificationView = notificationView;
- mNotificationInfos = notificationInfos;
- }
-
- @Override
- public void run() {
- mNotificationView.applyNotificationInfos(mNotificationInfos);
- }
- }
-
- /** Updates the system shortcut child based on the given shortcut info. */
- private static class UpdateSystemShortcutChild implements Runnable {
-
- private final PopupContainerWithArrow mContainer;
- private final View mSystemShortcutChild;
- private final SystemShortcut mSystemShortcutInfo;
- private final Launcher mLauncher;
- private final ItemInfo mItemInfo;
-
- public UpdateSystemShortcutChild(PopupContainerWithArrow container, View systemShortcutChild,
- SystemShortcut systemShortcut, Launcher launcher, ItemInfo originalInfo) {
- mContainer = container;
- mSystemShortcutChild = systemShortcutChild;
- mSystemShortcutInfo = systemShortcut;
- mLauncher = launcher;
- mItemInfo = originalInfo;
- }
-
- @Override
- public void run() {
- final Context context = mSystemShortcutChild.getContext();
- initializeSystemShortcut(context, mSystemShortcutChild, mSystemShortcutInfo);
- mSystemShortcutChild.setOnClickListener(mSystemShortcutInfo
- .getOnClickListener(mLauncher, mItemInfo));
- }
- }
-
- public static void initializeSystemShortcut(Context context, View view, SystemShortcut info) {
- if (view instanceof DeepShortcutView) {
- // Expanded system shortcut, with both icon and text shown on white background.
- final DeepShortcutView shortcutView = (DeepShortcutView) view;
- shortcutView.getIconView().setBackground(info.getIcon(context));
- shortcutView.getBubbleText().setText(info.getLabel(context));
- } else if (view instanceof ImageView) {
- // Only the system shortcut icon shows on a gray background header.
- final ImageView shortcutIcon = (ImageView) view;
- shortcutIcon.setImageDrawable(info.getIcon(context));
- shortcutIcon.setContentDescription(info.getLabel(context));
- }
- view.setTag(info);
- }
}
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index e709b93..c398aaa 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -1,9 +1,7 @@
package com.android.launcher3.popup;
-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;
@@ -31,20 +29,12 @@
* Example system shortcuts, defined as inner classes, include Widgets and AppInfo.
*/
public abstract class SystemShortcut extends ItemInfo {
- private final int mIconResId;
- private final int mLabelResId;
+ public final int iconResId;
+ public final int labelResId;
public SystemShortcut(int iconResId, int labelResId) {
- mIconResId = iconResId;
- mLabelResId = labelResId;
- }
-
- public Drawable getIcon(Context context) {
- return context.getResources().getDrawable(mIconResId, context.getTheme());
- }
-
- public String getLabel(Context context) {
- return context.getString(mLabelResId);
+ this.iconResId = iconResId;
+ this.labelResId = labelResId;
}
public abstract View.OnClickListener getOnClickListener(final Launcher launcher,
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
index 75a4886..450a690 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -29,6 +29,7 @@
import com.android.launcher3.R;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
+import com.android.launcher3.popup.PopupContainerWithArrow;
/**
* A {@link android.widget.FrameLayout} that contains a {@link DeepShortcutView}.
@@ -42,6 +43,7 @@
private BubbleTextView mBubbleText;
private View mIconView;
+ private View mDivider;
private ShortcutInfo mInfo;
private ShortcutInfoCompat mDetail;
@@ -65,6 +67,11 @@
super.onFinishInflate();
mBubbleText = findViewById(R.id.bubble_text);
mIconView = findViewById(R.id.icon);
+ mDivider = findViewById(R.id.divider);
+ }
+
+ public void setDividerVisibility(int visibility) {
+ mDivider.setVisibility(visibility);
}
public BubbleTextView getBubbleText() {
@@ -98,7 +105,7 @@
/** package private **/
public void applyShortcutInfo(ShortcutInfo info, ShortcutInfoCompat detail,
- ShortcutsItemView container) {
+ PopupContainerWithArrow container) {
mInfo = info;
mDetail = detail;
mBubbleText.applyFromShortcutInfo(info);
diff --git a/src/com/android/launcher3/shortcuts/ShortcutsItemView.java b/src/com/android/launcher3/shortcuts/ShortcutsItemView.java
deleted file mode 100644
index b4fa04e..0000000
--- a/src/com/android/launcher3/shortcuts/ShortcutsItemView.java
+++ /dev/null
@@ -1,340 +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.shortcuts;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.LinearLayout;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.R;
-import com.android.launcher3.anim.PropertyListBuilder;
-import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.dragndrop.DragView;
-import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
-import com.android.launcher3.popup.PopupContainerWithArrow;
-import com.android.launcher3.popup.PopupItemView;
-import com.android.launcher3.popup.PopupPopulator;
-import com.android.launcher3.popup.SystemShortcut;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A {@link PopupItemView} that contains all of the {@link DeepShortcutView}s for an app,
- * as well as the system shortcuts such as Widgets and App Info.
- */
-public class ShortcutsItemView extends PopupItemView implements View.OnLongClickListener,
- View.OnTouchListener, LogContainerProvider {
-
- private static final String TAG = "ShortcutsItem";
-
- private Launcher mLauncher;
- private LinearLayout mContent;
- private LinearLayout mShortcutsLayout;
- private LinearLayout mSystemShortcutIcons;
- private final Point mIconShift = new Point();
- private final Point mIconLastTouchPos = new Point();
- private final List<DeepShortcutView> mDeepShortcutViews = new ArrayList<>();
- private final List<View> mSystemShortcutViews = new ArrayList<>();
-
- private int mHiddenShortcutsHeight;
-
- public ShortcutsItemView(Context context) {
- this(context, null, 0);
- }
-
- public ShortcutsItemView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ShortcutsItemView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- mLauncher = Launcher.getLauncher(context);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mContent = findViewById(R.id.content);
- mShortcutsLayout = findViewById(R.id.shortcuts);
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent ev) {
- // Touched a shortcut, update where it was touched so we can drag from there on long click.
- switch (ev.getAction()) {
- case MotionEvent.ACTION_DOWN:
- case MotionEvent.ACTION_MOVE:
- mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
- break;
- }
- return false;
- }
-
- @Override
- public boolean onLongClick(View v) {
- // Return early if not the correct view
- if (!(v.getParent() instanceof DeepShortcutView)) return false;
- // Return early if global dragging is not enabled
- if (!mLauncher.isDraggingEnabled()) return false;
- // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
- if (mLauncher.getDragController().isDragging()) return false;
-
- // Long clicked on a shortcut.
- DeepShortcutView sv = (DeepShortcutView) v.getParent();
- sv.setWillDrawIcon(false);
-
- // Move the icon to align with the center-top of the touch point
- mIconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
- mIconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
-
- DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
- (PopupContainerWithArrow) getParent(), sv.getFinalInfo(),
- new ShortcutDragPreviewProvider(sv.getIconView(), mIconShift), new DragOptions());
- dv.animateShift(-mIconShift.x, -mIconShift.y);
-
- // TODO: support dragging from within folder without having to close it
- AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
- return false;
- }
-
- public void addShortcutView(View shortcutView, PopupPopulator.Item shortcutType) {
- addShortcutView(shortcutView, shortcutType, -1);
- }
-
- private void addShortcutView(View shortcutView, PopupPopulator.Item shortcutType, int index) {
- if (shortcutType == PopupPopulator.Item.SHORTCUT) {
- mDeepShortcutViews.add((DeepShortcutView) shortcutView);
- } else {
- mSystemShortcutViews.add(shortcutView);
- }
- if (shortcutType == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
- // System shortcut icons are added to a header that is separate from the full shortcuts.
- if (mSystemShortcutIcons == null) {
- mSystemShortcutIcons = (LinearLayout) mLauncher.getLayoutInflater().inflate(
- R.layout.system_shortcut_icons, mContent, false);
- boolean iconsAreBelowShortcuts = mShortcutsLayout.getChildCount() > 0;
- mContent.addView(mSystemShortcutIcons, iconsAreBelowShortcuts ? -1 : 0);
- }
- mSystemShortcutIcons.addView(shortcutView, index);
- } else {
- if (mShortcutsLayout.getChildCount() > 0) {
- View prevChild = mShortcutsLayout.getChildAt(mShortcutsLayout.getChildCount() - 1);
- if (prevChild instanceof DeepShortcutView) {
- prevChild.findViewById(R.id.divider).setVisibility(VISIBLE);
- }
- }
- mShortcutsLayout.addView(shortcutView, index);
- }
- }
-
- public List<DeepShortcutView> getDeepShortcutViews(boolean reverseOrder) {
- if (reverseOrder) {
- Collections.reverse(mDeepShortcutViews);
- }
- return mDeepShortcutViews;
- }
-
- public List<View> getSystemShortcutViews(boolean reverseOrder) {
- // Always reverse system shortcut icons (in the header)
- // so they are in priority order from right to left.
- if (reverseOrder || mSystemShortcutIcons != null) {
- Collections.reverse(mSystemShortcutViews);
- }
- return mSystemShortcutViews;
- }
-
- /**
- * Hides shortcuts until only {@param maxShortcuts} are showing. Also sets
- * {@link #mHiddenShortcutsHeight} to be the amount of extra space that shortcuts will
- * require when {@link #showAllShortcuts(boolean)} is called.
- */
- public void hideShortcuts(boolean hideFromTop, int maxShortcuts) {
- // When shortcuts are shown, they get more space allocated to them.
- final int oldHeight = mShortcutsLayout.getChildAt(0).getLayoutParams().height;
- final int newHeight = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height);
- mHiddenShortcutsHeight = (newHeight - oldHeight) * mShortcutsLayout.getChildCount();
-
- int numToHide = mShortcutsLayout.getChildCount() - maxShortcuts;
- if (numToHide <= 0) {
- return;
- }
- final int numShortcuts = mShortcutsLayout.getChildCount();
- final int dir = hideFromTop ? 1 : -1;
- for (int i = hideFromTop ? 0 : numShortcuts - 1; 0 <= i && i < numShortcuts; i += dir) {
- View child = mShortcutsLayout.getChildAt(i);
- if (child instanceof DeepShortcutView) {
- mHiddenShortcutsHeight += child.getLayoutParams().height;
- child.setVisibility(GONE);
- int prev = i + dir;
- if (!hideFromTop && 0 <= prev && prev < numShortcuts) {
- // When hiding views from the bottom, make sure to hide the last divider.
- mShortcutsLayout.getChildAt(prev).findViewById(R.id.divider).setVisibility(GONE);
- }
- numToHide--;
- if (numToHide == 0) {
- break;
- }
- }
- }
- }
-
- public int getHiddenShortcutsHeight() {
- return mHiddenShortcutsHeight;
- }
-
- /**
- * Sets all shortcuts in {@link #mShortcutsLayout} to VISIBLE, then creates an
- * animation to reveal the newly shown shortcuts.
- *
- * @see #hideShortcuts(boolean, int)
- */
- public Animator showAllShortcuts(boolean showFromTop) {
- // First set all the shortcuts to VISIBLE.
- final int numShortcuts = mShortcutsLayout.getChildCount();
- if (numShortcuts == 0) {
- Log.w(TAG, "Tried to show all shortcuts but there were no shortcuts to show");
- return null;
- }
- final int oldHeight = mShortcutsLayout.getChildAt(0).getLayoutParams().height;
- final int newHeight = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height);
- for (int i = 0; i < numShortcuts; i++) {
- DeepShortcutView view = (DeepShortcutView) mShortcutsLayout.getChildAt(i);
- view.getLayoutParams().height = newHeight;
- view.requestLayout();
- view.setVisibility(VISIBLE);
- if (i < numShortcuts - 1) {
- view.findViewById(R.id.divider).setVisibility(VISIBLE);
- }
- }
-
- // Now reveal the newly shown shortcuts.
- AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
-
- if (showFromTop) {
- // The new shortcuts pushed the original shortcuts down, but we want to animate them
- // to that position. So we revert the translation and animate to the new.
- animation.play(translateYFrom(mShortcutsLayout, -mHiddenShortcutsHeight));
- } else if (mSystemShortcutIcons != null) {
- // When adding the shortcuts from the bottom, things are a little trickier, since
- // that means they push the icons header down. To account for this, we do the same
- // translation trick as above, but on the header. Since this means leaving behind
- // a blank area where the header was, we also need to clip the background.
- animation.play(translateYFrom(mSystemShortcutIcons, -mHiddenShortcutsHeight));
- // mPillRect is the bounds of this view before the new shortcuts were shown.
- Rect backgroundStartRect = new Rect(mPillRect);
- Rect backgroundEndRect = new Rect(mPillRect);
- backgroundEndRect.bottom += mHiddenShortcutsHeight;
- animation.play(new RoundedRectRevealOutlineProvider(getBackgroundRadius(),
- getBackgroundRadius(), backgroundStartRect, backgroundEndRect, mRoundedCorners)
- .createRevealAnimator(this, false));
- }
- for (int i = 0; i < numShortcuts; i++) {
- // Animate each shortcut to its new height.
- DeepShortcutView shortcut = (DeepShortcutView) mShortcutsLayout.getChildAt(i);
- int heightDiff = newHeight - oldHeight;
- int heightAdjustmentIndex = showFromTop ? numShortcuts - i - 1 : i;
- int fromDir = showFromTop ? 1 : -1;
- animation.play(translateYFrom(shortcut, heightDiff * heightAdjustmentIndex * fromDir));
- // Make sure the text and icon stay centered in the shortcut.
- animation.play(translateYFrom(shortcut.getBubbleText(), heightDiff / 2 * fromDir));
- animation.play(translateYFrom(shortcut.getIconView(), heightDiff / 2 * fromDir));
- // Scale icons back up to full size.
- animation.play(LauncherAnimUtils.ofPropertyValuesHolder(shortcut.getIconView(),
- new PropertyListBuilder().scale(1f).build()));
- }
- return animation;
- }
-
- /**
- * Animates the translationY of the view from the given offset to the view's current translation
- * @return an Animator, which should be started by the caller.
- */
- private Animator translateYFrom(View v, int diff) {
- float finalY = v.getTranslationY();
- return ObjectAnimator.ofFloat(v, TRANSLATION_Y, finalY + diff, finalY);
- }
-
- /**
- * Adds a {@link SystemShortcut.Widgets} item if there are widgets for the given ItemInfo.
- */
- public void enableWidgetsIfExist(final BubbleTextView originalIcon) {
- ItemInfo itemInfo = (ItemInfo) originalIcon.getTag();
- SystemShortcut widgetInfo = new SystemShortcut.Widgets();
- View.OnClickListener onClickListener = widgetInfo.getOnClickListener(mLauncher, itemInfo);
- View widgetsView = null;
- for (View systemShortcutView : mSystemShortcutViews) {
- if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) {
- widgetsView = systemShortcutView;
- break;
- }
- }
- final PopupPopulator.Item widgetsItem = mSystemShortcutIcons == null
- ? PopupPopulator.Item.SYSTEM_SHORTCUT
- : PopupPopulator.Item.SYSTEM_SHORTCUT_ICON;
- if (onClickListener != null && widgetsView == null) {
- // We didn't have any widgets cached but now there are some, so enable the shortcut.
- widgetsView = mLauncher.getLayoutInflater().inflate(widgetsItem.layoutId, this, false);
- PopupPopulator.initializeSystemShortcut(getContext(), widgetsView, widgetInfo);
- widgetsView.setOnClickListener(onClickListener);
- if (widgetsItem == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
- addShortcutView(widgetsView, widgetsItem, 0);
- } else {
- // If using the expanded system shortcut (as opposed to just the icon), we need to
- // reopen the container to ensure measurements etc. all work out. While this could
- // be quite janky, in practice the user would typically see a small flicker as the
- // animation restarts partway through, and this is a very rare edge case anyway.
- ((PopupContainerWithArrow) getParent()).close(false);
- PopupContainerWithArrow.showForIcon(originalIcon);
- }
- } else if (onClickListener == null && widgetsView != null) {
- // No widgets exist, but we previously added the shortcut so remove it.
- if (widgetsItem == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
- mSystemShortcutViews.remove(widgetsView);
- mSystemShortcutIcons.removeView(widgetsView);
- } else {
- ((PopupContainerWithArrow) getParent()).close(false);
- PopupContainerWithArrow.showForIcon(originalIcon);
- }
- }
- }
-
- @Override
- public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
- LauncherLogProto.Target targetParent) {
- target.itemType = LauncherLogProto.ItemType.DEEPSHORTCUT;
- target.rank = info.rank;
- targetParent.containerType = LauncherLogProto.ContainerType.DEEPSHORTCUTS;
- }
-}
diff --git a/src/com/android/launcher3/states/AllAppsState.java b/src/com/android/launcher3/states/AllAppsState.java
new file mode 100644
index 0000000..ed3023a
--- /dev/null
+++ b/src/com/android/launcher3/states/AllAppsState.java
@@ -0,0 +1,60 @@
+/*
+ * 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.states;
+
+import static com.android.launcher3.LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
+
+import android.view.View;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+
+/**
+ * Definition for AllApps state
+ */
+public class AllAppsState extends LauncherState {
+
+ public static final String APPS_VIEW_SHOWN = "launcher.apps_view_shown";
+
+ private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY;
+
+ public AllAppsState(int id) {
+ super(id, ContainerType.ALLAPPS, ALL_APPS_TRANSITION_MS, 0f, STATE_FLAGS);
+ }
+
+ @Override
+ public void onStateEnabled(Launcher launcher) {
+ if (!launcher.getSharedPrefs().getBoolean(APPS_VIEW_SHOWN, false)) {
+ launcher.getSharedPrefs().edit().putBoolean(APPS_VIEW_SHOWN, true).apply();
+ }
+
+ AbstractFloatingView.closeAllOpenViews(launcher);
+ dispatchWindowStateChanged(launcher);
+ }
+
+ @Override
+ public String getDescription(Launcher launcher) {
+ return launcher.getString(R.string.all_apps_button_label);
+ }
+
+ @Override
+ public View getFinalFocus(Launcher launcher) {
+ return launcher.getAppsView();
+ }
+}
diff --git a/src/com/android/launcher3/states/InternalStateHandler.java b/src/com/android/launcher3/states/InternalStateHandler.java
new file mode 100644
index 0000000..f084fd2
--- /dev/null
+++ b/src/com/android/launcher3/states/InternalStateHandler.java
@@ -0,0 +1,68 @@
+/*
+ * 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.states;
+
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.Launcher.OnResumeCallback;
+
+/**
+ * Utility class to sending state handling logic to Launcher from within the same process.
+ *
+ * Extending {@link Binder} ensures that the platform maintains a single instance of each object
+ * which allows this object to safely navigate the system process.
+ */
+public abstract class InternalStateHandler extends Binder implements OnResumeCallback {
+
+ public static final String EXTRA_STATE_HANDLER = "launcher.state_handler";
+
+ protected abstract void init(Launcher launcher, boolean alreadyOnHome);
+
+ public final Intent addToIntent(Intent intent) {
+ Bundle extras = new Bundle();
+ extras.putBinder(EXTRA_STATE_HANDLER, this);
+ intent.putExtras(extras);
+ return intent;
+ }
+
+ public static boolean handleCreate(Launcher launcher, Intent intent) {
+ return handleIntent(launcher, intent, false);
+ }
+
+ public static boolean handleNewIntent(Launcher launcher, Intent intent, boolean alreadyOnHome) {
+ return handleIntent(launcher, intent, alreadyOnHome);
+ }
+
+ private static boolean handleIntent(
+ Launcher launcher, Intent intent, boolean alreadyOnHome) {
+ boolean result = false;
+ if (intent != null && intent.getExtras() != null) {
+ IBinder stateBinder = intent.getExtras().getBinder(EXTRA_STATE_HANDLER);
+ if (stateBinder instanceof InternalStateHandler) {
+ InternalStateHandler handler = (InternalStateHandler) stateBinder;
+ launcher.setOnResumeCallback(handler);
+ handler.init(launcher, alreadyOnHome);
+ result = true;
+ }
+ intent.getExtras().remove(EXTRA_STATE_HANDLER);
+ }
+ return result;
+ }
+}
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
new file mode 100644
index 0000000..3864e3a
--- /dev/null
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -0,0 +1,114 @@
+/*
+ * 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.states;
+
+import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_TRANSITION_MS;
+
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.view.View;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InstallShortcutReceiver;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+
+/**
+ * Definition for spring loaded state used during drag and drop.
+ */
+public class SpringLoadedState extends LauncherState {
+
+ private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_MULTI_PAGE |
+ FLAG_DISABLE_ACCESSIBILITY | FLAG_DO_NOT_RESTORE;
+
+ // Determines how long to wait after a rotation before restoring the screen orientation to
+ // match the sensor state.
+ private static final int RESTORE_SCREEN_ORIENTATION_DELAY = 500;
+
+ public SpringLoadedState(int id) {
+ super(id, ContainerType.OVERVIEW, SPRING_LOADED_TRANSITION_MS, 1f, STATE_FLAGS);
+ }
+
+ @Override
+ public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
+ DeviceProfile grid = launcher.getDeviceProfile();
+ Workspace ws = launcher.getWorkspace();
+ if (grid.isVerticalBarLayout() || ws.getChildCount() == 0) {
+ return super.getWorkspaceScaleAndTranslation(launcher);
+ }
+
+ float scale = grid.workspaceSpringLoadShrinkFactor;
+ Rect insets = launcher.getDragLayer().getInsets();
+
+ float scaledHeight = scale * ws.getNormalChildHeight();
+ float shrunkTop = insets.top + grid.dropTargetBarSizePx;
+ float shrunkBottom = ws.getViewportHeight() - insets.bottom
+ - grid.getWorkspacePadding(null).bottom
+ - grid.workspaceSpringLoadedBottomSpace;
+ float totalShrunkSpace = shrunkBottom - shrunkTop;
+
+ float desiredCellTop = shrunkTop + (totalShrunkSpace - scaledHeight) / 2;
+
+ float halfHeight = ws.getHeight() / 2;
+ float myCenter = ws.getTop() + halfHeight;
+ float cellTopFromCenter = halfHeight - ws.getChildAt(0).getTop();
+ float actualCellTop = myCenter - cellTopFromCenter * scale;
+ return new float[] { scale, (desiredCellTop - actualCellTop) / scale};
+ }
+
+ @Override
+ public void onStateEnabled(Launcher launcher) {
+ Workspace ws = launcher.getWorkspace();
+ ws.showPageIndicatorAtCurrentScroll();
+ ws.getPageIndicator().setShouldAutoHide(false);
+
+ // Lock the orientation:
+ if (launcher.isRotationEnabled()) {
+ launcher.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
+ }
+
+ // Prevent any Un/InstallShortcutReceivers from updating the db while we are
+ // in spring loaded mode
+ InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_DRAG_AND_DROP);
+ }
+
+ @Override
+ public void onStateDisabled(final Launcher launcher) {
+ launcher.getWorkspace().getPageIndicator().setShouldAutoHide(true);
+
+ // Unlock rotation lock
+ if (launcher.isRotationEnabled()) {
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ launcher.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ }
+ }, RESTORE_SCREEN_ORIENTATION_DELAY);
+ }
+
+ // Re-enable any Un/InstallShortcutReceiver and now process any queued items
+ InstallShortcutReceiver.disableAndFlushInstallQueue(
+ InstallShortcutReceiver.FLAG_DRAG_AND_DROP, launcher);
+ }
+
+ @Override
+ public View getFinalFocus(Launcher launcher) {
+ return null;
+ }
+}
diff --git a/src/com/android/launcher3/testing/LauncherExtension.java b/src/com/android/launcher3/testing/LauncherExtension.java
deleted file mode 100644
index c40e1fb..0000000
--- a/src/com/android/launcher3/testing/LauncherExtension.java
+++ /dev/null
@@ -1,128 +0,0 @@
-package com.android.launcher3.testing;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.Menu;
-
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherCallbacks;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-/**
- * This class represents a very trivial LauncherExtension. It primarily serves as a simple
- * class to exercise the LauncherOverlay interface.
- */
-public class LauncherExtension extends Launcher {
-
- //------ Activity methods -------//
- @Override
- public void onCreate(Bundle savedInstanceState) {
- setLauncherCallbacks(new LauncherExtensionCallbacks());
- super.onCreate(savedInstanceState);
- }
-
- public class LauncherExtensionCallbacks implements LauncherCallbacks {
-
- @Override
- public void preOnCreate() {
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- }
-
- @Override
- public void onResume() {
- }
-
- @Override
- public void onStart() {
- }
-
- @Override
- public void onStop() {
- }
-
- @Override
- public void onPause() {
- }
-
- @Override
- public void onDestroy() {
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- }
-
- @Override
- public void onNewIntent(Intent intent) {
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, String[] permissions,
- int[] grantResults) {
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasFocus) {
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- return false;
- }
-
- @Override
- public void dump(String prefix, FileDescriptor fd, PrintWriter w, String[] args) {
- }
-
- @Override
- public void onHomeIntent() {
- }
-
- @Override
- public boolean handleBackPressed() {
- return false;
- }
-
- @Override
- public void onTrimMemory(int level) {
- }
-
- @Override
- public void onLauncherProviderChange() {
- }
-
- @Override
- public void bindAllApplications(ArrayList<AppInfo> apps) {
- }
-
- @Override
- public boolean startSearch(String initialQuery, boolean selectInitialQuery,
- Bundle appSearchData) {
- return false;
- }
-
- @Override
- public boolean hasSettings() {
- return false;
- }
-
- @Override
- public void onAttachedToWindow() {
- }
-
- @Override
- public void onDetachedFromWindow() {
- }
- }
-}
diff --git a/src/com/android/launcher3/testing/MemoryDumpActivity.java b/src/com/android/launcher3/testing/MemoryDumpActivity.java
deleted file mode 100644
index 9bcf92b..0000000
--- a/src/com/android/launcher3/testing/MemoryDumpActivity.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2013 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.testing;
-
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.IBinder;
-import android.util.Log;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-
-public class MemoryDumpActivity extends Activity {
- private static final String TAG = "MemoryDumpActivity";
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-
- public static String zipUp(ArrayList<String> paths) {
- final int BUFSIZ = 256 * 1024; // 256K
- final byte[] buf = new byte[BUFSIZ];
- final String zipfilePath = String.format("%s/hprof-%d.zip",
- Environment.getExternalStorageDirectory(),
- System.currentTimeMillis());
- ZipOutputStream zos = null;
- try {
- OutputStream os = new FileOutputStream(zipfilePath);
- zos = new ZipOutputStream(new BufferedOutputStream(os));
- for (String filename : paths) {
- InputStream is = null;
- try {
- is = new BufferedInputStream(new FileInputStream(filename));
- ZipEntry entry = new ZipEntry(filename);
- zos.putNextEntry(entry);
- int len;
- while ( 0 < (len = is.read(buf, 0, BUFSIZ)) ) {
- zos.write(buf, 0, len);
- }
- zos.closeEntry();
- } finally {
- is.close();
- }
- }
- } catch (IOException e) {
- Log.e(TAG, "error zipping up profile data", e);
- return null;
- } finally {
- if (zos != null) {
- try {
- zos.close();
- } catch (IOException e) {
- // ugh, whatever
- }
- }
- }
- return zipfilePath;
- }
-
- public static void dumpHprofAndShare(final Context context, MemoryTracker tracker) {
- final StringBuilder body = new StringBuilder();
-
- final ArrayList<String> paths = new ArrayList<String>();
- final int myPid = android.os.Process.myPid();
-
- final int[] pids_orig = tracker.getTrackedProcesses();
- final int[] pids_copy = Arrays.copyOf(pids_orig, pids_orig.length);
- for (int pid : pids_copy) {
- MemoryTracker.ProcessMemInfo info = tracker.getMemInfo(pid);
- if (info != null) {
- body.append("pid ").append(pid).append(":")
- .append(" up=").append(info.getUptime())
- .append(" pss=").append(info.currentPss)
- .append(" uss=").append(info.currentUss)
- .append("\n");
- }
- if (pid == myPid) {
- final String path = String.format("%s/launcher-memory-%d.ahprof",
- Environment.getExternalStorageDirectory(),
- pid);
- Log.v(TAG, "Dumping memory info for process " + pid + " to " + path);
- try {
- android.os.Debug.dumpHprofData(path); // will block
- } catch (IOException e) {
- Log.e(TAG, "error dumping memory:", e);
- }
- paths.add(path);
- }
- }
-
- String zipfile = zipUp(paths);
-
- if (zipfile == null) return;
-
- Intent shareIntent = new Intent(Intent.ACTION_SEND);
- shareIntent.setType("application/zip");
-
- final PackageManager pm = context.getPackageManager();
- shareIntent.putExtra(Intent.EXTRA_SUBJECT, String.format("Launcher memory dump (%d)", myPid));
- String appVersion;
- try {
- appVersion = pm.getPackageInfo(context.getPackageName(), 0).versionName;
- } catch (PackageManager.NameNotFoundException e) {
- appVersion = "?";
- }
-
- body.append("\nApp version: ").append(appVersion).append("\nBuild: ").append(Build.DISPLAY).append("\n");
- shareIntent.putExtra(Intent.EXTRA_TEXT, body.toString());
-
- final File pathFile = new File(zipfile);
- final Uri pathUri = Uri.fromFile(pathFile);
-
- shareIntent.putExtra(Intent.EXTRA_STREAM, pathUri);
- context.startActivity(shareIntent);
- }
-
- @Override
- public void onStart() {
- super.onStart();
-
- startDump(this, new Runnable() {
- @Override
- public void run() {
- finish();
- }
- });
- }
-
- public static void startDump(final Context context) {
- startDump(context, null);
- }
-
- public static void startDump(final Context context, final Runnable andThen) {
- final ServiceConnection connection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
- Log.v(TAG, "service connected, dumping...");
- dumpHprofAndShare(context,
- ((MemoryTracker.MemoryTrackerInterface) service).getService());
- context.unbindService(this);
- if (andThen != null) andThen.run();
- }
-
- public void onServiceDisconnected(ComponentName className) {
- }
- };
- Log.v(TAG, "attempting to bind to memory tracker");
- context.bindService(new Intent(context, MemoryTracker.class),
- connection, Context.BIND_AUTO_CREATE);
- }
-}
diff --git a/src/com/android/launcher3/testing/MemoryTracker.java b/src/com/android/launcher3/testing/MemoryTracker.java
deleted file mode 100644
index ed2a312..0000000
--- a/src/com/android/launcher3/testing/MemoryTracker.java
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright (C) 2013 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.testing;
-
-import android.app.ActivityManager;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Binder;
-import android.os.Debug;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.SystemClock;
-import android.util.Log;
-import android.util.LongSparseArray;
-
-import com.android.launcher3.util.TestingUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class MemoryTracker extends Service {
- public static final String TAG = MemoryTracker.class.getSimpleName();
-
- private static final long UPDATE_RATE = 5000;
-
- private static final int MSG_START = 1;
- private static final int MSG_STOP = 2;
- private static final int MSG_UPDATE = 3;
-
- public static class ProcessMemInfo {
- public int pid;
- public String name;
- public long startTime;
- public long currentPss, currentUss;
- public long[] pss = new long[256];
- public long[] uss = new long[256];
- //= new Meminfo[(int) (30 * 60 / (UPDATE_RATE / 1000))]; // 30 minutes
- public long max = 1;
- public int head = 0;
- public ProcessMemInfo(int pid, String name, long start) {
- this.pid = pid;
- this.name = name;
- this.startTime = start;
- }
- public long getUptime() {
- return System.currentTimeMillis() - startTime;
- }
- };
- public final LongSparseArray<ProcessMemInfo> mData = new LongSparseArray<ProcessMemInfo>();
- public final ArrayList<Long> mPids = new ArrayList<Long>();
- private int[] mPidsArray = new int[0];
- private final Object mLock = new Object();
-
- Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message m) {
- switch (m.what) {
- case MSG_START:
- mHandler.removeMessages(MSG_UPDATE);
- mHandler.sendEmptyMessage(MSG_UPDATE);
- break;
- case MSG_STOP:
- mHandler.removeMessages(MSG_UPDATE);
- break;
- case MSG_UPDATE:
- update();
- mHandler.removeMessages(MSG_UPDATE);
- mHandler.sendEmptyMessageDelayed(MSG_UPDATE, UPDATE_RATE);
- break;
- }
- }
- };
-
- ActivityManager mAm;
-
- public ProcessMemInfo getMemInfo(int pid) {
- return mData.get(pid);
- }
-
- public int[] getTrackedProcesses() {
- return mPidsArray;
- }
-
- public void startTrackingProcess(int pid, String name, long start) {
- synchronized (mLock) {
- final Long lpid = Long.valueOf(pid);
-
- if (mPids.contains(lpid)) return;
-
- mPids.add(lpid);
- updatePidsArrayL();
-
- mData.put(pid, new ProcessMemInfo(pid, name, start));
- }
- }
-
- void updatePidsArrayL() {
- final int N = mPids.size();
- mPidsArray = new int[N];
- StringBuffer sb = new StringBuffer("Now tracking processes: ");
- for (int i=0; i<N; i++) {
- final int p = mPids.get(i).intValue();
- mPidsArray[i] = p;
- sb.append(p); sb.append(" ");
- }
- Log.v(TAG, sb.toString());
- }
-
- void update() {
- synchronized (mLock) {
- Debug.MemoryInfo[] dinfos = mAm.getProcessMemoryInfo(mPidsArray);
- for (int i=0; i<dinfos.length; i++) {
- Debug.MemoryInfo dinfo = dinfos[i];
- if (i > mPids.size()) {
- Log.e(TAG, "update: unknown process info received: " + dinfo);
- break;
- }
- final long pid = mPids.get(i).intValue();
- final ProcessMemInfo info = mData.get(pid);
- info.head = (info.head+1) % info.pss.length;
- info.pss[info.head] = info.currentPss = dinfo.getTotalPss();
- info.uss[info.head] = info.currentUss = dinfo.getTotalPrivateDirty();
- if (info.currentPss > info.max) info.max = info.currentPss;
- if (info.currentUss > info.max) info.max = info.currentUss;
- // Log.v(TAG, "update: pid " + pid + " pss=" + info.currentPss + " uss=" + info.currentUss);
- if (info.currentPss == 0) {
- Log.v(TAG, "update: pid " + pid + " has pss=0, it probably died");
- mData.remove(pid);
- }
- }
- for (int i=mPids.size()-1; i>=0; i--) {
- final long pid = mPids.get(i).intValue();
- if (mData.get(pid) == null) {
- mPids.remove(i);
- updatePidsArrayL();
- }
- }
- }
- }
-
- @Override
- public void onCreate() {
- mAm = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
-
- // catch up in case we crashed but other processes are still running
- List<ActivityManager.RunningServiceInfo> svcs = mAm.getRunningServices(256);
- for (ActivityManager.RunningServiceInfo svc : svcs) {
- if (svc.service.getPackageName().equals(getPackageName())) {
- Log.v(TAG, "discovered running service: " + svc.process + " (" + svc.pid + ")");
- startTrackingProcess(svc.pid, svc.process,
- System.currentTimeMillis() - (SystemClock.elapsedRealtime() - svc.activeSince));
- }
- }
-
- List<ActivityManager.RunningAppProcessInfo> procs = mAm.getRunningAppProcesses();
- for (ActivityManager.RunningAppProcessInfo proc : procs) {
- final String pname = proc.processName;
- if (pname.startsWith(getPackageName())) {
- Log.v(TAG, "discovered other running process: " + pname + " (" + proc.pid + ")");
- startTrackingProcess(proc.pid, pname, System.currentTimeMillis());
- }
- }
- }
-
- @Override
- public void onDestroy() {
- mHandler.sendEmptyMessage(MSG_STOP);
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- Log.v(TAG, "Received start id " + startId + ": " + intent);
-
- if (intent != null) {
- if (TestingUtils.ACTION_START_TRACKING.equals(intent.getAction())) {
- final int pid = intent.getIntExtra("pid", -1);
- final String name = intent.getStringExtra("name");
- final long start = intent.getLongExtra("start", System.currentTimeMillis());
- startTrackingProcess(pid, name, start);
- }
- }
-
- mHandler.sendEmptyMessage(MSG_START);
-
- return START_STICKY;
- }
-
- public class MemoryTrackerInterface extends Binder {
- MemoryTracker getService() {
- return MemoryTracker.this;
- }
- }
-
- private final IBinder mBinder = new MemoryTrackerInterface();
-
- public IBinder onBind(Intent intent) {
- mHandler.sendEmptyMessage(MSG_START);
-
- return mBinder;
- }
-}
diff --git a/src/com/android/launcher3/testing/ToggleWeightWatcher.java b/src/com/android/launcher3/testing/ToggleWeightWatcher.java
deleted file mode 100644
index f0c3920..0000000
--- a/src/com/android/launcher3/testing/ToggleWeightWatcher.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.android.launcher3.testing;
-
-import android.app.Activity;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.view.View;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.util.TestingUtils;
-
-public class ToggleWeightWatcher extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- SharedPreferences sp = Utilities.getPrefs(this);
- boolean show = sp.getBoolean(TestingUtils.SHOW_WEIGHT_WATCHER, true);
-
- show = !show;
- sp.edit().putBoolean(TestingUtils.SHOW_WEIGHT_WATCHER, show).apply();
-
- Launcher launcher = (Launcher) LauncherAppState.getInstance(this).getModel().getCallback();
- if (launcher != null && launcher.mWeightWatcher != null) {
- launcher.mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
- }
- finish();
- }
-}
diff --git a/src/com/android/launcher3/testing/WeightWatcher.java b/src/com/android/launcher3/testing/WeightWatcher.java
deleted file mode 100644
index a26a2b6..0000000
--- a/src/com/android/launcher3/testing/WeightWatcher.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * Copyright (C) 2013 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.testing;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.launcher3.util.Thunk;
-
-public class WeightWatcher extends LinearLayout {
- private static final int RAM_GRAPH_RSS_COLOR = 0xFF990000;
- private static final int RAM_GRAPH_PSS_COLOR = 0xFF99CC00;
- private static final int TEXT_COLOR = 0xFFFFFFFF;
- private static final int BACKGROUND_COLOR = 0xc0000000;
-
- private static final int UPDATE_RATE = 5000;
-
- private static final int MSG_START = 1;
- private static final int MSG_STOP = 2;
- private static final int MSG_UPDATE = 3;
-
- static int indexOf(int[] a, int x) {
- for (int i=0; i<a.length; i++) {
- if (a[i] == x) return i;
- }
- return -1;
- }
-
- Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message m) {
- switch (m.what) {
- case MSG_START:
- mHandler.sendEmptyMessage(MSG_UPDATE);
- break;
- case MSG_STOP:
- mHandler.removeMessages(MSG_UPDATE);
- break;
- case MSG_UPDATE:
- int[] pids = mMemoryService.getTrackedProcesses();
-
- final int N = getChildCount();
- if (pids.length != N) initViews();
- else for (int i=0; i<N; i++) {
- ProcessWatcher pw = ((ProcessWatcher) getChildAt(i));
- if (indexOf(pids, pw.getPid()) < 0) {
- initViews();
- break;
- }
- pw.update();
- }
- mHandler.sendEmptyMessageDelayed(MSG_UPDATE, UPDATE_RATE);
- break;
- }
- }
- };
- @Thunk MemoryTracker mMemoryService;
-
- public WeightWatcher(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- ServiceConnection connection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
- mMemoryService = ((MemoryTracker.MemoryTrackerInterface)service).getService();
- initViews();
- }
-
- public void onServiceDisconnected(ComponentName className) {
- mMemoryService = null;
- }
- };
- context.bindService(new Intent(context, MemoryTracker.class),
- connection, Context.BIND_AUTO_CREATE);
-
- setOrientation(LinearLayout.VERTICAL);
-
- setBackgroundColor(BACKGROUND_COLOR);
- }
-
- public void initViews() {
- removeAllViews();
- int[] processes = mMemoryService.getTrackedProcesses();
- for (int i=0; i<processes.length; i++) {
- final ProcessWatcher v = new ProcessWatcher(getContext());
- v.setPid(processes[i]);
- addView(v);
- }
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
- mHandler.sendEmptyMessage(MSG_START);
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mHandler.sendEmptyMessage(MSG_STOP);
- }
-
- public class ProcessWatcher extends LinearLayout {
- GraphView mRamGraph;
- TextView mText;
- int mPid;
- @Thunk MemoryTracker.ProcessMemInfo mMemInfo;
-
- public ProcessWatcher(Context context) {
- this(context, null);
- }
-
- public ProcessWatcher(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- final float dp = getResources().getDisplayMetrics().density;
-
- mText = new TextView(getContext());
- mText.setTextColor(TEXT_COLOR);
- mText.setTextSize(TypedValue.COMPLEX_UNIT_PX, 10 * dp);
- mText.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
-
- final int p = (int)(2*dp);
- setPadding(p, 0, p, 0);
-
- mRamGraph = new GraphView(getContext());
-
- LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
- 0,
- (int)(14 * dp),
- 1f
- );
-
- addView(mText, params);
- params.leftMargin = (int)(4*dp);
- params.weight = 0f;
- params.width = (int)(200 * dp);
- addView(mRamGraph, params);
- }
-
- public void setPid(int pid) {
- mPid = pid;
- mMemInfo = mMemoryService.getMemInfo(mPid);
- if (mMemInfo == null) {
- Log.v("WeightWatcher", "Missing info for pid " + mPid + ", removing view: " + this);
- initViews();
- }
- }
-
- public int getPid() {
- return mPid;
- }
-
- public String getUptimeString() {
- long sec = mMemInfo.getUptime() / 1000;
- StringBuilder sb = new StringBuilder();
- long days = sec / 86400;
- if (days > 0) {
- sec -= days * 86400;
- sb.append(days);
- sb.append("d");
- }
-
- long hours = sec / 3600;
- if (hours > 0) {
- sec -= hours * 3600;
- sb.append(hours);
- sb.append("h");
- }
-
- long mins = sec / 60;
- if (mins > 0) {
- sec -= mins * 60;
- sb.append(mins);
- sb.append("m");
- }
-
- sb.append(sec);
- sb.append("s");
- return sb.toString();
- }
-
- public void update() {
- //Log.v("WeightWatcher.ProcessWatcher",
- // "MSG_UPDATE pss=" + mMemInfo.currentPss);
- mText.setText("(" + mPid
- + (mPid == android.os.Process.myPid()
- ? "/A" // app
- : "/S") // service
- + ") up " + getUptimeString()
- + " P=" + mMemInfo.currentPss
- + " U=" + mMemInfo.currentUss
- );
- mRamGraph.invalidate();
- }
-
- public class GraphView extends View {
- Paint pssPaint, ussPaint, headPaint;
-
- public GraphView(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- pssPaint = new Paint();
- pssPaint.setColor(RAM_GRAPH_PSS_COLOR);
- ussPaint = new Paint();
- ussPaint.setColor(RAM_GRAPH_RSS_COLOR);
- headPaint = new Paint();
- headPaint.setColor(Color.WHITE);
- }
-
- public GraphView(Context context) {
- this(context, null);
- }
-
- @Override
- public void onDraw(Canvas c) {
- int w = c.getWidth();
- int h = c.getHeight();
-
- if (mMemInfo == null) return;
-
- final int N = mMemInfo.pss.length;
- final float barStep = (float) w / N;
- final float barWidth = Math.max(1, barStep);
- final float scale = (float) h / mMemInfo.max;
-
- int i;
- float x;
- for (i=0; i<N; i++) {
- x = i * barStep;
- c.drawRect(x, h - scale * mMemInfo.pss[i], x + barWidth, h, pssPaint);
- c.drawRect(x, h - scale * mMemInfo.uss[i], x + barWidth, h, ussPaint);
- }
- x = mMemInfo.head * barStep;
- c.drawRect(x, 0, x + barWidth, h, headPaint);
- }
- }
- }
-}
diff --git a/src/com/android/launcher3/touch/SwipeDetector.java b/src/com/android/launcher3/touch/SwipeDetector.java
index be4648e..351f88d 100644
--- a/src/com/android/launcher3/touch/SwipeDetector.java
+++ b/src/com/android/launcher3/touch/SwipeDetector.java
@@ -16,6 +16,7 @@
package com.android.launcher3.touch;
import static android.view.MotionEvent.INVALID_POINTER_ID;
+
import android.content.Context;
import android.graphics.PointF;
import android.support.annotation.NonNull;
@@ -23,7 +24,6 @@
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
-import android.view.animation.Interpolator;
/**
* One dimensional scroll/drag/swipe gesture detector.
@@ -43,7 +43,6 @@
public static final int DIRECTION_BOTH = DIRECTION_NEGATIVE | DIRECTION_POSITIVE;
private static final float ANIMATION_DURATION = 1200;
- private static final float FAST_FLING_PX_MS = 10;
protected int mActivePointerId = INVALID_POINTER_ID;
@@ -351,22 +350,4 @@
}
return duration;
}
-
- public static class ScrollInterpolator implements Interpolator {
-
- boolean mSteeper;
-
- public void setVelocityAtZero(float velocity) {
- mSteeper = velocity > FAST_FLING_PX_MS;
- }
-
- public float getInterpolation(float t) {
- t -= 1.0f;
- float output = t * t * t;
- if (mSteeper) {
- output *= t * t; // Make interpolation initial slope steeper
- }
- return output + 1;
- }
- }
}
diff --git a/src/com/android/launcher3/util/FlingAnimation.java b/src/com/android/launcher3/util/FlingAnimation.java
index ec62764..fe0571b 100644
--- a/src/com/android/launcher3/util/FlingAnimation.java
+++ b/src/com/android/launcher3/util/FlingAnimation.java
@@ -1,6 +1,6 @@
package com.android.launcher3.util;
-import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_NEXT_FRAME;
+import static com.android.launcher3.LauncherState.NORMAL;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
@@ -97,7 +97,7 @@
Runnable onAnimationEndRunnable = new Runnable() {
@Override
public void run() {
- mLauncher.exitSpringLoadedDragMode(SPRING_LOADED_EXIT_NEXT_FRAME);
+ mLauncher.getStateManager().goToState(NORMAL);
mDropTarget.completeDrop(mDragObject);
}
};
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index 18787b6..daedaef 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -93,6 +93,19 @@
};
}
+ /**
+ * Returns a new matcher which returns the opposite boolean value of the provided
+ * {@param matcher}.
+ */
+ public static ItemInfoMatcher not(final ItemInfoMatcher matcher) {
+ return new ItemInfoMatcher() {
+ @Override
+ public boolean matches(ItemInfo info, ComponentName cn) {
+ return !matcher.matches(info, cn);
+ }
+ };
+ }
+
public static ItemInfoMatcher ofUser(final UserHandle user) {
return new ItemInfoMatcher() {
@Override
diff --git a/src/com/android/launcher3/util/TestingUtils.java b/src/com/android/launcher3/util/TestingUtils.java
deleted file mode 100644
index d927dc3..0000000
--- a/src/com/android/launcher3/util/TestingUtils.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package com.android.launcher3.util;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.view.Gravity;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-
-public class TestingUtils {
-
- public static final String MEMORY_TRACKER = "com.android.launcher3.testing.MemoryTracker";
- public static final String ACTION_START_TRACKING = "com.android.launcher3.action.START_TRACKING";
-
- public static final boolean MEMORY_DUMP_ENABLED = false;
- public static final String SHOW_WEIGHT_WATCHER = "debug.show_mem";
-
- public static void startTrackingMemory(Context context) {
- if (MEMORY_DUMP_ENABLED) {
- context.startService(new Intent()
- .setComponent(new ComponentName(context.getPackageName(), MEMORY_TRACKER))
- .setAction(ACTION_START_TRACKING)
- .putExtra("pid", android.os.Process.myPid())
- .putExtra("name", "L"));
- }
- }
-
- public static void addWeightWatcher(Launcher launcher) {
- if (MEMORY_DUMP_ENABLED) {
- boolean show = Utilities.getPrefs(launcher).getBoolean(SHOW_WEIGHT_WATCHER, true);
-
- int id = launcher.getResources().getIdentifier("zzz_weight_watcher", "layout",
- launcher.getPackageName());
- View watcher = launcher.getLayoutInflater().inflate(id, null);
- watcher.setAlpha(0.5f);
- ((FrameLayout) launcher.findViewById(R.id.launcher)).addView(watcher,
- new FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.MATCH_PARENT,
- FrameLayout.LayoutParams.WRAP_CONTENT,
- Gravity.BOTTOM)
- );
-
- watcher.setVisibility(show ? View.VISIBLE : View.GONE);
- launcher.mWeightWatcher = watcher;
- }
- }
-}
diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
index ec494f1..5c24687 100644
--- a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
+++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
@@ -10,11 +10,11 @@
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
-import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
+import com.android.launcher3.anim.Interpolators;
/**
* Utility class to handle wallpaper scrolling along with workspace.
@@ -199,7 +199,7 @@
public OffsetHandler(Context context) {
super(UiThreadHelper.getBackgroundLooper());
- mInterpolator = new DecelerateInterpolator(1.5f);
+ mInterpolator = Interpolators.DEACCEL_1_5;
mWM = WallpaperManager.getInstance(context);
}
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 8c9a441..8f20a8d 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -98,6 +98,7 @@
private String mPopupSectionName;
protected BaseRecyclerView mRv;
+ private RecyclerView.OnScrollListener mOnScrollListener;
private int mDownX;
private int mDownY;
@@ -141,7 +142,10 @@
public void setRecyclerView(BaseRecyclerView rv, TextView popupView) {
mRv = rv;
- mRv.addOnScrollListener(new RecyclerView.OnScrollListener() {
+ if (mOnScrollListener != null) {
+ mRv.removeOnScrollListener(mOnScrollListener);
+ }
+ mRv.addOnScrollListener(mOnScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
mDy = dy;
diff --git a/src/com/android/launcher3/views/SlidingTabStrip.java b/src/com/android/launcher3/views/SlidingTabStrip.java
new file mode 100644
index 0000000..45c6261
--- /dev/null
+++ b/src/com/android/launcher3/views/SlidingTabStrip.java
@@ -0,0 +1,107 @@
+/*
+ * 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.views;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
+
+public class SlidingTabStrip extends LinearLayout {
+
+ private final Paint mSelectedIndicatorPaint;
+ private int mSelectedIndicatorHeight;
+ private int mIndicatorLeft = -1;
+ private int mIndicatorRight = -1;
+ private int mSelectedPosition = -1;
+ private float mSelectionOffset;
+
+ public SlidingTabStrip(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ setOrientation(HORIZONTAL);
+ setWillNotDraw(false);
+ mSelectedIndicatorPaint = new Paint();
+ mSelectedIndicatorPaint.setColor(Themes.getAttrColor(context, android.R.attr.colorAccent));
+ mSelectedIndicatorHeight = getResources()
+ .getDimensionPixelSize(R.dimen.all_apps_tabs_indicator_height);
+ }
+
+ public void updateIndicatorPosition(int position, float positionOffset) {
+ mSelectedPosition = position;
+ mSelectionOffset = positionOffset;
+ updateIndicatorPosition();
+ }
+
+ public void updateTabTextColor(int pos) {
+ for (int i=0; i < getChildCount(); i++) {
+ Button tab = (Button) getChildAt(i);
+ tab.setSelected(i == pos);
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ updateTabTextColor(0);
+ updateIndicatorPosition(0, 0);
+ }
+
+ private void updateIndicatorPosition() {
+ final View tab = getChildAt(mSelectedPosition);
+ int left, right;
+
+ if (tab != null && tab.getWidth() > 0) {
+ left = tab.getLeft();
+ right = tab.getRight();
+
+ if (mSelectionOffset > 0f && mSelectedPosition < getChildCount() - 1) {
+ // Draw the selection partway between the tabs
+ View nextTitle = getChildAt(mSelectedPosition + 1);
+ left = (int) (mSelectionOffset * nextTitle.getLeft() +
+ (1.0f - mSelectionOffset) * left);
+ right = (int) (mSelectionOffset * nextTitle.getRight() +
+ (1.0f - mSelectionOffset) * right);
+ }
+ } else {
+ left = right = -1;
+ }
+
+ setIndicatorPosition(left, right);
+ }
+
+ private void setIndicatorPosition(int left, int right) {
+ if (left != mIndicatorLeft || right != mIndicatorRight) {
+ mIndicatorLeft = left;
+ mIndicatorRight = right;
+ invalidate();
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,
+ mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 61a7333..e328759 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.widget;
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import android.animation.Animator;
@@ -29,8 +30,7 @@
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
import android.widget.Toast;
import com.android.launcher3.AbstractFloatingView;
@@ -41,6 +41,7 @@
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.graphics.GradientView;
import com.android.launcher3.touch.SwipeDetector;
@@ -76,12 +77,12 @@
private Toast mWidgetInstructionToast;
protected final Launcher mLauncher;
- protected final SwipeDetector.ScrollInterpolator mScrollInterpolator;
protected final SwipeDetector mSwipeDetector;
protected final ObjectAnimator mOpenCloseAnimator;
protected View mContent;
protected GradientView mGradientView;
+ protected Interpolator mScrollInterpolator;
// range [0, 1], 0=> completely open, 1=> completely closed
protected float mTranslationShift = TRANSLATION_SHIFT_CLOSED;
@@ -92,7 +93,7 @@
super(context, attrs, defStyleAttr);
mLauncher = Launcher.getLauncher(context);
- mScrollInterpolator = new SwipeDetector.ScrollInterpolator();
+ mScrollInterpolator = Interpolators.SCROLL_CUBIC;
mSwipeDetector = new SwipeDetector(context, this, SwipeDetector.VERTICAL);
mOpenCloseAnimator = LauncherAnimUtils.ofPropertyValuesHolder(this);
@@ -206,7 +207,7 @@
@Override
public void onDragEnd(float velocity, boolean fling) {
if ((fling && velocity > 0) || mTranslationShift > 0.5f) {
- mScrollInterpolator.setVelocityAtZero(velocity);
+ mScrollInterpolator = scrollInterpolatorForVelocity(velocity);
mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration(
velocity, TRANSLATION_SHIFT_CLOSED - mTranslationShift));
close(true);
@@ -215,7 +216,7 @@
TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
mOpenCloseAnimator.setDuration(
SwipeDetector.calculateDuration(velocity, mTranslationShift))
- .setInterpolator(new DecelerateInterpolator());
+ .setInterpolator(Interpolators.DEACCEL);
mOpenCloseAnimator.start();
}
}
@@ -236,7 +237,7 @@
if (mSwipeDetector.isIdleState()) {
mOpenCloseAnimator
.setDuration(defaultDuration)
- .setInterpolator(new AccelerateInterpolator());
+ .setInterpolator(Interpolators.ACCEL);
} else {
mOpenCloseAnimator.setInterpolator(mScrollInterpolator);
}
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 3bb3fcb..7fa5ff0 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -24,8 +24,6 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
import android.widget.TextView;
import com.android.launcher3.Insettable;
@@ -33,6 +31,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.graphics.GradientView;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.util.PackageUserKey;
@@ -46,7 +45,6 @@
private static final int DEFAULT_CLOSE_DURATION = 200;
private ItemInfo mOriginalItemInfo;
- private Interpolator mFastOutSlowInInterpolator;
private Rect mInsets;
public WidgetsBottomSheet(Context context, AttributeSet attrs) {
@@ -56,8 +54,6 @@
public WidgetsBottomSheet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setWillNotDraw(false);
- mFastOutSlowInInterpolator =
- AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in);
mInsets = new Rect();
mGradientView = (GradientView) mLauncher.getLayoutInflater().inflate(
@@ -148,7 +144,7 @@
if (animate) {
mOpenCloseAnimator.setValues(
PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
- mOpenCloseAnimator.setInterpolator(mFastOutSlowInInterpolator);
+ mOpenCloseAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
mOpenCloseAnimator.start();
} else {
setTranslationShift(TRANSLATION_SHIFT_OPENED);
diff --git a/src_config/com/android/launcher3/BuildConfig.java b/src_config/com/android/launcher3/BuildConfig.java
deleted file mode 100644
index 4df75a1..0000000
--- a/src_config/com/android/launcher3/BuildConfig.java
+++ /dev/null
@@ -1,24 +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;
-
-/**
- * Config file used by Make. This file is automatically generated when using gradle.
- */
-public class BuildConfig {
- public static final String APPLICATION_ID = "com.android.launcher3";
-}
diff --git a/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewAccessibilityDelegate.java
similarity index 87%
rename from src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java
rename to src_ui_overrides/com/android/launcher3/uioverrides/OverviewAccessibilityDelegate.java
index 29dd95c..88a1e10 100644
--- a/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewAccessibilityDelegate.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.launcher3.accessibility;
+package com.android.launcher3.uioverrides;
import android.content.Context;
import android.os.Bundle;
@@ -24,6 +24,7 @@
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -54,17 +55,18 @@
@Override
public boolean performAccessibilityAction(View host, int action, Bundle args) {
Launcher launcher = Launcher.getLauncher(host.getContext());
+ OverviewPanel overviewPanel = launcher.findViewById(R.id.overview_panel);
if (action == OVERVIEW) {
- launcher.showOverviewMode(true);
+ launcher.getStateManager().goToState(LauncherState.OVERVIEW);
return true;
} else if (action == WALLPAPERS) {
launcher.onClickWallpaperPicker(host);
return true;
} else if (action == WIDGETS) {
- launcher.onClickAddWidgetButton(host);
+ overviewPanel.onClickAddWidgetButton();
return true;
} else if (action == SETTINGS) {
- launcher.onClickSettingsButton(host);
+ overviewPanel.onClickSettingsButton(host);
return true;
}
return super.performAccessibilityAction(host, action, args);
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java
new file mode 100644
index 0000000..3ce1014
--- /dev/null
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java
@@ -0,0 +1,198 @@
+/*
+ * 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.uioverrides;
+
+import static com.android.launcher3.WorkspaceStateTransitionAnimation.NO_ANIM_PROPERTY_SETTER;
+
+import android.animation.AnimatorSet;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.Toast;
+
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.WorkspaceStateTransitionAnimation.AnimatedPropertySetter;
+import com.android.launcher3.WorkspaceStateTransitionAnimation.PropertySetter;
+import com.android.launcher3.anim.AnimationLayerSet;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
+import com.android.launcher3.widget.WidgetsFullSheet;
+
+public class OverviewPanel extends LinearLayout implements Insettable, View.OnClickListener,
+ View.OnLongClickListener, LauncherStateManager.StateHandler {
+
+ // Out of 100, the percent of space the overview bar should try and take vertically.
+ private static final float OVERVIEW_ICON_ZONE_RATIO = 0.22f;
+
+ private final Launcher mLauncher;
+
+ public OverviewPanel(Context context) {
+ this(context, null);
+ }
+
+ public OverviewPanel(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public OverviewPanel(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mLauncher = Launcher.getLauncher(context);
+ setAlpha(0);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ int visibleChildCount = 3;
+ // Attach buttons.
+ attachListeners(findViewById(R.id.wallpaper_button));
+ attachListeners(findViewById(R.id.widget_button));
+
+ View settingsButton = findViewById(R.id.settings_button);
+ if (mLauncher.hasSettings()) {
+ attachListeners(settingsButton);
+ } else {
+ settingsButton.setVisibility(GONE);
+ visibleChildCount--;
+ }
+
+ // Init UI
+ Resources res = getResources();
+ int itemWidthPx =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
+ int spacerWidthPx =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
+
+ int totalItemWidth = visibleChildCount * itemWidthPx;
+ int maxWidth = totalItemWidth + (visibleChildCount - 1) * spacerWidthPx;
+
+ getLayoutParams().width = Math.min(mLauncher.getDeviceProfile().availableWidthPx, maxWidth);
+ getLayoutParams().height = getButtonBarHeight(mLauncher);
+ }
+
+ private void attachListeners(View view) {
+ view.setOnClickListener(this);
+ view.setOnLongClickListener(this);
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ ((FrameLayout.LayoutParams) getLayoutParams()).bottomMargin = insets.bottom;
+ }
+
+ @Override
+ public void onClick(View view) {
+ handleViewClick(view, Action.Touch.TAP);
+ }
+
+ @Override
+ public boolean onLongClick(View view) {
+ return handleViewClick(view, Action.Touch.LONGPRESS);
+ }
+
+ private boolean handleViewClick(View view, int action) {
+ if (mLauncher.getWorkspace().isSwitchingState()) {
+ return false;
+ }
+
+ final int controlType;
+ if (view.getId() == R.id.wallpaper_button) {
+ mLauncher.onClickWallpaperPicker(view);
+ controlType = ControlType.WALLPAPER_BUTTON;
+ } else if (view.getId() == R.id.widget_button) {
+ onClickAddWidgetButton();
+ controlType = ControlType.WIDGETS_BUTTON;
+ } else if (view.getId() == R.id.settings_button) {
+ onClickSettingsButton(view);
+ controlType = ControlType.SETTINGS_BUTTON;
+ } else {
+ return false;
+ }
+
+ mLauncher.getUserEventDispatcher().logActionOnControl(action, controlType);
+ return true;
+ }
+
+ /**
+ * Event handler for the (Add) Widgets button that appears after a long press
+ * on the home screen.
+ */
+ public void onClickAddWidgetButton() {
+ if (getContext().getPackageManager().isSafeMode()) {
+ Toast.makeText(mLauncher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
+ } else {
+ WidgetsFullSheet.show(mLauncher, true /* animated */);
+ }
+ }
+
+ /**
+ * Event handler for a click on the settings button that appears after a long press
+ * on the home screen.
+ */
+ public void onClickSettingsButton(View v) {
+ Intent intent = new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
+ .setPackage(getContext().getPackageName());
+ intent.setSourceBounds(mLauncher.getViewBounds(v));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ getContext().startActivity(intent, mLauncher.getActivityLaunchOptions(v));
+ }
+
+ @Override
+ public void setState(LauncherState state) {
+ setState(state, NO_ANIM_PROPERTY_SETTER);
+ }
+
+ @Override
+ public void setStateWithAnimation(LauncherState toState, AnimationLayerSet layerViews,
+ AnimatorSet anim, AnimationConfig config) {
+ setState(toState, new AnimatedPropertySetter(config.duration, layerViews, anim));
+ }
+
+ private void setState(LauncherState state, PropertySetter setter) {
+ boolean isOverview = state == LauncherState.OVERVIEW;
+ float finalHotseatAlpha = isOverview ? 0 : 1;
+
+ setter.setViewAlpha(null, this, isOverview ? 1 : 0);
+ setter.setViewAlpha(
+ mLauncher.getWorkspace().createHotseatAlphaAnimator(finalHotseatAlpha),
+ mLauncher.getHotseat(), finalHotseatAlpha);
+ }
+
+ public static int getButtonBarHeight(Launcher launcher) {
+ int zoneHeight = (int) (OVERVIEW_ICON_ZONE_RATIO *
+ launcher.getDeviceProfile().availableHeightPx);
+ Resources res = launcher.getResources();
+ int overviewModeMinIconZoneHeightPx =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
+ int overviewModeMaxIconZoneHeightPx =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
+ return Utilities.boundToRange(zoneHeight,
+ overviewModeMinIconZoneHeightPx,
+ overviewModeMaxIconZoneHeightPx);
+ }
+}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
new file mode 100644
index 0000000..dcf7453
--- /dev/null
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
@@ -0,0 +1,83 @@
+/*
+ * 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.uioverrides;
+
+import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
+import static com.android.launcher3.Utilities.isAccessibilityEnabled;
+
+import android.graphics.Rect;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+
+/**
+ * Definition for overview state
+ */
+public class OverviewState extends LauncherState {
+
+ // The percent to shrink the workspace during overview mode
+ private static final float SCALE_FACTOR = 0.7f;
+
+ private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_MULTI_PAGE;
+
+ public OverviewState(int id) {
+ super(id, ContainerType.WORKSPACE, OVERVIEW_TRANSITION_MS, 1f, STATE_FLAGS);
+ }
+
+ @Override
+ public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
+ DeviceProfile grid = launcher.getDeviceProfile();
+ Workspace ws = launcher.getWorkspace();
+ Rect insets = launcher.getDragLayer().getInsets();
+
+ int overviewButtonBarHeight = OverviewPanel.getButtonBarHeight(launcher);
+ int scaledHeight = (int) (SCALE_FACTOR * ws.getNormalChildHeight());
+ Rect workspacePadding = grid.getWorkspacePadding(null);
+ int workspaceTop = insets.top + workspacePadding.top;
+ int workspaceBottom = ws.getViewportHeight() - insets.bottom - workspacePadding.bottom;
+ int overviewTop = insets.top;
+ int overviewBottom = ws.getViewportHeight() - insets.bottom - overviewButtonBarHeight;
+ int workspaceOffsetTopEdge =
+ workspaceTop + ((workspaceBottom - workspaceTop) - scaledHeight) / 2;
+ int overviewOffsetTopEdge = overviewTop + (overviewBottom - overviewTop - scaledHeight) / 2;
+ return new float[] {SCALE_FACTOR, -workspaceOffsetTopEdge + overviewOffsetTopEdge };
+ }
+
+ @Override
+ public void onStateEnabled(Launcher launcher) {
+ launcher.getWorkspace().setPageRearrangeEnabled(true);
+
+ if (isAccessibilityEnabled(launcher)) {
+ launcher.getOverviewPanel().getChildAt(0).performAccessibilityAction(
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+ }
+ }
+
+ @Override
+ public void onStateDisabled(Launcher launcher) {
+ launcher.getWorkspace().setPageRearrangeEnabled(false);
+ }
+
+ @Override
+ public View getFinalFocus(Launcher launcher) {
+ return launcher.getOverviewPanel();
+ }
+}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/PinchToOverviewListener.java b/src_ui_overrides/com/android/launcher3/uioverrides/PinchToOverviewListener.java
new file mode 100644
index 0000000..40bf057
--- /dev/null
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/PinchToOverviewListener.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2016 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;
+
+import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.Utilities.isAccessibilityEnabled;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.ScaleGestureDetector.OnScaleGestureListener;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.util.TouchController;
+
+/**
+ * Detects pinches and animates the Workspace to/from overview mode.
+ */
+public class PinchToOverviewListener extends AnimatorListenerAdapter
+ implements TouchController, OnScaleGestureListener {
+
+ private static final float ACCEPT_THRESHOLD = 0.65f;
+ /**
+ * The velocity threshold at which a pinch will be completed instead of canceled,
+ * even if the first threshold has not been passed. Measured in scale / millisecond
+ */
+ private static final float FLING_VELOCITY = 0.001f;
+
+ private final ScaleGestureDetector mPinchDetector;
+ private Launcher mLauncher;
+ private Workspace mWorkspace = null;
+ private boolean mPinchStarted = false;
+
+ private AnimatorPlaybackController mCurrentAnimation;
+ private float mCurrentScale;
+ private boolean mShouldGoToFinalState;
+
+ private LauncherState mToState;
+
+ public PinchToOverviewListener(Launcher launcher) {
+ mLauncher = launcher;
+ mPinchDetector = new ScaleGestureDetector(mLauncher, this);
+ }
+
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ mPinchDetector.onTouchEvent(ev);
+ return mPinchStarted;
+ }
+
+ public boolean onControllerTouchEvent(MotionEvent ev) {
+ return mPinchDetector.onTouchEvent(ev);
+ }
+
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ if (isAccessibilityEnabled(mLauncher)) {
+ return false;
+ }
+ if (!mLauncher.isInState(NORMAL) && !mLauncher.isInState(OVERVIEW)) {
+ // Don't listen for the pinch gesture if on all apps, widget picker, -1, etc.
+ return false;
+ }
+ if (mCurrentAnimation != null) {
+ // Don't listen for the pinch gesture if we are already animating from a previous one.
+ return false;
+ }
+ if (mLauncher.isWorkspaceLocked()) {
+ // Don't listen for the pinch gesture if the workspace isn't ready.
+ return false;
+ }
+ if (mWorkspace == null) {
+ mWorkspace = mLauncher.getWorkspace();
+ }
+ if (mWorkspace.isSwitchingState()) {
+ // Don't listen for the pinch gesture while switching state, as it will cause a jump
+ // once the state switching animation is complete.
+ return false;
+ }
+ if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
+ // Don't listen for the pinch gesture if a floating view is open.
+ return false;
+ }
+
+ if (mLauncher.getDragController().isDragging()) {
+ mLauncher.getDragController().cancelDrag();
+ }
+
+ mToState = mLauncher.isInState(OVERVIEW) ? NORMAL : OVERVIEW;
+ mCurrentAnimation = mLauncher.getStateManager()
+ .createAnimationToNewWorkspace(mToState, OVERVIEW_TRANSITION_MS);
+ mCurrentAnimation.getTarget().addListener(this);
+ mPinchStarted = true;
+ mCurrentScale = 1;
+ mShouldGoToFinalState = false;
+
+ mCurrentAnimation.dispatchOnStart();
+ return true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCurrentAnimation = null;
+ mPinchStarted = false;
+ }
+
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ if (mShouldGoToFinalState) {
+ mCurrentAnimation.start();
+ } else {
+ mCurrentAnimation.setEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mLauncher.getStateManager().goToState(
+ mToState == OVERVIEW ? NORMAL : OVERVIEW, false);
+ }
+ });
+ mCurrentAnimation.reverse();
+ }
+ }
+
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ mCurrentScale = detector.getScaleFactor() * mCurrentScale;
+
+ // If we are zooming out, inverse the mCurrentScale so that animationFraction = [0, 1]
+ // 0 => Animation complete
+ // 1=> Animation started
+ float animationFraction = mToState == OVERVIEW ? mCurrentScale : (1 / mCurrentScale);
+
+ float velocity = (1 - detector.getScaleFactor()) / detector.getTimeDelta();
+ if (Math.abs(velocity) >= FLING_VELOCITY) {
+ LauncherState toState = velocity > 0 ? OVERVIEW : NORMAL;
+ mShouldGoToFinalState = toState == mToState;
+ } else {
+ mShouldGoToFinalState = animationFraction <= ACCEPT_THRESHOLD;
+ }
+
+ // Move the transition animation to that duration.
+ mCurrentAnimation.setPlayFraction(1 - animationFraction);
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
new file mode 100644
index 0000000..51cf661
--- /dev/null
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -0,0 +1,48 @@
+/*
+ * 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.uioverrides;
+
+import static com.android.launcher3.LauncherState.OVERVIEW;
+
+import android.view.View.AccessibilityDelegate;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.VerticalSwipeController;
+import com.android.launcher3.util.TouchController;
+
+public class UiFactory {
+
+ public static TouchController[] createTouchControllers(Launcher launcher) {
+ return new TouchController[] {
+ new VerticalSwipeController(launcher), new PinchToOverviewListener(launcher)};
+ }
+
+ public static AccessibilityDelegate newPageIndicatorAccessibilityDelegate() {
+ return new OverviewAccessibilityDelegate();
+ }
+
+ public static StateHandler[] getStateHandler(Launcher launcher) {
+ return new StateHandler[] {
+ (OverviewPanel) launcher.getOverviewPanel(),
+ launcher.getAllAppsController(), launcher.getWorkspace() };
+ }
+
+ public static void onWorkspaceLongPress(Launcher launcher) {
+ launcher.getStateManager().goToState(OVERVIEW);
+ }
+}
diff --git a/tests/src/com/android/launcher3/logging/FileLogTest.java b/tests/src/com/android/launcher3/logging/FileLogTest.java
index 7048c28..9c7cb8f 100644
--- a/tests/src/com/android/launcher3/logging/FileLogTest.java
+++ b/tests/src/com/android/launcher3/logging/FileLogTest.java
@@ -1,41 +1,51 @@
package com.android.launcher3.logging;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Calendar;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
/**
* Tests for {@link FileLog}
*/
@SmallTest
-public class FileLogTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class FileLogTest {
private File mTempDir;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() throws Exception {
int count = 0;
do {
- mTempDir = new File(getContext().getCacheDir(), "log-test-" + (count++));
+ mTempDir = new File(InstrumentationRegistry.getTargetContext().getCacheDir(),
+ "log-test-" + (count++));
} while(!mTempDir.mkdir());
FileLog.setDir(mTempDir);
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
// Clear existing logs
new File(mTempDir, "log-0").delete();
new File(mTempDir, "log-1").delete();
mTempDir.delete();
- super.tearDown();
}
+ @Test
public void testPrintLog() throws Exception {
if (!FileLog.ENABLED) {
return;
@@ -56,6 +66,7 @@
assertTrue(writer.toString().contains("hoolalala"));
}
+ @Test
public void testOldFileTruncated() throws Exception {
if (!FileLog.ENABLED) {
return;
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
index a486ceb..401711d 100644
--- a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
+++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
@@ -7,6 +7,7 @@
import android.content.Intent;
import android.graphics.Rect;
import android.net.Uri;
+import android.support.test.runner.AndroidJUnit4;
import android.util.Pair;
import com.android.launcher3.ItemInfo;
@@ -15,21 +16,25 @@
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.LongArrayMap;
-import com.android.launcher3.util.Provider;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
import java.util.List;
-import static org.mockito.Matchers.isNull;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
/**
* Tests for {@link AddWorkspaceItemsTask}
*/
+@RunWith(AndroidJUnit4.class)
public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
private final ComponentName mComponent1 = new ComponentName("a", "b");
@@ -39,9 +44,8 @@
private ArrayList<Long> newScreens;
private LongArrayMap<GridOccupancy> screenOccupancy;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void initData() throws Exception {
existingScreens = new ArrayList<>();
screenOccupancy = new LongArrayMap<>();
newScreens = new ArrayList<>();
@@ -62,6 +66,7 @@
};
}
+ @Test
public void testFindSpaceForItem_prefers_second() {
// First screen has only one hole of size 1
int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
@@ -83,13 +88,12 @@
.isRegionVacant(spaceFound.second[0], spaceFound.second[1], 2, 3));
}
+ @Test
public void testFindSpaceForItem_adds_new_screen() throws Exception {
// First screen has 2 holes of sizes 3x2 and 2x3
setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
commitScreensToDb();
- when(appState.getContext()).thenReturn(getMockContext());
-
ArrayList<Long> oldScreens = new ArrayList<>(existingScreens);
Pair<Long, int[]> spaceFound = newTask()
.findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 3, 3);
@@ -97,6 +101,7 @@
assertTrue(newScreens.contains(spaceFound.first));
}
+ @Test
public void testAddItem_existing_item_ignored() throws Exception {
ShortcutInfo info = new ShortcutInfo();
info.intent = new Intent().setComponent(mComponent1);
@@ -105,12 +110,11 @@
setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
commitScreensToDb();
- when(appState.getContext()).thenReturn(getMockContext());
-
// Nothing was added
assertTrue(executeTaskForTest(newTask(info)).isEmpty());
}
+ @Test
public void testAddItem_some_items_added() throws Exception {
ShortcutInfo info = new ShortcutInfo();
info.intent = new Intent().setComponent(mComponent1);
@@ -122,8 +126,6 @@
setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
commitScreensToDb();
- when(appState.getContext()).thenReturn(getMockContext());
-
executeTaskForTest(newTask(info, info2)).get(0).run();
ArgumentCaptor<ArrayList> notAnimated = ArgumentCaptor.forClass(ArrayList.class);
ArgumentCaptor<ArrayList> animated = ArgumentCaptor.forClass(ArrayList.class);
@@ -168,7 +170,7 @@
}
private void commitScreensToDb() throws Exception {
- LauncherSettings.Settings.call(getMockContentResolver(),
+ LauncherSettings.Settings.call(mProviderRule.getResolver(),
LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
@@ -183,6 +185,6 @@
v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
}
- getMockContentResolver().applyBatch(LauncherProvider.AUTHORITY, ops);
+ mProviderRule.getResolver().applyBatch(LauncherProvider.AUTHORITY, ops);
}
}
diff --git a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
index 3d03507..bbb6772 100644
--- a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
+++ b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -1,7 +1,9 @@
package com.android.launcher3.model;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
+import android.content.ContextWrapper;
import android.content.Intent;
import android.content.pm.LauncherActivityInfo;
import android.content.res.Resources;
@@ -11,7 +13,8 @@
import android.os.UserHandle;
import android.support.annotation.NonNull;
import android.support.test.InstrumentationRegistry;
-import android.test.ProviderTestCase2;
+import android.support.test.rule.provider.ProviderTestRule;
+import android.support.test.runner.AndroidJUnit4;
import com.android.launcher3.AllAppsList;
import com.android.launcher3.AppFilter;
@@ -28,6 +31,8 @@
import com.android.launcher3.util.Provider;
import com.android.launcher3.util.TestLauncherProvider;
+import org.junit.Before;
+import org.junit.Rule;
import org.mockito.ArgumentCaptor;
import java.io.BufferedReader;
@@ -46,7 +51,12 @@
/**
* Base class for writing tests for Model update tasks.
*/
-public class BaseModelUpdateTaskTestCase extends ProviderTestCase2<TestLauncherProvider> {
+public class BaseModelUpdateTaskTestCase {
+
+ @Rule
+ public ProviderTestRule mProviderRule =
+ new ProviderTestRule.Builder(TestLauncherProvider.class, LauncherProvider.AUTHORITY)
+ .build();
public final HashMap<Class, HashMap<String, Field>> fieldCache = new HashMap<>();
@@ -63,14 +73,8 @@
public AllAppsList allAppsList;
public Callbacks callbacks;
- public BaseModelUpdateTaskTestCase() {
- super(TestLauncherProvider.class, LauncherProvider.AUTHORITY);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
+ @Before
+ public void setUp() throws Exception {
callbacks = mock(Callbacks.class);
appState = mock(LauncherAppState.class);
model = mock(LauncherModel.class);
@@ -83,7 +87,12 @@
myUser = Process.myUserHandle();
bgDataModel = new BgDataModel();
- targetContext = InstrumentationRegistry.getTargetContext();
+ targetContext = new ContextWrapper(InstrumentationRegistry.getTargetContext()) {
+ @Override
+ public ContentResolver getContentResolver() {
+ return mProviderRule.getResolver();
+ }
+ };
idp = new InvariantDeviceProfile();
iconCache = new MyIconCache(targetContext, idp);
@@ -91,6 +100,8 @@
when(appState.getIconCache()).thenReturn(iconCache);
when(appState.getInvariantDeviceProfile()).thenReturn(idp);
+ when(appState.getContext()).thenReturn(targetContext);
+
}
/**
diff --git a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
index d595e6c..ac9d319 100644
--- a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
+++ b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -1,23 +1,34 @@
package com.android.launcher3.model;
+import android.support.test.runner.AndroidJUnit4;
+
import com.android.launcher3.AppInfo;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.ShortcutInfo;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.util.Arrays;
import java.util.HashSet;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+
/**
* Tests for {@link CacheDataUpdatedTask}
*/
+@RunWith(AndroidJUnit4.class)
public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase {
private static final String NEW_LABEL_PREFIX = "new-label-";
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
+ @Before
+ public void initData() throws Exception {
initializeData("cache_data_updated_task_data");
// Add dummy entries in the cache to simulate update
for (ItemInfo info : bgDataModel.itemsIdMap) {
@@ -29,6 +40,7 @@
return new CacheDataUpdatedTask(op, myUser, new HashSet<>(Arrays.asList(pkg)));
}
+ @Test
public void testCacheUpdate_update_apps() throws Exception {
// Clear all icons from apps list so that its easy to check what was updated
for (AppInfo info : allAppsList.data) {
@@ -52,6 +64,7 @@
}
}
+ @Test
public void testSessionUpdate_ignores_normal_apps() throws Exception {
executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app1"));
@@ -59,6 +72,7 @@
verifyUpdate();
}
+ @Test
public void testSessionUpdate_updates_pending_apps() throws Exception {
executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app3"));
diff --git a/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java b/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
index fd62d36..b92f612 100644
--- a/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
+++ b/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
@@ -1,11 +1,16 @@
package com.android.launcher3.model;
+import android.content.ContentResolver;
import android.content.ContentValues;
+import android.content.Context;
+import android.content.ContextWrapper;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Point;
-import android.test.ProviderTestCase2;
-import android.test.suitebuilder.annotation.MediumTest;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.provider.ProviderTestRule;
+import android.support.test.runner.AndroidJUnit4;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherModel;
@@ -15,15 +20,29 @@
import com.android.launcher3.model.GridSizeMigrationTask.MultiStepMigrationTask;
import com.android.launcher3.util.TestLauncherProvider;
+import org.junit.Before;
+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;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
/**
* Unit tests for {@link GridSizeMigrationTask}
*/
@MediumTest
-public class GridSizeMigrationTaskTest extends ProviderTestCase2<TestLauncherProvider> {
+@RunWith(AndroidJUnit4.class)
+public class GridSizeMigrationTaskTest {
+
+ @Rule
+ public ProviderTestRule mProviderRule =
+ 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;
@@ -37,20 +56,25 @@
private HashSet<String> mValidPackages;
private InvariantDeviceProfile mIdp;
+ private Context mContext;
- public GridSizeMigrationTaskTest() {
- super(TestLauncherProvider.class, LauncherProvider.AUTHORITY);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() throws Exception {
mValidPackages = new HashSet<>();
mValidPackages.add(TEST_PACKAGE);
mIdp = new InvariantDeviceProfile();
+
+ mContext = new ContextWrapper(InstrumentationRegistry.getTargetContext()) {
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mProviderRule.getResolver();
+ }
+ };
}
+ @Test
public void testHotseatMigration_apps_dropped() throws Exception {
long[] hotseatItems = {
addItem(APPLICATION, 0, HOTSEAT, 0, 0),
@@ -61,7 +85,7 @@
};
mIdp.numHotseatIcons = 3;
- new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages, 5, 3)
+ new GridSizeMigrationTask(mContext, mIdp, mValidPackages, 5, 3)
.migrateHotseat();
if (FeatureFlags.NO_ALL_APPS_ICON) {
// First item is dropped as it has the least weight.
@@ -72,6 +96,7 @@
}
}
+ @Test
public void testHotseatMigration_shortcuts_dropped() throws Exception {
long[] hotseatItems = {
addItem(APPLICATION, 0, HOTSEAT, 0, 0),
@@ -82,7 +107,7 @@
};
mIdp.numHotseatIcons = 3;
- new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages, 5, 3)
+ new GridSizeMigrationTask(mContext, mIdp, mValidPackages, 5, 3)
.migrateHotseat();
if (FeatureFlags.NO_ALL_APPS_ICON) {
// First item is dropped as it has the least weight.
@@ -98,7 +123,7 @@
int total = 0;
for (long id : sortedIds) {
- Cursor c = getMockContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+ Cursor c = mProviderRule.getResolver().query(LauncherSettings.Favorites.CONTENT_URI,
new String[]{LauncherSettings.Favorites._ID},
"container=-101 and screen=" + screenId, null, null, null);
@@ -116,13 +141,14 @@
}
// Verify that not other entry exist in the DB.
- Cursor c = getMockContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+ Cursor c = mProviderRule.getResolver().query(LauncherSettings.Favorites.CONTENT_URI,
new String[]{LauncherSettings.Favorites._ID},
"container=-101", null, null, null);
assertEquals(total, c.getCount());
c.close();
}
+ @Test
public void testWorkspace_empty_row_column_removed() throws Exception {
long[][][] ids = createGrid(new int[][][]{{
{ 0, 0, -1, 1},
@@ -131,7 +157,7 @@
{ 5, 2, -1, 6},
}});
- new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
+ new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Column 2 and row 2 got removed.
@@ -142,6 +168,7 @@
}});
}
+ @Test
public void testWorkspace_new_screen_created() throws Exception {
long[][][] ids = createGrid(new int[][][]{{
{ 0, 0, 0, 1},
@@ -150,7 +177,7 @@
{ 5, 2, -1, 6},
}});
- new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
+ new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Items in the second column get moved to new screen
@@ -163,6 +190,7 @@
}});
}
+ @Test
public void testWorkspace_items_merged_in_next_screen() throws Exception {
long[][][] ids = createGrid(new int[][][]{{
{ 0, 0, 0, 1},
@@ -174,7 +202,7 @@
{ 3, 1, -1, 4},
}});
- new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
+ new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Items in the second column of the first screen should get placed on the 3rd
@@ -190,6 +218,7 @@
}});
}
+ @Test
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)
@@ -205,7 +234,7 @@
{ 5, 2, -1, 6},
}});
- new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
+ new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
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.
@@ -222,6 +251,7 @@
}});
}
+ @Test
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.
@@ -232,7 +262,7 @@
{ 5, 2, 7, -1},
}}, 0);
- new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
+ new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
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.
@@ -246,6 +276,7 @@
}});
}
+ @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[][][]{{
@@ -255,7 +286,7 @@
{ 5, 6, 7, -1},
}}, 0);
- new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
+ new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
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.
@@ -281,7 +312,7 @@
* @return the same grid representation where each entry is the corresponding item id.
*/
private long[][][] createGrid(int[][][] typeArray, long startScreen) throws Exception {
- LauncherSettings.Settings.call(getMockContentResolver(),
+ LauncherSettings.Settings.call(mProviderRule.getResolver(),
LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
long[][][] ids = new long[typeArray.length][][];
@@ -290,13 +321,13 @@
long screenId = startScreen + i;
// Keep the screen id counter up to date
- LauncherSettings.Settings.call(getMockContentResolver(),
+ LauncherSettings.Settings.call(mProviderRule.getResolver(),
LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
ContentValues v = new ContentValues();
v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
- getMockContentResolver().insert(LauncherSettings.WorkspaceScreens.CONTENT_URI, v);
+ mProviderRule.getResolver().insert(LauncherSettings.WorkspaceScreens.CONTENT_URI, v);
ids[i] = new long[typeArray[i].length][];
for (int y = 0; y < typeArray[i].length; y++) {
@@ -320,7 +351,7 @@
* represent the workspace grid.
*/
private void verifyWorkspace(long[][][] ids) {
- ArrayList<Long> allScreens = LauncherModel.loadWorkspaceScreensDb(getMockContext());
+ ArrayList<Long> allScreens = LauncherModel.loadWorkspaceScreensDb(mContext);
assertEquals(ids.length, allScreens.size());
int total = 0;
@@ -330,7 +361,8 @@
for (int x = 0; x < ids[i][y].length; x++) {
long id = ids[i][y][x];
- Cursor c = getMockContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+ Cursor c = mProviderRule.getResolver().query(
+ LauncherSettings.Favorites.CONTENT_URI,
new String[]{LauncherSettings.Favorites._ID},
"container=-100 and screen=" + screenId +
" and cellX=" + x + " and cellY=" + y, null, null, null);
@@ -349,7 +381,7 @@
}
// Verify that not other entry exist in the DB.
- Cursor c = getMockContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+ Cursor c = mProviderRule.getResolver().query(LauncherSettings.Favorites.CONTENT_URI,
new String[]{LauncherSettings.Favorites._ID},
"container=-100", null, null, null);
assertEquals(total, c.getCount());
@@ -362,7 +394,7 @@
* 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(getMockContentResolver(),
+ long id = LauncherSettings.Settings.call(mProviderRule.getResolver(),
LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
.getLong(LauncherSettings.Settings.EXTRA_VALUE);
@@ -387,16 +419,18 @@
}
}
- getMockContentResolver().insert(LauncherSettings.Favorites.CONTENT_URI, values);
+ mProviderRule.getResolver().insert(LauncherSettings.Favorites.CONTENT_URI, values);
return id;
}
+ @Test
public void testMultiStepMigration_small_to_large() throws Exception {
MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier();
verifier.migrate(new Point(3, 3), new Point(5, 5));
verifier.assertCompleted();
}
+ @Test
public void testMultiStepMigration_large_to_small() throws Exception {
MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier(
5, 5, 4, 4,
@@ -406,6 +440,7 @@
verifier.assertCompleted();
}
+ @Test
public void testMultiStepMigration_zig_zag() throws Exception {
MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier(
5, 7, 4, 7,
diff --git a/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
index ed893c4..0a741c4 100644
--- a/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
+++ b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
@@ -1,22 +1,30 @@
package com.android.launcher3.model;
+import android.support.test.runner.AndroidJUnit4;
+
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.util.Arrays;
import java.util.HashSet;
+import static org.junit.Assert.assertEquals;
+
/**
* Tests for {@link PackageInstallStateChangedTask}
*/
+@RunWith(AndroidJUnit4.class)
public class PackageInstallStateChangedTaskTest extends BaseModelUpdateTaskTestCase {
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void initData() throws Exception {
initializeData("package_install_state_change_task_data");
}
@@ -26,6 +34,7 @@
return new PackageInstallStateChangedTask(installInfo);
}
+ @Test
public void testSessionUpdate_ignore_installed() throws Exception {
executeTaskForTest(newTask("app1", 30));
@@ -33,12 +42,14 @@
verifyProgressUpdate(0);
}
+ @Test
public void testSessionUpdate_shortcuts_updated() throws Exception {
executeTaskForTest(newTask("app3", 30));
verifyProgressUpdate(30, 5L, 6L, 7L);
}
+ @Test
public void testSessionUpdate_widgets_updated() throws Exception {
executeTaskForTest(newTask("app4", 30));
diff --git a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index 5858e13..5d417b5 100644
--- a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -3,23 +3,32 @@
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
import com.android.launcher3.LauncherProvider.DatabaseHelper;
import com.android.launcher3.LauncherSettings.Favorites;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+
/**
* Tests for {@link RestoreDbTask}
*/
@MediumTest
-public class RestoreDbTaskTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class RestoreDbTaskTest {
+ @Test
public void testGetProfileId() throws Exception {
SQLiteDatabase db = new MyDatabaseHelper(23).getWritableDatabase();
assertEquals(23, new RestoreDbTask().getDefaultProfileId(db));
}
+ @Test
public void testMigrateProfileId() throws Exception {
SQLiteDatabase db = new MyDatabaseHelper(42).getWritableDatabase();
// Add some dummy data
@@ -57,7 +66,7 @@
private final long mProfileId;
MyDatabaseHelper(long profileId) {
- super(getContext(), null, null);
+ super(InstrumentationRegistry.getContext(), null, null);
mProfileId = profileId;
}
@@ -66,6 +75,9 @@
return mProfileId;
}
+ @Override
+ protected void handleOneTimeDataUpgrade(SQLiteDatabase db) { }
+
protected void onEmptyDbCreated() { }
}
}
diff --git a/tests/src/com/android/launcher3/util/FocusLogicTest.java b/tests/src/com/android/launcher3/util/FocusLogicTest.java
index 79aed80..691d9bc 100644
--- a/tests/src/com/android/launcher3/util/FocusLogicTest.java
+++ b/tests/src/com/android/launcher3/util/FocusLogicTest.java
@@ -16,30 +16,24 @@
package com.android.launcher3.util;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
import android.view.KeyEvent;
-import android.view.View;
-import com.android.launcher3.util.FocusLogic;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
/**
* Tests the {@link FocusLogic} class that handles key event based focus handling.
*/
@SmallTest
-public final class FocusLogicTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public final class FocusLogicTest {
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- // Nothing to set up as this class only tests static methods.
- }
-
- @Override
- protected void tearDown() throws Exception {
- // Nothing to tear down as this class only tests static methods.
- }
-
+ @Test
public void testShouldConsume() {
assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_LEFT));
assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_RIGHT));
@@ -51,12 +45,14 @@
assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_DOWN));
}
+ @Test
public void testCreateSparseMatrix() {
// Either, 1) create a helper method to generate/instantiate all possible cell layout that
// may get created in real world to test this method. OR 2) Move all the matrix
// management routine to celllayout and write tests for them.
}
+ @Test
public void testMoveFromBottomRightToBottomLeft() {
int[][] map = transpose(new int[][] {
{-1, 0, -1, -1, -1, -1},
@@ -69,6 +65,7 @@
assertEquals(1, i);
}
+ @Test
public void testMoveFromBottomRightToTopLeft() {
int[][] map = transpose(new int[][] {
{-1, 0, -1, -1, -1, -1},
@@ -81,6 +78,7 @@
assertEquals(FocusLogic.NEXT_PAGE_FIRST_ITEM, i);
}
+ @Test
public void testMoveIntoHotseatWithEqualHotseatAndWorkspaceColumns() {
// Test going from an icon right above the All Apps button to the All Apps button.
int[][] map = transpose(new int[][] {
@@ -105,6 +103,7 @@
assertEquals(4, i);
}
+ @Test
public void testMoveIntoHotseatWithExtraColumnForAllApps() {
// Test going from an icon above and to the left
// of the All Apps button to the All Apps button.
@@ -187,6 +186,7 @@
assertEquals(12, i);
}
+ @Test
public void testCrossingAllAppsColumn() {
// Test crossing from left to right in portrait.
int[][] map = transpose(new int[][] {
diff --git a/tests/src/com/android/launcher3/util/GridOccupancyTest.java b/tests/src/com/android/launcher3/util/GridOccupancyTest.java
index 7d0fe71..fdd8e88 100644
--- a/tests/src/com/android/launcher3/util/GridOccupancyTest.java
+++ b/tests/src/com/android/launcher3/util/GridOccupancyTest.java
@@ -1,15 +1,23 @@
package com.android.launcher3.util;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
-import junit.framework.TestCase;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link GridOccupancy}
*/
@SmallTest
-public class GridOccupancyTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+public class GridOccupancyTest {
+ @Test
public void testFindVacantCell() {
GridOccupancy grid = initGrid(4,
1, 1, 1, 0, 0,
@@ -30,6 +38,7 @@
assertFalse(grid.findVacantCell(vacant, 3, 3));
}
+ @Test
public void testIsRegionVacant() {
GridOccupancy grid = initGrid(4,
1, 1, 1, 0, 0,