diff --git a/Android.mk b/Android.mk index d41e184f0a..0dc7ff1113 100644 --- a/Android.mk +++ b/Android.mk @@ -16,6 +16,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. # @@ -26,12 +34,11 @@ LOCAL_MODULE_TAGS := optional LOCAL_STATIC_JAVA_LIBRARIES := \ android-support-v4 \ android-support-v7-recyclerview \ - android-support-v7-palette \ android-support-dynamic-animation 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) @@ -72,12 +79,11 @@ LOCAL_MODULE_TAGS := optional LOCAL_STATIC_JAVA_LIBRARIES := \ android-support-v4 \ android-support-v7-recyclerview \ - android-support-v7-palette \ android-support-dynamic-animation 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) @@ -114,22 +120,57 @@ LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.* 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 \ + +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 $(BUILD_HOST_JAVA_LIBRARY) # ================================================== include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml index ad404c09a9..bb03f50763 100644 --- a/AndroidManifest-common.xml +++ b/AndroidManifest-common.xml @@ -43,6 +43,7 @@ + - - - - - diff --git a/build.gradle b/build.gradle index 886ccace68..61c05e5625 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ buildscript { 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' } } @@ -13,12 +13,12 @@ apply plugin: 'com.android.application' apply plugin: 'com.google.protobuf' android { - compileSdkVersion 26 - buildToolsVersion '26.0.0' + compileSdkVersion 27 + buildToolsVersion '27.0.0' defaultConfig { minSdkVersion 21 - targetSdkVersion 26 + targetSdkVersion 27 versionCode 1 versionName "1.0" @@ -40,7 +40,20 @@ android { 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 @@ android { } } + 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" } } } @@ -86,19 +102,21 @@ repositories { jcenter() } -final String SUPPORT_LIBS_VERSION = '26.0.0-SNAPSHOT' +final String SUPPORT_LIBS_VERSION = '27.0.0-SNAPSHOT' dependencies { compile "com.android.support:support-v4:${SUPPORT_LIBS_VERSION}" compile "com.android.support:support-dynamic-animation:${SUPPORT_LIBS_VERSION}" compile "com.android.support:recyclerview-v7:${SUPPORT_LIBS_VERSION}" - compile "com.android.support:palette-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/go/res/values-az-rAZ/strings.xml b/go/res/values-az-rAZ/strings.xml deleted file mode 100644 index c4b8cb7801..0000000000 --- a/go/res/values-az-rAZ/strings.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - "Qısayolu seçmək üçün toxunub saxlayın." - "Qısayolu seçmək üçün iki dəfə basıb saxlayın və ya fərdi əməliyyatlardan istifadə edin." - "Qısayollar" - "%1$s qısayolları" - diff --git a/go/res/values-bs-rBA/strings.xml b/go/res/values-bs-rBA/strings.xml deleted file mode 100644 index 7042468b47..0000000000 --- a/go/res/values-bs-rBA/strings.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - "Dodirnite i držite da uzmete prečicu." - "Dvaput dodirnite i držite da uzmete prečicu ili koristite prilagođene akcije." - "Prečice" - "Prečice aplikacije %1$s" - diff --git a/go/res/values-hi/strings.xml b/go/res/values-hi/strings.xml index bc057760fb..2c1650a0dd 100644 --- a/go/res/values-hi/strings.xml +++ b/go/res/values-hi/strings.xml @@ -20,7 +20,7 @@ "शॉर्टकट चुनने के लिए छूकर रखें." - "शॉर्टकट चुनने के लिए डबल टैप करके रखें या कस्टम कार्रवाइयों का उपयोग करें." + "शॉर्टकट चुनने के लिए दो बार छूएं और कुछ देर दबाएं रखें या अपने मुताबिक कार्रवाइयों का इस्तेमाल करें." "शॉर्टकट" "%1$s शॉर्टकट" diff --git a/go/res/values-ka-rGE/strings.xml b/go/res/values-ka-rGE/strings.xml deleted file mode 100644 index 1b46534788..0000000000 --- a/go/res/values-ka-rGE/strings.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - "შეეხეთ და დააყოვნეთ მალსახმობის ასარჩევად." - "ორმაგად შეეხეთ და გეჭიროთ მალსახმობის ასარჩევად ან მორგებული მოქმედებების გამოსაყენებლად." - "მალსახმობები" - "%1$s-ის მალსახმობები" - diff --git a/go/res/values-ky-rKG/strings.xml b/go/res/values-ky-rKG/strings.xml deleted file mode 100644 index 4c7e973cef..0000000000 --- a/go/res/values-ky-rKG/strings.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - "Кыска жолду тандоо үчүн басып туруңуз." - "Кыска жолду тандоо үчүн эки жолу таптап, кармап туруңуз же ыңгайлаштырылган аракеттерди колдонуңуз." - "Кыска жолдор" - "%1$s кыска жол" - diff --git a/go/res/values-lo-rLA/strings.xml b/go/res/values-lo-rLA/strings.xml deleted file mode 100644 index 7864884acc..0000000000 --- a/go/res/values-lo-rLA/strings.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - "ແຕະຄ້າງໄວ້ເພື່ອຮັບປຸ່ມລັດ." - "ແຕະສອງເທື່ອຄ້າງໄວ້ເພື່ອຮັບປຸ່ມລັດ ຫຼື ໃຊ້ຄຳສັ່ງແບບກຳນົດເອງ." - "ປຸ່ມລັດ" - "ປຸ່ມລັດ %1$s" - diff --git a/go/res/values-pa/strings.xml b/go/res/values-pa/strings.xml index f3982ab71b..c7e4abf3a8 100644 --- a/go/res/values-pa/strings.xml +++ b/go/res/values-pa/strings.xml @@ -20,7 +20,7 @@ "ਕੋਈ ਸ਼ਾਰਟਕੱਟ ਚੁਣਨ ਲਈ ਸਪੱਰਸ਼ ਕਰੋ ਅਤੇ ਦਬਾ ਕੇ ਰੱਖੋ।" - "ਕੋਈ ਸ਼ਾਰਟਕੱਟ ਚੁਣਨ ਲਈ ਡਬਲ-ਟੈਪ ਕਰੋ ਅਤੇ ਦਬਾ ਕੇ ਰੱਖੋ ਜਾਂ ਵਿਸ਼ੇਸ਼-ਵਿਉਂਤਬੱਧ ਕਾਰਵਾਈਆਂ ਵਰਤੋ।" + "ਕੋਈ ਸ਼ਾਰਟਕੱਟ ਚੁਣਨ ਲਈ ਦੋ ਵਾਰ ਟੈਪ ਕਰੋ ਅਤੇ ਦਬਾ ਕੇ ਰੱਖੋ ਜਾਂ ਵਿਉਂਂਤੀ ਕਾਰਵਾਈਆਂ ਵਰਤੋ।" "ਸ਼ਾਰਟਕੱਟ" "%1$s ਸ਼ਾਰਟਕੱਟ" diff --git a/go/res/values-si-rLK/strings.xml b/go/res/values-si-rLK/strings.xml deleted file mode 100644 index 4b25c90b0b..0000000000 --- a/go/res/values-si-rLK/strings.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - "කෙටි මගක් තෝරා ගැනීමට ස්පර්ශ කර අල්ලාගෙන සිටින්න." - "විජට් එකක් තෝරා ගැනීමට හෝ අභිරුචි භාවිත කිරීමට දෙවරක් තට්ටු කර අල්ලා ගෙන සිටින්න." - "කෙටි මං" - "කෙටි මං %1$s" - diff --git a/go/res/values-sw/strings.xml b/go/res/values-sw/strings.xml index 0379ed012a..13c12e4a6c 100644 --- a/go/res/values-sw/strings.xml +++ b/go/res/values-sw/strings.xml @@ -20,7 +20,7 @@ "Gusa na ushikilie ili uchague njia ya mkato." - "Gonga mara mbili na ushikilie ile uchague njia ya mkato au utumie vitendo maalum." + "Gusa mara mbili na ushikilie ile uchague njia ya mkato au utumie vitendo maalum." "Njia za mkato" "Njia za mkato za %1$s" diff --git a/go/res/values-uz-rUZ/strings.xml b/go/res/values-uz-rUZ/strings.xml deleted file mode 100644 index 318bc15722..0000000000 --- a/go/res/values-uz-rUZ/strings.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - "Yorliqni tanlab olish uchun bosib turing." - "Ikki marta bosib va bosib turgan holatda yorliqni tanlang yoki maxsus amaldan foydalaning." - "Yorliqlar" - "%1$s ilovasi yorliqlari" - diff --git a/proguard.flags b/proguard.flags index 51abccae99..a315cdce27 100644 --- a/proguard.flags +++ b/proguard.flags @@ -86,13 +86,15 @@ public (...); } +# The support library contains references to newer platform versions. +# Don't warn about those in case this app is linking against an older +# platform version. We know about them, and they are safe. +-dontwarn android.support.** + # Proguard will strip methods required for talkback to properly scroll to # next row when focus is on the last item of last row when using a RecyclerView # Keep optimized and shrunk proguard to prevent issues like this when using # support jar. -#-keep,allowoptimization,allowshrinking class android.support.** { -# *; -#} -keep class android.support.v7.widget.RecyclerView { *; } -keep interface com.android.launcher3.userevent.nano.LauncherLogProto.** { diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto index 0bbec188db..de74fcec5f 100644 --- a/protos/launcher_log.proto +++ b/protos/launcher_log.proto @@ -132,7 +132,9 @@ message Action { // 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 0000000000..08c740c4f0 --- /dev/null +++ b/quickstep/AndroidManifest.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar new file mode 100644 index 0000000000..f9ce6e0c9e Binary files /dev/null and b/quickstep/libs/sysui_shared.jar differ diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml new file mode 100644 index 0000000000..466470f3b4 --- /dev/null +++ b/quickstep/res/layout/overview_panel.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml new file mode 100644 index 0000000000..ef612268f6 --- /dev/null +++ b/quickstep/res/values/strings.xml @@ -0,0 +1,23 @@ + + + + + + Quickstep + \ 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 0000000000..51a8a5e837 --- /dev/null +++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java @@ -0,0 +1,66 @@ +/* + * 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; +import com.android.quickstep.RecentsView; + +/** + * Definition for overview state + */ +public class OverviewState extends LauncherState { + + // The percent to shrink the workspace during overview mode + public static final float SCALE_FACTOR = 0.7f; + + private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_MULTI_PAGE | FLAG_HIDE_HOTSEAT; + + 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[] {SCALE_FACTOR, 0}; + } + + @Override + public void onStateEnabled(Launcher launcher) { + ((RecentsView) launcher.getOverviewPanel()).setViewVisible(true); + } + + @Override + public void onStateDisabled(Launcher launcher) { + ((RecentsView) launcher.getOverviewPanel()).setViewVisible(false); + } + + @Override + public View getFinalFocus(Launcher launcher) { + return launcher.getOverviewPanel(); + } +} 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 0000000000..540b20041d --- /dev/null +++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java @@ -0,0 +1,34 @@ +/* + * 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.view.View.AccessibilityDelegate; + +import com.android.launcher3.Launcher; +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)}; + } + + public static AccessibilityDelegate newPageIndicatorAccessibilityDelegate() { + return null; + } +} diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java new file mode 100644 index 0000000000..8f759800a7 --- /dev/null +++ b/quickstep/src/com/android/quickstep/RecentsActivity.java @@ -0,0 +1,46 @@ +/* + * 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.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 mAdapter; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(this); + plan.preloadPlan(new RecentsTaskLoader(this, 1, 1, 0), -1, UserHandle.myUserId()); + + mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1); + mAdapter.addAll(plan.getTaskStack().getStackTasks()); + 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 0000000000..e474ecba12 --- /dev/null +++ b/quickstep/src/com/android/quickstep/RecentsView.java @@ -0,0 +1,41 @@ +/* + * 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.util.AttributeSet; +import android.widget.HorizontalScrollView; + +/** + * A placeholder view for recents + */ +public class RecentsView extends HorizontalScrollView { + 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); + setAlpha(0); + } + + public void setViewVisible(boolean isVisible) { } +} diff --git a/src_config/com/android/launcher3/BuildConfig.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java similarity index 66% rename from src_config/com/android/launcher3/BuildConfig.java rename to quickstep/src/com/android/quickstep/TouchInteractionService.java index 4df75a1a12..091ab54989 100644 --- a/src_config/com/android/launcher3/BuildConfig.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -13,12 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package com.android.quickstep; -package com.android.launcher3; +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; /** - * Config file used by Make. This file is automatically generated when using gradle. + * Service connected by system-UI for handling touch interaction. */ -public class BuildConfig { - public static final String APPLICATION_ID = "com.android.launcher3"; +public class TouchInteractionService extends Service { + + @Override + public IBinder onBind(Intent intent) { + return null; + } } diff --git a/res/drawable/all_apps_handle_landscape.xml b/res/drawable/all_apps_handle_landscape.xml new file mode 100644 index 0000000000..23826ab06d --- /dev/null +++ b/res/drawable/all_apps_handle_landscape.xml @@ -0,0 +1,37 @@ + + + + + + + + + + diff --git a/res/layout/zzz_dummy_widget.xml b/res/drawable/all_apps_handle_portrait.xml similarity index 50% rename from res/layout/zzz_dummy_widget.xml rename to res/drawable/all_apps_handle_portrait.xml index a0fa8fc3e4..75aa44853b 100644 --- a/res/layout/zzz_dummy_widget.xml +++ b/res/drawable/all_apps_handle_portrait.xml @@ -1,5 +1,5 @@ - - + - + - - - \ No newline at end of file + + \ No newline at end of file diff --git a/res/drawable/ic_install_no_shadow.xml b/res/drawable/ic_install_no_shadow.xml new file mode 100644 index 0000000000..ffce22aac1 --- /dev/null +++ b/res/drawable/ic_install_no_shadow.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml index ac440fc01e..4ea32b46f4 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" /> + + + + @@ -60,25 +73,12 @@ android:id="@+id/overview_panel" android:visibility="gone" /> - - - - diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml index c41a6e380d..f58a87ecd0 100644 --- a/res/layout-port/launcher.xml +++ b/res/layout-port/launcher.xml @@ -66,12 +66,6 @@ android:id="@+id/drop_target_bar" layout="@layout/drop_target_bar_horz" /> - - - - + android:clipChildren="true" + android:clipToPadding="false" + android:focusable="true" + android:focusableInTouchMode="true" + android:saveEnabled="false" > - + - - - + android:descendantFocusability="afterDescendants" + android:focusable="true" + android:overScrollMode="never" /> - - + + - - + - - - + android:layout_alignParentBottom="true" + android:background="?attr/allAppsNavBarScrimColor" /> \ No newline at end of file diff --git a/res/layout/app_widget_resize_frame.xml b/res/layout/app_widget_resize_frame.xml index 874fecccf3..12561b634d 100644 --- a/res/layout/app_widget_resize_frame.xml +++ b/res/layout/app_widget_resize_frame.xml @@ -21,42 +21,47 @@ android:background="@drawable/widget_resize_shadow" android:foreground="@drawable/widget_resize_frame" android:foregroundTint="?attr/workspaceTextColor" - android:padding="0dp" > + android:padding="0dp"> - - + - - + + - - + + - - + + + + + + \ No newline at end of file diff --git a/res/layout/gradient_bg.xml b/res/layout/gradient_bg.xml index db448d7818..6c6626c1d2 100644 --- a/res/layout/gradient_bg.xml +++ b/res/layout/gradient_bg.xml @@ -20,5 +20,4 @@ android:id="@+id/gradient_bg" android:layout_width="match_parent" android:layout_height="match_parent" - android:visibility="gone" launcher:layout_ignoreInsets="true" /> \ No newline at end of file diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml index d1ac56c505..c795b81fee 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. --> - - \ No newline at end of file + \ No newline at end of file diff --git a/res/layout/page_indicator.xml b/res/layout/page_indicator.xml index 92f52d672c..2df511b573 100644 --- a/res/layout/page_indicator.xml +++ b/res/layout/page_indicator.xml @@ -14,7 +14,7 @@ limitations under the License. --> - - + diff --git a/res/layout/widgets_view.xml b/res/layout/widgets_full_sheet.xml similarity index 56% rename from res/layout/widgets_view.xml rename to res/layout/widgets_full_sheet.xml index 4f3c7c8df0..153529968f 100644 --- a/res/layout/widgets_view.xml +++ b/res/layout/widgets_full_sheet.xml @@ -1,5 +1,5 @@ - - - + android:orientation="vertical" + android:theme="?attr/widgetsTheme" > - + android:layout_height="match_parent" /> + android:background="?android:attr/colorPrimary" + android:elevation="4dp"> + android:layout_height="match_parent" + android:clipToPadding="false" + /> - - + - - \ No newline at end of file + \ No newline at end of file diff --git a/res/layout/zzz_weight_watcher.xml b/res/layout/zzz_weight_watcher.xml deleted file mode 100644 index 07fd39e91f..0000000000 --- a/res/layout/zzz_weight_watcher.xml +++ /dev/null @@ -1,4 +0,0 @@ - - diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml index d71b4c78b3..5a776a6486 100644 --- a/res/values-af/strings.xml +++ b/res/values-af/strings.xml @@ -47,6 +47,7 @@ "Verwyder" "Deïnstalleer" "Programinligting" + "Installeer" "installeer kortpaaie" "Laat \'n program toe om kortpaaie by te voeg sonder gebruikerinmenging." "lees Tuis-instellings en -kortpaaie" diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml index 293b452f68..5225dbab57 100644 --- a/res/values-am/strings.xml +++ b/res/values-am/strings.xml @@ -47,6 +47,7 @@ "አስወግድ" "አራግፍ" "የመተግበሪያ መረጃ" + "ጫን" "አቋራጮችን ይጭናል" "መተግበሪያው ያለተጠቃሚ ጣልቃ ገብነት አቋራጭ እንዲያክል ያስችለዋል።" "የመነሻ ቅንብሮች እና አቋራጮችን ያነባል" diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml index 6447bf0150..5f2d58d125 100644 --- a/res/values-ar/strings.xml +++ b/res/values-ar/strings.xml @@ -47,6 +47,7 @@ "إزالة" "إلغاء التثبيت" "معلومات عن التطبيق" + "تثبيت" "تثبيت اختصارات" "للسماح لتطبيق ما بإضافة اختصارات بدون تدخل المستخدم." "قراءة إعدادات واختصارات الشاشة الرئيسية" diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml index 18f87bcf33..dddb6faeff 100644 --- a/res/values-az/strings.xml +++ b/res/values-az/strings.xml @@ -47,6 +47,7 @@ "Silin" "Sistemdən sil" "Tətbiq məlumatı" + "Quraşdırın" "qısayolları quraşdır" "Tətbiqə istifadəçi müdaxiləsi olmadan qısayolları əlavə etməyə icazə verir." "Əsas Səhifə ayarlarını və qısayolları oxuyun" diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml index 777512a966..4fa86d294a 100644 --- a/res/values-b+sr+Latn/strings.xml +++ b/res/values-b+sr+Latn/strings.xml @@ -47,6 +47,7 @@ "Ukloni" "Deinstaliraj" "Inform. o aplikaciji" + "Instaliraj" "instaliranje prečica" "Dozvoljava aplikaciji da dodaje prečice bez intervencije korisnika." "čitanje podešavanja i prečica na početnom ekranu" diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml index 0a31ddf7c1..1d7797d98d 100644 --- a/res/values-be/strings.xml +++ b/res/values-be/strings.xml @@ -47,6 +47,7 @@ "Выдаліць" "Выдаліць" "Звесткі пра праграмы" + "Усталяваць" "усталёўваць ярлыкі" "Дазваляе праграмам дадаваць ярлыкі без умяшання карыстальніка." "счытваць налады і ярлыкі на Галоўнай старонцы" diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml index 3e05e0be57..b429492d0b 100644 --- a/res/values-bg/strings.xml +++ b/res/values-bg/strings.xml @@ -47,6 +47,7 @@ "Премахване" "Деинсталиране" "Данни за прилож." + "Инсталиране" "инсталиране на преки пътища" "Разрешава на приложението да добавя преки пътища без намеса на потребителя." "четене на настройките и преките пътища в Начало" diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml index 471602f47d..897a8db0e3 100644 --- a/res/values-bn/strings.xml +++ b/res/values-bn/strings.xml @@ -35,10 +35,10 @@ "%2$d উচ্চতা অনুযায়ী %1$d প্রস্থ" "নিজে যোগ করতে টাচ করে ধরে রাখুন" "স্বয়ংক্রিয়ভাবে যোগ করুন" - "অ্যাপ অনুসন্ধান করুন" + "অ্যাপ খুঁজুন" "অ্যাপ লোড হচ্ছে…" "\"%1$s\" এর সাথে মেলে এমন কোনো অ্যাপ পাওয়া যায়নি" - "আরো অ্যাপ্লিকেশানের জন্য অনুসন্ধান করুন" + "আরও অ্যাপ্লিকেশানের জন্য খুঁজুন" "বিজ্ঞপ্তি" "এই হোম স্ক্রীনে আর কোনো জায়গা নেই৷" "পছন্দসই ট্রে-তে আর কোনো জায়গা নেই" @@ -46,7 +46,8 @@ "হোম" "সরান" "আনইনস্টল করুন" - "অ্যাপ্লিকেশানের তথ্য" + "অ্যাপের তথ্য" + "ইনস্টল করুন" "শর্টকাটগুলি ইনস্টল করে" "একটি অ্যাপ্লিকেশানকে ব্যবহারকারীর হস্তক্ষেপ ছাড়াই শর্টকাটগুলি যোগ করার অনুমতি দেয়৷" "হোম সেটিংস এবং শর্টকাটগুলি পড়ে" diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml index 49c862a8cd..b062b88a50 100644 --- a/res/values-bs/strings.xml +++ b/res/values-bs/strings.xml @@ -47,6 +47,7 @@ "Ukloni" "Deinstaliraj" "Informacije o aplikaciji" + "Instaliraj" "instaliraj prečice" "Dopušta aplikaciji dodavanje prečica bez posredovanja korisnika." "čitaj postavke na početnom ekranu i prečice" @@ -64,7 +65,7 @@ "Nova stranica početnog ekrana" "Folder je otvoren, (š) %1$d (v) %2$d" "Dodirnite da zatvorite folder" - "Dodirnite da sačuvate promjenu imena" + "Dodirnite da sačuvate promjenu naziva" "Folder je zatvoren" "Ime foldera je promijenjeno u %1$s" "Folder: %1$s" @@ -82,7 +83,7 @@ "Potreban je pristup obavještenjima" "Za prikaz tačaka obavještenja, uključite obavještenja za aplikacije za aplikaciju %1$s" "Promijeni postavke" - "Dodajte ikonu na početni ekran" + "Dodaj ikonu na početni ekran" "Za nove aplikacije" "Promjena oblika ikona" "Koristite sistemski zadano" diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml index b1a1f85e06..008531a97f 100644 --- a/res/values-ca/strings.xml +++ b/res/values-ca/strings.xml @@ -47,6 +47,7 @@ "Suprimeix" "Desinstal·la" "Dades de l\'aplicació" + "Instal·la" "instal·la dreceres" "Permet que una aplicació afegeixi dreceres sense la intervenció de l\'usuari." "llegeix la configuració i les dreceres de la pantalla d\'inici" diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml index e6dee3c3b3..7cd71c2777 100644 --- a/res/values-cs/strings.xml +++ b/res/values-cs/strings.xml @@ -46,7 +46,8 @@ "Plocha" "Odstranit" "Odinstalovat" - "Informace o aplikaci" + "O aplikaci" + "Nainstalovat" "instalace zástupce" "Umožňuje aplikaci přidat zástupce bez zásahu uživatele." "čtení nastavení a odkazů plochy" diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml index 653105e058..9a91bc49b4 100644 --- a/res/values-da/strings.xml +++ b/res/values-da/strings.xml @@ -46,7 +46,8 @@ "Startskærm" "Fjern" "Afinstaller" - "Oplysninger om appen" + "Appinfo" + "Installer" "installere genveje" "Tillader, at en app tilføjer genveje uden brugerens indgriben." "læs indstillinger og genveje for startskærmen" diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 1c4cd7a7ca..6bc08755fe 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -47,6 +47,7 @@ "Entfernen" "Deinstallieren" "App-Details" + "Installieren" "Verknüpfungen installieren" "Ermöglicht einer App das Hinzufügen von Verknüpfungen ohne Eingreifen des Nutzers" "Einstellungen und Verknüpfungen auf dem Startbildschirm lesen" diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml index 25c8866b85..cdb255d5d2 100644 --- a/res/values-el/strings.xml +++ b/res/values-el/strings.xml @@ -47,6 +47,7 @@ "Κατάργηση" "Απεγκατάσταση" "Πληροφορίες εφαρμογής" + "Εγκατάσταση" "εγκατάσταση συντομεύσεων" "Επιτρέπει σε μια εφαρμογή την προσθήκη συντομεύσεων χωρίς την παρέμβαση του χρήστη." "ανάγνωση ρυθμίσεων και συντομεύσεων αρχικής οθόνης" diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml index f3f10cc82a..afd6757964 100644 --- a/res/values-en-rAU/strings.xml +++ b/res/values-en-rAU/strings.xml @@ -47,6 +47,7 @@ "Remove" "Uninstall" "App info" + "Install" "install shortcuts" "Allows an app to add shortcuts without user intervention." "read Home settings and shortcuts" diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml index f3f10cc82a..afd6757964 100644 --- a/res/values-en-rGB/strings.xml +++ b/res/values-en-rGB/strings.xml @@ -47,6 +47,7 @@ "Remove" "Uninstall" "App info" + "Install" "install shortcuts" "Allows an app to add shortcuts without user intervention." "read Home settings and shortcuts" diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml index f3f10cc82a..afd6757964 100644 --- a/res/values-en-rIN/strings.xml +++ b/res/values-en-rIN/strings.xml @@ -47,6 +47,7 @@ "Remove" "Uninstall" "App info" + "Install" "install shortcuts" "Allows an app to add shortcuts without user intervention." "read Home settings and shortcuts" diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml index f60e1862ae..05f1e067f0 100644 --- a/res/values-es-rUS/strings.xml +++ b/res/values-es-rUS/strings.xml @@ -47,6 +47,7 @@ "Quitar" "Desinstalar" "Información de app" + "Instalar" "instalar accesos directos" "Permite que una aplicación agregue accesos directos sin que el usuario intervenga." "leer configuración y accesos directos de la pantalla principal" diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index 3ab41caf69..e7e1cf818c 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -47,6 +47,7 @@ "Quitar" "Desinstalar" "Datos de aplicación" + "Instalar" "instalar accesos directos" "Permite que una aplicación añada accesos directos sin intervención del usuario." "leer información de accesos directos y de ajustes de la pantalla de inicio" @@ -83,7 +84,7 @@ "Para mostrar burbujas de notificación, activa las notificaciones de %1$s" "Cambiar ajustes" "Añadir icono a la pantalla de inicio" - "Para nuevas aplicaciones" + "Para aplicaciones nuevas" "Cambiar forma de los iconos" "Usar opción predeterminada del sistema" "Cuadrado" diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml index 9d9991ddce..ccc85a3e55 100644 --- a/res/values-et/strings.xml +++ b/res/values-et/strings.xml @@ -47,6 +47,7 @@ "Eemalda" "Desinstalli" "Rakenduse teave" + "Installimine" "installi otseteed" "Võimaldab rakendusel lisada otseteid kasutaja sekkumiseta." "loe avaekraani seadeid ja otseteid" diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml index b6082fadab..68478dedf1 100644 --- a/res/values-eu/strings.xml +++ b/res/values-eu/strings.xml @@ -47,6 +47,7 @@ "Kendu" "Desinstalatu" "Aplikazioaren datuak" + "Instalatu" "Instalatu lasterbideak" "Erabiltzaileak ezer egin gabe lasterbideak gehitzea baimentzen die aplikazioei." "Irakurri hasierako ezarpenak eta lasterbideak" diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml index 8fe874b321..8051b85394 100644 --- a/res/values-fa/strings.xml +++ b/res/values-fa/strings.xml @@ -47,6 +47,7 @@ "برداشتن" "حذف نصب" "اطلاعات برنامه" + "نصب" "نصب میان‌برها" "به برنامه اجازه می‌دهد میان‌برها را بدون دخالت کاربر اضافه کند." "خواندن تنظیمات و میان‌برهای صفحه اصلی" diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml index 016dbbce37..5e05bbe17e 100644 --- a/res/values-fi/strings.xml +++ b/res/values-fi/strings.xml @@ -47,6 +47,7 @@ "Poista" "Poista asennus" "Sovelluksen tiedot" + "Asenna" "asenna pikakuvakkeita" "Antaa sovelluksen lisätä pikakuvakkeita itsenäisesti ilman käyttäjän valintaa." "lue aloitusruudun asetuksia ja pikakuvakkeita" diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml index e57433d0c3..da403a0e8a 100644 --- a/res/values-fr-rCA/strings.xml +++ b/res/values-fr-rCA/strings.xml @@ -24,7 +24,7 @@ "Travail" "L\'application n\'est pas installée." "Application indisponible" - "L\'application téléchargée est désactivée en mode sécurisé." + "L\'application téléchargée est désactivée en mode sans échec." "Widgets désactivés en mode sans échec" "Le raccourci n\'est pas disponible" "Écran d\'accueil" @@ -47,6 +47,7 @@ "Supprimer" "Désinstaller" "Détails de l\'appli" + "Installer" "installer des raccourcis" "Permet à une application d\'ajouter des raccourcis sans l\'intervention de l\'utilisateur." "lire les paramètres et les raccourcis de la page d\'accueil" diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index dde439288c..bc98cd25f6 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -47,6 +47,7 @@ "Supprimer" "Désinstaller" "Infos sur l\'appli" + "Installer" "installer des raccourcis" "Permettre à une application d\'ajouter des raccourcis sans l\'intervention de l\'utilisateur" "lire les paramètres et les raccourcis de l\'écran d\'accueil" diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml index d55514d1e9..54f96049cc 100644 --- a/res/values-gl/strings.xml +++ b/res/values-gl/strings.xml @@ -47,6 +47,7 @@ "Eliminar" "Desinstalar" "Info. da aplicación" + "Instalar" "instalar atallos" "Permite a unha aplicación engadir atallos sen intervención do usuario." "ler a configuración e os atallos da pantalla de inicio" diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml index 7096a3ee0a..973478146c 100644 --- a/res/values-gu/strings.xml +++ b/res/values-gu/strings.xml @@ -39,7 +39,7 @@ "ઍપ્લિકેશનો લોડ કરી રહ્યું છે…" "\"%1$s\"થી મેળ ખાતી કોઈ ઍપ્લિકેશનો મળી નથી" "વધુ ઍપ્લિકેશનો શોધો" - "સૂચનાઓ" + "નોટિફિકેશનો" "આ હોમ સ્ક્રીન પર વધુ જગ્યા નથી." "મનપસંદ ટ્રે પર વધુ જગ્યા નથી" "ઍપ્લિકેશનોની સૂચિ" @@ -47,6 +47,7 @@ "દૂર કરો" "અનઇન્સ્ટોલ કરો" "ઍપ્લિકેશન માહિતી" + "ઇન્સ્ટૉલ કરો" "શોર્ટકટ્સ ઇન્સ્ટોલ કરો" "એપ્લિકેશનને વપરાશકર્તા હસ્તક્ષેપ વગર શોર્ટકટ્સ ઉમેરવાની મંજૂરી આપે છે." "હોમ સેટિંગ્સ અને શોર્ટકટ્સ વાંચો" diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml index f1ee0f2366..238740471b 100644 --- a/res/values-hi/strings.xml +++ b/res/values-hi/strings.xml @@ -28,31 +28,32 @@ "विजेट सुरक्षित मोड में अक्षम हैं" "शॉर्टकट उपलब्ध नहीं है" "होम स्क्रीन" - "कस्टम कार्रवाई" - "विजेट को चुनने के लिए स्‍पर्श करके रखें." - "कोई विजेट चुनने के लिए डबल टैप करके रखें या कस्‍टम कार्रवाइयां चुनें." + "कस्टम कार्रवाइयां" + "विजेट को चुनने के लिए दबाकर रखें" + "कोई विजेट चुनने के लिए दो बार छूएं और दबाये रखें या अपने मुताबिक कार्रवाइयां चुनें." "%1$d × %2$d" "%1$d चौड़ाई गुणा %2$d ऊंचाई" - "मैन्युअल रूप से जोड़ने के लिए स्पर्श करके रखें" + "खुद जोड़ने के लिए दबाकर रखें" "अपने आप जोड़ें" - "ऐप्लिकेशन खोजें" + "ऐप सर्च करें" "ऐप्लिकेशन लोड हो रहे हैं…" "\"%1$s\" से मिलता-जुलता कोई ऐप्लिकेशन नहीं मिला" - "अधिक ऐप्लिकेशन खोजें" - "नोटिफ़िकेशन" - "इस होम स्‍क्रीन पर स्थान शेष नहीं है." - "पसंदीदा ट्रे में और स्थान नहीं है" + "और ऐप सर्च करें" + "सूचनाएं" + "इस होम स्‍क्रीन पर जगह नहीं बची है" + "पसंदीदा ट्रे में और जगह नहीं है" "ऐप्लिकेशन सूची" - "होम" + "होम पेज" "निकालें" "अनइंस्टॉल करें" "ऐप की जानकारी" + "इंस्‍टॉल करें" "शॉर्टकट इंस्‍टॉल करें" - "ऐप्लिकेशन को उपयोगकर्ता के हस्‍तक्षेप के बिना शॉर्टकट जोड़ने देती है." - "होम सेटिंग और शॉर्टकट पढ़ें" - "ऐप्लिकेशन को होम में सेटिंग और शॉर्टकट पढ़ने देती है." - "होम सेटिंग और शॉर्टकट लिखें" - "ऐप्लिकेशन को होम में सेटिंग और शॉर्टकट बदलने देती है." + "ऐप को उपयोगकर्ता के हस्‍तक्षेप के बिना शॉर्टकट जोड़ने देती है." + "होम पेज की सेटिंग और शॉर्टकट पढ़ें" + "ऐप्लिकेशन को होम पेज में सेटिंग और शॉर्टकट पढ़ने देती है." + "होम पेज की सेटिंग और शॉर्टकट लिखें" + "ऐप्लिकेशन को होम पेज में सेटिंग और शॉर्टकट बदलने देती है." "%1$s को फ़ोन कॉल करने की अनुमति नहीं है" "विजेट लोड करने में समस्‍या" "सेटअप" @@ -61,7 +62,7 @@ "%1$s अक्षम है" "पेज %2$d में से %1$d" "होम स्क्रीन %2$d में से %1$d" - "नया होम स्‍क्रीन पृष्‍ठ" + "नया होम स्‍क्रीन पेज" "फ़ोल्डर खोला गया, %1$d गुणा %2$d" "फ़ोल्डर बंद करने के लिए टैप करें" "नाम बदलना सहेजने के लिए टैप करें" @@ -70,32 +71,32 @@ "फ़ोल्डर: %1$s" "शॉर्टकट" "वॉलपेपर" - "होम सेटिंग" + "होम पेज की सेटिंग" "आपके व्यवस्थापक द्वारा अक्षम" "खास जानकारी" "होमस्क्रीन घुमाने की अनुमति दें" "फ़ोन घुुमाए जाने पर" - "वर्तमान प्रदर्शन सेटिंग घुमाने की अनुमति नहीं देती" - "नोटिफ़िकेशन बिंदु" + "इस डिसप्ले सेटिंग में रोटेशन (स्क्रीन पर सामग्री को घुमाकर देखने) की सुविधा काम नहीं करती" + "सूचना बिंदु" "चालू" "बंद" - "नोटिफ़िकेशन एक्‍सेस ज़रूरी है" - "नोटिफ़िकेशन बिंदु दिखाने के लिए, %1$s के ऐप्लिकेशन नोटिफ़िकेशन चालू करें" + "सूचना के एक्सेस की ज़रूरत है" + "सूचना बिंदु दिखाने के लिए, %1$s के ऐप्लिकेशन सूचना चालू करें" "सेटिंग बदलें" - "होम स्क्रीन में आइकन जोड़ें" - "नए ऐप्लिकेशन के लिए" - "आइकन का आकार बदलें" + "होम स्क्रीन में आइकॉन जोड़ें" + "नए ऐप के लिए" + "आइकॉन का आकार बदलें" "सिस्टम डिफ़ॉल्ट का उपयोग करें" "वर्ग" "गोल कोनों वाला वर्ग" "मंडली" "आंसू की बूंद" - "आइकन के आकार में बदलाव लागू किए जा रहे हैं" + "आइकॉन के आकार में बदलाव किए जा रहे हैं" "अज्ञात" "निकालें" - "खोजें" + "सर्च करें" "यह ऐप्स इंस्टॉल नहीं है" - "इस आइकन का ऐप्स इंस्टॉल नहीं है. आप उसे निकाल सकते हैं या ऐप्स की खोज करके उसे मैन्युअल रूप से इंस्टॉल कर सकते हैं." + "इस आइकॉन का ऐप इंस्टॉल नहीं है. आप उसे निकाल सकते हैं या ऐप को खोज कर उसे मैन्युअल रूप से इंस्टॉल कर सकते हैं." "%1$s डाउनलोड हो रहा है, %2$s पूर्ण" "%1$s के इंस्टॉल होने की प्रतीक्षा की जा रही है" "%1$s विजेट" @@ -125,7 +126,7 @@ "विजेट का आकार बदलकर उसकी चौड़ाई %1$s और ऊंचाई %2$s कर दी गई" "शॉर्टकट" "%2$s के लिए %1$d शॉर्टकट" - "%3$s के लिए %1$d शॉर्टकट और %2$d नोटिफ़िकेशन हैं" + "%3$s के लिए %1$d शॉर्टकट और %2$d सूचनाएं हैं" "खारिज करें" - "नोटिफ़िकेशन को खारिज किया गया" + "सूचना को खारिज किया गया" diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml index aaae258b80..f5330157c9 100644 --- a/res/values-hr/strings.xml +++ b/res/values-hr/strings.xml @@ -47,6 +47,7 @@ "Ukloni" "Deinstaliraj" "Info o aplikaciji" + "Instaliraj" "instaliranje prečaca" "Aplikaciji omogućuje dodavanje prečaca bez intervencije korisnika." "čitanje postavki početnog zaslona i prečaca" diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml index 2da64b06bc..e38da35dad 100644 --- a/res/values-hu/strings.xml +++ b/res/values-hu/strings.xml @@ -47,6 +47,7 @@ "Törlés" "Eltávolítás" "Alkalmazásinformáció" + "Telepítés" "parancsikonok telepítése" "Lehetővé teszi egy alkalmazás számára, hogy felhasználói beavatkozás nélkül adjon hozzá parancsikonokat." "Főoldal beállításainak és parancsikonjainak beolvasása" diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml index 2b55e966a4..4901dc5c20 100644 --- a/res/values-hy/strings.xml +++ b/res/values-hy/strings.xml @@ -47,6 +47,7 @@ "Հեռացնել" "Հեռացնել" "Հավելվածի տվյալներ" + "Տեղադրել" "տեղադրել դյուրանցումներ" "Ծրագրին թույլ է տալիս ավելացնել դյուրանցումներ՝ առանց օգտագործողի միջամտության:" "կարդալ հիմնաէջի կարգավորումներն ու դյուրանցումները" @@ -76,11 +77,11 @@ "Թույլ տալ հիմնական էկրանի պտտումը" "Հեռախոսը պտտելու դեպքում" "Ցուցադրման ընթացիկ կարգավորումներն արգելում են պտտումը" - "Ծանուցման կետեր" + "Ծանուցումների կետիկներ" "Միացված է" "Անջատված է" "Անհրաժեշտ է ծանուցման թույլտվություն" - "Ծանուցման կետերը ցուցադրելու համար միացրեք հավելվածի ծանուցումները %1$s-ի համար" + "Ծանուցումների կետիկները ցուցադրելու համար միացրեք ծանուցումները %1$s-ի համար" "Փոխել կարգավորումները" "Ավելացնել պատկերակը Հիմնական էկրանին" "Նոր հավելվածների համար" diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml index 379a960d05..5112223640 100644 --- a/res/values-in/strings.xml +++ b/res/values-in/strings.xml @@ -47,6 +47,7 @@ "Hapus" "Uninstal" "Info aplikasi" + "Instal" "memasang pintasan" "Mengizinkan aplikasi menambahkan pintasan tanpa campur tangan pengguna." "membaca setelan dan pintasan layar Utama" @@ -82,7 +83,7 @@ "Perlu akses notifikasi" "Guna menampilkan Titik Notifikasi, aktifkan notifikasi aplikasi untuk %1$s" "Ubah setelan" - "Tambahkan ikon ke layar Utama" + "Tambahkan ikon ke Layar utama" "Untuk aplikasi baru" "Ubah bentuk ikon" "Gunakan default sistem" diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml index cf178a56ad..e1b04e283e 100644 --- a/res/values-is/strings.xml +++ b/res/values-is/strings.xml @@ -47,6 +47,7 @@ "Fjarlægja" "Fjarlægja" "Forritsupplýsingar" + "Setja upp" "setja upp flýtileiðir" "Leyfir forriti að bæta við flýtileiðum án íhlutunar notanda." "lesa stillingar og flýtileiðir heimaskjás" diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index a52aee4295..a22a4ce9a0 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -47,6 +47,7 @@ "Rimuovi" "Disinstalla" "Informazioni app" + "Installa" "aggiunta di scorciatoie" "Consente a un\'app di aggiungere scorciatoie automaticamente." "lettura di impostazioni e scorciatoie in Home" diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml index 650a90db81..d7905b4db5 100644 --- a/res/values-iw/strings.xml +++ b/res/values-iw/strings.xml @@ -47,6 +47,7 @@ "הסר" "הסר התקנה" "פרטי אפליקציה" + "התקנה" "התקן קיצורי דרך" "מאפשר לאפליקציה להוסיף קיצורי דרך ללא התערבות המשתמש." "קרא הגדרות וקיצורי דרך של דף הבית" @@ -93,7 +94,7 @@ "משנה את הצורה של הסמלים" "לא ידוע" "הסר" - "חפש" + "חיפוש" "אפליקציה זו אינה מותקנת" "האפליקציה של סמל זה אינה מותקנת. ניתן להסיר אותו, או לחפש את האפליקציה ולהתקין אותה ידנית." "מוריד את %1$s, %2$s הושלמו" diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml index 4071a6cc99..4b6c3ef43d 100644 --- a/res/values-ja/strings.xml +++ b/res/values-ja/strings.xml @@ -47,6 +47,7 @@ "削除" "アンインストール" "アプリ情報" + "インストール" "ショートカットのインストール" "ユーザー操作なしでショートカットを追加することをアプリに許可します。" "ホームの設定とショートカットの読み取り" @@ -83,7 +84,7 @@ "通知ドットを表示するには、「%1$s」のアプリ通知を ON にしてください" "設定を変更" "ホーム画面にアイコンを追加" - "新しいアプリをダウンロードしたときに" + "新しいアプリをダウンロードしたとき" "アイコンの形の変更" "システムのデフォルトを使用" "スクエア" diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml index 7e8b46ce88..ec60583309 100644 --- a/res/values-ka/strings.xml +++ b/res/values-ka/strings.xml @@ -47,6 +47,7 @@ "ამოშლა" "დეინსტალაცია" "აპის შესახებ" + "ინსტალაცია" "მალსახმობების დაყენება" "აპისთვის მალსახმობების დამოუკიდებლად დამატების უფლების მიცემა." "მთავარი ეკრანის პარამეტრებისა და მალსახმობების წაკითხვა" diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml index 50ed9aa680..115b752933 100644 --- a/res/values-kk/strings.xml +++ b/res/values-kk/strings.xml @@ -47,6 +47,7 @@ "Жою" "Жою" "Қолданба ақпараты" + "Орнату" "төте пернелерді орнату" "Қолданбаға пайдаланушының қатысуынсыз төте пернелерді қосу мүмкіндігін береді." "Негізгі экрандағы параметрлер мен төте пернелерді оқу" diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml index 7028f7e3ae..9040811544 100644 --- a/res/values-km/strings.xml +++ b/res/values-km/strings.xml @@ -47,6 +47,7 @@ "យកចេញ" "លុបការដំឡើង" "ព័ត៌មាន​កម្មវិធី" + "ដំឡើង" "ដំឡើង​ផ្លូវកាត់" "អនុញ្ញាត​ឲ្យ​កម្មវិធី​បន្ថែម​ផ្លូវកាត់​ ដោយ​មិន​ចាំបាច់​​អំពើ​ពី​អ្នក​ប្រើ។" "អាន​ការ​កំណត់​ និង​ផ្លូវកាត់​​អេក្រង់​ដើម" diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml index 9a31649be3..4156a21e14 100644 --- a/res/values-kn/strings.xml +++ b/res/values-kn/strings.xml @@ -45,8 +45,9 @@ "ಅಪ್ಲಿಕೇಶನ್‌ಗಳ ಪಟ್ಟಿ" "ಮುಖಪುಟ" "ತೆಗೆದುಹಾಕಿ" - "ಅಸ್ಥಾಪಿಸು" + "ಅನ್‌ಇನ್‌ಸ್ಟಾಲ್" "ಅಪ್ಲಿಕೇಶನ್ ಮಾಹಿತಿ" + "ಸ್ಥಾಪಿಸಿ" "ಶಾರ್ಟ್‌ಕಟ್‌ಗಳನ್ನು ಸ್ಥಾಪಿಸಿ" "ಬಳಕೆದಾರರ ಹಸ್ತಕ್ಷೇಪವಿಲ್ಲದೆ ಶಾರ್ಟ್‌ಕಟ್‌ಗಳನ್ನು ಸೇರಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ." "ಮುಖಪುಟದ ಸೆಟ್ಟಿಂಗ್‌ಗಳು ಮತ್ತು ಶಾರ್ಟ್‌ಕಟ್‌ಗಳನ್ನು ಓದಿ" diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml index e3511d4413..d9d8da68f2 100644 --- a/res/values-ko/strings.xml +++ b/res/values-ko/strings.xml @@ -47,6 +47,7 @@ "삭제" "제거" "앱 정보" + "설치" "바로가기 설치" "앱이 사용자의 작업 없이 바로가기를 추가할 수 있도록 합니다." "홈 설정 및 바로가기 읽기" diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml index 85ba8be3c3..0808214bc9 100644 --- a/res/values-ky/strings.xml +++ b/res/values-ky/strings.xml @@ -47,6 +47,7 @@ "Алып салуу" "Чыгарып салуу" "Колдонмо тууралуу" + "Орнотуу" "тез чакырмаларды орнотуу" "Колдонмого колдонуучуга кайрылбастан тез чакырма кошууга уруксат берет." "Үйдүн тууралоолорун жана тез чакырмаларын окуу" diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml index 9a50c4b517..c1a1e71583 100644 --- a/res/values-lo/strings.xml +++ b/res/values-lo/strings.xml @@ -47,6 +47,7 @@ "ເອົາ​ອອກ" "ຖອນ​ການ​ຕິດ​ຕັ້ງ" "ຂໍ້ມູນແອັບ" + "ຕິດຕັ້ງ" "ຕິດຕັ້ງທາງລັດ" "ອະນຸຍາດໃຫ້ແອັບຯ ເພີ່ມທາງລັດໂດຍບໍ່ຕ້ອງຮັບການຢືນຢັນຈາກຜູ່ໃຊ້." "ອ່ານການຕັ້ງຄ່າໜ້າຫຼັກ ແລະທາງລັດ" diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml index d415e4df1b..ca45583db7 100644 --- a/res/values-lt/strings.xml +++ b/res/values-lt/strings.xml @@ -47,6 +47,7 @@ "Ištrinti" "Pašalinti" "Programos inform." + "Įdiegti" "įdiegti sparčiuosius klavišus" "Programai leidžiama pridėti sparčiuosius klavišus be naudotojo įsikišimo." "skaityti pagrindinio puslapio nustatymus ir sparčiuosius klavišus" diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml index 2d8bfd83a3..7a4903ab68 100644 --- a/res/values-lv/strings.xml +++ b/res/values-lv/strings.xml @@ -46,7 +46,8 @@ "Sākums" "Noņemt" "Atinstalēt" - "Lietotnes informācija" + "Par lietotni" + "Instalēt" "instalēt saīsnes" "Ļauj lietotnei pievienot saīsnes, nejautājot lietotājam." "lasīt sākuma ekrāna iestatījumus un saīsnes" diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml index 7d95a23a6c..0aaed6231b 100644 --- a/res/values-mk/strings.xml +++ b/res/values-mk/strings.xml @@ -47,6 +47,7 @@ "Отстрани" "Деинсталирај" "Инф. за апликација" + "Инсталирај" "инсталирај кратенки" "Овозможува апликацијата да додава кратенки без интервенција на корисникот." "чита поставки и кратенки на почетна страница" @@ -68,8 +69,8 @@ "Папката е затворена" "Папката е преименувана во %1$s" "Папка: %1$s" - "Додатоци" - "Позадини" + "Виџети" + "Тапети" "Поставки за Home" "Оневозможено од администраторот" "Краток преглед" @@ -82,7 +83,7 @@ "Потребен е пристап до известувањата" "За да се прикажуваат „Точки за известување“, вклучете ги известувањата за апликацијата %1$s" "Промени ги поставките" - "Додајте икона на почетниот екран" + "Додај икона на почетниот екран" "За нови апликации" "Променете ја формата на иконата" "Користи ја стандардната поставка на системот" diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml index 0cc7aa023c..299fc4592d 100644 --- a/res/values-ml/strings.xml +++ b/res/values-ml/strings.xml @@ -47,6 +47,7 @@ "നീക്കംചെയ്യുക" "അൺഇൻസ്റ്റാളുചെയ്യുക" "ആപ്പ് വിവരം" + "ഇൻസ്‌റ്റാൾ ചെയ്യുക" "കുറുക്കുവഴികൾ ഇൻസ്റ്റാളുചെയ്യുക" "ഉപയോക്തൃ ഇടപെടൽ ഇല്ലാതെ കുറുക്കുവഴികൾ ചേർക്കാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു." "ഹോം ക്രമീകരണങ്ങളും കുറുക്കുവഴികളും റീഡുചെയ്യുക" diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml index 22479cc3e4..8921c77935 100644 --- a/res/values-mn/strings.xml +++ b/res/values-mn/strings.xml @@ -47,6 +47,7 @@ "Арилгах" "Устгах" "Апп-н мэдээлэл" + "Суулгах" "товчлол суулгах" "Апп нь хэрэглэгчийн оролцоогүйгээр товчлолыг нэмэж чадна" "Нүүрний тохиргоо болон товчлолыг унших" diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml index 751ad227ab..ca9a4028d0 100644 --- a/res/values-mr/strings.xml +++ b/res/values-mr/strings.xml @@ -22,7 +22,7 @@ "Launcher3" "कार्य" - "अॅप स्थापित केलेला नाही." + "अॅप इंस्टॉल केलेला नाही." "अॅप उपलब्ध नाही" "डाउनलोड केलेला अ‍ॅप सुरक्षित मोड मध्‍ये अक्षम केला" "विजेट सुरक्षित मोडमध्ये अक्षम झाले" @@ -45,18 +45,19 @@ "अॅप्स सूची" "होम" "काढा" - "विस्थापित करा" + "अनइंस्टॉल करा" "अॅप माहिती" + "इंस्टॉल करा" "शॉर्टकट स्‍थापित करा" "वापरकर्ता हस्तक्षेपाशिवाय शॉर्टकट जोडण्यास अॅप ला अनुमती देते." - "होम सेटिग्ज आणि शॉर्टकट वाचा" + "होम सेटिंग्ज आणि शॉर्टकट वाचा" "मुख्यपृष्ठातील सेटिंग्ज आणि शॉर्टकट वाचण्यास अॅप ला अनुमती देते." - "होम स्क्रीन सेटिंग्ज आणि शॉर्टकट लिहा" + "होम सेटिंग्ज आणि शॉर्टकट लिहा" "मुख्यपृष्ठातील सेटिंग्ज आणि शॉर्टकट बदलण्यास अॅप ला अनुमती देते." "%1$s ला फोन कॉल करण्याची अनुमती नाही" "विजेट लोड करण्यात समस्या" "सेटअप" - "हा सिस्टम अॅप आहे आणि विस्थापित केला जाऊ शकत नाही." + "हा सिस्टम अॅप आहे आणि अनइंस्टॉल केला जाऊ शकत नाही." "अनामित फोल्डर" "%1$s अक्षम केला आहे" "%2$d पैकी %1$d पृष्ठ" @@ -75,7 +76,7 @@ "अवलोकन" "मुख्यस्क्रीन फिरविण्‍यास अनुमती द्या" "फोन फिरविला जातो तेव्हा" - "वर्तमान प्रदर्शन सेटिंग फिरविण्यास परवानगी देत नाही" + "वर्तमान डिस्प्ले सेटिंग रोटेशनला परवानगी देत नाही" "सूचना बिंदू" "चालू" "बंद" @@ -94,10 +95,10 @@ "अज्ञात" "काढा" "शोधा" - "हा अॅप स्थापित केलेला नाही" - "या चिन्हासाठी अॅप स्थापित केलेला नाही. आपण ते काढू शकता किंवा अॅपचा शोध घेऊ शकता आणि त्यास व्यक्तिचलितपणे स्थापित करू शकता." + "हा अॅप इंस्टॉल केलेला नाही" + "या चिन्हासाठी अॅप इंस्टॉल केलेला नाही. आपण ते काढू शकता किंवा अॅपचा शोध घेऊ शकता आणि त्यास व्यक्तिचलितपणे इंस्टॉल करू शकता." "%1$s डाउनलोड होत आहे , %2$s पूर्ण झाले" - "%1$s स्थापित करण्याची प्रतिक्षा करीत आहे" + "%1$s इंस्टॉल करण्याची प्रतिक्षा करीत आहे" "%1$s विजेट" "होम स्क्रीनवर जोडा" "आयटम येथे हलवा" diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml index f94b167a69..6aeac8b28e 100644 --- a/res/values-ms/strings.xml +++ b/res/values-ms/strings.xml @@ -47,6 +47,7 @@ "Alih keluar" "Nyahpasang" "Maklumat apl" + "Pasang" "pasang pintasan" "Membenarkan apl menambah pintasan tanpa campur tangan pengguna." "baca tetapan dan pintasan Laman Utama" diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml index 74574b9b23..0b2e0d5366 100644 --- a/res/values-my/strings.xml +++ b/res/values-my/strings.xml @@ -47,6 +47,7 @@ "ဖယ်ရှားမည်" "ဖယ်ထုတ်မည်" "အက်ပ်အချက်အလက်များ" + "ထည့်သွင်းရန်" "အတိုကောက်မှတ်သားမှုများအား ထည့်သွင်းခြင်း" "အသုံးပြုသူ လုပ်ဆောင်မှုမရှိပဲ အပ်ပလီကေးရှင်းကို အတိုကောက်မှတ်သားမှုများ ပြုလုပ်ခွင့် ပေးခြင်း" "ပင်မမျက်နှာစာ အပြင်အဆင် နှင့် အတိုကောက်မှတ်သားမှုများအား ဖတ်ခြင်း" diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml index 65247c168d..0a6276b9ca 100644 --- a/res/values-nb/strings.xml +++ b/res/values-nb/strings.xml @@ -47,6 +47,7 @@ "Fjern" "Avinstaller" "Info om appen" + "Installer" "installere snarveier" "Gir apper tillatelse til å legge til snarveier uten innblanding fra brukeren." "lese startsideinnstillinger og -snarveier" diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml index 904dad45e2..41e43a7501 100644 --- a/res/values-ne/strings.xml +++ b/res/values-ne/strings.xml @@ -47,6 +47,7 @@ "हटाउनुहोस्" "विस्थापित गर्नुहोस्" "अनुप्रयोग जानकारी" + "स्थापना गर्नुहोस्" "सर्टकट स्थापना गर्नेहोस्" "प्रयोगकर्ताको हस्तक्षेप बिना एउटा अनुप्रयोगलाई सर्टकटमा थप्नको लागि अनुमति दिनुहोस्।" "गृह सेटिङहरू र सर्टकटहरू पढ्नुहोस्" diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index 7e8def4ca8..16a79aef91 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -47,6 +47,7 @@ "Verwijderen" "Deïnstalleren" "App-info" + "Installeren" "Snelle links instellen" "Een app toestaan snelkoppelingen toe te voegen zonder tussenkomst van de gebruiker." "instellingen en snelkoppelingen op de homepage lezen" diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml index cf5351c7a7..525f2f629d 100644 --- a/res/values-pa/strings.xml +++ b/res/values-pa/strings.xml @@ -27,10 +27,10 @@ "ਡਾਊਨਲੋਡ ਕੀਤਾ ਐਪ ਸੁਰੱਖਿਅਤ ਮੋਡ ਵਿੱਚ ਅਸਮਰਥਿਤ" "ਵਿਜੇਟ ਸੁਰੱਖਿਅਤ ਮੋਡ ਵਿੱਚ ਅਸਮਰਥਿਤ" "ਸ਼ਾਰਟਕੱਟ ਉਪਲਬਧ ਨਹੀਂ ਹੈ" - "ਮੁੱਖ ਸਕ੍ਰੀਨ" - "ਵਿਸ਼ੇਸ਼-ਵਿਉਂਤਬੱਧ ਕਾਰਵਾਈਆਂ" + "ਹੋਮ ਸਕ੍ਰੀਨ" + "ਵਿਉਂਂਤੀ ਕਾਰਵਾਈਆਂ" "ਇੱਕ ਵਿਜੇਟ ਚੁਣਨ ਲਈ ਛੋਹਵੋT & ਹੋਲਡ ਕਰੋ।" - "ਡਬਲ-ਟੈਪ & ਇੱਕ ਵਿਜੇਟ ਚੁਣਨ ਲਈ ਹੋਲਡ ਕਰੋ ਅਤੇ ਕਸਟਮ ਕਿਰਿਆਵਾਂ ਵਰਤੋ।" + "ਇੱਕ ਵਿਜੇਟ ਚੁਣਨ ਲਈ ਜਾਂ ਵਿਉਂਂਤੀ ਕਾਰਵਾਈਆਂ ਵਰਤਣ ਲਈ ਦੋ ਵਾਰ ਟੈਪ ਕਰੋ ਅਤੇ ਦਬਾ ਕੇ ਰੱਖੋ।" "%1$d × %2$d" "%1$d ਚੌੜਾਈ ਅਤੇ %2$d ਲੰਬਾਈ" "ਹੱਥੀਂ ਰੱਖਣ ਲਈ ਸਪੱਰਸ਼ ਕਰੋ ਅਤੇ ਦਬਾਈ ਰੱਖੋ" @@ -45,15 +45,16 @@ "ਐਪ ਸੂਚੀ" "ਹੋਮ" "ਹਟਾਓ" - "ਸਥਾਪਨਾ ਰੱਦ ਕਰੋ" + "ਅਣਸਥਾਪਤ ਕਰੋ" "ਐਪ ਜਾਣਕਾਰੀ" - "ਸ਼ਾਰਟਕੱਟ ਇੰਸਟੌਲ ਕਰੋ" - "ਇੱਕ ਐਪ ਨੂੰ ਉਪਭੋਗਤਾ ਦੇ ਦਖ਼ਲ ਤੋਂ ਬਿਨਾਂ ਸ਼ਾਰਟਕੱਟ ਜੋੜਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।" + "ਸਥਾਪਤ ਕਰੋ" + "ਸ਼ਾਰਟਕੱਟ ਸਥਾਪਤ ਕਰੋ" + "ਇੱਕ ਐਪ ਨੂੰ ਵਰਤੋਂਕਾਰ ਦੇ ਦਖ਼ਲ ਤੋਂ ਬਿਨਾਂ ਸ਼ਾਰਟਕੱਟ ਸ਼ਾਮਲ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।" "ਹੋਮ ਸੈਟਿੰਗਾਂ ਅਤੇ ਸ਼ਾਰਟਕੱਟ ਪੜ੍ਹੋ" "ਐਪ ਨੂੰ ਹੋਮ ਵਿੱਚ ਸੈਟਿੰਗਾਂ ਅਤੇ ਸ਼ਾਰਟਕੱਟ ਪੜ੍ਹਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।" "ਹੋਮ ਸੈਟਿੰਗਾਂ ਅਤੇ ਸ਼ਾਰਟਕੱਟ ਲਿਖੋ" "ਐਪ ਨੂੰ ਹੋਮ ਵਿੱਚ ਸੈਟਿੰਗਾਂ ਅਤੇ ਸ਼ਾਰਟਕੱਟ ਬਦਲਣ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।" - "%1$s ਨੂੰ ਫੋਨ ਕਾਲਾਂ ਕਰਨ ਦੀ ਆਗਿਆ ਨਹੀਂ ਹੈ" + "%1$s ਨੂੰ ਫ਼ੋਨ ਕਾਲਾਂ ਕਰਨ ਦੀ ਆਗਿਆ ਨਹੀਂ ਹੈ" "ਵਿਜੇਟ ਲੋਡ ਕਰਨ ਵਿੱਚ ਸਮੱਸਿਆ" "ਸਥਾਪਤ ਕਰੋ" "ਇਹ ਇੱਕ ਸਿਸਟਮ ਐਪ ਹੈ ਅਤੇ ਇਸਨੂੰ ਅਣਇੰਸਟੌਲ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ।" @@ -73,47 +74,47 @@ "ਹੋਮ ਸੈਟਿੰਗਾਂ" "ਤੁਹਾਡੇ ਪ੍ਰਸ਼ਾਸਕ ਦੁਆਰਾ ਅਯੋਗ ਬਣਾਈ ਗਈ" "ਰੂਪ-ਰੇਖਾ" - "ਮੁੱਖ ਸਕ੍ਰੀਨ ਨੂੰ ਘੁੰਮਾਉਣ ਦੀ ਆਗਿਆ ਦਿਓ" + "ਹੋਮ ਸਕ੍ਰੀਨ ਨੂੰ ਘੁੰਮਾਉਣ ਦੀ ਆਗਿਆ ਦਿਓ" "ਜਦੋਂ ਫ਼ੋਨ ਘੁੰਮਾਇਆ ਜਾਂਦਾ ਹੈ" "ਵਰਤਮਾਨ ਡਿਸਪਲੇ ਸੈਟਿੰਗ ਘੁੰਮਾਉਣ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਦਿੰਦੀ ਹੈ" "ਸੂਚਨਾ ਬਿੰਦੂ" "ਚਾਲੂ" "ਬੰਦ" "ਸੂਚਨਾ ਪਹੁੰਚ ਲੋੜੀਂਦੀ ਹੈ" - "ਸੂਚਨਾ ਬਿੰਦੀਆਂ ਦਿਖਾਉਣ ਲਈ, %1$s ਲਈ ਐਪ ਸੂਚਨਾਵਾਂ ਚਾਲੂ ਕਰੋ" + "ਸੂਚਨਾ ਬਿੰਦੂਆਂ ਦਿਖਾਉਣ ਲਈ, %1$s ਲਈ ਐਪ ਸੂਚਨਾਵਾਂ ਚਾਲੂ ਕਰੋ" "ਸੈਟਿੰਗਾਂ ਬਦਲੋ" - "ਮੁੱਖ ਸਕ੍ਰੀਨ \'ਤੇ ਪ੍ਰਤੀਕ ਸ਼ਾਮਲ ਕਰੋ" + "ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਪ੍ਰਤੀਕ ਸ਼ਾਮਲ ਕਰੋ" "ਨਵੀਆਂ ਐਪਾਂ ਲਈ" - "ਆਈਕਨ ਦੀ ਆਕ੍ਰਿਤੀ ਬਦਲੋ" + "ਪ੍ਰਤੀਕ ਦੀ ਆਕ੍ਰਿਤੀ ਬਦਲੋ" "ਸਿਸਟਮ ਦੀ ਪੂਰਵ-ਨਿਰਧਾਰਤ ਸੈਟਿੰਗ ਵਰਤੋ" "ਵਰਗ" "ਵਰਗਾਕਾਰ-ਚੱਕਰ" "ਚੱਕਰ" "ਹੰਝੂ ਦੀ ਬੂੰਦ" - "ਆਈਕਨ ਦੀ ਆਕ੍ਰਿਤੀ ਵਿੱਚ ਤਬਦੀਲੀਆਂ ਨੂੰ ਲਾਗੂ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ" + "ਪ੍ਰਤੀਕ ਦੀ ਆਕ੍ਰਿਤੀ ਵਿੱਚ ਤਬਦੀਲੀਆਂ ਨੂੰ ਲਾਗੂ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ" "ਅਗਿਆਤ" "ਹਟਾਓ" "ਖੋਜੋ" "ਇਹ ਐਪ ਇੰਸਟੌਲ ਨਹੀਂ ਕੀਤਾ ਹੋਇਆ ਹੈ।" - "ਇਸ ਆਈਕਨ ਲਈ ਐਪ ਇੰਸਟੌਲ ਨਹੀਂ ਕੀਤਾ ਹੋਇਆ ਹੈ। ਤੁਸੀਂ ਇਸਨੂੰ ਹਟਾ ਸਕਦੇ ਹੋ ਜਾਂ ਐਪ ਖੋਜ ਸਕਦੇ ਹੋ ਅਤੇ ਇਸਨੂੰ ਮੈਨੂਅਲੀ ਇੰਸਟੌਲ ਕਰ ਸਕਦੇ ਹੋ।" + "ਇਸ ਪ੍ਰਤੀਕ ਲਈ ਐਪ ਸਥਾਪਤ ਨਹੀਂ ਕੀਤਾ ਹੋਇਆ ਹੈ। ਤੁਸੀਂ ਇਸਨੂੰ ਹਟਾ ਸਕਦੇ ਹੋ ਜਾਂ ਐਪ ਖੋਜ ਸਕਦੇ ਹੋ ਅਤੇ ਇਸਨੂੰ ਮੈਨੂਅਲੀ ਸਥਾਪਤ ਕਰ ਸਕਦੇ ਹੋ।" "%1$s ਡਾਉਨਲੋਡ ਹੋਰ ਰਿਹਾ ਹੈ, %2$s ਸੰਪੂਰਣ" - "%1$s ਸਥਾਪਿਤ ਕਰਨ ਦੀ ਉਡੀਕ ਕਰ ਰਿਹਾ ਹੈ" + "%1$s ਸਥਾਪਤ ਕਰਨ ਦੀ ਉਡੀਕ ਕਰ ਰਿਹਾ ਹੈ" "%1$s ਵਿਜੇਟ" - "ਹੋਮ ਸਕ੍ਰੀਨ ਵਿੱਚ ਜੋੜੋ" + "ਹੋਮ ਸਕ੍ਰੀਨ ਵਿੱਚ ਸ਼ਾਮਲ ਕਰੋ" "ਆਈਟਮ ਨੂੰ ਇੱਥੇ ਮੂਵ ਕਰੋ" - "ਆਈਟਮ ਨੂੰ ਮੁੱਖ ਸਕ੍ਰੀਨ ਵਿੱਚ ਜੋੜਿਆ ਗਿਆ" + "ਆਈਟਮ ਨੂੰ ਹੋਮ ਸਕ੍ਰੀਨ ਵਿੱਚ ਜੋੜਿਆ ਗਿਆ" "ਅਈਟਮ ਹਟਾਈ ਗਈ" "ਆਈਟਮ ਨੂੰ ਮੂਵ ਕਰੋ" "ਕਤਾਰ %1$s ਕਾਲਮ %2$s ਵਿੱਚ ਮੂਵ ਕਰੋ" "ਸਥਿਤੀ %1$s ਵਿੱਚ ਮੂਵ ਕਰੋ" "ਮਨਪਸੰਦ ਸਥਿਤੀ %1$s ਵਿੱਚ ਮੂਵ ਕਰੋ" "ਆਈਟਮ ਮੂਵ ਕੀਤੀ ਗਈ" - "ਇਸ ਫੋਲਡਰ ਵਿੱਚ ਜੋੜੋ: %1$s" - "%1$s ਦੇ ਨਾਲ ਫੋਲਡਰ ਵਿੱਚ ਜੋੜੋ" + "ਇਸ ਫੋਲਡਰ ਵਿੱਚ ਸ਼ਾਮਲ ਕਰੋ: %1$s" + "%1$s ਦੇ ਨਾਲ ਫੋਲਡਰ ਵਿੱਚ ਸ਼ਾਮਲ ਕਰੋ" "ਆਈਟਮ ਨੂੰ ਫੋਲਡਰ ਵਿੱਚ ਜੋੜਿਆ ਗਿਆ" "ਇਸਦੇ ਨਾਲ ਫੋਲਡਰ ਬਣਾਓ: %1$s" "ਫੋਲਡਰ ਬਣਾਇਆ ਗਿਆ" - "ਮੁੱਖ ਸਕ੍ਰੀਨ ਵਿੱਚ ਮੂਵ ਕਰੋ" + "ਹੋਮ ਸਕ੍ਰੀਨ ਵਿੱਚ ਮੂਵ ਕਰੋ" "ਸਕ੍ਰੀਨ ਨੂੰ ਖੱਬੇ ਮੂਵ ਕਰੋ" "ਸਕ੍ਰੀਨ ਨੂੰ ਸੱਜੇ ਮੂਵ ਕਰੋ" "ਸਕ੍ਰੀਨ ਨੂੰ ਮੂਵ ਕੀਤਾ ਗਿਆ" diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index c07f7defb3..2a01d04e0a 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -47,6 +47,7 @@ "Usuń" "Odinstaluj" "O aplikacji" + "Zainstaluj" "instalowanie skrótów" "Pozwala aplikacji dodawać skróty bez interwencji użytkownika." "odczytywanie ustawień i skrótów na ekranie głównym" diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml index 100f07b66b..ee4bf081d0 100644 --- a/res/values-pt-rPT/strings.xml +++ b/res/values-pt-rPT/strings.xml @@ -47,6 +47,7 @@ "Remover" "Desinstalar" "Inf. da aplicação" + "Instalar" "instalar atalhos" "Permite a uma aplicação adicionar atalhos sem a intervenção do utilizador." "ler definições e atalhos do Ecrã Principal" diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml index d75159f94a..b550f6fc2f 100644 --- a/res/values-pt/strings.xml +++ b/res/values-pt/strings.xml @@ -47,6 +47,7 @@ "Remover" "Desinstalar" "Informações do app" + "Instalar" "instalar atalhos" "Permite que um app adicione atalhos sem intervenção do usuário." "ler configurações e atalhos da tela inicial" diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml index 512d96d6b8..9cf1cc682c 100644 --- a/res/values-ro/strings.xml +++ b/res/values-ro/strings.xml @@ -47,6 +47,7 @@ "Eliminați" "Dezinstalați" "Informații aplicație" + "Instalați" "instalează comenzi rapide" "Permite unei aplicații să adauge comenzi rapide fără intervenția utilizatorului." "citește setări și comenzi rapide pentru ecranul de pornire" diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index 923f3573b3..bfd014e050 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -47,6 +47,7 @@ "Убрать" "Удалить" "О приложении" + "Установить" "Создание ярлыков" "Приложение сможет самостоятельно добавлять ярлыки." "Доступ к настройкам и ярлыкам главного экрана" diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml index f6c42b2d9a..349b7ca21d 100644 --- a/res/values-si/strings.xml +++ b/res/values-si/strings.xml @@ -47,6 +47,7 @@ "ඉවත් කරන්න" "අස්ථාපනය කරන්න" "යෙදුම් තොරතුරු" + "ස්ථාපනය කරන්න" "කෙටිමං ස්ථාපනය කරන්න" "පරිශීලක මැදිහත්වීමෙන් තොරව කෙටිමං එක් කිරීමට යෙදුමකට අවසර දෙයි." "මුල් පිටු සැකසීම් සහ කෙටිමං කියවන්න" diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml index 4dbc407b9c..ba84811c83 100644 --- a/res/values-sk/strings.xml +++ b/res/values-sk/strings.xml @@ -47,6 +47,7 @@ "Odstrániť" "Odinštalovať" "Info o aplikácii" + "Inštalovať" "inštalovať odkazy" "Povoľuje aplikácii pridať odkazy bez zásahu používateľa." "čítanie nastavení a odkazov plochy" diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml index 0b7d36ef8b..c317c0ea1c 100644 --- a/res/values-sl/strings.xml +++ b/res/values-sl/strings.xml @@ -47,6 +47,7 @@ "Odstrani" "Odstrani" "Podatki o aplikaciji" + "Namesti" "namestitev bližnjic" "Aplikaciji dovoli dodajanje bližnjic brez posredovanja uporabnika." "branje nastavitev in bližnjic na začetnem zaslonu" diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml index 3e6afee609..ed07912bf1 100644 --- a/res/values-sq/strings.xml +++ b/res/values-sq/strings.xml @@ -47,6 +47,7 @@ "Hiqe" "Çinstalo" "Informacion mbi aplikacionin" + "Instalo" "instalo shkurtore" "Lejon një aplikacion të shtojë shkurtore pa ndërhyrjen e përdoruesit." "lexo cilësimet dhe shkurtoret e ekranit bazë" diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml index f41e02d33f..f40366f486 100644 --- a/res/values-sr/strings.xml +++ b/res/values-sr/strings.xml @@ -47,6 +47,7 @@ "Уклони" "Деинсталирај" "Информ. о апликацији" + "Инсталирај" "инсталирање пречица" "Дозвољава апликацији да додаје пречице без интервенције корисника." "читање подешавања и пречица на почетном екрану" diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml index 6c598ff5dc..ff6b4e80f7 100644 --- a/res/values-sv/strings.xml +++ b/res/values-sv/strings.xml @@ -47,6 +47,7 @@ "Ta bort" "Avinstallera" "Info om appen" + "Installera" "installera genvägar" "Tillåter att en app lägger till genvägar utan åtgärd från användaren." "läsa inställningar och genvägar för startsidan" diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml index 3de28e011d..402f3a6d4c 100644 --- a/res/values-sw/strings.xml +++ b/res/values-sw/strings.xml @@ -30,7 +30,7 @@ "Skrini ya kwanza" "Vitendo maalum" "Gusa na ushikilie ili kuteua wijeti." - "Gonga mara mbili na ushikilie ile uchague wijeti au utumie vitendo maalum." + "Gusa mara mbili na ushikilie ile uchague wijeti au utumie vitendo maalum." "%1$d × %2$d" "Upana wa %1$d na kimo cha %2$d" "Gusa na ushikilie ili uweke mwenyewe" @@ -47,6 +47,7 @@ "Ondoa" "Ondoa" "Maelezo ya programu" + "Sakinisha" "kuweka njia za mkato" "Huruhusu programu kuongeza njia za mkato bila mtumiaji kuingilia kati." "soma mipangilio ya Mwanzo na njia za mkato" @@ -97,7 +98,7 @@ "Ondoa" "Tafuta" "Programu hii haijasakinishwa" - "Programu ya ikoni hii haijasakinishwa. Unaweza kuiondoa, au utafute programu na uisakinishe wewe mwenyewe." + "Programu ya aikoni hii haijasakinishwa. Unaweza kuiondoa, au utafute programu na uisakinishe wewe mwenyewe." "%1$s inapakuliwa, %2$s imekamilika" "%1$s inasubiri kusakinisha" "Wijeti za %1$s" diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml index 1d92581a05..5d72030ca7 100644 --- a/res/values-ta/strings.xml +++ b/res/values-ta/strings.xml @@ -46,7 +46,8 @@ "முகப்பு" "அகற்று" "நிறுவல் நீக்கு" - "பயன்பாட்டுத் தகவல்" + "ஆப்ஸ் தகவல்" + "நிறுவு" "குறுக்குவழிகளை நிறுவுதல்" "பயனரின் அனுமதி இல்லாமல் குறுக்குவழிகளைச் சேர்க்கப் பயன்பாட்டை அனுமதிக்கிறது." "முகப்பின் அமைப்பு மற்றும் குறுக்குவழிகளைப் படித்தல்" @@ -77,7 +78,7 @@ "மொபைலைச் சுழற்றும் போது" "தற்போதைய திரை அமைப்பு சுழற்றுவதை அனுமதிக்கவில்லை" "அறிவிப்புப் புள்ளிகள்" - "இயக்கப்பட்டுள்ளது" + "ஆன்" "முடக்கப்பட்டுள்ளது" "அறிவிப்பிற்கான அணுகல் தேவை" "அறிவிப்புப் புள்ளிகளைக் காட்ட, %1$s இன் பயன்பாட்டு அறிவிப்புகளை இயக்கவும்" diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml index 6c6669938b..4db5352e51 100644 --- a/res/values-te/strings.xml +++ b/res/values-te/strings.xml @@ -26,7 +26,7 @@ "యాప్ అందుబాటులో లేదు" "డౌన్‌లోడ్ చేసిన యాప్ సురక్షిత మోడ్‌లో నిలిపివేయబడింది" "సురక్షిత మోడ్‌లో విడ్జెట్‌లు నిలిపివేయబడ్డాయి" - "సత్వరమార్గం అందుబాటులో లేదు" + "షార్ట్‌కట్ అందుబాటులో లేదు" "హోమ్ స్క్రీన్" "అనుకూల చర్యలు" "విడ్జెట్‌ను ఎంచుకోవడానికి తాకి & నొక్కి పెట్టండి." @@ -38,7 +38,7 @@ "అప్లికేషన్‌లను శోధించండి" "అప్లికేషన్‌లను లోడ్ చేస్తోంది…" "\"%1$s\"కి సరిపోలే అప్లికేషన్‌లేవీ కనుగొనబడలేదు" - "మరిన్ని అనువర్తనాల కోసం శోధించు" + "మరిన్ని యాప్‌ల కోసం వెతుకు" "నోటిఫికేషన్‌లు" "ఈ హోమ్ స్క్రీన్‌లో ఖాళీ లేదు." "ఇష్టమైనవి ట్రేలో ఖాళీ లేదు" @@ -46,7 +46,8 @@ "హోమ్" "తీసివేయి" "అన్ఇన్‌స్టాల్ చేయి" - "అనువర్తన సమాచారం" + "యాప్ సమాచారం" + "ఇన్‌స్టాల్ చేయండి" "సత్వరమార్గాలను ఇన్‌స్టాల్ చేయడం" "వినియోగదారు ప్రమేయం లేకుండా సత్వరమార్గాలను జోడించడానికి అనువర్తనాన్ని అనుమతిస్తుంది." "హోమ్ సెట్టింగ్‌లు మరియు సత్వరమార్గాలను చదవడం" @@ -83,7 +84,7 @@ "నోటిఫికేషన్ డాట్‌లను చూపించడానికి %1$sకు యాప్ నోటిఫికేషన్‌లను ఆన్ చేయండి" "సెట్టింగ్‌లను మార్చు" "హోమ్ స్క్రీన్‌కి చిహ్నాన్ని జోడించు" - "కొత్త అనువర్తనాల కోసం" + "కొత్త యాప్‌ల కోసం" "చిహ్న ఆకారాన్ని మార్చు" "సిస్టమ్ డిఫాల్ట్‌ను ఉపయోగించండి" "చతురస్రం" @@ -93,7 +94,7 @@ "చిహ్న ఆకార మార్పులను వర్తింపజేస్తోంది" "తెలియదు" "తీసివేయి" - "శోధించు" + "వెతుకు" "ఈ యాప్ ఇన్‌స్టాల్ చేయబడలేదు" "ఈ చిహ్నం యొక్క యాప్ ఇన్‌స్టాల్ చేయబడలేదు. మీరు దీన్ని తీసివేయవచ్చు లేదా ఆ యాప్ కోసం శోధించి దాన్ని మాన్యువల్‌గా ఇన్‌స్టాల్ చేయవచ్చు." "%1$s డౌన్‌లోడ్ అవుతోంది, %2$s పూర్తయింది" diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml index b370099889..ae5aeb5160 100644 --- a/res/values-th/strings.xml +++ b/res/values-th/strings.xml @@ -47,6 +47,7 @@ "นำออก" "ถอนการติดตั้ง" "ข้อมูลแอป" + "ติดตั้ง" "ติดตั้งทางลัด" "อนุญาตให้แอปเพิ่มทางลัดโดยไม่ต้องให้ผู้ใช้จัดการ" "อ่านการตั้งค่าและทางลัดหน้าแรกแล้ว" diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml index 54135c9d58..11f13b097e 100644 --- a/res/values-tl/strings.xml +++ b/res/values-tl/strings.xml @@ -47,6 +47,7 @@ "Alisin" "I-uninstall" "Impormasyon ng app" + "I-install" "i-install ang mga shortcut" "Pinapayagan ang isang app na magdagdag ng mga shortcut nang walang panghihimasok ng user." "basahin ang mga setting at shortcut ng Home" diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml index cb8b50af20..f19cd78476 100644 --- a/res/values-tr/strings.xml +++ b/res/values-tr/strings.xml @@ -47,6 +47,7 @@ "Kaldır" "Yüklemeyi kaldır" "Uygulama bilgileri" + "Yükle" "kısayolları yükle" "Uygulamaya, kullanıcı müdahalesi olmadan kısayol ekleme izni verir." "Ana ekran ayarlarını ve kısayollarını oku" diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml index a9e010921f..08f15756bf 100644 --- a/res/values-uk/strings.xml +++ b/res/values-uk/strings.xml @@ -47,6 +47,7 @@ "Видалити" "Видалити" "Про додаток" + "Установити" "створення ярликів" "Дозволяє програмі самостійно додавати ярлики." "читати налаштування та ярлики головного екрана" diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml index a704fab4ca..99b8c806f4 100644 --- a/res/values-ur/strings.xml +++ b/res/values-ur/strings.xml @@ -47,6 +47,7 @@ "ہٹائیں" "اَن انسٹال کریں" "ایپ کی معلومات" + "انسٹال کریں" "شارٹ کٹس انسٹال کریں" "کسی ایپ کو صارف کی مداخلت کے بغیر شارٹ کٹس شامل کرنے کی اجازت دیتا ہے۔" "ہوم ترتیبات اور شارٹ کٹس کو پڑھیں" diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml index 7e31889138..5167670dfd 100644 --- a/res/values-uz/strings.xml +++ b/res/values-uz/strings.xml @@ -47,6 +47,7 @@ "Olib tashlash" "O‘chirib tashlash" "Ilova haqida" + "O‘rnatish" "yorliqlar yaratish" "Ilovalarga foydalanuvchidan so‘ramasdan yorliqlar qo‘shishga ruxsat beradi." "Uy sozlamalari va yorliqlarini o‘qish" @@ -76,11 +77,11 @@ "Asosiy ekranni aylantirishga ruxsat berish" "Telefon burilganda" "Ekran sozlamalariga ko‘ra uni aylantirib bo‘lmaydi" - "Bildirishnoma nuqtalari" + "Bildirishnoma belgilari" "Yoniq" "O‘chiq" "Bildirishnomalarga ruxsat berilmagan" - "Bildirishnoma nuqtalarini ko‘rsatish uchun %1$s ilovasida bildirishnomalarni yoqing" + "Bildirishnoma belgilarini ko‘rsatish uchun %1$s ilovasida bildirishnomalarni yoqing" "Sozlamalarni o‘zgartirish" "Bosh ekranga ikonka qo‘shish" "Yangi o‘rnatilgan ilovalar ikonkasini bosh ekranga chiqarish" diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml index 83e1ceabaa..c0e1454527 100644 --- a/res/values-vi/strings.xml +++ b/res/values-vi/strings.xml @@ -47,6 +47,7 @@ "Xóa" "Gỡ cài đặt" "Thông tin ứng dụng" + "Cài đặt" "cài đặt lối tắt" "Cho phép ứng dụng thêm lối tắt mà không cần sự can thiệp của người dùng." "đọc cài đặt và lối tắt trên Màn hình chính" diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index bef12fb61b..2342133472 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -47,6 +47,7 @@ "移除" "卸载" "应用信息" + "安装" "安装快捷方式" "允许应用自行添加快捷方式。" "读取主屏幕设置和快捷方式" diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml index 7ac5553957..f87ff7fe69 100644 --- a/res/values-zh-rHK/strings.xml +++ b/res/values-zh-rHK/strings.xml @@ -47,6 +47,7 @@ "移除" "解除安裝" "應用程式資料" + "安裝" "安裝捷徑" "允許應用程式無需使用者許可也可新增捷徑。" "讀取主畫面的設定和捷徑" diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml index e0c4c9973e..814a6e48f2 100644 --- a/res/values-zh-rTW/strings.xml +++ b/res/values-zh-rTW/strings.xml @@ -47,6 +47,7 @@ "移除" "解除安裝" "應用程式資訊" + "安裝" "安裝捷徑" "允許應用程式自動新增捷徑。" "讀取主螢幕的設定和捷徑" diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml index ef6fdeb10a..35fa1c7861 100644 --- a/res/values-zu/strings.xml +++ b/res/values-zu/strings.xml @@ -47,6 +47,7 @@ "Susa" "Khipha" "Ulwazi lohlelo lokusebenza" + "Faka" "faka izinqamuleli" "Ivumela uhlelo lokusebenza ukufaka izinqamuleli ngaphandle kokungenelela komsebenzisi." "funda izilungiselelo zokuthi Ikhaya nezinqamuleli" diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 5aee715b67..ad5f0b880f 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -63,13 +63,6 @@ - - - - - - @@ -144,4 +137,19 @@ + + + + + + + + + + + + + + + diff --git a/res/values/config.xml b/res/values/config.xml index 10b612ba2d..54328e1486 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -1,8 +1,4 @@ - - - 22 - false false @@ -46,21 +42,10 @@ - 30 - 100 - 250 + 76 90 - - 70 - - - 220 - 320 - 300 - 60 @@ -86,9 +71,7 @@ 500 - 120 200 - 60 30 @@ -128,9 +111,6 @@ - - - 150 80 diff --git a/res/values/dimens.xml b/res/values/dimens.xml index b1f9d63797..eb12dc8210 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -54,7 +54,6 @@ 14dp - 8dp 13dp 24dp @@ -87,9 +86,6 @@ 144dp 700dp 475dp - 2dp - 1dp - 13dp 18dp @@ -233,8 +229,4 @@ 19dp 0.5dp 70dp - - - - 24dp diff --git a/res/values/strings.xml b/res/values/strings.xml index 1197b1cf2c..e9b00f6faf 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -90,6 +90,8 @@ Uninstall App info + + Install diff --git a/res/xml/custom_widgets.xml b/res/xml/custom_widgets.xml new file mode 100644 index 0000000000..4b54386501 --- /dev/null +++ b/res/xml/custom_widgets.xml @@ -0,0 +1,31 @@ + + + + + + \ No newline at end of file diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java index 597e937039..62e0fb1bcd 100644 --- a/src/com/android/launcher3/AbstractFloatingView.java +++ b/src/com/android/launcher3/AbstractFloatingView.java @@ -20,11 +20,14 @@ import android.annotation.SuppressLint; 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; import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.userevent.nano.LauncherLogProto.Action; +import com.android.launcher3.util.TouchController; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -32,18 +35,25 @@ import java.lang.annotation.RetentionPolicy; /** * Base class for a View which shows a floating UI on top of the launcher UI. */ -public abstract class AbstractFloatingView extends LinearLayout { +public abstract class AbstractFloatingView extends LinearLayout implements TouchController { @IntDef(flag = true, value = { TYPE_FOLDER, - TYPE_POPUP_CONTAINER_WITH_ARROW, - TYPE_WIDGETS_BOTTOM_SHEET + TYPE_ACTION_POPUP, + TYPE_WIDGETS_BOTTOM_SHEET, + TYPE_WIDGET_RESIZE_FRAME, + TYPE_WIDGETS_FULL_SHEET }) @Retention(RetentionPolicy.SOURCE) public @interface FloatingViewType {} public static final int TYPE_FOLDER = 1 << 0; - public static final int TYPE_POPUP_CONTAINER_WITH_ARROW = 1 << 1; + public static final int TYPE_ACTION_POPUP = 1 << 1; 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_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP + | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET; protected boolean mIsOpen; @@ -72,21 +82,7 @@ public abstract class AbstractFloatingView extends LinearLayout { protected abstract void handleClose(boolean animate); - /** - * If the view is current handling keyboard, return the active target, null otherwise - */ - public ExtendedEditText getActiveTextView() { - return null; - } - - - /** - * Any additional view (outside of this container) where touch should be allowed while this - * view is visible. - */ - public View getExtendedTouchView() { - return null; - } + public abstract void logActionCommand(int command); public final boolean isOpen() { return mIsOpen; @@ -97,6 +93,16 @@ public abstract class AbstractFloatingView extends LinearLayout { protected abstract boolean isOfType(@FloatingViewType int type); + public void onBackPressed() { + logActionCommand(Action.Command.BACK); + close(true); + } + + @Override + public boolean onControllerTouchEvent(MotionEvent ev) { + return false; + } + protected static T getOpenView( Launcher launcher, @FloatingViewType int type) { DragLayer dragLayer = launcher.getDragLayer(); @@ -121,26 +127,31 @@ public abstract class AbstractFloatingView extends LinearLayout { } } - public static void closeAllOpenViews(Launcher launcher, boolean animate) { + public static void closeOpenViews(Launcher launcher, boolean animate, + @FloatingViewType int type) { DragLayer dragLayer = launcher.getDragLayer(); // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer, // and will be one of the last views. for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) { View child = dragLayer.getChildAt(i); if (child instanceof AbstractFloatingView) { - ((AbstractFloatingView) child).close(animate); + AbstractFloatingView abs = (AbstractFloatingView) child; + if (abs.isOfType(type)) { + abs.close(animate); + } } } } + public static void closeAllOpenViews(Launcher launcher, boolean animate) { + closeOpenViews(launcher, animate, TYPE_ALL); + } + public static void closeAllOpenViews(Launcher launcher) { closeAllOpenViews(launcher, true); } public static AbstractFloatingView getTopOpenView(Launcher launcher) { - return getOpenView(launcher, TYPE_FOLDER | TYPE_POPUP_CONTAINER_WITH_ARROW - | TYPE_WIDGETS_BOTTOM_SHEET); + return getOpenView(launcher, TYPE_ALL); } - - public abstract int getLogContainerType(); } diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java index 8ac8570829..5eb6cc72dc 100644 --- a/src/com/android/launcher3/AllAppsList.java +++ b/src/com/android/launcher3/AllAppsList.java @@ -154,7 +154,7 @@ public class AllAppsList { 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 7d2f753852..a5422aa7be 100644 --- a/src/com/android/launcher3/AppInfo.java +++ b/src/com/android/launcher3/AppInfo.java @@ -32,10 +32,6 @@ import com.android.launcher3.util.PackageManagerHelper; */ 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 +39,6 @@ public class AppInfo extends ItemInfoWithIcon { 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; } @@ -74,15 +60,14 @@ public class AppInfo extends ItemInfoWithIcon { this.container = ItemInfo.NO_ID; this.user = user; if (PackageManagerHelper.isAppSuspended(info.getApplicationInfo())) { - isDisabled |= ShortcutInfo.FLAG_DISABLED_SUSPENDED; + runtimeStatusFlags |= ShortcutInfo.FLAG_DISABLED_SUSPENDED; } if (quietModeEnabled) { - isDisabled |= ShortcutInfo.FLAG_DISABLED_QUIET_USER; + runtimeStatusFlags |= ShortcutInfo.FLAG_DISABLED_QUIET_USER; } intent = makeLaunchIntent(info); - - isSystemApp = (info.getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 0 + runtimeStatusFlags |= (info.getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 0 ? FLAG_SYSTEM_NO : FLAG_SYSTEM_YES; } @@ -92,8 +77,6 @@ public class AppInfo extends ItemInfoWithIcon { componentName = info.componentName; title = Utilities.trim(info.title); intent = new Intent(info.intent); - isDisabled = info.isDisabled; - isSystemApp = info.isSystemApp; } @Override @@ -119,9 +102,4 @@ public class AppInfo extends ItemInfoWithIcon { .setComponent(cn) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); } - - @Override - public boolean isDisabled() { - return isDisabled != 0; - } } diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java index a486a3aa34..1e95333ff2 100644 --- a/src/com/android/launcher3/AppWidgetResizeFrame.java +++ b/src/com/android/launcher3/AppWidgetResizeFrame.java @@ -8,22 +8,19 @@ import android.animation.ValueAnimator.AnimatorUpdateListener; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; -import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; -import android.widget.FrameLayout; +import android.view.ViewGroup; import com.android.launcher3.accessibility.DragViewStateAnnouncer; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.util.FocusLogic; -import com.android.launcher3.util.TouchController; -public class AppWidgetResizeFrame extends FrameLayout - implements View.OnKeyListener, TouchController { +public class AppWidgetResizeFrame extends AbstractFloatingView implements View.OnKeyListener { private static final int SNAP_DURATION = 150; private static final float DIMMED_HANDLE_ALPHA = 0f; private static final float RESIZE_THRESHOLD = 0.66f; @@ -109,12 +106,28 @@ public class AppWidgetResizeFrame extends FrameLayout protected void onFinishInflate() { super.onFinishInflate(); + ViewGroup content = (ViewGroup) getChildAt(0); for (int i = 0; i < HANDLE_COUNT; i ++) { - mDragHandles[i] = getChildAt(i); + mDragHandles[i] = content.getChildAt(i); } } - public void setupForWidget(LauncherAppWidgetHostView widgetView, CellLayout cellLayout, + public static void showForWidget(LauncherAppWidgetHostView widget, CellLayout cellLayout) { + Launcher launcher = Launcher.getLauncher(cellLayout.getContext()); + AbstractFloatingView.closeAllOpenViews(launcher); + + DragLayer dl = launcher.getDragLayer(); + AppWidgetResizeFrame frame = (AppWidgetResizeFrame) launcher.getLayoutInflater() + .inflate(R.layout.app_widget_resize_frame, dl, false); + frame.setupForWidget(widget, cellLayout, dl); + ((DragLayer.LayoutParams) frame.getLayoutParams()).customPosition = true; + + dl.addView(frame); + frame.mIsOpen = true; + frame.snapToWidget(false); + } + + private void setupForWidget(LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) { mCellLayout = cellLayout; mWidgetView = widgetView; @@ -126,14 +139,8 @@ public class AppWidgetResizeFrame extends FrameLayout mMinHSpan = info.minSpanX; mMinVSpan = info.minSpanY; - if (!info.isCustomWidget) { - mWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(getContext(), - widgetView.getAppWidgetInfo().provider, null); - } else { - Resources r = getContext().getResources(); - int padding = r.getDimensionPixelSize(R.dimen.default_widget_padding); - mWidgetPadding = new Rect(padding, padding, padding, padding); - } + mWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(getContext(), + widgetView.getAppWidgetInfo().provider, null); if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) { mDragHandles[INDEX_TOP].setVisibility(GONE); @@ -391,7 +398,7 @@ public class AppWidgetResizeFrame extends FrameLayout out.bottom = out.top + height; } - public void snapToWidget(boolean animate) { + private void snapToWidget(boolean animate) { getSnappedRectRelativeToDragLayer(sTmpRect); int newWidth = sTmpRect.width(); int newHeight = sTmpRect.height(); @@ -455,7 +462,7 @@ public class AppWidgetResizeFrame extends FrameLayout public boolean onKey(View v, int keyCode, KeyEvent event) { // Clear the frame and give focus to the widget host view when a directional key is pressed. if (FocusLogic.shouldConsume(keyCode)) { - mDragLayer.clearResizeFrame(); + close(false); mWidgetView.requestFocus(); return true; } @@ -505,9 +512,25 @@ public class AppWidgetResizeFrame extends FrameLayout if (ev.getAction() == MotionEvent.ACTION_DOWN && handleTouchDown(ev)) { return true; } + close(false); return false; } + @Override + protected void handleClose(boolean animate) { + mDragLayer.removeView(this); + } + + @Override + public void logActionCommand(int command) { + // TODO: Log this case. + } + + @Override + protected boolean isOfType(int type) { + return (type & TYPE_WIDGET_RESIZE_FRAME) != 0; + } + /** * A mutable class for describing the range of two int values. */ diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java index d82579bf77..162aa0889c 100644 --- a/src/com/android/launcher3/AutoInstallsLayout.java +++ b/src/com/android/launcher3/AutoInstallsLayout.java @@ -28,7 +28,9 @@ import android.content.res.XmlResourceParser; import android.database.sqlite.SQLiteDatabase; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Build; import android.os.Bundle; +import android.os.Process; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; @@ -433,8 +435,10 @@ public class AutoInstallsLayout { return -1; } - mValues.put(LauncherSettings.Favorites.ICON, - Utilities.flattenBitmap(LauncherIcons.createIconBitmap(icon, mContext))); + // Auto installs should always support the current platform version. + mValues.put(LauncherSettings.Favorites.ICON, Utilities.flattenBitmap( + LauncherIcons.createBadgedIconBitmap( + icon, Process.myUserHandle(), mContext, Build.VERSION.SDK_INT))); mValues.put(Favorites.ICON_PACKAGE, mIconRes.getResourcePackageName(iconId)); mValues.put(Favorites.ICON_RESOURCE, mIconRes.getResourceName(iconId)); diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java deleted file mode 100644 index 82175b721d..0000000000 --- a/src/com/android/launcher3/BaseContainerView.java +++ /dev/null @@ -1,218 +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.annotation.SuppressLint; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.PointF; -import android.graphics.Rect; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.InsetDrawable; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.widget.FrameLayout; - -import com.android.launcher3.allapps.AllAppsContainerView; -import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.util.TransformingTouchDelegate; - -/** - * A base container view, which supports resizing. - */ -public abstract class BaseContainerView extends FrameLayout - implements DeviceProfile.LauncherLayoutChangeListener { - - private static final Rect sBgPaddingRect = new Rect(); - - protected final Drawable mBaseDrawable; - - private View mRevealView; - private View mContent; - - private TransformingTouchDelegate mTouchDelegate; - - private final PointF mLastTouchDownPosPx = new PointF(-1.0f, -1.0f); - - public BaseContainerView(Context context) { - this(context, null); - } - - public BaseContainerView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public BaseContainerView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - if (this instanceof AllAppsContainerView) { - mBaseDrawable = new ColorDrawable(); - } else { - TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.BaseContainerView, defStyleAttr, 0); - mBaseDrawable = a.getDrawable(R.styleable.BaseContainerView_revealBackground); - a.recycle(); - } - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile(); - grid.addLauncherLayoutChangedListener(this); - - View touchDelegateTargetView = getTouchDelegateTargetView(); - if (touchDelegateTargetView != null) { - mTouchDelegate = new TransformingTouchDelegate(touchDelegateTargetView); - ((View) touchDelegateTargetView.getParent()).setTouchDelegate(mTouchDelegate); - } - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - - DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile(); - grid.removeLauncherLayoutChangedListener(this); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - mContent = findViewById(R.id.main_content); - mRevealView = findViewById(R.id.reveal_view); - - updatePaddings(); - } - - @Override - public void onLauncherLayoutChanged() { - updatePaddings(); - } - - /** - * Calculate the background padding as it can change due to insets/content padding change. - */ - private void updatePaddings() { - DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile(); - int[] padding = grid.getContainerPadding(); - - int paddingLeft = padding[0]; - int paddingRight = padding[1]; - int paddingTop = 0; - int paddingBottom = 0; - - if (!grid.isVerticalBarLayout()) { - paddingLeft += grid.edgeMarginPx; - paddingRight += grid.edgeMarginPx; - paddingTop = paddingBottom = grid.edgeMarginPx; - } - updateBackground(paddingLeft, paddingTop, paddingRight, paddingBottom); - } - - /** - * Update the background for the reveal view and content view based on the background padding. - */ - protected void updateBackground(int paddingLeft, int paddingTop, - int paddingRight, int paddingBottom) { - mRevealView.setBackground(new InsetDrawable(mBaseDrawable, - paddingLeft, paddingTop, paddingRight, paddingBottom)); - mContent.setBackground(new InsetDrawable(mBaseDrawable, - paddingLeft, paddingTop, paddingRight, paddingBottom)); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - - View touchDelegateTargetView = getTouchDelegateTargetView(); - if (touchDelegateTargetView != null) { - getRevealView().getBackground().getPadding(sBgPaddingRect); - mTouchDelegate.setBounds( - touchDelegateTargetView.getLeft() - sBgPaddingRect.left, - touchDelegateTargetView.getTop() - sBgPaddingRect.top, - touchDelegateTargetView.getRight() + sBgPaddingRect.right, - touchDelegateTargetView.getBottom() + sBgPaddingRect.bottom); - } - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - return handleTouchEvent(ev); - } - - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouchEvent(MotionEvent ev) { - return handleTouchEvent(ev); - } - - public void setRevealDrawableColor(int color) { - ((ColorDrawable) mBaseDrawable).setColor(color); - } - - public final View getContentView() { - return mContent; - } - - public final View getRevealView() { - return mRevealView; - } - - - /** - * Handles the touch events that shows the workspace when clicking outside the bounds of the - * touch delegate target view. - */ - private boolean handleTouchEvent(MotionEvent ev) { - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: - // Check if the touch is outside touch delegate target view - View touchDelegateTargetView = getTouchDelegateTargetView(); - float leftBoundPx = touchDelegateTargetView.getLeft(); - if (ev.getX() < leftBoundPx || - ev.getX() > (touchDelegateTargetView.getWidth() + leftBoundPx)) { - mLastTouchDownPosPx.set((int) ev.getX(), (int) ev.getY()); - } - break; - case MotionEvent.ACTION_UP: - if (mLastTouchDownPosPx.x > -1) { - ViewConfiguration viewConfig = ViewConfiguration.get(getContext()); - float dx = ev.getX() - mLastTouchDownPosPx.x; - float dy = ev.getY() - mLastTouchDownPosPx.y; - float distance = PointF.length(dx, dy); - if (distance < viewConfig.getScaledTouchSlop()) { - // The background was clicked, so just go home - Launcher.getLauncher(getContext()).showWorkspace(true); - return true; - } - } - // Fall through - case MotionEvent.ACTION_CANCEL: - mLastTouchDownPosPx.set(-1, -1); - break; - } - return false; - } - - public abstract View getTouchDelegateTargetView(); -} diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java index 3ee6e51b8d..afb83be580 100644 --- a/src/com/android/launcher3/BaseRecyclerView.java +++ b/src/com/android/launcher3/BaseRecyclerView.java @@ -21,6 +21,7 @@ import android.graphics.Canvas; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.MotionEvent; +import android.view.View; import android.view.ViewGroup; import android.widget.TextView; @@ -99,11 +100,15 @@ public abstract class BaseRecyclerView extends RecyclerView // DO NOT REMOVE, NEEDED IMPLEMENTATION FOR M BUILDS } + public int getScrollBarTop() { + return getPaddingTop(); + } + /** * Returns the height of the fast scroll bar */ public int getScrollbarTrackHeight() { - return getHeight() - getPaddingTop() - getPaddingBottom(); + return getHeight() - getScrollBarTop() - getPaddingBottom(); } /** @@ -121,13 +126,6 @@ public abstract class BaseRecyclerView extends RecyclerView return availableScrollBarHeight; } - /** - * Returns the scrollbar for this recycler view. - */ - public RecyclerViewFastScroller getScrollBar() { - return mScrollbar; - } - @Override protected void dispatchDraw(Canvas canvas) { onUpdateScrollbar(0); @@ -159,6 +157,28 @@ public abstract class BaseRecyclerView extends RecyclerView mScrollbar.setThumbOffsetY(scrollBarY); } + /** + * Returns whether the view itself will handle the touch event or not. + * @param ev MotionEvent in {@param eventSource} + */ + public boolean shouldContainerScroll(MotionEvent ev, View eventSource) { + int[] point = new int[2]; + point[0] = (int) ev.getX(); + point[1] = (int) ev.getY(); + Utilities.mapCoordInSelfToDescendant(mScrollbar, eventSource, point); + // IF the MotionEvent is inside the thumb, container should not be pulled down. + if (mScrollbar.shouldBlockIntercept(point[0], point[1])) { + return false; + } + + // IF scroller is at the very top OR there is no scroll bar because there is probably not + // enough items to scroll, THEN it's okay for the container to be pulled down. + if (getCurrentScrollY() == 0) { + return true; + } + return false; + } + /** * @return whether fast scrolling is supported in the current state. */ diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index ac842f92e6..a590504033 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -42,6 +42,7 @@ import android.widget.TextView; import com.android.launcher3.IconCache.IconLoadRequest; import com.android.launcher3.IconCache.ItemInfoUpdateReceiver; +import com.android.launcher3.Launcher.OnResumeCallback; import com.android.launcher3.badge.BadgeInfo; import com.android.launcher3.badge.BadgeRenderer; import com.android.launcher3.folder.FolderIcon; @@ -59,7 +60,7 @@ import java.text.NumberFormat; * because we want to make the bubble taller than the text and TextView's clip is * too aggressive. */ -public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver { +public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback { private static final int DISPLAY_WORKSPACE = 0; private static final int DISPLAY_ALL_APPS = 1; @@ -177,6 +178,16 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver { } + /** + * 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); } @@ -330,6 +341,13 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver { refreshDrawableState(); } + @Override + public void onLauncherResume() { + // Reset the pressed state of icon that was locked in the press state while activity + // was launching + setStayPressed(false); + } + void clearPressedBackground() { setPressed(false); setStayPressed(false); @@ -496,11 +514,17 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver { public PreloadIconDrawable applyProgressLevel(int progressLevel) { if (getTag() instanceof ItemInfoWithIcon) { ItemInfoWithIcon info = (ItemInfoWithIcon) getTag(); - setContentDescription(progressLevel > 0 - ? getContext().getString(R.string.app_downloading_title, info.title, - NumberFormat.getPercentInstance().format(progressLevel * 0.01)) - : getContext().getString(R.string.app_waiting_download_title, info.title)); - + if (progressLevel >= 100) { + setContentDescription(info.contentDescription != null + ? info.contentDescription : ""); + } else if (progressLevel > 0) { + setContentDescription(getContext() + .getString(R.string.app_downloading_title, info.title, + NumberFormat.getPercentInstance().format(progressLevel * 0.01))); + } else { + setContentDescription(getContext() + .getString(R.string.app_waiting_download_title, info.title)); + } if (mIcon != null) { final PreloadIconDrawable preloadDrawable; if (mIcon instanceof PreloadIconDrawable) { diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java index 632e490590..d8c4efaa4e 100644 --- a/src/com/android/launcher3/ButtonDropTarget.java +++ b/src/com/android/launcher3/ButtonDropTarget.java @@ -16,6 +16,8 @@ package com.android.launcher3; +import static com.android.launcher3.LauncherState.NORMAL; + import android.animation.AnimatorSet; import android.animation.FloatArrayEvaluator; import android.animation.ObjectAnimator; @@ -35,10 +37,9 @@ import android.view.View; 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; @@ -178,7 +179,7 @@ public abstract class ButtonDropTarget extends TextView @Override public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { - mActive = supportsDrop(dragObject.dragSource, dragObject.dragInfo); + mActive = supportsDrop(dragObject.dragInfo); mDrawable.setColorFilter(null); if (mCurrentColorAnim != null) { mCurrentColorAnim.cancel(); @@ -194,10 +195,14 @@ public abstract class ButtonDropTarget extends TextView @Override public final boolean acceptDrop(DragObject dragObject) { - return supportsDrop(dragObject.dragSource, dragObject.dragInfo); + return supportsDrop(dragObject.dragInfo); } - protected abstract boolean supportsDrop(DragSource source, ItemInfo info); + protected abstract boolean supportsDrop(ItemInfo info); + + public boolean supportsAccessibilityDrop(ItemInfo info) { + return supportsDrop(info); + } @Override public boolean isDropEnabled() { @@ -215,7 +220,7 @@ public abstract class ButtonDropTarget extends TextView * On drop animate the dropView to the icon. */ @Override - public void onDrop(final DragObject d) { + public void onDrop(final DragObject d, final DragOptions options) { final DragLayer dragLayer = mLauncher.getDragLayer(); final Rect from = new Rect(); dragLayer.getViewRectRelativeToSelf(d.dragView, from); @@ -229,19 +234,22 @@ public abstract class ButtonDropTarget extends TextView public void run() { completeDrop(d); mDropTargetBar.onDragEnd(); - mLauncher.exitSpringLoadedDragModeDelayed(true, 0, null); + 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 diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index a75c6d1c4d..3162286c1c 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -44,15 +44,15 @@ import android.view.View; 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.GridOccupancy; 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; @@ -89,8 +90,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { private int mCountY; private boolean mDropPending = false; - private boolean mIsDragTarget = true; - private boolean mJailContent = true; // 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. @@ -106,8 +105,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { private final ArrayList mFolderBackgrounds = new ArrayList<>(); final PreviewBackground mFolderLeaveBehind = new PreviewBackground(); - private float mBackgroundAlpha; - private static final int[] BACKGROUND_STATE_ACTIVE = new int[] { android.R.attr.state_active }; private static final int[] BACKGROUND_STATE_DEFAULT = new int[0]; private final Drawable mBackground; @@ -223,12 +220,12 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { mBackground = res.getDrawable(R.drawable.bg_celllayout); mBackground.setCallback(this); - mBackground.setAlpha((int) (mBackgroundAlpha * 255)); + mBackground.setAlpha(0); 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); @@ -346,7 +343,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { // 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; } @@ -357,10 +354,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint); } - public void buildHardwareLayer() { - mShortcutsAndWidgets.buildLayer(); - } - public void setCellDimensions(int width, int height) { mFixedCellWidth = mCellWidth = width; mFixedCellHeight = mCellHeight = height; @@ -404,14 +397,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { } } - void disableDragTarget() { - mIsDragTarget = false; - } - - public boolean isDragTarget() { - return mIsDragTarget; - } - void setIsDragOverlapping(boolean isDragOverlapping) { if (mIsDragOverlapping != isDragOverlapping) { mIsDragOverlapping = isDragOverlapping; @@ -421,26 +406,22 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { } } - public void disableJailContent() { - mJailContent = false; - } - @Override protected void dispatchSaveInstanceState(SparseArray container) { - if (mJailContent) { - ParcelableSparseArray jail = getJailedArray(container); - super.dispatchSaveInstanceState(jail); - container.put(R.id.cell_layout_jail_id, jail); - } else { - super.dispatchSaveInstanceState(container); - } + ParcelableSparseArray jail = getJailedArray(container); + super.dispatchSaveInstanceState(jail); + container.put(R.id.cell_layout_jail_id, jail); } @Override protected void dispatchRestoreInstanceState(SparseArray container) { - super.dispatchRestoreInstanceState(mJailContent ? getJailedArray(container) : container); + super.dispatchRestoreInstanceState(getJailedArray(container)); } + /** + * Wrap the SparseArray in another Parcelable so that the item ids do not conflict with our + * our internal resource ids + */ private ParcelableSparseArray getJailedArray(SparseArray container) { final Parcelable parcelable = container.get(R.id.cell_layout_jail_id); return parcelable instanceof ParcelableSparseArray ? @@ -453,16 +434,12 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { @Override protected void onDraw(Canvas canvas) { - if (!mIsDragTarget) { - return; - } - // When we're large, we are either drawn in a "hover" state (ie when dragging an item to // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f) // When we're small, we are either drawn normally or in the "accepts drops" state (during // a drag). However, we also drag the mini hover background *over* one of those two // backgrounds - if (mBackgroundAlpha > 0.0f) { + if (mBackground.getAlpha() > 0) { mBackground.draw(canvas); } @@ -838,16 +815,10 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { - boolean isFullscreen = mShortcutsAndWidgets.getChildCount() > 0 && - ((LayoutParams) mShortcutsAndWidgets.getChildAt(0).getLayoutParams()).isFullscreen; int left = getPaddingLeft(); - if (!isFullscreen) { - left += (int) Math.ceil(getUnusedHorizontalSpace() / 2f); - } + left += (int) Math.ceil(getUnusedHorizontalSpace() / 2f); int right = r - l - getPaddingRight(); - if (!isFullscreen) { - right -= (int) Math.ceil(getUnusedHorizontalSpace() / 2f); - } + right -= (int) Math.ceil(getUnusedHorizontalSpace() / 2f); int top = getPaddingTop(); int bottom = b - t - getPaddingBottom(); @@ -875,20 +846,13 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth); } - public float getBackgroundAlpha() { - return mBackgroundAlpha; - } - - public void setBackgroundAlpha(float alpha) { - if (mBackgroundAlpha != alpha) { - mBackgroundAlpha = alpha; - mBackground.setAlpha((int) (mBackgroundAlpha * 255)); - } + public Drawable getScrimBackground() { + return mBackground; } @Override protected boolean verifyDrawable(Drawable who) { - return super.verifyDrawable(who) || (mIsDragTarget && who == mBackground); + return super.verifyDrawable(who) || (who == mBackground); } public void setShortcutAndWidgetAlpha(float alpha) { @@ -2653,11 +2617,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { */ public boolean isLockedToGrid = true; - /** - * Indicates that this item should use the full extents of its parent. - */ - public boolean isFullscreen = false; - /** * Indicates whether this item can be reordered. Always true except in the case of the * the AllApps button and QSB place holder. diff --git a/src/com/android/launcher3/CustomAppWidget.java b/src/com/android/launcher3/CustomAppWidget.java deleted file mode 100644 index 1b4ed79c0b..0000000000 --- a/src/com/android/launcher3/CustomAppWidget.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.android.launcher3; - -public interface CustomAppWidget { - public String getLabel(); - public int getPreviewImage(); - public int getIcon(); - public int getWidgetLayout(); - - public int getSpanX(); - public int getSpanY(); - public int getMinSpanX(); - public int getMinSpanY(); - public int getResizeMode(); -} diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java index 4dcb64f01d..c12ea57c37 100644 --- a/src/com/android/launcher3/DeleteDropTarget.java +++ b/src/com/android/launcher3/DeleteDropTarget.java @@ -21,6 +21,7 @@ import android.text.TextUtils; 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; @@ -46,27 +47,35 @@ public class DeleteDropTarget extends ButtonDropTarget { @Override public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { super.onDragStart(dragObject, options); - setTextBasedOnDragSource(dragObject.dragSource); + 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 - protected boolean supportsDrop(DragSource source, ItemInfo info) { + public int getAccessibilityAction() { + return LauncherAccessibilityDelegate.REMOVE; + } + + @Override + protected boolean supportsDrop(ItemInfo info) { return true; } /** - * Set the drop target's text to either "Remove" or "Cancel" depending on the drag source. + * Set the drop target's text to either "Remove" or "Cancel" depending on the drag item. */ - public void setTextBasedOnDragSource(DragSource dragSource) { + private void setTextBasedOnDragSource(ItemInfo item) { if (!TextUtils.isEmpty(mText)) { - mText = getResources().getString(dragSource.supportsDeleteDropTarget() + mText = getResources().getString(item.id != ItemInfo.NO_ID ? R.string.remove_drop_target_label : android.R.string.cancel); requestLayout(); @@ -77,19 +86,21 @@ public class DeleteDropTarget extends ButtonDropTarget { 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 69ee03e9e3..6030e08865 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -65,12 +65,6 @@ public class DeviceProfile { 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 @@ public class DeviceProfile { 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); @@ -277,8 +261,12 @@ public class DeviceProfile { DeviceProfile profile = new DeviceProfile(context, inv, mwSize, mwSize, mwSize.x, mwSize.y, isLandscape); - // Hide labels on the workspace. - profile.adjustToHideWorkspaceLabels(); + // If there isn't enough vertical cell padding with the labels displayed, hide the labels. + float workspaceCellPaddingY = profile.getCellSize().y - profile.iconSizePx + - iconDrawablePaddingPx - profile.iconTextSizePx; + if (workspaceCellPaddingY < profile.iconDrawablePaddingPx * 2) { + profile.adjustToHideWorkspaceLabels(); + } // We use these scales to measure and layout the widgets using their full invariant profile // sizes and then draw them scaled and centered to fit in their multi-window mode cellspans. @@ -566,13 +554,6 @@ public class DeviceProfile { } } - int getOverviewModeButtonBarHeight() { - int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx); - return Utilities.boundToRange(zoneHeight, - overviewModeMinIconZoneHeightPx, - overviewModeMaxIconZoneHeightPx); - } - public static int calculateCellWidth(int width, int countX) { return width / countX; } @@ -593,16 +574,6 @@ public class DeviceProfile { 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(); @@ -697,20 +668,6 @@ public class DeviceProfile { 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); int paddingLeftRight = desiredWorkspaceLeftRightMarginPx + cellLayoutPaddingLeftRightPx; diff --git a/src/com/android/launcher3/DragSource.java b/src/com/android/launcher3/DragSource.java index dcd8f589a6..93f865c9f9 100644 --- a/src/com/android/launcher3/DragSource.java +++ b/src/com/android/launcher3/DragSource.java @@ -26,25 +26,9 @@ import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider; */ public interface DragSource extends LogContainerProvider { - /** - * @return whether items dragged from this source supports 'App Info' - */ - boolean supportsAppInfoDropTarget(); - - /** - * @return whether items dragged from this source supports 'Delete' drop target (e.g. to remove - * a shortcut.) If this returns false, the drop target will say "Cancel" instead of "Remove." - */ - boolean supportsDeleteDropTarget(); - - /* - * @return the scale of the icons over the workspace icon size - */ - float getIntrinsicIconScaleFactor(); - /** * A callback made back to the source after an item from this source has been dropped on a * DropTarget. */ - void onDropCompleted(View target, DragObject d, boolean isFlingToDelete, boolean success); + void onDropCompleted(View target, DragObject d, boolean success); } diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java index 7d047d748d..4d30479204 100644 --- a/src/com/android/launcher3/DropTarget.java +++ b/src/com/android/launcher3/DropTarget.java @@ -19,6 +19,7 @@ package com.android.launcher3; import android.graphics.Rect; import com.android.launcher3.accessibility.DragViewStateAnnouncer; +import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.dragndrop.DragView; /** @@ -58,9 +59,6 @@ public interface DropTarget { /** The object is part of an accessible drag operation */ public boolean accessibleDrag; - /** Post drag animation runnable */ - public Runnable postAnimationRunnable = null; - /** Indicates that the drag operation was cancelled */ public boolean cancelled = false; @@ -104,9 +102,16 @@ public interface DropTarget { boolean isDropEnabled(); /** - * Handle an object being dropped on the DropTarget + * Handle an object being dropped on the DropTarget. + * + * This will be called only if this target previously returned true for {@link #acceptDrop}. It + * is the responsibility of this target to exit out of the spring loaded mode (either + * immediately or after any pending animations). + * + * If the drop was cancelled for some reason, onDrop will never get called, the UI will + * automatically exit out of this mode. */ - void onDrop(DragObject dragObject); + void onDrop(DragObject dragObject, DragOptions options); void onDragEnter(DragObject dragObject); diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java index 29a1349d5c..3eca5cd44a 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.View; 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 @@ public class DropTargetBar extends LinearLayout implements DragController.DragLi @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 class DropTargetBar extends LinearLayout implements DragController.DragLi public void setup(DragController dragController) { dragController.addDragListener(this); - setupButtonDropTarget(this, dragController); + ArrayList 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 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 @@ public class DropTargetBar extends LinearLayout implements DragController.DragLi 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 @@ public class DropTargetBar extends LinearLayout implements DragController.DragLi mDeferOnDragEnd = false; } } + + public ButtonDropTarget[] getDropTargets() { + return mDropTargets; + } } diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java index 596aa8f7bc..403c8b8ba7 100644 --- a/src/com/android/launcher3/ExtendedEditText.java +++ b/src/com/android/launcher3/ExtendedEditText.java @@ -16,12 +16,16 @@ package com.android.launcher3; import android.content.Context; +import android.text.TextUtils; import android.util.AttributeSet; import android.view.DragEvent; import android.view.KeyEvent; +import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; +import com.android.launcher3.util.UiThreadHelper; + /** * The edit text that reports back when the back key has been pressed. @@ -102,8 +106,7 @@ public class ExtendedEditText extends EditText { } public void dispatchBackKey() { - ((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE)) - .hideSoftInputFromWindow(getWindowToken(), 0); + UiThreadHelper.hideKeyboardAsync(getContext(), getWindowToken()); if (mBackKeyListener != null) { mBackKeyListener.onBackKey(); } @@ -121,4 +124,17 @@ public class ExtendedEditText extends EditText { public boolean isSuggestionsEnabled() { return !mForceDisableSuggestions && super.isSuggestionsEnabled(); } + + public void reset() { + if (!TextUtils.isEmpty(getText())) { + setText(""); + } + if (isFocused()) { + View nextFocus = focusSearch(View.FOCUS_DOWN); + if (nextFocus != null) { + nextFocus.requestFocus(); + } + } + UiThreadHelper.hideKeyboardAsync(getContext(), getWindowToken()); + } } diff --git a/src/com/android/launcher3/FirstFrameAnimatorHelper.java b/src/com/android/launcher3/FirstFrameAnimatorHelper.java index 3cbc989eb3..cea7e431a2 100644 --- a/src/com/android/launcher3/FirstFrameAnimatorHelper.java +++ b/src/com/android/launcher3/FirstFrameAnimatorHelper.java @@ -24,6 +24,7 @@ import android.view.View; import android.view.ViewPropertyAnimator; import android.view.ViewTreeObserver; import com.android.launcher3.util.Thunk; +import com.android.launcher3.util.TraceHelper; /* * This is a helper class that listens to updates from the corresponding animation. @@ -71,15 +72,12 @@ public class FirstFrameAnimatorHelper extends AnimatorListenerAdapter if (sGlobalDrawListener != null) { view.getViewTreeObserver().removeOnDrawListener(sGlobalDrawListener); } + + TraceHelper.beginSection("TICK"); sGlobalDrawListener = new ViewTreeObserver.OnDrawListener() { - private long mTime = System.currentTimeMillis(); public void onDraw() { sGlobalFrameCounter++; - if (DEBUG) { - long newTime = System.currentTimeMillis(); - Log.d(TAG, "TICK " + (newTime - mTime)); - mTime = newTime; - } + TraceHelper.partitionSection("TICK", "Frame drawn"); } }; view.getViewTreeObserver().addOnDrawListener(sGlobalDrawListener); diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java index fe7acda17b..1f18ea1ac8 100644 --- a/src/com/android/launcher3/FocusHelper.java +++ b/src/com/android/launcher3/FocusHelper.java @@ -288,25 +288,11 @@ public class FocusHelper { case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN: // Go to the previous page but keep the focus on the same hotseat icon. workspace.snapToPage(pageIndex - 1); - // If the page we are going to is fullscreen, have it take the focus from hotseat. - CellLayout prevPage = (CellLayout) workspace.getPageAt(pageIndex - 1); - boolean isPrevPageFullscreen = ((CellLayout.LayoutParams) prevPage - .getShortcutsAndWidgets().getChildAt(0).getLayoutParams()).isFullscreen; - if (isPrevPageFullscreen) { - workspace.getPageAt(pageIndex - 1).requestFocus(); - } break; case FocusLogic.NEXT_PAGE_LEFT_COLUMN: case FocusLogic.NEXT_PAGE_RIGHT_COLUMN: // Go to the next page but keep the focus on the same hotseat icon. workspace.snapToPage(pageIndex + 1); - // If the page we are going to is fullscreen, have it take the focus from hotseat. - CellLayout nextPage = (CellLayout) workspace.getPageAt(pageIndex + 1); - boolean isNextPageFullscreen = ((CellLayout.LayoutParams) nextPage - .getShortcutsAndWidgets().getChildAt(0).getLayoutParams()).isFullscreen; - if (isNextPageFullscreen) { - workspace.getPageAt(pageIndex + 1).requestFocus(); - } break; } if (parent == iconParent && newIconIndex >= iconParent.getChildCount()) { diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java index a6d80e336e..09f9e827cd 100644 --- a/src/com/android/launcher3/Hotseat.java +++ b/src/com/android/launcher3/Hotseat.java @@ -16,16 +16,9 @@ package com.android.launcher3; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ArgbEvaluator; -import android.animation.ValueAnimator; import android.content.Context; -import android.graphics.Color; import android.graphics.Rect; -import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; -import android.support.v4.graphics.ColorUtils; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -35,11 +28,9 @@ import android.widget.FrameLayout; import android.widget.TextView; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.dynamicui.ExtractedColors; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; -import com.android.launcher3.util.Themes; public class Hotseat extends FrameLayout implements UserEventDispatcher.LogContainerProvider { @@ -51,12 +42,6 @@ public class Hotseat extends FrameLayout @ViewDebug.ExportedProperty(category = "launcher") private final boolean mHasVerticalHotseat; - @ViewDebug.ExportedProperty(category = "launcher") - private int mBackgroundColor; - @ViewDebug.ExportedProperty(category = "launcher") - private ColorDrawable mBackground; - private ValueAnimator mBackgroundColorAnimator; - public Hotseat(Context context) { this(context, null); } @@ -69,12 +54,6 @@ public class Hotseat extends FrameLayout super(context, attrs, defStyle); mLauncher = Launcher.getLauncher(context); mHasVerticalHotseat = mLauncher.getDeviceProfile().isVerticalBarLayout(); - mBackgroundColor = ColorUtils.setAlphaComponent( - Themes.getAttrColor(context, android.R.attr.colorPrimary), 0); - mBackground = new ColorDrawable(mBackgroundColor); - if (!FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) { - setBackground(mBackground); - } } public CellLayout getLayout() { @@ -177,49 +156,4 @@ public class Hotseat extends FrameLayout target.gridY = info.cellY; targetParent.containerType = ContainerType.HOTSEAT; } - - public void updateColor(ExtractedColors extractedColors, boolean animate) { - if (FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) { - // not hotseat visible - return; - } - if (!mHasVerticalHotseat) { - int color = extractedColors.getColor(ExtractedColors.HOTSEAT_INDEX); - if (mBackgroundColorAnimator != null) { - mBackgroundColorAnimator.cancel(); - } - if (!animate) { - setBackgroundColor(color); - } else { - mBackgroundColorAnimator = ValueAnimator.ofInt(mBackgroundColor, color); - mBackgroundColorAnimator.setEvaluator(new ArgbEvaluator()); - mBackgroundColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - mBackground.setColor((Integer) animation.getAnimatedValue()); - } - }); - mBackgroundColorAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mBackgroundColorAnimator = null; - } - }); - mBackgroundColorAnimator.start(); - } - mBackgroundColor = color; - } - } - - public void setBackgroundTransparent(boolean enable) { - if (enable) { - mBackground.setAlpha(0); - } else { - mBackground.setAlpha(255); - } - } - - public int getBackgroundDrawableColor() { - return mBackgroundColor; - } } diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java index f919dd052c..289242f61e 100644 --- a/src/com/android/launcher3/InfoDropTarget.java +++ b/src/com/android/launcher3/InfoDropTarget.java @@ -26,6 +26,7 @@ import android.util.AttributeSet; 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,28 +50,29 @@ public class InfoDropTarget extends UninstallDropTarget { } @Override - public void completeDrop(DragObject d) { - DropTargetResultCallback callback = d.dragSource instanceof DropTargetResultCallback - ? (DropTargetResultCallback) d.dragSource : null; - startDetailsActivityForInfo(d.dragInfo, mLauncher, callback); + protected ComponentName performDropAction(ItemInfo item) { + return performDropAction(mLauncher, item, null, null); } /** * @return Whether the activity was started. */ public static boolean startDetailsActivityForInfo( - ItemInfo info, Launcher launcher, DropTargetResultCallback callback) { - return startDetailsActivityForInfo(info, launcher, callback, null, null); + ItemInfo info, Launcher launcher, Rect sourceBounds, Bundle opts) { + return performDropAction(launcher, info, sourceBounds, opts) != null; } - public static boolean startDetailsActivityForInfo(ItemInfo info, Launcher launcher, - DropTargetResultCallback callback, Rect sourceBounds, Bundle opts) { + /** + * 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, + Rect sourceBounds, Bundle opts) { if (info instanceof PromiseAppInfo) { PromiseAppInfo promiseAppInfo = (PromiseAppInfo) info; - launcher.startActivity(promiseAppInfo.getMarketIntent()); - return true; + context.startActivity(promiseAppInfo.getMarketIntent()); + return null; } - boolean result = false; ComponentName componentName = null; if (info instanceof AppInfo) { componentName = ((AppInfo) info).componentName; @@ -83,29 +85,27 @@ public class InfoDropTarget extends UninstallDropTarget { } if (componentName != null) { try { - LauncherAppsCompat.getInstance(launcher) + LauncherAppsCompat.getInstance(context) .showAppDetailsForProfile(componentName, info.user, sourceBounds, opts); - result = true; + return componentName; } catch (SecurityException | ActivityNotFoundException e) { - Toast.makeText(launcher, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); + Toast.makeText(context, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); Log.e(TAG, "Unable to launch settings", e); } } - - if (callback != null) { - sendUninstallResult(launcher, result, componentName, info.user, callback); - } - return result; + return null; } @Override - protected boolean supportsDrop(DragSource source, ItemInfo info) { - return source.supportsAppInfoDropTarget() && 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/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java index 0370777d17..df1eec6610 100644 --- a/src/com/android/launcher3/InstallShortcutReceiver.java +++ b/src/com/android/launcher3/InstallShortcutReceiver.java @@ -27,7 +27,9 @@ import android.content.pm.LauncherActivityInfo; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.os.Handler; import android.os.Looper; +import android.os.Message; import android.os.Parcelable; import android.os.Process; import android.os.UserHandle; @@ -61,6 +63,9 @@ import java.util.Set; public class InstallShortcutReceiver extends BroadcastReceiver { + private static final int MSG_ADD_TO_QUEUE = 1; + private static final int MSG_FLUSH_QUEUE = 2; + public static final int FLAG_ACTIVITY_PAUSED = 1; public static final int FLAG_LOADER_RUNNING = 2; public static final int FLAG_DRAG_AND_DROP = 4; @@ -93,73 +98,98 @@ public class InstallShortcutReceiver extends BroadcastReceiver { public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450; public static final int NEW_SHORTCUT_STAGGER_DELAY = 85; - private static final Object sLock = new Object(); + private static final Handler sHandler = new Handler(LauncherModel.getWorkerLooper()) { - private static void addToInstallQueue( - SharedPreferences sharedPrefs, PendingInstallShortcutInfo info) { - synchronized(sLock) { - String encoded = info.encodeToString(); - if (encoded != null) { - Set strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null); - strings = (strings != null) ? new HashSet<>(strings) : new HashSet(1); - strings.add(encoded); - sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply(); + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_ADD_TO_QUEUE: { + Pair pair = + (Pair) msg.obj; + String encoded = pair.second.encodeToString(); + SharedPreferences prefs = Utilities.getPrefs(pair.first); + Set strings = prefs.getStringSet(APPS_PENDING_INSTALL, null); + strings = (strings != null) ? new HashSet<>(strings) : new HashSet(1); + strings.add(encoded); + prefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply(); + return; + } + case MSG_FLUSH_QUEUE: { + Context context = (Context) msg.obj; + LauncherModel model = LauncherAppState.getInstance(context).getModel(); + if (model.getCallback() == null) { + // Launcher not loaded + return; + } + + ArrayList> installQueue = new ArrayList<>(); + SharedPreferences prefs = Utilities.getPrefs(context); + Set strings = prefs.getStringSet(APPS_PENDING_INSTALL, null); + if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings); + if (strings == null) { + return; + } + + LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context); + for (String encoded : strings) { + PendingInstallShortcutInfo info = decode(encoded, context); + if (info == null) { + continue; + } + + String pkg = getIntentPackage(info.launchIntent); + if (!TextUtils.isEmpty(pkg) + && !launcherApps.isPackageEnabledForProfile(pkg, info.user)) { + if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: " + + info.launchIntent); + continue; + } + + // Generate a shortcut info to add into the model + installQueue.add(info.getItemInfo()); + } + prefs.edit().remove(APPS_PENDING_INSTALL).apply(); + if (!installQueue.isEmpty()) { + model.addAndBindAddedWorkspaceItems(installQueue); + } + return; + } } } - } + }; public static void removeFromInstallQueue(Context context, HashSet packageNames, UserHandle user) { if (packageNames.isEmpty()) { return; } + Preconditions.assertWorkerThread(); + SharedPreferences sp = Utilities.getPrefs(context); - synchronized(sLock) { - Set strings = sp.getStringSet(APPS_PENDING_INSTALL, null); - if (DBG) { - Log.d(TAG, "APPS_PENDING_INSTALL: " + strings - + ", removing packages: " + packageNames); - } - if (Utilities.isEmpty(strings)) { - return; - } - Set newStrings = new HashSet<>(strings); - Iterator newStringsIter = newStrings.iterator(); - while (newStringsIter.hasNext()) { - String encoded = newStringsIter.next(); - try { - Decoder decoder = new Decoder(encoded, context); - if (packageNames.contains(getIntentPackage(decoder.launcherIntent)) && - user.equals(decoder.user)) { - newStringsIter.remove(); - } - } catch (JSONException | URISyntaxException e) { - Log.d(TAG, "Exception reading shortcut to add: " + e); + Set strings = sp.getStringSet(APPS_PENDING_INSTALL, null); + if (DBG) { + Log.d(TAG, "APPS_PENDING_INSTALL: " + strings + + ", removing packages: " + packageNames); + } + if (Utilities.isEmpty(strings)) { + return; + } + Set newStrings = new HashSet<>(strings); + Iterator newStringsIter = newStrings.iterator(); + while (newStringsIter.hasNext()) { + String encoded = newStringsIter.next(); + try { + Decoder decoder = new Decoder(encoded, context); + if (packageNames.contains(getIntentPackage(decoder.launcherIntent)) && + user.equals(decoder.user)) { newStringsIter.remove(); } + } catch (JSONException | URISyntaxException e) { + Log.d(TAG, "Exception reading shortcut to add: " + e); + newStringsIter.remove(); } - sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply(); - } - } - - private static ArrayList getAndClearInstallQueue(Context context) { - SharedPreferences sharedPrefs = Utilities.getPrefs(context); - synchronized(sLock) { - ArrayList infos = new ArrayList<>(); - Set strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null); - if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings); - if (strings == null) { - return infos; - } - for (String encoded : strings) { - PendingInstallShortcutInfo info = decode(encoded, context); - if (info != null) { - infos.add(info); - } - } - sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, new HashSet()).apply(); - return infos; } + sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply(); } public void onReceive(Context context, Intent data) { @@ -256,7 +286,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) { // Queue the item up for adding if launcher has not loaded properly yet - addToInstallQueue(Utilities.getPrefs(context), info); + Message.obtain(sHandler, MSG_ADD_TO_QUEUE, Pair.create(context, info)).sendToTarget(); flushInstallQueue(context); } @@ -269,17 +299,10 @@ public class InstallShortcutReceiver extends BroadcastReceiver { } static void flushInstallQueue(Context context) { - LauncherModel model = LauncherAppState.getInstance(context).getModel(); - boolean launcherNotLoaded = model.getCallback() == null; - if (sInstallQueueDisabledFlags != 0 || launcherNotLoaded) { + if (sInstallQueueDisabledFlags != 0) { return; } - - ArrayList items = getAndClearInstallQueue(context); - if (!items.isEmpty()) { - model.addAndBindAddedWorkspaceItems( - new LazyShortcutsProvider(context.getApplicationContext(), items)); - } + Message.obtain(sHandler, MSG_FLUSH_QUEUE, context.getApplicationContext()).sendToTarget(); } /** @@ -601,42 +624,6 @@ public class InstallShortcutReceiver extends BroadcastReceiver { return new PendingInstallShortcutInfo(info, original.mContext); } - private static class LazyShortcutsProvider extends Provider>> { - - private final Context mContext; - private final ArrayList mPendingItems; - - public LazyShortcutsProvider(Context context, ArrayList items) { - mContext = context; - mPendingItems = items; - } - - /** - * This must be called on the background thread as this requires multiple calls to - * packageManager and icon cache. - */ - @Override - public ArrayList> get() { - Preconditions.assertNonUiThread(); - ArrayList> installQueue = new ArrayList<>(); - LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mContext); - for (PendingInstallShortcutInfo pendingInfo : mPendingItems) { - // If the intent specifies a package, make sure the package exists - String packageName = getIntentPackage(pendingInfo.launchIntent); - if (!TextUtils.isEmpty(packageName) && !launcherApps.isPackageEnabledForProfile( - packageName, pendingInfo.user)) { - if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: " - + pendingInfo.launchIntent); - continue; - } - - // Generate a shortcut info to add into the model - installQueue.add(pendingInfo.getItemInfo()); - } - return installQueue; - } - } - private static ShortcutInfo createShortcutInfo(Intent data, LauncherAppState app) { Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java index 1e020e2587..1c4e88b5b0 100644 --- a/src/com/android/launcher3/ItemInfoWithIcon.java +++ b/src/com/android/launcher3/ItemInfoWithIcon.java @@ -33,11 +33,70 @@ public abstract class ItemInfoWithIcon extends ItemInfo { */ 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; + + /** + * 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 32af059008..75968ae2bc 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -16,6 +16,22 @@ package com.android.launcher3; +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; + import android.Manifest; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -41,7 +57,6 @@ import android.content.IntentFilter; 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; @@ -50,11 +65,9 @@ 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; -import android.os.SystemClock; -import android.os.Trace; import android.os.UserHandle; import android.support.annotation.Nullable; import android.text.Selection; @@ -62,6 +75,7 @@ import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.text.method.TextKeyListener; import android.util.Log; +import android.util.SparseArray; import android.view.Display; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; @@ -73,10 +87,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.View.OnLongClickListener; import android.view.ViewGroup; -import android.view.ViewTreeObserver; -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; @@ -87,7 +98,6 @@ import com.android.launcher3.Workspace.ItemOperator; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.allapps.AllAppsTransitionController; -import com.android.launcher3.anim.AnimationLayerSet; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.LauncherAppsCompatVO; @@ -98,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.ExtractedColors; import com.android.launcher3.dynamicui.WallpaperColorInfo; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; @@ -107,65 +116,60 @@ import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.logging.FileLog; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.model.ModelWriter; -import com.android.launcher3.model.PackageItemInfo; -import com.android.launcher3.model.WidgetItem; 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.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Action; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType; +import com.android.launcher3.userevent.nano.LauncherLogProto.Target; import com.android.launcher3.util.ActivityResultInfo; -import com.android.launcher3.util.RunnableWithId; import com.android.launcher3.util.ComponentKey; -import com.android.launcher3.util.ComponentKeyMapper; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PackageUserKey; 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; +import com.android.launcher3.util.UiThreadHelper; import com.android.launcher3.util.ViewOnDrawExecutor; import com.android.launcher3.widget.PendingAddShortcutInfo; import com.android.launcher3.widget.PendingAddWidgetInfo; import com.android.launcher3.widget.WidgetAddFlowHandler; import com.android.launcher3.widget.WidgetHostViewLoader; -import com.android.launcher3.widget.WidgetsContainerView; - +import com.android.launcher3.widget.WidgetListRowEntry; +import com.android.launcher3.widget.WidgetsFullSheet; +import com.android.launcher3.widget.custom.CustomWidgetParser; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.Executor; -import static com.android.launcher3.util.RunnableWithId.RUNNABLE_ID_BIND_APPS; -import static com.android.launcher3.util.RunnableWithId.RUNNABLE_ID_BIND_WIDGETS; - /** * Default launcher application. */ 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; - static final boolean DEBUG_WIDGETS = false; static final boolean DEBUG_STRICT_MODE = false; - static final boolean DEBUG_RESUME_TIME = false; private static final int REQUEST_CREATE_SHORTCUT = 1; private static final int REQUEST_CREATE_APPWIDGET = 5; @@ -187,11 +191,6 @@ public class Launcher extends BaseActivity */ 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"; @@ -204,19 +203,13 @@ public class Launcher extends BaseActivity private static final String RUNTIME_STATE_PENDING_REQUEST_ARGS = "launcher.request_args"; // Type: ActivityResultInfo private static final String RUNTIME_STATE_PENDING_ACTIVITY_RESULT = "launcher.activity_result"; + // Type: SparseArray + 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 { NONE, WORKSPACE, WORKSPACE_SPRING_LOADED, APPS, APPS_SPRING_LOADED, - WIDGETS, WIDGETS_SPRING_LOADED } - - @Thunk State mState = State.WORKSPACE; - @Thunk LauncherStateTransitionAnimation mStateTransitionAnimation; + private LauncherStateManager mStateManager; private boolean mIsSafeModeEnabled; - public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 500; private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500; // How long to wait before the new-shortcut animation automatically pans the workspace @@ -224,8 +217,6 @@ public class Launcher extends BaseActivity private static final int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5; @Thunk static final int NEW_APPS_ANIMATION_DELAY = 500; - private final ExtractedColors mExtractedColors = new ExtractedColors(); - @Thunk Workspace mWorkspace; private View mLauncherView; @Thunk DragLayer mDragLayer; @@ -242,7 +233,6 @@ public class Launcher extends BaseActivity private ViewGroup mOverviewPanel; private View mAllAppsButton; - private View mWidgetsButton; private DropTargetBar mDropTargetBar; @@ -250,18 +240,10 @@ public class Launcher extends BaseActivity @Thunk AllAppsContainerView mAppsView; AllAppsTransitionController mAllAppsController; - // Main container view and the model for the widget tray screen. - @Thunk WidgetsContainerView mWidgetsView; - // 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; - // We set the state in both onCreate and then onNewIntent in some cases, which causes both - // scroll issues (because the workspace may not have been measured yet) and extra work. - // Instead, just save the state that we need to restore Launcher to, and commit it in onResume. - private State mOnResumeState = State.NONE; - private SpannableStringBuilder mDefaultKeySsb = null; @Thunk boolean mWorkspaceLoading = true; @@ -270,14 +252,14 @@ public class Launcher extends BaseActivity private boolean mOnResumeNeedsLoad; private final ArrayList mBindOnResumeCallbacks = new ArrayList<>(); - private final ArrayList mOnResumeCallbacks = new ArrayList<>(); + private OnResumeCallback mOnResumeCallback; + private ViewOnDrawExecutor mPendingExecutor; private LauncherModel mModel; private ModelWriter mModelWriter; private IconCache mIconCache; private LauncherAccessibilityDelegate mAccessibilityDelegate; - private final Handler mHandler = new Handler(); private boolean mHasFocus = false; private ObjectAnimator mScrimAnimator; @@ -285,45 +267,12 @@ public class Launcher extends BaseActivity 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 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; - private boolean mMoveToDefaultScreenFromNewIntent; - - // This is set to the view that launched the activity that navigated the user away from - // launcher. Since there is no callback for when the activity has finished launching, enable - // the press state and keep this reference to reset the press state when we return to launcher. - private BubbleTextView mWaitingForResume; - - protected static final HashMap sCustomAppWidgets = - new HashMap<>(); - - static { - if (TestingUtils.ENABLE_CUSTOM_WIDGET_TEST) { - TestingUtils.addDummyWidget(sCustomAppWidgets); - } - } - - // 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; - - @Thunk final Runnable mBuildLayersRunnable = new Runnable() { - public void run() { - if (mWorkspace != null) { - mWorkspace.buildPageHardwareLayers(); - } - } - }; - // Activity result which needs to be processed after workspace has loaded. private ActivityResultInfo mPendingActivityResult; /** @@ -336,15 +285,7 @@ public class Launcher extends BaseActivity 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; @@ -364,9 +305,7 @@ public class Launcher extends BaseActivity .penaltyDeath() .build()); } - if (LauncherAppState.PROFILE_STARTUP) { - Trace.beginSection("Launcher-onCreate"); - } + TraceHelper.beginSection("Launcher-onCreate"); if (mLauncherCallbacks != null) { mLauncherCallbacks.preOnCreate(); @@ -377,6 +316,7 @@ public class Launcher extends BaseActivity overrideTheme(wallpaperColorInfo.isDark(), wallpaperColorInfo.supportsDarkText()); super.onCreate(savedInstanceState); + TraceHelper.partitionSection("Launcher-onCreate", "super call"); LauncherAppState app = LauncherAppState.getInstance(this); @@ -399,14 +339,11 @@ public class Launcher extends BaseActivity mDragController = new DragController(this); mAllAppsController = new AllAppsTransitionController(this); - mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, mAllAppsController); + mStateManager = new LauncherStateManager(this, mAllAppsController); mAppWidgetManager = AppWidgetManagerCompat.getInstance(this); mAppWidgetHost = new LauncherAppWidgetHost(this); - if (Utilities.ATLEAST_MARSHMALLOW) { - mAppWidgetHost.addProviderChangeListener(this); - } mAppWidgetHost.startListening(); // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here, @@ -418,21 +355,11 @@ public class Launcher extends BaseActivity setupViews(); mDeviceProfile.layout(this, false /* notifyListeners */); - loadExtractedColorsAndColorItems(); mPopupDataProvider = new PopupDataProvider(this); - ((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE)) - .addAccessibilityStateChangeListener(this); - - lockAllApps(); - restoreState(savedInstanceState); - if (LauncherAppState.PROFILE_STARTUP) { - Trace.endSection(); - } - // We only load the page synchronously if the user rotates (or triggers a // configuration change) while launcher is in the foreground int currentScreen = PagedView.INVALID_RESTORE_PAGE; @@ -470,7 +397,8 @@ public class Launcher extends BaseActivity // 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); @@ -487,6 +415,8 @@ public class Launcher extends BaseActivity if (mLauncherCallbacks != null) { mLauncherCallbacks.onCreate(savedInstanceState); } + + TraceHelper.endSection("Launcher-onCreate"); } @Override @@ -494,6 +424,10 @@ public class Launcher extends BaseActivity recreate(); } + public LauncherStateManager getStateManager() { + return mStateManager; + } + protected void overrideTheme(boolean isDark, boolean supportsDarkText) { if (isDark) { setTheme(R.style.LauncherThemeDark); @@ -507,16 +441,6 @@ public class Launcher extends BaseActivity return mLauncherView.findViewById(id); } - @Override - public void onExtractedColorsChanged() { - loadExtractedColorsAndColorItems(); - mExtractedColors.notifyChange(); - } - - public ExtractedColors getExtractedColors() { - return mExtractedColors; - } - @Override public void onAppWidgetHostReset() { if (mAppWidgetHost != null) { @@ -524,24 +448,8 @@ public class Launcher extends BaseActivity } } - private void loadExtractedColorsAndColorItems() { - // TODO: do this in pre-N as well, once the extraction part is complete. - if (Utilities.ATLEAST_NOUGAT) { - mExtractedColors.load(this); - mHotseat.updateColor(mExtractedColors, !mPaused); - mWorkspace.getPageIndicator().updateColor(mExtractedColors); - } - } - private LauncherCallbacks mLauncherCallbacks; - public void onPostCreate(Bundle savedInstanceState) { - super.onPostCreate(savedInstanceState); - if (mLauncherCallbacks != null) { - mLauncherCallbacks.onPostCreate(savedInstanceState); - } - } - public void onInsetsChanged(Rect insets) { mDeviceProfile.updateInsets(insets); mDeviceProfile.layout(this, true /* notifyListeners */); @@ -569,44 +477,6 @@ public class Launcher extends BaseActivity } } - /** To be overridden by subclasses to hint to Launcher that we have custom content */ - protected boolean hasCustomContentToLeft() { - if (mLauncherCallbacks != null) { - return mLauncherCallbacks.hasCustomContentToLeft(); - } - return false; - } - - /** - * To be overridden by subclasses to populate the custom content container and call - * {@link #addToCustomContentPage}. This will only be invoked if - * {@link #hasCustomContentToLeft()} is {@code true}. - */ - protected void populateCustomContentContainer() { - if (mLauncherCallbacks != null) { - mLauncherCallbacks.populateCustomContentContainer(); - } - } - - /** - * Invoked by subclasses to signal a change to the {@link #addToCustomContentPage} value to - * ensure the custom content page is added or removed if necessary. - */ - protected void invalidateHasCustomContentToLeft() { - if (mWorkspace == null || mWorkspace.getScreenOrder().isEmpty()) { - // Not bound yet, wait for bindScreens to be called. - return; - } - - if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) { - // Create the custom content page and call the subclass to populate it. - mWorkspace.createCustomContentContainer(); - populateCustomContentContainer(); - } else if (mWorkspace.hasCustomContent() && !hasCustomContentToLeft()) { - mWorkspace.removeCustomContentPage(); - } - } - public boolean isDraggingEnabled() { // We prevent dragging when we are loading the workspace as it is possible to pick up a view // that is subsequently removed from the workspace in startBinding(). @@ -689,8 +559,7 @@ public class Launcher extends BaseActivity Runnable exitSpringLoaded = new Runnable() { @Override public void run() { - exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), - EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); + mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); } }; @@ -710,11 +579,11 @@ public class Launcher extends BaseActivity } 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; } @@ -742,7 +611,7 @@ public class Launcher extends BaseActivity final Runnable onComplete = new Runnable() { @Override public void run() { - exitSpringLoadedDragModeDelayed(false, 0, null); + getStateManager().goToState(NORMAL); } }; @@ -806,7 +675,7 @@ public class Launcher extends BaseActivity } } - /** @Override for MNC */ + @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { PendingRequestArgs pendingArgs = mPendingRequestArgs; @@ -870,8 +739,7 @@ public class Launcher extends BaseActivity @Override public void run() { completeAddAppWidget(appWidgetId, requestArgs, layout, null); - exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), - EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); + mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); } }; } else if (resultCode == RESULT_CANCELED) { @@ -901,6 +769,10 @@ public class Launcher extends BaseActivity mAppWidgetHost.stopListening(); } + if (!mAppLaunchSuccess) { + getUserEventDispatcher().logActionCommand(Action.Command.STOP, + mStateManager.getState().containerType); + } NotificationListener.removeNotificationsChangedListener(); } @@ -943,36 +815,12 @@ public class Launcher extends BaseActivity @Override protected void onResume() { - long startTime = 0; - if (DEBUG_RESUME_TIME) { - startTime = System.currentTimeMillis(); - Log.v(TAG, "Launcher.onResume()"); - } - - if (mLauncherCallbacks != null) { - mLauncherCallbacks.preOnResume(); - } - + TraceHelper.beginSection("ON_RESUME"); super.onResume(); + TraceHelper.partitionSection("ON_RESUME", "superCall"); + + mAppLaunchSuccess = false; getUserEventDispatcher().resetElapsedSessionMillis(); - - // Restore the previous launcher state - if (mOnResumeState == State.WORKSPACE) { - showWorkspace(false); - } else if (mOnResumeState == State.APPS) { - boolean launchedFromApp = (mWaitingForResume != null); - // Don't update the predicted apps if the user is returning to launcher in the apps - // view after launching an app, as they may be depending on the UI to be static to - // switch to another app, otherwise, if it was - showAppsView(false /* animated */, !launchedFromApp /* updatePredictedApps */); - } else if (mOnResumeState == State.WIDGETS) { - showWidgetsView(false, false); - } - if (mOnResumeState != State.APPS) { - tryAndUpdatePredictedApps(); - } - mOnResumeState = State.NONE; - mPaused = false; if (mOnResumeNeedsLoad) { setWorkspaceLoading(true); @@ -982,64 +830,13 @@ public class Launcher extends BaseActivity if (mBindOnResumeCallbacks.size() > 0) { // We might have postponed some bind calls until onResume (see waitUntilResume) -- // execute them here - long startTimeCallbacks = 0; - if (DEBUG_RESUME_TIME) { - startTimeCallbacks = System.currentTimeMillis(); - } - for (int i = 0; i < mBindOnResumeCallbacks.size(); i++) { mBindOnResumeCallbacks.get(i).run(); } mBindOnResumeCallbacks.clear(); - if (DEBUG_RESUME_TIME) { - Log.d(TAG, "Time spent processing callbacks in onResume: " + - (System.currentTimeMillis() - startTimeCallbacks)); - } - } - if (mOnResumeCallbacks.size() > 0) { - for (int i = 0; i < mOnResumeCallbacks.size(); i++) { - mOnResumeCallbacks.get(i).run(); - } - mOnResumeCallbacks.clear(); } - // Reset the pressed state of icons that were locked in the press state while activities - // were launching - if (mWaitingForResume != null) { - // Resets the previous workspace icon press state - mWaitingForResume.setStayPressed(false); - } - - // It is possible that widgets can receive updates while launcher is not in the foreground. - // Consequently, the widgets will be inflated in the orientation of the foreground activity - // (framework issue). On resuming, we ensure that any widgets are inflated for the current - // orientation. - if (!isWorkspaceLoading()) { - getWorkspace().reinflateWidgetsIfNecessary(); - } - - if (DEBUG_RESUME_TIME) { - Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime)); - } - - // We want to suppress callbacks about CustomContent being shown if we have just received - // onNewIntent while the user was present within launcher. In that case, we post a call - // to move the user to the main screen (which will occur after onResume). We don't want to - // have onHide (from onPause), then onShow, then onHide again, which we get if we don't - // suppress here. - if (mWorkspace.getCustomContentCallbacks() != null - && !mMoveToDefaultScreenFromNewIntent) { - // If we are resuming and the custom content is the current page, we call onShow(). - // It is also possible that onShow will instead be called slightly after first layout - // if PagedView#setRestorePage was set to the custom content page in onCreate(). - if (mWorkspace.isOnOrMovingToCustomContent()) { - mWorkspace.getCustomContentCallbacks().onShow(true); - } - } - mMoveToDefaultScreenFromNewIntent = false; - updateInteraction(Workspace.State.NORMAL, mWorkspace.getState()); - mWorkspace.onResume(); - + setOnResumeCallback(null); // Process any items that were added while Launcher was away. InstallShortcutReceiver.disableAndFlushInstallQueue( InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED, this); @@ -1054,6 +851,9 @@ public class Launcher extends BaseActivity mLauncherCallbacks.onResume(); } + clearTypedText(); + + TraceHelper.endSection("ON_RESUME"); } @Override @@ -1066,32 +866,11 @@ public class Launcher extends BaseActivity mDragController.cancelDrag(); mDragController.resetLastGestureUpTime(); - // We call onHide() aggressively. The custom content callbacks should be able to - // debounce excess onHide calls. - if (mWorkspace.getCustomContentCallbacks() != null) { - mWorkspace.getCustomContentCallbacks().onHide(); - } - if (mLauncherCallbacks != null) { mLauncherCallbacks.onPause(); } } - public interface CustomContentCallbacks { - // Custom content is completely shown. {@code fromResume} indicates whether this was caused - // by a onResume or by scrolling otherwise. - void onShow(boolean fromResume); - - // Custom content is completely hidden - void onHide(); - - // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing). - void onScrollProgressChanged(float progress); - - // Indicates whether the user is allowed to scroll away from the custom content. - boolean isScrollingAllowed(); - } - public interface LauncherOverlay { /** @@ -1130,7 +909,7 @@ public class Launcher extends BaseActivity } } - protected boolean hasSettings() { + public boolean hasSettings() { if (mLauncherCallbacks != null) { return mLauncherCallbacks.hasSettings(); } else { @@ -1141,16 +920,6 @@ public class Launcher extends BaseActivity } } - public void addToCustomContentPage(View customContent, - CustomContentCallbacks callbacks, String description) { - mWorkspace.addToCustomContentPage(customContent, callbacks, description); - } - - // The custom content needs to offset its content to account for the QSB - public int getTopOffsetForCustomContent() { - return mWorkspace.getPaddingTop(); - } - @Override public Object onRetainNonConfigurationInstance() { // Flag the loader to stop early before switching @@ -1206,29 +975,6 @@ public class Launcher extends BaseActivity 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 (!isOnCustomContent() && !mDragController.isDragging()) { - // Close any open floating view - AbstractFloatingView.closeAllOpenViews(this); - - // Stop resizing any widgets - mWorkspace.exitWidgetResizeMode(); - - // 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(); } @@ -1240,6 +986,10 @@ public class Launcher extends BaseActivity Selection.setSelection(mDefaultKeySsb, 0); } + public boolean isInState(LauncherState state) { + return mStateManager.getState() == state; + } + /** * Restores the previous state, if it exists. * @@ -1250,12 +1000,11 @@ public class Launcher extends BaseActivity 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 || state == State.WIDGETS) { - mOnResumeState = state; + 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); @@ -1264,6 +1013,12 @@ public class Launcher extends BaseActivity } mPendingActivityResult = savedState.getParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT); + + SparseArray widgetsState = + savedState.getSparseParcelableArray(RUNTIME_STATE_WIDGET_PANEL); + if (widgetsState != null) { + WidgetsFullSheet.show(this, false).restoreHierarchyState(widgetsState); + } } /** @@ -1274,6 +1029,7 @@ public class Launcher extends BaseActivity 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 @@ -1288,9 +1044,6 @@ public class Launcher extends BaseActivity mHotseat.setOnLongClickListener(this); } - // Setup the overview panel - setupOverviewPanel(); - // Setup the workspace mWorkspace.setHapticFeedbackEnabled(false); mWorkspace.setOnLongClickListener(this); @@ -1304,9 +1057,8 @@ public class Launcher extends BaseActivity // Get the search/delete/uninstall bar mDropTargetBar = mDragLayer.findViewById(R.id.drop_target_bar); - // Setup Apps and Widgets - mAppsView = (AllAppsContainerView) findViewById(R.id.apps_view); - mWidgetsView = (WidgetsContainerView) findViewById(R.id.widgets_view); + // Setup Apps + mAppsView = findViewById(R.id.apps_view); // Setup the drag controller (drop targets have to be added in reverse order in priority) mDragController.setMoveTarget(mWorkspace); @@ -1314,48 +1066,6 @@ public class Launcher extends BaseActivity mDropTargetBar.setup(mDragController); mAllAppsController.setupViews(mAppsView, mHotseat, mWorkspace); - - if (TestingUtils.MEMORY_DUMP_ENABLED) { - TestingUtils.addWeightWatcher(this); - } - } - - private void setupOverviewPanel() { - mOverviewPanel = (ViewGroup) 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 - mWidgetsButton = findViewById(R.id.widget_button); - new OverviewButtonClickListener(ControlType.WIDGETS_BUTTON) { - @Override - public void handleViewClick(View view) { - onClickAddWidgetButton(view); - } - }.attachTo(mWidgetsButton); - - // 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); } /** @@ -1366,14 +1076,6 @@ public class Launcher extends BaseActivity mAllAppsButton = allAppsButton; } - public View getStartViewForAllAppsRevealAnimation() { - return FeatureFlags.NO_ALL_APPS_ICON ? mWorkspace.getPageIndicator() : mAllAppsButton; - } - - public View getWidgetsButton() { - return mWidgetsButton; - } - /** * Creates a view representing a shortcut. * @@ -1449,7 +1151,7 @@ public class Launcher extends BaseActivity // If appropriate, either create a folder or add to an existing folder if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0, - true, null, null)) { + true, null)) { return; } DragObject dragObject = new DragObject(); @@ -1502,17 +1204,13 @@ public class Launcher extends BaseActivity appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(appWidgetId); } - if (appWidgetInfo.isCustomWidget) { - appWidgetId = LauncherAppWidgetInfo.CUSTOM_WIDGET_ID; - } - LauncherAppWidgetInfo launcherInfo; launcherInfo = new LauncherAppWidgetInfo(appWidgetId, appWidgetInfo.provider); launcherInfo.spanX = itemInfo.spanX; launcherInfo.spanY = itemInfo.spanY; launcherInfo.minSpanX = itemInfo.minSpanX; launcherInfo.minSpanY = itemInfo.minSpanY; - launcherInfo.user = appWidgetInfo.getUser(); + launcherInfo.user = appWidgetInfo.getProfile(); getModelWriter().addItemToDatabase(launcherInfo, itemInfo.container, itemInfo.screenId, itemInfo.cellX, itemInfo.cellY); @@ -1538,15 +1236,10 @@ public class Launcher extends BaseActivity public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (Intent.ACTION_SCREEN_OFF.equals(action)) { - mDragLayer.clearResizeFrame(); - // Reset AllApps to its initial state only if we are not in the middle of // processing a multi-step drop - if (mAppsView != null && mWidgetsView != null && mPendingRequestArgs == null) { - if (!showWorkspace(false)) { - // If we are already on the workspace, then manually reset all apps - mAppsView.reset(); - } + if (mAppsView != null && mPendingRequestArgs == null) { + mStateManager.goToState(NORMAL); } mShouldFadeInScrim = true; } else if (Intent.ACTION_USER_PRESENT.equals(action)) { @@ -1564,9 +1257,9 @@ public class Launcher extends BaseActivity mWorkspace.updateIconBadges(updatedBadges); mAppsView.updateIconBadges(updatedBadges); - PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(Launcher.this); - if (popup != null) { - popup.updateNotificationHeader(updatedBadges); + BaseActionPopup popup = BaseActionPopup.getOpen(Launcher.this); + if (popup instanceof PopupContainerWithArrow) { + ((PopupContainerWithArrow) popup).updateNotificationHeader(updatedBadges); } } }; @@ -1594,44 +1287,6 @@ public class Launcher extends BaseActivity } } - public void onWindowVisibilityChanged(int visibility) { - // The following code used to be in onResume, but it turns out onResume is called when - // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged - // is a more appropriate event to handle - if (visibility == View.VISIBLE) { - if (!mWorkspaceLoading) { - final ViewTreeObserver observer = mWorkspace.getViewTreeObserver(); - // We want to let Launcher draw itself at least once before we force it to build - // layers on all the workspace pages, so that transitioning to Launcher from other - // apps is nice and speedy. - observer.addOnDrawListener(new ViewTreeObserver.OnDrawListener() { - private boolean mStarted = false; - public void onDraw() { - if (mStarted) return; - mStarted = true; - // We delay the layer building a bit in order to give - // other message processing a time to run. In particular - // this avoids a delay in hiding the IME if it was - // currently shown, because doing that may involve - // some communication back with the app. - mWorkspace.postDelayed(mBuildLayersRunnable, 500); - final ViewTreeObserver.OnDrawListener listener = this; - mWorkspace.post(new Runnable() { - public void run() { - if (mWorkspace != null && - mWorkspace.getViewTreeObserver() != null) { - mWorkspace.getViewTreeObserver(). - removeOnDrawListener(listener); - } - } - }); - } - }); - } - clearTypedText(); - } - } - public DragLayer getDragLayer() { return mDragLayer; } @@ -1640,10 +1295,6 @@ public class Launcher extends BaseActivity return mAppsView; } - public WidgetsContainerView getWidgetsView() { - return mWidgetsView; - } - public Workspace getWorkspace() { return mWorkspace; } @@ -1680,10 +1331,7 @@ public class Launcher extends BaseActivity @Override protected void onNewIntent(Intent intent) { - long startTime = 0; - if (DEBUG_RESUME_TIME) { - startTime = System.currentTimeMillis(); - } + TraceHelper.beginSection("NEW_INTENT"); super.onNewIntent(intent); boolean alreadyOnHome = mHasFocus && ((intent.getFlags() & @@ -1691,8 +1339,8 @@ public class Launcher extends BaseActivity != 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()); if (isActionMain) { @@ -1704,39 +1352,22 @@ public class Launcher extends BaseActivity // Note: There should be at most one log per method call. This is enforced implicitly // by using if-else statements. UserEventDispatcher ued = getUserEventDispatcher(); - - // TODO: Log this case. - mWorkspace.exitWidgetResizeMode(); - AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(this); - if (topOpenView instanceof PopupContainerWithArrow) { - ued.logActionCommand(Action.Command.HOME_INTENT, - topOpenView.getExtendedTouchView(), ContainerType.DEEPSHORTCUTS); - } else if (topOpenView instanceof Folder) { - ued.logActionCommand(Action.Command.HOME_INTENT, - ((Folder) topOpenView).getFolderIcon(), ContainerType.FOLDER); + if (topOpenView != null) { + topOpenView.logActionCommand(Action.Command.HOME_INTENT); } else if (alreadyOnHome) { - ued.logActionCommand(Action.Command.HOME_INTENT, - mWorkspace.getState().containerType, mWorkspace.getCurrentPage()); + Target target = newContainerTarget(mStateManager.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); - exitSpringLoadedDragMode(); - - // If we are already on home, then just animate back to the workspace, - // otherwise, just wait until onResume to set the state back to Workspace - if (alreadyOnHome) { - showWorkspace(true); - } else { - mOnResumeState = State.WORKSPACE; - } + mStateManager.goToState(NORMAL, alreadyOnHome /* animated */); final View v = getWindow().peekDecorView(); if (v != null && v.getWindowToken() != null) { - InputMethodManager imm = (InputMethodManager) getSystemService( - INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(v.getWindowToken(), 0); + UiThreadHelper.hideKeyboardAsync(this, v.getWindowToken()); } // Reset the apps view @@ -1744,11 +1375,6 @@ public class Launcher extends BaseActivity mAppsView.reset(); } - // Reset the widgets view - if (!alreadyOnHome && mWidgetsView != null) { - mWidgetsView.scrollToTop(); - } - if (mLauncherCallbacks != null) { mLauncherCallbacks.onHomeIntent(); } @@ -1763,29 +1389,20 @@ public class Launcher extends BaseActivity // as slow logic in the callbacks eat into the time the scroller expects for the snapToPage // animation. if (isActionMain) { - boolean callbackAllowsMoveToDefaultScreen = - mLauncherCallbacks == null || mLauncherCallbacks - .shouldMoveToDefaultScreenOnHomeIntent(); - if (shouldMoveToDefaultScreen && !mWorkspace.isTouchActive() - && callbackAllowsMoveToDefaultScreen) { + if (shouldMoveToDefaultScreen && !mWorkspace.isTouchActive()) { - // We use this flag to suppress noisy callbacks above custom content state - // from onResume. - mMoveToDefaultScreenFromNewIntent = true; mWorkspace.post(new Runnable() { @Override public void run() { if (mWorkspace != null) { - mWorkspace.moveToDefaultScreen(true); + mWorkspace.moveToDefaultScreen(); } } }); } } - if (DEBUG_RESUME_TIME) { - Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime)); - } + TraceHelper.endSection("NEW_INTENT"); } @Override @@ -1799,13 +1416,22 @@ public class Launcher extends BaseActivity @Override protected void onSaveInstanceState(Bundle outState) { if (mWorkspace.getChildCount() > 0) { - outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, - mWorkspace.getCurrentPageOffsetFromCustomContent()); + outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getNextPage()); } - super.onSaveInstanceState(outState); + outState.putInt(RUNTIME_STATE, mStateManager.getState().ordinal); + + + AbstractFloatingView widgets = AbstractFloatingView + .getOpenView(this, AbstractFloatingView.TYPE_WIDGETS_FULL_SHEET); + if (widgets != null) { + SparseArray widgetsState = new SparseArray<>(); + widgets.saveHierarchyState(widgetsState); + outState.putSparseParcelableArray(RUNTIME_STATE_WIDGET_PANEL, widgetsState); + } else { + outState.remove(RUNTIME_STATE_WIDGET_PANEL); + } - outState.putInt(RUNTIME_STATE, mState.ordinal()); // We close any open folders and shortcut containers since they will not be re-opened, // and we need to make sure this state is reflected. AbstractFloatingView.closeAllOpenViews(this, false); @@ -1817,6 +1443,8 @@ public class Launcher extends BaseActivity outState.putParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT, mPendingActivityResult); } + super.onSaveInstanceState(outState); + if (mLauncherCallbacks != null) { mLauncherCallbacks.onSaveInstanceState(outState); } @@ -1827,7 +1455,6 @@ public class Launcher extends BaseActivity super.onDestroy(); unregisterReceiver(mReceiver); - mWorkspace.removeCallbacks(mBuildLayersRunnable); mWorkspace.removeFolderListeners(); // Stop callbacks from LauncherModel @@ -1850,10 +1477,6 @@ public class Launcher extends BaseActivity mAppWidgetHost = null; TextKeyListener.getInstance().release(); - - ((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE)) - .removeAccessibilityStateChangeListener(this); - WallpaperColorInfo.getInstance(this).setOnThemeChangeListener(null); LauncherAnimUtils.onDestroyActivity(); @@ -1913,7 +1536,7 @@ public class Launcher extends BaseActivity } // We need to show the workspace after starting the search - showWorkspace(true); + mStateManager.goToState(NORMAL); } /** @@ -1956,10 +1579,6 @@ public class Launcher extends BaseActivity } } - public boolean isOnCustomContent() { - return mWorkspace.isOnOrMovingToCustomContent(); - } - @Override public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); @@ -1985,25 +1604,11 @@ public class Launcher extends BaseActivity } private void setWorkspaceLoading(boolean value) { - boolean isLocked = isWorkspaceLocked(); mWorkspaceLoading = value; - if (isLocked != isWorkspaceLocked()) { - onWorkspaceLockedChanged(); - } } public void setWaitingForResult(PendingRequestArgs args) { - boolean isLocked = isWorkspaceLocked(); mPendingRequestArgs = args; - if (isLocked != isWorkspaceLocked()) { - onWorkspaceLockedChanged(); - } - } - - protected void onWorkspaceLockedChanged() { - if (mLauncherCallbacks != null) { - mLauncherCallbacks.onWorkspaceLockedChanged(); - } } void addAppWidgetFromDropImpl(int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget, @@ -2023,8 +1628,7 @@ public class Launcher extends BaseActivity @Override public void run() { // Exit spring loaded mode if necessary after adding the widget - exitSpringLoadedDragModeDelayed(true, EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, - null); + mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); } }; completeAddAppWidget(appWidgetId, info, boundWidget, addFlowHandler.getProviderInfo(this)); @@ -2032,12 +1636,6 @@ public class Launcher extends BaseActivity } } - protected void moveToCustomContentScreen(boolean animate) { - // Close any folders that may be open. - AbstractFloatingView.closeAllOpenViews(this, animate); - mWorkspace.moveToCustomContentScreen(animate); - } - public void addPendingItem(PendingAddItemInfo info, long container, long screenId, int[] cell, int spanX, int spanY) { info.container = container; @@ -2078,7 +1676,7 @@ public class Launcher extends BaseActivity */ private void addAppWidgetFromDrop(PendingAddWidgetInfo info) { AppWidgetHostView hostView = info.boundWidget; - int appWidgetId; + final int appWidgetId; WidgetAddFlowHandler addFlowHandler = info.getHandler(); if (hostView != null) { // In the case where we've prebound the widget, we remove it from the DragLayer @@ -2095,7 +1693,13 @@ public class Launcher extends BaseActivity } else { // In this case, we either need to start an activity to get permission to bind // the widget, or we need to start an activity to configure the widget, or both. - appWidgetId = getAppWidgetHost().allocateAppWidgetId(); + if (FeatureFlags.ENABLE_CUSTOM_WIDGETS && + info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET) { + appWidgetId = CustomWidgetParser.getWidgetIdForCustomProvider( + this, info.componentName); + } else { + appWidgetId = getAppWidgetHost().allocateAppWidgetId(); + } Bundle options = info.bindOptions; boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed( @@ -2204,31 +1808,11 @@ public class Launcher extends BaseActivity UserEventDispatcher ued = getUserEventDispatcher(); AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this); if (topView != null) { - if (topView.getActiveTextView() != null) { - topView.getActiveTextView().dispatchBackKey(); - } else { - if (topView instanceof PopupContainerWithArrow) { - ued.logActionCommand(Action.Command.BACK, - topView.getExtendedTouchView(), ContainerType.DEEPSHORTCUTS); - } else if (topView instanceof Folder) { - ued.logActionCommand(Action.Command.BACK, - ((Folder) topView).getFolderIcon(), ContainerType.FOLDER); - } - topView.close(true); - } - } else if (isAppsViewVisible()) { - ued.logActionCommand(Action.Command.BACK, ContainerType.ALLAPPS); - showWorkspace(true); - } else if (isWidgetsViewVisible()) { - ued.logActionCommand(Action.Command.BACK, ContainerType.WIDGETS); - showOverviewMode(true); - } else if (mWorkspace.isInOverviewMode()) { - ued.logActionCommand(Action.Command.BACK, ContainerType.OVERVIEW); - showWorkspace(true); + topView.onBackPressed(); + } else if (!isInState(NORMAL)) { + ued.logActionCommand(Action.Command.BACK, mStateManager.getState().containerType); + mStateManager.goToState(NORMAL); } else { - // TODO: Log this case. - mWorkspace.exitWidgetResizeMode(); - // Back button is a no-op here, but give at least some feedback for the button press mWorkspace.showOutlinesTemporarily(); } @@ -2251,23 +1835,23 @@ public class Launcher extends BaseActivity } 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; } @@ -2339,12 +1923,12 @@ public class Launcher extends BaseActivity */ 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 */, true /* updatePredictedApps */); + mStateManager.goToState(ALL_APPS); } else { - showWorkspace(true); + mStateManager.goToState(NORMAL); } } @@ -2377,11 +1961,7 @@ public class Launcher extends BaseActivity private void startMarketIntentForPackage(View v, String packageName) { ItemInfo item = (ItemInfo) v.getTag(); Intent intent = PackageManagerHelper.getMarketIntent(packageName); - boolean success = startActivitySafely(v, intent, item); - if (success && v instanceof BubbleTextView) { - mWaitingForResume = (BubbleTextView) v; - mWaitingForResume.setStayPressed(true); - } + startActivitySafely(v, intent, item); } /** @@ -2399,10 +1979,10 @@ public class Launcher extends BaseActivity // 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 { @@ -2413,10 +1993,10 @@ public class Launcher extends BaseActivity } // 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(); @@ -2451,13 +2031,7 @@ public class Launcher extends BaseActivity if (intent == null) { throw new IllegalArgumentException("Input must have a valid intent"); } - boolean success = startActivitySafely(v, intent, item); - getUserEventDispatcher().logAppLaunch(v, intent); // TODO for discovered apps b/35802115 - - if (success && v instanceof BubbleTextView) { - mWaitingForResume = (BubbleTextView) v; - mWaitingForResume.setStayPressed(true); - } + startActivitySafely(v, intent, item); } /** @@ -2478,19 +2052,6 @@ public class Launcher extends BaseActivity } } - /** - * 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 { - showWidgetsView(true /* animated */, true /* resetPageToZero */); - } - } - /** * Event handler for the wallpaper picker button that appears after a long press * on the home screen. @@ -2524,70 +2085,6 @@ public class Launcher extends BaseActivity } } - /** - * 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); - } - - public void onDragStarted() { - if (isOnCustomContent()) { - // Custom content screen doesn't participate in drag and drop. If on custom - // content screen, move to default. - moveWorkspaceToDefaultScreen(); - } - } - - /** - * Called when the user stops interacting with the launcher. - * This implies that the user is now on the homescreen and is not doing housekeeping. - */ - protected void onInteractionEnd() { - if (mLauncherCallbacks != null) { - mLauncherCallbacks.onInteractionEnd(); - } - } - - /** - * Called when the user starts interacting with the launcher. - * The possible interactions are: - * - open all apps - * - reorder an app shortcut, or a widget - * - open the overview mode. - * This is a good time to stop doing things that only make sense - * when the user is on the homescreen and not doing housekeeping. - */ - protected void onInteractionBegin() { - if (mLauncherCallbacks != null) { - mLauncherCallbacks.onInteractionBegin(); - } - } - - /** Updates the interaction state. */ - public void updateInteraction(Workspace.State fromState, Workspace.State toState) { - // Only update the interacting state if we are transitioning to/from a view with an - // overlay - boolean fromStateWithOverlay = fromState != Workspace.State.NORMAL; - boolean toStateWithOverlay = toState != Workspace.State.NORMAL; - if (toStateWithOverlay) { - onInteractionBegin(); - } else if (fromStateWithOverlay) { - onInteractionEnd(); - } - } - private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info) { try { StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy(); @@ -2664,9 +2161,10 @@ public class Launcher extends BaseActivity } 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). @@ -2696,12 +2194,23 @@ public class Launcher extends BaseActivity LauncherAppsCompat.getInstance(this).startActivityForProfile( intent.getComponent(), user, intent.getSourceBounds(), optsBundle); } - return true; + + if (v instanceof BubbleTextView) { + // This is set to the view that launched the activity that navigated the user away + // from launcher. Since there is no callback for when the activity has finished + // launching, enable the press state and keep this reference to reset the press + // state when we return to launcher. + BubbleTextView btv = (BubbleTextView) v; + btv.setStayPressed(true); + setOnResumeCallback(btv); + } + 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 @@ -2714,18 +2223,18 @@ public class Launcher extends BaseActivity 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); + getStateManager().goToState(OVERVIEW); mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); return true; @@ -2751,7 +2260,7 @@ public class Launcher extends BaseActivity 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); @@ -2762,7 +2271,7 @@ public class Launcher extends BaseActivity getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS, Action.Direction.NONE, ContainerType.WORKSPACE, mWorkspace.getCurrentPage()); - showOverviewMode(true); + getStateManager().goToState(OVERVIEW); } mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); @@ -2801,21 +2310,6 @@ public class Launcher extends BaseActivity } } - /** - * For overridden classes. - */ - public boolean isAllAppsVisible() { - return isAppsViewVisible(); - } - - public boolean isAppsViewVisible() { - return (mState == State.APPS) || (mOnResumeState == State.APPS); - } - - public boolean isWidgetsViewVisible() { - return (mState == State.WIDGETS) || (mOnResumeState == State.WIDGETS); - } - @Override public void onTrimMemory(int level) { super.onTrimMemory(level); @@ -2832,257 +2326,16 @@ public class Launcher extends BaseActivity } } - public boolean showWorkspace(boolean animated) { - return showWorkspace(animated, null); - } - - public boolean showWorkspace(boolean animated, Runnable onCompleteRunnable) { - boolean changed = mState != State.WORKSPACE || - mWorkspace.getState() != Workspace.State.NORMAL; - if (changed || mAllAppsController.isTransitioning()) { - mWorkspace.setVisibility(View.VISIBLE); - mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(), - Workspace.State.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(), - Workspace.State.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. - */ - public void showAppsView(boolean animated, boolean updatePredictedApps) { - markAppsViewShown(); - if (updatePredictedApps) { - tryAndUpdatePredictedApps(); - } - showAppsOrWidgets(State.APPS, animated); - } - - /** - * Shows the widgets view. - */ - void showWidgetsView(boolean animated, boolean resetPageToZero) { - if (LOGD) Log.d(TAG, "showWidgetsView:" + animated + " resetPageToZero:" + resetPageToZero); - if (resetPageToZero) { - mWidgetsView.scrollToTop(); - } - showAppsOrWidgets(State.WIDGETS, animated); - - mWidgetsView.post(new Runnable() { - @Override - public void run() { - mWidgetsView.requestFocus(); - } - }); - } - - /** - * Sets up the transition to show the apps/widgets 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. - private boolean showAppsOrWidgets(State toState, boolean animated) { - if (!(mState == State.WORKSPACE || - mState == State.APPS_SPRING_LOADED || - mState == State.WIDGETS_SPRING_LOADED || - (mState == State.APPS && mAllAppsController.isTransitioning()))) { - return false; - } - if (toState != State.APPS && toState != State.WIDGETS) { - return false; - } - - // This is a safe and supported transition to bypass spring_loaded mode. - if (mExitSpringLoadedModeRunnable != null) { - mHandler.removeCallbacks(mExitSpringLoadedModeRunnable); - mExitSpringLoadedModeRunnable = null; - } - - if (toState == State.APPS) { - mStateTransitionAnimation.startAnimationToAllApps(animated); - } else { - mStateTransitionAnimation.startAnimationToWidgets(animated); - } - - // Change the state *after* we've called all the transition code - setState(toState); - AbstractFloatingView.closeAllOpenViews(this); - - // Send an accessibility event to announce the context change - getWindow().getDecorView() - .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); - return true; - } - - /** - * Updates the workspace and interaction state on state change, and return the animation to this - * new state. - */ - public Animator startWorkspaceStateChangeAnimation(Workspace.State toState, - boolean animated, AnimationLayerSet layerViews) { - Workspace.State fromState = mWorkspace.getState(); - Animator anim = mWorkspace.setStateWithAnimation(toState, animated, layerViews); - updateInteraction(fromState, toState); - return anim; - } - - public void enterSpringLoadedDragMode() { - if (LOGD) Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s", mState.name())); - if (isStateSpringLoaded()) { - return; - } - - mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(), - Workspace.State.SPRING_LOADED, true /* animated */, - null /* onCompleteRunnable */); - setState(State.WORKSPACE_SPRING_LOADED); - } - - public void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay, - final Runnable onCompleteRunnable) { - if (!isStateSpringLoaded()) return; - - if (mExitSpringLoadedModeRunnable != null) { - mHandler.removeCallbacks(mExitSpringLoadedModeRunnable); - } - mExitSpringLoadedModeRunnable = new Runnable() { - @Override - public void run() { - if (successfulDrop) { - // TODO(hyunyoungs): verify if this hack is still needed, if not, delete. - // - // Before we show workspace, hide all apps again because - // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should - // clean up our state transition functions - mWidgetsView.setVisibility(View.GONE); - showWorkspace(true, onCompleteRunnable); - } else { - exitSpringLoadedDragMode(); - } - mExitSpringLoadedModeRunnable = null; - } - }; - mHandler.postDelayed(mExitSpringLoadedModeRunnable, delay); - } - - boolean isStateSpringLoaded() { - return mState == State.WORKSPACE_SPRING_LOADED || mState == State.APPS_SPRING_LOADED - || mState == State.WIDGETS_SPRING_LOADED; - } - - public void exitSpringLoadedDragMode() { - if (mState == State.APPS_SPRING_LOADED) { - showAppsView(true /* animated */, false /* updatePredictedApps */); - } else if (mState == State.WIDGETS_SPRING_LOADED) { - showWidgetsView(true, false); - } else if (mState == State.WORKSPACE_SPRING_LOADED) { - showWorkspace(true); - } - } - - /** - * Updates the set of predicted apps if it hasn't been updated since the last time Launcher was - * resumed. - */ - public void tryAndUpdatePredictedApps() { - if (mLauncherCallbacks != null) { - List> apps = mLauncherCallbacks.getPredictedApps(); - if (apps != null) { - mAppsView.setPredictedApps(apps); - } - } - } - - void lockAllApps() { - // TODO - } - - void unlockAllApps() { - // TODO - } - @Override public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { final boolean result = super.dispatchPopulateAccessibilityEvent(event); final List 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 (mState == State.WIDGETS) { - text.add(getString(R.string.widget_button_text)); - } 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; } @@ -3113,8 +2366,11 @@ public class Launcher extends BaseActivity } } - public void addOnResumeCallback(Runnable run) { - mOnResumeCallbacks.add(run); + public void setOnResumeCallback(OnResumeCallback callback) { + if (mOnResumeCallback != null) { + mOnResumeCallback.onLauncherResume(); + } + mOnResumeCallback = callback; } /** @@ -3173,11 +2429,12 @@ public class Launcher extends BaseActivity * Implementation of the method from LauncherModel.Callbacks. */ public void startBinding() { - if (LauncherAppState.PROFILE_STARTUP) { - Trace.beginSection("Starting page bind"); - } - - AbstractFloatingView.closeAllOpenViews(this); + TraceHelper.beginSection("startBinding"); + // Floating panels (except the full widget sheet) are associated with individual icons. If + // we are starting a fresh bind, close all such panels as all the icons are about + // to go away. + AbstractFloatingView.closeOpenViews(this, true, + AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_WIDGETS_FULL_SHEET); setWorkspaceLoading(true); @@ -3188,9 +2445,7 @@ public class Launcher extends BaseActivity if (mHotseat != null) { mHotseat.resetLayout(); } - if (LauncherAppState.PROFILE_STARTUP) { - Trace.endSection(); - } + TraceHelper.endSection("startBinding"); } @Override @@ -3207,13 +2462,6 @@ public class Launcher extends BaseActivity } bindAddScreens(orderedScreenIds); - // Create the custom content page (this call updates mDefaultScreen which calls - // setCurrentPage() so ensure that all pages are added before calling this). - if (hasCustomContentToLeft()) { - mWorkspace.createCustomContentContainer(); - populateCustomContentContainer(); - } - // After we have added all the screens, if the wallpaper was locked to the default state, // then notify to indicate that it can be released and a proper wallpaper offset can be // computed before the next layout @@ -3309,7 +2557,8 @@ public class Launcher extends BaseActivity (FolderInfo) item); break; } - case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: { + case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: + case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: { view = inflateAppWidget((LauncherAppWidgetInfo) item); if (view == null) { continue; @@ -3400,10 +2649,7 @@ public class Launcher extends BaseActivity return view; } - final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0; - if (DEBUG_WIDGETS) { - Log.d(TAG, "bindAppWidget: " + item); - } + TraceHelper.beginSection("BIND_WIDGET"); final LauncherAppWidgetProviderInfo appWidgetInfo; @@ -3421,11 +2667,9 @@ public class Launcher extends BaseActivity if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) && (item.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED)) { if (appWidgetInfo == null) { - if (DEBUG_WIDGETS) { - Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId - + " belongs to component " + item.providerName - + ", as the provider is null"); - } + Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId + + " belongs to component " + item.providerName + + ", as the provider is null"); getModelWriter().deleteItemFromDatabase(item); return null; } @@ -3486,11 +2730,6 @@ public class Launcher extends BaseActivity final AppWidgetHostView view; if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) { - if (DEBUG_WIDGETS) { - Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " - + appWidgetInfo.provider); - } - // Verify that we own the widget if (appWidgetInfo == null) { FileLog.e(TAG, "Removing invalid widget: id=" + item.appWidgetId); @@ -3506,10 +2745,7 @@ public class Launcher extends BaseActivity } prepareAppWidget(view, item); - if (DEBUG_WIDGETS) { - Log.d(TAG, "bound widget id="+item.appWidgetId+" in " - + (SystemClock.uptimeMillis()-start) + "ms"); - } + TraceHelper.endSection("BIND_WIDGET", "id=" + item.appWidgetId); return view; } @@ -3531,7 +2767,10 @@ public class Launcher extends BaseActivity info.pendingItemInfo = null; } - mWorkspace.reinflateWidgetsIfNecessary(); + if (((PendingAppWidgetHostView) view).isReinflateIfNeeded()) { + view.reinflate(); + } + getModelWriter().updateItemInDatabase(info); return info; } @@ -3595,9 +2834,7 @@ public class Launcher extends BaseActivity if (waitUntilResume(r)) { return; } - if (LauncherAppState.PROFILE_STARTUP) { - Trace.beginSection("Page bind completed"); - } + TraceHelper.beginSection("finishBindingItems"); mWorkspace.restoreInstanceStateForRemainingPages(); setWorkspaceLoading(false); @@ -3612,13 +2849,7 @@ public class Launcher extends BaseActivity InstallShortcutReceiver.FLAG_LOADER_RUNNING, this); NotificationListener.setNotificationsChangedListener(mPopupDataProvider); - - if (mLauncherCallbacks != null) { - mLauncherCallbacks.finishBindingItems(false); - } - if (LauncherAppState.PROFILE_STARTUP) { - Trace.endSection(); - } + TraceHelper.endSection("finishBindingItems"); } private boolean canRunNewAppsAnimation() { @@ -3638,13 +2869,6 @@ public class Launcher extends BaseActivity return mDeviceProfile.isVerticalBarLayout(); } - public int getSearchBarHeight() { - if (mLauncherCallbacks != null) { - return mLauncherCallbacks.getSearchBarHeight(); - } - return LauncherCallbacks.SEARCH_BAR_HEIGHT_NORMAL; - } - /** * Add the icons for all apps. * @@ -3662,7 +2886,7 @@ public class Launcher extends BaseActivity 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; @@ -3817,12 +3041,12 @@ public class Launcher extends BaseActivity // Update AllApps if (mAppsView != null) { mAppsView.removeApps(appInfos); - tryAndUpdatePredictedApps(); } } @Override - public void bindAllWidgets(final MultiHashMap allWidgets) { + public void bindAllWidgets(final ArrayList allWidgets) { + mPopupDataProvider.setAllWidgets(allWidgets); Runnable r = new RunnableWithId(RUNNABLE_ID_BIND_WIDGETS) { @Override public void run() { @@ -3833,32 +3057,12 @@ public class Launcher extends BaseActivity return; } - if (mWidgetsView != null && allWidgets != null) { - Executor pendingExecutor = getPendingExecutor(); - if (pendingExecutor != null && mState != State.WIDGETS) { - pendingExecutor.execute(r); - return; - } - mWidgetsView.setWidgets(allWidgets); - } - AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this); if (topView != null) { topView.onWidgetsBound(); } } - public List getWidgetsForPackageUser(PackageUserKey packageUserKey) { - return mWidgetsView.getWidgetsForPackageUser(packageUserKey); - } - - @Override - public void notifyWidgetProvidersChanged() { - if (mWorkspace.getState().shouldUpdateWidget) { - refreshAndBindWidgetsForPackageUser(null); - } - } - /** * @param packageUser if null, refreshes all widgets and shortcuts, otherwise only * refreshes the widgets and shortcuts associated with the given package/user @@ -3867,40 +3071,14 @@ public class Launcher extends BaseActivity mModel.refreshAndBindWidgetsAndShortcuts(packageUser); } - public void lockScreenOrientation() { - if (mRotationEnabled) { - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED); - } - } - - 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(); - } - - protected void moveWorkspaceToDefaultScreen() { - mWorkspace.moveToDefaultScreen(false); + return isInState(NORMAL) + && !mSharedPrefs.getBoolean(AllAppsState.APPS_VIEW_SHOWN, false) + && !UserManagerCompat.getInstance(this).isDemoUser(); } /** @@ -3912,7 +3090,7 @@ public class Launcher extends BaseActivity if (args.length > 0 && TextUtils.equals(args[0], "--all")) { writer.println(prefix + "Workspace Items"); - for (int i = mWorkspace.numCustomPages(); i < mWorkspace.getPageCount(); i++) { + for (int i = 0; i < mWorkspace.getPageCount(); i++) { writer.println(prefix + " Homescreen " + i); ViewGroup layout = ((CellLayout) mWorkspace.getPageAt(i)).getShortcutsAndWidgets(); @@ -3958,7 +3136,7 @@ public class Launcher extends BaseActivity List data, Menu menu, int deviceId) { ArrayList 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)); } @@ -3984,8 +3162,8 @@ public class Launcher extends BaseActivity if (event.hasModifiers(KeyEvent.META_CTRL_ON)) { switch (keyCode) { case KeyEvent.KEYCODE_A: - if (mState == State.WORKSPACE) { - showAppsView(true, true); + if (isInState(NORMAL)) { + getStateManager().goToState(ALL_APPS); return true; } break; @@ -3996,7 +3174,7 @@ public class Launcher extends BaseActivity && mAccessibilityDelegate.performAction(focusedView, (ItemInfo) focusedView.getTag(), LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) { - PopupContainerWithArrow.getOpen(this).requestFocus(); + BaseActionPopup.getOpen(this).requestFocus(); return true; } break; @@ -4011,14 +3189,6 @@ public class Launcher extends BaseActivity return super.onKeyShortcut(keyCode, event); } - public static CustomAppWidget getCustomAppWidget(String name) { - return sCustomAppWidgets.get(name); - } - - public static HashMap getCustomAppWidgets() { - return sCustomAppWidgets; - } - public static Launcher getLauncher(Context context) { if (context instanceof Launcher) { return (Launcher) context; @@ -4037,4 +3207,12 @@ public class Launcher extends BaseActivity } } } + + /** + * Callback for listening for onResume + */ + public interface OnResumeCallback { + + void onLauncherResume(); + } } diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java index cfb9b570b0..dfe51af868 100644 --- a/src/com/android/launcher3/LauncherAnimUtils.java +++ b/src/com/android/launcher3/LauncherAnimUtils.java @@ -30,6 +30,15 @@ import java.util.HashSet; import java.util.WeakHashMap; public class LauncherAnimUtils { + /** + * Durations for various state animations. These are not defined in resources to allow + * easier access from static classes and enums + */ + public static final int ALL_APPS_TRANSITION_MS = 320; + 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; + static WeakHashMap sAnimators = new WeakHashMap(); static Animator.AnimatorListener sEndAnimListener = new Animator.AnimatorListener() { public void onAnimationStart(Animator animation) { @@ -141,4 +150,18 @@ public class LauncherAnimUtils { drawable.setAlpha(alpha); } }; + + public static final Property SCALE_PROPERTY = + new Property(Float.class, "scale") { + @Override + public Float get(View view) { + return view.getScaleX(); + } + + @Override + public void set(View view, Float scale) { + view.setScaleX(scale); + view.setScaleY(scale); + } + }; } diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index 1ffe41bc6c..0136c7087f 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -27,13 +27,10 @@ import android.util.Log; 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.dynamicui.ExtractionUtils; 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; @@ -42,8 +39,6 @@ import static com.android.launcher3.SettingsActivity.NOTIFICATION_BADGING; public class LauncherAppState { - public static final boolean PROFILE_STARTUP = FeatureFlags.IS_DOGFOOD_BUILD; - // We do not need any synchronization for this variable as its only written on UI thread. private static LauncherAppState INSTANCE; @@ -91,10 +86,6 @@ public class LauncherAppState { 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); @@ -111,18 +102,11 @@ public class LauncherAppState { filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED); - // For extracting colors from the wallpaper - if (Utilities.ATLEAST_NOUGAT) { - // TODO: add a broadcast entry to the manifest for pre-N. - filter.addAction(Intent.ACTION_WALLPAPER_CHANGED); - } mContext.registerReceiver(mModel, filter); UserManagerCompat.getInstance(mContext).enableAndResetCache(); new ConfigMonitor(mContext).register(); - ExtractionUtils.startColorExtractionServiceIfNecessary(mContext); - if (!mContext.getResources().getBoolean(R.bool.notification_badging_enabled)) { mNotificationBadgingObserver = null; } else { diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java index 5573c5c151..70440fa309 100644 --- a/src/com/android/launcher3/LauncherAppWidgetHost.java +++ b/src/com/android/launcher3/LauncherAppWidgetHost.java @@ -116,13 +116,12 @@ public class LauncherAppWidgetHost extends AppWidgetHost { public AppWidgetHostView createView(Context context, int appWidgetId, LauncherAppWidgetProviderInfo appWidget) { - if (appWidget.isCustomWidget) { + if (appWidget.isCustomWidget()) { LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(context); LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(appWidget.initialLayout, lahv); lahv.setAppWidget(0, appWidget); - lahv.updateLastInflationOrientation(); return lahv; } else { try { diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java index 7fe1308764..6f953e5f0a 100644 --- a/src/com/android/launcher3/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java @@ -19,6 +19,7 @@ package com.android.launcher3; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; +import android.content.res.Configuration; import android.graphics.PointF; import android.graphics.Rect; import android.os.Handler; @@ -58,10 +59,14 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView private final CheckLongPressHelper mLongPressHelper; private final StylusEventHelper mStylusEventHelper; - private final Context mContext; + private final Launcher mLauncher; + + private static final int DONT_REINFLATE = 0; + private static final int REINFLATE_ON_RESUME = 1; + private static final int REINFLATE_ON_CONFIG_CHANGE = 2; @ViewDebug.ExportedProperty(category = "launcher") - private int mPreviousOrientation; + private int mReinflateStatus; private float mSlop; @@ -85,11 +90,11 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView public LauncherAppWidgetHostView(Context context) { super(context); - mContext = context; + mLauncher = Launcher.getLauncher(context); mLongPressHelper = new CheckLongPressHelper(this, this); mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); mInflater = LayoutInflater.from(context); - setAccessibilityDelegate(Launcher.getLauncher(context).getAccessibilityDelegate()); + setAccessibilityDelegate(mLauncher.getAccessibilityDelegate()); setBackgroundResource(R.drawable.widget_internal_focus_bg); if (Utilities.ATLEAST_OREO) { @@ -112,18 +117,28 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView return mInflater.inflate(R.layout.appwidget_error, this, false); } - public void updateLastInflationOrientation() { - mPreviousOrientation = mContext.getResources().getConfiguration().orientation; - } - @Override public void updateAppWidget(RemoteViews remoteViews) { - // Store the orientation in which the widget was inflated - updateLastInflationOrientation(); super.updateAppWidget(remoteViews); // The provider info or the views might have changed. checkIfAutoAdvance(); + + // It is possible that widgets can receive updates while launcher is not in the foreground. + // Consequently, the widgets will be inflated for the orientation of the foreground activity + // (framework issue). On resuming, we ensure that any widgets are inflated for the current + // orientation. + if (mReinflateStatus == DONT_REINFLATE && !isSameOrientation()) { + mReinflateStatus = REINFLATE_ON_RESUME; + if (!mLauncher.waitUntilResume(new ReInflateRunnable())) { + mReinflateStatus = REINFLATE_ON_CONFIG_CHANGE; + } + } + } + + private boolean isSameOrientation() { + return mLauncher.getResources().getConfiguration().orientation == + mLauncher.getOrientation(); } private boolean checkScrollableRecursively(ViewGroup viewGroup) { @@ -142,14 +157,6 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView return false; } - public boolean isReinflateRequired(int orientation) { - // Re-inflate is required if the orientation has changed since last inflated. - if (mPreviousOrientation != orientation) { - return true; - } - return false; - } - public boolean onInterceptTouchEvent(MotionEvent ev) { // Just in case the previous long press hasn't been cleared, we make sure to start fresh // on touch down. @@ -473,4 +480,45 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView public PointF getTranslationForCentering() { return mTranslationForCentering; } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + if (mReinflateStatus == REINFLATE_ON_CONFIG_CHANGE) { + // We are finally in the same orientation + reinflateIfNecessary(); + } + } + + private void reinflateIfNecessary() { + if (!isSameOrientation()) { + // We cannot reinflate yet, wait until next config change + mReinflateStatus = REINFLATE_ON_CONFIG_CHANGE; + return; + } + + mReinflateStatus = DONT_REINFLATE; + if (isAttachedToWindow()) { + LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) getTag(); + reinflate(); + } + } + + public void reinflate() { + LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) getTag(); + // Remove and rebind the current widget (which was inflated in the wrong + // orientation), but don't delete it from the database + mLauncher.removeItem(this, info, false /* deleteFromDb */); + mLauncher.bindAppWidget(info); + } + + private class ReInflateRunnable implements Runnable { + @Override + public void run() { + if (mReinflateStatus == REINFLATE_ON_RESUME) { + reinflateIfNecessary(); + } + } + } } diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java index 6f23e56b34..051846c871 100644 --- a/src/com/android/launcher3/LauncherAppWidgetInfo.java +++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java @@ -71,7 +71,7 @@ public class LauncherAppWidgetInfo extends ItemInfo { /** * Indicates that this is a locally defined widget and hence has no system allocated id. */ - static final int CUSTOM_WIDGET_ID = -100; + public static final int CUSTOM_WIDGET_ID = -100; /** * Identifier for this widget when talking with @@ -104,15 +104,15 @@ public class LauncherAppWidgetInfo extends ItemInfo { private boolean mHasNotifiedInitialWidgetSizeChanged; public LauncherAppWidgetInfo(int appWidgetId, ComponentName providerName) { - if (appWidgetId == CUSTOM_WIDGET_ID) { + this.appWidgetId = appWidgetId; + this.providerName = providerName; + + if (isCustomWidget()) { itemType = LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET; } else { itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; } - this.appWidgetId = appWidgetId; - this.providerName = providerName; - // Since the widget isn't instantiated yet, we don't know these values. Set them to -1 // to indicate that they should be calculated based on the layout and minWidth/minHeight spanX = -1; @@ -128,7 +128,7 @@ public class LauncherAppWidgetInfo extends ItemInfo { } public boolean isCustomWidget() { - return appWidgetId == CUSTOM_WIDGET_ID; + return appWidgetId <= CUSTOM_WIDGET_ID; } @Override diff --git a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java index 6cb703b43a..c7139925c8 100644 --- a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java +++ b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java @@ -2,15 +2,11 @@ package com.android.launcher3; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetProviderInfo; -import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.Point; import android.graphics.Rect; -import android.graphics.drawable.Drawable; import android.os.Parcel; -import android.os.Process; -import android.os.UserHandle; /** * This class is a thin wrapper around the framework AppWidgetProviderInfo class. This class affords @@ -20,7 +16,7 @@ import android.os.UserHandle; */ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo { - public boolean isCustomWidget = false; + public static final String CLS_CUSTOM_WIDGET_PREFIX = "#custom-widget-"; public int spanX; public int spanY; @@ -48,22 +44,12 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo { return launcherInfo; } - private LauncherAppWidgetProviderInfo(Parcel in) { + protected LauncherAppWidgetProviderInfo() {} + + protected LauncherAppWidgetProviderInfo(Parcel in) { super(in); } - public LauncherAppWidgetProviderInfo(Context context, CustomAppWidget widget) { - isCustomWidget = true; - - provider = new ComponentName(context, widget.getClass().getName()); - icon = widget.getIcon(); - label = widget.getLabel(); - previewImage = widget.getPreviewImage(); - initialLayout = widget.getWidgetLayout(); - resizeMode = widget.getResizeMode(); - initSpans(context); - } - public void initSpans(Context context) { InvariantDeviceProfile idp = LauncherAppState.getIDP(context); @@ -97,34 +83,15 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo { } public String getLabel(PackageManager packageManager) { - if (isCustomWidget) { - return Utilities.trim(label); - } return super.loadLabel(packageManager); } - public Drawable getIcon(Context context, IconCache cache) { - if (isCustomWidget) { - return cache.getFullResIcon(provider.getPackageName(), icon); - } - return super.loadIcon(context, LauncherAppState.getIDP(context).fillResIconDpi); + public Point getMinSpans() { + return new Point((resizeMode & RESIZE_HORIZONTAL) != 0 ? minSpanX : -1, + (resizeMode & RESIZE_VERTICAL) != 0 ? minSpanY : -1); } - public String toString(PackageManager pm) { - if (isCustomWidget) { - return "WidgetProviderInfo(" + provider + ")"; - } - return String.format("WidgetProviderInfo provider:%s package:%s short:%s label:%s", - provider.toString(), provider.getPackageName(), provider.getShortClassName(), getLabel(pm)); - } - - public Point getMinSpans(InvariantDeviceProfile idp, Context context) { - return new Point( - (resizeMode & RESIZE_HORIZONTAL) != 0 ? minSpanX : -1, - (resizeMode & RESIZE_VERTICAL) != 0 ? minSpanY : -1); - } - - public UserHandle getUser() { - return isCustomWidget ? Process.myUserHandle() : getProfile(); + public boolean isCustomWidget() { + return provider.getClassName().startsWith(CLS_CUSTOM_WIDGET_PREFIX); } } diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java index 66da046ef8..78d753ac86 100644 --- a/src/com/android/launcher3/LauncherCallbacks.java +++ b/src/com/android/launcher3/LauncherCallbacks.java @@ -19,14 +19,10 @@ package com.android.launcher3; import android.content.Intent; import android.os.Bundle; import android.view.Menu; -import android.view.View; - -import com.android.launcher3.util.ComponentKeyMapper; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.List; /** * LauncherCallbacks is an interface used to extend the Launcher activity. It includes many hooks @@ -43,14 +39,12 @@ public interface LauncherCallbacks { */ void preOnCreate(); void onCreate(Bundle savedInstanceState); - void preOnResume(); void onResume(); void onStart(); void onStop(); void onPause(); void onDestroy(); void onSaveInstanceState(Bundle outState); - void onPostCreate(Bundle savedInstanceState); void onNewIntent(Intent intent); void onActivityResult(int requestCode, int resultCode, Intent data); void onRequestPermissionsResult(int requestCode, String[] permissions, @@ -68,31 +62,16 @@ public interface LauncherCallbacks { * Extension points for providing custom behavior on certain user interactions. */ void onLauncherProviderChange(); - void finishBindingItems(final boolean upgradePath); void bindAllApplications(ArrayList apps); - void onInteractionBegin(); - void onInteractionEnd(); - - @Deprecated - void onWorkspaceLockedChanged(); /** * Starts a search with {@param initialQuery}. Return false if search was not started. */ boolean startSearch( String initialQuery, boolean selectInitialQuery, Bundle appSearchData); - boolean hasCustomContentToLeft(); - void populateCustomContentContainer(); - View getQsbBar(); - Bundle getAdditionalSearchWidgetOptions(); /* * Extensions points for adding / replacing some other aspects of the Launcher experience. */ - boolean shouldMoveToDefaultScreenOnHomeIntent(); boolean hasSettings(); - List> getPredictedApps(); - int SEARCH_BAR_HEIGHT_NORMAL = 0, SEARCH_BAR_HEIGHT_TALL = 1; - /** Must return one of {@link #SEARCH_BAR_HEIGHT_NORMAL} or {@link #SEARCH_BAR_HEIGHT_TALL} */ - int getSearchBarHeight(); } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index a906b00f1f..469c5f1c09 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -37,7 +37,6 @@ import android.util.Pair; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.dynamicui.ExtractionUtils; import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.model.AddWorkspaceItemsTask; import com.android.launcher3.model.BgDataModel; @@ -63,6 +62,7 @@ import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.Provider; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.ViewOnDrawExecutor; +import com.android.launcher3.widget.WidgetListRowEntry; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -134,7 +134,7 @@ public class LauncherModel extends BroadcastReceiver } }; - public interface Callbacks extends LauncherAppWidgetHost.ProviderChangedListener { + public interface Callbacks { public boolean setLoadOnResume(); public int getCurrentWorkspaceScreen(); public void clearPendingBinds(); @@ -154,7 +154,7 @@ public class LauncherModel extends BroadcastReceiver public void bindRestoreItemsChange(HashSet updates); public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher); public void bindAppInfosRemoved(ArrayList appInfos); - public void bindAllWidgets(MultiHashMap widgets); + public void bindAllWidgets(ArrayList widgets); public void onPageBoundSynchronously(int page); public void executeOnNextDraw(ViewOnDrawExecutor executor); public void bindDeepShortcutMap(MultiHashMap deepShortcutMap); @@ -193,9 +193,8 @@ public class LauncherModel extends BroadcastReceiver /** * Adds the provided items to the workspace. */ - public void addAndBindAddedWorkspaceItems( - Provider>> appsProvider) { - enqueueModelUpdateTask(new AddWorkspaceItemsTask(appsProvider)); + public void addAndBindAddedWorkspaceItems(List> itemList) { + enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList)); } public ModelWriter getWriter(boolean hasVerticalHotseat) { @@ -406,8 +405,6 @@ public class LauncherModel extends BroadcastReceiver enqueueModelUpdateTask(new UserLockStateChangedTask(user)); } } - } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(action)) { - ExtractionUtils.startColorExtractionServiceIfNecessary(context); } } diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index b31df98947..98568e4363 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -44,7 +44,6 @@ import android.os.Bundle; 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; @@ -55,7 +54,6 @@ import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.LauncherSettings.WorkspaceScreens; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.dynamicui.ExtractionUtils; import com.android.launcher3.graphics.IconShapeOverride; import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.DbDowngradeHelper; @@ -87,7 +85,7 @@ public class LauncherProvider extends ContentProvider { */ 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"; @@ -149,9 +147,6 @@ public class LauncherProvider extends ContentProvider { */ protected synchronized void createDbIfNotExists() { if (mOpenHelper == null) { - if (LauncherAppState.PROFILE_STARTUP) { - Trace.beginSection("Opening workspace DB"); - } mOpenHelper = new DatabaseHelper(getContext(), mListenerHandler); if (RestoreDbTask.isPending(getContext())) { @@ -162,10 +157,6 @@ public class LauncherProvider extends ContentProvider { // executed again. RestoreDbTask.setPending(getContext(), false); } - - if (LauncherAppState.PROFILE_STARTUP) { - Trace.endSection(); - } } } @@ -372,19 +363,6 @@ public class LauncherProvider extends ContentProvider { createDbIfNotExists(); switch (method) { - case LauncherSettings.Settings.METHOD_SET_EXTRACTED_COLORS_AND_WALLPAPER_ID: { - String extractedColors = extras.getString( - LauncherSettings.Settings.EXTRA_EXTRACTED_COLORS); - int wallpaperId = extras.getInt(LauncherSettings.Settings.EXTRA_WALLPAPER_ID); - Utilities.getPrefs(getContext()).edit() - .putString(ExtractionUtils.EXTRACTED_COLORS_PREFERENCE_KEY, extractedColors) - .putInt(ExtractionUtils.WALLPAPER_ID_PREFERENCE_KEY, wallpaperId) - .apply(); - mListenerHandler.sendEmptyMessage(ChangeListenerWrapper.MSG_EXTRACTED_COLORS_CHANGED); - Bundle result = new Bundle(); - result.putString(LauncherSettings.Settings.EXTRA_VALUE, extractedColors); - return result; - } case LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG: { clearFlagEmptyDbCreated(); return null; @@ -1160,8 +1138,7 @@ public class LauncherProvider extends ContentProvider { private static class ChangeListenerWrapper implements Handler.Callback { private static final int MSG_LAUNCHER_PROVIDER_CHANGED = 1; - private static final int MSG_EXTRACTED_COLORS_CHANGED = 2; - private static final int MSG_APP_WIDGET_HOST_RESET = 3; + private static final int MSG_APP_WIDGET_HOST_RESET = 2; private LauncherProviderChangeListener mListener; @@ -1172,9 +1149,6 @@ public class LauncherProvider extends ContentProvider { case MSG_LAUNCHER_PROVIDER_CHANGED: mListener.onLauncherProviderChanged(); break; - case MSG_EXTRACTED_COLORS_CHANGED: - mListener.onExtractedColorsChanged(); - break; case MSG_APP_WIDGET_HOST_RESET: mListener.onAppWidgetHostReset(); break; diff --git a/src/com/android/launcher3/LauncherProviderChangeListener.java b/src/com/android/launcher3/LauncherProviderChangeListener.java index 704481232d..024308863a 100644 --- a/src/com/android/launcher3/LauncherProviderChangeListener.java +++ b/src/com/android/launcher3/LauncherProviderChangeListener.java @@ -9,7 +9,5 @@ public interface LauncherProviderChangeListener { void onLauncherProviderChanged(); - void onExtractedColorsChanged(); - void onAppWidgetHostReset(); } diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index 87f62eb01b..3b337ef18e 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -304,11 +304,6 @@ public class LauncherSettings { public static final String METHOD_LOAD_DEFAULT_FAVORITES = "load_default_favorites"; - public static final String METHOD_SET_EXTRACTED_COLORS_AND_WALLPAPER_ID = - "set_extracted_colors_and_wallpaper_id_setting"; - public static final String EXTRA_EXTRACTED_COLORS = "extra_extractedColors"; - public static final String EXTRA_WALLPAPER_ID = "extra_wallpaperId"; - public static final String METHOD_REMOVE_GHOST_WIDGETS = "remove_ghost_widgets"; public static final String EXTRA_VALUE = "value"; diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java new file mode 100644 index 0000000000..bb09a9f860 --- /dev/null +++ b/src/com/android/launcher3/LauncherState.java @@ -0,0 +1,137 @@ +/* + * 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 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 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; + +import java.util.Arrays; + + +/** + * Base state for various states used for the Launcher + */ +public class LauncherState { + + protected static final int FLAG_SHOW_SCRIM = 1 << 0; + protected static final int FLAG_MULTI_PAGE = 1 << 1; + protected static final int FLAG_HIDE_HOTSEAT = 1 << 2; + protected static final int FLAG_DISABLE_ACCESSIBILITY = 1 << 3; + protected static final int FLAG_DO_NOT_RESTORE = 1 << 4; + + 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 + * + * @see WorkspaceStateTransitionAnimation + */ + public final boolean hasScrim; + public final boolean hideHotseat; + public final int transitionDuration; + + /** + * 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 0000000000..b99df717ac --- /dev/null +++ b/src/com/android/launcher3/LauncherStateManager.java @@ -0,0 +1,288 @@ +/* + * 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.allapps.AllAppsTransitionController; +import com.android.launcher3.anim.AnimationLayerSet; +import com.android.launcher3.anim.AnimationSuccessListener; +import com.android.launcher3.anim.AnimatorPlaybackController; + +/** + * 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 final AllAppsTransitionController mAllAppsController; + + private LauncherState mState = NORMAL; + + public LauncherStateManager( + Launcher l, AllAppsTransitionController allAppsController) { + mUiHandler = new Handler(Looper.getMainLooper()); + mLauncher = l; + mAllAppsController = allAppsController; + } + + public LauncherState getState() { + return mState; + } + + /** + * @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); + mAllAppsController.setFinalProgress(state.verticalProgress); + mLauncher.getWorkspace().setState(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(); + + mAllAppsController.animateToFinalProgress(state.verticalProgress, animation, mConfig); + mLauncher.getWorkspace().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); + } + + @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); + } + + /** + * 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); + } + } +} diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java deleted file mode 100644 index e2474900da..0000000000 --- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java +++ /dev/null @@ -1,719 +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.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; -import android.animation.TimeInterpolator; -import android.animation.ValueAnimator; -import android.content.res.Resources; -import android.util.Log; -import android.view.View; -import android.view.animation.AccelerateInterpolator; - -import com.android.launcher3.allapps.AllAppsContainerView; -import com.android.launcher3.allapps.AllAppsTransitionController; -import com.android.launcher3.anim.AnimationLayerSet; -import com.android.launcher3.anim.CircleRevealOutlineProvider; -import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.util.Thunk; -import com.android.launcher3.widget.WidgetsContainerView; - -/** - * 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 { - - /** - * animation used for the widget tray - */ - public static final int CIRCULAR_REVEAL = 0; - /** - * animation used for all apps tray - */ - public static final int PULLUP = 1; - - private static final float FINAL_REVEAL_ALPHA_FOR_WIDGETS = 0.3f; - - /** - * Private callbacks made during transition setup. - */ - private static class PrivateTransitionCallbacks { - private final float materialRevealViewFinalAlpha; - - PrivateTransitionCallbacks(float revealAlpha) { - materialRevealViewFinalAlpha = revealAlpha; - } - - float getMaterialRevealViewStartFinalRadius() { - return 0; - } - AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(View revealView, - View buttonView) { - return null; - } - void onTransitionComplete() {} - } - - public static final String TAG = "LSTAnimation"; - - public static final int SINGLE_FRAME_DELAY = 16; - - @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(final boolean animated) { - final AllAppsContainerView toView = mLauncher.getAppsView(); - final View buttonView = mLauncher.getStartViewForAllAppsRevealAnimation(); - PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) { - @Override - public float getMaterialRevealViewStartFinalRadius() { - int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize; - return allAppsButtonSize / 2; - } - @Override - public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( - final View revealView, final View allAppsButtonView) { - return new AnimatorListenerAdapter() { - public void onAnimationStart(Animator animation) { - allAppsButtonView.setVisibility(View.INVISIBLE); - } - public void onAnimationEnd(Animator animation) { - allAppsButtonView.setVisibility(View.VISIBLE); - } - }; - } - @Override - void onTransitionComplete() { - mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); - } - }; - // Only animate the search bar if animating from spring loaded mode back to all apps - startAnimationToOverlay( - Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, PULLUP, cb); - } - - /** - * Starts an animation to the widgets view. - */ - public void startAnimationToWidgets(final boolean animated) { - final WidgetsContainerView toView = mLauncher.getWidgetsView(); - final View buttonView = mLauncher.getWidgetsButton(); - startAnimationToOverlay( - Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated, CIRCULAR_REVEAL, - new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS){ - @Override - void onTransitionComplete() { - mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); - } - }); - } - - /** - * Starts an animation to the workspace from the current overlay view. - */ - public void startAnimationToWorkspace(final Launcher.State fromState, - final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, - final boolean animated, final Runnable onCompleteRunnable) { - if (toWorkspaceState != Workspace.State.NORMAL && - toWorkspaceState != Workspace.State.SPRING_LOADED && - toWorkspaceState != Workspace.State.OVERVIEW) { - Log.e(TAG, "Unexpected call to startAnimationToWorkspace"); - } - - if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED - || mAllAppsController.isTransitioning()) { - startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState, - animated, PULLUP, onCompleteRunnable); - } else if (fromState == Launcher.State.WIDGETS || - fromState == Launcher.State.WIDGETS_SPRING_LOADED) { - startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState, - animated, onCompleteRunnable); - } else { - startAnimationToNewWorkspaceState(fromWorkspaceState, toWorkspaceState, - animated, onCompleteRunnable); - } - } - - /** - * Creates and starts a new animation to a particular overlay view. - */ - private void startAnimationToOverlay( - final Workspace.State toWorkspaceState, - final View buttonView, final BaseContainerView toView, - final boolean animated, int animType, final PrivateTransitionCallbacks pCb) { - final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); - final Resources res = mLauncher.getResources(); - final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime); - final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime); - - final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger); - - final AnimationLayerSet layerViews = new AnimationLayerSet(); - - // If for some reason our views aren't initialized, don't animate - boolean initialized = buttonView != null; - - // Cancel the current animation - cancelAnimation(); - - final View contentView = toView.getContentView(); - playCommonTransitionAnimations(toWorkspaceState, - animated, initialized, animation, layerViews); - if (!animated || !initialized) { - if (toWorkspaceState == Workspace.State.NORMAL_HIDDEN) { - 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); - - // Show the content view - contentView.setVisibility(View.VISIBLE); - pCb.onTransitionComplete(); - return; - } - if (animType == CIRCULAR_REVEAL) { - // Setup the reveal view animation - final View revealView = toView.getRevealView(); - - int width = revealView.getMeasuredWidth(); - int height = revealView.getMeasuredHeight(); - float revealRadius = (float) Math.hypot(width / 2, height / 2); - revealView.setVisibility(View.VISIBLE); - revealView.setAlpha(0f); - revealView.setTranslationY(0f); - revealView.setTranslationX(0f); - - // Calculate the final animation values - int[] buttonViewToPanelDelta = - Utilities.getCenterDeltaInScreenSpace(revealView, buttonView); - final float revealViewToAlpha = pCb.materialRevealViewFinalAlpha; - final float revealViewToXDrift = buttonViewToPanelDelta[0]; - final float revealViewToYDrift = buttonViewToPanelDelta[1]; - - // Create the animators - PropertyValuesHolder panelAlpha = - PropertyValuesHolder.ofFloat(View.ALPHA, revealViewToAlpha, 1f); - PropertyValuesHolder panelDriftY = - PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, revealViewToYDrift, 0); - PropertyValuesHolder panelDriftX = - PropertyValuesHolder.ofFloat(View.TRANSLATION_X, revealViewToXDrift, 0); - ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView, - panelAlpha, panelDriftY, panelDriftX); - panelAlphaAndDrift.setDuration(revealDuration); - panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); - - // Play the animation - layerViews.addView(revealView); - animation.play(panelAlphaAndDrift); - - // Setup the animation for the content view - contentView.setVisibility(View.VISIBLE); - contentView.setAlpha(0f); - contentView.setTranslationY(revealViewToYDrift); - layerViews.addView(contentView); - - // Create the individual animators - ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", - revealViewToYDrift, 0); - pageDrift.setDuration(revealDuration); - pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); - pageDrift.setStartDelay(itemsAlphaStagger); - animation.play(pageDrift); - - ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f); - itemsAlpha.setDuration(revealDuration); - itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); - itemsAlpha.setStartDelay(itemsAlphaStagger); - animation.play(itemsAlpha); - - float startRadius = pCb.getMaterialRevealViewStartFinalRadius(); - AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener( - revealView, buttonView); - Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2, - startRadius, revealRadius).createRevealAnimator(revealView); - reveal.setDuration(revealDuration); - reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); - if (listener != null) { - reveal.addListener(listener); - } - animation.play(reveal); - - animation.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - // Hide the reveal view - revealView.setVisibility(View.INVISIBLE); - - // This can hold unnecessary references to views. - cleanupAnimation(); - pCb.onTransitionComplete(); - } - - }); - - toView.bringToFront(); - toView.setVisibility(View.VISIBLE); - - animation.addListener(layerViews); - toView.post(new StartAnimRunnable(animation, toView)); - mCurrentAnimation = animation; - } else if (animType == PULLUP) { - if (!FeatureFlags.LAUNCHER3_PHYSICS) { - // We are animating the content view alpha, so ensure we have a layer for it. - layerViews.addView(contentView); - } - - animation.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - cleanupAnimation(); - pCb.onTransitionComplete(); - } - }); - boolean shouldPost = mAllAppsController.animateToAllApps(animation, revealDurationSlide); - - Runnable startAnimRunnable = new StartAnimRunnable(animation, toView); - mCurrentAnimation = animation; - mCurrentAnimation.addListener(layerViews); - if (shouldPost) { - toView.post(startAnimRunnable); - } else { - startAnimRunnable.run(); - } - } - } - - /** - * Plays animations used by various transitions. - */ - private void playCommonTransitionAnimations( - Workspace.State toWorkspaceState, - boolean animated, boolean initialized, AnimatorSet animation, - AnimationLayerSet layerViews) { - // Create the workspace animation. - // NOTE: this call apparently also sets the state for the workspace if !animated - Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, - animated, layerViews); - - if (animated && initialized) { - // Play the workspace animation - if (workspaceAnim != null) { - animation.play(workspaceAnim); - } - } - } - - /** - * Starts an animation to the workspace from the apps view. - */ - private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState, - final Workspace.State toWorkspaceState, final boolean animated, int type, - final Runnable onCompleteRunnable) { - // No alpha anim from all apps - PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) { - @Override - float getMaterialRevealViewStartFinalRadius() { - int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize; - return allAppsButtonSize / 2; - } - @Override - public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( - final View revealView, final View allAppsButtonView) { - return new AnimatorListenerAdapter() { - public void onAnimationStart(Animator animation) { - // We set the alpha instead of visibility to ensure that the focus does not - // get taken from the all apps view - allAppsButtonView.setVisibility(View.VISIBLE); - allAppsButtonView.setAlpha(0f); - } - public void onAnimationEnd(Animator animation) { - // Hide the reveal view - revealView.setVisibility(View.INVISIBLE); - - // Show the all apps button, and focus it - allAppsButtonView.setAlpha(1f); - } - }; - } - @Override - void onTransitionComplete() { - mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); - } - }; - // Only animate the search bar if animating to spring loaded mode from all apps - startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState, - mLauncher.getStartViewForAllAppsRevealAnimation(), mLauncher.getAppsView(), - animated, type, onCompleteRunnable, cb); - } - - /** - * Starts an animation to the workspace from the widgets view. - */ - private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState, - final Workspace.State toWorkspaceState, final boolean animated, - final Runnable onCompleteRunnable) { - final WidgetsContainerView widgetsView = mLauncher.getWidgetsView(); - PrivateTransitionCallbacks cb = - new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS) { - @Override - public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( - final View revealView, final View widgetsButtonView) { - return new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animation) { - // Hide the reveal view - revealView.setVisibility(View.INVISIBLE); - } - }; - } - @Override - void onTransitionComplete() { - mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); - } - }; - startAnimationToWorkspaceFromOverlay( - fromWorkspaceState, toWorkspaceState, - mLauncher.getWidgetsButton(), widgetsView, - animated, CIRCULAR_REVEAL, onCompleteRunnable, cb); - } - - /** - * Starts an animation to the workspace from another workspace state, e.g. normal to overview. - */ - private void startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState, - final Workspace.State toWorkspaceState, final boolean animated, - final Runnable onCompleteRunnable) { - final View fromWorkspace = mLauncher.getWorkspace(); - final AnimationLayerSet layerViews = new AnimationLayerSet(); - final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); - - // Cancel the current animation - cancelAnimation(); - - playCommonTransitionAnimations(toWorkspaceState, animated, animated, animation, layerViews); - mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); - - if (animated) { - 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; - } else /* if (!animated) */ { - // Run any queued runnables - if (onCompleteRunnable != null) { - onCompleteRunnable.run(); - } - - mCurrentAnimation = null; - } - } - - /** - * Creates and starts a new animation to the workspace. - */ - private void startAnimationToWorkspaceFromOverlay( - final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, - final View buttonView, final BaseContainerView fromView, - final boolean animated, int animType, final Runnable onCompleteRunnable, - final PrivateTransitionCallbacks pCb) { - final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); - final Resources res = mLauncher.getResources(); - final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime); - final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime); - final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger); - - final View toView = mLauncher.getWorkspace(); - final View revealView = fromView.getRevealView(); - final View contentView = fromView.getContentView(); - - final AnimationLayerSet layerViews = new AnimationLayerSet(); - - // If for some reason our views aren't initialized, don't animate - boolean initialized = buttonView != null; - - // Cancel the current animation - cancelAnimation(); - - playCommonTransitionAnimations(toWorkspaceState, - animated, initialized, animation, layerViews); - if (!animated || !initialized) { - if (fromWorkspaceState == Workspace.State.NORMAL_HIDDEN) { - mAllAppsController.finishPullDown(); - } - fromView.setVisibility(View.GONE); - pCb.onTransitionComplete(); - - // Run any queued runnables - if (onCompleteRunnable != null) { - onCompleteRunnable.run(); - } - return; - } - if (animType == CIRCULAR_REVEAL) { - // hideAppsCustomizeHelper is called in some cases when it is already hidden - // don't perform all these no-op animations. In particularly, this was causing - // the all-apps button to pop in and out. - if (fromView.getVisibility() == View.VISIBLE) { - int width = revealView.getMeasuredWidth(); - int height = revealView.getMeasuredHeight(); - float revealRadius = (float) Math.hypot(width / 2, height / 2); - revealView.setVisibility(View.VISIBLE); - revealView.setAlpha(1f); - revealView.setTranslationY(0); - layerViews.addView(revealView); - - // Calculate the final animation values - int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, buttonView); - final float revealViewToXDrift = buttonViewToPanelDelta[0]; - final float revealViewToYDrift = buttonViewToPanelDelta[1]; - - // The vertical motion of the apps panel should be delayed by one frame - // from the conceal animation in order to give the right feel. We correspondingly - // shorten the duration so that the slide and conceal end at the same time. - TimeInterpolator decelerateInterpolator = new LogDecelerateInterpolator(100, 0); - ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY", - 0, revealViewToYDrift); - panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY); - panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); - panelDriftY.setInterpolator(decelerateInterpolator); - animation.play(panelDriftY); - - ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX", - 0, revealViewToXDrift); - panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY); - panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); - panelDriftX.setInterpolator(decelerateInterpolator); - animation.play(panelDriftX); - - // Setup animation for the reveal panel alpha - if (pCb.materialRevealViewFinalAlpha != 1f) { - ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha", - 1f, pCb.materialRevealViewFinalAlpha); - panelAlpha.setDuration(revealDuration); - panelAlpha.setInterpolator(decelerateInterpolator); - animation.play(panelAlpha); - } - - // Setup the animation for the content view - layerViews.addView(contentView); - - // Create the individual animators - ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", - 0, revealViewToYDrift); - contentView.setTranslationY(0); - pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY); - pageDrift.setInterpolator(decelerateInterpolator); - pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); - animation.play(pageDrift); - - contentView.setAlpha(1f); - ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f); - itemsAlpha.setDuration(100); - itemsAlpha.setInterpolator(decelerateInterpolator); - animation.play(itemsAlpha); - - // Invalidate the scrim throughout the animation to ensure the highlight - // cutout is correct throughout. - ValueAnimator invalidateScrim = ValueAnimator.ofFloat(0f, 1f); - invalidateScrim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - mLauncher.getDragLayer().invalidateScrim(); - } - }); - animation.play(invalidateScrim); - - // Animate the all apps button - float finalRadius = pCb.getMaterialRevealViewStartFinalRadius(); - AnimatorListenerAdapter listener = - pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView); - Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2, - revealRadius, finalRadius).createRevealAnimator(revealView); - reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); - reveal.setDuration(revealDuration); - reveal.setStartDelay(itemsAlphaStagger); - if (listener != null) { - reveal.addListener(listener); - } - animation.play(reveal); - } - - animation.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - fromView.setVisibility(View.GONE); - // Run any queued runnables - if (onCompleteRunnable != null) { - onCompleteRunnable.run(); - } - - // Reset page transforms - if (contentView != null) { - contentView.setTranslationX(0); - contentView.setTranslationY(0); - contentView.setAlpha(1); - } - - // This can hold unnecessary references to views. - cleanupAnimation(); - pCb.onTransitionComplete(); - } - }); - - mCurrentAnimation = animation; - mCurrentAnimation.addListener(layerViews); - fromView.post(new StartAnimRunnable(animation, null)); - } else if (animType == PULLUP) { - // We are animating the content view alpha, so ensure we have a layer for it - layerViews.addView(contentView); - - 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(); - pCb.onTransitionComplete(); - } - - }); - boolean shouldPost = mAllAppsController.animateToWorkspace(animation, revealDurationSlide); - - Runnable startAnimRunnable = new StartAnimRunnable(animation, toView); - mCurrentAnimation = animation; - mCurrentAnimation.addListener(layerViews); - if (shouldPost) { - fromView.post(startAnimRunnable); - } else { - startAnimRunnable.run(); - } - } - return; - } - - /** - * 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(); - } - } -} diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index 87f3ddaf47..4240a30b4f 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -46,10 +46,10 @@ import android.view.accessibility.AccessibilityManager; 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,8 +87,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc 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; @@ -137,7 +135,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc protected int mTouchSlop; private int mMaximumVelocity; protected boolean mAllowOverScroll = true; - protected int[] mTempVisiblePagesRange = new int[2]; protected static final int INVALID_POINTER = -1; @@ -218,7 +215,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc */ protected void init() { mScroller = new LauncherScroller(getContext()); - setDefaultInterpolator(new ScrollInterpolator()); + setDefaultInterpolator(Interpolators.SCROLL); mCurrentPage = 0; final ViewConfiguration configuration = ViewConfiguration.get(getContext()); @@ -380,16 +377,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } 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 +480,11 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc 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; @@ -886,7 +873,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc return 0; } - @Thunk void updateMaxScrollX() { + private void updateMaxScrollX() { mMaxScrollX = computeMaxScrollX(); } @@ -915,13 +902,11 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // 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 +959,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc 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)); @@ -1341,38 +1321,20 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc /** * 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 +1350,12 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc 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 +1450,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc 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 +1767,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc 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 +1974,14 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // 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/PendingAppWidgetHostView.java b/src/com/android/launcher3/PendingAppWidgetHostView.java index c2d5501693..b86d413185 100644 --- a/src/com/android/launcher3/PendingAppWidgetHostView.java +++ b/src/com/android/launcher3/PendingAppWidgetHostView.java @@ -110,9 +110,7 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView mClickListener = l; } - @Override - public boolean isReinflateRequired(int orientation) { - // Re inflate is required any time the widget restore status changes + public boolean isReinflateIfNeeded() { return mStartState != mInfo.restoreStatus; } diff --git a/src/com/android/launcher3/PinchAnimationManager.java b/src/com/android/launcher3/PinchAnimationManager.java deleted file mode 100644 index c3d3bb3dfa..0000000000 --- a/src/com/android/launcher3/PinchAnimationManager.java +++ /dev/null @@ -1,244 +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.Workspace.State.NORMAL; -import static com.android.launcher3.Workspace.State.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 = mWorkspace.getStateTransitionAnimation() - .mOverviewTransitionTime; - } - - 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); - setOverviewPanelsAlpha(1f - interpolatedProgress, 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 Workspace.State#NORMAL} or {@link Workspace.State#OVERVIEW}. - * @param goingTowards {@link Workspace.State#NORMAL} or {@link Workspace.State#OVERVIEW}. - * Note that this doesn't have to be the opposite of startState; - */ - public void animateThreshold(float threshold, Workspace.State startState, - Workspace.State 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(float 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.setBackgroundAlpha(alpha); - } else { - ObjectAnimator.ofFloat(cl, "backgroundAlpha", 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) { - float endValue = show ? mWorkspace.getStateTransitionAnimation().mWorkspaceScrimAlpha : 0; - startAnimator(INDEX_SCRIM, - ObjectAnimator.ofFloat(mLauncher.getDragLayer(), "backgroundAlpha", 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 52aac17352..0000000000 --- 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) { - Workspace.State fromState = mWorkspace.isInOverviewMode() ? Workspace.State.OVERVIEW - : Workspace.State.NORMAL; - Workspace.State toState = mWorkspace.isInOverviewMode() ? Workspace.State.NORMAL - : Workspace.State.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 42515d1fcf..0000000000 --- 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 || mLauncher.isOnCustomContent()) { - // 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/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java index a7e68ff148..841c0cd57a 100644 --- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java +++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java @@ -108,28 +108,21 @@ public class ShortcutAndWidgetContainer extends ViewGroup { public void measureChild(View child) { CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); - if (!lp.isFullscreen) { - final DeviceProfile profile = mLauncher.getDeviceProfile(); + final DeviceProfile profile = mLauncher.getDeviceProfile(); - if (child instanceof LauncherAppWidgetHostView) { - lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, - profile.appWidgetScale.x, profile.appWidgetScale.y); - // Widgets have their own padding - } else { - lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX); - // Center the icon/folder - int cHeight = getCellContentHeight(); - int cellPaddingY = (int) Math.max(0, ((lp.height - cHeight) / 2f)); - int cellPaddingX = mContainerType == CellLayout.WORKSPACE - ? profile.workspaceCellPaddingXPx - : (int) (profile.edgeMarginPx / 2f); - child.setPadding(cellPaddingX, cellPaddingY, cellPaddingX, 0); - } + if (child instanceof LauncherAppWidgetHostView) { + lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, + profile.appWidgetScale.x, profile.appWidgetScale.y); + // Widgets have their own padding } else { - lp.x = 0; - lp.y = 0; - lp.width = getMeasuredWidth(); - lp.height = getMeasuredHeight(); + lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX); + // Center the icon/folder + int cHeight = getCellContentHeight(); + int cellPaddingY = (int) Math.max(0, ((lp.height - cHeight) / 2f)); + int cellPaddingX = mContainerType == CellLayout.WORKSPACE + ? profile.workspaceCellPaddingXPx + : (int) (profile.edgeMarginPx / 2f); + child.setPadding(cellPaddingX, cellPaddingY, cellPaddingX, 0); } int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY); int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY); diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java index adf008bf44..ec608ca8de 100644 --- a/src/com/android/launcher3/ShortcutInfo.java +++ b/src/com/android/launcher3/ShortcutInfo.java @@ -64,13 +64,6 @@ public class ShortcutInfo extends ItemInfoWithIcon { */ 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. */ @@ -82,42 +75,6 @@ public class ShortcutInfo extends ItemInfoWithIcon { */ 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 @@ public class ShortcutInfo extends ItemInfoWithIcon { 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 @@ public class ShortcutInfo extends ItemInfoWithIcon { super(info); title = Utilities.trim(info.title); intent = new Intent(info.intent); - isDisabled = info.isDisabled; } /** @@ -219,9 +174,9 @@ public class ShortcutInfo extends ItemInfoWithIcon { 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(); } @@ -232,11 +187,6 @@ public class ShortcutInfo extends ItemInfoWithIcon { getIntent().getStringExtra(ShortcutInfoCompat.EXTRA_SHORTCUT_ID) : null; } - @Override - public boolean isDisabled() { - return isDisabled != 0; - } - @Override public ComponentName getTargetComponent() { ComponentName cn = super.getTargetComponent(); diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java index 902fd3439a..68a441ad98 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,18 +13,28 @@ import android.net.Uri; 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 mUninstallDisabledCache = new ArrayMap<>(1); + + private final Alarm mCacheExpireAlarm; public UninstallDropTarget(Context context, AttributeSet attrs) { this(context, attrs, 0); @@ -29,6 +42,9 @@ public class UninstallDropTarget extends ButtonDropTarget { public UninstallDropTarget(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + + mCacheExpireAlarm = new Alarm(); + mCacheExpireAlarm.setOnAlarmListener(this); } @Override @@ -44,34 +60,45 @@ public class UninstallDropTarget extends ButtonDropTarget { } @Override - protected boolean supportsDrop(DragSource source, 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 && @@ -80,7 +107,7 @@ public class UninstallDropTarget extends ButtonDropTarget { 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) { @@ -91,98 +118,101 @@ public class UninstallDropTarget extends ButtonDropTarget { } @Override - public void onDrop(DragObject d) { - // Differ item deletion - if (d.dragSource instanceof DropTargetSource) { - ((DropTargetSource) d.dragSource).deferCompleteDropAfterUninstallActivity(); - } - super.onDrop(d); + public void onDrop(DragObject d, DragOptions options) { + // Defer onComplete + d.dragSource = new DeferredOnComplete(d.dragSource, getContext()); + super.onDrop(d, options); } @Override public void completeDrop(final DragObject d) { - DropTargetResultCallback callback = d.dragSource instanceof DropTargetResultCallback - ? (DropTargetResultCallback) d.dragSource : null; - startUninstallActivity(mLauncher, d.dragInfo, callback); + ComponentName target = performDropAction(d.dragInfo); + if (d.dragSource instanceof DeferredOnComplete) { + DeferredOnComplete deferred = (DeferredOnComplete) d.dragSource; + if (target != null) { + deferred.mPackageName = target.getPackageName(); + mLauncher.setOnResumeCallback(deferred); + } else { + deferred.sendFailure(); + } + } } - public static boolean startUninstallActivity(Launcher launcher, ItemInfo info) { - return startUninstallActivity(launcher, info, null); - } - - public static boolean startUninstallActivity( - final Launcher launcher, ItemInfo info, DropTargetResultCallback callback) { - final ComponentName cn = getUninstallTarget(launcher, info); - - boolean canUninstall; + /** + * Performs the drop action and returns the target component for the dragObject or null if + * the action was not performed. + */ + 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(launcher, R.string.uninstall_system_app_text, Toast.LENGTH_SHORT).show(); - canUninstall = false; - } else { - try { - Intent i = Intent.parseUri(launcher.getString(R.string.delete_package_intent), 0) - .setData(Uri.fromParts("package", cn.getPackageName(), cn.getClassName())) - .putExtra(Intent.EXTRA_USER, info.user); - launcher.startActivity(i); - canUninstall = true; - } catch (URISyntaxException e) { - Log.e(TAG, "Failed to parse intent to start uninstall activity for item=" + info); - canUninstall = false; + Toast.makeText(mLauncher, R.string.uninstall_system_app_text, Toast.LENGTH_SHORT).show(); + return null; + } + try { + 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); + mLauncher.startActivity(i); + return cn; + } catch (URISyntaxException e) { + Log.e(TAG, "Failed to parse intent to start uninstall activity for item=" + info); + return null; + } + } + + @Override + public void onAccessibilityDrop(View view, ItemInfo item) { + performDropAction(item); + } + + /** + * A wrapper around {@link DragSource} which delays the {@link #onDropCompleted} action until + * {@link #onLauncherResume} + */ + private class DeferredOnComplete implements DragSource, OnResumeCallback { + + private final DragSource mOriginal; + private final Context mContext; + + private String mPackageName; + private DragObject mDragObject; + + public DeferredOnComplete(DragSource original, Context context) { + mOriginal = original; + mContext = context; + } + + @Override + public void onDropCompleted(View target, DragObject d, + boolean success) { + mDragObject = d; + } + + @Override + public void fillInLogContainerData(View v, ItemInfo info, Target target, + Target targetParent) { + mOriginal.fillInLogContainerData(v, info, target, targetParent); + } + + @Override + public void onLauncherResume() { + // We use MATCH_UNINSTALLED_PACKAGES as the app can be on SD card as well. + if (LauncherAppsCompat.getInstance(mContext) + .getApplicationInfo(mPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, + mDragObject.dragInfo.user) == null) { + mDragObject.dragSource = mOriginal; + mOriginal.onDropCompleted(UninstallDropTarget.this, mDragObject, true); + } else { + sendFailure(); } } - if (callback != null) { - sendUninstallResult(launcher, canUninstall, cn, info.user, callback); + + public void sendFailure() { + mDragObject.dragSource = mOriginal; + mDragObject.cancelled = true; + mOriginal.onDropCompleted(UninstallDropTarget.this, mDragObject, false); } - return canUninstall; - } - - /** - * Notifies the {@param callback} whether the uninstall was successful or not. - * - * Since there is no direct callback for an uninstall request, we check the package existence - * when the launch resumes next time. This assumes that the uninstall activity will finish only - * after the task is completed - */ - protected static void sendUninstallResult( - final Launcher launcher, boolean activityStarted, - final ComponentName cn, final UserHandle user, - final DropTargetResultCallback callback) { - if (activityStarted) { - final Runnable checkIfUninstallWasSuccess = new Runnable() { - @Override - public void run() { - // We use MATCH_UNINSTALLED_PACKAGES as the app can be on SD card as well. - boolean uninstallSuccessful = LauncherAppsCompat.getInstance(launcher) - .getApplicationInfo(cn.getPackageName(), - PackageManager.MATCH_UNINSTALLED_PACKAGES, user) == null; - callback.onDragObjectRemoved(uninstallSuccessful); - } - }; - launcher.addOnResumeCallback(checkIfUninstallWasSuccess); - } else { - callback.onDragObjectRemoved(false); - } - } - - public interface DropTargetResultCallback { - /** - * A drag operation was complete. - * @param isRemoved true if the drag object should be removed, false otherwise. - */ - void onDragObjectRemoved(boolean isRemoved); - } - - /** - * Interface defining an object that can provide uninstallable drag objects. - */ - public interface DropTargetSource extends DropTargetResultCallback { - - /** - * Indicates that an uninstall request are made and the actual result may come - * after some time. - */ - void deferCompleteDropAfterUninstallActivity(); } } diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index b6876f6708..e6bc770663 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -309,6 +309,9 @@ public final class Utilities { float highScore = -1; int bestHue = -1; + int[] pixels = new int[samples]; + int pixelCount = 0; + for (int y = 0; y < height; y += sampleStride) { for (int x = 0; x < width; x += sampleStride) { int argb = bitmap.getPixel(x, y); @@ -326,6 +329,9 @@ public final class Utilities { // Defensively avoid array bounds violations. continue; } + if (pixelCount < samples) { + pixels[pixelCount++] = rgb; + } float score = hsv[1] * hsv[2]; hueScoreHistogram[hue] += score; if (hueScoreHistogram[hue] > highScore) { @@ -335,31 +341,29 @@ public final class Utilities { } } - SparseArray rgbScores = new SparseArray(); + SparseArray rgbScores = new SparseArray<>(); int bestColor = 0xff000000; highScore = -1; // Go back over the RGB colors that match the winning hue, // creating a histogram of weighted s*v scores, for up to 100*100 [s,v] buckets. // The highest-scoring RGB color wins. - for (int y = 0; y < height; y += sampleStride) { - for (int x = 0; x < width; x += sampleStride) { - int rgb = bitmap.getPixel(x, y) | 0xff000000; - Color.colorToHSV(rgb, hsv); - int hue = (int) hsv[0]; - if (hue == bestHue) { - float s = hsv[1]; - float v = hsv[2]; - int bucket = (int) (s * 100) + (int) (v * 10000); - // Score by cumulative saturation * value. - float score = s * v; - Float oldTotal = rgbScores.get(bucket); - float newTotal = oldTotal == null ? score : oldTotal + score; - rgbScores.put(bucket, newTotal); - if (newTotal > highScore) { - highScore = newTotal; - // All the colors in the winning bucket are very similar. Last in wins. - bestColor = rgb; - } + for (int i = 0; i < pixelCount; i++) { + int rgb = pixels[i]; + Color.colorToHSV(rgb, hsv); + int hue = (int) hsv[0]; + if (hue == bestHue) { + float s = hsv[1]; + float v = hsv[2]; + int bucket = (int) (s * 100) + (int) (v * 10000); + // Score by cumulative saturation * value. + float score = s * v; + Float oldTotal = rgbScores.get(bucket); + float newTotal = oldTotal == null ? score : oldTotal + score; + rgbScores.put(bucket, newTotal); + if (newTotal > highScore) { + highScore = newTotal; + // All the colors in the winning bucket are very similar. Last in wins. + bestColor = rgb; } } } @@ -610,6 +614,12 @@ public final class Utilities { 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 0000000000..b3dc176b70 --- /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 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/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java index a65ea9b10b..bdfeae1627 100644 --- a/src/com/android/launcher3/WidgetPreviewLoader.java +++ b/src/com/android/launcher3/WidgetPreviewLoader.java @@ -22,7 +22,6 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.AsyncTask; -import android.os.Build; import android.os.CancellationSignal; import android.os.Handler; import android.os.UserHandle; @@ -93,12 +92,11 @@ public class WidgetPreviewLoader { * @return a request id which can be used to cancel the request. */ public CancellationSignal getPreview(WidgetItem item, int previewWidth, - int previewHeight, WidgetCell caller, boolean animate) { + int previewHeight, WidgetCell caller) { String size = previewWidth + "x" + previewHeight; WidgetCacheKey key = new WidgetCacheKey(item.componentName, item.user, size); - PreviewLoadTask task = new PreviewLoadTask(key, item, previewWidth, previewHeight, caller, - animate); + PreviewLoadTask task = new PreviewLoadTask(key, item, previewWidth, previewHeight, caller); task.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR); CancellationSignal signal = new CancellationSignal(); @@ -412,7 +410,8 @@ public class WidgetPreviewLoader { // Draw icon in the center. try { - Drawable icon = info.getIcon(launcher, mIconCache); + Drawable icon = + mIconCache.getFullResIcon(info.provider.getPackageName(), info.icon); if (icon != null) { int appIconSize = launcher.getDeviceProfile().iconSizePx; int iconSize = (int) Math.min(appIconSize * scale, @@ -527,19 +526,17 @@ public class WidgetPreviewLoader { private final int mPreviewHeight; private final int mPreviewWidth; private final WidgetCell mCaller; - private final boolean mAnimatePreviewIn; private final BaseActivity mActivity; @Thunk long[] mVersions; @Thunk Bitmap mBitmapToRecycle; PreviewLoadTask(WidgetCacheKey key, WidgetItem info, int previewWidth, - int previewHeight, WidgetCell caller, boolean animate) { + int previewHeight, WidgetCell caller) { mKey = key; mInfo = info; mPreviewHeight = previewHeight; mPreviewWidth = previewWidth; mCaller = caller; - mAnimatePreviewIn = animate; mActivity = BaseActivity.fromContext(mCaller.getContext()); if (DEBUG) { Log.d(TAG, String.format("%s, %s, %d, %d", @@ -595,7 +592,7 @@ public class WidgetPreviewLoader { @Override protected void onPostExecute(final Bitmap preview) { - mCaller.applyPreview(preview, mAnimatePreviewIn); + mCaller.applyPreview(preview); // Write the generated preview to the DB in the worker thread if (mVersions != null) { diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 37ee6d751b..daa9bd0119 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -16,6 +16,14 @@ package com.android.launcher3; +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; import android.animation.AnimatorSet; @@ -46,22 +54,17 @@ import android.util.SparseArray; 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.animation.DecelerateInterpolator; -import android.view.animation.Interpolator; import android.widget.Toast; -import com.android.launcher3.Launcher.CustomContentCallbacks; import com.android.launcher3.Launcher.LauncherOverlay; import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener; -import com.android.launcher3.UninstallDropTarget.DropTargetSource; +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; @@ -77,6 +80,8 @@ import com.android.launcher3.graphics.DragPreviewProvider; import com.android.launcher3.graphics.PreloadIconDrawable; import com.android.launcher3.popup.PopupContainerWithArrow; import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider; +import com.android.launcher3.uioverrides.OverviewState; +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; @@ -84,7 +89,6 @@ import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Thunk; -import com.android.launcher3.util.VerticalFlingDetector; import com.android.launcher3.util.WallpaperOffsetInterpolator; import com.android.launcher3.widget.PendingAddShortcutInfo; import com.android.launcher3.widget.PendingAddWidgetInfo; @@ -101,7 +105,7 @@ import java.util.Set; public class Workspace extends PagedView implements DropTarget, DragSource, View.OnTouchListener, DragController.DragListener, ViewGroup.OnHierarchyChangeListener, - Insettable, DropTargetSource { + Insettable { private static final String TAG = "Launcher.Workspace"; /** The value that {@link #mTransitionProgress} must be greater than for @@ -119,6 +123,8 @@ public class Workspace extends PagedView private static final int ADJACENT_SCREEN_DROP_DURATION = 300; + private static final int DEFAULT_PAGE = 0; + private static final boolean MAP_NO_RECURSE = false; private static final boolean MAP_RECURSE = true; @@ -127,12 +133,6 @@ public class Workspace extends PagedView // The is the first screen. It is always present, even if its empty. public static final long FIRST_SCREEN_ID = 0; - private final static long CUSTOM_CONTENT_SCREEN_ID = -301; - - private static final long CUSTOM_CONTENT_GESTURE_DELAY = 200; - private long mTouchDownTime = -1; - private long mCustomContentShowTime = -1; - private LayoutTransition mLayoutTransition; @Thunk final WallpaperManager mWallpaperManager; @@ -156,11 +156,6 @@ public class Workspace extends PagedView private int mDragOverX = -1; private int mDragOverY = -1; - CustomContentCallbacks mCustomContentCallbacks; - boolean mCustomContentShowing; - private float mLastCustomContentScrollProgress = -1f; - private String mCustomContentDescription = ""; - /** * The CellLayout that is currently being dragged over */ @@ -178,37 +173,11 @@ public class Workspace extends PagedView @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; - - // State variable that indicates whether the pages are small (ie when you're - // in all apps or customize mode) - - public enum State { - NORMAL (false, false, ContainerType.WORKSPACE), - NORMAL_HIDDEN (false, false, ContainerType.ALLAPPS), - SPRING_LOADED (false, true, ContainerType.WORKSPACE), - OVERVIEW (true, true, ContainerType.OVERVIEW), - OVERVIEW_HIDDEN (true, false, ContainerType.WIDGETS); - - public final boolean shouldUpdateWidget; - public final boolean hasMultipleVisiblePages; - public final int containerType; - - State(boolean shouldUpdateWidget, boolean hasMultipleVisiblePages, int containerType) { - this.shouldUpdateWidget = shouldUpdateWidget; - this.hasMultipleVisiblePages = hasMultipleVisiblePages; - this.containerType = containerType; - } - } // Direction used for moving the workspace and hotseat UI public enum Direction { @@ -235,11 +204,8 @@ public class Workspace extends PagedView */ private final float[] mHotseatAlpha = new float[] {1, 1, 1}; - @ViewDebug.ExportedProperty(category = "launcher") - private State mState = State.NORMAL; private boolean mIsSwitchingState = false; - boolean mAnimatingViewIntoPlace = false; boolean mChildrenLayersEnabled = true; private boolean mStripScreensOnPageStopMoving = false; @@ -250,9 +216,6 @@ public class Workspace extends PagedView final WallpaperOffsetInterpolator mWallpaperOffset; private boolean mUnlockWallpaperFromDefaultPageOnLayout; - @Thunk Runnable mDelayedResizeRunnable; - private Runnable mDelayedSnapToPageRunnable; - // Variables relating to the creation of user folders by hovering shortcuts over shortcuts private static final int FOLDER_CREATION_TIMEOUT = 0; public static final int REORDER_TIMEOUT = 350; @@ -264,8 +227,6 @@ public class Workspace extends PagedView private boolean mAddToExistingFolderOnDrop = false; private float mMaxDistanceForFolderCreation; - private final Canvas mCanvas = new Canvas(); - // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget) private float mXDown; private float mYDown; @@ -295,10 +256,6 @@ public class Workspace extends PagedView private float mCurrentScale; private float mTransitionProgress; - @Thunk Runnable mDeferredAction; - private boolean mDeferDropAfterUninstall; - private boolean mUninstallSuccessful; - // State related to Launcher Overlay LauncherOverlay mLauncherOverlay; boolean mScrollInteractionBegan; @@ -307,6 +264,8 @@ public class Workspace extends PagedView boolean mOverlayShown = false; private boolean mForceDrawAdjacentPages = false; + private boolean mPageRearrangeEnabled = false; + // Total over scrollX in the overlay direction. private float mOverlayTranslation; @@ -343,8 +302,6 @@ public class Workspace extends PagedView mWallpaperManager = WallpaperManager.getInstance(context); mWallpaperOffset = new WallpaperOffsetInterpolator(this); - mOverviewModeShrinkFactor = - res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100f; setOnHierarchyChangeListener(this); setHapticFeedbackEnabled(false); @@ -358,29 +315,18 @@ public class Workspace extends PagedView @Override public void setInsets(Rect insets) { mInsets.set(insets); - - CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID); - if (customScreen != null) { - View customContent = customScreen.getShortcutsAndWidgets().getChildAt(0); - if (customContent instanceof Insettable) { - ((Insettable) customContent).setInsets(mInsets); - } - } } /** * Estimates the size of an item using spans: hSpan, vSpan. * - * @param springLoaded True if we are in spring loaded mode. - * @param unscaledSize True if caller wants to return the unscaled size * @return MAX_VALUE for each dimension if unsuccessful. */ - public int[] estimateItemSize(ItemInfo itemInfo, boolean springLoaded, boolean unscaledSize) { - float shrinkFactor = mLauncher.getDeviceProfile().workspaceSpringLoadShrinkFactor; + public int[] estimateItemSize(ItemInfo itemInfo) { int[] size = new int[2]; if (getChildCount() > 0) { - // Use the first non-custom page to estimate the child position - CellLayout cl = (CellLayout) getChildAt(numCustomPages()); + // Use the first page to estimate the child position + CellLayout cl = (CellLayout) getChildAt(0); boolean isWidget = itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; Rect r = estimateItemPosition(cl, 0, 0, itemInfo.spanX, itemInfo.spanY); @@ -393,15 +339,10 @@ public class Workspace extends PagedView size[0] = r.width(); size[1] = r.height(); - if (isWidget && unscaledSize) { + if (isWidget) { size[0] /= scale; size[1] /= scale; } - - if (springLoaded) { - size[0] *= shrinkFactor; - size[1] *= shrinkFactor; - } return size; } else { size[0] = Integer.MAX_VALUE; @@ -428,16 +369,15 @@ public class Workspace extends PagedView } if (mOutlineProvider != null) { - // The outline is used to visualize where the item will land if dropped - mOutlineProvider.generateDragOutline(mCanvas); + if (dragObject.dragView != null) { + Bitmap preview = dragObject.dragView.getPreviewBitmap(); + + // The outline is used to visualize where the item will land if dropped + mOutlineProvider.generateDragOutline(preview); + } } - updateChildrenLayersEnabled(false); - mLauncher.onDragStarted(); - mLauncher.lockScreenOrientation(); - mLauncher.onInteractionBegin(); - // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging - InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_DRAG_AND_DROP); + updateChildrenLayersEnabled(); // Do not add a new page if it is a accessible drag which was not started by the workspace. // We do not support accessibility drag from other sources and instead provide a direct @@ -469,7 +409,7 @@ public class Workspace extends PagedView } // Always enter the spring loaded mode - mLauncher.enterSpringLoadedDragMode(); + mLauncher.getStateManager().goToState(SPRING_LOADED); } public void deferRemoveExtraEmptyScreen() { @@ -486,30 +426,23 @@ public class Workspace extends PagedView removeExtraEmptyScreen(true, mDragSourceInternal != null); } - updateChildrenLayersEnabled(false); - mLauncher.unlockScreenOrientation(false); - - // Re-enable any Un/InstallShortcutReceiver and now process any queued items - InstallShortcutReceiver.disableAndFlushInstallQueue( - InstallShortcutReceiver.FLAG_DRAG_AND_DROP, getContext()); - + updateChildrenLayersEnabled(); mOutlineProvider = null; - mDragInfo = null; mDragSourceInternal = null; - mLauncher.onInteractionEnd(); } /** * Initializes various states for this workspace. */ protected void initWorkspace() { - mCurrentPage = getDefaultPage(); + mCurrentPage = DEFAULT_PAGE; DeviceProfile grid = mLauncher.getDeviceProfile(); setWillNotDraw(false); setClipChildren(false); setClipToPadding(false); - setMinScale(mOverviewModeShrinkFactor); + // TODO: Remove this + setMinScale(OverviewState.SCALE_FACTOR); setupLayoutTransition(); mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx); @@ -521,11 +454,7 @@ public class Workspace extends PagedView @Override public void initParentViews(View parent) { super.initParentViews(parent); - mPageIndicator.setAccessibilityDelegate(new OverviewAccessibilityDelegate()); - } - - private int getDefaultPage() { - return numCustomPages(); + mPageIndicator.setAccessibilityDelegate(UiFactory.newPageIndicatorAccessibilityDelegate()); } private void setupLayoutTransition() { @@ -571,33 +500,6 @@ public class Workspace extends PagedView } // Add the first page CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, 0); - if (FeatureFlags.PULLDOWN_SEARCH) { - firstPage.setOnTouchListener(new VerticalFlingDetector(mLauncher) { - // detect fling when touch started from empty space - @Override - public boolean onTouch(View v, MotionEvent ev) { - if (workspaceInModalState()) return false; - if (shouldConsumeTouch(v)) return true; - if (super.onTouch(v, ev)) { - mLauncher.startSearch("", false, null, false); - return true; - } - return false; - } - }); - firstPage.setOnInterceptTouchListener(new VerticalFlingDetector(mLauncher) { - // detect fling when touch started from on top of the icons - @Override - public boolean onTouch(View v, MotionEvent ev) { - if (shouldConsumeTouch(v)) return true; - if (super.onTouch(v, ev)) { - mLauncher.startSearch("", false, null, false); - return true; - } - return false; - } - }); - } // Always add a QSB on the first screen. if (qsb == null) { // In transposed layout, we add the QSB in the Grid. As workspace does not touch the @@ -615,16 +517,9 @@ public class Workspace extends PagedView public void removeAllWorkspaceScreens() { // Disable all layout transitions before removing all pages to ensure that we don't get the - // transition animations competing with us changing the scroll when we add pages or the - // custom content screen + // transition animations competing with us changing the scroll when we add pages disableLayoutTransitions(); - // Since we increment the current page when we call addCustomContentPage via bindScreens - // (and other places), we need to adjust the current page back when we clear the pages - if (hasCustomContent()) { - removeCustomContentPage(); - } - // Recycle the QSB widget View qsb = findViewById(R.id.search_container_workspace); if (qsb != null) { @@ -677,6 +572,8 @@ public class Workspace extends PagedView 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); @@ -685,78 +582,6 @@ public class Workspace extends PagedView return newScreen; } - public void createCustomContentContainer() { - CellLayout customScreen = (CellLayout) - LayoutInflater.from(getContext()).inflate(R.layout.workspace_screen, this, false); - customScreen.disableDragTarget(); - customScreen.disableJailContent(); - - mWorkspaceScreens.put(CUSTOM_CONTENT_SCREEN_ID, customScreen); - mScreenOrder.add(0, CUSTOM_CONTENT_SCREEN_ID); - - // We want no padding on the custom content - customScreen.setPadding(0, 0, 0, 0); - - addFullScreenPage(customScreen); - - // Update the custom content hint - setCurrentPage(getCurrentPage() + 1); - } - - public void removeCustomContentPage() { - CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID); - if (customScreen == null) { - throw new RuntimeException("Expected custom content screen to exist"); - } - - mWorkspaceScreens.remove(CUSTOM_CONTENT_SCREEN_ID); - mScreenOrder.remove(CUSTOM_CONTENT_SCREEN_ID); - removeView(customScreen); - - if (mCustomContentCallbacks != null) { - mCustomContentCallbacks.onScrollProgressChanged(0); - mCustomContentCallbacks.onHide(); - } - - mCustomContentCallbacks = null; - - // Update the custom content hint - setCurrentPage(getCurrentPage() - 1); - } - - public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks, - String description) { - if (getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID) < 0) { - throw new RuntimeException("Expected custom content screen to exist"); - } - - // Add the custom content to the full screen custom page - CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID); - int spanX = customScreen.getCountX(); - int spanY = customScreen.getCountY(); - CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, spanX, spanY); - lp.canReorder = false; - lp.isFullscreen = true; - if (customContent instanceof Insettable) { - ((Insettable)customContent).setInsets(mInsets); - } - - // Verify that the child is removed from any existing parent. - if (customContent.getParent() instanceof ViewGroup) { - ViewGroup parent = (ViewGroup) customContent.getParent(); - parent.removeView(customContent); - } - customScreen.removeAllViews(); - customContent.setFocusable(true); - customContent.setOnKeyListener(new FullscreenKeyEventListener()); - customContent.setOnFocusChangeListener(mLauncher.mFocusHandler - .getHideIndicatorOnFocusListener()); - customScreen.addViewToCellLayout(customContent, 0, 0, lp, true); - mCustomContentDescription = description; - - mCustomContentCallbacks = callbacks; - } - public void addExtraEmptyScreenOnDrag() { boolean lastChildOnScreen = false; boolean childOnFinalScreen = false; @@ -800,7 +625,6 @@ public class Workspace extends PagedView if (hasExtraEmptyScreen() || mScreenOrder.size() == 0) return; long finalScreenId = mScreenOrder.get(mScreenOrder.size() - 1); - if (finalScreenId == CUSTOM_CONTENT_SCREEN_ID) return; CellLayout finalScreen = mWorkspaceScreens.get(finalScreenId); // If the final screen is empty, convert it to the extra empty screen @@ -809,7 +633,7 @@ public class Workspace extends PagedView mWorkspaceScreens.remove(finalScreenId); mScreenOrder.remove(finalScreenId); - // if this is the last non-custom content screen, convert it to the empty screen + // if this is the last screen, convert it to the empty screen mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, finalScreen); mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID); @@ -905,9 +729,7 @@ public class Workspace extends PagedView } public boolean hasExtraEmptyScreen() { - int nScreens = getChildCount(); - nScreens = nScreens - numCustomPages(); - return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && nScreens > 1; + return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && getChildCount() > 1; } public long commitExtraEmptyScreen() { @@ -988,7 +810,7 @@ public class Workspace extends PagedView // We enforce at least one page to add new items to. In the case that we remove the last // such screen, we convert the last screen to the empty screen - int minScreens = 1 + numCustomPages(); + int minScreens = 1; int pageShift = 0; for (Long id: removeScreens) { @@ -1007,7 +829,7 @@ public class Workspace extends PagedView removeView(cl); } else { - // if this is the last non-custom content screen, convert it to the empty screen + // if this is the last screen, convert it to the empty screen mRemoveEmptyScreenRunnable = null; mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl); mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID); @@ -1156,10 +978,6 @@ public class Workspace extends PagedView || (mTransitionProgress > FINISHED_SWITCHING_STATE_TRANSITION_PROGRESS); } - protected void onWindowVisibilityChanged (int visibility) { - mLauncher.onWindowVisibilityChanged(visibility); - } - @Override public boolean dispatchUnhandledMove(View focused, int direction) { if (workspaceInModalState() || !isFinishedSwitchingState()) { @@ -1175,7 +993,6 @@ public class Workspace extends PagedView case MotionEvent.ACTION_DOWN: mXDown = ev.getX(); mYDown = ev.getY(); - mTouchDownTime = System.currentTimeMillis(); break; case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_UP: @@ -1189,41 +1006,6 @@ public class Workspace extends PagedView return super.onInterceptTouchEvent(ev); } - @Override - public boolean onGenericMotionEvent(MotionEvent event) { - // Ignore pointer scroll events if the custom content doesn't allow scrolling. - if ((getScreenIdForPageIndex(getCurrentPage()) == CUSTOM_CONTENT_SCREEN_ID) - && (mCustomContentCallbacks != null) - && !mCustomContentCallbacks.isScrollingAllowed()) { - return false; - } - return super.onGenericMotionEvent(event); - } - - protected void reinflateWidgetsIfNecessary() { - final int clCount = getChildCount(); - for (int i = 0; i < clCount; i++) { - CellLayout cl = (CellLayout) getChildAt(i); - ShortcutAndWidgetContainer swc = cl.getShortcutsAndWidgets(); - final int itemCount = swc.getChildCount(); - for (int j = 0; j < itemCount; j++) { - View v = swc.getChildAt(j); - - if (v instanceof LauncherAppWidgetHostView - && v.getTag() instanceof LauncherAppWidgetInfo) { - LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag(); - LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) v; - if (lahv.isReinflateRequired(mLauncher.getOrientation())) { - // Remove and rebind the current widget (which was inflated in the wrong - // orientation), but don't delete it from the database - mLauncher.removeItem(lahv, info, false /* deleteFromDb */); - mLauncher.bindAppWidget(info); - } - } - } - } - } - @Override protected void determineScrollingStart(MotionEvent ev) { if (!isFinishedSwitchingState()) return; @@ -1241,24 +1023,6 @@ public class Workspace extends PagedView cancelCurrentPageLongPress(); } - boolean passRightSwipesToCustomContent = - (mTouchDownTime - mCustomContentShowTime) > CUSTOM_CONTENT_GESTURE_DELAY; - - boolean swipeInIgnoreDirection = mIsRtl ? deltaX < 0 : deltaX > 0; - boolean onCustomContentScreen = - getScreenIdForPageIndex(getCurrentPage()) == CUSTOM_CONTENT_SCREEN_ID; - if (swipeInIgnoreDirection && onCustomContentScreen && passRightSwipesToCustomContent) { - // Pass swipes to the right to the custom content page. - return; - } - - if (onCustomContentScreen && (mCustomContentCallbacks != null) - && !mCustomContentCallbacks.isScrollingAllowed()) { - // Don't allow workspace scrolling if the current custom content screen doesn't allow - // scrolling. - return; - } - if (theta > MAX_SWIPE_ANGLE) { // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace return; @@ -1279,12 +1043,12 @@ public class Workspace extends PagedView protected void onPageBeginTransition() { super.onPageBeginTransition(); - updateChildrenLayersEnabled(false); + updateChildrenLayersEnabled(); } protected void onPageEndTransition() { super.onPageEndTransition(); - updateChildrenLayersEnabled(false); + updateChildrenLayersEnabled(); if (mDragController.isDragging()) { if (workspaceInModalState()) { @@ -1294,15 +1058,6 @@ public class Workspace extends PagedView } } - if (mDelayedResizeRunnable != null && !mIsSwitchingState) { - mDelayedResizeRunnable.run(); - mDelayedResizeRunnable = null; - } - - if (mDelayedSnapToPageRunnable != null) { - mDelayedSnapToPageRunnable.run(); - mDelayedSnapToPageRunnable = null; - } if (mStripScreensOnPageStopMoving) { stripEmptyScreens(); mStripScreensOnPageStopMoving = false; @@ -1364,11 +1119,10 @@ public class Workspace extends PagedView } updatePageAlphaValues(); - updateStateForCustomContent(); enableHwLayersOnVisiblePages(); } - private void showPageIndicatorAtCurrentScroll() { + public void showPageIndicatorAtCurrentScroll() { if (mPageIndicator != null) { mPageIndicator.setScroll(getScrollX(), computeMaxScrollX()); } @@ -1376,9 +1130,6 @@ public class Workspace extends PagedView @Override protected void overScroll(float amount) { - boolean shouldOverScroll = (amount <= 0 && (!hasCustomContent() || mIsRtl)) || - (amount >= 0 && (!hasCustomContent() || !mIsRtl)); - boolean shouldScrollOverlay = mLauncherOverlay != null && ((amount <= 0 && !mIsRtl) || (amount >= 0 && mIsRtl)); @@ -1393,7 +1144,7 @@ public class Workspace extends PagedView mLastOverlayScroll = Math.abs(amount / getViewportWidth()); mLauncherOverlay.onScrollChange(mLastOverlayScroll, mIsRtl); - } else if (shouldOverScroll) { + } else { dampedOverScroll(amount); } @@ -1409,8 +1160,6 @@ public class Workspace extends PagedView super.shouldFlingForVelocity(velocityX); } - private final Interpolator mAlphaInterpolator = new DecelerateInterpolator(3f); - /** * The overlay scroll is being controlled locally, just update our overlay effect */ @@ -1435,7 +1184,7 @@ public class Workspace extends PagedView 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; @@ -1527,9 +1276,7 @@ public class Workspace extends PagedView } }); - AccessibilityManager am = (AccessibilityManager) - mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE); - final boolean accessibilityEnabled = am.isEnabled(); + final boolean accessibilityEnabled = isAccessibilityEnabled(mLauncher); animator.addUpdateListener( new AlphaUpdateListener(mLauncher.getHotseat(), accessibilityEnabled)); animator.addUpdateListener( @@ -1546,22 +1293,6 @@ public class Workspace extends PagedView mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE, swipeDirection, ContainerType.WORKSPACE, prevPage); } - if (hasCustomContent() && getNextPage() == 0 && !mCustomContentShowing) { - mCustomContentShowing = true; - if (mCustomContentCallbacks != null) { - mCustomContentCallbacks.onShow(false); - mCustomContentShowTime = System.currentTimeMillis(); - } - } else if (hasCustomContent() && getNextPage() != 0 && mCustomContentShowing) { - mCustomContentShowing = false; - if (mCustomContentCallbacks != null) { - mCustomContentCallbacks.onHide(); - } - } - } - - protected CustomContentCallbacks getCustomContentCallbacks() { - return mCustomContentCallbacks; } protected void setWallpaperDimension() { @@ -1588,26 +1319,6 @@ public class Workspace extends PagedView } } - protected void snapToPage(int whichPage, Runnable r) { - snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION, r); - } - - protected void snapToPage(int whichPage, int duration, Runnable r) { - if (mDelayedSnapToPageRunnable != null) { - mDelayedSnapToPageRunnable.run(); - } - mDelayedSnapToPageRunnable = r; - snapToPage(whichPage, duration); - } - - public void snapToScreenId(long screenId) { - snapToScreenId(screenId, null); - } - - protected void snapToScreenId(long screenId, Runnable r) { - snapToPage(getPageIndexForScreenId(screenId), r); - } - @Override public void computeScroll() { super.computeScroll(); @@ -1628,7 +1339,7 @@ public class Workspace extends PagedView @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); } } @@ -1642,7 +1353,7 @@ public class Workspace extends PagedView private void updatePageAlphaValues() { if (!workspaceInModalState() && !mIsSwitchingState) { int screenCenter = getScrollX() + getViewportWidth() / 2; - for (int i = numCustomPages(); i < getChildCount(); i++) { + for (int i = 0; i < getChildCount(); i++) { CellLayout child = (CellLayout) getChildAt(i); if (child != null) { float scrollProgress = getScrollProgress(screenCenter, child, i); @@ -1660,66 +1371,6 @@ public class Workspace extends PagedView } } - public boolean hasCustomContent() { - return (mScreenOrder.size() > 0 && mScreenOrder.get(0) == CUSTOM_CONTENT_SCREEN_ID); - } - - public int numCustomPages() { - return hasCustomContent() ? 1 : 0; - } - - public boolean isOnOrMovingToCustomContent() { - return hasCustomContent() && getNextPage() == 0; - } - - private void updateStateForCustomContent() { - float translationX = 0; - float progress = 0; - if (hasCustomContent()) { - int index = mScreenOrder.indexOf(CUSTOM_CONTENT_SCREEN_ID); - - int scrollDelta = getScrollX() - getScrollForPage(index) - - getLayoutTransitionOffsetForPage(index); - float scrollRange = getScrollForPage(index + 1) - getScrollForPage(index); - translationX = scrollRange - scrollDelta; - progress = (scrollRange - scrollDelta) / scrollRange; - - if (mIsRtl) { - translationX = Math.min(0, translationX); - } else { - translationX = Math.max(0, translationX); - } - progress = Math.max(0, progress); - } - - if (Float.compare(progress, mLastCustomContentScrollProgress) == 0) return; - - CellLayout cc = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID); - if (progress > 0 && cc.getVisibility() != VISIBLE && !workspaceInModalState()) { - cc.setVisibility(VISIBLE); - } - - mLastCustomContentScrollProgress = progress; - - // We should only update the drag layer background alpha if we are not in all apps or the - // widgets tray - if (mState == State.NORMAL) { - mLauncher.getDragLayer().setBackgroundAlpha(progress == 1 ? 0 : progress * 0.8f); - } - - if (mLauncher.getHotseat() != null) { - mLauncher.getHotseat().setTranslationX(translationX); - } - - if (mPageIndicator != null) { - mPageIndicator.setTranslationX(translationX); - } - - if (mCustomContentCallbacks != null) { - mCustomContentCallbacks.onScrollProgressChanged(progress); - } - } - protected void onAttachedToWindow() { super.onAttachedToWindow(); IBinder windowToken = getWindowToken(); @@ -1733,10 +1384,6 @@ public class Workspace extends PagedView mWallpaperOffset.setWindowToken(null); } - protected void onResume() { - mWallpaperOffset.onResume(); - } - @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (mUnlockWallpaperFromDefaultPageOnLayout) { @@ -1759,18 +1406,18 @@ public class Workspace extends PagedView return super.getDescendantFocusability(); } - public boolean workspaceInModalState() { - return mState != State.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 == State.NORMAL || mState == State.SPRING_LOADED; + return mLauncher.isInState(NORMAL) || mLauncher.isInState(SPRING_LOADED); } - @Thunk void updateChildrenLayersEnabled(boolean force) { - boolean small = mState == State.OVERVIEW || mIsSwitchingState; - boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageInTransition(); + private void updateChildrenLayersEnabled() { + boolean enableChildrenLayers = + isPageRearrangeEnabled() || mIsSwitchingState || isPageInTransition(); if (enableChildrenLayers != mChildrenLayersEnabled) { mChildrenLayersEnabled = enableChildrenLayers; @@ -1800,7 +1447,7 @@ public class Workspace extends PagedView int leftScreen = -1; int rightScreen = -1; - for (int i = numCustomPages(); i < screenCount; i++) { + for (int i = 0; i < screenCount; i++) { final View child = getPageAt(i); float left = child.getLeft() + child.getTranslationX() - getScrollX(); @@ -1813,8 +1460,7 @@ public class Workspace extends PagedView } if (mForceDrawAdjacentPages) { // In overview mode, make sure that the two side pages are visible. - leftScreen = Utilities.boundToRange(getCurrentPage() - 1, - numCustomPages(), rightScreen); + leftScreen = Utilities.boundToRange(getCurrentPage() - 1, 0, rightScreen); rightScreen = Utilities.boundToRange(getCurrentPage() + 1, leftScreen, getPageCount() - 1); } @@ -1828,7 +1474,7 @@ public class Workspace extends PagedView } } - for (int i = numCustomPages(); i < screenCount; i++) { + for (int i = 0; i < screenCount; i++) { final CellLayout layout = (CellLayout) getPageAt(i); // enable layers between left and right screen inclusive. boolean enableLayer = leftScreen <= i && i <= rightScreen; @@ -1837,19 +1483,6 @@ public class Workspace extends PagedView } } - public void buildPageHardwareLayers() { - // force layers to be enabled just for the call to buildLayer - updateChildrenLayersEnabled(true); - if (getWindowToken() != null) { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - CellLayout cl = (CellLayout) getChildAt(i); - cl.buildHardwareLayer(); - } - } - updateChildrenLayersEnabled(false); - } - protected void onWallpaperTap(MotionEvent ev) { final int[] position = mTempXY; getLocationOnScreen(position); @@ -1868,24 +1501,6 @@ public class Workspace extends PagedView mOutlineProvider = outlineProvider; } - public void exitWidgetResizeMode() { - DragLayer dragLayer = mLauncher.getDragLayer(); - dragLayer.clearResizeFrame(); - } - - @Override - protected void getFreeScrollPageRange(int[] range) { - getOverviewModePages(range); - } - - private void getOverviewModePages(int[] range) { - int start = numCustomPages(); - int end = getChildCount() - 1; - - range[0] = Math.max(0, Math.min(start, getChildCount() - 1)); - range[1] = Math.max(0, end); - } - public void onStartReordering() { super.onStartReordering(); // Reordering handles its own animations, disable the automatic ones. @@ -1920,115 +1535,71 @@ public class Workspace extends PagedView enableLayoutTransitions(); } - public boolean isInOverviewMode() { - return mState == State.OVERVIEW; - } - public void snapToPageFromOverView(int whichPage) { - mStateTransitionAnimation.snapToPageFromOverView(whichPage); + 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; - } - - float getOverviewModeShrinkFactor() { - return mOverviewModeShrinkFactor; + updateChildrenLayersEnabled(); + updateAccessibilityFlags(); } /** - * Sets the current workspace {@link State}, returning an animation transitioning the workspace - * to that new state. + * Sets the current workspace {@link LauncherState} and updates the UI without any animations */ - public Animator setStateWithAnimation(State toState, boolean animated, - AnimationLayerSet layerViews) { - final State fromState = mState; - - // Update the current state - mState = toState; - - // Create the animation to the new state - AnimatorSet workspaceAnim = mStateTransitionAnimation.getAnimationToState(fromState, - toState, animated, layerViews); - - boolean shouldNotifyWidgetChange = !fromState.shouldUpdateWidget - && toState.shouldUpdateWidget; - - updateAccessibilityFlags(); - - if (shouldNotifyWidgetChange) { - mLauncher.notifyWidgetProvidersChanged(); - } - - onPrepareStateTransition(mState.hasMultipleVisiblePages); - - StateTransitionListener listener = new StateTransitionListener(); - if (animated) { - ValueAnimator stepAnimator = ValueAnimator.ofFloat(0, 1); - stepAnimator.addUpdateListener(listener); - - workspaceAnim.play(stepAnimator); - workspaceAnim.addListener(listener); - } else { - listener.onAnimationStart(null); - listener.onAnimationEnd(null); - } - - return workspaceAnim; + public void setState(LauncherState toState) { + onStartStateTransition(toState); + mStateTransitionAnimation.setState(toState); + onEndStateTransition(); } - public State getState() { - return mState; + /** + * Sets the current workspace {@link LauncherState}, then animates the UI + */ + public void setStateWithAnimation(LauncherState toState, AnimationLayerSet layerViews, + AnimatorSet anim, AnimationConfig config) { + StateTransitionListener listener = new StateTransitionListener(toState); + mStateTransitionAnimation.setStateWithAnimation(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(). + + ValueAnimator stepAnimator = ValueAnimator.ofFloat(0, 1); + stepAnimator.addUpdateListener(listener); + stepAnimator.setDuration(config.duration); + anim.play(stepAnimator); + anim.addListener(listener); } 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 = numCustomPages(); i < total; i++) { - updateAccessibilityFlags((CellLayout) getPageAt(i), i); + for (int i = 0; i < total; i++) { + updateAccessibilityFlags(accessibilityFlag, (CellLayout) getPageAt(i), i); } - setImportantForAccessibility((mState == State.NORMAL || mState == State.OVERVIEW) - ? IMPORTANT_FOR_ACCESSIBILITY_AUTO - : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + setImportantForAccessibility(accessibilityFlag); } } - private void updateAccessibilityFlags(CellLayout page, int pageNo) { - if (mState == State.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); @@ -2042,62 +1613,26 @@ public class Workspace extends PagedView page.setAccessibilityDelegate(mPagesAccessibilityDelegate); } } else { - int accessible = mState == State.NORMAL ? - IMPORTANT_FOR_ACCESSIBILITY_AUTO : - IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS; page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); - page.getShortcutsAndWidgets().setImportantForAccessibility(accessible); + page.getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag); page.setContentDescription(null); page.setAccessibilityDelegate(null); } } - public void onPrepareStateTransition(boolean multiplePagesVisible) { - mIsSwitchingState = true; - mTransitionProgress = 0; - - if (multiplePagesVisible) { - mForceDrawAdjacentPages = true; - } - invalidate(); // This will call dispatchDraw(), which calls getVisiblePages(). - - updateChildrenLayersEnabled(false); - hideCustomContentIfNecessary(); - } - - public void onEndStateTransition() { - mIsSwitchingState = false; - updateChildrenLayersEnabled(false); - showCustomContentIfNecessary(); - mForceDrawAdjacentPages = false; - mTransitionProgress = 1; - } - - void updateCustomContentVisibility() { - int visibility = mState == Workspace.State.NORMAL ? VISIBLE : INVISIBLE; - setCustomContentVisibility(visibility); - } - - void setCustomContentVisibility(int visibility) { - if (hasCustomContent()) { - mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(visibility); + public void setPageRearrangeEnabled(boolean isEnabled) { + if (mPageRearrangeEnabled != isEnabled) { + mPageRearrangeEnabled = isEnabled; + if (isEnabled) { + enableFreeScroll(); + } else { + disableFreeScroll(); + } } } - void showCustomContentIfNecessary() { - boolean show = mState == Workspace.State.NORMAL; - if (show && hasCustomContent()) { - mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(VISIBLE); - } - } - - void hideCustomContentIfNecessary() { - boolean hide = mState != Workspace.State.NORMAL; - if (hide && hasCustomContent()) { - disableLayoutTransitions(); - mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(INVISIBLE); - enableLayoutTransitions(); - } + public boolean isPageRearrangeEnabled() { + return mPageRearrangeEnabled; } public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) { @@ -2144,7 +1679,7 @@ public class Workspace extends PagedView mOutlineProvider = previewProvider; // The drag bitmap follows the touch point around on the screen - final Bitmap b = previewProvider.createDragBitmap(mCanvas); + final Bitmap b = previewProvider.createDragBitmap(); int halfPadding = previewProvider.previewPadding / 2; float scale = previewProvider.getScaleAndPosition(b, mTempXY); @@ -2191,19 +1726,19 @@ public class Workspace extends PagedView DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, dragObject, dragVisualizeOffset, dragRect, scale, dragOptions); - dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor()); - b.recycle(); + dv.setIntrinsicIconScaleFactor(dragOptions.intrinsicIconScaleFactor); return dv; } private boolean transitionStateShouldAllowDrop() { - return ((!isSwitchingState() || mTransitionProgress > ALLOW_DROP_TRANSITION_PROGRESS) && - (mState == State.NORMAL || mState == State.SPRING_LOADED)); + return (!isSwitchingState() || mTransitionProgress > ALLOW_DROP_TRANSITION_PROGRESS) && + workspaceIconsCanBeDragged(); } /** * {@inheritDoc} */ + @Override public boolean acceptDrop(DragObject d) { // If it's an external drop (e.g. from All Apps), check if it should be accepted CellLayout dropTargetLayout = mDropToLayout; @@ -2335,8 +1870,7 @@ public class Workspace extends PagedView } boolean createUserFolderIfNecessary(View newView, long container, CellLayout target, - int[] targetCell, float distance, boolean external, DragView dragView, - Runnable postAnimationRunnable) { + int[] targetCell, float distance, boolean external, DragView dragView) { if (distance > mMaxDistanceForFolderCreation) return false; View v = target.getChildAt(targetCell[0], targetCell[1]); @@ -2380,8 +1914,8 @@ public class Workspace extends PagedView // folder background to the newly created icon. This preserves animation state. fi.setFolderBackground(mFolderCreateBg); mFolderCreateBg = new PreviewBackground(); - fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale, - postAnimationRunnable); + fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale + ); } else { fi.prepareCreateAnimation(v); fi.addItem(destInfo); @@ -2403,7 +1937,7 @@ public class Workspace extends PagedView if (dropOverView instanceof FolderIcon) { FolderIcon fi = (FolderIcon) dropOverView; if (fi.acceptDrop(d.dragInfo)) { - fi.onDrop(d); + fi.onDrop(d, false /* itemReturnedOnFailedDrop */); // if the drag started here, we need to remove it from the workspace if (!external) { @@ -2418,7 +1952,7 @@ public class Workspace extends PagedView @Override public void prepareAccessibilityDrop() { } - public void onDrop(final DragObject d) { + public void onDrop(final DragObject d, DragOptions options) { mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter); CellLayout dropTargetLayout = mDropToLayout; @@ -2435,13 +1969,14 @@ public class Workspace extends PagedView int snapScreen = -1; boolean resizeOnDrop = false; - if (d.dragSource != this) { + if (d.dragSource != this || mDragInfo == null) { final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1] }; onDropExternal(touchXY, dropTargetLayout, d); - } else if (mDragInfo != null) { + } else { final View cell = mDragInfo.cell; boolean droppedOnOriginalCellDuringTransition = false; + Runnable onCompleteRunnable = null; if (dropTargetLayout != null && !d.cancelled) { // Move internally @@ -2465,12 +2000,10 @@ public class Workspace extends PagedView // If the item being dropped is a shortcut and the nearest drop // cell also contains a shortcut, then create a folder with the two shortcuts. if (createUserFolderIfNecessary(cell, container, - dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) { - return; - } - - if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell, - distance, d, false)) { + dropTargetLayout, mTargetCell, distance, false, d.dragView) || + addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell, + distance, d, false)) { + mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); return; } @@ -2552,11 +2085,10 @@ public class Workspace extends PagedView AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo(); if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE && !d.accessibleDrag) { - mDelayedResizeRunnable = new Runnable() { + onCompleteRunnable = new Runnable() { public void run() { if (!isPageInTransition()) { - DragLayer dragLayer = mLauncher.getDragLayer(); - dragLayer.addResizeFrame(hostView, cellLayout); + AppWidgetResizeFrame.showForWidget(hostView, cellLayout); } } }; @@ -2580,24 +2112,13 @@ public class Workspace extends PagedView } final CellLayout parent = (CellLayout) cell.getParent().getParent(); - // Prepare it to be animated into its new position - // This must be called after the view has been re-parented - final Runnable onCompleteRunnable = new Runnable() { - @Override - public void run() { - mAnimatingViewIntoPlace = false; - updateChildrenLayersEnabled(false); - } - }; - mAnimatingViewIntoPlace = true; if (d.dragView.hasDrawn()) { if (droppedOnOriginalCellDuringTransition) { // Animate the item to its original position, while simultaneously exiting // spring-loaded mode so the page meets the icon where it was picked up. mLauncher.getDragController().animateDragViewToOriginalPosition( - mDelayedResizeRunnable, cell, - mStateTransitionAnimation.mSpringLoadedTransitionTime); - mLauncher.exitSpringLoadedDragMode(); + onCompleteRunnable, cell, SPRING_LOADED_TRANSITION_MS); + mLauncher.getStateManager().goToState(NORMAL); mLauncher.getDropTargetBar().onDragEnd(); parent.onDropChild(cell); return; @@ -2608,19 +2129,22 @@ public class Workspace extends PagedView if (isWidget) { int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE : ANIMATE_INTO_POSITION_AND_DISAPPEAR; - animateWidgetDrop(info, parent, d.dragView, - onCompleteRunnable, animationType, cell, false); + animateWidgetDrop(info, parent, d.dragView, null, animationType, cell, false); } else { int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION; mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration, - onCompleteRunnable, this); + this); } } else { d.deferDragViewCleanupPostAnimation = false; cell.setVisibility(VISIBLE); } parent.onDropChild(cell); + + mLauncher.getStateManager().goToState( + NORMAL, SPRING_LOADED_EXIT_DELAY, onCompleteRunnable); } + if (d.stateAnnouncer != null && !droppedOnOriginalCell) { d.stateAnnouncer.completeAction(R.string.item_moved); } @@ -2838,17 +2362,6 @@ public class Workspace extends PagedView xy[1] = mTempXY[1]; } - /* - * - * Convert the 2D coordinate xy from this CellLayout's coordinate space to - * the parent View's coordinate space. The argument xy is modified with the return result. - * - */ - void mapPointFromChildToSelf(View v, float[] xy) { - xy[0] += v.getLeft(); - xy[1] += v.getTop(); - } - private boolean isDragWidget(DragObject d) { return (d.dragInfo instanceof LauncherAppWidgetInfo || d.dragInfo instanceof PendingAddWidgetInfo); @@ -2977,7 +2490,7 @@ public class Workspace extends PagedView } // Always pick the current page. - if (layout == null && nextPage >= numCustomPages() && nextPage < getPageCount()) { + if (layout == null && nextPage >= 0 && nextPage < getPageCount()) { layout = (CellLayout) getChildAt(nextPage); } if (layout != mDragTargetLayout) { @@ -2992,7 +2505,7 @@ public class Workspace extends PagedView * Returns the child CellLayout if the point is inside the page coordinates, null otherwise. */ private CellLayout verifyInsidePage(int pageNo, float[] touchXy) { - if (pageNo >= numCustomPages() && pageNo < getPageCount()) { + if (pageNo >= 0 && pageNo < getPageCount()) { CellLayout cl = (CellLayout) getChildAt(pageNo); mapPointFromSelfToChild(cl, touchXy); if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() && @@ -3139,14 +2652,6 @@ public class Workspace extends PagedView * to add an item to one of the workspace screens. */ private void onDropExternal(final int[] touchXY, final CellLayout cellLayout, DragObject d) { - final Runnable exitSpringLoadedRunnable = new Runnable() { - @Override - public void run() { - mLauncher.exitSpringLoadedDragModeDelayed(true, - Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); - } - }; - if (d.dragInfo instanceof PendingAddShortcutInfo) { ShortcutInfo si = ((PendingAddShortcutInfo) d.dragInfo) .activityInfo.createShortcutInfo(); @@ -3169,8 +2674,8 @@ public class Workspace extends PagedView final long screenId = getIdForScreen(cellLayout); if (!mLauncher.isHotseatLayout(cellLayout) && screenId != getScreenIdForPageIndex(mCurrentPage) - && mState != State.SPRING_LOADED) { - snapToScreenId(screenId, null); + && !mLauncher.isInState(SPRING_LOADED)) { + snapToPage(getPageIndexForScreenId(screenId)); } if (info instanceof PendingAddItemInfo) { @@ -3244,6 +2749,8 @@ public class Workspace extends PagedView animationStyle, finalView, true); } else { // This is for other drag/drop cases, like dragging from All Apps + mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); + View view; switch (info.itemType) { @@ -3272,9 +2779,8 @@ public class Workspace extends PagedView cellLayout, mTargetCell); float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell); - d.postAnimationRunnable = exitSpringLoadedRunnable; if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance, - true, d.dragView, d.postAnimationRunnable)) { + true, d.dragView)) { return; } if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d, @@ -3305,16 +2811,15 @@ public class Workspace extends PagedView // We wrap the animation call in the temporary set and reset of the current // cellLayout to its final transform -- this means we animate the drag view to // the correct final location. - setFinalTransitionTransform(cellLayout); - mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view, - exitSpringLoadedRunnable, this); - resetTransitionTransform(cellLayout); + setFinalTransitionTransform(); + mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view, this); + resetTransitionTransform(); } } } public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) { - int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo, false, true); + int[] unScaledSize = estimateItemSize(widgetInfo); int visibility = layout.getVisibility(); layout.setVisibility(VISIBLE); @@ -3322,12 +2827,9 @@ public class Workspace extends PagedView int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY); Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1], Bitmap.Config.ARGB_8888); - mCanvas.setBitmap(b); - layout.measure(width, height); layout.layout(0, 0, unScaledSize[0], unScaledSize[1]); - layout.draw(mCanvas); - mCanvas.setBitmap(null); + layout.draw(new Canvas(b)); layout.setVisibility(visibility); return b; } @@ -3347,10 +2849,10 @@ public class Workspace extends PagedView loc[0] = r.left; loc[1] = r.top; - setFinalTransitionTransform(layout); + setFinalTransitionTransform(); float cellLayoutScale = mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true); - resetTransitionTransform(layout); + resetTransitionTransform(); if (scale) { float dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth(); @@ -3434,24 +2936,20 @@ public class Workspace extends PagedView } } - public void setFinalTransitionTransform(CellLayout layout) { + public void setFinalTransitionTransform() { if (isSwitchingState()) { mCurrentScale = getScaleX(); setScaleX(mStateTransitionAnimation.getFinalScale()); setScaleY(mStateTransitionAnimation.getFinalScale()); } } - public void resetTransitionTransform(CellLayout layout) { + public void resetTransitionTransform() { if (isSwitchingState()) { setScaleX(mCurrentScale); setScaleY(mCurrentScale); } } - 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/ @@ -3462,10 +2960,6 @@ public class Workspace extends PagedView return mDragInfo; } - public int getCurrentPageOffsetFromCustomContent() { - return getNextPage() - numCustomPages(); - } - /** * Calculate the nearest cell where the given object would be dropped. * @@ -3483,29 +2977,16 @@ public class Workspace extends PagedView // hardware layers on children are enabled on startup, but should be disabled until // needed - updateChildrenLayersEnabled(false); + updateChildrenLayersEnabled(); } /** * Called at the end of a drag which originated on the workspace. */ public void onDropCompleted(final View target, final DragObject d, - final boolean isFlingToDelete, final boolean success) { - if (mDeferDropAfterUninstall) { - final CellLayout.CellInfo dragInfo = mDragInfo; - mDeferredAction = new Runnable() { - public void run() { - mDragInfo = dragInfo; // Restore the drag info that was cleared in onDragEnd() - onDropCompleted(target, d, isFlingToDelete, success); - mDeferredAction = null; - } - }; - return; - } + final boolean success) { - boolean beingCalledAfterUninstall = mDeferredAction != null; - - if (success && !(beingCalledAfterUninstall && !mUninstallSuccessful)) { + if (success) { if (target != this && mDragInfo != null) { removeWorkspaceItem(mDragInfo.cell); } @@ -3519,18 +3000,10 @@ public class Workspace extends PagedView + "Workspace#onDropCompleted. Please file a bug. "); } } - if ((d.cancelled || (beingCalledAfterUninstall && !mUninstallSuccessful)) - && mDragInfo != null && mDragInfo.cell != null) { + if (d.cancelled && mDragInfo != null && mDragInfo.cell != null) { mDragInfo.cell.setVisibility(VISIBLE); } mDragInfo = null; - - if (!isFlingToDelete) { - // Fling to delete already exits spring loaded mode after the animation finishes. - mLauncher.exitSpringLoadedDragModeDelayed(success, - Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, mDelayedResizeRunnable); - mDelayedResizeRunnable = null; - } } /** @@ -3567,36 +3040,6 @@ public class Workspace extends PagedView }); } - @Override - public void deferCompleteDropAfterUninstallActivity() { - mDeferDropAfterUninstall = true; - } - - /// maybe move this into a smaller part - @Override - public void onDragObjectRemoved(boolean success) { - mDeferDropAfterUninstall = false; - mUninstallSuccessful = success; - if (mDeferredAction != null) { - mDeferredAction.run(); - } - } - - @Override - public float getIntrinsicIconScaleFactor() { - return 1f; - } - - @Override - public boolean supportsAppInfoDropTarget() { - return true; - } - - @Override - public boolean supportsDeleteDropTarget() { - return true; - } - public boolean isDropEnabled() { return true; } @@ -3967,7 +3410,7 @@ public class Workspace extends PagedView .getInstance(mLauncher).findProvider(item.providerName, item.user); } else { widgetInfo = AppWidgetManagerCompat.getInstance(mLauncher) - .getAppWidgetInfo(item.appWidgetId); + .getLauncherAppWidgetInfo(item.appWidgetId); } if (widgetInfo != null) { @@ -3992,14 +3435,10 @@ public class Workspace extends PagedView } } - void moveToDefaultScreen(boolean animate) { - int page = getDefaultPage(); + void moveToDefaultScreen() { + int page = DEFAULT_PAGE; if (!workspaceInModalState() && getNextPage() != page) { - if (animate) { - snapToPage(page); - } else { - setCurrentPage(page); - } + snapToPage(page); } View child = getChildAt(page); if (child != null) { @@ -4007,22 +3446,6 @@ public class Workspace extends PagedView } } - void moveToCustomContentScreen(boolean animate) { - if (hasCustomContent()) { - int ccIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID); - if (animate) { - snapToPage(ccIndex); - } else { - setCurrentPage(ccIndex); - } - View child = getChildAt(ccIndex); - if (child != null) { - child.requestFocus(); - } - } - exitWidgetResizeMode(); - } - @Override protected String getPageIndicatorDescription() { return getResources().getString(R.string.all_apps_button_label); @@ -4030,16 +3453,12 @@ public class Workspace extends PagedView @Override protected String getCurrentPageDescription() { - if (hasCustomContent() && getNextPage() == 0) { - return mCustomContentDescription; - } int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; return getPageDescription(page); } private String getPageDescription(int page) { - int delta = numCustomPages(); - int nScreens = getChildCount() - delta; + int nScreens = getChildCount(); int extraScreenId = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID); if (extraScreenId >= 0 && nScreens > 1) { if (page == extraScreenId) { @@ -4051,8 +3470,7 @@ public class Workspace extends PagedView // When the workspace is not loaded, we do not know how many screen will be bound. return getContext().getString(R.string.all_apps_home_button_label); } - return getContext().getString(R.string.workspace_scroll_format, - page + 1 - delta, nScreens); + return getContext().getString(R.string.workspace_scroll_format, page + 1, nScreens); } @Override @@ -4069,16 +3487,6 @@ public class Workspace extends PagedView } } - @Override - public boolean enableFreeScroll() { - if (getState() == State.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. @@ -4135,6 +3543,13 @@ public class Workspace extends PagedView 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(); @@ -4142,11 +3557,7 @@ public class Workspace extends PagedView @Override public void onAnimationStart(Animator animation) { - if (mState == State.SPRING_LOADED) { - // Show the page indicator at the same time as the rest of the transition. - showPageIndicatorAtCurrentScroll(); - } - mTransitionProgress = 0; + onStartStateTransition(mToState); } @Override diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java index a105a73034..e14461e85d 100644 --- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java +++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java @@ -16,6 +16,9 @@ package com.android.launcher3; +import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA; +import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; @@ -24,17 +27,13 @@ import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; +import android.util.Property; import android.view.View; -import android.view.ViewGroup; import android.view.accessibility.AccessibilityManager; -import android.view.accessibility.AccessibilityNodeInfo; -import android.view.animation.DecelerateInterpolator; +import com.android.launcher3.LauncherStateManager.AnimationConfig; import com.android.launcher3.anim.AnimationLayerSet; -import com.android.launcher3.anim.PropertyListBuilder; -import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.dragndrop.DragLayer; -import com.android.launcher3.util.Thunk; +import com.android.launcher3.anim.Interpolators; /** * A convenience class to update a view's visibility state after an alpha animation. @@ -86,123 +85,21 @@ class AlphaUpdateListener extends AnimatorListenerAdapter implements ValueAnimat } } -/** - * 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)); - } -} - -/** - * Stores the transition states for convenience. - */ -class TransitionStates { - - // Raw states - final boolean oldStateIsNormal; - final boolean oldStateIsSpringLoaded; - final boolean oldStateIsNormalHidden; - final boolean oldStateIsOverviewHidden; - final boolean oldStateIsOverview; - - final boolean stateIsNormal; - final boolean stateIsSpringLoaded; - final boolean stateIsNormalHidden; - final boolean stateIsOverviewHidden; - final boolean stateIsOverview; - - // Convenience members - final boolean workspaceToAllApps; - final boolean overviewToAllApps; - final boolean allAppsToWorkspace; - final boolean workspaceToOverview; - final boolean overviewToWorkspace; - - public TransitionStates(final Workspace.State fromState, final Workspace.State toState) { - oldStateIsNormal = (fromState == Workspace.State.NORMAL); - oldStateIsSpringLoaded = (fromState == Workspace.State.SPRING_LOADED); - oldStateIsNormalHidden = (fromState == Workspace.State.NORMAL_HIDDEN); - oldStateIsOverviewHidden = (fromState == Workspace.State.OVERVIEW_HIDDEN); - oldStateIsOverview = (fromState == Workspace.State.OVERVIEW); - - stateIsNormal = (toState == Workspace.State.NORMAL); - stateIsSpringLoaded = (toState == Workspace.State.SPRING_LOADED); - stateIsNormalHidden = (toState == Workspace.State.NORMAL_HIDDEN); - stateIsOverviewHidden = (toState == Workspace.State.OVERVIEW_HIDDEN); - stateIsOverview = (toState == Workspace.State.OVERVIEW); - - workspaceToOverview = (oldStateIsNormal && stateIsOverview); - workspaceToAllApps = (oldStateIsNormal && stateIsNormalHidden); - overviewToWorkspace = (oldStateIsOverview && stateIsNormal); - overviewToAllApps = (oldStateIsOverview && stateIsOverviewHidden); - allAppsToWorkspace = (oldStateIsNormalHidden && stateIsNormal); - } -} - /** * Manages the animations between each of the workspace states. */ public class WorkspaceStateTransitionAnimation { - public static final String TAG = "WorkspaceStateTransitionAnimation"; + private static final PropertySetter NO_ANIM_PROPERTY_SETTER = new PropertySetter(); - @Thunk static final int BACKGROUND_FADE_OUT_DURATION = 350; + public final int mWorkspaceScrimAlpha; - final @Thunk Launcher mLauncher; - final @Thunk Workspace mWorkspace; + private final Launcher mLauncher; + private final Workspace mWorkspace; - @Thunk AnimatorSet mStateAnimator; + private final boolean mWorkspaceFadeInAdjacentScreens; - @Thunk float mCurrentScale; - @Thunk float mNewScale; - - @Thunk final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator(); - - @Thunk float mSpringLoadedShrinkFactor; - @Thunk float mOverviewModeShrinkFactor; - @Thunk float mWorkspaceScrimAlpha; - @Thunk int mAllAppsTransitionTime; - @Thunk int mOverviewTransitionTime; - @Thunk int mOverlayTransitionTime; - @Thunk int mSpringLoadedTransitionTime; - @Thunk boolean mWorkspaceFadeInAdjacentScreens; + private float mNewScale; public WorkspaceStateTransitionAnimation(Launcher launcher, Workspace workspace) { mLauncher = launcher; @@ -210,278 +107,154 @@ public class WorkspaceStateTransitionAnimation { DeviceProfile grid = mLauncher.getDeviceProfile(); Resources res = launcher.getResources(); - mAllAppsTransitionTime = res.getInteger(R.integer.config_allAppsTransitionTime); - mOverviewTransitionTime = res.getInteger(R.integer.config_overviewTransitionTime); - mOverlayTransitionTime = res.getInteger(R.integer.config_overlayTransitionTime); - mSpringLoadedTransitionTime = mOverlayTransitionTime / 2; - mSpringLoadedShrinkFactor = mLauncher.getDeviceProfile().workspaceSpringLoadShrinkFactor; - mOverviewModeShrinkFactor = - res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100f; - mWorkspaceScrimAlpha = res.getInteger(R.integer.config_workspaceScrimAlpha) / 100f; + mWorkspaceScrimAlpha = res.getInteger(R.integer.config_workspaceScrimAlpha); mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens(); } - public void snapToPageFromOverView(int whichPage) { - mWorkspace.snapToPage(whichPage, mOverviewTransitionTime, mZoomInInterpolator); + public void setState(LauncherState toState) { + setWorkspaceProperty(toState, NO_ANIM_PROPERTY_SETTER); } - public AnimatorSet getAnimationToState(Workspace.State fromState, Workspace.State toState, - boolean animated, AnimationLayerSet layerViews) { - AccessibilityManager am = (AccessibilityManager) - mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE); - final boolean accessibilityEnabled = am.isEnabled(); - TransitionStates states = new TransitionStates(fromState, toState); - int workspaceDuration = getAnimationDuration(states); - animateWorkspace(states, animated, workspaceDuration, layerViews, - accessibilityEnabled); - animateBackgroundGradient(states, animated, BACKGROUND_FADE_OUT_DURATION); - return mStateAnimator; + public void setStateWithAnimation(LauncherState toState, AnimatorSet anim, + AnimationLayerSet layerViews, AnimationConfig config) { + AnimatedPropertySetter propertySetter = + new AnimatedPropertySetter(config.duration, layerViews, anim); + setWorkspaceProperty(toState, propertySetter); } public float getFinalScale() { return mNewScale; } - /** - * Returns the proper animation duration for a transition. - */ - private int getAnimationDuration(TransitionStates states) { - if (states.workspaceToAllApps || states.overviewToAllApps) { - return mAllAppsTransitionTime; - } else if (states.workspaceToOverview || states.overviewToWorkspace) { - return mOverviewTransitionTime; - } else if (mLauncher.mState == Launcher.State.WORKSPACE_SPRING_LOADED - || states.oldStateIsNormal && states.stateIsSpringLoaded) { - return mSpringLoadedTransitionTime; - } else { - return mOverlayTransitionTime; - } - } - /** * Starts a transition animation for the workspace. */ - private void animateWorkspace(final TransitionStates states, final boolean animated, - final int duration, AnimationLayerSet layerViews, final boolean accessibilityEnabled) { - // Cancel existing workspace animations and create a new animator set if requested - cancelAnimation(); - if (animated) { - mStateAnimator = LauncherAnimUtils.createAnimatorSet(); - } - - // Update the workspace state - float finalBackgroundAlpha = (states.stateIsSpringLoaded || states.stateIsOverview) ? - 1.0f : 0f; - float finalHotseatAlpha = (states.stateIsNormal || states.stateIsSpringLoaded || - states.stateIsNormalHidden) ? 1f : 0f; - float finalQsbAlpha = (states.stateIsNormal || states.stateIsNormalHidden) ? 1f : 0f; - - float finalWorkspaceTranslationY = 0; - if (states.stateIsOverview || states.stateIsOverviewHidden) { - finalWorkspaceTranslationY = mWorkspace.getOverviewModeTranslationY(); - } else if (states.stateIsSpringLoaded) { - finalWorkspaceTranslationY = mWorkspace.getSpringLoadedTranslationY(); - } - - final int childCount = mWorkspace.getChildCount(); - final int customPageCount = mWorkspace.numCustomPages(); - - mNewScale = 1.0f; - - if (states.oldStateIsOverview) { - mWorkspace.disableFreeScroll(); - } else if (states.stateIsOverview) { - mWorkspace.enableFreeScroll(); - } - - if (!states.stateIsNormal) { - if (states.stateIsSpringLoaded) { - mNewScale = mSpringLoadedShrinkFactor; - } else if (states.stateIsOverview || states.stateIsOverviewHidden) { - mNewScale = mOverviewModeShrinkFactor; - } - } + private void setWorkspaceProperty(LauncherState state, PropertySetter propertySetter) { + float[] scaleAndTranslationY = state.getWorkspaceScaleAndTranslation(mLauncher); + mNewScale = scaleAndTranslationY[0]; + final float finalWorkspaceTranslationY = scaleAndTranslationY[1]; int toPage = mWorkspace.getPageNearestToCenterOfScreen(); - // TODO: Animate the celllayout alpha instead of the pages. + final int childCount = mWorkspace.getChildCount(); for (int i = 0; i < childCount; i++) { - final CellLayout cl = (CellLayout) mWorkspace.getChildAt(i); - float initialAlpha = cl.getShortcutsAndWidgets().getAlpha(); - float finalAlpha; - if (states.stateIsOverviewHidden) { - finalAlpha = 0f; - } else if(states.stateIsNormalHidden) { - finalAlpha = (i == mWorkspace.getNextPage()) ? 1 : 0; - } else if (states.stateIsNormal && mWorkspaceFadeInAdjacentScreens) { - finalAlpha = (i == toPage || i < customPageCount) ? 1f : 0f; - } else { - finalAlpha = 1f; - } - - // If we are animating to/from the small state, then hide the side pages and fade the - // current page in - if (!FeatureFlags.NO_ALL_APPS_ICON && !mWorkspace.isSwitchingState()) { - if (states.workspaceToAllApps || states.allAppsToWorkspace) { - boolean isCurrentPage = (i == toPage); - if (states.allAppsToWorkspace && isCurrentPage) { - initialAlpha = 0f; - } else if (!isCurrentPage) { - initialAlpha = finalAlpha = 0f; - } - cl.setShortcutAndWidgetAlpha(initialAlpha); - } - } - - if (animated) { - float oldBackgroundAlpha = cl.getBackgroundAlpha(); - if (initialAlpha != finalAlpha) { - Animator alphaAnim = ObjectAnimator.ofFloat( - cl.getShortcutsAndWidgets(), View.ALPHA, finalAlpha); - alphaAnim.setDuration(duration) - .setInterpolator(mZoomInInterpolator); - mStateAnimator.play(alphaAnim); - } - if (oldBackgroundAlpha != 0 || finalBackgroundAlpha != 0) { - ValueAnimator bgAnim = ObjectAnimator.ofFloat(cl, "backgroundAlpha", - oldBackgroundAlpha, finalBackgroundAlpha); - bgAnim.setInterpolator(mZoomInInterpolator); - bgAnim.setDuration(duration); - mStateAnimator.play(bgAnim); - } - } else { - cl.setBackgroundAlpha(finalBackgroundAlpha); - cl.setShortcutAndWidgetAlpha(finalAlpha); - } + applyChildState(state, (CellLayout) mWorkspace.getChildAt(i), i, toPage, + propertySetter); } - final ViewGroup overviewPanel = mLauncher.getOverviewPanel(); + float finalHotseatAlpha = state.hideHotseat ? 0f : 1f; - float finalOverviewPanelAlpha = states.stateIsOverview ? 1f : 0f; - if (animated) { - // This is true when transitioning between: - // - Overview <-> Workspace - // - Overview <-> Widget Tray - if (finalOverviewPanelAlpha != overviewPanel.getAlpha()) { - Animator overviewPanelAlpha = ObjectAnimator.ofFloat( - overviewPanel, View.ALPHA, finalOverviewPanelAlpha); - overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel, - accessibilityEnabled)); - layerViews.addView(overviewPanel); + // This is true when transitioning between: + // - Overview <-> Workspace + propertySetter.setViewAlpha(null, mLauncher.getOverviewPanel(), 1 - finalHotseatAlpha); + propertySetter.setViewAlpha(mWorkspace.createHotseatAlphaAnimator(finalHotseatAlpha), + mLauncher.getHotseat(), finalHotseatAlpha); - if (states.overviewToWorkspace) { - overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2)); - } else if (states.workspaceToOverview) { - overviewPanelAlpha.setInterpolator(null); - } + propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, Interpolators.ZOOM_IN); + propertySetter.setFloat(mWorkspace, View.TRANSLATION_Y, + finalWorkspaceTranslationY, Interpolators.ZOOM_IN); - overviewPanelAlpha.setDuration(duration); - mStateAnimator.play(overviewPanelAlpha); - } + // Set scrim + propertySetter.setInt(mLauncher.getDragLayer().getScrim(), DRAWABLE_ALPHA, + state.hasScrim ? mWorkspaceScrimAlpha : 0, Interpolators.DEACCEL_1_5); + } - Animator scale = LauncherAnimUtils.ofPropertyValuesHolder(mWorkspace, - new PropertyListBuilder().scale(mNewScale) - .translationY(finalWorkspaceTranslationY).build()) - .setDuration(duration); - scale.setInterpolator(mZoomInInterpolator); - mStateAnimator.play(scale); + public void applyChildState(LauncherState state, CellLayout cl, int childIndex) { + applyChildState(state, cl, childIndex, mWorkspace.getPageNearestToCenterOfScreen(), + NO_ANIM_PROPERTY_SETTER); + } - // For animation optimization, we may need to provide the Launcher transition - // with a set of views on which to force build and manage layers in certain scenarios. - layerViews.addView(mLauncher.getHotseat()); - layerViews.addView(mWorkspace.getPageIndicator()); + 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); - Animator hotseatAlpha = mWorkspace.createHotseatAlphaAnimator(finalHotseatAlpha); - if (states.workspaceToOverview) { - hotseatAlpha.setInterpolator(new DecelerateInterpolator(2)); - } else if (states.overviewToWorkspace) { - hotseatAlpha.setInterpolator(null); - } - hotseatAlpha.setDuration(duration); - mStateAnimator.play(hotseatAlpha); - mStateAnimator.addListener(new AnimatorListenerAdapter() { - boolean canceled = false; - @Override - public void onAnimationCancel(Animator animation) { - canceled = true; - } - - @Override - public void onAnimationStart(Animator animation) { - mWorkspace.getPageIndicator().setShouldAutoHide(!states.stateIsSpringLoaded); - } - - @Override - public void onAnimationEnd(Animator animation) { - mStateAnimator = null; - if (canceled) return; - if (accessibilityEnabled && overviewPanel.getVisibility() == View.VISIBLE) { - overviewPanel.getChildAt(0).performAccessibilityAction( - AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); - } - } - }); - } else { - overviewPanel.setAlpha(finalOverviewPanelAlpha); - AlphaUpdateListener.updateVisibility(overviewPanel, accessibilityEnabled); - mWorkspace.getPageIndicator().setShouldAutoHide(!states.stateIsSpringLoaded); - - mWorkspace.createHotseatAlphaAnimator(finalHotseatAlpha).end(); - mWorkspace.updateCustomContentVisibility(); - mWorkspace.setScaleX(mNewScale); - mWorkspace.setScaleY(mNewScale); - mWorkspace.setTranslationY(finalWorkspaceTranslationY); - - if (accessibilityEnabled && overviewPanel.getVisibility() == View.VISIBLE) { - overviewPanel.getChildAt(0).performAccessibilityAction( - AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); - } + // 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); } } - /** - * Animates the background scrim. Add to the state animator to prevent jankiness. - * - * @param states the current and final workspace states - * @param animated whether or not to set the background alpha immediately - * @duration duration of the animation - */ - private void animateBackgroundGradient(TransitionStates states, - boolean animated, int duration) { + private static class PropertySetter { - final DragLayer dragLayer = mLauncher.getDragLayer(); - final float startAlpha = dragLayer.getBackgroundAlpha(); - float finalAlpha = states.stateIsNormal || states.stateIsNormalHidden ? - 0 : mWorkspaceScrimAlpha; - - if (finalAlpha != startAlpha) { - if (animated) { - // These properties refer to the background protection gradient used for AllApps - // and Widget tray. - ValueAnimator bgFadeOutAnimation = ValueAnimator.ofFloat(startAlpha, finalAlpha); - bgFadeOutAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - dragLayer.setBackgroundAlpha( - ((Float)animation.getAnimatedValue()).floatValue()); - } - }); - bgFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f)); - bgFadeOutAnimation.setDuration(duration); - mStateAnimator.play(bgFadeOutAnimation); - } else { - dragLayer.setBackgroundAlpha(finalAlpha); + public void setViewAlpha(Animator anim, View view, float alpha) { + if (anim != null) { + anim.end(); + return; } + view.setAlpha(alpha); + AlphaUpdateListener.updateVisibility(view, isAccessibilityEnabled(view)); + } + + public void setFloat(T target, Property property, float value, + TimeInterpolator interpolator) { + property.set(target, value); + } + + public void setInt(T target, Property property, int value, + TimeInterpolator interpolator) { + property.set(target, value); + } + + protected boolean isAccessibilityEnabled(View v) { + AccessibilityManager am = (AccessibilityManager) + v.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); + return am.isEnabled(); } } - /** - * Cancels the current animation. - */ - private void cancelAnimation() { - if (mStateAnimator != null) { - mStateAnimator.setDuration(0); - mStateAnimator.cancel(); + private static class AnimatedPropertySetter extends PropertySetter { + + private final long mDuration; + private final AnimationLayerSet mLayerViews; + private final AnimatorSet mStateAnimator; + + AnimatedPropertySetter(long duration, AnimationLayerSet layerView, AnimatorSet anim) { + mDuration = duration; + mLayerViews = layerView; + mStateAnimator = anim; + } + + @Override + public void setViewAlpha(Animator anim, View view, float alpha) { + if (anim == null) { + if (view.getAlpha() == alpha) { + return; + } + anim = ObjectAnimator.ofFloat(view, View.ALPHA, alpha); + anim.addListener(new AlphaUpdateListener(view, isAccessibilityEnabled(view))); + } + + anim.setDuration(mDuration).setInterpolator(getFadeInterpolator(alpha)); + mLayerViews.addView(view); + mStateAnimator.play(anim); + } + + @Override + public void setFloat(T target, Property property, float value, + TimeInterpolator interpolator) { + if (property.get(target) == value) { + return; + } + Animator anim = ObjectAnimator.ofFloat(target, property, value); + anim.setDuration(mDuration).setInterpolator(interpolator); + mStateAnimator.play(anim); + } + + @Override + public void setInt(T target, Property property, int value, + TimeInterpolator interpolator) { + if (property.get(target) == value) { + return; + } + Animator anim = ObjectAnimator.ofInt(target, property, value); + anim.setDuration(mDuration).setInterpolator(interpolator); + mStateAnimator.play(anim); + } + + private TimeInterpolator getFadeInterpolator(float finalAlpha) { + return finalAlpha == 0 ? Interpolators.DEACCEL_2 : null; } - mStateAnimator = 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 a0ad07aeb1..512db721e8 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 android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 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.Launcher; 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 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme 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 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme 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 class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme } 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); - 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 itemList = new ArrayList<>(); @@ -178,7 +170,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme 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 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme 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; } @@ -411,7 +410,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme CellLayout layout = (CellLayout) workspace.getPageAt(screenIndex); boolean found = layout.findCellForSpan(outCoordinates, info.spanX, info.spanY); - screenIndex = workspace.hasCustomContent() ? 1 : 0; + screenIndex = 0; while (!found && screenIndex < workspaceScreens.size()) { screenId = workspaceScreens.get(screenIndex); layout = (CellLayout) workspace.getPageAt(screenIndex); diff --git a/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java index edb0b168d0..f9eb2eda62 100644 --- a/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java @@ -88,7 +88,7 @@ public class OverviewScreenAccessibilityDelegate extends AccessibilityDelegate { info.addAction(mActions.get(MOVE_FORWARD)); } - int startIndex = mWorkspace.numCustomPages() + (FeatureFlags.QSB_ON_FIRST_SCREEN ? 1 : 0); + int startIndex = FeatureFlags.QSB_ON_FIRST_SCREEN ? 1 : 0; if (index > startIndex) { info.addAction(mActions.get(MOVE_BACKWARD)); } diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java index 5b7353aa18..cfb0520293 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 @@ public class ShortcutMenuAccessibilityDelegate extends LauncherAccessibilityDele } }; - 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 583b49f7bc..0000000000 --- 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 4eba5c6df6..a1f37ba222 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -16,10 +16,8 @@ package com.android.launcher3.allapps; import android.content.Context; -import android.graphics.Color; +import android.graphics.Bitmap; import android.graphics.Rect; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.InsetDrawable; import android.support.v7.widget.LinearLayoutManager; import android.text.Selection; import android.text.SpannableStringBuilder; @@ -28,30 +26,29 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.widget.RelativeLayout; import com.android.launcher3.AppInfo; -import com.android.launcher3.BaseContainerView; import com.android.launcher3.BubbleTextView; -import com.android.launcher3.DeleteDropTarget; +import com.android.launcher3.ClickShadowView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; +import com.android.launcher3.DropTarget.DragObject; 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.Utilities; import com.android.launcher3.anim.SpringAnimationHandler; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragOptions; -import com.android.launcher3.folder.Folder; 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.PackageUserKey; +import com.android.launcher3.util.TransformingTouchDelegate; import java.util.List; import java.util.Set; @@ -59,13 +56,17 @@ import java.util.Set; /** * The all apps view container. */ -public class AllAppsContainerView extends BaseContainerView implements DragSource, - View.OnLongClickListener, Insettable { +public class AllAppsContainerView extends RelativeLayout implements DragSource, + View.OnLongClickListener, Insettable, DeviceProfile.LauncherLayoutChangeListener, + BubbleTextView.BubbleTextShadowHandler { + + protected final Rect mBasePadding = new Rect(); private final Launcher mLauncher; private final AlphabeticalAppsList mApps; private final AllAppsGridAdapter mAdapter; private final LinearLayoutManager mLayoutManager; + private final ClickShadowView mTouchFeedbackView; private AllAppsRecyclerView mAppsRecyclerView; private SearchUiManager mSearchUiManager; @@ -78,6 +79,8 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc private SpringAnimationHandler mSpringAnimationHandler; + private TransformingTouchDelegate mTouchDelegate; + public AllAppsContainerView(Context context) { this(context, null); } @@ -98,27 +101,70 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc mSearchQueryBuilder = new SpannableStringBuilder(); Selection.setSelection(mSearchQueryBuilder, 0); + + mTouchFeedbackView = new ClickShadowView(context); + // Make the feedback view large enough to hold the blur bitmap. + int size = mLauncher.getDeviceProfile().allAppsIconSizePx + + mTouchFeedbackView.getExtraSize(); + addView(mTouchFeedbackView, size, size); } @Override - protected void updateBackground( - int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) { - if (mLauncher.getDeviceProfile().isVerticalBarLayout()) { - getRevealView().setBackground(new InsetDrawable(mBaseDrawable, - paddingLeft, paddingTop, paddingRight, paddingBottom)); - getContentView().setBackground( - new InsetDrawable(new ColorDrawable(Color.TRANSPARENT), - paddingLeft, paddingTop, paddingRight, paddingBottom)); - } else { - getRevealView().setBackground(mBaseDrawable); - } + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile(); + grid.addLauncherLayoutChangedListener(this); + + mTouchDelegate = new TransformingTouchDelegate(mAppsRecyclerView); + setTouchDelegate(mTouchDelegate); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile(); + grid.removeLauncherLayoutChangedListener(this); } /** - * Sets the current set of predicted apps. + * Calculate the background padding as it can change due to insets/content padding change. */ - public void setPredictedApps(List> apps) { - mApps.setPredictedApps(apps); + @Override + public void onLauncherLayoutChanged() { + DeviceProfile grid = mLauncher.getDeviceProfile(); + if (!grid.isVerticalBarLayout()) { + return; + } + + int[] padding = grid.getContainerPadding(); + int paddingLeft = padding[0]; + int paddingRight = padding[1]; + mBasePadding.set(paddingLeft, 0, paddingRight, 0); + setPadding(paddingLeft, 0, paddingRight, 0); + } + + @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); + } + + @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(); + } } /** @@ -164,23 +210,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc if (mLauncher.getDragLayer().isEventOverView(mSearchContainer, ev)) { return true; } - - int[] point = new int[2]; - point[0] = (int) ev.getX(); - point[1] = (int) ev.getY(); - Utilities.mapCoordInSelfToDescendant( - mAppsRecyclerView.getScrollBar(), mLauncher.getDragLayer(), point); - // IF the MotionEvent is inside the thumb, container should not be pulled down. - if (mAppsRecyclerView.getScrollBar().shouldBlockIntercept(point[0], point[1])) { - return false; - } - - // IF scroller is at the very top OR there is no scroll bar because there is probably not - // enough items to scroll, THEN it's okay for the container to be pulled down. - if (mAppsRecyclerView.getCurrentScrollY() == 0) { - return true; - } - return false; + return mAppsRecyclerView.shouldContainerScroll(ev, mLauncher.getDragLayer()); } /** @@ -198,7 +228,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc // This is a focus listener that proxies focus from a view into the list view. This is to // work around the search box from getting first focus and showing the cursor. - getContentView().setOnFocusChangeListener(new View.OnFocusChangeListener() { + setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { @@ -229,20 +259,13 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc mAppsRecyclerView.preMeasureViews(mAdapter); mAdapter.setIconFocusListener(focusedItemDecorator.getFocusListener()); - getRevealView().setVisibility(View.VISIBLE); - getContentView().setVisibility(View.VISIBLE); - getContentView().setBackground(null); + onLauncherLayoutChanged(); } public SearchUiManager getSearchUiManager() { return mSearchUiManager; } - @Override - public View getTouchDelegateTargetView() { - return mAppsRecyclerView; - } - @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { DeviceProfile grid = mLauncher.getDeviceProfile(); @@ -270,7 +293,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc @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; @@ -290,42 +313,16 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc dragController.removeDragListener(this); } }); - mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions()); - return false; - } - @Override - public boolean supportsAppInfoDropTarget() { - return true; - } - - @Override - public boolean supportsDeleteDropTarget() { - return false; - } - - @Override - public float getIntrinsicIconScaleFactor() { DeviceProfile grid = mLauncher.getDeviceProfile(); - return (float) grid.allAppsIconSizePx / grid.iconSizePx; + DragOptions options = new DragOptions(); + options.intrinsicIconScaleFactor = (float) grid.allAppsIconSizePx / grid.iconSizePx; + mLauncher.getWorkspace().beginDragShared(v, this, options); + return false; } @Override - public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete, - boolean success) { - if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() && - !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) { - // Exit spring loaded mode if we have not successfully dropped or have not handled the - // drop in Workspace - mLauncher.exitSpringLoadedDragModeDelayed(true, - Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); - } - mLauncher.unlockScreenOrientation(false); - - if (!success) { - d.deferDragViewCleanupPostAnimation = false; - } - } + public void onDropCompleted(View target, DragObject d, boolean success) { } @Override public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) { diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index 1f60fcc735..ed5bf9f8b7 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -336,6 +336,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter PROGRESS = + new Property(Float.class, "progress") { - private final Interpolator mWorkspaceAccelnterpolator = new AccelerateInterpolator(2f); - private final Interpolator mHotseatAccelInterpolator = new AccelerateInterpolator(1.5f); - private final Interpolator mDecelInterpolator = new DecelerateInterpolator(3f); - private final Interpolator mFastOutSlowInInterpolator = new FastOutSlowInInterpolator(); - private final SwipeDetector.ScrollInterpolator mScrollInterpolator - = new SwipeDetector.ScrollInterpolator(); + @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 int mAllAppsBackgroundColor; private Workspace mWorkspace; private Hotseat mHotseat; - private int mHotseatBackgroundColor; - - 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}. @@ -80,215 +69,36 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect // 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(); - mAllAppsBackgroundColor = Themes.getAttrColor(l, android.R.attr.colorPrimary); 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 */, false /* updatePredictedApps */); - 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 */ false /* updatePredictedApps */); - } - } - } - - 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); - mHotseatBackgroundColor = mHotseat.getBackgroundDrawableColor(); - mHotseat.setBackgroundTransparent(true /* transparent */); - if (!mLauncher.isAllAppsVisible()) { - mLauncher.tryAndUpdatePredictedApps(); - mAppsView.setVisibility(View.VISIBLE); - if (!FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) { - mAppsView.setRevealDrawableColor(mHotseatBackgroundColor); - } - } - } + private void onProgressAnimationStart() { + // Initialize values that should not change until #onDragEnd + mHotseat.setVisibility(View.VISIBLE); + mAppsView.setVisibility(View.VISIBLE); } private void updateLightStatusBar(float shift) { - // Do not modify status bar on landscape as all apps is not full bleed. - if (!FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS - && mLauncher.getDeviceProfile().isVerticalBarLayout()) { - return; - } - // Use a light system UI (dark icons) if all apps is behind at least half of the status bar. - boolean forceChange = FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS ? - shift <= mShiftRange / 4 : - shift <= mStatusBarHeight / 2; + boolean forceChange = shift <= mShiftRange / 4; if (forceChange) { mLauncher.getSystemUiController().updateUiState( SystemUiController.UI_STATE_ALL_APPS, !mIsDarkTheme); @@ -301,17 +111,21 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect private void updateAllAppsBg(float progress) { // gradient if (mGradientView == null) { - mGradientView = (GradientView) mLauncher.findViewById(R.id.gradient_bg); - mGradientView.setVisibility(View.VISIBLE); + mGradientView = mLauncher.findViewById(R.id.gradient_bg); } mGradientView.setProgress(progress); } /** - * @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 #setFinalProgress(float) + * @see #animateToFinalProgress(float, AnimatorSet, AnimationConfig) */ public void setProgress(float progress) { - float shiftPrevious = mProgress * mShiftRange; mProgress = progress; float shiftCurrent = progress * mShiftRange; @@ -320,18 +134,8 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect float workspaceAlpha = mWorkspaceAccelnterpolator.getInterpolation(workspaceHotseatAlpha); float hotseatAlpha = mHotseatAccelInterpolator.getInterpolation(workspaceHotseatAlpha); - int color = (Integer) mEvaluator.evaluate(mDecelInterpolator.getInterpolation(alpha), - mHotseatBackgroundColor, mAllAppsBackgroundColor); - int bgAlpha = Color.alpha((int) mEvaluator.evaluate(alpha, - mHotseatBackgroundColor, mAllAppsBackgroundColor)); - - if (FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) { - updateAllAppsBg(alpha); - } else { - mAppsView.setRevealDrawableColor(ColorUtils.setAlphaComponent(color, bgAlpha)); - } - - mAppsView.getContentView().setAlpha(alpha); + updateAllAppsBg(alpha); + mAppsView.setAlpha(alpha); mAppsView.setTranslationY(shiftCurrent); if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) { @@ -349,12 +153,6 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect 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); } @@ -362,58 +160,48 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect return mProgress; } - private void calculateDuration(float velocity, float disp) { - mAnimationDuration = SwipeDetector.calculateDuration(velocity, disp / mShiftRange); + /** + * Sets the vertical transition progress to {@param progress} and updates all the dependent UI + * accordingly. + */ + public void setFinalProgress(float progress) { + setProgress(progress); + onProgressAnimationEnd(); } - public boolean animateToAllApps(AnimatorSet animationOut, long duration) { - boolean shouldPost = true; - if (animationOut == null) { - return shouldPost; - } - Interpolator interpolator; - if (mDetector.isIdleState()) { - preparePull(true); - mAnimationDuration = duration; - 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; - } - shouldPost = false; + /** + * Creates an animation which updates the vertical transition progress and updates all the + * dependent UI using various animation events + * + * @param progress the final vertical progress at the end of the animation + * @param animationOut the target AnimatorSet where this animation should be added + * @param outConfig an in/out configuration which can be shared with other animations + */ + public void animateToFinalProgress( + float progress, AnimatorSet animationOut, AnimationConfig outConfig) { + if (Float.compare(mProgress, progress) == 0) { + // Fail fast + onProgressAnimationEnd(); + return; } - ObjectAnimator driftAndAlpha = ObjectAnimator.ofFloat(this, "progress", - mProgress, 0f); - driftAndAlpha.setDuration(mAnimationDuration); - driftAndAlpha.setInterpolator(interpolator); - animationOut.play(driftAndAlpha); - - animationOut.addListener(new AnimatorListenerAdapter() { - boolean canceled = false; - + Interpolator interpolator = outConfig.userControlled ? LINEAR : FAST_OUT_SLOW_IN; + ObjectAnimator anim = ObjectAnimator.ofFloat(this, PROGRESS, mProgress, progress); + anim.setDuration(outConfig.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 onAnimationEnd(Animator animation) { - if (canceled) { - return; - } else { - finishPullUp(); - cleanUpAnimation(); - mDetector.finishedScrolling(); - } + public void onAnimationStart(Animator animation) { + onProgressAnimationStart(); } }); - mCurrentAnimation = animationOut; - return shouldPost; + + animationOut.play(anim); } public void showDiscoveryBounce() { @@ -427,12 +215,12 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect @Override public void onAnimationStart(Animator animator) { mIsTranslateWithoutWorkspace = true; - preparePull(true); + onProgressAnimationStart(); } @Override public void onAnimationEnd(Animator animator) { - finishPullDown(); + onProgressAnimationEnd(); mDiscoBounceAnimation = null; mIsTranslateWithoutWorkspace = false; } @@ -449,84 +237,6 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect }); } - public boolean animateToWorkspace(AnimatorSet animationOut, long duration) { - boolean shouldPost = true; - if (animationOut == null) { - return shouldPost; - } - Interpolator interpolator; - if (mDetector.isIdleState()) { - preparePull(true); - mAnimationDuration = duration; - 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; - } - shouldPost = false; - } - - 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; - return shouldPost; - } - - public void finishPullUp() { - mHotseat.setVisibility(View.INVISIBLE); - if (hasSpringAnimationHandler()) { - mSpringAnimationHandler.remove(mSearchSpring); - mSpringAnimationHandler.reset(); - } - setProgress(0f); - } - - public void finishPullDown() { - mAppsView.setVisibility(View.INVISIBLE); - mHotseat.setBackgroundTransparent(false /* transparent */); - 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; @@ -535,24 +245,12 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect 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 @@ -560,4 +258,19 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect 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 6bbe3ea559..f0b650bd2f 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -362,9 +362,12 @@ public class AlphabeticalAppsList { int size = apps.size(); for (int i = 0; i < size; ++i) { AppInfo info = apps.get(i); - AdapterItem appItem = AdapterItem.asPredictedApp(i, "", info, i); - appItem.rowAppIndex = i; - mAdapterItems.set(i, appItem); + AdapterItem orgItem = mAdapterItems.get(i); + AdapterItem newItem = AdapterItem.asPredictedApp(orgItem.position, "", info, + orgItem.appIndex); + newItem.rowAppIndex = orgItem.rowAppIndex; + + mAdapterItems.set(i, newItem); mFilteredApps.set(i, info); mAdapter.notifyItemChanged(i); } diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java index 63aa7be3a6..bf03a0ee1a 100644 --- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java +++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java @@ -15,16 +15,13 @@ */ package com.android.launcher3.allapps.search; -import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.view.KeyEvent; -import android.view.View; import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; @@ -50,7 +47,6 @@ public class AllAppsSearchBarController protected String mQuery; protected SearchAlgorithm mSearchAlgorithm; - protected InputMethodManager mInputMethodManager; public void setVisibility(int visibility) { mInput.setVisibility(visibility); @@ -68,10 +64,6 @@ public class AllAppsSearchBarController mInput.addTextChangedListener(this); mInput.setOnEditorActionListener(this); mInput.setOnBackKeyListener(this); - - mInputMethodManager = (InputMethodManager) - mInput.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - mSearchAlgorithm = searchAlgorithm; } @@ -137,22 +129,9 @@ public class AllAppsSearchBarController * Resets the search bar state. */ public void reset() { - unfocusSearchField(); mCb.clearSearchResult(); - mInput.setText(""); + mInput.reset(); mQuery = null; - hideKeyboard(); - } - - protected void hideKeyboard() { - mInputMethodManager.hideSoftInputFromWindow(mInput.getWindowToken(), 0); - } - - protected void unfocusSearchField() { - View nextFocus = mInput.focusSearch(View.FOCUS_DOWN); - if (nextFocus != null) { - nextFocus.requestFocus(); - } } /** diff --git a/src/com/android/launcher3/anim/AnimationSuccessListener.java b/src/com/android/launcher3/anim/AnimationSuccessListener.java new file mode 100644 index 0000000000..9448632ac5 --- /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 0000000000..826a20e7a2 --- /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 childAnims = new ArrayList<>(); + getAnimationsRecur(mAnim, childAnims); + mChildAnimations = childAnims.toArray(new ValueAnimator[childAnims.size()]); + } + + private void getAnimationsRecur(AnimatorSet anim, ArrayList 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 List nonNullList(ArrayList list) { + return list == null ? Collections.emptyList() : list; + } +} diff --git a/src/com/android/launcher3/anim/CircleRevealOutlineProvider.java b/src/com/android/launcher3/anim/CircleRevealOutlineProvider.java deleted file mode 100644 index 9fb6b498bb..0000000000 --- a/src/com/android/launcher3/anim/CircleRevealOutlineProvider.java +++ /dev/null @@ -1,53 +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.anim; - -public class CircleRevealOutlineProvider extends RevealOutlineAnimation { - - private int mCenterX; - private int mCenterY; - private float mRadius0; - private float mRadius1; - - /** - * @param x reveal center x - * @param y reveal center y - * @param r0 initial radius - * @param r1 final radius - */ - public CircleRevealOutlineProvider(int x, int y, float r0, float r1) { - mCenterX = x; - mCenterY = y; - mRadius0 = r0; - mRadius1 = r1; - } - - @Override - public boolean shouldRemoveElevationDuringAnimation() { - return true; - } - - @Override - public void setProgress(float progress) { - mOutlineRadius = (1 - progress) * mRadius0 + progress * mRadius1; - - mOutline.left = (int) (mCenterX - mOutlineRadius); - mOutline.top = (int) (mCenterY - mOutlineRadius); - mOutline.right = (int) (mCenterX + mOutlineRadius); - mOutline.bottom = (int) (mCenterY + mOutlineRadius); - } -} diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java new file mode 100644 index 0000000000..8826e64d8e --- /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/SpringAnimationHandler.java b/src/com/android/launcher3/anim/SpringAnimationHandler.java index eec3a48ee3..29a2430a56 100644 --- a/src/com/android/launcher3/anim/SpringAnimationHandler.java +++ b/src/com/android/launcher3/anim/SpringAnimationHandler.java @@ -83,6 +83,10 @@ public class SpringAnimationHandler { mAnimations.add(spring); } + public AnimationFactory 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 c2cc215ce5..6ce334ef35 100644 --- a/src/com/android/launcher3/badge/BadgeRenderer.java +++ b/src/com/android/launcher3/badge/BadgeRenderer.java @@ -26,6 +26,7 @@ import android.graphics.Point; 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 @@ import com.android.launcher3.graphics.ShadowGenerator; */ 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 class BadgeRenderer { */ 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/compat/AppWidgetManagerCompat.java b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java index a77a87f2c4..fd1f0cca2e 100644 --- a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java +++ b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java @@ -24,10 +24,13 @@ import android.os.Bundle; import android.os.UserHandle; import android.support.annotation.Nullable; +import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.Utilities; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.PackageUserKey; +import com.android.launcher3.widget.custom.CustomWidgetParser; import java.util.HashMap; import java.util.List; @@ -58,12 +61,13 @@ public abstract class AppWidgetManagerCompat { mAppWidgetManager = AppWidgetManager.getInstance(context); } - public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) { - return mAppWidgetManager.getAppWidgetInfo(appWidgetId); - } - public LauncherAppWidgetProviderInfo getLauncherAppWidgetInfo(int appWidgetId) { - AppWidgetProviderInfo info = getAppWidgetInfo(appWidgetId); + if (FeatureFlags.ENABLE_CUSTOM_WIDGETS + && appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) { + return CustomWidgetParser.getWidgetProvider(mContext, appWidgetId); + } + + AppWidgetProviderInfo info = mAppWidgetManager.getAppWidgetInfo(appWidgetId); return info == null ? null : LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info); } diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java index cb3bd6c7d6..843028503e 100644 --- a/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java +++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java @@ -20,14 +20,17 @@ import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; import android.content.Context; import android.os.Bundle; +import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import android.support.annotation.Nullable; +import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.PackageUserKey; +import com.android.launcher3.widget.custom.CustomWidgetParser; import java.util.ArrayList; import java.util.Collections; @@ -54,6 +57,10 @@ class AppWidgetManagerCompatVL extends AppWidgetManagerCompat { for (UserHandle user : mUserManager.getUserProfiles()) { providers.addAll(mAppWidgetManager.getInstalledProvidersForProfile(user)); } + + if (FeatureFlags.ENABLE_CUSTOM_WIDGETS) { + providers.addAll(CustomWidgetParser.getCustomWidgets(mContext)); + } return providers; } // Only get providers for the given package/user. @@ -65,6 +72,11 @@ class AppWidgetManagerCompatVL extends AppWidgetManagerCompat { iterator.remove(); } } + + if (FeatureFlags.ENABLE_CUSTOM_WIDGETS && Process.myUserHandle().equals(packageUser.mUser) + && mContext.getPackageName().equals(packageUser.mPackageName)) { + providers.addAll(CustomWidgetParser.getCustomWidgets(mContext)); + } return providers; } @@ -74,6 +86,11 @@ class AppWidgetManagerCompatVL extends AppWidgetManagerCompat { if (FeatureFlags.GO_DISABLE_WIDGETS) { return false; } + + if (FeatureFlags.ENABLE_CUSTOM_WIDGETS + && appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) { + return true; + } return mAppWidgetManager.bindAppWidgetIdIfAllowed( appWidgetId, info.getProfile(), info.provider, options); } @@ -89,6 +106,15 @@ class AppWidgetManagerCompatVL extends AppWidgetManagerCompat { return LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info); } } + + if (FeatureFlags.ENABLE_CUSTOM_WIDGETS && Process.myUserHandle().equals(user)) { + for (LauncherAppWidgetProviderInfo info : + CustomWidgetParser.getCustomWidgets(mContext)) { + if (info.provider.equals(provider)) { + return info; + } + } + } return null; } @@ -104,6 +130,13 @@ class AppWidgetManagerCompatVL extends AppWidgetManagerCompat { result.put(new ComponentKey(info.provider, user), info); } } + + if (FeatureFlags.ENABLE_CUSTOM_WIDGETS) { + for (LauncherAppWidgetProviderInfo info : + CustomWidgetParser.getCustomWidgets(mContext)) { + result.put(new ComponentKey(info.provider, info.getProfile()), info); + } + } return result; } } diff --git a/src/com/android/launcher3/compat/WallpaperManagerCompatVL.java b/src/com/android/launcher3/compat/WallpaperManagerCompatVL.java index 8e572ee1aa..4cc70d3677 100644 --- a/src/com/android/launcher3/compat/WallpaperManagerCompatVL.java +++ b/src/com/android/launcher3/compat/WallpaperManagerCompatVL.java @@ -15,6 +15,10 @@ */ package com.android.launcher3.compat; +import static android.app.WallpaperManager.FLAG_SYSTEM; + +import static com.android.launcher3.Utilities.getDevicePrefs; + import android.app.WallpaperInfo; import android.app.WallpaperManager; import android.app.job.JobInfo; @@ -38,7 +42,6 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.ParcelFileDescriptor; import android.support.annotation.Nullable; -import android.support.v7.graphics.Palette; import android.util.Log; import android.util.Pair; @@ -46,12 +49,6 @@ import com.android.launcher3.Utilities; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -import static android.app.WallpaperManager.FLAG_SYSTEM; -import static com.android.launcher3.Utilities.getDevicePrefs; public class WallpaperManagerCompatVL extends WallpaperManagerCompat { @@ -260,27 +257,8 @@ public class WallpaperManagerCompatVL extends WallpaperManagerCompat { String value = VERSION_PREFIX + wallpaperId; if (bitmap != null) { - Palette palette = Palette.from(bitmap).generate(); - bitmap.recycle(); - - StringBuilder builder = new StringBuilder(value); - List> colorsToOccurrences = new ArrayList<>(); - for (Palette.Swatch swatch : palette.getSwatches()) { - colorsToOccurrences.add(new Pair(swatch.getRgb(), swatch.getPopulation())); - } - - Collections.sort(colorsToOccurrences, new Comparator>() { - @Override - public int compare(Pair a, Pair b) { - return b.second - a.second; - } - }); - - for (int i=0; i < Math.min(3, colorsToOccurrences.size()); i++) { - builder.append(',').append(colorsToOccurrences.get(i).first); - } - - value = builder.toString(); + int color = Utilities.findDominantColorByHue(bitmap, MAX_WALLPAPER_EXTRACTION_AREA); + value += "," + color; } // Send the result diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java index 6a4cbcbf08..a03dabb2bb 100644 --- a/src/com/android/launcher3/config/BaseFlags.java +++ b/src/com/android/launcher3/config/BaseFlags.java @@ -29,20 +29,14 @@ abstract class BaseFlags { 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_LEGACY_FOLDER_ICON = false; - public static boolean LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW = false; - public static boolean LAUNCHER3_NEW_FOLDER_ANIMATION = true; // 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 uses the AllAppsRadialGradientAndScrimDrawable for all apps - public static final boolean LAUNCHER3_GRADIENT_ALL_APPS = true; // When enabled allows use of physics based motions in the Launcher. public static final boolean LAUNCHER3_PHYSICS = true; // When enabled allows use of spring motions on the icons. @@ -52,19 +46,17 @@ abstract class BaseFlags { public static final boolean QSB_ON_FIRST_SCREEN = true; // When enabled the all-apps icon is not added to the hotseat. public static final boolean NO_ALL_APPS_ICON = true; - // When enabled fling down gesture on the first workspace triggers search. - public static final boolean PULLDOWN_SEARCH = false; - // When enabled the status bar may show dark icons based on the top of the wallpaper. - public static final boolean LIGHT_STATUS_BAR = false; // When enabled, icons not supporting {@link AdaptiveIconDrawable} will be wrapped in {@link FixedScaleDrawable}. public static final boolean LEGACY_ICON_TREATMENT = true; // When enabled, adaptive icons would have shadows baked when being stored to icon cache. public static final boolean ADAPTIVE_ICON_SHADOW = true; // When enabled, app discovery will be enabled if service is implemented public static final boolean DISCOVERY_ENABLED = false; - // When enabled, the qsb will be moved to the hotseat. - public static final boolean QSB_IN_HOTSEAT = true; + + // When true, custom widgets are loaded using CustomWidgetParser. + public static final boolean ENABLE_CUSTOM_WIDGETS = false; // Features to control Launcher3Go behavior public static final boolean GO_DISABLE_WIDGETS = false; + } diff --git a/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java index 06493b2b17..ed5cfeb7d6 100644 --- a/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java +++ b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java @@ -41,7 +41,6 @@ public class AppDiscoveryAppInfo extends AppInfo { 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/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java index d0f2629aa7..96aaee03b3 100644 --- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java +++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java @@ -28,12 +28,10 @@ import android.util.Log; import android.view.DragEvent; import android.view.View; -import com.android.launcher3.DeleteDropTarget; import com.android.launcher3.DragSource; -import com.android.launcher3.DropTarget; +import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.Launcher; import com.android.launcher3.R; -import com.android.launcher3.folder.Folder; import com.android.launcher3.widget.PendingItemDragHelper; import java.util.UUID; @@ -141,7 +139,7 @@ public abstract class BaseItemDragListener implements } @Override - public void onPreDragStart(DropTarget.DragObject dragObject) { + public void onPreDragStart(DragObject dragObject) { // The predrag starts when the workspace is not yet loaded. In some cases we set // the dragLayer alpha to 0 to have a nice fade-in animation. But that will prevent the // dragView from being visible. Instead just skip the fade-in animation here. @@ -152,41 +150,14 @@ public abstract class BaseItemDragListener implements } @Override - public void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted) { + public void onPreDragEnd(DragObject dragObject, boolean dragStarted) { if (dragStarted) { dragObject.dragView.setColor(0); } } @Override - public boolean supportsAppInfoDropTarget() { - return false; - } - - @Override - public boolean supportsDeleteDropTarget() { - return false; - } - - @Override - public float getIntrinsicIconScaleFactor() { - return 1f; - } - - @Override - public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete, - boolean success) { - if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() && - !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) { - // Exit spring loaded mode if we have not successfully dropped or have not handled the - // drop in Workspace - mLauncher.exitSpringLoadedDragModeDelayed(true, - Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); - } - - if (!success) { - d.deferDragViewCleanupPostAnimation = false; - } + public void onDropCompleted(View target, DragObject d, boolean success) { postCleanup(); } diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java index b8527148be..94023833ce 100644 --- a/src/com/android/launcher3/dragndrop/DragController.java +++ b/src/com/android/launcher3/dragndrop/DragController.java @@ -16,6 +16,9 @@ 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; import android.graphics.Bitmap; @@ -27,7 +30,6 @@ import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; -import android.view.inputmethod.InputMethodManager; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; @@ -39,6 +41,7 @@ import com.android.launcher3.accessibility.DragViewStateAnnouncer; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.TouchController; +import com.android.launcher3.util.UiThreadHelper; import java.util.ArrayList; @@ -120,6 +123,9 @@ public class DragController implements DragDriver.EventListener, TouchController /** * Starts a drag. + * When the drag is started, the UI automatically goes into spring loaded mode. On a successful + * drop, it is the responsibility of the {@link DropTarget} to exit out of the spring loaded + * mode. If the drop was cancelled for some reason, the UI will automatically exit out of this mode. * * @param b The bitmap to display as the drag image. It will be re-scaled to the * enlarged size. @@ -138,8 +144,7 @@ public class DragController implements DragDriver.EventListener, TouchController } // Hide soft keyboard, if visible - mLauncher.getSystemService(InputMethodManager.class) - .hideSoftInputFromWindow(mWindowToken, 0); + UiThreadHelper.hideKeyboardAsync(mLauncher, mWindowToken); mOptions = options; if (mOptions.systemDndStartPoint != null) { @@ -249,12 +254,23 @@ public class DragController implements DragDriver.EventListener, TouchController mDragObject.cancelled = true; mDragObject.dragComplete = true; if (!mIsInPreDrag) { - mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false); + dispatchDropComplete(null, false); } } endDrag(); } + private void dispatchDropComplete(View dropTarget, boolean accepted) { + 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.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); + mDragObject.deferDragViewCleanupPostAnimation = false; + } + + mDragObject.dragSource.onDropCompleted(dropTarget, mDragObject, accepted); + } + public void onAppsRemoved(ItemInfoMatcher matcher) { // Cancel the current drag if we are removing an app that we are dragging if (mDragObject != null) { @@ -576,7 +592,7 @@ public class DragController implements DragDriver.EventListener, TouchController if (flingAnimation != null) { flingAnimation.run(); } else if (!mIsInPreDrag) { - dropTarget.onDrop(mDragObject); + dropTarget.onDrop(mDragObject, mOptions); } accepted = true; } @@ -584,8 +600,7 @@ public class DragController implements DragDriver.EventListener, TouchController final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null; if (!mIsInPreDrag) { mLauncher.getUserEventDispatcher().logDragNDrop(mDragObject, dropTargetAsView); - mDragObject.dragSource.onDropCompleted( - dropTargetAsView, mDragObject, flingAnimation != null, accepted); + dispatchDropComplete(dropTargetAsView, accepted); } } diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index fde7995ce4..8189b236e9 100644 --- a/src/com/android/launcher3/dragndrop/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -24,46 +24,36 @@ import android.animation.ValueAnimator.AnimatorUpdateListener; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.Rect; -import android.graphics.Region; -import android.support.v4.graphics.ColorUtils; +import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.KeyEvent; -import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; 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; import com.android.launcher3.AbstractFloatingView; -import com.android.launcher3.AppWidgetResizeFrame; import com.android.launcher3.CellLayout; import com.android.launcher3.DropTargetBar; -import com.android.launcher3.ExtendedEditText; import com.android.launcher3.InsettableFrameLayout; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherAppWidgetHostView; -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.dynamicui.WallpaperColorInfo; +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.logging.LoggerUtils; +import com.android.launcher3.uioverrides.UiFactory; import com.android.launcher3.util.Themes; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.TouchController; -import com.android.launcher3.widget.WidgetsBottomSheet; import java.util.ArrayList; @@ -81,40 +71,29 @@ public class DragLayer extends InsettableFrameLayout { private Launcher mLauncher; - // Variables relating to resizing widgets - private final boolean mIsRtl; - private AppWidgetResizeFrame mCurrentResizeFrame; - // 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; private boolean mHoverPointClosesFolder = false; private final Rect mHitRect = new Rect(); - private final Rect mHighlightRect = new Rect(); private TouchCompleteListener mTouchCompleteListener; private int mTopViewIndex; private int mChildCountOnLastUpdate = -1; - // Darkening scrim - private float mBackgroundAlpha = 0; - // Related to adjacent page hints - private final Rect mScrollChildPosition = new Rect(); private final ViewGroupFocusHelper mFocusIndicatorHelper; - private final WallpaperColorInfo mWallpaperColorInfo; - - // Related to pinch-to-go-to-overview gesture. - private PinchToOverviewListener mPinchListener = null; + private final PageCutOutScrimDrawable mPageCutOutScrim; // Handles all apps pull up interaction private AllAppsTransitionController mAllAppsController; + protected TouchController[] mControllers; private TouchController mActiveController; /** * Used to create a new DragLayer from XML. @@ -129,9 +108,9 @@ public class DragLayer extends InsettableFrameLayout { setMotionEventSplittingEnabled(false); setChildrenDrawingOrderEnabled(true); - mIsRtl = Utilities.isRtl(getResources()); mFocusIndicatorHelper = new ViewGroupFocusHelper(this); - mWallpaperColorInfo = WallpaperColorInfo.getInstance(getContext()); + mPageCutOutScrim = new PageCutOutScrimDrawable(this); + mPageCutOutScrim.setCallback(this); } public void setup(Launcher launcher, DragController dragController, @@ -139,10 +118,7 @@ public class DragLayer extends InsettableFrameLayout { mLauncher = launcher; mDragController = dragController; mAllAppsController = allAppsTransitionController; - - boolean isAccessibilityEnabled = ((AccessibilityManager) mLauncher.getSystemService( - Context.ACCESSIBILITY_SERVICE)).isEnabled(); - onAccessibilityStateChanged(isAccessibilityEnabled); + mControllers = UiFactory.createTouchControllers(mLauncher); } public ViewGroupFocusHelper getFocusIndicatorHelper() { @@ -154,13 +130,9 @@ public class DragLayer extends InsettableFrameLayout { return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event); } - public void onAccessibilityStateChanged(boolean isAccessibilityEnabled) { - mPinchListener = FeatureFlags.LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW || isAccessibilityEnabled - ? null : new PinchToOverviewListener(mLauncher); - } - - public boolean isEventOverPageIndicator(MotionEvent ev) { - return isEventOverView(mLauncher.getWorkspace().getPageIndicator(), ev); + @Override + protected boolean verifyDrawable(Drawable who) { + return super.verifyDrawable(who) || who == mPageCutOutScrim; } public boolean isEventOverHotseat(MotionEvent ev) { @@ -180,36 +152,6 @@ public class DragLayer extends InsettableFrameLayout { return mHitRect.contains((int) ev.getX(), (int) ev.getY()); } - private boolean handleTouchDown(MotionEvent ev, boolean intercept) { - AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher); - if (topView != null && intercept) { - ExtendedEditText textView = topView.getActiveTextView(); - if (textView != null) { - if (!isEventOverView(textView, ev)) { - textView.dispatchBackKey(); - return true; - } - } else if (!isEventOverView(topView, ev)) { - if (isInAccessibleDrag()) { - // Do not close the container if in drag and drop. - if (!isEventOverDropTargetBar(ev)) { - return true; - } - } else { - mLauncher.getUserEventDispatcher().logActionTapOutside( - LoggerUtils.newContainerTarget(topView.getLogContainerType())); - topView.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. - View extendedTouch = topView.getExtendedTouchView(); - return extendedTouch == null || !isEventOverView(extendedTouch, ev); - } - } - } - return false; - } - @Override public boolean onInterceptTouchEvent(MotionEvent ev) { int action = ev.getAction(); @@ -219,9 +161,6 @@ public class DragLayer extends InsettableFrameLayout { // dray layer even if mAllAppsController is NOT the active controller. // TODO: handle other input other than touch mAllAppsController.cancelDiscoveryAnimation(); - if (handleTouchDown(ev, true)) { - return true; - } } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { if (mTouchCompleteListener != null) { mTouchCompleteListener.onTouchComplete(); @@ -230,12 +169,10 @@ public class DragLayer extends InsettableFrameLayout { } mActiveController = null; - if (mCurrentResizeFrame != null - && mCurrentResizeFrame.onControllerInterceptTouchEvent(ev)) { - mActiveController = mCurrentResizeFrame; + AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher); + if (topView != null && topView.onControllerInterceptTouchEvent(ev)) { + mActiveController = topView; return true; - } else { - clearResizeFrame(); } if (mDragController.onControllerInterceptTouchEvent(ev)) { @@ -243,21 +180,11 @@ public class DragLayer extends InsettableFrameLayout { return true; } - if (mAllAppsController.onControllerInterceptTouchEvent(ev)) { - mActiveController = mAllAppsController; - return true; - } - - WidgetsBottomSheet widgetsBottomSheet = WidgetsBottomSheet.getOpen(mLauncher); - if (widgetsBottomSheet != null && widgetsBottomSheet.onControllerInterceptTouchEvent(ev)) { - mActiveController = widgetsBottomSheet; - 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; } @@ -357,12 +284,7 @@ public class DragLayer extends InsettableFrameLayout { @Override public boolean onTouchEvent(MotionEvent ev) { int action = ev.getAction(); - - if (action == MotionEvent.ACTION_DOWN) { - if (handleTouchDown(ev, false)) { - return true; - } - } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { if (mTouchCompleteListener != null) { mTouchCompleteListener.onTouchComplete(); } @@ -542,25 +464,6 @@ public class DragLayer extends InsettableFrameLayout { } } - public void clearResizeFrame() { - if (mCurrentResizeFrame != null) { - removeView(mCurrentResizeFrame); - mCurrentResizeFrame = null; - } - } - - public void addResizeFrame(LauncherAppWidgetHostView widget, CellLayout cellLayout) { - clearResizeFrame(); - - mCurrentResizeFrame = (AppWidgetResizeFrame) LayoutInflater.from(mLauncher) - .inflate(R.layout.app_widget_resize_frame, this, false); - mCurrentResizeFrame.setupForWidget(widget, cellLayout, this); - ((LayoutParams) mCurrentResizeFrame.getLayoutParams()).customPosition = true; - - addView(mCurrentResizeFrame); - mCurrentResizeFrame.snapToWidget(false); - } - public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha, float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable, int duration) { @@ -573,13 +476,12 @@ public class DragLayer extends InsettableFrameLayout { onFinishRunnable, animationEndStyle, duration, null); } - public void animateViewIntoPosition(DragView dragView, final View child, - final Runnable onFinishAnimationRunnable, View anchorView) { - animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, anchorView); + public void animateViewIntoPosition(DragView dragView, final View child, View anchorView) { + animateViewIntoPosition(dragView, child, -1, anchorView); } public void animateViewIntoPosition(DragView dragView, final View child, int duration, - final Runnable onFinishAnimationRunnable, View anchorView) { + View anchorView) { ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent(); CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); parentChildren.measureChild(child); @@ -636,9 +538,6 @@ public class DragLayer extends InsettableFrameLayout { Runnable onCompleteRunnable = new Runnable() { public void run() { child.setVisibility(VISIBLE); - if (onFinishAnimationRunnable != null) { - onFinishAnimationRunnable.run(); - } } }; animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, toScale, toScale, @@ -858,48 +757,23 @@ public class DragLayer extends InsettableFrameLayout { } public void invalidateScrim() { - if (mBackgroundAlpha > 0.0f) { + if (mPageCutOutScrim.getAlpha() > 0) { invalidate(); } } + public Drawable getScrim() { + return mPageCutOutScrim; + } + @Override protected void dispatchDraw(Canvas canvas) { // Draw the background below children. - if (mBackgroundAlpha > 0.0f) { - // Update the scroll position first to ensure scrim cutout is in the right place. - mLauncher.getWorkspace().computeScrollWithoutInvalidation(); - - int alpha = (int) (mBackgroundAlpha * 255); - CellLayout currCellLayout = mLauncher.getWorkspace().getCurrentDragOverlappingLayout(); - canvas.save(); - if (currCellLayout != null && currCellLayout != mLauncher.getHotseat().getLayout()) { - // Cut a hole in the darkening scrim on the page that should be highlighted, if any. - getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect); - canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE); - } - // for super light wallpaper it needs to be darken for contrast to workspace - // for dark wallpapers the text is white so darkening works as well - int color = ColorUtils.compositeColors(0x66000000, mWallpaperColorInfo.getMainColor()); - canvas.drawColor(ColorUtils.setAlphaComponent(color, alpha)); - canvas.restore(); - } - + mPageCutOutScrim.draw(canvas); mFocusIndicatorHelper.draw(canvas); super.dispatchDraw(canvas); } - public void setBackgroundAlpha(float alpha) { - if (alpha != mBackgroundAlpha) { - mBackgroundAlpha = alpha; - invalidate(); - } - } - - public float getBackgroundAlpha() { - return mBackgroundAlpha; - } - @Override protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { View topView = AbstractFloatingView.getTopOpenView(mLauncher); diff --git a/src/com/android/launcher3/dragndrop/DragOptions.java b/src/com/android/launcher3/dragndrop/DragOptions.java index 9433aadc7e..f108f8b535 100644 --- a/src/com/android/launcher3/dragndrop/DragOptions.java +++ b/src/com/android/launcher3/dragndrop/DragOptions.java @@ -34,6 +34,9 @@ public class DragOptions { /** Determines when a pre-drag should transition to a drag. By default, this is immediate. */ public PreDragCondition preDragCondition = null; + /** Scale of the icons over the workspace icon size. */ + public float intrinsicIconScaleFactor = 1f; + /** * Specifies a condition that must be met before DragListener#onDragStart() is called. * By default, there is no condition and onDragStart() is called immediately following diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java index 33d4fa628a..7c89df37c0 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.FloatPropertyCompat; 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.LauncherModel; 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; @@ -169,7 +169,7 @@ public class DragView extends View { } }); - mBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight()); + mBitmap = bitmap; setDragRegion(new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight())); // The point in our scaled bitmap that the touch events are located @@ -427,6 +427,10 @@ public class DragView extends View { return mDragRegion; } + public Bitmap getPreviewBitmap() { + return mBitmap; + } + @Override protected void onDraw(Canvas canvas) { mHasDrawn = true; @@ -468,7 +472,7 @@ public class DragView extends View { 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/PageCutOutScrimDrawable.java b/src/com/android/launcher3/dragndrop/PageCutOutScrimDrawable.java new file mode 100644 index 0000000000..367124b5dd --- /dev/null +++ b/src/com/android/launcher3/dragndrop/PageCutOutScrimDrawable.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.dragndrop; + +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.Region; +import android.graphics.drawable.Drawable; +import android.support.v4.graphics.ColorUtils; + +import com.android.launcher3.CellLayout; +import com.android.launcher3.Launcher; +import com.android.launcher3.dynamicui.WallpaperColorInfo; + +/** + * Scrim drawable which draws a hole for the current drop target page. + */ +public class PageCutOutScrimDrawable extends Drawable { + + private final Rect mHighlightRect = new Rect(); + private final DragLayer mDragLayer; + private final Launcher mLauncher; + private final WallpaperColorInfo mWallpaperColorInfo; + + private int mAlpha = 0; + + public PageCutOutScrimDrawable(DragLayer dragLayer) { + mDragLayer = dragLayer; + mLauncher = Launcher.getLauncher(mDragLayer.getContext()); + mWallpaperColorInfo = WallpaperColorInfo.getInstance(mLauncher); + } + + @Override + public void draw(Canvas canvas) { + // Draw the background below children. + if (mAlpha > 0) { + // Update the scroll position first to ensure scrim cutout is in the right place. + mLauncher.getWorkspace().computeScrollWithoutInvalidation(); + CellLayout currCellLayout = mLauncher.getWorkspace().getCurrentDragOverlappingLayout(); + canvas.save(); + if (currCellLayout != null && currCellLayout != mLauncher.getHotseat().getLayout()) { + // Cut a hole in the darkening scrim on the page that should be highlighted, if any. + mDragLayer.getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect); + canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE); + } + // for super light wallpaper it needs to be darken for contrast to workspace + // for dark wallpapers the text is white so darkening works as well + int color = ColorUtils.compositeColors(0x66000000, mWallpaperColorInfo.getMainColor()); + canvas.drawColor(ColorUtils.setAlphaComponent(color, mAlpha)); + canvas.restore(); + } + } + + @Override + public void setAlpha(int alpha) { + if (mAlpha != alpha) { + mAlpha = alpha; + invalidateSelf(); + } + } + + @Override + public int getAlpha() { + return mAlpha; + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } +} diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java index a70a9bb1fe..52167bb8bf 100644 --- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java +++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java @@ -29,7 +29,7 @@ import android.os.Process; import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.IconCache; -import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAnimUtils; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; @@ -83,8 +83,8 @@ class PinShortcutRequestActivityInfo extends ShortcutConfigActivityInfo { public com.android.launcher3.ShortcutInfo createShortcutInfo() { // Total duration for the drop animation to complete. long duration = mContext.getResources().getInteger(R.integer.config_dropAnimMaxDuration) + - Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT + - mContext.getResources().getInteger(R.integer.config_overlayTransitionTime) / 2; + LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY + + LauncherAnimUtils.SPRING_LOADED_TRANSITION_MS; // Delay the actual accept() call until the drop animation is complete. return LauncherAppsCompatVO.createShortcutInfoFromPinItemRequest( mContext, mRequest, duration); diff --git a/src/com/android/launcher3/dynamicui/ColorExtractionService.java b/src/com/android/launcher3/dynamicui/ColorExtractionService.java deleted file mode 100644 index b9dd3b588f..0000000000 --- a/src/com/android/launcher3/dynamicui/ColorExtractionService.java +++ /dev/null @@ -1,204 +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.dynamicui; - -import android.annotation.TargetApi; -import android.app.WallpaperManager; -import android.app.job.JobParameters; -import android.app.job.JobService; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.BitmapRegionDecoder; -import android.graphics.Rect; -import android.graphics.drawable.BitmapDrawable; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.ParcelFileDescriptor; -import android.support.v7.graphics.Palette; -import android.util.Log; - -import com.android.launcher3.LauncherProvider; -import com.android.launcher3.LauncherSettings; -import com.android.launcher3.R; -import com.android.launcher3.Utilities; -import com.android.launcher3.config.FeatureFlags; - -import java.io.IOException; - -/** - * Extracts colors from the wallpaper, and saves results to {@link LauncherProvider}. - */ -public class ColorExtractionService extends JobService { - - private static final String TAG = "ColorExtractionService"; - private static final boolean DEBUG = false; - - /** The fraction of the wallpaper to extract colors for use on the hotseat. */ - private static final float HOTSEAT_FRACTION = 1f / 4; - - private HandlerThread mWorkerThread; - private Handler mWorkerHandler; - - @Override - public void onCreate() { - super.onCreate(); - mWorkerThread = new HandlerThread("ColorExtractionService"); - mWorkerThread.start(); - mWorkerHandler = new Handler(mWorkerThread.getLooper()); - } - - @Override - public void onDestroy() { - super.onDestroy(); - mWorkerThread.quit(); - } - - @Override - public boolean onStartJob(final JobParameters jobParameters) { - if (DEBUG) Log.d(TAG, "onStartJob"); - mWorkerHandler.post(new Runnable() { - @Override - public void run() { - WallpaperManager wallpaperManager = WallpaperManager.getInstance( - ColorExtractionService.this); - int wallpaperId = ExtractionUtils.getWallpaperId(wallpaperManager); - - ExtractedColors extractedColors = new ExtractedColors(); - if (wallpaperManager.getWallpaperInfo() != null) { - // We can't extract colors from live wallpapers; always use the default color. - extractedColors.updateHotseatPalette(null); - - if (FeatureFlags.QSB_IN_HOTSEAT || FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) { - extractedColors.updateWallpaperThemePalette(null); - } - } else { - // We extract colors for the hotseat and status bar separately, - // since they only consider part of the wallpaper. - extractedColors.updateHotseatPalette(getHotseatPalette()); - - if (FeatureFlags.LIGHT_STATUS_BAR) { - extractedColors.updateStatusBarPalette(getStatusBarPalette()); - } - - if (FeatureFlags.QSB_IN_HOTSEAT || FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) { - extractedColors.updateWallpaperThemePalette(getWallpaperPalette()); - } - } - - // Save the extracted colors and wallpaper id to LauncherProvider. - String colorsString = extractedColors.encodeAsString(); - Bundle extras = new Bundle(); - extras.putInt(LauncherSettings.Settings.EXTRA_WALLPAPER_ID, wallpaperId); - extras.putString(LauncherSettings.Settings.EXTRA_EXTRACTED_COLORS, colorsString); - getContentResolver().call( - LauncherSettings.Settings.CONTENT_URI, - LauncherSettings.Settings.METHOD_SET_EXTRACTED_COLORS_AND_WALLPAPER_ID, - null, extras); - jobFinished(jobParameters, false /* needsReschedule */); - if (DEBUG) Log.d(TAG, "job finished!"); - } - }); - return true; - } - - @Override - public boolean onStopJob(JobParameters jobParameters) { - if (DEBUG) Log.d(TAG, "onStopJob"); - mWorkerHandler.removeCallbacksAndMessages(null); - return true; - } - - @TargetApi(Build.VERSION_CODES.N) - private Palette getHotseatPalette() { - WallpaperManager wallpaperManager = WallpaperManager.getInstance(this); - if (Utilities.ATLEAST_NOUGAT) { - try (ParcelFileDescriptor fd = wallpaperManager - .getWallpaperFile(WallpaperManager.FLAG_SYSTEM)) { - BitmapRegionDecoder decoder = BitmapRegionDecoder - .newInstance(fd.getFileDescriptor(), false); - int height = decoder.getHeight(); - Rect decodeRegion = new Rect(0, (int) (height * (1f - HOTSEAT_FRACTION)), - decoder.getWidth(), height); - Bitmap bitmap = decoder.decodeRegion(decodeRegion, null); - decoder.recycle(); - if (bitmap != null) { - return Palette.from(bitmap).clearFilters().generate(); - } - } catch (IOException | NullPointerException e) { - Log.e(TAG, "Fetching partial bitmap failed, trying old method", e); - } - } - - Bitmap wallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap(); - return Palette.from(wallpaper) - .setRegion(0, (int) (wallpaper.getHeight() * (1f - HOTSEAT_FRACTION)), - wallpaper.getWidth(), wallpaper.getHeight()) - .clearFilters() - .generate(); - } - - @TargetApi(Build.VERSION_CODES.N) - private Palette getStatusBarPalette() { - WallpaperManager wallpaperManager = WallpaperManager.getInstance(this); - int statusBarHeight = getResources() - .getDimensionPixelSize(R.dimen.status_bar_height); - - if (Utilities.ATLEAST_NOUGAT) { - try (ParcelFileDescriptor fd = wallpaperManager - .getWallpaperFile(WallpaperManager.FLAG_SYSTEM)) { - BitmapRegionDecoder decoder = BitmapRegionDecoder - .newInstance(fd.getFileDescriptor(), false); - Rect decodeRegion = new Rect(0, 0, - decoder.getWidth(), statusBarHeight); - Bitmap bitmap = decoder.decodeRegion(decodeRegion, null); - decoder.recycle(); - if (bitmap != null) { - return Palette.from(bitmap).clearFilters().generate(); - } - } catch (IOException | NullPointerException e) { - Log.e(TAG, "Fetching partial bitmap failed, trying old method", e); - } - } - - Bitmap wallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap(); - return Palette.from(wallpaper) - .setRegion(0, 0, wallpaper.getWidth(), statusBarHeight) - .clearFilters() - .generate(); - } - - @TargetApi(Build.VERSION_CODES.N) - private Palette getWallpaperPalette() { - WallpaperManager wallpaperManager = WallpaperManager.getInstance(this); - if (Utilities.ATLEAST_NOUGAT) { - try (ParcelFileDescriptor fd = wallpaperManager - .getWallpaperFile(WallpaperManager.FLAG_SYSTEM)) { - Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor()); - if (bitmap != null) { - return Palette.from(bitmap).clearFilters().generate(); - } - } catch (IOException | NullPointerException e) { - Log.e(TAG, "Fetching partial bitmap failed, trying old method", e); - } - } - - Bitmap wallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap(); - return Palette.from(wallpaper).clearFilters().generate(); - } -} diff --git a/src/com/android/launcher3/dynamicui/ExtractedColors.java b/src/com/android/launcher3/dynamicui/ExtractedColors.java deleted file mode 100644 index 2d8bb869f4..0000000000 --- a/src/com/android/launcher3/dynamicui/ExtractedColors.java +++ /dev/null @@ -1,184 +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.dynamicui; - -import android.app.WallpaperManager; -import android.content.Context; -import android.graphics.Color; -import android.support.annotation.Nullable; -import android.support.v4.graphics.ColorUtils; -import android.support.v7.graphics.Palette; -import android.util.Log; - -import com.android.launcher3.Utilities; -import com.android.launcher3.config.FeatureFlags; - -import java.util.ArrayList; -import java.util.Arrays; - -/** - * Saves and loads colors extracted from the wallpaper, as well as the associated wallpaper id. - */ -public class ExtractedColors { - private static final String TAG = "ExtractedColors"; - - public static final int DEFAULT_LIGHT = Color.WHITE; - public static final int DEFAULT_DARK = Color.BLACK; - - // These color profile indices should NOT be changed, since they are used when saving and - // loading extracted colors. New colors should always be added at the end. - public static final int VERSION_INDEX = 0; - public static final int HOTSEAT_INDEX = 1; - public static final int STATUS_BAR_INDEX = 2; - public static final int WALLPAPER_VIBRANT_INDEX = 3; - public static final int ALLAPPS_GRADIENT_MAIN_INDEX = 4; - public static final int ALLAPPS_GRADIENT_SECONDARY_INDEX = 5; - - private static final int VERSION; - private static final int[] DEFAULT_VALUES; - - static { - if (FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) { - VERSION = 3; - DEFAULT_VALUES = new int[] { - VERSION, // VERSION_INDEX - 0x40FFFFFF, // HOTSEAT_INDEX: White with 25% alpha - DEFAULT_DARK, // STATUS_BAR_INDEX - 0xFFCCCCCC, // WALLPAPER_VIBRANT_INDEX - 0xFF000000, // ALLAPPS_GRADIENT_MAIN_INDEX - 0xFF000000 // ALLAPPS_GRADIENT_SECONDARY_INDEX - }; - } else if (FeatureFlags.QSB_IN_HOTSEAT) { - VERSION = 2; - DEFAULT_VALUES = new int[] { - VERSION, // VERSION_INDEX - 0x40FFFFFF, // HOTSEAT_INDEX: White with 25% alpha - DEFAULT_DARK, // STATUS_BAR_INDEX - 0xFFCCCCCC, // WALLPAPER_VIBRANT_INDEX - }; - } else { - VERSION = 1; - DEFAULT_VALUES = new int[] { - VERSION, // VERSION_INDEX - 0x40FFFFFF, // HOTSEAT_INDEX: White with 25% alpha - DEFAULT_DARK, // STATUS_BAR_INDEX - }; - } - } - - private static final String COLOR_SEPARATOR = ","; - - private final ArrayList mListeners = new ArrayList<>(); - private final int[] mColors; - - public ExtractedColors() { - // The first entry is reserved for the version number. - mColors = Arrays.copyOf(DEFAULT_VALUES, DEFAULT_VALUES.length); - } - - public void setColorAtIndex(int index, int color) { - if (index > VERSION_INDEX && index < mColors.length) { - mColors[index] = color; - } else { - Log.e(TAG, "Attempted to set a color at an invalid index " + index); - } - } - - /** - * Encodes {@link #mColors} as a comma-separated String. - */ - String encodeAsString() { - StringBuilder colorsStringBuilder = new StringBuilder(); - for (int color : mColors) { - colorsStringBuilder.append(color).append(COLOR_SEPARATOR); - } - return colorsStringBuilder.toString(); - } - - /** - * Loads colors and wallpaper id from {@link Utilities#getPrefs(Context)}. - * These were saved there in {@link ColorExtractionService}. - */ - public void load(Context context) { - String encodedString = Utilities.getPrefs(context).getString( - ExtractionUtils.EXTRACTED_COLORS_PREFERENCE_KEY, VERSION + ""); - - String[] splitColorsString = encodedString.split(COLOR_SEPARATOR); - if (splitColorsString.length == DEFAULT_VALUES.length && - Integer.parseInt(splitColorsString[VERSION_INDEX]) == VERSION) { - // Parse and apply the saved values. - for (int i = 0; i < mColors.length; i++) { - mColors[i] = Integer.parseInt(splitColorsString[i]); - } - } else { - // Leave the values as default values as the saved values may not be compatible. - ExtractionUtils.startColorExtractionService(context); - } - } - - /** @param index must be one of the index values defined at the top of this class. */ - public int getColor(int index) { - return mColors[index]; - } - - /** - * The hotseat's color is defined as follows: - * - 12% black for super light wallpaper - * - 18% white for super dark - * - 25% white otherwise - */ - public void updateHotseatPalette(Palette hotseatPalette) { - int hotseatColor; - if (hotseatPalette != null && ExtractionUtils.isSuperLight(hotseatPalette)) { - hotseatColor = ColorUtils.setAlphaComponent(Color.BLACK, (int) (0.12f * 255)); - } else if (hotseatPalette != null && ExtractionUtils.isSuperDark(hotseatPalette)) { - hotseatColor = ColorUtils.setAlphaComponent(Color.WHITE, (int) (0.18f * 255)); - } else { - hotseatColor = DEFAULT_VALUES[HOTSEAT_INDEX]; - } - setColorAtIndex(HOTSEAT_INDEX, hotseatColor); - } - - public void updateStatusBarPalette(Palette statusBarPalette) { - setColorAtIndex(STATUS_BAR_INDEX, ExtractionUtils.isSuperLight(statusBarPalette) ? - DEFAULT_LIGHT : DEFAULT_DARK); - } - - public void updateWallpaperThemePalette(@Nullable Palette wallpaperPalette) { - int defaultColor = DEFAULT_VALUES[WALLPAPER_VIBRANT_INDEX]; - setColorAtIndex(WALLPAPER_VIBRANT_INDEX, wallpaperPalette == null - ? defaultColor : wallpaperPalette.getVibrantColor(defaultColor)); - } - - public void addOnChangeListener(OnChangeListener listener) { - mListeners.add(listener); - } - - public void notifyChange() { - for (OnChangeListener listener : mListeners) { - listener.onExtractedColorsChanged(); - } - } - - /** - * Interface for listening for extracted color changes - */ - public interface OnChangeListener { - - void onExtractedColorsChanged(); - } -} diff --git a/src/com/android/launcher3/dynamicui/ExtractionUtils.java b/src/com/android/launcher3/dynamicui/ExtractionUtils.java deleted file mode 100644 index cc0e0bed1d..0000000000 --- a/src/com/android/launcher3/dynamicui/ExtractionUtils.java +++ /dev/null @@ -1,125 +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.dynamicui; - -import android.annotation.TargetApi; -import android.app.WallpaperManager; -import android.app.job.JobInfo; -import android.app.job.JobScheduler; -import android.content.ComponentName; -import android.content.Context; -import android.content.SharedPreferences; -import android.graphics.Color; -import android.os.Build; -import android.support.v4.graphics.ColorUtils; -import android.support.v7.graphics.Palette; - -import com.android.launcher3.Utilities; -import com.android.launcher3.config.FeatureFlags; - -import java.util.List; - -/** - * Contains helper fields and methods related to extracting colors from the wallpaper. - */ -public class ExtractionUtils { - public static final String EXTRACTED_COLORS_PREFERENCE_KEY = "pref_extractedColors"; - public static final String WALLPAPER_ID_PREFERENCE_KEY = "pref_wallpaperId"; - - private static final float MIN_CONTRAST_RATIO = 2f; - - /** - * Extract colors in the :wallpaper-chooser process, if the wallpaper id has changed. - * When the new colors are saved in the LauncherProvider, - * Launcher will be notified in Launcher#onSettingsChanged(String, String). - */ - public static void startColorExtractionServiceIfNecessary(final Context context) { - if (FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) { - return; - } - // Run on a background thread, since the service is asynchronous anyway. - Utilities.THREAD_POOL_EXECUTOR.execute(new Runnable() { - @Override - public void run() { - if (hasWallpaperIdChanged(context)) { - startColorExtractionService(context); - } - } - }); - } - - /** Starts the {@link ColorExtractionService} without checking the wallpaper id */ - public static void startColorExtractionService(Context context) { - if (FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) { - return; - } - JobScheduler jobScheduler = (JobScheduler) context.getSystemService( - Context.JOB_SCHEDULER_SERVICE); - jobScheduler.schedule(new JobInfo.Builder(Utilities.COLOR_EXTRACTION_JOB_ID, - new ComponentName(context, ColorExtractionService.class)) - .setMinimumLatency(0).build()); - } - - private static boolean hasWallpaperIdChanged(Context context) { - if (!Utilities.ATLEAST_NOUGAT) { - // TODO: update an id in sharedprefs in onWallpaperChanged broadcast, and read it here. - return false; - } - final SharedPreferences sharedPrefs = Utilities.getPrefs(context); - int wallpaperId = getWallpaperId(WallpaperManager.getInstance(context)); - int savedWallpaperId = sharedPrefs.getInt(ExtractionUtils.WALLPAPER_ID_PREFERENCE_KEY, -1); - return wallpaperId != savedWallpaperId; - } - - @TargetApi(Build.VERSION_CODES.N) - public static int getWallpaperId(WallpaperManager wallpaperManager) { - return Utilities.ATLEAST_NOUGAT ? - wallpaperManager.getWallpaperId(WallpaperManager.FLAG_SYSTEM) : -1; - } - - public static boolean isSuperLight(Palette p) { - return !isLegibleOnWallpaper(Color.WHITE, p.getSwatches()); - } - - public static boolean isSuperDark(Palette p) { - return !isLegibleOnWallpaper(Color.BLACK, p.getSwatches()); - } - - /** - * Given a color, returns true if that color is legible on - * the given wallpaper color swatches, else returns false. - */ - private static boolean isLegibleOnWallpaper(int color, List wallpaperSwatches) { - int legiblePopulation = 0; - int illegiblePopulation = 0; - for (Palette.Swatch swatch : wallpaperSwatches) { - if (isLegible(color, swatch.getRgb())) { - legiblePopulation += swatch.getPopulation(); - } else { - illegiblePopulation += swatch.getPopulation(); - } - } - return legiblePopulation > illegiblePopulation; - } - - /** @return Whether the foreground color is legible on the background color. */ - private static boolean isLegible(int foreground, int background) { - background = ColorUtils.setAlphaComponent(background, 255); - return ColorUtils.calculateContrast(foreground, background) >= MIN_CONTRAST_RATIO; - } - -} diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java index f25345ee8d..5954efa95f 100644 --- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java +++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java @@ -1,9 +1,8 @@ package com.android.launcher3.folder; +public class ClippedFolderIconLayoutRule { -public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule { - - static final int MAX_NUM_ITEMS_IN_PREVIEW = 4; + public static final int MAX_NUM_ITEMS_IN_PREVIEW = 4; private static final int MIN_NUM_ITEMS_IN_PREVIEW = 2; private static final float MIN_SCALE = 0.48f; @@ -11,8 +10,8 @@ public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule private static final float MAX_RADIUS_DILATION = 0.15f; private static final float ITEM_RADIUS_SCALE_FACTOR = 1.33f; - private static final int EXIT_INDEX = -2; - private static final int ENTER_INDEX = -3; + public static final int EXIT_INDEX = -2; + public static final int ENTER_INDEX = -3; private float[] mTmpPoint = new float[2]; @@ -22,7 +21,6 @@ public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule private boolean mIsRtl; private float mBaselineIconScale; - @Override public void init(int availableSpace, float intrinsicIconSize, boolean rtl) { mAvailableSpace = availableSpace; mRadius = ITEM_RADIUS_SCALE_FACTOR * availableSpace / 2f; @@ -31,19 +29,18 @@ public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule mBaselineIconScale = availableSpace / (intrinsicIconSize * 1f); } - @Override public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems, PreviewItemDrawingParams params) { - float totalScale = scaleForItem(index, curNumItems); + float totalScale = scaleForItem(curNumItems); float transX; float transY; float overlayAlpha = 0; - if (index == getExitIndex()) { + if (index == EXIT_INDEX) { // 0 1 * <-- Exit position (row 0, col 2) // 2 3 getGridPosition(0, 2, mTmpPoint); - } else if (index == getEnterIndex()) { + } else if (index == ENTER_INDEX) { // 0 1 // 2 3 * <-- Enter position (row 1, col 2) getGridPosition(1, 2, mTmpPoint); @@ -120,7 +117,7 @@ public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule MIN_NUM_ITEMS_IN_PREVIEW) / (MAX_NUM_ITEMS_IN_PREVIEW - MIN_NUM_ITEMS_IN_PREVIEW)); double theta = theta0 + index * (2 * Math.PI / curNumItems) * direction; - float halfIconSize = (mIconSize * scaleForItem(index, curNumItems)) / 2; + float halfIconSize = (mIconSize * scaleForItem(curNumItems)) / 2; // Map the location along the circle, and offset the coordinates to represent the center // of the icon, and to be based from the top / left of the preview area. The y component @@ -130,10 +127,9 @@ public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule } - @Override - public float scaleForItem(int index, int numItems) { + public float scaleForItem(int numItems) { // Scale is determined by the number of items in the preview. - float scale = 1f; + final float scale; if (numItems <= 2) { scale = MAX_SCALE; } else if (numItems == 3) { @@ -141,37 +137,10 @@ public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule } else { scale = MIN_SCALE; } - return scale * mBaselineIconScale; } - @Override public float getIconSize() { return mIconSize; } - - @Override - public int maxNumItems() { - return MAX_NUM_ITEMS_IN_PREVIEW; - } - - @Override - public boolean clipToBackground() { - return true; - } - - @Override - public boolean hasEnterExitIndices() { - return true; - } - - @Override - public int getExitIndex() { - return EXIT_INDEX; - } - - @Override - public int getEnterIndex() { - return ENTER_INDEX; - } } diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 85792d4cc9..2168001ca3 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -16,11 +16,12 @@ 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; import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Resources; @@ -34,10 +35,10 @@ import android.view.FocusFinder; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; +import android.view.MotionEvent; import android.view.View; import android.view.ViewDebug; import android.view.accessibility.AccessibilityEvent; -import android.view.animation.AccelerateInterpolator; import android.view.animation.AnimationUtils; import android.view.inputmethod.EditorInfo; import android.widget.TextView; @@ -55,24 +56,20 @@ import com.android.launcher3.FolderInfo; import com.android.launcher3.FolderInfo.FolderListener; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherAnimUtils; import com.android.launcher3.LauncherSettings; -import com.android.launcher3.LogDecelerateInterpolator; import com.android.launcher3.OnAlarmListener; import com.android.launcher3.PagedView; import com.android.launcher3.R; import com.android.launcher3.ShortcutInfo; -import com.android.launcher3.UninstallDropTarget.DropTargetSource; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace.ItemOperator; import com.android.launcher3.accessibility.AccessibleDragListenerAdapter; -import com.android.launcher3.anim.AnimationLayerSet; -import com.android.launcher3.anim.CircleRevealOutlineProvider; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragController.DragListener; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragOptions; +import com.android.launcher3.logging.LoggerUtils; import com.android.launcher3.pageindicators.PageIndicatorDots; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; @@ -89,8 +86,7 @@ import java.util.List; */ public class Folder extends AbstractFloatingView implements DragSource, View.OnClickListener, View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener, - View.OnFocusChangeListener, DragListener, DropTargetSource, - ExtendedEditText.OnBackKeyListener { + View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener { private static final String TAG = "Launcher.Folder"; /** @@ -137,10 +133,6 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC private AnimatorSet mCurrentAnimator; - private final int mExpandDuration; - public final int mMaterialExpandDuration; - private final int mMaterialExpandStagger; - protected final Launcher mLauncher; protected DragController mDragController; public FolderInfo mInfo; @@ -181,10 +173,6 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC @ViewDebug.ExportedProperty(category = "launcher") private boolean mDestroyed; - @Thunk Runnable mDeferredAction; - private boolean mDeferDropAfterUninstall; - private boolean mUninstallSuccessful; - // Folder scrolling private int mScrollAreaOffset; @@ -201,9 +189,6 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC super(context, attrs); setAlwaysDrawnWithCacheEnabled(false); Resources res = getResources(); - mExpandDuration = res.getInteger(R.integer.config_folderExpandDuration); - mMaterialExpandDuration = res.getInteger(R.integer.config_materialFolderExpandDuration); - mMaterialExpandStagger = res.getInteger(R.integer.config_materialFolderExpandStagger); if (sDefaultFolderName == null) { sDefaultFolderName = res.getString(R.string.folder_name); @@ -382,11 +367,6 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC return false; } - @Override - public ExtendedEditText getActiveTextView() { - return isEditingName() ? mFolderName : null; - } - public FolderIcon getFolderIcon() { return mFolderIcon; } @@ -487,25 +467,6 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC ? R.layout.user_folder : R.layout.user_folder_icon_normalized, null); } - /** - * This method is intended to make the UserFolder to be visually identical in size and position - * to its associated FolderIcon. This allows for a seamless transition into the expanded state. - */ - private void positionAndSizeAsIcon() { - if (!(getParent() instanceof DragLayer)) return; - setScaleX(0.8f); - setScaleY(0.8f); - setAlpha(0f); - mState = STATE_SMALL; - } - - private void prepareReveal() { - setScaleX(1f); - setScaleY(1f); - setAlpha(1f); - mState = STATE_SMALL; - } - private void startAnimation(final AnimatorSet a) { if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) { mCurrentAnimator.cancel(); @@ -525,61 +486,6 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC a.start(); } - private AnimatorSet getOpeningAnimator() { - prepareReveal(); - mFolderIcon.growAndFadeOut(); - - AnimatorSet anim = LauncherAnimUtils.createAnimatorSet(); - - int width = getFolderWidth(); - int height = getFolderHeight(); - - float transX = - 0.075f * (width / 2 - getPivotX()); - float transY = - 0.075f * (height / 2 - getPivotY()); - setTranslationX(transX); - setTranslationY(transY); - PropertyValuesHolder tx = PropertyValuesHolder.ofFloat(TRANSLATION_X, transX, 0); - PropertyValuesHolder ty = PropertyValuesHolder.ofFloat(TRANSLATION_Y, transY, 0); - - Animator drift = ObjectAnimator.ofPropertyValuesHolder(this, tx, ty); - drift.setDuration(mMaterialExpandDuration); - drift.setStartDelay(mMaterialExpandStagger); - drift.setInterpolator(new LogDecelerateInterpolator(100, 0)); - - int rx = (int) Math.max(Math.max(width - getPivotX(), 0), getPivotX()); - int ry = (int) Math.max(Math.max(height - getPivotY(), 0), getPivotY()); - float radius = (float) Math.hypot(rx, ry); - - Animator reveal = new CircleRevealOutlineProvider((int) getPivotX(), - (int) getPivotY(), 0, radius).createRevealAnimator(this); - reveal.setDuration(mMaterialExpandDuration); - reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); - - mContent.setAlpha(0f); - Animator iconsAlpha = ObjectAnimator.ofFloat(mContent, "alpha", 0f, 1f); - iconsAlpha.setDuration(mMaterialExpandDuration); - iconsAlpha.setStartDelay(mMaterialExpandStagger); - iconsAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); - - mFooter.setAlpha(0f); - Animator textAlpha = ObjectAnimator.ofFloat(mFooter, "alpha", 0f, 1f); - textAlpha.setDuration(mMaterialExpandDuration); - textAlpha.setStartDelay(mMaterialExpandStagger); - textAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); - - anim.play(drift); - anim.play(iconsAlpha); - anim.play(textAlpha); - anim.play(reveal); - - AnimationLayerSet layerSet = new AnimationLayerSet(); - layerSet.addView(mContent); - layerSet.addView(mFooter); - anim.addListener(layerSet); - - return anim; - } - /** * Opens the user folder described by the specified tag. The opening of the folder * is animated relative to the specified View. If the View is null, no animation @@ -621,9 +527,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC final Runnable onCompleteRunnable; centerAboutIcon(); - AnimatorSet anim = FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION - ? new FolderAnimationManager(this, true /* isOpening */).getAnimator() - : getOpeningAnimator(); + AnimatorSet anim = new FolderAnimationManager(this, true /* isOpening */).getAnimator(); onCompleteRunnable = new Runnable() { @Override public void run() { @@ -633,12 +537,8 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { - if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION) { - mFolderIcon.setBackgroundVisible(false); - mFolderIcon.drawLeaveBehindIfExists(); - } else { - mFolderIcon.setVisibility(INVISIBLE); - } + mFolderIcon.setBackgroundVisible(false); + mFolderIcon.drawLeaveBehindIfExists(); Utilities.sendCustomAccessibilityEvent( Folder.this, @@ -728,11 +628,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC } if (mFolderIcon != null) { - if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION) { - mFolderIcon.clearLeaveBehindIfExists(); - } else { - mFolderIcon.shrinkAndFadeIn(animate); - } + mFolderIcon.clearLeaveBehindIfExists(); } if (!(getParent() instanceof DragLayer)) return; @@ -749,21 +645,8 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC parent.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); } - private AnimatorSet getClosingAnimator() { - AnimatorSet animatorSet = LauncherAnimUtils.createAnimatorSet(); - animatorSet.play(LauncherAnimUtils.ofViewAlphaAndScale(this, 0, 0.9f, 0.9f)); - - AnimationLayerSet layerSet = new AnimationLayerSet(); - layerSet.addView(this); - animatorSet.addListener(layerSet); - animatorSet.setDuration(mExpandDuration); - return animatorSet; - } - private void animateClosed() { - AnimatorSet a = FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION - ? new FolderAnimationManager(this, false /* isOpening */).getAnimator() - : getClosingAnimator(); + AnimatorSet a = new FolderAnimationManager(this, false /* isOpening */).getAnimator(); a.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -790,16 +673,12 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC clearFocus(); if (mFolderIcon != null) { mFolderIcon.setVisibility(View.VISIBLE); - if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION) { - mFolderIcon.setBackgroundVisible(true); - mFolderIcon.mFolderName.setTextVisibility(true); - } + mFolderIcon.setBackgroundVisible(true); + mFolderIcon.mFolderName.setTextVisibility(true); if (wasAnimated) { - if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION) { - mFolderIcon.mBackground.fadeInBackgroundShadow(); - mFolderIcon.mBackground.animateBackgroundStroke(); - mFolderIcon.onFolderClose(mContent.getCurrentPage()); - } + mFolderIcon.mBackground.fadeInBackgroundShadow(); + mFolderIcon.mBackground.animateBackgroundStroke(); + mFolderIcon.onFolderClose(mContent.getCurrentPage()); if (mFolderIcon.hasBadge()) { mFolderIcon.createBadgeScaleAnimator(0f, 1f).start(); } @@ -824,6 +703,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC mContent.setCurrentPage(0); } + @Override public boolean acceptDrop(DragObject d) { final ItemInfo item = d.dragInfo; final int itemType = item.itemType; @@ -852,18 +732,14 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC return (getLayoutDirection() == LAYOUT_DIRECTION_RTL); } - @Override - public void onDragOver(DragObject d) { - onDragOver(d, REORDER_DELAY); - } - private int getTargetRank(DragObject d, float[] recycle) { recycle = d.getVisualCenter(recycle); return mContent.findNearestArea( (int) recycle[0] - getPaddingLeft(), (int) recycle[1] - getPaddingTop()); } - @Thunk void onDragOver(DragObject d, int reorderDelay) { + @Override + public void onDragOver(DragObject d) { if (mScrollPauseAlarm.alarmPending()) { return; } @@ -976,23 +852,9 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC } public void onDropCompleted(final View target, final DragObject d, - final boolean isFlingToDelete, final boolean success) { - if (mDeferDropAfterUninstall) { - Log.d(TAG, "Deferred handling drop because waiting for uninstall."); - mDeferredAction = new Runnable() { - public void run() { - onDropCompleted(target, d, isFlingToDelete, success); - mDeferredAction = null; - } - }; - return; - } + final boolean success) { - boolean beingCalledAfterUninstall = mDeferredAction != null; - boolean successfulDrop = - success && (!beingCalledAfterUninstall || mUninstallSuccessful); - - if (successfulDrop) { + if (success) { if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon && target != this) { replaceFolderWithFinalItem(); } @@ -1007,14 +869,14 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC mItemsInvalidated = true; try (SuppressInfoChanges s = new SuppressInfoChanges()) { - mFolderIcon.onDrop(d); + mFolderIcon.onDrop(d, true /* itemReturnedOnFailedDrop */); } } if (target != this) { if (mOnExitAlarm.alarmPending()) { mOnExitAlarm.cancelAlarm(); - if (!successfulDrop) { + if (!success) { mSuppressFolderDeletion = true; } mScrollPauseAlarm.cancelAlarm(); @@ -1038,41 +900,6 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, false, mLauncher.getModelWriter()); } - - if (!isFlingToDelete) { - // Fling to delete already exits spring loaded mode after the animation finishes. - mLauncher.exitSpringLoadedDragModeDelayed(successfulDrop, - Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); - } - } - - @Override - public void deferCompleteDropAfterUninstallActivity() { - mDeferDropAfterUninstall = true; - } - - @Override - public void onDragObjectRemoved(boolean success) { - mDeferDropAfterUninstall = false; - mUninstallSuccessful = success; - if (mDeferredAction != null) { - mDeferredAction.run(); - } - } - - @Override - public float getIntrinsicIconScaleFactor() { - return 1f; - } - - @Override - public boolean supportsAppInfoDropTarget() { - return true; - } - - @Override - public boolean supportsDeleteDropTarget() { - return true; } private void updateItemLocationsInDatabaseBatch() { @@ -1095,10 +922,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC } public boolean isDropEnabled() { - if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION) { - return mState != STATE_ANIMATING; - } - return true; + return mState != STATE_ANIMATING; } public boolean isFull() { @@ -1113,7 +937,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC int width = getFolderWidth(); int height = getFolderHeight(); - float scale = parent.getDescendantRectRelativeToSelf(mFolderIcon, sTempRect); + parent.getDescendantRectRelativeToSelf(mFolderIcon, sTempRect); int centerX = sTempRect.centerX(); int centerY = sTempRect.centerY(); int centeredLeft = centerX - width / 2; @@ -1329,22 +1153,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC } } - public void onDrop(DragObject d) { - Runnable cleanUpRunnable = null; - - // If we are coming from All Apps space, we defer removing the extra empty screen - // until the folder closes - if (d.dragSource != mLauncher.getWorkspace() && !(d.dragSource instanceof Folder)) { - cleanUpRunnable = new Runnable() { - @Override - public void run() { - mLauncher.exitSpringLoadedDragModeDelayed(true, - Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, - null); - } - }; - } - + public void onDrop(DragObject d, DragOptions options) { // If the icon was dropped while the page was being scrolled, we need to compute // the target location again such that the icon is placed of the final page. if (!mContent.rankOnCurrentPage(mEmptyCellRank)) { @@ -1410,8 +1219,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC float scaleY = getScaleY(); setScaleX(1.0f); setScaleY(1.0f); - mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, currentDragView, - cleanUpRunnable, null); + mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, currentDragView, null); setScaleX(scaleX); setScaleY(scaleY); } else { @@ -1436,6 +1244,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, mLauncher.getModelWriter()); } + mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); if (d.stateAnnouncer != null) { d.stateAnnouncer.completeAction(R.string.item_moved); } @@ -1611,7 +1420,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC @Override public void onAlarm(Alarm alarm) { // Reorder immediately on page change. - onDragOver(mDragObject, 1); + onDragOver(mDragObject); } } @@ -1654,7 +1463,45 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC } @Override - public int getLogContainerType() { - return ContainerType.FOLDER; + public void logActionCommand(int command) { + mLauncher.getUserEventDispatcher().logActionCommand( + command, getFolderIcon(), ContainerType.FOLDER); + } + + @Override + public void onBackPressed() { + if (isEditingName()) { + mFolderName.dispatchBackKey(); + } else { + super.onBackPressed(); + } + } + + @Override + public boolean onControllerInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + DragLayer dl = mLauncher.getDragLayer(); + + if (isEditingName()) { + if (!dl.isEventOverView(mFolderName, ev)) { + mFolderName.dispatchBackKey(); + return true; + } + return false; + } else if (!dl.isEventOverView(this, ev)) { + if (mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) { + // Do not close the container if in drag and drop. + if (!dl.isEventOverView(mLauncher.getDropTargetBar(), ev)) { + return true; + } + } else { + mLauncher.getUserEventDispatcher().logActionTapOutside( + LoggerUtils.newContainerTarget(ContainerType.FOLDER)); + close(true); + return true; + } + } + } + return false; } } diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java index 69705d5942..ec448e9978 100644 --- a/src/com/android/launcher3/folder/FolderAnimationManager.java +++ b/src/com/android/launcher3/folder/FolderAnimationManager.java @@ -16,12 +16,16 @@ package com.android.launcher3.folder; +import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; +import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; import android.content.Context; +import android.content.res.Resources; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.GradientDrawable; @@ -74,19 +78,6 @@ public class FolderAnimationManager { private final PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0); - private static final Property SCALE_PROPERTY = - new Property(Float.class, "scale") { - @Override - public Float get(View view) { - return view.getScaleX(); - } - - @Override - public void set(View view, Float scale) { - view.setScaleX(scale); - view.setScaleY(scale); - } - }; public FolderAnimationManager(Folder folder, boolean isOpening) { mFolder = folder; @@ -101,8 +92,9 @@ public class FolderAnimationManager { mIsOpening = isOpening; - mDuration = mFolder.mMaterialExpandDuration; - mDelay = mContext.getResources().getInteger(R.integer.config_folderDelay); + Resources res = mContent.getResources(); + mDuration = res.getInteger(R.integer.config_materialFolderExpandDuration); + mDelay = res.getInteger(R.integer.config_folderDelay); mFolderInterpolator = AnimationUtils.loadInterpolator(mContext, R.interpolator.folder_interpolator); @@ -118,7 +110,7 @@ public class FolderAnimationManager { */ public AnimatorSet getAnimator() { final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) mFolder.getLayoutParams(); - FolderIcon.PreviewLayoutRule rule = mFolderIcon.getLayoutRule(); + ClippedFolderIconLayoutRule rule = mFolderIcon.getLayoutRule(); final List itemsInPreview = mFolderIcon.getPreviewItems(); // Match position of the FolderIcon @@ -129,7 +121,7 @@ public class FolderAnimationManager { float initialSize = (scaledRadius * 2) * scaleRelativeToDragLayer; // Match size/scale of icons in the preview - float previewScale = rule.scaleForItem(0, itemsInPreview.size()); + float previewScale = rule.scaleForItem(itemsInPreview.size()); float previewSize = rule.getIconSize() * previewScale; float initialScale = previewSize / itemsInPreview.get(0).getIconSize() * scaleRelativeToDragLayer; @@ -242,15 +234,14 @@ public class FolderAnimationManager { */ private void addPreviewItemAnimators(AnimatorSet animatorSet, final float folderScale, int previewItemOffsetX, int previewItemOffsetY) { - FolderIcon.PreviewLayoutRule rule = mFolderIcon.getLayoutRule(); + ClippedFolderIconLayoutRule rule = mFolderIcon.getLayoutRule(); boolean isOnFirstPage = mFolder.mContent.getCurrentPage() == 0; final List itemsInPreview = isOnFirstPage ? mFolderIcon.getPreviewItems() : mFolderIcon.getPreviewItemsOnPage(mFolder.mContent.getCurrentPage()); final int numItemsInPreview = itemsInPreview.size(); final int numItemsInFirstPagePreview = isOnFirstPage - ? numItemsInPreview - : FolderIcon.NUM_ITEMS_IN_PREVIEW; + ? numItemsInPreview : MAX_NUM_ITEMS_IN_PREVIEW; TimeInterpolator previewItemInterpolator = getPreviewItemInterpolator(); @@ -264,7 +255,7 @@ public class FolderAnimationManager { cwc.setupLp(btv); // Match scale of icons in the preview of the items on the first page. - float previewScale = rule.scaleForItem(i, numItemsInFirstPagePreview); + float previewScale = rule.scaleForItem(numItemsInFirstPagePreview); float previewSize = rule.getIconSize() * previewScale; float iconScale = previewSize / itemsInPreview.get(i).getIconSize(); @@ -299,7 +290,7 @@ public class FolderAnimationManager { scaleAnimator.setInterpolator(previewItemInterpolator); play(animatorSet, scaleAnimator); - if (mFolder.getItemCount() > FolderIcon.NUM_ITEMS_IN_PREVIEW) { + if (mFolder.getItemCount() > MAX_NUM_ITEMS_IN_PREVIEW) { // These delays allows the preview items to move as part of the Folder's motion, // and its only necessary for large folders because of differing interpolators. int delay = mIsOpening ? mDelay : mDelay * 2; @@ -349,7 +340,7 @@ public class FolderAnimationManager { } private TimeInterpolator getPreviewItemInterpolator() { - if (mFolder.getItemCount() > FolderIcon.NUM_ITEMS_IN_PREVIEW) { + if (mFolder.getItemCount() > MAX_NUM_ITEMS_IN_PREVIEW) { // With larger folders, we want the preview items to reach their final positions faster // (when opening) and later (when closing) so that they appear aligned with the rest of // the folder items when they are both visible. diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 8339bc5b8c..5983029749 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -16,8 +16,10 @@ 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.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Canvas; @@ -34,8 +36,6 @@ import android.view.MotionEvent; 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; @@ -49,7 +49,6 @@ import com.android.launcher3.FolderInfo; import com.android.launcher3.FolderInfo.FolderListener; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherAnimUtils; import com.android.launcher3.LauncherSettings; import com.android.launcher3.OnAlarmListener; import com.android.launcher3.R; @@ -58,9 +57,9 @@ import com.android.launcher3.SimpleOnStylusPressListener; 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.config.FeatureFlags; import com.android.launcher3.dragndrop.BaseItemDragListener; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragView; @@ -71,8 +70,6 @@ import com.android.launcher3.widget.PendingAddShortcutInfo; import java.util.ArrayList; import java.util.List; -import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION; - /** * An icon that can appear on in the workspace representing an {@link Folder}. */ @@ -82,10 +79,6 @@ public class FolderIcon extends FrameLayout implements FolderListener { private FolderInfo mInfo; @Thunk static boolean sStaticValuesDirty = true; - public static final int NUM_ITEMS_IN_PREVIEW = FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON ? - StackFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW : - ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; - private CheckLongPressHelper mLongPressHelper; private StylusEventHelper mStylusEventHelper; @@ -103,9 +96,10 @@ public class FolderIcon extends FrameLayout implements FolderListener { private boolean mBackgroundIsVisible = true; FolderIconPreviewVerifier mPreviewVerifier; - PreviewLayoutRule mPreviewLayoutRule; + ClippedFolderIconLayoutRule mPreviewLayoutRule; private PreviewItemManager mPreviewItemManager; private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0); + private List mCurrentPreviewItems = new ArrayList<>(); boolean mAnimating = false; private Rect mTempBounds = new Rect(); @@ -146,9 +140,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { private void init() { mLongPressHelper = new CheckLongPressHelper(this); mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); - mPreviewLayoutRule = FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON ? - new StackFolderIconLayoutRule() : - new ClippedFolderIconLayoutRule(); + mPreviewLayoutRule = new ClippedFolderIconLayoutRule(); mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); mPreviewItemManager = new PreviewItemManager(this); } @@ -206,7 +198,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { private void setFolder(Folder folder) { mFolder = folder; mPreviewVerifier = new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv); - mPreviewItemManager.updateItemDrawingParams(false); + updatePreviewItems(false); } private boolean willAcceptItem(ItemInfo item) { @@ -261,7 +253,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { public void performCreateAnimation(final ShortcutInfo destInfo, final View destView, final ShortcutInfo srcInfo, final DragView srcView, Rect dstRect, - float scaleRelativeToDragLayer, Runnable postAnimationRunnable) { + float scaleRelativeToDragLayer) { prepareCreateAnimation(destView); addItem(destInfo); // This will animate the first item from it's position as an icon into its @@ -270,7 +262,8 @@ public class FolderIcon extends FrameLayout implements FolderListener { .start(); // This will animate the dragView (srcView) into the new folder - onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, postAnimationRunnable); + onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, + false /* itemReturnedOnFailedDrop */); } public void performDestroyAnimation(Runnable onCompleteRunnable) { @@ -285,7 +278,8 @@ public class FolderIcon extends FrameLayout implements FolderListener { } private void onDrop(final ShortcutInfo item, DragView animateView, Rect finalRect, - float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable) { + float scaleRelativeToDragLayer, int index, + boolean itemReturnedOnFailedDrop) { item.cellX = -1; item.cellY = -1; @@ -301,7 +295,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { to = new Rect(); Workspace workspace = mLauncher.getWorkspace(); // Set cellLayout and this to it's final state to compute final animation locations - workspace.setFinalTransitionTransform((CellLayout) getParent().getParent()); + workspace.setFinalTransitionTransform(); float scaleX = getScaleX(); float scaleY = getScaleY(); setScaleX(1.0f); @@ -310,25 +304,28 @@ public class FolderIcon extends FrameLayout implements FolderListener { // Finished computing final animation locations, restore current state setScaleX(scaleX); setScaleY(scaleY); - workspace.resetTransitionTransform((CellLayout) getParent().getParent()); + workspace.resetTransitionTransform(); } + int numItemsInPreview = Math.min(MAX_NUM_ITEMS_IN_PREVIEW, index + 1); boolean itemAdded = false; - if (index >= mPreviewLayoutRule.maxNumItems() - && mPreviewLayoutRule.hasEnterExitIndices()) { - List oldPreviewItems = getPreviewItemsOnPage(0); + if (itemReturnedOnFailedDrop || index >= MAX_NUM_ITEMS_IN_PREVIEW) { + List oldPreviewItems = new ArrayList<>(mCurrentPreviewItems); addItem(item, false); - List newPreviewItems = getPreviewItemsOnPage(0); + mCurrentPreviewItems.clear(); + mCurrentPreviewItems.addAll(getPreviewItems()); - if (!oldPreviewItems.containsAll(newPreviewItems)) { - for (int i = 0; i < newPreviewItems.size(); ++i) { - if (newPreviewItems.get(i).getTag().equals(item)) { + if (!oldPreviewItems.equals(mCurrentPreviewItems)) { + for (int i = 0; i < mCurrentPreviewItems.size(); ++i) { + if (mCurrentPreviewItems.get(i).getTag().equals(item)) { // If the item dropped is going to be in the preview, we update the // index here to reflect its position in the preview. index = i; } } - mPreviewItemManager.onDrop(oldPreviewItems, newPreviewItems, item); + + mPreviewItemManager.hidePreviewItem(index, true); + mPreviewItemManager.onDrop(oldPreviewItems, mCurrentPreviewItems, item); itemAdded = true; } else { removeItem(item, false); @@ -340,20 +337,20 @@ public class FolderIcon extends FrameLayout implements FolderListener { } int[] center = new int[2]; - float scale = getLocalCenterForIndex(index, index + 1, center); + float scale = getLocalCenterForIndex(index, numItemsInPreview, center); center[0] = (int) Math.round(scaleRelativeToDragLayer * center[0]); center[1] = (int) Math.round(scaleRelativeToDragLayer * center[1]); to.offset(center[0] - animateView.getMeasuredWidth() / 2, center[1] - animateView.getMeasuredHeight() / 2); - float finalAlpha = index < mPreviewLayoutRule.maxNumItems() ? 0.5f : 0f; + float finalAlpha = index < MAX_NUM_ITEMS_IN_PREVIEW ? 0.5f : 0f; float finalScale = scale * scaleRelativeToDragLayer; dragLayer.animateView(animateView, from, to, finalAlpha, 1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION, - new DecelerateInterpolator(2), new AccelerateInterpolator(2), - postAnimationRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null); + Interpolators.DEACCEL_2, Interpolators.ACCEL_2, + null, DragLayer.ANIMATION_END_DISAPPEAR, null); mFolder.hideItem(item); @@ -371,7 +368,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { } } - public void onDrop(DragObject d) { + public void onDrop(DragObject d, boolean itemReturnedOnFailedDrop) { ShortcutInfo item; if (d.dragInfo instanceof AppInfo) { // Came from all apps -- make a copy @@ -383,7 +380,8 @@ public class FolderIcon extends FrameLayout implements FolderListener { item = (ShortcutInfo) d.dragInfo; } mFolder.notifyDrop(); - onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable); + onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), + itemReturnedOnFailedDrop); } public void setBadgeInfo(FolderBadgeInfo badgeInfo) { @@ -391,7 +389,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { mBadgeInfo = badgeInfo; } - public PreviewLayoutRule getLayoutRule() { + public ClippedFolderIconLayoutRule getLayoutRule() { return mPreviewLayoutRule; } @@ -420,7 +418,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { private float getLocalCenterForIndex(int index, int curNumItems, int[] center) { mTmpParams = mPreviewItemManager.computePreviewItemDrawingParams( - Math.min(mPreviewLayoutRule.maxNumItems(), index), curNumItems, mTmpParams); + Math.min(MAX_NUM_ITEMS_IN_PREVIEW, index), curNumItems, mTmpParams); mTmpParams.transX += mBackground.basePreviewOffsetX; mTmpParams.transY += mBackground.basePreviewOffsetY; @@ -474,19 +472,17 @@ public class FolderIcon extends FrameLayout implements FolderListener { Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG); } else { saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG); - if (mPreviewLayoutRule.clipToBackground()) { - canvas.clipPath(mBackground.getClipPath(), Region.Op.INTERSECT); - } + canvas.clipPath(mBackground.getClipPath(), Region.Op.INTERSECT); } mPreviewItemManager.draw(canvas); - if (mPreviewLayoutRule.clipToBackground() && canvas.isHardwareAccelerated()) { + if (canvas.isHardwareAccelerated()) { mBackground.clipCanvasHardware(canvas); } canvas.restoreToCount(saveCount); - if (mPreviewLayoutRule.clipToBackground() && !mBackground.drawingDelegated()) { + if (!mBackground.drawingDelegated()) { mBackground.drawBackgroundStroke(canvas); } @@ -542,7 +538,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { itemsToDisplay.add(itemsOnPage.get(rank)); } - if (itemsToDisplay.size() == FolderIcon.NUM_ITEMS_IN_PREVIEW) { + if (itemsToDisplay.size() == MAX_NUM_ITEMS_IN_PREVIEW) { break; } } @@ -556,11 +552,17 @@ public class FolderIcon extends FrameLayout implements FolderListener { @Override public void onItemsChanged(boolean animate) { - mPreviewItemManager.updateItemDrawingParams(animate); + updatePreviewItems(animate); invalidate(); requestLayout(); } + private void updatePreviewItems(boolean animate) { + mPreviewItemManager.updatePreviewItems(animate); + mCurrentPreviewItems.clear(); + mCurrentPreviewItems.addAll(getPreviewItems()); + } + @Override public void prepareAutoUpdate() { } @@ -631,30 +633,6 @@ public class FolderIcon extends FrameLayout implements FolderListener { mInfo.removeListener(mFolder); } - public void shrinkAndFadeIn(boolean animate) { - // We remove and re-draw the FolderIcon in-case it has changed - final PreviewImageView previewImage = PreviewImageView.get(getContext()); - previewImage.removeFromParent(); - copyToPreview(previewImage); - - clearLeaveBehindIfExists(); - - ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(previewImage, 1, 1, 1); - oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration)); - oa.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - // Remove the ImageView copy of the FolderIcon and make the original visible. - previewImage.removeFromParent(); - setVisibility(View.VISIBLE); - } - }); - oa.start(); - if (!animate) { - oa.end(); - } - } - public void clearLeaveBehindIfExists() { ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true; if (mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { @@ -673,48 +651,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { } } - public void growAndFadeOut() { - drawLeaveBehindIfExists(); - - // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original - PreviewImageView previewImage = PreviewImageView.get(getContext()); - copyToPreview(previewImage); - setVisibility(View.INVISIBLE); - - ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(previewImage, 0, 1.5f, 1.5f); - oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration)); - oa.start(); - } - - /** - * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView - * in the DragLayer in the exact absolute location of the original FolderIcon. - */ - private void copyToPreview(PreviewImageView previewImageView) { - previewImageView.copy(this); - if (mFolder != null) { - previewImageView.setPivotX(mFolder.getPivotXForIconAnimation()); - previewImageView.setPivotY(mFolder.getPivotYForIconAnimation()); - mFolder.bringToFront(); - } - } - public void onFolderClose(int currentPage) { mPreviewItemManager.onFolderClose(currentPage); } - - interface PreviewLayoutRule { - PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems, - PreviewItemDrawingParams params); - void init(int availableSpace, float intrinsicIconSize, boolean rtl); - float scaleForItem(int index, int totalNumItems); - float getIconSize(); - int maxNumItems(); - boolean clipToBackground(); - - boolean hasEnterExitIndices(); - int getExitIndex(); - int getEnterIndex(); - - } } diff --git a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java index d054a5d424..5a27cd43e2 100644 --- a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java +++ b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java @@ -18,7 +18,8 @@ package com.android.launcher3.folder; import com.android.launcher3.FolderInfo; import com.android.launcher3.InvariantDeviceProfile; -import com.android.launcher3.config.FeatureFlags; + +import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; /** * Verifies whether an item in a Folder is displayed in the FolderIcon preview. @@ -45,9 +46,7 @@ public class FolderIconPreviewVerifier { mMaxGridCountY, mMaxItemsPerPage, mGridSize); mGridCountX = mGridSize[0]; - mDisplayingUpperLeftQuadrant = FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION - && !FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON - && numItemsInFolder > FolderIcon.NUM_ITEMS_IN_PREVIEW; + mDisplayingUpperLeftQuadrant = numItemsInFolder > MAX_NUM_ITEMS_IN_PREVIEW; } /** @@ -70,6 +69,6 @@ public class FolderIconPreviewVerifier { int row = rank / mGridCountX; return col < 2 && row < 2; } - return rank < FolderIcon.NUM_ITEMS_IN_PREVIEW; + return rank < MAX_NUM_ITEMS_IN_PREVIEW; } } diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index f4ac0a1404..9e5bc4f093 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -27,7 +27,6 @@ import android.view.Gravity; 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.ShortcutAndWidgetContainer; 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 @@ public class FolderPagedView extends PagedView { 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/folder/PreviewImageView.java b/src/com/android/launcher3/folder/PreviewImageView.java deleted file mode 100644 index 65d9db1180..0000000000 --- a/src/com/android/launcher3/folder/PreviewImageView.java +++ /dev/null @@ -1,97 +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.folder; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.view.View; -import android.widget.ImageView; - -import com.android.launcher3.Launcher; -import com.android.launcher3.R; -import com.android.launcher3.dragndrop.DragLayer; - -/** - * A temporary view which displays the a bitmap (used for folder icon animation) - */ -public class PreviewImageView extends ImageView { - - private final Rect mTempRect = new Rect(); - private final DragLayer mParent; - - private Bitmap mBitmap; - private Canvas mCanvas; - - public PreviewImageView(DragLayer parent) { - super(parent.getContext()); - mParent = parent; - } - - public void copy(View view) { - final int width = view.getMeasuredWidth(); - final int height = view.getMeasuredHeight(); - - if (mBitmap == null || mBitmap.getWidth() != width || mBitmap.getHeight() != height) { - mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - mCanvas = new Canvas(mBitmap); - } - - DragLayer.LayoutParams lp; - if (getLayoutParams() instanceof DragLayer.LayoutParams) { - lp = (DragLayer.LayoutParams) getLayoutParams(); - } else { - lp = new DragLayer.LayoutParams(width, height); - } - - // The layout from which the folder is being opened may be scaled, adjust the starting - // view size by this scale factor. - float scale = mParent.getDescendantRectRelativeToSelf(view, mTempRect); - lp.customPosition = true; - lp.x = mTempRect.left; - lp.y = mTempRect.top; - lp.width = (int) (scale * width); - lp.height = (int) (scale * height); - - mCanvas.drawColor(0, PorterDuff.Mode.CLEAR); - view.draw(mCanvas); - setImageBitmap(mBitmap); - - // Just in case this image view is still in the drag layer from a previous animation, - // we remove it and re-add it. - removeFromParent(); - mParent.addView(this, lp); - } - - public void removeFromParent() { - if (mParent.indexOfChild(this) != -1) { - mParent.removeView(this); - } - } - - public static PreviewImageView get(Context context) { - DragLayer dragLayer = Launcher.getLauncher(context).getDragLayer(); - PreviewImageView view = (PreviewImageView) dragLayer.getTag(R.id.preview_image_id); - if (view == null) { - view = new PreviewImageView(dragLayer); - dragLayer.setTag(R.id.preview_image_id, view); - } - return view; - } -} diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java index 2d979a6619..06d3eb166b 100644 --- a/src/com/android/launcher3/folder/PreviewItemManager.java +++ b/src/com/android/launcher3/folder/PreviewItemManager.java @@ -29,11 +29,13 @@ import android.widget.TextView; import com.android.launcher3.BubbleTextView; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.Utilities; -import com.android.launcher3.config.FeatureFlags; import java.util.ArrayList; import java.util.List; +import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ENTER_INDEX; +import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.EXIT_INDEX; +import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; import static com.android.launcher3.folder.FolderIcon.DROP_IN_ANIMATION_DURATION; /** @@ -108,7 +110,7 @@ public class PreviewItemManager { mIcon.mPreviewLayoutRule.init(mIcon.mBackground.previewSize, mIntrinsicIconSize, Utilities.isRtl(mIcon.getResources())); - updateItemDrawingParams(false); + updatePreviewItems(false); } } @@ -183,6 +185,11 @@ public class PreviewItemManager { } public void hidePreviewItem(int index, boolean hidden) { + // If there are more params than visible in the preview, they are used for enter/exit + // animation purposes and they were added to the front of the list. + // To index the params properly, we need to skip these params. + index = index + Math.max(mFirstPageParams.size() - MAX_NUM_ITEMS_IN_PREVIEW, 0); + PreviewItemDrawingParams params = index < mFirstPageParams.size() ? mFirstPageParams.get(index) : null; if (params != null) { @@ -202,7 +209,7 @@ public class PreviewItemManager { params.add(new PreviewItemDrawingParams(0, 0, 0, 0)); } - int numItemsInFirstPagePreview = page == 0 ? items.size() : FolderIcon.NUM_ITEMS_IN_PREVIEW; + int numItemsInFirstPagePreview = page == 0 ? items.size() : MAX_NUM_ITEMS_IN_PREVIEW; for (int i = 0; i < params.size(); i++) { PreviewItemDrawingParams p = params.get(i); p.drawable = items.get(i).getCompoundDrawables()[1]; @@ -213,7 +220,7 @@ public class PreviewItemManager { p.drawable.setCallback(mIcon); } - if (!animate || FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON) { + if (!animate) { computePreviewItemDrawingParams(i, numItemsInFirstPagePreview, p); if (mReferenceDrawable == null) { mReferenceDrawable = p.drawable; @@ -264,7 +271,7 @@ public class PreviewItemManager { } } - void updateItemDrawingParams(boolean animate) { + void updatePreviewItems(boolean animate) { buildParamsForPage(0, mFirstPageParams, animate); } @@ -308,8 +315,8 @@ public class PreviewItemManager { int prevIndex = newParams.indexOf(moveIn.get(i)); PreviewItemDrawingParams p = params.get(prevIndex); computePreviewItemDrawingParams(prevIndex, numItems, p); - updateTransitionParam(p, moveIn.get(i), mIcon.mPreviewLayoutRule.getEnterIndex(), - newParams.indexOf(moveIn.get(i))); + updateTransitionParam(p, moveIn.get(i), ENTER_INDEX, newParams.indexOf(moveIn.get(i)), + numItems); } // Items that are moving into new positions within the preview. @@ -317,7 +324,7 @@ public class PreviewItemManager { int oldIndex = oldParams.indexOf(newParams.get(newIndex)); if (oldIndex >= 0 && newIndex != oldIndex) { PreviewItemDrawingParams p = params.get(newIndex); - updateTransitionParam(p, newParams.get(newIndex), oldIndex, newIndex); + updateTransitionParam(p, newParams.get(newIndex), oldIndex, newIndex, numItems); } } @@ -328,7 +335,7 @@ public class PreviewItemManager { BubbleTextView item = moveOut.get(i); int oldIndex = oldParams.indexOf(item); PreviewItemDrawingParams p = computePreviewItemDrawingParams(oldIndex, numItems, null); - updateTransitionParam(p, item, oldIndex, mIcon.mPreviewLayoutRule.getExitIndex()); + updateTransitionParam(p, item, oldIndex, EXIT_INDEX, numItems); params.add(0, p); // We want these items first so that they are on drawn last. } @@ -340,7 +347,7 @@ public class PreviewItemManager { } private void updateTransitionParam(final PreviewItemDrawingParams p, BubbleTextView btv, - int prevIndex, int newIndex) { + int prevIndex, int newIndex, int numItems) { p.drawable = btv.getCompoundDrawables()[1]; if (!mIcon.mFolder.isOpen()) { // Set the callback to FolderIcon as it is responsible to drawing the icon. The @@ -348,9 +355,8 @@ public class PreviewItemManager { p.drawable.setCallback(mIcon); } - FolderPreviewItemAnim anim = new FolderPreviewItemAnim(this, p, prevIndex, - FolderIcon.NUM_ITEMS_IN_PREVIEW, newIndex, FolderIcon.NUM_ITEMS_IN_PREVIEW, - DROP_IN_ANIMATION_DURATION, null); + FolderPreviewItemAnim anim = new FolderPreviewItemAnim(this, p, prevIndex, numItems, + newIndex, numItems, DROP_IN_ANIMATION_DURATION, null); if (p.anim != null && !p.anim.hasEqualFinalState(anim)) { p.anim.cancel(); } diff --git a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java deleted file mode 100644 index 7d10556d01..0000000000 --- a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java +++ /dev/null @@ -1,115 +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.folder; - -public class StackFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule { - - static final int MAX_NUM_ITEMS_IN_PREVIEW = 3; - - // The degree to which the item in the back of the stack is scaled [0...1] - // (0 means it's not scaled at all, 1 means it's scaled to nothing) - private static final float PERSPECTIVE_SCALE_FACTOR = 0.35f; - - // The amount of vertical spread between items in the stack [0...1] - private static final float PERSPECTIVE_SHIFT_FACTOR = 0.18f; - - private float mBaselineIconScale; - private int mBaselineIconSize; - private int mAvailableSpaceInPreview; - private float mMaxPerspectiveShift; - - @Override - public void init(int availableSpace, float intrinsicIconSize, boolean rtl) { - mAvailableSpaceInPreview = availableSpace; - - // cos(45) = 0.707 + ~= 0.1) = 0.8f - int adjustedAvailableSpace = (int) ((mAvailableSpaceInPreview / 2) * (1 + 0.8f)); - - int unscaledHeight = (int) (intrinsicIconSize * (1 + PERSPECTIVE_SHIFT_FACTOR)); - - mBaselineIconScale = (1.0f * adjustedAvailableSpace / unscaledHeight); - - mBaselineIconSize = (int) (intrinsicIconSize * mBaselineIconScale); - mMaxPerspectiveShift = mBaselineIconSize * PERSPECTIVE_SHIFT_FACTOR; - } - - @Override - public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems, - PreviewItemDrawingParams params) { - float scale = scaleForItem(index, curNumItems); - - index = MAX_NUM_ITEMS_IN_PREVIEW - index - 1; - float r = (index * 1.0f) / (MAX_NUM_ITEMS_IN_PREVIEW - 1); - - float offset = (1 - r) * mMaxPerspectiveShift; - float scaledSize = scale * mBaselineIconSize; - float scaleOffsetCorrection = (1 - scale) * mBaselineIconSize; - - // We want to imagine our coordinates from the bottom left, growing up and to the - // right. This is natural for the x-axis, but for the y-axis, we have to invert things. - float transY = mAvailableSpaceInPreview - (offset + scaledSize + scaleOffsetCorrection); - float transX = (mAvailableSpaceInPreview - scaledSize) / 2; - float totalScale = mBaselineIconScale * scale; - final float overlayAlpha = (80 * (1 - r)) / 255f; - - if (params == null) { - params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha); - } else { - params.update(transX, transY, totalScale); - params.overlayAlpha = overlayAlpha; - } - return params; - } - - @Override - public int maxNumItems() { - return MAX_NUM_ITEMS_IN_PREVIEW; - } - - @Override - public float getIconSize() { - return mBaselineIconSize; - } - - @Override - public float scaleForItem(int index, int numItems) { - // Scale is determined by the position of the icon in the preview. - index = MAX_NUM_ITEMS_IN_PREVIEW - index - 1; - float r = (index * 1.0f) / (MAX_NUM_ITEMS_IN_PREVIEW - 1); - return (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r)); - } - - @Override - public boolean clipToBackground() { - return false; - } - - @Override - public boolean hasEnterExitIndices() { - return false; - } - - @Override - public int getExitIndex() { - throw new RuntimeException("hasEnterExitIndices not supported"); - } - - @Override - public int getEnterIndex() { - throw new RuntimeException("hasEnterExitIndices not supported"); - } -} diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java index 10e91c0a83..355c231f3e 100644 --- a/src/com/android/launcher3/graphics/DragPreviewProvider.java +++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java @@ -18,10 +18,15 @@ package com.android.launcher3.graphics; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.BlurMaskFilter; import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.Region.Op; import android.graphics.drawable.Drawable; +import android.os.Handler; import android.view.View; import com.android.launcher3.BubbleTextView; @@ -30,6 +35,9 @@ import com.android.launcher3.LauncherAppWidgetHostView; import com.android.launcher3.R; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.util.UiThreadHelper; + +import java.nio.ByteBuffer; /** * A utility class to generate preview bitmap for dragging. @@ -45,6 +53,7 @@ public class DragPreviewProvider { protected final int blurSizeOutline; + private OutlineGeneratorCallback mOutlineGeneratorCallback; public Bitmap generatedDragOutline; public DragPreviewProvider(View view) { @@ -106,7 +115,7 @@ public class DragPreviewProvider { * Returns a new bitmap to show when the {@link #mView} is being dragged around. * Responsibility for the bitmap is transferred to the caller. */ - public Bitmap createDragBitmap(Canvas canvas) { + public Bitmap createDragBitmap() { float scale = 1f; int width = mView.getWidth(); int height = mView.getHeight(); @@ -124,7 +133,7 @@ public class DragPreviewProvider { Bitmap b = Bitmap.createBitmap(width + blurSizeOutline, height + blurSizeOutline, Bitmap.Config.ARGB_8888); - canvas.setBitmap(b); + Canvas canvas = new Canvas(b); canvas.save(); canvas.scale(scale, scale); @@ -136,43 +145,13 @@ public class DragPreviewProvider { return b; } - public final void generateDragOutline(Canvas canvas) { - if (FeatureFlags.IS_DOGFOOD_BUILD && generatedDragOutline != null) { + public final void generateDragOutline(Bitmap preview) { + if (FeatureFlags.IS_DOGFOOD_BUILD && mOutlineGeneratorCallback != null) { throw new RuntimeException("Drag outline generated twice"); } - generatedDragOutline = createDragOutline(canvas); - } - - /** - * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location. - * Responsibility for the bitmap is transferred to the caller. - */ - public Bitmap createDragOutline(Canvas canvas) { - float scale = 1f; - int width = mView.getWidth(); - int height = mView.getHeight(); - - if (mView instanceof LauncherAppWidgetHostView) { - scale = ((LauncherAppWidgetHostView) mView).getScaleToFit(); - width = (int) Math.floor(mView.getWidth() * scale); - height = (int) Math.floor(mView.getHeight() * scale); - } - - Bitmap b = Bitmap.createBitmap(width + blurSizeOutline, height + blurSizeOutline, - Bitmap.Config.ALPHA_8); - canvas.setBitmap(b); - - canvas.save(); - canvas.scale(scale, scale); - drawDragView(canvas); - canvas.restore(); - - HolographicOutlineHelper.getInstance(mView.getContext()) - .applyExpensiveOutlineWithBlur(b, canvas); - - canvas.setBitmap(null); - return b; + mOutlineGeneratorCallback = new OutlineGeneratorCallback(preview); + new Handler(UiThreadHelper.getBackgroundLooper()).post(mOutlineGeneratorCallback); } protected static Rect getDrawableBounds(Drawable d) { @@ -201,4 +180,89 @@ public class DragPreviewProvider { - previewPadding / 2); return scale; } + + protected Bitmap convertPreviewToAlphaBitmap(Bitmap preview) { + return preview.copy(Bitmap.Config.ALPHA_8, true); + } + + private class OutlineGeneratorCallback implements Runnable { + + private final Bitmap mPreviewSnapshot; + private final Context mContext; + + OutlineGeneratorCallback(Bitmap preview) { + mPreviewSnapshot = preview; + mContext = mView.getContext(); + } + + @Override + public void run() { + Bitmap preview = convertPreviewToAlphaBitmap(mPreviewSnapshot); + + // We start by removing most of the alpha channel so as to ignore shadows, and + // other types of partial transparency when defining the shape of the object + byte[] pixels = new byte[preview.getWidth() * preview.getHeight()]; + ByteBuffer buffer = ByteBuffer.wrap(pixels); + buffer.rewind(); + preview.copyPixelsToBuffer(buffer); + + for (int i = 0; i < pixels.length; i++) { + if ((pixels[i] & 0xFF) < 188) { + pixels[i] = 0; + } + } + + buffer.rewind(); + preview.copyPixelsFromBuffer(buffer); + + final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + Canvas canvas = new Canvas(); + + // calculate the outer blur first + paint.setMaskFilter(new BlurMaskFilter(blurSizeOutline, BlurMaskFilter.Blur.OUTER)); + int[] outerBlurOffset = new int[2]; + Bitmap thickOuterBlur = preview.extractAlpha(paint, outerBlurOffset); + + paint.setMaskFilter(new BlurMaskFilter( + mContext.getResources().getDimension(R.dimen.blur_size_thin_outline), + BlurMaskFilter.Blur.OUTER)); + int[] brightOutlineOffset = new int[2]; + Bitmap brightOutline = preview.extractAlpha(paint, brightOutlineOffset); + + // calculate the inner blur + canvas.setBitmap(preview); + canvas.drawColor(0xFF000000, PorterDuff.Mode.SRC_OUT); + paint.setMaskFilter(new BlurMaskFilter(blurSizeOutline, BlurMaskFilter.Blur.NORMAL)); + int[] thickInnerBlurOffset = new int[2]; + Bitmap thickInnerBlur = preview.extractAlpha(paint, thickInnerBlurOffset); + + // mask out the inner blur + paint.setMaskFilter(null); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); + canvas.setBitmap(thickInnerBlur); + canvas.drawBitmap(preview, -thickInnerBlurOffset[0], + -thickInnerBlurOffset[1], paint); + canvas.drawRect(0, 0, -thickInnerBlurOffset[0], thickInnerBlur.getHeight(), paint); + canvas.drawRect(0, 0, thickInnerBlur.getWidth(), -thickInnerBlurOffset[1], paint); + + // draw the inner and outer blur + paint.setXfermode(null); + canvas.setBitmap(preview); + canvas.drawColor(0, PorterDuff.Mode.CLEAR); + canvas.drawBitmap(thickInnerBlur, thickInnerBlurOffset[0], thickInnerBlurOffset[1], + paint); + canvas.drawBitmap(thickOuterBlur, outerBlurOffset[0], outerBlurOffset[1], paint); + + // draw the bright outline + canvas.drawBitmap(brightOutline, brightOutlineOffset[0], brightOutlineOffset[1], paint); + + // cleanup + canvas.setBitmap(null); + brightOutline.recycle(); + thickOuterBlur.recycle(); + thickInnerBlur.recycle(); + + generatedDragOutline = preview; + } + } } diff --git a/src/com/android/launcher3/graphics/GradientView.java b/src/com/android/launcher3/graphics/GradientView.java index 5455b43ec6..bacb06323d 100644 --- a/src/com/android/launcher3/graphics/GradientView.java +++ b/src/com/android/launcher3/graphics/GradientView.java @@ -29,12 +29,12 @@ import android.support.v4.graphics.ColorUtils; 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 @@ public class GradientView extends View implements WallpaperColorInfo.OnChangeLis 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 b221828837..9e67f56e3f 100644 --- a/src/com/android/launcher3/graphics/HolographicOutlineHelper.java +++ b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java @@ -17,7 +17,6 @@ package com.android.launcher3.graphics; import android.content.Context; -import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BlurMaskFilter; import android.graphics.Canvas; @@ -31,9 +30,6 @@ import android.util.SparseArray; import com.android.launcher3.BubbleTextView; import com.android.launcher3.R; -import com.android.launcher3.config.FeatureFlags; - -import java.nio.ByteBuffer; /** * Utility class to generate shadow and outline effect, which are used for click feedback @@ -44,14 +40,9 @@ public class HolographicOutlineHelper { private static HolographicOutlineHelper sInstance; private final Canvas mCanvas = new Canvas(); - private final Paint mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); private final Paint mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); private final Paint mErasePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); - private final BlurMaskFilter mMediumOuterBlurMaskFilter; - private final BlurMaskFilter mThinOuterBlurMaskFilter; - private final BlurMaskFilter mMediumInnerBlurMaskFilter; - private final float mShadowBitmapShift; private final BlurMaskFilter mShadowBlurMaskFilter; @@ -59,18 +50,8 @@ public class HolographicOutlineHelper { private final SparseArray mBitmapCache = new SparseArray<>(4); private HolographicOutlineHelper(Context context) { - Resources res = context.getResources(); - - float mediumBlur = res.getDimension(R.dimen.blur_size_medium_outline); - mMediumOuterBlurMaskFilter = new BlurMaskFilter(mediumBlur, BlurMaskFilter.Blur.OUTER); - mMediumInnerBlurMaskFilter = new BlurMaskFilter(mediumBlur, BlurMaskFilter.Blur.NORMAL); - - mThinOuterBlurMaskFilter = new BlurMaskFilter( - res.getDimension(R.dimen.blur_size_thin_outline), BlurMaskFilter.Blur.OUTER); - - mShadowBitmapShift = res.getDimension(R.dimen.blur_size_click_shadow); + mShadowBitmapShift = context.getResources().getDimension(R.dimen.blur_size_click_shadow); mShadowBlurMaskFilter = new BlurMaskFilter(mShadowBitmapShift, BlurMaskFilter.Blur.NORMAL); - mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); } @@ -81,75 +62,6 @@ public class HolographicOutlineHelper { return sInstance; } - /** - * Applies a more expensive and accurate outline to whatever is currently drawn in a specified - * bitmap. - */ - public void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas) { - if (FeatureFlags.IS_DOGFOOD_BUILD && srcDst.getConfig() != Bitmap.Config.ALPHA_8) { - throw new RuntimeException("Outline blue is only supported on alpha bitmaps"); - } - - // We start by removing most of the alpha channel so as to ignore shadows, and - // other types of partial transparency when defining the shape of the object - byte[] pixels = new byte[srcDst.getWidth() * srcDst.getHeight()]; - ByteBuffer buffer = ByteBuffer.wrap(pixels); - buffer.rewind(); - srcDst.copyPixelsToBuffer(buffer); - - for (int i = 0; i < pixels.length; i++) { - if ((pixels[i] & 0xFF) < 188) { - pixels[i] = 0; - } - } - - buffer.rewind(); - srcDst.copyPixelsFromBuffer(buffer); - - // calculate the outer blur first - mBlurPaint.setMaskFilter(mMediumOuterBlurMaskFilter); - int[] outerBlurOffset = new int[2]; - Bitmap thickOuterBlur = srcDst.extractAlpha(mBlurPaint, outerBlurOffset); - - mBlurPaint.setMaskFilter(mThinOuterBlurMaskFilter); - int[] brightOutlineOffset = new int[2]; - Bitmap brightOutline = srcDst.extractAlpha(mBlurPaint, brightOutlineOffset); - - // calculate the inner blur - srcDstCanvas.setBitmap(srcDst); - srcDstCanvas.drawColor(0xFF000000, PorterDuff.Mode.SRC_OUT); - mBlurPaint.setMaskFilter(mMediumInnerBlurMaskFilter); - int[] thickInnerBlurOffset = new int[2]; - Bitmap thickInnerBlur = srcDst.extractAlpha(mBlurPaint, thickInnerBlurOffset); - - // mask out the inner blur - srcDstCanvas.setBitmap(thickInnerBlur); - srcDstCanvas.drawBitmap(srcDst, -thickInnerBlurOffset[0], - -thickInnerBlurOffset[1], mErasePaint); - srcDstCanvas.drawRect(0, 0, -thickInnerBlurOffset[0], thickInnerBlur.getHeight(), - mErasePaint); - srcDstCanvas.drawRect(0, 0, thickInnerBlur.getWidth(), -thickInnerBlurOffset[1], - mErasePaint); - - // draw the inner and outer blur - srcDstCanvas.setBitmap(srcDst); - srcDstCanvas.drawColor(0, PorterDuff.Mode.CLEAR); - srcDstCanvas.drawBitmap(thickInnerBlur, thickInnerBlurOffset[0], thickInnerBlurOffset[1], - mDrawPaint); - srcDstCanvas.drawBitmap(thickOuterBlur, outerBlurOffset[0], outerBlurOffset[1], - mDrawPaint); - - // draw the bright outline - srcDstCanvas.drawBitmap(brightOutline, brightOutlineOffset[0], brightOutlineOffset[1], - mDrawPaint); - - // cleanup - srcDstCanvas.setBitmap(null); - brightOutline.recycle(); - thickOuterBlur.recycle(); - thickInnerBlur.recycle(); - } - public Bitmap createMediumDropShadow(BubbleTextView view) { Drawable drawable = view.getIcon(); if (drawable == null) { diff --git a/src/com/android/launcher3/graphics/IconNormalizer.java b/src/com/android/launcher3/graphics/IconNormalizer.java index 28fc423685..5ee6a30b76 100644 --- a/src/com/android/launcher3/graphics/IconNormalizer.java +++ b/src/com/android/launcher3/graphics/IconNormalizer.java @@ -28,12 +28,15 @@ import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; import com.android.launcher3.LauncherAppState; import com.android.launcher3.Utilities; +import com.android.launcher3.dragndrop.FolderAdaptiveIcon; + import java.io.File; import java.io.FileOutputStream; import java.nio.ByteBuffer; @@ -231,12 +234,17 @@ public class IconNormalizer { */ public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds, @Nullable Path path, @Nullable boolean[] outMaskShape) { - if (Utilities.ATLEAST_OREO && d instanceof AdaptiveIconDrawable && - mAdaptiveIconScale != SCALE_NOT_INITIALIZED) { - if (outBounds != null) { - outBounds.set(mAdaptiveIconBounds); + if (Utilities.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) { + if (mAdaptiveIconScale != SCALE_NOT_INITIALIZED) { + if (outBounds != null) { + outBounds.set(mAdaptiveIconBounds); + } + return mAdaptiveIconScale; + } + if (d instanceof FolderAdaptiveIcon) { + // Since we just want the scale, avoid heavy drawing operations + d = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null); } - return mAdaptiveIconScale; } int width = d.getIntrinsicWidth(); int height = d.getIntrinsicHeight(); diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java index d55baf0f8c..1eddb2cf04 100644 --- a/src/com/android/launcher3/graphics/LauncherIcons.java +++ b/src/com/android/launcher3/graphics/LauncherIcons.java @@ -16,10 +16,12 @@ package com.android.launcher3.graphics; +import android.annotation.TargetApi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.Intent.ShortcutIconResource; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Bitmap; @@ -73,8 +75,12 @@ public class LauncherIcons { Resources resources = packageManager.getResourcesForApplication(iconRes.packageName); if (resources != null) { final int id = resources.getIdentifier(iconRes.resourceName, null, null); - return createIconBitmap(resources.getDrawableForDensity( - id, LauncherAppState.getIDP(context).fillResIconDpi), context); + // do not stamp old legacy shortcuts as the app may have already forgotten about it + return createBadgedIconBitmap(resources.getDrawableForDensity( + id, LauncherAppState.getIDP(context).fillResIconDpi), + Process.myUserHandle() /* only available on primary user */, + context, + 0 /* do not apply legacy treatment */); } } catch (Exception e) { // Icon not found. @@ -90,11 +96,12 @@ public class LauncherIcons { if (iconBitmapSize == icon.getWidth() && iconBitmapSize == icon.getHeight()) { return icon; } - return createIconBitmap(new BitmapDrawable(context.getResources(), icon), context); + return createIconBitmap(new BitmapDrawable(context.getResources(), icon), context, 1f); } /** - * Returns a bitmap suitable for the all apps view. The icon is badged for {@param user}. + * Returns a bitmap suitable for displaying as an icon at various launcher UIs like all apps + * view or workspace. The icon is badged for {@param user}. * The bitmap is also visually normalized with other icons. */ public static Bitmap createBadgedIconBitmap( @@ -127,24 +134,18 @@ public class LauncherIcons { icon instanceof AdaptiveIconDrawable) { bitmap = ShadowGenerator.getInstance(context).recreateIcon(bitmap); } - return badgeIconForUser(bitmap, user, context); - } - /** - * Badges the provided icon with the user badge if required. - */ - public static Bitmap badgeIconForUser(Bitmap icon, UserHandle user, Context context) { if (user != null && !Process.myUserHandle().equals(user)) { - BitmapDrawable drawable = new FixedSizeBitmapDrawable(icon); + BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap); Drawable badged = context.getPackageManager().getUserBadgedIcon( drawable, user); if (badged instanceof BitmapDrawable) { return ((BitmapDrawable) badged).getBitmap(); } else { - return createIconBitmap(badged, context); + return createIconBitmap(badged, context, 1f); } } else { - return icon; + return bitmap; } } @@ -152,7 +153,8 @@ public class LauncherIcons { * Creates a normalized bitmap suitable for the all apps view. The bitmap is also visually * normalized with other icons and has enough spacing to add shadow. */ - public static Bitmap createScaledBitmapWithoutShadow(Drawable icon, Context context, int iconAppTargetSdk) { + public static Bitmap createScaledBitmapWithoutShadow( + Drawable icon, Context context, int iconAppTargetSdk) { RectF iconBounds = new RectF(); IconNormalizer normalizer; float scale = 1f; @@ -181,21 +183,6 @@ public class LauncherIcons { return createIconBitmap(icon, context, scale); } - /** - * Adds a shadow to the provided icon. It assumes that the icon has already been scaled using - * {@link #createScaledBitmapWithoutShadow(Drawable, Context, int)} - */ - public static Bitmap addShadowToIcon(Bitmap icon, Context context) { - return ShadowGenerator.getInstance(context).recreateIcon(icon); - } - - /** - * Adds the {@param badge} on top of {@param srcTgt} using the badge dimensions. - */ - public static Bitmap badgeWithBitmap(Bitmap srcTgt, Bitmap badge, Context context) { - return badgeWithDrawable(srcTgt, new FastBitmapDrawable(badge), context); - } - public static Bitmap badgeWithDrawable(Bitmap srcTgt, Drawable badge, Context context) { int badgeSize = context.getResources().getDimensionPixelSize(R.dimen.profile_badge_size); synchronized (sCanvas) { @@ -208,27 +195,10 @@ public class LauncherIcons { return srcTgt; } - /** - * Returns a bitmap suitable for the all apps view. - */ - public static Bitmap createIconBitmap(Drawable icon, Context context) { - float scale = 1f; - if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.ATLEAST_OREO && - icon instanceof AdaptiveIconDrawable) { - scale = ShadowGenerator.getScaleForBounds(new RectF(0, 0, 0, 0)); - } - Bitmap bitmap = createIconBitmap(icon, context, scale); - if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.ATLEAST_OREO && - icon instanceof AdaptiveIconDrawable) { - bitmap = ShadowGenerator.getInstance(context).recreateIcon(bitmap); - } - return bitmap; - } - /** * @param scale the scale to apply before drawing {@param icon} on the canvas */ - public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) { + private static Bitmap createIconBitmap(Drawable icon, Context context, float scale) { synchronized (sCanvas) { final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize; int width = iconBitmapSize; @@ -295,7 +265,9 @@ public class LauncherIcons { * shrink the legacy icon and set it as foreground. Use color drawable as background to * create AdaptiveIconDrawable. */ - static Drawable wrapToAdaptiveIconDrawable(Context context, Drawable drawable, float scale) { + @TargetApi(Build.VERSION_CODES.O) + private static Drawable wrapToAdaptiveIconDrawable( + Context context, Drawable drawable, float scale) { if (!(FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.ATLEAST_OREO)) { return drawable; } @@ -307,7 +279,7 @@ public class LauncherIcons { FixedScaleDrawable fsd = ((FixedScaleDrawable) iconWrapper.getForeground()); fsd.setDrawable(drawable); fsd.setScale(scale); - return (Drawable) iconWrapper; + return iconWrapper; } } catch (Exception e) { return drawable; @@ -326,15 +298,9 @@ public class LauncherIcons { public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context, final Bitmap fallbackIcon) { - Provider fallbackIconProvider = new Provider() { - @Override - public Bitmap get() { - // If the shortcut is pinned but no longer has an icon in the system, - // keep the current icon instead of reverting to the default icon. - return fallbackIcon; - } - }; - return createShortcutIcon(shortcutInfo, context, true, fallbackIconProvider); + // If the shortcut is pinned but no longer has an icon in the system, + // keep the current icon instead of reverting to the default icon. + return createShortcutIcon(shortcutInfo, context, true, Provider.of(fallbackIcon)); } public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context, @@ -360,8 +326,9 @@ public class LauncherIcons { if (!badged) { return unbadgedBitmap; } - unbadgedBitmap = LauncherIcons.addShadowToIcon(unbadgedBitmap, context); - return badgeWithBitmap(unbadgedBitmap, getShortcutInfoBadge(shortcutInfo, cache), context); + unbadgedBitmap = ShadowGenerator.getInstance(context).recreateIcon(unbadgedBitmap); + return badgeWithDrawable(unbadgedBitmap, + new FastBitmapDrawable(getShortcutInfoBadge(shortcutInfo, cache)), context); } public static Bitmap getShortcutInfoBadge(ShortcutInfoCompat shortcutInfo, IconCache cache) { diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java index 06dc7acfe0..6d486eed72 100644 --- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java +++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java @@ -30,9 +30,9 @@ import android.graphics.PathMeasure; 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 @@ public class PreloadIconDrawable extends FastBitmapDrawable { 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 @@ public class PreloadIconDrawable extends FastBitmapDrawable { } mCurrentAnim.start(); } - } /** diff --git a/src/com/android/launcher3/keyboard/CustomActionsPopup.java b/src/com/android/launcher3/keyboard/CustomActionsPopup.java index 938955ca2c..150522ef6f 100644 --- a/src/com/android/launcher3/keyboard/CustomActionsPopup.java +++ b/src/com/android/launcher3/keyboard/CustomActionsPopup.java @@ -27,7 +27,7 @@ import android.widget.PopupMenu.OnMenuItemClickListener; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; -import com.android.launcher3.popup.PopupContainerWithArrow; +import com.android.launcher3.popup.BaseActionPopup; import java.util.ArrayList; import java.util.Collections; @@ -46,7 +46,7 @@ public class CustomActionsPopup implements OnMenuItemClickListener { public CustomActionsPopup(Launcher launcher, View icon) { mLauncher = launcher; mIcon = icon; - PopupContainerWithArrow container = PopupContainerWithArrow.getOpen(launcher); + BaseActionPopup container = BaseActionPopup.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 81333b1a22..00ee0096ad 100644 --- a/src/com/android/launcher3/logging/LoggerUtils.java +++ b/src/com/android/launcher3/logging/LoggerUtils.java @@ -91,7 +91,8 @@ public class LoggerUtils { 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 @@ public class LoggerUtils { 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 d5c6515c0b..243dbeae67 100644 --- a/src/com/android/launcher3/logging/UserEventDispatcher.java +++ b/src/com/android/launcher3/logging/UserEventDispatcher.java @@ -177,13 +177,11 @@ public class UserEventDispatcher { } public void logActionCommand(int command, int containerType) { - logActionCommand(command, containerType, 0); + logActionCommand(command, newContainerTarget(containerType)); } - public void logActionCommand(int command, int containerType, int pageIndex) { - LauncherEvent event = newLauncherEvent( - newCommandAction(command), newContainerTarget(containerType)); - event.srcTarget[0].pageIndex = pageIndex; + public void logActionCommand(int command, Target target) { + LauncherEvent event = newLauncherEvent(newCommandAction(command), target); dispatchUserEvent(event, null); } @@ -234,6 +232,10 @@ public class UserEventDispatcher { 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) { @@ -325,6 +327,7 @@ public class UserEventDispatcher { 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/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java index 42926fa3ea..a33a039c0e 100644 --- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java +++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java @@ -38,7 +38,6 @@ import com.android.launcher3.ShortcutInfo; import com.android.launcher3.Utilities; import com.android.launcher3.util.GridOccupancy; import com.android.launcher3.util.ManagedProfileHeuristic.UserFolderInfo; -import com.android.launcher3.util.Provider; import java.util.ArrayList; import java.util.List; @@ -47,19 +46,18 @@ import java.util.List; */ public class AddWorkspaceItemsTask extends BaseModelUpdateTask { - private final Provider>> mAppsProvider; + private final List> mItemList; /** - * @param appsProvider items to add on the workspace + * @param itemList items to add on the workspace */ - public AddWorkspaceItemsTask(Provider>> appsProvider) { - mAppsProvider = appsProvider; + public AddWorkspaceItemsTask(List> itemList) { + mItemList = itemList; } @Override public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { - List> workspaceApps = mAppsProvider.get(); - if (workspaceApps.isEmpty()) { + if (mItemList.isEmpty()) { return; } Context context = app.getContext(); @@ -75,7 +73,7 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask { synchronized(dataModel) { List filteredItems = new ArrayList<>(); - for (Pair entry : workspaceApps) { + for (Pair entry : mItemList) { ItemInfo item = entry.first; if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java index d5b5aa7cf3..9aa30e7cca 100644 --- a/src/com/android/launcher3/model/BaseModelUpdateTask.java +++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java @@ -28,6 +28,8 @@ import com.android.launcher3.ShortcutInfo; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.MultiHashMap; +import com.android.launcher3.widget.WidgetListRowEntry; +import com.android.launcher3.widget.WidgetsListAdapter; import java.util.ArrayList; import java.util.concurrent.Executor; @@ -117,8 +119,8 @@ public abstract class BaseModelUpdateTask implements ModelUpdateTask { } public void bindUpdatedWidgets(BgDataModel dataModel) { - final MultiHashMap widgets - = dataModel.widgetsModel.getWidgetsMap(); + final ArrayList widgets = + dataModel.widgetsModel.getWidgetsList(mApp.getContext()); scheduleCallbackTask(new CallbackTask() { @Override public void execute(Callbacks callbacks) { diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java index 8de0de053f..d9b1a3f9e7 100644 --- a/src/com/android/launcher3/model/GridSizeMigrationTask.java +++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java @@ -726,7 +726,7 @@ public class GridSizeMigrationTask { mContext).getLauncherAppWidgetInfo(widgetId); Point spans = null; if (pInfo != null) { - spans = pInfo.getMinSpans(mIdp, mContext); + spans = pInfo.getMinSpans(); } if (spans != null) { entry.minSpanX = spans.x > 0 ? spans.x : entry.spanX; diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java index bc7da9b120..47f370a175 100644 --- a/src/com/android/launcher3/model/LoaderCursor.java +++ b/src/com/android/launcher3/model/LoaderCursor.java @@ -16,11 +16,15 @@ package com.android.launcher3.model; +import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_NO; +import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_YES; + import android.content.ComponentName; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.Intent.ShortcutIconResource; +import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; import android.database.Cursor; import android.database.CursorWrapper; @@ -274,8 +278,13 @@ public class LoaderCursor extends CursorWrapper { info.iconBitmap = icon != null ? icon : info.iconBitmap; } - if (lai != null && PackageManagerHelper.isAppSuspended(lai.getApplicationInfo())) { - info.isDisabled = ShortcutInfo.FLAG_DISABLED_SUSPENDED; + if (lai != null) { + ApplicationInfo appInfo = lai.getApplicationInfo(); + if (PackageManagerHelper.isAppSuspended(appInfo)) { + info.runtimeStatusFlags |= ShortcutInfo.FLAG_DISABLED_SUSPENDED; + } + info.runtimeStatusFlags |= (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0 + ? FLAG_SYSTEM_NO : FLAG_SYSTEM_YES; } // from the db diff --git a/src/com/android/launcher3/model/LoaderResults.java b/src/com/android/launcher3/model/LoaderResults.java index b7a6b68e83..24e5b9c58c 100644 --- a/src/com/android/launcher3/model/LoaderResults.java +++ b/src/com/android/launcher3/model/LoaderResults.java @@ -36,6 +36,8 @@ import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.LooperIdleLock; import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.ViewOnDrawExecutor; +import com.android.launcher3.widget.WidgetListRowEntry; +import com.android.launcher3.widget.WidgetsListAdapter; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -362,8 +364,8 @@ public class LoaderResults { } public void bindWidgets() { - final MultiHashMap widgets - = mBgDataModel.widgetsModel.getWidgetsMap(); + final ArrayList widgets = + mBgDataModel.widgetsModel.getWidgetsList(mApp.getContext()); Runnable r = new Runnable() { public void run() { Callbacks callbacks = mCallbacks.get(); diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 4756edcc03..c2cfebb85f 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -27,8 +27,6 @@ import android.content.pm.PackageInstaller; import android.graphics.Bitmap; 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; @@ -53,7 +51,6 @@ import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.Folder; -import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.folder.FolderIconPreviewVerifier; import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.logging.FileLog; @@ -67,6 +64,7 @@ import com.android.launcher3.util.ManagedProfileHeuristic; import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.Provider; +import com.android.launcher3.util.TraceHelper; import java.util.ArrayList; import java.util.Collections; @@ -76,6 +74,11 @@ import java.util.List; 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; + /** * Runnable for the thread that loads the contents of the launcher: * - workspace icons @@ -84,7 +87,6 @@ import java.util.concurrent.CancellationException; * - deep shortcuts within apps */ public class LoaderTask implements Runnable { - private static final boolean DEBUG_LOADERS = false; private static final String TAG = "LoaderTask"; private final LauncherAppState mApp; @@ -141,73 +143,64 @@ public class LoaderTask implements Runnable { } } + TraceHelper.beginSection(TAG); try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) { - long now = 0; - if (DEBUG_LOADERS) Log.d(TAG, "step 1.1: loading workspace"); + TraceHelper.partitionSection(TAG, "step 1.1: loading workspace"); loadWorkspace(); verifyNotStopped(); - if (DEBUG_LOADERS) Log.d(TAG, "step 1.2: bind workspace workspace"); + TraceHelper.partitionSection(TAG, "step 1.2: bind workspace workspace"); mResults.bindWorkspace(); // Take a break - if (DEBUG_LOADERS) { - Log.d(TAG, "step 1 completed, wait for idle"); - now = SystemClock.uptimeMillis(); - } + TraceHelper.partitionSection(TAG, "step 1 completed, wait for idle"); waitForIdle(); - if (DEBUG_LOADERS) Log.d(TAG, "Waited " + (SystemClock.uptimeMillis() - now) + "ms"); verifyNotStopped(); // second step - if (DEBUG_LOADERS) Log.d(TAG, "step 2.1: loading all apps"); + TraceHelper.partitionSection(TAG, "step 2.1: loading all apps"); loadAllApps(); - if (DEBUG_LOADERS) Log.d(TAG, "step 2.2: Binding all apps"); + TraceHelper.partitionSection(TAG, "step 2.2: Binding all apps"); verifyNotStopped(); mResults.bindAllApps(); verifyNotStopped(); - if (DEBUG_LOADERS) Log.d(TAG, "step 2.3: Update icon cache"); + TraceHelper.partitionSection(TAG, "step 2.3: Update icon cache"); updateIconCache(); // Take a break - if (DEBUG_LOADERS) { - Log.d(TAG, "step 2 completed, wait for idle"); - now = SystemClock.uptimeMillis(); - } + TraceHelper.partitionSection(TAG, "step 2 completed, wait for idle"); waitForIdle(); - if (DEBUG_LOADERS) Log.d(TAG, "Waited " + (SystemClock.uptimeMillis() - now) + "ms"); verifyNotStopped(); // third step - if (DEBUG_LOADERS) Log.d(TAG, "step 3.1: loading deep shortcuts"); + TraceHelper.partitionSection(TAG, "step 3.1: loading deep shortcuts"); loadDeepShortcuts(); verifyNotStopped(); - if (DEBUG_LOADERS) Log.d(TAG, "step 3.2: bind deep shortcuts"); + TraceHelper.partitionSection(TAG, "step 3.2: bind deep shortcuts"); mResults.bindDeepShortcuts(); // Take a break - if (DEBUG_LOADERS) Log.d(TAG, "step 3 completed, wait for idle"); + TraceHelper.partitionSection(TAG, "step 3 completed, wait for idle"); waitForIdle(); verifyNotStopped(); // fourth step - if (DEBUG_LOADERS) Log.d(TAG, "step 4.1: loading widgets"); + TraceHelper.partitionSection(TAG, "step 4.1: loading widgets"); mBgDataModel.widgetsModel.update(mApp, null); verifyNotStopped(); - if (DEBUG_LOADERS) Log.d(TAG, "step 4.2: Binding widgets"); + TraceHelper.partitionSection(TAG, "step 4.2: Binding widgets"); mResults.bindWidgets(); transaction.commit(); } catch (CancellationException e) { // Loader stopped, ignore - if (DEBUG_LOADERS) { - Log.d(TAG, "Loader cancelled", e); - } + TraceHelper.partitionSection(TAG, "Cancelled"); } + TraceHelper.endSection(TAG); } public synchronized void stopLocked() { @@ -216,10 +209,6 @@ public class LoaderTask implements Runnable { } private void loadWorkspace() { - if (LauncherAppState.PROFILE_STARTUP) { - Trace.beginSection("Loading Workspace"); - } - final Context context = mApp.getContext(); final ContentResolver contentResolver = context.getContentResolver(); final PackageManagerHelper pmHelper = new PackageManagerHelper(context); @@ -478,13 +467,13 @@ public class LoaderTask implements Runnable { 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(); @@ -492,7 +481,7 @@ public class LoaderTask implements Runnable { // 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 @@ -515,9 +504,9 @@ public class LoaderTask implements Runnable { 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)) { @@ -734,7 +723,7 @@ public class LoaderTask implements Runnable { numItemsInPreview++; } - if (numItemsInPreview >= FolderIcon.NUM_ITEMS_IN_PREVIEW) { + if (numItemsInPreview >= MAX_NUM_ITEMS_IN_PREVIEW) { break; } } @@ -765,9 +754,6 @@ public class LoaderTask implements Runnable { LauncherModel.updateWorkspaceScreenOrder(context, mBgDataModel.workspaceScreens); } } - if (LauncherAppState.PROFILE_STARTUP) { - Trace.endSection(); - } } private void updateIconCache() { @@ -792,21 +778,13 @@ public class LoaderTask implements Runnable { } private void loadAllApps() { - final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; - final List profiles = mUserManager.getUserProfiles(); // Clear the list of apps mBgAllAppsList.clear(); for (UserHandle user : profiles) { // Query for the set of apps - final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; final List apps = mLauncherApps.getActivityList(null, user); - if (DEBUG_LOADERS) { - Log.d(TAG, "getActivityList took " - + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user); - Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user); - } // Fail if we don't have any apps // TODO: Fix this. Only fail for the current user. if (apps == null || apps.isEmpty()) { @@ -833,10 +811,6 @@ public class LoaderTask implements Runnable { } mBgAllAppsList.added = new ArrayList<>(); - if (DEBUG_LOADERS) { - Log.d(TAG, "All apps loaded in in " - + (SystemClock.uptimeMillis() - loadTime) + "ms"); - } } private void loadDeepShortcuts() { diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java index 78ecbc6218..470dadf724 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -244,9 +244,9 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { 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; } } @@ -336,17 +336,7 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { }); } - // Notify launcher of widget update. From marshmallow onwards we use AppWidgetHost to - // get widget update signals. - if (!Utilities.ATLEAST_MARSHMALLOW && - (mOp == OP_ADD || mOp == OP_REMOVE || mOp == OP_UPDATE)) { - scheduleCallbackTask(new CallbackTask() { - @Override - public void execute(Callbacks callbacks) { - callbacks.notifyWidgetProvidersChanged(); - } - }); - } else if (Utilities.ATLEAST_OREO && mOp == OP_ADD) { + if (Utilities.ATLEAST_OREO && mOp == OP_ADD) { // Load widgets for the new package. Changes due to app updates are handled through // AppWidgetHost events, this is just to initialize the long-press options. for (int i = 0; i < N; i++) { diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java index 8170f9a67f..2e9ac72243 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 @@ public class UserLockStateChangedTask extends BaseModelUpdateTask { 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/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java index ed900bf358..1ff0daca05 100644 --- a/src/com/android/launcher3/model/WidgetsModel.java +++ b/src/com/android/launcher3/model/WidgetsModel.java @@ -15,6 +15,7 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.Utilities; +import com.android.launcher3.compat.AlphabeticIndexCompat; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.ShortcutConfigActivityInfo; @@ -22,10 +23,14 @@ import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Preconditions; +import com.android.launcher3.widget.WidgetItemComparator; +import com.android.launcher3.widget.WidgetListRowEntry; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; +import java.util.Map; /** * Widgets data model that is used by the adapters of the widget views and controllers. @@ -42,8 +47,26 @@ public class WidgetsModel { private AppFilter mAppFilter; - public synchronized MultiHashMap getWidgetsMap() { - return mWidgetsList.clone(); + /** + * Returns a list of {@link WidgetListRowEntry}. All {@link WidgetItem} in a single row + * are sorted (based on label and user), but the overall list of {@link WidgetListRowEntry}s + * is not sorted. This list is sorted at the UI when using + * {@link com.android.launcher3.widget.WidgetsDiffReporter} + * + * @see com.android.launcher3.widget.WidgetsListAdapter#setWidgets(ArrayList) + */ + public synchronized ArrayList getWidgetsList(Context context) { + ArrayList result = new ArrayList<>(); + AlphabeticIndexCompat indexer = new AlphabeticIndexCompat(context); + + WidgetItemComparator widgetComparator = new WidgetItemComparator(); + for (Map.Entry> entry : mWidgetsList.entrySet()) { + WidgetListRowEntry row = new WidgetListRowEntry(entry.getKey(), entry.getValue()); + row.titleSectionName = indexer.computeSectionName(row.pkgItem.title); + Collections.sort(row.widgets, widgetComparator); + result.add(row); + } + return result; } /** diff --git a/src/com/android/launcher3/notification/Interpolators.java b/src/com/android/launcher3/notification/Interpolators.java deleted file mode 100644 index 5c3b22ab11..0000000000 --- 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 ad07d37bd5..3cf3ff62cf 100644 --- a/src/com/android/launcher3/notification/NotificationFooterLayout.java +++ b/src/com/android/launcher3/notification/NotificationFooterLayout.java @@ -37,6 +37,7 @@ 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 java.util.ArrayList; @@ -193,16 +194,17 @@ public class NotificationFooterLayout extends FrameLayout { private void removeViewFromIconRow(View child) { mIconRow.removeView(child); - mNotifications.remove((NotificationInfo) child.getTag()); + mNotifications.remove(child.getTag()); updateOverflowEllipsisVisibility(); if (mIconRow.getChildCount() == 0) { // There are no more icons in the footer, so hide it. - PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen( + BaseActionPopup popup = BaseActionPopup.getOpen( Launcher.getLauncher(getContext())); - if (popup != null) { + if (popup instanceof PopupContainerWithArrow) { final int newHeight = getResources().getDimensionPixelSize( R.dimen.notification_empty_footer_height); - Animator collapseFooter = popup.reduceNotificationViewHeight(getHeight() - newHeight, + Animator collapseFooter = ((PopupContainerWithArrow) popup) + .reduceNotificationViewHeight(getHeight() - newHeight, getResources().getInteger(R.integer.config_removeNotificationViewDuration)); collapseFooter.addListener(new AnimatorListenerAdapter() { @Override diff --git a/src/com/android/launcher3/notification/NotificationGroup.java b/src/com/android/launcher3/notification/NotificationGroup.java new file mode 100644 index 0000000000..bce211754d --- /dev/null +++ b/src/com/android/launcher3/notification/NotificationGroup.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.launcher3.notification; + +import java.util.HashSet; +import java.util.Set; + +/** + * Contains data related to a group of notifications, like the group summary key and the child keys. + */ +public class NotificationGroup { + private String mGroupSummaryKey; + private Set mChildKeys; + + public NotificationGroup() { + mChildKeys = new HashSet<>(); + } + + public void setGroupSummaryKey(String groupSummaryKey) { + mGroupSummaryKey = groupSummaryKey; + } + + public String getGroupSummaryKey() { + return mGroupSummaryKey; + } + + public void addChildKey(String childKey) { + mChildKeys.add(childKey); + } + + public void removeChildKey(String childKey) { + mChildKeys.remove(childKey); + } + + public boolean isEmpty() { + return mChildKeys.isEmpty(); + } +} diff --git a/src/com/android/launcher3/notification/NotificationInfo.java b/src/com/android/launcher3/notification/NotificationInfo.java index 6e36f4f51b..120de0464d 100644 --- a/src/com/android/launcher3/notification/NotificationInfo.java +++ b/src/com/android/launcher3/notification/NotificationInfo.java @@ -31,7 +31,6 @@ import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.graphics.IconPalette; -import com.android.launcher3.popup.PopupContainerWithArrow; import com.android.launcher3.util.PackageUserKey; /** @@ -73,7 +72,7 @@ public class NotificationInfo implements View.OnClickListener { if (icon == null) { // Use the small icon. icon = notification.getSmallIcon(); - mIconDrawable = icon.loadDrawable(context); + mIconDrawable = icon == null ? null : icon.loadDrawable(context); mIconColor = statusBarNotification.getNotification().color; mIsIconLarge = false; } else { @@ -110,7 +109,7 @@ public class NotificationInfo implements View.OnClickListener { launcher.getPopupDataProvider().cancelNotification(notificationKey); } AbstractFloatingView.closeOpenContainer(launcher, AbstractFloatingView - .TYPE_POPUP_CONTAINER_WITH_ARROW); + .TYPE_ACTION_POPUP); } public Drawable getIconForBackground(Context context, int background) { diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java index 91266263f1..7b70df77e4 100644 --- a/src/com/android/launcher3/notification/NotificationListener.java +++ b/src/com/android/launcher3/notification/NotificationListener.java @@ -38,7 +38,9 @@ import com.android.launcher3.util.SettingsObserver; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import static com.android.launcher3.SettingsActivity.NOTIFICATION_BADGING; @@ -66,6 +68,8 @@ public class NotificationListener extends NotificationListenerService { private final Handler mWorkerHandler; private final Handler mUiHandler; private final Ranking mTempRanking = new Ranking(); + /** Maps groupKey's to the corresponding group of notifications. */ + private final Map mNotificationGroupMap = new HashMap<>(); private SettingsObserver mNotificationBadgingObserver; @@ -227,6 +231,15 @@ public class NotificationListener extends NotificationListenerService { NotificationKeyData.fromNotification(sbn)); mWorkerHandler.obtainMessage(MSG_NOTIFICATION_REMOVED, packageUserKeyAndNotificationKey) .sendToTarget(); + + NotificationGroup notificationGroup = mNotificationGroupMap.get(sbn.getGroupKey()); + if (notificationGroup != null) { + notificationGroup.removeChildKey(sbn.getKey()); + if (notificationGroup.isEmpty()) { + cancelNotification(notificationGroup.getGroupSummaryKey()); + mNotificationGroupMap.remove(sbn.getGroupKey()); + } + } } /** This makes a potentially expensive binder call and should be run on a background thread. */ @@ -264,18 +277,34 @@ public class NotificationListener extends NotificationListenerService { } private boolean shouldBeFilteredOut(StatusBarNotification sbn) { + Notification notification = sbn.getNotification(); + + boolean isGroupHeader = (notification.flags & Notification.FLAG_GROUP_SUMMARY) != 0; + if (sbn.isGroup()) { + // Maintain group info so we can cancel the summary when the last child is canceled. + NotificationGroup notificationGroup = mNotificationGroupMap.get(sbn.getGroupKey()); + if (notificationGroup == null) { + notificationGroup = new NotificationGroup(); + mNotificationGroupMap.put(sbn.getGroupKey(), notificationGroup); + } + if (isGroupHeader) { + notificationGroup.setGroupSummaryKey(sbn.getKey()); + } else { + notificationGroup.addChildKey(sbn.getKey()); + } + } + getCurrentRanking().getRanking(sbn.getKey(), mTempRanking); if (!mTempRanking.canShowBadge()) { return true; } - Notification notification = sbn.getNotification(); if (mTempRanking.getChannel().getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) { // Special filtering for the default, legacy "Miscellaneous" channel. if ((notification.flags & Notification.FLAG_ONGOING_EVENT) != 0) { return true; } } - boolean isGroupHeader = (notification.flags & Notification.FLAG_GROUP_SUMMARY) != 0; + CharSequence title = notification.extras.getCharSequence(Notification.EXTRA_TITLE); CharSequence text = notification.extras.getCharSequence(Notification.EXTRA_TEXT); boolean missingTitleAndText = TextUtils.isEmpty(title) && TextUtils.isEmpty(text); diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java index 5aff28db4c..c8e1fdb3f5 100644 --- a/src/com/android/launcher3/notification/NotificationMainView.java +++ b/src/com/android/launcher3/notification/NotificationMainView.java @@ -16,6 +16,8 @@ package com.android.launcher3.notification; +import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity; + import android.animation.ObjectAnimator; import android.content.Context; import android.content.res.ColorStateList; @@ -25,7 +27,6 @@ import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; -import android.view.ViewPropertyAnimator; import android.widget.FrameLayout; import android.widget.TextView; @@ -167,14 +168,11 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L endTranslation = 0; } - SwipeDetector.ScrollInterpolator interpolator = new SwipeDetector.ScrollInterpolator(); - interpolator.setVelocityAtZero(velocity); - long duration = SwipeDetector.calculateDuration(velocity, (endTranslation - getTranslationX()) / getWidth()); animate() .setDuration(duration) - .setInterpolator(interpolator) + .setInterpolator(scrollInterpolatorForVelocity(velocity)) .translationX(endTranslation) .withEndAction(new Runnable() { @Override diff --git a/src/com/android/launcher3/pageindicators/CaretDrawable.java b/src/com/android/launcher3/pageindicators/CaretDrawable.java deleted file mode 100644 index 5ade497280..0000000000 --- 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 47c2ffb381..be6bcc5825 100644 --- a/src/com/android/launcher3/pageindicators/PageIndicator.java +++ b/src/com/android/launcher3/pageindicators/PageIndicator.java @@ -16,17 +16,13 @@ package com.android.launcher3.pageindicators; import android.content.Context; -import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.widget.FrameLayout; -import com.android.launcher3.dynamicui.ExtractedColors; - /** * Base class for a page indicator. */ public abstract class PageIndicator extends FrameLayout { - private CaretDrawable mCaretDrawable; protected int mNumPages = 1; @@ -54,30 +50,7 @@ public abstract class PageIndicator extends FrameLayout { 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) {} - - public void updateColor(ExtractedColors extractedColors) {} - - @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/PageIndicatorLandscape.java similarity index 56% rename from src/com/android/launcher3/pageindicators/PageIndicatorCaretLandscape.java rename to src/com/android/launcher3/pageindicators/PageIndicatorLandscape.java index 911be93fcd..7325235db4 100644 --- a/src/com/android/launcher3/pageindicators/PageIndicatorCaretLandscape.java +++ b/src/com/android/launcher3/pageindicators/PageIndicatorLandscape.java @@ -16,49 +16,30 @@ 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 { +public class PageIndicatorLandscape extends PageIndicator { // all apps pull up handle drawable. - public PageIndicatorCaretLandscape(Context context) { + public PageIndicatorLandscape(Context context) { this(context, null); } - public PageIndicatorCaretLandscape(Context context, AttributeSet attrs) { + public PageIndicatorLandscape(Context context, AttributeSet attrs) { this(context, attrs, 0); } - public PageIndicatorCaretLandscape(Context context, AttributeSet attrs, int defStyle) { + public PageIndicatorLandscape(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/PageIndicatorLineCaret.java b/src/com/android/launcher3/pageindicators/PageIndicatorLine.java similarity index 73% rename from src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java rename to src/com/android/launcher3/pageindicators/PageIndicatorLine.java index 6281fec030..09a06b0da6 100644 --- a/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java +++ b/src/com/android/launcher3/pageindicators/PageIndicatorLine.java @@ -11,9 +11,7 @@ import android.graphics.Color; import android.graphics.Paint; import android.os.Handler; import android.os.Looper; -import android.support.v4.graphics.ColorUtils; import android.util.AttributeSet; -import android.util.Log; import android.util.Property; import android.view.ViewConfiguration; import android.widget.ImageView; @@ -21,8 +19,6 @@ import android.widget.ImageView; import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.dynamicui.ExtractedColors; import com.android.launcher3.dynamicui.WallpaperColorInfo; /** @@ -30,10 +26,7 @@ import com.android.launcher3.dynamicui.WallpaperColorInfo; * * The fraction is 1 / number of pages and the position is based on the progress of the page scroll. */ -public class PageIndicatorLineCaret extends PageIndicator { - private static final String TAG = "PageIndicatorLine"; - - private static final int[] sTempCoords = new int[2]; +public class PageIndicatorLine extends PageIndicator { private static final int LINE_ANIMATE_DURATION = ViewConfiguration.getScrollBarFadeDuration(); private static final int LINE_FADE_DELAY = ViewConfiguration.getScrollDefaultDelay(); @@ -63,43 +56,43 @@ public class PageIndicatorLineCaret extends PageIndicator { private final int mLineHeight; private ImageView mAllAppsHandle; - private static final Property PAINT_ALPHA - = new Property(Integer.class, "paint_alpha") { + private static final Property PAINT_ALPHA + = new Property(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 NUM_PAGES - = new Property(Float.class, "num_pages") { + private static final Property NUM_PAGES + = new Property(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 TOTAL_SCROLL - = new Property(Integer.class, "total_scroll") { + private static final Property TOTAL_SCROLL + = new Property(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(); } @@ -112,15 +105,15 @@ public class PageIndicatorLineCaret extends PageIndicator { } }; - 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(); @@ -129,7 +122,6 @@ public class PageIndicatorLineCaret extends PageIndicator { 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; @@ -139,8 +131,7 @@ public class PageIndicatorLineCaret extends PageIndicator { @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); @@ -218,32 +209,6 @@ public class PageIndicatorLineCaret extends PageIndicator { } } - /** - * The line's color will be: - * - mostly opaque white if the hotseat is white (ignoring alpha) - * - mostly opaque black if the hotseat is black (ignoring alpha) - */ - public void updateColor(ExtractedColors extractedColors) { - if (FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) { - return; - } - int originalLineAlpha = mLinePaint.getAlpha(); - int color = extractedColors.getColor(ExtractedColors.HOTSEAT_INDEX); - if (color != Color.TRANSPARENT) { - color = ColorUtils.setAlphaComponent(color, 255); - if (color == Color.BLACK) { - mActiveAlpha = BLACK_ALPHA; - } else if (color == Color.WHITE) { - mActiveAlpha = WHITE_ALPHA; - } else { - Log.e(TAG, "Setting workspace page indicators to an unsupported color: #" - + Integer.toHexString(color)); - } - mLinePaint.setColor(color); - mLinePaint.setAlpha(originalLineAlpha); - } - } - private void animateLineToAlpha(int alpha) { if (alpha == mToAlpha) { // Ignore the new animation if it is going to the same alpha as the current animation. diff --git a/src/com/android/launcher3/popup/BaseActionPopup.java b/src/com/android/launcher3/popup/BaseActionPopup.java new file mode 100644 index 0000000000..7ffe2ef8bc --- /dev/null +++ b/src/com/android/launcher3/popup/BaseActionPopup.java @@ -0,0 +1,599 @@ +/* + * 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 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 8441598cf7..4435afb8f6 100644 --- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java +++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java @@ -16,123 +16,71 @@ 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.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.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 com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BubbleTextView; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; +import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAnimUtils; import com.android.launcher3.LauncherModel; 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.PropertyResetListener; -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.graphics.IconPalette; -import com.android.launcher3.graphics.TriangleShape; 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.util.PackageUserKey; import com.android.launcher3.util.Themes; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; -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 within apps. + * A container for shortcuts to deep links and notifications associated with an app. */ @TargetApi(Build.VERSION_CODES.N) -public class PopupContainerWithArrow extends AbstractFloatingView implements DragSource, +public class PopupContainerWithArrow extends BaseActionPopup implements DragSource, DragController.DragListener { - 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; private final int mStartDragThreshold; - private LauncherAccessibilityDelegate mAccessibilityDelegate; - private final boolean mIsRtl; - public ShortcutsItemView mShortcutsItemView; private NotificationItemView mNotificationItemView; - - protected BubbleTextView mOriginalIcon; - private final Rect mTempRect = new Rect(); - private PointF mInterceptTouchDown = new PointF(); - private boolean mIsLeftAligned; - protected boolean mIsAboveIcon; - private View mArrow; - private int mGravity; - - protected Animator mOpenCloseAnimator; - private boolean mDeferContainerRemoval; private AnimatorSet mReduceHeightAnimatorSet; - private final Rect mStartRect = new Rect(); - private final Rect mEndRect = new Rect(); + private int mNumNotifications; public PopupContainerWithArrow(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - mLauncher = Launcher.getLauncher(context); - mStartDragThreshold = getResources().getDimensionPixelSize( R.dimen.deep_shortcuts_start_drag_threshold); - mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher); - mIsRtl = Utilities.isRtl(getResources()); } public PopupContainerWithArrow(Context context, AttributeSet attrs) { @@ -143,10 +91,6 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra this(context, null, 0); } - public LauncherAccessibilityDelegate getAccessibilityDelegate() { - return mAccessibilityDelegate; - } - /** * Shows the notifications and deep shortcuts associated with {@param icon}. * @return the container if shown or null. @@ -173,49 +117,24 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra final PopupContainerWithArrow container = (PopupContainerWithArrow) launcher.getLayoutInflater().inflate( R.layout.popup_container, launcher.getDragLayer(), false); - container.setVisibility(View.INVISIBLE); - launcher.getDragLayer().addView(container); container.populateAndShow(icon, shortcutIds, notificationKeys, systemShortcuts); return container; } - public void populateAndShow(final BubbleTextView originalIcon, final List shortcutIds, + private void populateAndShow(final BubbleTextView originalIcon, final List shortcutIds, final List notificationKeys, List systemShortcuts) { - 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. + mNumNotifications = notificationKeys.size(); PopupPopulator.Item[] itemsToPopulate = PopupPopulator .getItemsToPopulate(shortcutIds, notificationKeys, systemShortcuts); - addDummyViews(itemsToPopulate, notificationKeys.size()); - - measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); - orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset); - - boolean reverseOrder = mIsAboveIcon; - if (reverseOrder) { - removeAllViews(); - mNotificationItemView = null; - mShortcutsItemView = null; - itemsToPopulate = PopupPopulator.reverseItems(itemsToPopulate); - addDummyViews(itemsToPopulate, notificationKeys.size()); - - measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); - orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset); - } + populateAndShow(originalIcon, itemsToPopulate); ItemInfo originalItemInfo = (ItemInfo) originalIcon.getTag(); List shortcutViews = mShortcutsItemView == null ? Collections.EMPTY_LIST - : mShortcutsItemView.getDeepShortcutViews(reverseOrder); + : mShortcutsItemView.getDeepShortcutViews(mIsAboveIcon); List systemShortcutViews = mShortcutsItemView == null ? Collections.EMPTY_LIST - : mShortcutsItemView.getSystemShortcutViews(reverseOrder); + : mShortcutsItemView.getSystemShortcutViews(mIsAboveIcon); if (mNotificationItemView != null) { updateNotificationHeader(); } @@ -231,17 +150,6 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra numNotifications, originalIcon.getContentDescription().toString())); } - // 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(); - mLauncher.getDragController().addDragListener(this); mOriginalIcon.forceHideBadge(true); @@ -253,6 +161,60 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra systemShortcuts, systemShortcutViews)); } + @Override + protected void addDummyViews(Item[] itemTypesToPopulate) { + mNotificationItemView = null; + super.addDummyViews(itemTypesToPopulate); + if (mNumNotifications > 0) { + mShortcutsItemView.hideShortcuts(mIsAboveIcon, MAX_SHORTCUTS_IF_NOTIFICATIONS); + } + } + + @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); + } + + 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 (itemType == PopupPopulator.Item.SHORTCUT) { + view.setAccessibilityDelegate(mAccessibilityDelegate); + } + + 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); + } + } + } + private void addDummyViews(PopupPopulator.Item[] itemTypesToPopulate, int numNotifications) { final Resources res = getResources(); final LayoutInflater inflater = mLauncher.getLayoutInflater(); @@ -336,266 +298,18 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra } } - protected PopupItemView getItemViewAt(int index) { - if (!mIsAboveIcon) { - // Opening down, so arrow is the first view. - index++; + @Override + protected void onWidgetsBound() { + if (mShortcutsItemView != null) { + mShortcutsItemView.enableWidgetsIfExist(mOriginalIcon); } - return (PopupItemView) getChildAt(index); - } - - protected int getItemCount() { - // All children except the arrow are items. - return getChildCount() - 1; - } - - private 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( - PopupContainerWithArrow.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. - */ - private void orientAboutIcon(BubbleTextView icon, int arrowHeight) { - int width = getMeasuredWidth(); - int height = getMeasuredHeight() + arrowHeight; - - DragLayer dragLayer = mLauncher.getDragLayer(); - dragLayer.getDescendantRectRelativeToSelf(icon, mTempRect); - Rect insets = dragLayer.getInsets(); - - // Align left (right in RTL) if there is room. - int leftAlignedX = mTempRect.left + icon.getPaddingLeft(); - int rightAlignedX = mTempRect.right - width - icon.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 = icon.getWidth() - icon.getTotalPaddingLeft() - icon.getTotalPaddingRight(); - iconWidth *= icon.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 = icon.getIcon() != null - ? icon.getIcon().getBounds().height() - : icon.getHeight(); - int y = mTempRect.top + icon.getPaddingTop() - height; - mIsAboveIcon = y > dragLayer.getTop() + insets.top; - if (!mIsAboveIcon) { - y = mTempRect.top + icon.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); - } - - private 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 - */ - private 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(); - // Note that we have to use getChildAt() instead of getItemViewAt(), - // since the latter expects the arrow which hasn't been added yet. - PopupItemView itemAttachedToArrow = (PopupItemView) - (getChildAt(mIsAboveIcon ? getChildCount() - 1 : 0)); - 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 View getExtendedTouchView() { - return mOriginalIcon; + protected int getIconHeightForPopupPlacement() { + return mOriginalIcon.getIcon() != null + ? mOriginalIcon.getIcon().getBounds().height() + : mOriginalIcon.getHeight(); } /** @@ -642,17 +356,6 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra }; } - @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(); - } - /** * Updates the notification header if the original icon's badge updated. */ @@ -723,18 +426,6 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra badgeInfo.getNotificationKeys())); } - @Override - protected void onWidgetsBound() { - if (mShortcutsItemView != null) { - mShortcutsItemView.enableWidgetsIfExist(mOriginalIcon); - } - } - - private ObjectAnimator createArrowScaleAnim(float scale) { - return LauncherAnimUtils.ofPropertyValuesHolder( - mArrow, new PropertyListBuilder().scale(scale).build()); - } - public Animator reduceNotificationViewHeight(int heightToRemove, int duration) { return adjustItemHeights(heightToRemove, 0, duration); } @@ -797,29 +488,7 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra } @Override - public boolean supportsAppInfoDropTarget() { - return true; - } - - @Override - public boolean supportsDeleteDropTarget() { - return false; - } - - @Override - public float getIntrinsicIconScaleFactor() { - return 1f; - } - - @Override - public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete, - boolean success) { - if (!success) { - d.dragView.remove(); - mLauncher.showWorkspace(true); - mLauncher.getDropTargetBar().onDragEnd(); - } - } + public void onDropCompleted(View target, DragObject d, boolean success) { } @Override public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { @@ -851,106 +520,20 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra } @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(); - final Resources res = getResources(); - final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration); - 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.setDuration(revealDuration); - revealAnim.setInterpolator(revealInterpolator); - closeAnim.play(revealAnim); - - Animator fadeOut = ObjectAnimator.ofFloat(this, ALPHA, 0); - fadeOut.setDuration(revealDuration); - fadeOut.setInterpolator(revealInterpolator); - closeAnim.play(fadeOut); - + protected void prepareCloseAnimator(AnimatorSet closeAnim) { // Animate original icon's text back in. - Animator fadeText = mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */); - fadeText.setDuration(revealDuration); - closeAnim.play(fadeText); + closeAnim.play(mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */)); - closeAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mOpenCloseAnimator = null; - if (mDeferContainerRemoval) { - setVisibility(INVISIBLE); - } else { - closeComplete(); - } - } - }); - mOpenCloseAnimator = closeAnim; - closeAnim.start(); mOriginalIcon.forceHideBadge(false); + super.prepareCloseAnimator(closeAnim); } - /** - * Closes the folder without animation. - */ + @Override protected void closeComplete() { - if (mOpenCloseAnimator != null) { - mOpenCloseAnimator.cancel(); - mOpenCloseAnimator = null; - } - mIsOpen = false; - mDeferContainerRemoval = false; mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible()); mOriginalIcon.forceHideBadge(false); + mLauncher.getDragController().removeDragListener(this); - mLauncher.getDragLayer().removeView(this); - } - - @Override - protected boolean isOfType(int type) { - return (type & TYPE_POPUP_CONTAINER_WITH_ARROW) != 0; - } - - /** - * Returns a DeepShortcutsContainer which is already open or null - */ - public static PopupContainerWithArrow getOpen(Launcher launcher) { - return getOpenView(launcher, TYPE_POPUP_CONTAINER_WITH_ARROW); - } - - @Override - public int getLogContainerType() { - return ContainerType.DEEPSHORTCUTS; + super.closeComplete(); } } diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java index de9f25e12f..070ac3971e 100644 --- a/src/com/android/launcher3/popup/PopupDataProvider.java +++ b/src/com/android/launcher3/popup/PopupDataProvider.java @@ -25,6 +25,8 @@ import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.Utilities; import com.android.launcher3.badge.BadgeInfo; +import com.android.launcher3.model.PackageItemInfo; +import com.android.launcher3.model.WidgetItem; import com.android.launcher3.notification.NotificationInfo; import com.android.launcher3.notification.NotificationKeyData; import com.android.launcher3.notification.NotificationListener; @@ -32,6 +34,7 @@ import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.PackageUserKey; +import com.android.launcher3.widget.WidgetListRowEntry; import java.util.ArrayList; import java.util.Collections; @@ -53,6 +56,7 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan private static final SystemShortcut[] SYSTEM_SHORTCUTS = new SystemShortcut[] { new SystemShortcut.AppInfo(), new SystemShortcut.Widgets(), + new SystemShortcut.Install() }; private final Launcher mLauncher; @@ -61,6 +65,8 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan private MultiHashMap mDeepShortcutMap = new MultiHashMap<>(); /** Maps packages to their BadgeInfo's . */ private Map mPackageUserToBadgeInfos = new HashMap<>(); + /** Maps packages to their Widgets */ + private ArrayList mAllWidgets = new ArrayList<>(); public PopupDataProvider(Launcher launcher) { mLauncher = launcher; @@ -101,11 +107,7 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan mPackageUserToBadgeInfos.remove(removedPackageUserKey); } updateLauncherIconBadges(Utilities.singletonHashSet(removedPackageUserKey)); - - PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher); - if (openContainer != null) { - openContainer.trimNotifications(mPackageUserToBadgeInfos); - } + trimNotifications(mPackageUserToBadgeInfos); } } @@ -142,10 +144,13 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan if (!updatedBadges.isEmpty()) { updateLauncherIconBadges(updatedBadges.keySet()); } + trimNotifications(updatedBadges); + } - PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher); - if (openContainer != null) { - openContainer.trimNotifications(updatedBadges); + private void trimNotifications(Map updatedBadges) { + BaseActionPopup openContainer = BaseActionPopup.getOpen(mLauncher); + if (openContainer instanceof PopupContainerWithArrow) { + ((PopupContainerWithArrow) openContainer).trimNotifications(updatedBadges); } } @@ -265,4 +270,29 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan } notificationListener.cancelNotification(notificationKey); } + + public void setAllWidgets(ArrayList allWidgets) { + mAllWidgets = allWidgets; + } + + public ArrayList getAllWidgets() { + return mAllWidgets; + } + + public List getWidgetsForPackageUser(PackageUserKey packageUserKey) { + for (WidgetListRowEntry entry : mAllWidgets) { + if (entry.pkgItem.packageName.equals(packageUserKey.mPackageName)) { + ArrayList widgets = new ArrayList<>(entry.widgets); + // Remove widgets not associated with the correct user. + Iterator iterator = widgets.iterator(); + while (iterator.hasNext()) { + if (!iterator.next().user.equals(packageUserKey.mUser)) { + iterator.remove(); + } + } + return widgets.isEmpty() ? null : widgets; + } + } + return null; + } } diff --git a/src/com/android/launcher3/popup/PopupItemView.java b/src/com/android/launcher3/popup/PopupItemView.java index 8ec051b304..75c3f26cfd 100644 --- a/src/com/android/launcher3/popup/PopupItemView.java +++ b/src/com/android/launcher3/popup/PopupItemView.java @@ -32,7 +32,7 @@ import android.widget.FrameLayout; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.popup.PopupContainerWithArrow.RoundedCornerFlags; +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; diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java index 6254d2d008..e709b93688 100644 --- a/src/com/android/launcher3/popup/SystemShortcut.java +++ b/src/com/android/launcher3/popup/SystemShortcut.java @@ -1,6 +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; @@ -11,7 +12,10 @@ import com.android.launcher3.InfoDropTarget; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.R; +import com.android.launcher3.ShortcutInfo; import com.android.launcher3.model.WidgetItem; +import com.android.launcher3.util.InstantAppResolver; +import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.widget.WidgetsBottomSheet; @@ -55,8 +59,9 @@ public abstract class SystemShortcut extends ItemInfo { @Override public View.OnClickListener getOnClickListener(final Launcher launcher, final ItemInfo itemInfo) { - final List widgets = launcher.getWidgetsForPackageUser(new PackageUserKey( - itemInfo.getTargetComponent().getPackageName(), itemInfo.user)); + final List widgets = + launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey( + itemInfo.getTargetComponent().getPackageName(), itemInfo.user)); if (widgets == null) { return null; } @@ -88,11 +93,42 @@ public abstract class SystemShortcut extends ItemInfo { public void onClick(View view) { Rect sourceBounds = launcher.getViewBounds(view); Bundle opts = launcher.getActivityLaunchOptions(view); - InfoDropTarget.startDetailsActivityForInfo(itemInfo, launcher, null, sourceBounds, opts); + InfoDropTarget.startDetailsActivityForInfo(itemInfo, launcher, sourceBounds, opts); launcher.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP, ControlType.APPINFO_TARGET, view); } }; } } + + public static class Install extends SystemShortcut { + public Install() { + super(R.drawable.ic_install_no_shadow, R.string.install_drop_target_label); + } + + @Override + public View.OnClickListener getOnClickListener(final Launcher launcher, + final ItemInfo itemInfo) { + boolean supportsWebUI = (itemInfo instanceof ShortcutInfo) && + ((ShortcutInfo) itemInfo).hasStatusFlag(ShortcutInfo.FLAG_SUPPORTS_WEB_UI); + boolean isInstantApp = false; + if (itemInfo instanceof com.android.launcher3.AppInfo) { + com.android.launcher3.AppInfo appInfo = (com.android.launcher3.AppInfo) itemInfo; + isInstantApp = InstantAppResolver.newInstance(launcher).isInstantApp(appInfo); + } + boolean enabled = supportsWebUI || isInstantApp; + if (!enabled) { + return null; + } + return new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = PackageManagerHelper.getMarketIntent(itemInfo + .getTargetComponent().getPackageName()); + launcher.startActivitySafely(view, intent, itemInfo); + AbstractFloatingView.closeAllOpenViews(launcher); + } + }; + } + } } diff --git a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java index e9d2b50eae..cfb9258dd9 100644 --- a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java +++ b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java @@ -26,7 +26,6 @@ import android.view.View; import com.android.launcher3.Launcher; import com.android.launcher3.Utilities; import com.android.launcher3.graphics.DragPreviewProvider; -import com.android.launcher3.graphics.HolographicOutlineHelper; /** * Extension of {@link DragPreviewProvider} which generates bitmaps scaled to the default icon size. @@ -40,35 +39,17 @@ public class ShortcutDragPreviewProvider extends DragPreviewProvider { mPositionShift = shift; } - @Override - public Bitmap createDragOutline(Canvas canvas) { - Bitmap b = drawScaledPreview(canvas, Bitmap.Config.ALPHA_8); - - HolographicOutlineHelper.getInstance(mView.getContext()) - .applyExpensiveOutlineWithBlur(b, canvas); - canvas.setBitmap(null); - return b; - } - - @Override - public Bitmap createDragBitmap(Canvas canvas) { - Bitmap b = drawScaledPreview(canvas, Bitmap.Config.ARGB_8888); - canvas.setBitmap(null); - return b; - } - - private Bitmap drawScaledPreview(Canvas canvas, Bitmap.Config config) { + public Bitmap createDragBitmap() { Drawable d = mView.getBackground(); Rect bounds = getDrawableBounds(d); int size = Launcher.getLauncher(mView.getContext()).getDeviceProfile().iconSizePx; - final Bitmap b = Bitmap.createBitmap( size + blurSizeOutline, size + blurSizeOutline, - config); + Bitmap.Config.ARGB_8888); - canvas.setBitmap(b); + Canvas canvas = new Canvas(b); canvas.save(Canvas.MATRIX_SAVE_FLAG); canvas.translate(blurSizeOutline / 2, blurSizeOutline / 2); canvas.scale(((float) size) / bounds.width(), ((float) size) / bounds.height(), 0, 0); diff --git a/src/com/android/launcher3/states/AllAppsState.java b/src/com/android/launcher3/states/AllAppsState.java new file mode 100644 index 0000000000..ed3023ab13 --- /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/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java new file mode 100644 index 0000000000..3864e3a857 --- /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/DummyWidget.java b/src/com/android/launcher3/testing/DummyWidget.java deleted file mode 100644 index df887ac1fc..0000000000 --- a/src/com/android/launcher3/testing/DummyWidget.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.android.launcher3.testing; - -import android.appwidget.AppWidgetProviderInfo; - -import com.android.launcher3.CustomAppWidget; -import com.android.launcher3.R; - -public class DummyWidget implements CustomAppWidget { - @Override - public String getLabel() { - return "Dumb Launcher Widget"; - } - - @Override - public int getPreviewImage() { - return 0; - } - - @Override - public int getIcon() { - return 0; - } - - @Override - public int getWidgetLayout() { - return R.layout.zzz_dummy_widget; - } - - @Override - public int getSpanX() { - return 2; - } - - @Override - public int getSpanY() { - return 2; - } - - @Override - public int getMinSpanX() { - return 1; - } - - @Override - public int getMinSpanY() { - return 1; - } - - @Override - public int getResizeMode() { - return AppWidgetProviderInfo.RESIZE_BOTH; - } -} diff --git a/src/com/android/launcher3/testing/LauncherExtension.java b/src/com/android/launcher3/testing/LauncherExtension.java deleted file mode 100644 index 5cb8c4c7d5..0000000000 --- a/src/com/android/launcher3/testing/LauncherExtension.java +++ /dev/null @@ -1,219 +0,0 @@ -package com.android.launcher3.testing; - -import android.content.Intent; -import android.graphics.Color; -import android.os.Bundle; -import android.view.Menu; -import android.view.View; -import android.widget.FrameLayout; - -import com.android.launcher3.AppInfo; -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherCallbacks; -import com.android.launcher3.util.ComponentKey; -import com.android.launcher3.util.ComponentKeyMapper; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; - -/** - * 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 preOnResume() { - } - - @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 onPostCreate(Bundle savedInstanceState) { - } - - @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 finishBindingItems(boolean upgradePath) { - } - - @Override - public void bindAllApplications(ArrayList apps) { - } - - @Override - public void onWorkspaceLockedChanged() { - } - - @Override - public void onInteractionBegin() { - } - - @Override - public void onInteractionEnd() { - } - - @Override - public boolean startSearch(String initialQuery, boolean selectInitialQuery, - Bundle appSearchData) { - return false; - } - - CustomContentCallbacks mCustomContentCallbacks = new CustomContentCallbacks() { - - // Custom content is completely shown. {@code fromResume} indicates whether this was caused - // by a onResume or by scrolling otherwise. - public void onShow(boolean fromResume) { - } - - // Custom content is completely hidden - public void onHide() { - } - - // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing). - public void onScrollProgressChanged(float progress) { - - } - - // Indicates whether the user is allowed to scroll away from the custom content. - public boolean isScrollingAllowed() { - return true; - } - - }; - - @Override - public boolean hasCustomContentToLeft() { - return true; - } - - @Override - public void populateCustomContentContainer() { - FrameLayout customContent = new FrameLayout(LauncherExtension.this); - customContent.setBackgroundColor(Color.GRAY); - addToCustomContentPage(customContent, mCustomContentCallbacks, ""); - } - - @Override - public View getQsbBar() { - return null; - } - - @Override - public Bundle getAdditionalSearchWidgetOptions() { - return new Bundle(); - } - - @Override - public boolean shouldMoveToDefaultScreenOnHomeIntent() { - return true; - } - - @Override - public boolean hasSettings() { - return false; - } - - @Override - public List> getPredictedApps() { - // To debug app predictions, enable AlphabeticalAppsList#DEBUG_PREDICTIONS - return new ArrayList<>(); - } - - @Override - public int getSearchBarHeight() { - return SEARCH_BAR_HEIGHT_NORMAL; - } - - @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 9bcf92b1b2..0000000000 --- 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 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 paths = new ArrayList(); - 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 ed2a3122c0..0000000000 --- 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 mData = new LongSparseArray(); - public final ArrayList mPids = new ArrayList(); - 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 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 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 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 f0c39202ba..0000000000 --- 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 a26a2b6420..0000000000 --- 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 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 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 d475ee9dfb..fe0571b8ad 100644 --- a/src/com/android/launcher3/util/FlingAnimation.java +++ b/src/com/android/launcher3/util/FlingAnimation.java @@ -1,5 +1,7 @@ package com.android.launcher3.util; +import static com.android.launcher3.LauncherState.NORMAL; + import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; @@ -95,7 +97,7 @@ public class FlingAnimation implements AnimatorUpdateListener, Runnable { Runnable onAnimationEndRunnable = new Runnable() { @Override public void run() { - mLauncher.exitSpringLoadedDragMode(); + mLauncher.getStateManager().goToState(NORMAL); mDropTarget.completeDrop(mDragObject); } }; diff --git a/src/com/android/launcher3/util/InstantAppResolver.java b/src/com/android/launcher3/util/InstantAppResolver.java index e60d76808a..99ce7ca90f 100644 --- a/src/com/android/launcher3/util/InstantAppResolver.java +++ b/src/com/android/launcher3/util/InstantAppResolver.java @@ -19,6 +19,7 @@ package com.android.launcher3.util; import android.content.Context; import android.content.pm.ApplicationInfo; +import com.android.launcher3.AppInfo; import com.android.launcher3.R; import com.android.launcher3.Utilities; @@ -39,6 +40,10 @@ public class InstantAppResolver { return false; } + public boolean isInstantApp(AppInfo info) { + return false; + } + public List getInstantApps() { return Collections.emptyList(); } diff --git a/src/com/android/launcher3/util/TestingUtils.java b/src/com/android/launcher3/util/TestingUtils.java deleted file mode 100644 index a7cc42b5fa..0000000000 --- a/src/com/android/launcher3/util/TestingUtils.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.android.launcher3.util; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.util.Log; -import android.view.Gravity; -import android.view.View; -import android.widget.FrameLayout; - -import com.android.launcher3.CustomAppWidget; -import com.android.launcher3.Launcher; -import com.android.launcher3.R; -import com.android.launcher3.Utilities; - -import java.util.HashMap; - -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 final boolean ENABLE_CUSTOM_WIDGET_TEST = false; - public static final String DUMMY_WIDGET = "com.android.launcher3.testing.DummyWidget"; - - 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; - } - } - - public static void addDummyWidget(HashMap set) { - if (ENABLE_CUSTOM_WIDGET_TEST) { - try { - Class clazz = Class.forName(DUMMY_WIDGET); - CustomAppWidget widget = (CustomAppWidget) clazz.newInstance(); - set.put(widget.getClass().getName(), widget); - } catch (Exception e) { - Log.e("TestingUtils", "Error adding dummy widget", e); - } - } - } -} diff --git a/src/com/android/launcher3/util/TraceHelper.java b/src/com/android/launcher3/util/TraceHelper.java new file mode 100644 index 0000000000..5b66fcda62 --- /dev/null +++ b/src/com/android/launcher3/util/TraceHelper.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.util; + +import android.os.SystemClock; +import android.os.Trace; +import android.util.ArrayMap; +import android.util.Log; +import android.util.MutableLong; + +import com.android.launcher3.config.FeatureFlags; + +/** + * A wrapper around {@link Trace} to allow easier proguarding for production builds. + * + * To enable any tracing log, execute the following command: + * $ adb shell setprop log.tag.TAGNAME VERBOSE + */ +public class TraceHelper { + + private static final boolean ENABLED = FeatureFlags.IS_DOGFOOD_BUILD; + + private static final boolean SYSTEM_TRACE = true; + private static final ArrayMap sUpTimes = + ENABLED ? new ArrayMap() : null; + + public static void beginSection(String sectionName) { + if (ENABLED) { + MutableLong time = sUpTimes.get(sectionName); + if (time == null) { + time = new MutableLong(Log.isLoggable(sectionName, Log.VERBOSE) ? 0 : -1); + sUpTimes.put(sectionName, time); + } + if (time.value >= 0) { + if (SYSTEM_TRACE) { + Trace.beginSection(sectionName); + } + time.value = SystemClock.uptimeMillis(); + } + } + } + + public static void partitionSection(String sectionName, String partition) { + if (ENABLED) { + MutableLong time = sUpTimes.get(sectionName); + if (time.value >= 0) { + + if (SYSTEM_TRACE) { + Trace.endSection(); + Trace.beginSection(sectionName); + } + + long now = SystemClock.uptimeMillis(); + Log.d(sectionName, partition + " : " + (now - time.value)); + time.value = now; + } + } + } + + public static void endSection(String sectionName) { + if (ENABLED) { + endSection(sectionName, "End"); + } + } + + public static void endSection(String sectionName, String msg) { + if (ENABLED) { + MutableLong time = sUpTimes.get(sectionName); + if (time.value >= 0) { + if (SYSTEM_TRACE) { + Trace.endSection(); + } + Log.d(sectionName, msg + " : " + (SystemClock.uptimeMillis() - time.value)); + } + } + } +} diff --git a/src/com/android/launcher3/util/UiThreadHelper.java b/src/com/android/launcher3/util/UiThreadHelper.java new file mode 100644 index 0000000000..27140a1138 --- /dev/null +++ b/src/com/android/launcher3/util/UiThreadHelper.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.util; + +import android.content.Context; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.view.inputmethod.InputMethodManager; + +/** + * Utility class for offloading some class from UI thread + */ +public class UiThreadHelper { + + private static HandlerThread sHandlerThread; + private static Handler sHandler; + + private static final int MSG_HIDE_KEYBOARD = 1; + + public static Looper getBackgroundLooper() { + if (sHandlerThread == null) { + sHandlerThread = + new HandlerThread("UiThreadHelper", Process.THREAD_PRIORITY_FOREGROUND); + sHandlerThread.start(); + } + return sHandlerThread.getLooper(); + } + + private static Handler getHandler(Context context) { + if (sHandler == null) { + sHandler = new Handler(getBackgroundLooper(), + new UiCallbacks(context.getApplicationContext())); + } + return sHandler; + } + + public static void hideKeyboardAsync(Context context, IBinder token) { + Message.obtain(getHandler(context), MSG_HIDE_KEYBOARD, token).sendToTarget(); + } + + private static class UiCallbacks implements Handler.Callback { + + private final InputMethodManager mIMM; + + UiCallbacks(Context context) { + mIMM = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + } + + @Override + public boolean handleMessage(Message message) { + switch (message.what) { + case MSG_HIDE_KEYBOARD: + mIMM.hideSoftInputFromWindow((IBinder) message.obj, 0); + return true; + } + return false; + } + } +} diff --git a/src/com/android/launcher3/util/VerticalFlingDetector.java b/src/com/android/launcher3/util/VerticalFlingDetector.java deleted file mode 100644 index 7236c2d1bb..0000000000 --- a/src/com/android/launcher3/util/VerticalFlingDetector.java +++ /dev/null @@ -1,88 +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.util; - -import android.content.Context; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewConfiguration; - -public class VerticalFlingDetector implements View.OnTouchListener { - - private static final float CUSTOM_SLOP_MULTIPLIER = 2.2f; - private static final int SEC_IN_MILLIS = 1000; - - private VelocityTracker mVelocityTracker; - private float mMinimumFlingVelocity; - private float mMaximumFlingVelocity; - private float mDownX, mDownY; - private boolean mShouldCheckFling; - private double mCustomTouchSlop; - - public VerticalFlingDetector(Context context) { - ViewConfiguration vc = ViewConfiguration.get(context); - mMinimumFlingVelocity = vc.getScaledMinimumFlingVelocity(); - mMaximumFlingVelocity = vc.getScaledMaximumFlingVelocity(); - mCustomTouchSlop = CUSTOM_SLOP_MULTIPLIER * vc.getScaledTouchSlop(); - } - - @Override - public boolean onTouch(View v, MotionEvent ev) { - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(ev); - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: - mDownX = ev.getX(); - mDownY = ev.getY(); - mShouldCheckFling = false; - break; - case MotionEvent.ACTION_MOVE: - if (mShouldCheckFling) { - break; - } - if (Math.abs(ev.getY() - mDownY) > mCustomTouchSlop && - Math.abs(ev.getY() - mDownY) > Math.abs(ev.getX() - mDownX)) { - mShouldCheckFling = true; - } - break; - case MotionEvent.ACTION_UP: - if (mShouldCheckFling) { - mVelocityTracker.computeCurrentVelocity(SEC_IN_MILLIS, mMaximumFlingVelocity); - // only when fling is detected in down direction - if (mVelocityTracker.getYVelocity() > mMinimumFlingVelocity) { - cleanUp(); - return true; - } - } - // fall through. - case MotionEvent.ACTION_CANCEL: - cleanUp(); - } - return false; - } - - private void cleanUp() { - if (mVelocityTracker == null) { - return; - } - mVelocityTracker.recycle(); - mVelocityTracker = null; - } -} diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java index 392697458b..5c24687404 100644 --- a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java +++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java @@ -1,72 +1,48 @@ package com.android.launcher3.util; import android.app.WallpaperManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; import android.os.IBinder; +import android.os.Message; +import android.os.SystemClock; import android.util.Log; -import android.view.Choreographer; -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. */ -public class WallpaperOffsetInterpolator implements Choreographer.FrameCallback { +public class WallpaperOffsetInterpolator extends BroadcastReceiver { + + private static final int[] sTempInt = new int[2]; private static final String TAG = "WPOffsetInterpolator"; private static final int ANIMATION_DURATION = 250; // Don't use all the wallpaper for parallax until you have at least this many pages private static final int MIN_PARALLAX_PAGE_SPAN = 4; - private final Choreographer mChoreographer; - private final Interpolator mInterpolator; - private final WallpaperManager mWallpaperManager; private final Workspace mWorkspace; private final boolean mIsRtl; + private final Handler mHandler; + private boolean mRegistered = false; private IBinder mWindowToken; private boolean mWallpaperIsLiveWallpaper; - private float mLastSetWallpaperOffsetSteps = 0; - private float mFinalOffset = 0.0f; - private float mCurrentOffset = 0.5f; // to force an initial update - private boolean mWaitingForUpdate; private boolean mLockedToDefaultPage; - - private boolean mAnimating; - private long mAnimationStartTime; - private float mAnimationStartOffset; - int mNumScreens; - int mNumPagesForWallpaperParallax; + private int mNumScreens; public WallpaperOffsetInterpolator(Workspace workspace) { - mChoreographer = Choreographer.getInstance(); - mInterpolator = new DecelerateInterpolator(1.5f); - mWorkspace = workspace; - mWallpaperManager = WallpaperManager.getInstance(workspace.getContext()); mIsRtl = Utilities.isRtl(workspace.getResources()); - } - - @Override - public void doFrame(long frameTimeNanos) { - updateOffset(false); - } - - private void updateOffset(boolean force) { - if (mWaitingForUpdate || force) { - mWaitingForUpdate = false; - if (computeScrollOffset() && mWindowToken != null) { - try { - mWallpaperManager.setWallpaperOffsets(mWindowToken, getCurrX(), 0.5f); - setWallpaperOffsetSteps(); - } catch (IllegalArgumentException e) { - Log.e(TAG, "Error updating wallpaper offset: " + e); - } - } - } + mHandler = new OffsetHandler(workspace.getContext()); } /** @@ -80,55 +56,34 @@ public class WallpaperOffsetInterpolator implements Choreographer.FrameCallback return mLockedToDefaultPage; } - public boolean computeScrollOffset() { - final float oldOffset = mCurrentOffset; - if (mAnimating) { - long durationSinceAnimation = System.currentTimeMillis() - mAnimationStartTime; - float t0 = durationSinceAnimation / (float) ANIMATION_DURATION; - float t1 = mInterpolator.getInterpolation(t0); - mCurrentOffset = mAnimationStartOffset + - (mFinalOffset - mAnimationStartOffset) * t1; - mAnimating = durationSinceAnimation < ANIMATION_DURATION; - } else { - mCurrentOffset = mFinalOffset; - } - - if (Math.abs(mCurrentOffset - mFinalOffset) > 0.0000001f) { - scheduleUpdate(); - } - if (Math.abs(oldOffset - mCurrentOffset) > 0.0000001f) { - return true; - } - return false; - } - /** + * Computes the wallpaper offset as an int ratio (out[0] / out[1]) + * * TODO: do different behavior if it's a live wallpaper? */ - public float wallpaperOffsetForScroll(int scroll) { + private void wallpaperOffsetForScroll(int scroll, int numScrollingPages, final int[] out) { + out[1] = 1; + // To match the default wallpaper behavior in the system, we default to either the left // or right edge on initialization - int numScrollingPages = getNumScreensExcludingEmptyAndCustom(); if (mLockedToDefaultPage || numScrollingPages <= 1) { - return mIsRtl ? 1f : 0f; + out[0] = mIsRtl ? 1 : 0; + return; } // Distribute the wallpaper parallax over a minimum of MIN_PARALLAX_PAGE_SPAN workspace // screens, not including the custom screen, and empty screens (if > MIN_PARALLAX_PAGE_SPAN) - if (mWallpaperIsLiveWallpaper) { - mNumPagesForWallpaperParallax = numScrollingPages; - } else { - mNumPagesForWallpaperParallax = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages); - } + int numPagesForWallpaperParallax = mWallpaperIsLiveWallpaper ? numScrollingPages : + Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages); // Offset by the custom screen int leftPageIndex; int rightPageIndex; if (mIsRtl) { - rightPageIndex = mWorkspace.numCustomPages(); + rightPageIndex = 0; leftPageIndex = rightPageIndex + numScrollingPages - 1; } else { - leftPageIndex = mWorkspace.numCustomPages(); + leftPageIndex = 0; rightPageIndex = leftPageIndex + numScrollingPages - 1; } @@ -136,106 +91,184 @@ public class WallpaperOffsetInterpolator implements Choreographer.FrameCallback int leftPageScrollX = mWorkspace.getScrollForPage(leftPageIndex); int rightPageScrollX = mWorkspace.getScrollForPage(rightPageIndex); int scrollRange = rightPageScrollX - leftPageScrollX; - if (scrollRange == 0) { - return 0f; + if (scrollRange <= 0) { + out[0] = 0; + return; } // Sometimes the left parameter of the pages is animated during a layout transition; // this parameter offsets it to keep the wallpaper from animating as well int adjustedScroll = scroll - leftPageScrollX - mWorkspace.getLayoutTransitionOffsetForPage(0); - float offset = Utilities.boundToRange((float) adjustedScroll / scrollRange, 0f, 1f); + adjustedScroll = Utilities.boundToRange(adjustedScroll, 0, scrollRange); + out[1] = (numPagesForWallpaperParallax - 1) * scrollRange; // The offset is now distributed 0..1 between the left and right pages that we care about, // so we just map that between the pages that we are using for parallax - float rtlOffset = 0; + int rtlOffset = 0; if (mIsRtl) { // In RTL, the pages are right aligned, so adjust the offset from the end - rtlOffset = (float) ((mNumPagesForWallpaperParallax - 1) - (numScrollingPages - 1)) / - (mNumPagesForWallpaperParallax - 1); + rtlOffset = out[1] - (numScrollingPages - 1) * scrollRange; } - return rtlOffset + offset * - ((float) (numScrollingPages - 1) / (mNumPagesForWallpaperParallax - 1)); + out[0] = rtlOffset + adjustedScroll * (numScrollingPages - 1); } - private float wallpaperOffsetForCurrentScroll() { - return wallpaperOffsetForScroll(mWorkspace.getScrollX()); + public float wallpaperOffsetForScroll(int scroll) { + wallpaperOffsetForScroll(scroll, getNumScreensExcludingEmpty(), sTempInt); + return ((float) sTempInt[0]) / sTempInt[1]; } - private int numEmptyScreensToIgnore() { - int numScrollingPages = mWorkspace.getChildCount() - mWorkspace.numCustomPages(); + private int getNumScreensExcludingEmpty() { + int numScrollingPages = mWorkspace.getChildCount(); if (numScrollingPages >= MIN_PARALLAX_PAGE_SPAN && mWorkspace.hasExtraEmptyScreen()) { - return 1; + return numScrollingPages - 1; } else { - return 0; + return numScrollingPages; } } - private int getNumScreensExcludingEmptyAndCustom() { - return mWorkspace.getChildCount() - numEmptyScreensToIgnore() - mWorkspace.numCustomPages(); - } - public void syncWithScroll() { - float offset = wallpaperOffsetForCurrentScroll(); - setFinalX(offset); - updateOffset(true); - } - - public float getCurrX() { - return mCurrentOffset; - } - - public float getFinalX() { - return mFinalOffset; - } - - private void animateToFinal() { - mAnimating = true; - mAnimationStartOffset = mCurrentOffset; - mAnimationStartTime = System.currentTimeMillis(); - } - - private void setWallpaperOffsetSteps() { - // Set wallpaper offset steps (1 / (number of screens - 1)) - float xOffset = 1.0f / (mNumPagesForWallpaperParallax - 1); - if (xOffset != mLastSetWallpaperOffsetSteps) { - mWallpaperManager.setWallpaperOffsetSteps(xOffset, 1.0f); - mLastSetWallpaperOffsetSteps = xOffset; - } - } - - public void setFinalX(float x) { - scheduleUpdate(); - mFinalOffset = Math.max(0f, Math.min(x, 1f)); - if (getNumScreensExcludingEmptyAndCustom() != mNumScreens) { - if (mNumScreens > 0 && Float.compare(mCurrentOffset, mFinalOffset) != 0) { - // Don't animate if we're going from 0 screens, or if the final offset is the same - // as the current offset - animateToFinal(); + int numScreens = getNumScreensExcludingEmpty(); + wallpaperOffsetForScroll(mWorkspace.getScrollX(), numScreens, sTempInt); + Message msg = Message.obtain(mHandler, MSG_UPDATE_OFFSET, sTempInt[0], sTempInt[1], + mWindowToken); + if (numScreens != mNumScreens) { + if (mNumScreens > 0) { + // Don't animate if we're going from 0 screens + msg.what = MSG_START_ANIMATION; } - mNumScreens = getNumScreensExcludingEmptyAndCustom(); + mNumScreens = numScreens; + updateOffset(); } + msg.sendToTarget(); } - private void scheduleUpdate() { - if (!mWaitingForUpdate) { - mChoreographer.postFrameCallback(this); - mWaitingForUpdate = true; + private void updateOffset() { + int numPagesForWallpaperParallax; + if (mWallpaperIsLiveWallpaper) { + numPagesForWallpaperParallax = mNumScreens; + } else { + numPagesForWallpaperParallax = Math.max(MIN_PARALLAX_PAGE_SPAN, mNumScreens); } + Message.obtain(mHandler, MSG_SET_NUM_PARALLAX, numPagesForWallpaperParallax, 0, + mWindowToken).sendToTarget(); } public void jumpToFinal() { - mCurrentOffset = mFinalOffset; - } - - public void onResume() { - mWallpaperIsLiveWallpaper = mWallpaperManager.getWallpaperInfo() != null; - // Force the wallpaper offset steps to be set again, because another app might have changed - // them - mLastSetWallpaperOffsetSteps = 0f; + Message.obtain(mHandler, MSG_JUMP_TO_FINAL, mWindowToken).sendToTarget(); } public void setWindowToken(IBinder token) { mWindowToken = token; + if (mWindowToken == null && mRegistered) { + mWorkspace.getContext().unregisterReceiver(this); + mRegistered = false; + } else if (mWindowToken != null && !mRegistered) { + mWorkspace.getContext() + .registerReceiver(this, new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED)); + onReceive(mWorkspace.getContext(), null); + mRegistered = true; + } + } + + @Override + public void onReceive(Context context, Intent intent) { + mWallpaperIsLiveWallpaper = + WallpaperManager.getInstance(mWorkspace.getContext()).getWallpaperInfo() != null; + updateOffset(); + } + + private static final int MSG_START_ANIMATION = 1; + private static final int MSG_UPDATE_OFFSET = 2; + private static final int MSG_APPLY_OFFSET = 3; + private static final int MSG_SET_NUM_PARALLAX = 4; + private static final int MSG_JUMP_TO_FINAL = 5; + + private static class OffsetHandler extends Handler { + + private final Interpolator mInterpolator; + private final WallpaperManager mWM; + + private float mCurrentOffset = 0.5f; // to force an initial update + private boolean mAnimating; + private long mAnimationStartTime; + private float mAnimationStartOffset; + + private float mFinalOffset; + private float mOffsetX; + + public OffsetHandler(Context context) { + super(UiThreadHelper.getBackgroundLooper()); + mInterpolator = Interpolators.DEACCEL_1_5; + mWM = WallpaperManager.getInstance(context); + } + + @Override + public void handleMessage(Message msg) { + final IBinder token = (IBinder) msg.obj; + if (token == null) { + return; + } + + switch (msg.what) { + case MSG_START_ANIMATION: { + mAnimating = true; + mAnimationStartOffset = mCurrentOffset; + mAnimationStartTime = msg.getWhen(); + // Follow through + } + case MSG_UPDATE_OFFSET: + mFinalOffset = ((float) msg.arg1) / msg.arg2; + // Follow through + case MSG_APPLY_OFFSET: { + float oldOffset = mCurrentOffset; + if (mAnimating) { + long durationSinceAnimation = SystemClock.uptimeMillis() + - mAnimationStartTime; + float t0 = durationSinceAnimation / (float) ANIMATION_DURATION; + float t1 = mInterpolator.getInterpolation(t0); + mCurrentOffset = mAnimationStartOffset + + (mFinalOffset - mAnimationStartOffset) * t1; + mAnimating = durationSinceAnimation < ANIMATION_DURATION; + } else { + mCurrentOffset = mFinalOffset; + } + + if (Float.compare(mCurrentOffset, oldOffset) != 0) { + setOffsetSafely(token); + // Force the wallpaper offset steps to be set again, because another app + // might have changed them + mWM.setWallpaperOffsetSteps(mOffsetX, 1.0f); + } + if (mAnimating) { + // If we are animating, keep updating the offset + Message.obtain(this, MSG_APPLY_OFFSET, token).sendToTarget(); + } + return; + } + case MSG_SET_NUM_PARALLAX: { + // Set wallpaper offset steps (1 / (number of screens - 1)) + mOffsetX = 1.0f / (msg.arg1 - 1); + mWM.setWallpaperOffsetSteps(mOffsetX, 1.0f); + return; + } + case MSG_JUMP_TO_FINAL: { + if (Float.compare(mCurrentOffset, mFinalOffset) != 0) { + mCurrentOffset = mFinalOffset; + setOffsetSafely(token); + } + mAnimating = false; + return; + } + } + } + + private void setOffsetSafely(IBinder token) { + try { + mWM.setWallpaperOffsets(token, mCurrentOffset, 0.5f); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Error updating wallpaper offset: " + e); + } + } } } \ No newline at end of file diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java index 7b5bcdbd42..8c9a44186d 100644 --- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java +++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java @@ -285,7 +285,7 @@ public class RecyclerViewFastScroller extends View { return; } int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); - canvas.translate(getWidth() / 2, mRv.getPaddingTop()); + canvas.translate(getWidth() / 2, mRv.getScrollBarTop()); // Draw the track float halfW = mWidth / 2; canvas.drawRoundRect(-halfW, 0, halfW, mRv.getScrollbarTrackHeight(), @@ -317,7 +317,7 @@ public class RecyclerViewFastScroller extends View { * Returns whether the specified point is inside the thumb bounds. */ private boolean isNearThumb(int x, int y) { - int offset = y - mRv.getPaddingTop() - mThumbOffsetY; + int offset = y - mRv.getScrollBarTop() - mThumbOffsetY; return x >= 0 && x < getWidth() && offset >= 0 && offset <= mThumbHeight; } @@ -348,7 +348,7 @@ public class RecyclerViewFastScroller extends View { private void updatePopupY(int lastTouchY) { int height = mPopupView.getHeight(); float top = lastTouchY - (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * height) - + mRv.getPaddingTop(); + + mRv.getScrollBarTop(); top = Utilities.boundToRange(top, mMaxWidth, mRv.getScrollbarTrackHeight() - mMaxWidth - height); mPopupView.setTranslationY(top); diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java new file mode 100644 index 0000000000..e328759265 --- /dev/null +++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java @@ -0,0 +1,280 @@ +/* + * 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.widget; + +import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity; +import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.content.Context; +import android.graphics.Point; +import android.util.AttributeSet; +import android.util.Property; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnLongClickListener; +import android.view.animation.Interpolator; +import android.widget.Toast; + +import com.android.launcher3.AbstractFloatingView; +import com.android.launcher3.DragSource; +import com.android.launcher3.DropTarget.DragObject; +import com.android.launcher3.ItemInfo; +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.Interpolators; +import com.android.launcher3.dragndrop.DragOptions; +import com.android.launcher3.graphics.GradientView; +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.util.SystemUiController; +import com.android.launcher3.util.Themes; + +/** + * Base class for various widgets popup + */ +abstract class BaseWidgetSheet extends AbstractFloatingView + implements OnClickListener, OnLongClickListener, DragSource, SwipeDetector.Listener { + + + protected static Property TRANSLATION_SHIFT = + new Property(Float.class, "translationShift") { + + @Override + public Float get(BaseWidgetSheet view) { + return view.mTranslationShift; + } + + @Override + public void set(BaseWidgetSheet view, Float value) { + view.setTranslationShift(value); + } + }; + protected static final float TRANSLATION_SHIFT_CLOSED = 1f; + protected static final float TRANSLATION_SHIFT_OPENED = 0f; + + /* Touch handling related member variables. */ + private Toast mWidgetInstructionToast; + + protected final Launcher mLauncher; + 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; + + protected boolean mNoIntercept; + + public BaseWidgetSheet(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mLauncher = Launcher.getLauncher(context); + + mScrollInterpolator = Interpolators.SCROLL_CUBIC; + mSwipeDetector = new SwipeDetector(context, this, SwipeDetector.VERTICAL); + + mOpenCloseAnimator = LauncherAnimUtils.ofPropertyValuesHolder(this); + mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mSwipeDetector.finishedScrolling(); + } + }); + } + + @Override + public final void onClick(View v) { + // Let the user know that they have to long press to add a widget + if (mWidgetInstructionToast != null) { + mWidgetInstructionToast.cancel(); + } + + CharSequence msg = Utilities.wrapForTts( + getContext().getText(R.string.long_press_widget_to_add), + getContext().getString(R.string.long_accessible_way_to_add)); + mWidgetInstructionToast = Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT); + mWidgetInstructionToast.show(); + } + + @Override + public final boolean onLongClick(View v) { + if (!mLauncher.isDraggingEnabled()) return false; + + if (v instanceof WidgetCell) { + return beginDraggingWidget((WidgetCell) v); + } + return true; + } + + protected void setTranslationShift(float translationShift) { + mTranslationShift = translationShift; + mGradientView.setAlpha(1 - mTranslationShift); + mContent.setTranslationY(mTranslationShift * mContent.getHeight()); + } + + private boolean beginDraggingWidget(WidgetCell v) { + // Get the widget preview as the drag representation + WidgetImageView image = v.getWidgetView(); + + // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and + // we abort the drag. + if (image.getBitmap() == null) { + return false; + } + + int[] loc = new int[2]; + mLauncher.getDragLayer().getLocationInDragLayer(image, loc); + + new PendingItemDragHelper(v).startDrag( + image.getBitmapBounds(), image.getBitmap().getWidth(), image.getWidth(), + new Point(loc[0], loc[1]), this, new DragOptions()); + close(true); + return true; + } + + // + // Drag related handling methods that implement {@link DragSource} interface. + // + + @Override + public void onDropCompleted(View target, DragObject d, boolean success) { } + + + @Override + public boolean onControllerInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_UP && !mNoIntercept) { + // If we got ACTION_UP without ever returning true on intercept, + // the user never started dragging the bottom sheet. + if (!mLauncher.getDragLayer().isEventOverView(mContent, ev)) { + close(true); + return false; + } + } + + if (mNoIntercept) { + return false; + } + + int directionsToDetectScroll = mSwipeDetector.isIdleState() ? + SwipeDetector.DIRECTION_NEGATIVE : 0; + mSwipeDetector.setDetectableScrollConditions( + directionsToDetectScroll, false); + mSwipeDetector.onTouchEvent(ev); + return mSwipeDetector.isDraggingOrSettling(); + } + + @Override + public boolean onControllerTouchEvent(MotionEvent ev) { + return mSwipeDetector.onTouchEvent(ev); + } + + /* SwipeDetector.Listener */ + + @Override + public void onDragStart(boolean start) { } + + @Override + public boolean onDrag(float displacement, float velocity) { + float range = mContent.getHeight(); + displacement = Utilities.boundToRange(displacement, 0, range); + setTranslationShift(displacement / range); + return true; + } + + @Override + public void onDragEnd(float velocity, boolean fling) { + if ((fling && velocity > 0) || mTranslationShift > 0.5f) { + mScrollInterpolator = scrollInterpolatorForVelocity(velocity); + mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration( + velocity, TRANSLATION_SHIFT_CLOSED - mTranslationShift)); + close(true); + } else { + mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat( + TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED)); + mOpenCloseAnimator.setDuration( + SwipeDetector.calculateDuration(velocity, mTranslationShift)) + .setInterpolator(Interpolators.DEACCEL); + mOpenCloseAnimator.start(); + } + } + + protected void handleClose(boolean animate, long defaultDuration) { + if (!mIsOpen || mOpenCloseAnimator.isRunning()) { + return; + } + if (animate) { + mOpenCloseAnimator.setValues( + PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_CLOSED)); + mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + onCloseComplete(); + } + }); + if (mSwipeDetector.isIdleState()) { + mOpenCloseAnimator + .setDuration(defaultDuration) + .setInterpolator(Interpolators.ACCEL); + } else { + mOpenCloseAnimator.setInterpolator(mScrollInterpolator); + } + mOpenCloseAnimator.start(); + } else { + setTranslationShift(TRANSLATION_SHIFT_CLOSED); + onCloseComplete(); + } + } + + protected void onCloseComplete() { + mIsOpen = false; + mLauncher.getDragLayer().removeView(this); + mLauncher.getSystemUiController().updateUiState( + SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET, 0); + } + + protected void setupNavBarColor() { + boolean isSheetDark = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark); + mLauncher.getSystemUiController().updateUiState( + SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET, + isSheetDark ? SystemUiController.FLAG_DARK_NAV : SystemUiController.FLAG_LIGHT_NAV); + } + + @Override + public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) { + targetParent.containerType = ContainerType.WIDGETS; + targetParent.cardinality = getElementsRowCount(); + } + + @Override + public final void logActionCommand(int command) { + Target target = newContainerTarget(ContainerType.WIDGETS); + target.cardinality = getElementsRowCount(); + mLauncher.getUserEventDispatcher().logActionCommand(command, target); + } + + protected abstract int getElementsRowCount(); + +} diff --git a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java index ad05ce9171..bc404842eb 100644 --- a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java +++ b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java @@ -35,13 +35,13 @@ public class PendingAddWidgetInfo extends PendingAddItemInfo { public Bundle bindOptions = null; public PendingAddWidgetInfo(LauncherAppWidgetProviderInfo i) { - if (i.isCustomWidget) { + if (i.isCustomWidget()) { itemType = LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET; } else { itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; } this.info = i; - user = i.getUser(); + user = i.getProfile(); componentName = i.provider; previewImage = i.previewImage; icon = i.icon; diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java index 19be28d832..c5cf5e23a4 100644 --- a/src/com/android/launcher3/widget/PendingItemDragHelper.java +++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java @@ -22,7 +22,6 @@ import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.os.Build; import android.view.View; import android.widget.RemoteViews; @@ -32,11 +31,9 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.PendingAddItemInfo; import com.android.launcher3.R; -import com.android.launcher3.Workspace; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.dragndrop.LivePreviewWidgetCell; import com.android.launcher3.graphics.DragPreviewProvider; -import com.android.launcher3.graphics.HolographicOutlineHelper; import com.android.launcher3.graphics.LauncherIcons; /** @@ -48,8 +45,8 @@ public class PendingItemDragHelper extends DragPreviewProvider { private static final float MAX_WIDGET_SCALE = 1.25f; private final PendingAddItemInfo mAddInfo; + private int[] mEstimatedCellSize; - private Bitmap mPreviewBitmap; private RemoteViews mPreview; public PendingItemDragHelper(View view) { @@ -80,12 +77,12 @@ public class PendingItemDragHelper extends DragPreviewProvider { final Point dragOffset; final Rect dragRegion; + mEstimatedCellSize = launcher.getWorkspace().estimateItemSize(mAddInfo); if (mAddInfo instanceof PendingAddWidgetInfo) { PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) mAddInfo; - int[] size = launcher.getWorkspace().estimateItemSize(createWidgetInfo, true, false); - int maxWidth = Math.min((int) (previewBitmapWidth * MAX_WIDGET_SCALE), size[0]); + int maxWidth = Math.min((int) (previewBitmapWidth * MAX_WIDGET_SCALE), mEstimatedCellSize[0]); int[] previewSizeBeforeScale = new int[1]; @@ -117,14 +114,12 @@ public class PendingItemDragHelper extends DragPreviewProvider { PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) mAddInfo; Drawable icon = createShortcutInfo.activityInfo.getFullResIcon(app.getIconCache()); preview = LauncherIcons.createScaledBitmapWithoutShadow(icon, launcher, 0); - mAddInfo.spanX = mAddInfo.spanY = 1; scale = ((float) launcher.getDeviceProfile().iconSizePx) / preview.getWidth(); dragOffset = new Point(previewPadding / 2, previewPadding / 2); // Create a preview same as the workspace cell size and draw the icon at the // appropriate position. - int[] size = launcher.getWorkspace().estimateItemSize(mAddInfo, false, true); DeviceProfile dp = launcher.getDeviceProfile(); int iconSize = dp.iconSizePx; @@ -134,9 +129,10 @@ public class PendingItemDragHelper extends DragPreviewProvider { previewBounds.top += padding; dragRegion = new Rect(); - dragRegion.left = (size[0] - iconSize) / 2; + dragRegion.left = (mEstimatedCellSize[0] - iconSize) / 2; dragRegion.right = dragRegion.left + iconSize; - dragRegion.top = (size[1] - iconSize - dp.iconTextSizePx - dp.iconDrawablePaddingPx) / 2; + dragRegion.top = (mEstimatedCellSize[1] + - iconSize - dp.iconTextSizePx - dp.iconDrawablePaddingPx) / 2; dragRegion.bottom = dragRegion.top + iconSize; } @@ -149,60 +145,31 @@ public class PendingItemDragHelper extends DragPreviewProvider { int dragLayerY = screenPos.y + previewBounds.top + (int) ((scale * preview.getHeight() - preview.getHeight()) / 2); - mPreviewBitmap = preview; // Start the drag launcher.getDragController().startDrag(preview, dragLayerX, dragLayerY, source, mAddInfo, dragOffset, dragRegion, scale, options); } - @Override - public Bitmap createDragOutline(Canvas canvas) { - if (mAddInfo instanceof PendingAddShortcutInfo) { - int width = mPreviewBitmap.getWidth(); - int height = mPreviewBitmap.getHeight(); - Bitmap b = Bitmap.createBitmap(width + blurSizeOutline, height + blurSizeOutline, - Bitmap.Config.ALPHA_8); - canvas.setBitmap(b); - - Launcher launcher = Launcher.getLauncher(mView.getContext()); - int size = launcher.getDeviceProfile().iconSizePx; - - Rect src = new Rect(0, 0, mPreviewBitmap.getWidth(), mPreviewBitmap.getHeight()); - Rect dst = new Rect(0, 0, size, size); - dst.offset(blurSizeOutline / 2, blurSizeOutline / 2); - canvas.drawBitmap(mPreviewBitmap, src, dst, new Paint(Paint.FILTER_BITMAP_FLAG)); - - HolographicOutlineHelper.getInstance(mView.getContext()) - .applyExpensiveOutlineWithBlur(b, canvas); - - canvas.setBitmap(null); - return b; + protected Bitmap convertPreviewToAlphaBitmap(Bitmap preview) { + if (mAddInfo instanceof PendingAddShortcutInfo || mEstimatedCellSize == null) { + return super.convertPreviewToAlphaBitmap(preview); } - Workspace workspace = Launcher.getLauncher(mView.getContext()).getWorkspace(); - int[] size = workspace.estimateItemSize(mAddInfo, false, false); - - int w = size[0]; - int h = size[1]; + int w = mEstimatedCellSize[0]; + int h = mEstimatedCellSize[1]; final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ALPHA_8); - canvas.setBitmap(b); + Rect src = new Rect(0, 0, preview.getWidth(), preview.getHeight()); - Rect src = new Rect(0, 0, mPreviewBitmap.getWidth(), mPreviewBitmap.getHeight()); - float scaleFactor = Math.min((w - blurSizeOutline) / (float) mPreviewBitmap.getWidth(), - (h - blurSizeOutline) / (float) mPreviewBitmap.getHeight()); - int scaledWidth = (int) (scaleFactor * mPreviewBitmap.getWidth()); - int scaledHeight = (int) (scaleFactor * mPreviewBitmap.getHeight()); + float scaleFactor = Math.min((w - blurSizeOutline) / (float) preview.getWidth(), + (h - blurSizeOutline) / (float) preview.getHeight()); + int scaledWidth = (int) (scaleFactor * preview.getWidth()); + int scaledHeight = (int) (scaleFactor * preview.getHeight()); Rect dst = new Rect(0, 0, scaledWidth, scaledHeight); // center the image dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2); - - canvas.drawBitmap(mPreviewBitmap, src, dst, null); - HolographicOutlineHelper.getInstance(mView.getContext()) - .applyExpensiveOutlineWithBlur(b, canvas); - canvas.setBitmap(null); - + new Canvas(b).drawBitmap(preview, src, dst, new Paint(Paint.FILTER_BITMAP_FLAG)); return b; } } diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java index 40dbd523ca..2ba55ab977 100644 --- a/src/com/android/launcher3/widget/WidgetCell.java +++ b/src/com/android/launcher3/widget/WidgetCell.java @@ -75,6 +75,9 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { protected CancellationSignal mActiveRequest; private boolean mAnimatePreview = true; + private boolean mApplyBitmapDeferred = false; + private Bitmap mDeferredBitmap; + protected final BaseActivity mActivity; public WidgetCell(Context context) { @@ -150,15 +153,31 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { return mWidgetImage; } + /** + * Sets if applying bitmap preview should be deferred. The UI will still load the bitmap, but + * will not cause invalidate, so that when deferring is disabled later, all the bitmaps are + * ready. + * This prevents invalidates while the animation is running. + */ + public void setApplyBitmapDeferred(boolean isDeferred) { + if (mApplyBitmapDeferred != isDeferred) { + mApplyBitmapDeferred = isDeferred; + if (!mApplyBitmapDeferred && mDeferredBitmap != null) { + applyPreview(mDeferredBitmap); + mDeferredBitmap = null; + } + } + } + public void setAnimatePreview(boolean shouldAnimate) { mAnimatePreview = shouldAnimate; } public void applyPreview(Bitmap bitmap) { - applyPreview(bitmap, true); - } - - public void applyPreview(Bitmap bitmap, boolean animate) { + if (mApplyBitmapDeferred) { + mDeferredBitmap = bitmap; + return; + } if (bitmap != null) { mWidgetImage.setBitmap(bitmap, DrawableFactory.get(getContext()).getBadgeForUser(mItem.user, getContext())); @@ -173,15 +192,11 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { } public void ensurePreview() { - ensurePreview(true); - } - - public void ensurePreview(boolean animate) { if (mActiveRequest != null) { return; } mActiveRequest = mWidgetPreviewLoader.getPreview( - mItem, mPresetPreviewSize, mPresetPreviewSize, this, animate); + mItem, mPresetPreviewSize, mPresetPreviewSize, this); } @Override diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java index 5eeea44b89..8dcdd4465f 100644 --- a/src/com/android/launcher3/widget/WidgetHostViewLoader.java +++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java @@ -85,7 +85,7 @@ public class WidgetHostViewLoader implements DragController.DragListener { private boolean preloadWidget() { final LauncherAppWidgetProviderInfo pInfo = mInfo.info; - if (pInfo.isCustomWidget) { + if (pInfo.isCustomWidget()) { return false; } final Bundle options = getDefaultOptionsForWidget(mLauncher, mInfo); @@ -129,7 +129,7 @@ public class WidgetHostViewLoader implements DragController.DragListener { mWidgetLoadingId = -1; hostView.setVisibility(View.INVISIBLE); - int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(mInfo, false, true); + int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(mInfo); // We want the first widget layout to be the correct size. This will be important // for width size reporting to the AppWidgetManager. DragLayer.LayoutParams lp = new DragLayer.LayoutParams(unScaledSize[0], diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java index 01101ac74c..7fa5ff054b 100644 --- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java +++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java @@ -16,63 +16,36 @@ package com.android.launcher3.widget; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; import android.view.Gravity; import android.view.LayoutInflater; -import android.view.MotionEvent; 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.AbstractFloatingView; -import com.android.launcher3.DropTarget; import com.android.launcher3.Insettable; import com.android.launcher3.ItemInfo; -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherAnimUtils; import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.touch.SwipeDetector; -import com.android.launcher3.anim.PropertyListBuilder; -import com.android.launcher3.dragndrop.DragController; -import com.android.launcher3.dragndrop.DragOptions; +import com.android.launcher3.anim.Interpolators; import com.android.launcher3.graphics.GradientView; import com.android.launcher3.model.WidgetItem; -import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.util.PackageUserKey; -import com.android.launcher3.util.SystemUiController; -import com.android.launcher3.util.Themes; -import com.android.launcher3.util.TouchController; import java.util.List; /** * Bottom sheet for the "Widgets" system shortcut in the long-press popup. */ -public class WidgetsBottomSheet extends AbstractFloatingView implements Insettable, TouchController, - SwipeDetector.Listener, View.OnClickListener, View.OnLongClickListener, - DragController.DragListener { +public class WidgetsBottomSheet extends BaseWidgetSheet implements Insettable { - private int mTranslationYOpen; - private int mTranslationYClosed; - private float mTranslationYRange; - - private Launcher mLauncher; + private static final int DEFAULT_CLOSE_DURATION = 200; private ItemInfo mOriginalItemInfo; - private ObjectAnimator mOpenCloseAnimator; - private Interpolator mFastOutSlowInInterpolator; - private SwipeDetector.ScrollInterpolator mScrollInterpolator; private Rect mInsets; - private SwipeDetector mSwipeDetector; - private GradientView mGradientBackground; public WidgetsBottomSheet(Context context, AttributeSet attrs) { this(context, attrs, 0); @@ -81,23 +54,18 @@ public class WidgetsBottomSheet extends AbstractFloatingView implements Insettab public WidgetsBottomSheet(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setWillNotDraw(false); - mLauncher = Launcher.getLauncher(context); - mOpenCloseAnimator = LauncherAnimUtils.ofPropertyValuesHolder(this); - mFastOutSlowInInterpolator = - AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in); - mScrollInterpolator = new SwipeDetector.ScrollInterpolator(); mInsets = new Rect(); - mSwipeDetector = new SwipeDetector(context, this, SwipeDetector.VERTICAL); - mGradientBackground = (GradientView) mLauncher.getLayoutInflater().inflate( + + mGradientView = (GradientView) mLauncher.getLayoutInflater().inflate( R.layout.gradient_bg, mLauncher.getDragLayer(), false); + mGradientView.setProgress(1, false); + mContent = this; } @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - mTranslationYOpen = 0; - mTranslationYClosed = getMeasuredHeight(); - mTranslationYRange = mTranslationYClosed - mTranslationYOpen; + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + setTranslationShift(mTranslationShift); } public void populateAndShow(ItemInfo itemInfo) { @@ -107,22 +75,21 @@ public class WidgetsBottomSheet extends AbstractFloatingView implements Insettab onWidgetsBound(); - mLauncher.getDragLayer().addView(mGradientBackground); - mGradientBackground.setVisibility(VISIBLE); + mLauncher.getDragLayer().addView(mGradientView); mLauncher.getDragLayer().addView(this); - measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); - setTranslationY(mTranslationYClosed); mIsOpen = false; open(true); } @Override protected void onWidgetsBound() { - List widgets = mLauncher.getWidgetsForPackageUser(new PackageUserKey( - mOriginalItemInfo.getTargetComponent().getPackageName(), mOriginalItemInfo.user)); + List widgets = mLauncher.getPopupDataProvider().getWidgetsForPackageUser( + new PackageUserKey( + mOriginalItemInfo.getTargetComponent().getPackageName(), + mOriginalItemInfo.user)); - ViewGroup widgetRow = (ViewGroup) findViewById(R.id.widgets); - ViewGroup widgetCells = (ViewGroup) widgetRow.findViewById(R.id.widgets_cell_list); + ViewGroup widgetRow = findViewById(R.id.widgets); + ViewGroup widgetCells = widgetRow.findViewById(R.id.widgets_cell_list); widgetCells.removeAllViews(); @@ -168,72 +135,31 @@ public class WidgetsBottomSheet extends AbstractFloatingView implements Insettab return widget; } - @Override - public void onClick(View view) { - mLauncher.getWidgetsView().handleClick(); - } - - @Override - public boolean onLongClick(View view) { - mLauncher.getDragController().addDragListener(this); - return mLauncher.getWidgetsView().handleLongClick(view); - } - private void open(boolean animate) { if (mIsOpen || mOpenCloseAnimator.isRunning()) { return; } mIsOpen = true; - boolean isSheetDark = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark); - mLauncher.getSystemUiController().updateUiState( - SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET, - isSheetDark ? SystemUiController.FLAG_DARK_NAV : SystemUiController.FLAG_LIGHT_NAV); + setupNavBarColor(); if (animate) { - mOpenCloseAnimator.setValues(new PropertyListBuilder() - .translationY(mTranslationYOpen).build()); - mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mSwipeDetector.finishedScrolling(); - } - }); - mOpenCloseAnimator.setInterpolator(mFastOutSlowInInterpolator); + mOpenCloseAnimator.setValues( + PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED)); + mOpenCloseAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); mOpenCloseAnimator.start(); } else { - setTranslationY(mTranslationYOpen); + setTranslationShift(TRANSLATION_SHIFT_OPENED); } } @Override protected void handleClose(boolean animate) { - if (!mIsOpen || mOpenCloseAnimator.isRunning()) { - return; - } - if (animate) { - mOpenCloseAnimator.setValues(new PropertyListBuilder() - .translationY(mTranslationYClosed).build()); - mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mSwipeDetector.finishedScrolling(); - onCloseComplete(); - } - }); - mOpenCloseAnimator.setInterpolator(mSwipeDetector.isIdleState() - ? mFastOutSlowInInterpolator : mScrollInterpolator); - mOpenCloseAnimator.start(); - } else { - setTranslationY(mTranslationYClosed); - onCloseComplete(); - } + handleClose(animate, DEFAULT_CLOSE_DURATION); } - private void onCloseComplete() { - mIsOpen = false; - mLauncher.getDragLayer().removeView(mGradientBackground); - mLauncher.getDragLayer().removeView(WidgetsBottomSheet.this); - mLauncher.getSystemUiController().updateUiState( - SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET, 0); + @Override + protected void onCloseComplete() { + super.onCloseComplete(); + mLauncher.getDragLayer().removeView(mGradientView); } @Override @@ -241,18 +167,6 @@ public class WidgetsBottomSheet extends AbstractFloatingView implements Insettab return (type & TYPE_WIDGETS_BOTTOM_SHEET) != 0; } - @Override - public int getLogContainerType() { - return LauncherLogProto.ContainerType.WIDGETS; // TODO: be more specific - } - - /** - * Returns a {@link WidgetsBottomSheet} which is already open or null - */ - public static WidgetsBottomSheet getOpen(Launcher launcher) { - return getOpenView(launcher, TYPE_WIDGETS_BOTTOM_SHEET); - } - @Override public void setInsets(Rect insets) { // Extend behind left, right, and bottom insets. @@ -264,67 +178,8 @@ public class WidgetsBottomSheet extends AbstractFloatingView implements Insettab getPaddingRight() + rightInset, getPaddingBottom() + bottomInset); } - /* SwipeDetector.Listener */ - @Override - public void onDragStart(boolean start) { - } - - @Override - public boolean onDrag(float displacement, float velocity) { - setTranslationY(Utilities.boundToRange(displacement, mTranslationYOpen, - mTranslationYClosed)); - return true; - } - - @Override - public void setTranslationY(float translationY) { - super.setTranslationY(translationY); - if (mGradientBackground == null) return; - float p = (mTranslationYClosed - translationY) / mTranslationYRange; - boolean showScrim = p <= 0; - mGradientBackground.setProgress(p, showScrim); - } - - @Override - public void onDragEnd(float velocity, boolean fling) { - if ((fling && velocity > 0) || getTranslationY() > (mTranslationYRange) / 2) { - mScrollInterpolator.setVelocityAtZero(velocity); - mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration(velocity, - (mTranslationYClosed - getTranslationY()) / mTranslationYRange)); - close(true); - } else { - mIsOpen = false; - mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration(velocity, - (getTranslationY() - mTranslationYOpen) / mTranslationYRange)); - open(true); - } - } - - @Override - public boolean onControllerTouchEvent(MotionEvent ev) { - return mSwipeDetector.onTouchEvent(ev); - } - - @Override - public boolean onControllerInterceptTouchEvent(MotionEvent ev) { - int directionsToDetectScroll = mSwipeDetector.isIdleState() ? - SwipeDetector.DIRECTION_NEGATIVE : 0; - mSwipeDetector.setDetectableScrollConditions( - directionsToDetectScroll, false); - mSwipeDetector.onTouchEvent(ev); - return mSwipeDetector.isDraggingOrSettling(); - } - - /* DragListener */ - - @Override - public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { - // A widget or custom shortcut was dragged. - close(true); - } - - @Override - public void onDragEnd() { + protected int getElementsRowCount() { + return 1; } } diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java deleted file mode 100644 index acec3dd3b8..0000000000 --- a/src/com/android/launcher3/widget/WidgetsContainerView.java +++ /dev/null @@ -1,262 +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.widget; - -import android.content.Context; -import android.content.pm.LauncherApps; -import android.graphics.Point; -import android.support.v7.widget.LinearLayoutManager; -import android.util.AttributeSet; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; - -import com.android.launcher3.BaseContainerView; -import com.android.launcher3.DeleteDropTarget; -import com.android.launcher3.DragSource; -import com.android.launcher3.DropTarget.DragObject; -import com.android.launcher3.ItemInfo; -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherAppState; -import com.android.launcher3.R; -import com.android.launcher3.Utilities; -import com.android.launcher3.compat.AlphabeticIndexCompat; -import com.android.launcher3.dragndrop.DragOptions; -import com.android.launcher3.folder.Folder; -import com.android.launcher3.model.PackageItemInfo; -import com.android.launcher3.model.WidgetItem; -import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; -import com.android.launcher3.userevent.nano.LauncherLogProto.Target; -import com.android.launcher3.util.MultiHashMap; -import com.android.launcher3.util.PackageUserKey; -import com.android.launcher3.util.Thunk; - -import java.util.List; - -/** - * The widgets list view container. - */ -public class WidgetsContainerView extends BaseContainerView - implements View.OnLongClickListener, View.OnClickListener, DragSource { - private static final String TAG = "WidgetsContainerView"; - private static final boolean LOGD = false; - - /* Global instances that are used inside this container. */ - @Thunk Launcher mLauncher; - - /* Recycler view related member variables */ - private WidgetsRecyclerView mRecyclerView; - private WidgetsListAdapter mAdapter; - - /* Touch handling related member variables. */ - private Toast mWidgetInstructionToast; - - public WidgetsContainerView(Context context) { - this(context, null); - } - - public WidgetsContainerView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public WidgetsContainerView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - mLauncher = Launcher.getLauncher(context); - LauncherAppState apps = LauncherAppState.getInstance(context); - mAdapter = new WidgetsListAdapter(context, LayoutInflater.from(context), - apps.getWidgetCache(), new AlphabeticIndexCompat(context), this, this, - new WidgetsDiffReporter(apps.getIconCache())); - mAdapter.setNotifyListener(); - if (LOGD) { - Log.d(TAG, "WidgetsContainerView constructor"); - } - } - - @Override - public View getTouchDelegateTargetView() { - return mRecyclerView; - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mRecyclerView = (WidgetsRecyclerView) getContentView().findViewById(R.id.widgets_list_view); - mRecyclerView.setAdapter(mAdapter); - mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); - } - - // - // Returns views used for launcher transitions. - // - - public void scrollToTop() { - mRecyclerView.scrollToPosition(0); - } - - // - // Touch related handling. - // - - @Override - public void onClick(View v) { - // When we have exited widget tray or are in transition, disregard clicks - if (!mLauncher.isWidgetsViewVisible() - || mLauncher.getWorkspace().isSwitchingState() - || !(v instanceof WidgetCell)) return; - - handleClick(); - } - - public void handleClick() { - // Let the user know that they have to long press to add a widget - if (mWidgetInstructionToast != null) { - mWidgetInstructionToast.cancel(); - } - - CharSequence msg = Utilities.wrapForTts( - getContext().getText(R.string.long_press_widget_to_add), - getContext().getString(R.string.long_accessible_way_to_add)); - mWidgetInstructionToast = Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT); - mWidgetInstructionToast.show(); - } - - @Override - public boolean onLongClick(View v) { - // When we have exited the widget tray, disregard long clicks - if (!mLauncher.isWidgetsViewVisible()) return false; - return handleLongClick(v); - } - - public boolean handleLongClick(View v) { - if (LOGD) { - Log.d(TAG, String.format("onLongClick [v=%s]", v)); - } - // When we are in transition, disregard long clicks - if (mLauncher.getWorkspace().isSwitchingState()) return false; - // Return if global dragging is not enabled - if (!mLauncher.isDraggingEnabled()) return false; - - return beginDragging(v); - } - - private boolean beginDragging(View v) { - if (v instanceof WidgetCell) { - if (!beginDraggingWidget((WidgetCell) v)) { - return false; - } - } else { - Log.e(TAG, "Unexpected dragging view: " + v); - } - - // We don't enter spring-loaded mode if the drag has been cancelled - if (mLauncher.getDragController().isDragging()) { - // Go into spring loaded mode (must happen before we startDrag()) - mLauncher.enterSpringLoadedDragMode(); - } - - return true; - } - - private boolean beginDraggingWidget(WidgetCell v) { - // Get the widget preview as the drag representation - WidgetImageView image = (WidgetImageView) v.findViewById(R.id.widget_preview); - - // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and - // we abort the drag. - if (image.getBitmap() == null) { - return false; - } - - int[] loc = new int[2]; - mLauncher.getDragLayer().getLocationInDragLayer(image, loc); - - new PendingItemDragHelper(v).startDrag( - image.getBitmapBounds(), image.getBitmap().getWidth(), image.getWidth(), - new Point(loc[0], loc[1]), this, new DragOptions()); - return true; - } - - // - // Drag related handling methods that implement {@link DragSource} interface. - // - - @Override - public boolean supportsAppInfoDropTarget() { - return true; - } - - /* - * Both this method and {@link #supportsFlingToDelete} has to return {@code false} for the - * {@link DeleteDropTarget} to be invisible.) - */ - @Override - public boolean supportsDeleteDropTarget() { - return false; - } - - @Override - public float getIntrinsicIconScaleFactor() { - return 0; - } - - @Override - public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete, - boolean success) { - if (LOGD) { - Log.d(TAG, "onDropCompleted"); - } - if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() && - !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) { - // Exit spring loaded mode if we have not successfully dropped or have not handled the - // drop in Workspace - mLauncher.exitSpringLoadedDragModeDelayed(true, - Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); - } - mLauncher.unlockScreenOrientation(false); - - if (!success) { - d.deferDragViewCleanupPostAnimation = false; - } - } - - /** - * Initialize the widget data model. - */ - public void setWidgets(MultiHashMap model) { - mAdapter.setWidgets(model); - - View loader = getContentView().findViewById(R.id.loader); - if (loader != null) { - ((ViewGroup) getContentView()).removeView(loader); - } - } - - public boolean isEmpty() { - return mAdapter.getItemCount() == 0; - } - - public List getWidgetsForPackageUser(PackageUserKey packageUserKey) { - return mAdapter.copyWidgetsForPackageUser(packageUserKey); - } - - @Override - public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) { - targetParent.containerType = ContainerType.WIDGETS; - } -} \ No newline at end of file diff --git a/src/com/android/launcher3/widget/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/WidgetsDiffReporter.java index 52deec32b0..d67f403655 100644 --- a/src/com/android/launcher3/widget/WidgetsDiffReporter.java +++ b/src/com/android/launcher3/widget/WidgetsDiffReporter.java @@ -16,6 +16,7 @@ package com.android.launcher3.widget; +import android.support.v7.widget.RecyclerView; import android.util.Log; import com.android.launcher3.IconCache; @@ -26,26 +27,18 @@ import java.util.ArrayList; import java.util.Iterator; /** - * Do diff on widget's tray list items and call the {@link NotifyListener} methods accordingly. + * Do diff on widget's tray list items and call the {@link RecyclerView.Adapter} + * methods accordingly. */ public class WidgetsDiffReporter { - private final boolean DEBUG = false; - private final String TAG = "WidgetsDiffReporter"; + private static final boolean DEBUG = false; + private static final String TAG = "WidgetsDiffReporter"; + private final IconCache mIconCache; - private NotifyListener mListener; + private final RecyclerView.Adapter mListener; - public interface NotifyListener { - void notifyDataSetChanged(); - void notifyItemChanged(int index); - void notifyItemInserted(int index); - void notifyItemRemoved(int index); - } - - public WidgetsDiffReporter(IconCache iconCache) { + public WidgetsDiffReporter(IconCache iconCache, RecyclerView.Adapter listener) { mIconCache = iconCache; - } - - public void setListener(NotifyListener listener) { mListener = listener; } @@ -55,9 +48,17 @@ public class WidgetsDiffReporter { Log.d(TAG, "process oldEntries#=" + currentEntries.size() + " newEntries#=" + newEntries.size()); } - if (currentEntries.size() == 0 && newEntries.size() > 0) { - currentEntries.addAll(newEntries); - mListener.notifyDataSetChanged(); + // Early exit if either of the list is empty + if (currentEntries.isEmpty() || newEntries.isEmpty()) { + // Skip if both list are empty. + // On rotation, we open the widget tray with empty. Then try to fetch the list again + // when the animation completes (which still gives empty). And we get the final result + // when the bind actually completes. + if (currentEntries.size() != newEntries.size()) { + currentEntries.clear(); + currentEntries.addAll(newEntries); + mListener.notifyDataSetChanged(); + } return; } ArrayList orgEntries = diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java new file mode 100644 index 0000000000..a40ea1b644 --- /dev/null +++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java @@ -0,0 +1,227 @@ +/* + * 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.widget; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.PropertyValuesHolder; +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.animation.AnimationUtils; + +import com.android.launcher3.Insettable; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener; +import com.android.launcher3.R; + +/** + * Popup for showing the full list of available widgets + */ +public class WidgetsFullSheet extends BaseWidgetSheet + implements Insettable, ProviderChangedListener { + + private static final long DEFAULT_OPEN_DURATION = 267; + private static final long FADE_IN_DURATION = 150; + private static final float VERTICAL_START_POSITION = 0.3f; + + private static final Rect sTempRect = new Rect(); + + private final Rect mInsets = new Rect(); + + private final WidgetsListAdapter mAdapter; + + private View mNavBarScrim; + private WidgetsRecyclerView mRecyclerView; + + public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + LauncherAppState apps = LauncherAppState.getInstance(context); + mAdapter = new WidgetsListAdapter(context, + LayoutInflater.from(context), apps.getWidgetCache(), apps.getIconCache(), + this, this); + } + + public WidgetsFullSheet(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mContent = findViewById(R.id.container); + mNavBarScrim = findViewById(R.id.nav_bar_bg); + + mRecyclerView = findViewById(R.id.widgets_list_view); + mRecyclerView.setAdapter(mAdapter); + mAdapter.setApplyBitmapDeferred(true, mRecyclerView); + + mGradientView = findViewById(R.id.gradient_bg); + mGradientView.setProgress(1, false); + + onWidgetsBound(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mLauncher.getAppWidgetHost().addProviderChangeListener(this); + notifyWidgetProvidersChanged(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mLauncher.getAppWidgetHost().removeProviderChangeListener(this); + } + + @Override + public void setInsets(Rect insets) { + mInsets.set(insets); + + mNavBarScrim.getLayoutParams().height = insets.bottom; + mRecyclerView.setPadding( + mRecyclerView.getPaddingLeft(), mRecyclerView.getPaddingTop(), + mRecyclerView.getPaddingRight(), insets.bottom); + if (insets.bottom > 0) { + setupNavBarColor(); + } + requestLayout(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int widthUsed; + if (mInsets.bottom > 0) { + // If we have bottom insets, we do not show the scrim as it would overlap + // with the navbar scrim + mGradientView.setVisibility(View.INVISIBLE); + widthUsed = 0; + } else { + mLauncher.getDeviceProfile().getWorkspacePadding(sTempRect); + widthUsed = Math.max(sTempRect.left + sTempRect.right, + 2 * (mInsets.left + mInsets.right)); + } + + int heightUsed = mInsets.top + mLauncher.getDeviceProfile().edgeMarginPx; + measureChildWithMargins(mContent, widthMeasureSpec, + widthUsed, heightMeasureSpec, heightUsed); + measureChild(mGradientView, widthMeasureSpec, heightMeasureSpec); + setMeasuredDimension(mGradientView.getMeasuredWidth(), mGradientView.getMeasuredHeight()); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int width = r - l; + int height = b - t; + mGradientView.layout(0, 0, width, height); + + // Content is laid out as center bottom aligned + int contentWidth = mContent.getMeasuredWidth(); + int contentLeft = (width - contentWidth) / 2; + mContent.layout(contentLeft, height - mContent.getMeasuredHeight(), + contentLeft + contentWidth, height); + + setTranslationShift(mTranslationShift); + } + + @Override + public void notifyWidgetProvidersChanged() { + mLauncher.refreshAndBindWidgetsForPackageUser(null); + } + + @Override + protected void onWidgetsBound() { + mAdapter.setWidgets(mLauncher.getPopupDataProvider().getAllWidgets()); + } + + private void open(boolean animate) { + if (mIsOpen) { + return; + } + mIsOpen = true; + if (animate) { + if (mLauncher.getDragLayer().getInsets().bottom > 0) { + mContent.setAlpha(0); + setTranslationShift(VERTICAL_START_POSITION); + } + mOpenCloseAnimator.setValues( + PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED)); + mOpenCloseAnimator + .setDuration(DEFAULT_OPEN_DURATION) + .setInterpolator(AnimationUtils.loadInterpolator( + getContext(), android.R.interpolator.linear_out_slow_in)); + mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRecyclerView.setLayoutFrozen(false); + mAdapter.setApplyBitmapDeferred(false, mRecyclerView); + mOpenCloseAnimator.removeListener(this); + } + }); + post(new Runnable() { + @Override + public void run() { + mRecyclerView.setLayoutFrozen(true); + mOpenCloseAnimator.start(); + mContent.animate().alpha(1).setDuration(FADE_IN_DURATION); + } + }); + } else { + setTranslationShift(TRANSLATION_SHIFT_OPENED); + mAdapter.setApplyBitmapDeferred(false, mRecyclerView); + } + } + + @Override + protected void handleClose(boolean animate) { + handleClose(animate, DEFAULT_OPEN_DURATION); + } + + @Override + protected boolean isOfType(int type) { + return (type & TYPE_WIDGETS_FULL_SHEET) != 0; + } + + @Override + public boolean onControllerInterceptTouchEvent(MotionEvent ev) { + // Disable swipe down when recycler view is scrolling + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + mNoIntercept = false; + if (mLauncher.getDragLayer().isEventOverView(mContent, ev)) { + mNoIntercept = !mRecyclerView.shouldContainerScroll(ev, mLauncher.getDragLayer()); + } + } + return super.onControllerInterceptTouchEvent(ev); + } + + public static WidgetsFullSheet show(Launcher launcher, boolean animate) { + WidgetsFullSheet sheet = (WidgetsFullSheet) launcher.getLayoutInflater() + .inflate(R.layout.widgets_full_sheet, launcher.getDragLayer(), false); + launcher.getDragLayer().addView(sheet); + sheet.open(animate); + return sheet; + } + + @Override + protected int getElementsRowCount() { + return mAdapter.getItemCount(); + } +} diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java index 6b1800c676..0147ea4278 100644 --- a/src/com/android/launcher3/widget/WidgetsListAdapter.java +++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java @@ -25,13 +25,11 @@ import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.view.ViewGroup; +import com.android.launcher3.IconCache; import com.android.launcher3.R; import com.android.launcher3.WidgetPreviewLoader; -import com.android.launcher3.compat.AlphabeticIndexCompat; -import com.android.launcher3.model.PackageItemInfo; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.util.LabelComparator; -import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.PackageUserKey; import java.util.ArrayList; @@ -39,7 +37,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; -import java.util.Map; /** * List view adapter for the widget tray. @@ -56,7 +53,6 @@ public class WidgetsListAdapter extends Adapter { private final WidgetPreviewLoader mWidgetPreviewLoader; private final LayoutInflater mLayoutInflater; - private final AlphabeticIndexCompat mIndexer; private final OnClickListener mIconClickListener; private final OnLongClickListener mIconLongClickListener; @@ -64,56 +60,43 @@ public class WidgetsListAdapter extends Adapter { private ArrayList mEntries = new ArrayList<>(); private final WidgetsDiffReporter mDiffReporter; + private boolean mApplyBitmapDeferred; + public WidgetsListAdapter(Context context, LayoutInflater layoutInflater, - WidgetPreviewLoader widgetPreviewLoader, AlphabeticIndexCompat indexCompat, - OnClickListener iconClickListener, OnLongClickListener iconLongClickListener, - WidgetsDiffReporter diffReporter) { + WidgetPreviewLoader widgetPreviewLoader, IconCache iconCache, + OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) { mLayoutInflater = layoutInflater; mWidgetPreviewLoader = widgetPreviewLoader; - mIndexer = indexCompat; mIconClickListener = iconClickListener; mIconLongClickListener = iconLongClickListener; mIndent = context.getResources().getDimensionPixelSize(R.dimen.widget_section_indent); - mDiffReporter = diffReporter; + mDiffReporter = new WidgetsDiffReporter(iconCache, this); } - public void setNotifyListener() { - mDiffReporter.setListener(new WidgetsDiffReporter.NotifyListener() { - @Override - public void notifyDataSetChanged() { - WidgetsListAdapter.this.notifyDataSetChanged(); - } + /** + * Defers applying bitmap on all the {@link WidgetCell} in the {@param rv} + * + * @see WidgetCell#setApplyBitmapDeferred(boolean) + */ + public void setApplyBitmapDeferred(boolean isDeferred, RecyclerView rv) { + mApplyBitmapDeferred = isDeferred; - @Override - public void notifyItemChanged(int index) { - WidgetsListAdapter.this.notifyItemChanged(index); + for (int i = rv.getChildCount() - 1; i >= 0; i--) { + WidgetsRowViewHolder holder = (WidgetsRowViewHolder) + rv.getChildViewHolder(rv.getChildAt(i)); + for (int j = holder.cellContainer.getChildCount() - 1; j >= 0; j--) { + View v = holder.cellContainer.getChildAt(j); + if (v instanceof WidgetCell) { + ((WidgetCell) v).setApplyBitmapDeferred(mApplyBitmapDeferred); + } } - - @Override - public void notifyItemInserted(int index) { - WidgetsListAdapter.this.notifyItemInserted(index); - } - - @Override - public void notifyItemRemoved(int index) { - WidgetsListAdapter.this.notifyItemRemoved(index); - } - }); + } } /** * Update the widget list. */ - public void setWidgets(MultiHashMap widgets) { - ArrayList tempEntries = new ArrayList<>(); - - WidgetItemComparator widgetComparator = new WidgetItemComparator(); - for (Map.Entry> entry : widgets.entrySet()) { - WidgetListRowEntry row = new WidgetListRowEntry(entry.getKey(), entry.getValue()); - row.titleSectionName = mIndexer.computeSectionName(row.pkgItem.title); - Collections.sort(row.widgets, widgetComparator); - tempEntries.add(row); - } + public void setWidgets(ArrayList tempEntries) { WidgetListRowEntryComparator rowComparator = new WidgetListRowEntryComparator(); Collections.sort(tempEntries, rowComparator); mDiffReporter.process(mEntries, tempEntries, rowComparator); @@ -128,26 +111,6 @@ public class WidgetsListAdapter extends Adapter { return mEntries.get(pos).titleSectionName; } - /** - * Copies and returns the widgets associated with the package and user of the ComponentKey. - */ - public List copyWidgetsForPackageUser(PackageUserKey packageUserKey) { - for (WidgetListRowEntry entry : mEntries) { - if (entry.pkgItem.packageName.equals(packageUserKey.mPackageName)) { - ArrayList widgets = new ArrayList<>(entry.widgets); - // Remove widgets not associated with the correct user. - Iterator iterator = widgets.iterator(); - while (iterator.hasNext()) { - if (!iterator.next().user.equals(packageUserKey.mUser)) { - iterator.remove(); - } - } - return widgets.isEmpty() ? null : widgets; - } - } - return null; - } - @Override public void onBindViewHolder(WidgetsRowViewHolder holder, int pos) { WidgetListRowEntry entry = mEntries.get(pos); @@ -194,6 +157,7 @@ public class WidgetsListAdapter extends Adapter { for (int i=0; i < infoList.size(); i++) { WidgetCell widget = (WidgetCell) row.getChildAt(2*i); widget.applyFromCellItem(infoList.get(i), mWidgetPreviewLoader); + widget.setApplyBitmapDeferred(mApplyBitmapDeferred); widget.ensurePreview(); widget.setVisibility(View.VISIBLE); @@ -253,5 +217,4 @@ public class WidgetsListAdapter extends Adapter { return mComparator.compare(a.pkgItem.title.toString(), b.pkgItem.title.toString()); } } - } diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java index 9730a82aad..89c88a4e7f 100644 --- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java +++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java @@ -17,12 +17,12 @@ package com.android.launcher3.widget; import android.content.Context; -import android.graphics.Color; import android.support.v7.widget.LinearLayoutManager; import android.util.AttributeSet; import android.view.View; import com.android.launcher3.BaseRecyclerView; +import com.android.launcher3.R; /** * The widgets recycler view. @@ -32,6 +32,8 @@ public class WidgetsRecyclerView extends BaseRecyclerView { private static final String TAG = "WidgetsRecyclerView"; private WidgetsListAdapter mAdapter; + private final int mScrollbarTop; + public WidgetsRecyclerView(Context context) { this(context, null); } @@ -43,6 +45,7 @@ public class WidgetsRecyclerView extends BaseRecyclerView { public WidgetsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { // API 21 and below only support 3 parameter ctor. super(context, attrs, defStyleAttr); + mScrollbarTop = getResources().getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin); } public WidgetsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr, @@ -130,13 +133,16 @@ public class WidgetsRecyclerView extends BaseRecyclerView { @Override protected int getAvailableScrollHeight() { View child = getChildAt(0); - int height = child.getMeasuredHeight() * mAdapter.getItemCount(); - int totalHeight = getPaddingTop() + height + getPaddingBottom(); - int availableScrollHeight = totalHeight - getScrollbarTrackHeight(); - return availableScrollHeight; + return child.getMeasuredHeight() * mAdapter.getItemCount() - getScrollbarTrackHeight() + - mScrollbarTop; } private boolean isModelNotReady() { return mAdapter.getItemCount() == 0; } + + @Override + public int getScrollBarTop() { + return mScrollbarTop; + } } \ No newline at end of file diff --git a/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java new file mode 100644 index 0000000000..1086987f83 --- /dev/null +++ b/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java @@ -0,0 +1,103 @@ +/* + * 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.widget.custom; + +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.launcher3.LauncherAppWidgetProviderInfo; +import com.android.launcher3.Utilities; + +/** + * Custom app widget provider info that can be used as a widget, but provide extra functionality + * by allowing custom code and views. + */ +public class CustomAppWidgetProviderInfo extends LauncherAppWidgetProviderInfo + implements Parcelable { + + public final int providerId; + + protected CustomAppWidgetProviderInfo(Parcel parcel, boolean readSelf, int providerId) { + super(parcel); + if (readSelf) { + this.providerId = parcel.readInt(); + + provider = new ComponentName(parcel.readString(), CLS_CUSTOM_WIDGET_PREFIX + providerId); + + label = parcel.readString(); + initialLayout = parcel.readInt(); + icon = parcel.readInt(); + previewImage = parcel.readInt(); + + resizeMode = parcel.readInt(); + spanX = parcel.readInt(); + spanY = parcel.readInt(); + minSpanX = parcel.readInt(); + minSpanY = parcel.readInt(); + } else { + this.providerId = providerId; + } + } + + @Override + public void initSpans(Context context) { } + + @Override + public String getLabel(PackageManager packageManager) { + return Utilities.trim(label); + } + + @Override + public String toString() { + return "WidgetProviderInfo(" + provider + ")"; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeInt(providerId); + out.writeString(provider.getPackageName()); + + out.writeString(label); + out.writeInt(initialLayout); + out.writeInt(icon); + out.writeInt(previewImage); + + out.writeInt(resizeMode); + out.writeInt(spanX); + out.writeInt(spanY); + out.writeInt(minSpanX); + out.writeInt(minSpanY); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + + @Override + public CustomAppWidgetProviderInfo createFromParcel(Parcel parcel) { + return new CustomAppWidgetProviderInfo(parcel, true, 0); + } + + @Override + public CustomAppWidgetProviderInfo[] newArray(int size) { + return new CustomAppWidgetProviderInfo[size]; + } + }; +} diff --git a/src/com/android/launcher3/widget/custom/CustomWidgetParser.java b/src/com/android/launcher3/widget/custom/CustomWidgetParser.java new file mode 100644 index 0000000000..00720c4496 --- /dev/null +++ b/src/com/android/launcher3/widget/custom/CustomWidgetParser.java @@ -0,0 +1,142 @@ +/* + * 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.widget.custom; + +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProviderInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.os.Parcel; +import android.os.Process; +import android.util.SparseArray; +import android.util.Xml; + +import com.android.launcher3.LauncherAppWidgetInfo; +import com.android.launcher3.LauncherAppWidgetProviderInfo; +import com.android.launcher3.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static com.android.launcher3.LauncherAppWidgetProviderInfo.CLS_CUSTOM_WIDGET_PREFIX; + +/** + * Utility class to parse {@ink CustomAppWidgetProviderInfo} definitions from xml + */ +public class CustomWidgetParser { + + private static List sCustomWidgets; + private static SparseArray sWidgetsIdMap; + + public static List getCustomWidgets(Context context) { + if (sCustomWidgets == null) { + // Synchronization not needed as it it safe to load multiple times + parseCustomWidgets(context); + } + + return sCustomWidgets; + } + + public static int getWidgetIdForCustomProvider(Context context, ComponentName provider) { + if (sWidgetsIdMap == null) { + parseCustomWidgets(context); + } + int index = sWidgetsIdMap.indexOfValue(provider); + if (index >= 0) { + return LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - sWidgetsIdMap.keyAt(index); + } else { + return AppWidgetManager.INVALID_APPWIDGET_ID; + } + } + + public static LauncherAppWidgetProviderInfo getWidgetProvider(Context context, int widgetId) { + if (sWidgetsIdMap == null || sCustomWidgets == null) { + parseCustomWidgets(context); + } + ComponentName cn = sWidgetsIdMap.get(LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - widgetId); + for (LauncherAppWidgetProviderInfo info : sCustomWidgets) { + if (info.provider.equals(cn)) { + return info; + } + } + return null; + } + + private static void parseCustomWidgets(Context context) { + ArrayList widgets = new ArrayList<>(); + SparseArray idMap = new SparseArray<>(); + + List providers = AppWidgetManager.getInstance(context) + .getInstalledProvidersForProfile(Process.myUserHandle()); + if (providers.isEmpty()) { + sCustomWidgets = widgets; + sWidgetsIdMap = idMap; + return; + } + + Parcel parcel = Parcel.obtain(); + providers.get(0).writeToParcel(parcel, 0); + + try (XmlResourceParser parser = context.getResources().getXml(R.xml.custom_widgets)) { + final int depth = parser.getDepth(); + int type; + + while (((type = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { + if ((type == XmlPullParser.START_TAG) && "widget".equals(parser.getName())) { + TypedArray a = context.obtainStyledAttributes( + Xml.asAttributeSet(parser), R.styleable.CustomAppWidgetProviderInfo); + + parcel.setDataPosition(0); + CustomAppWidgetProviderInfo info = newInfo(a, parcel, context); + widgets.add(info); + a.recycle(); + + idMap.put(info.providerId, info.provider); + } + } + } catch (IOException | XmlPullParserException e) { + throw new RuntimeException(e); + } + parcel.recycle(); + sCustomWidgets = widgets; + sWidgetsIdMap = idMap; + } + + private static CustomAppWidgetProviderInfo newInfo(TypedArray a, Parcel parcel, Context context) { + int providerId = a.getInt(R.styleable.CustomAppWidgetProviderInfo_providerId, 0); + CustomAppWidgetProviderInfo info = new CustomAppWidgetProviderInfo(parcel, false, providerId); + info.provider = new ComponentName(context.getPackageName(), CLS_CUSTOM_WIDGET_PREFIX + providerId); + + info.label = a.getString(R.styleable.CustomAppWidgetProviderInfo_android_label); + info.initialLayout = a.getResourceId(R.styleable.CustomAppWidgetProviderInfo_android_initialLayout, 0); + info.icon = a.getResourceId(R.styleable.CustomAppWidgetProviderInfo_android_icon, 0); + info.previewImage = a.getResourceId(R.styleable.CustomAppWidgetProviderInfo_android_previewImage, 0); + info.resizeMode = a.getInt(R.styleable.CustomAppWidgetProviderInfo_android_resizeMode, 0); + + info.spanX = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numColumns, 1); + info.spanY = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numRows, 1); + info.minSpanX = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numMinColumns, 1); + info.minSpanY = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numMinRows, 1); + return info; + } +} diff --git a/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewAccessibilityDelegate.java similarity index 88% rename from src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java rename to src_ui_overrides/com/android/launcher3/uioverrides/OverviewAccessibilityDelegate.java index 29dd95c1a4..88a1e10d84 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; 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 @@ public class OverviewAccessibilityDelegate extends AccessibilityDelegate { @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 0000000000..1fb56e74fe --- /dev/null +++ b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java @@ -0,0 +1,170 @@ +/* + * 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.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.util.Log; +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.R; +import com.android.launcher3.Utilities; +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 { + + // 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)); + } + + + public static int getButtonBarHeight(Launcher launcher) { + int zoneHeight = (int) (OVERVIEW_ICON_ZONE_RATIO * + launcher.getDeviceProfile().availableWidthPx); + 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 0000000000..9e2ad982f4 --- /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 + public static final float SCALE_FACTOR = 0.7f; + + private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_MULTI_PAGE | FLAG_HIDE_HOTSEAT; + + 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 0000000000..40bf057286 --- /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 0000000000..6776150c9a --- /dev/null +++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java @@ -0,0 +1,35 @@ +/* + * 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.view.View.AccessibilityDelegate; + +import com.android.launcher3.Launcher; +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(); + } +} diff --git a/tests/src/com/android/launcher3/logging/FileLogTest.java b/tests/src/com/android/launcher3/logging/FileLogTest.java index 7048c2868c..9c7cb8f61c 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 @@ public class FileLogTest extends AndroidTestCase { 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 82f34e43de..401711d072 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.Context; 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.LauncherSettings; 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 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase { private ArrayList newScreens; private LongArrayMap screenOccupancy; - @Override - protected void setUp() throws Exception { - super.setUp(); + @Before + public void initData() throws Exception { existingScreens = new ArrayList<>(); screenOccupancy = new LongArrayMap<>(); newScreens = new ArrayList<>(); @@ -55,13 +59,14 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase { for (ItemInfo item : items) { list.add(Pair.create(item, null)); } - return new AddWorkspaceItemsTask(Provider.of(list)) { + return new AddWorkspaceItemsTask(list) { @Override protected void updateScreens(Context context, ArrayList workspaceScreens) { } }; } + @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 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase { .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 oldScreens = new ArrayList<>(existingScreens); Pair spaceFound = newTask() .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 3, 3); @@ -97,6 +101,7 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase { 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 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase { 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 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase { setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3)); commitScreensToDb(); - when(appState.getContext()).thenReturn(getMockContext()); - executeTaskForTest(newTask(info, info2)).get(0).run(); ArgumentCaptor notAnimated = ArgumentCaptor.forClass(ArrayList.class); ArgumentCaptor animated = ArgumentCaptor.forClass(ArrayList.class); @@ -168,7 +170,7 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase { } 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 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase { 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 3d03507eab..bbb6772d3f 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.Process; 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.ComponentKey; 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 @@ import static org.mockito.Mockito.when; /** * Base class for writing tests for Model update tasks. */ -public class BaseModelUpdateTaskTestCase extends ProviderTestCase2 { +public class BaseModelUpdateTaskTestCase { + + @Rule + public ProviderTestRule mProviderRule = + new ProviderTestRule.Builder(TestLauncherProvider.class, LauncherProvider.AUTHORITY) + .build(); public final HashMap> fieldCache = new HashMap<>(); @@ -63,14 +73,8 @@ public class BaseModelUpdateTaskTestCase extends ProviderTestCase2(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 @@ public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase { } } + @Test public void testSessionUpdate_ignores_normal_apps() throws Exception { executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app1")); @@ -59,6 +72,7 @@ public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase { 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 fd62d36440..b92f61205c 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.config.FeatureFlags; 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 { +@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 @@ public class GridSizeMigrationTaskTest extends ProviderTestCase2 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 @@ public class GridSizeMigrationTaskTest extends ProviderTestCase2 allScreens = LauncherModel.loadWorkspaceScreensDb(getMockContext()); + ArrayList allScreens = LauncherModel.loadWorkspaceScreensDb(mContext); assertEquals(ids.length, allScreens.size()); int total = 0; @@ -330,7 +361,8 @@ public class GridSizeMigrationTaskTest extends ProviderTestCase2 generateSampleMap(int num) { - MultiHashMap newMap = new MultiHashMap(); - if (num <= 0) return newMap; + private ArrayList generateSampleMap(int num) { + ArrayList result = new ArrayList<>(); + if (num <= 0) return result; + MultiHashMap newMap = new MultiHashMap(); PackageManager pm = mContext.getPackageManager(); AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(mContext); for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(null)) { @@ -144,6 +143,10 @@ public class WidgetsListAdapterTest { break; } } - return newMap; + for (Map.Entry> entry : newMap.entrySet()) { + result.add(new WidgetListRowEntry(entry.getKey(), entry.getValue())); + } + + return result; } }