merged ub-launcher3-dorval, and resolved conflicts
Bug: 36904684
Bug: 37929893
Bug: 36068989
Test: make -j 32 dist checkbuild
Change-Id: If9b11b212852cb1048d54db2224dab4acf2d93e0
diff --git a/Android.mk b/Android.mk
index ad8ce02..713d082 100644
--- a/Android.mk
+++ b/Android.mk
@@ -35,12 +35,14 @@
LOCAL_RESOURCE_DIR := \
$(LOCAL_PATH)/res \
- frameworks/support/v7/recyclerview/res
+ prebuilts/sdk/current/support/v7/recyclerview/res \
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
LOCAL_PROTOC_OPTIMIZE_TYPE := nano
LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/
+LOCAL_PROTO_JAVA_OUTPUT_PARAMS := enum_style=java
+
LOCAL_AAPT_FLAGS := \
--auto-add-overlay \
--extra-packages android.support.v7.recyclerview \
@@ -48,6 +50,7 @@
LOCAL_SDK_VERSION := current
LOCAL_MIN_SDK_VERSION := 21
LOCAL_PACKAGE_NAME := Launcher3
+LOCAL_PRIVILEGED_MODULE := true
LOCAL_OVERRIDES_PACKAGES := Home Launcher2
LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/AndroidManifest-common.xml
@@ -65,6 +68,7 @@
LOCAL_PROTOC_OPTIMIZE_TYPE := nano
LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/
+LOCAL_PROTO_JAVA_OUTPUT_PARAMS := enum_style=java
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := launcher_proto_lib
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index bbe1f4a..3a60a98 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -49,7 +49,7 @@
android:fullBackupOnly="true"
android:fullBackupContent="@xml/backupscheme"
android:hardwareAccelerated="true"
- android:icon="@mipmap/ic_launcher_home"
+ android:icon="@drawable/ic_launcher_home"
android:label="@string/derived_app_name"
android:largeHeap="@bool/config_largeHeap"
android:restoreAnyVersion="true"
@@ -58,12 +58,21 @@
<!-- Intent received used to install shortcuts from other applications -->
<receiver
android:name="com.android.launcher3.InstallShortcutReceiver"
- android:permission="com.android.launcher.permission.INSTALL_SHORTCUT">
+ android:permission="com.android.launcher.permission.INSTALL_SHORTCUT"
+ android:enabled="@bool/enable_install_shortcut_api" >
<intent-filter>
<action android:name="com.android.launcher.action.INSTALL_SHORTCUT" />
</intent-filter>
</receiver>
+ <!-- Intent received when a session is committed -->
+ <receiver
+ android:name="com.android.launcher3.SessionCommitReceiver" >
+ <intent-filter>
+ <action android:name="android.content.pm.action.SESSION_COMMITTED" />
+ </intent-filter>
+ </receiver>
+
<!-- Intent received used to initialize a restored widget -->
<receiver android:name="com.android.launcher3.AppWidgetsRestoredReceiver" >
<intent-filter>
@@ -76,8 +85,35 @@
android:process=":wallpaper_chooser">
</service>
+ <service android:name="com.android.launcher3.notification.NotificationListener"
+ android:enabled="@bool/notification_badging_enabled"
+ android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.notification.NotificationListenerService" />
+ </intent-filter>
+ </service>
+
<meta-data android:name="android.nfc.disable_beam_default"
android:value="true" />
+ <activity android:name="com.android.launcher3.dragndrop.AddItemActivity"
+ android:theme="@android:style/Theme.DeviceDefault.Light.Dialog.Alert"
+ android:excludeFromRecents="true"
+ android:autoRemoveFromRecents="true"
+ android:label="@string/action_add_to_workspace" >
+ <intent-filter>
+ <action android:name="android.content.pm.action.CONFIRM_PIN_SHORTCUT" />
+ <action android:name="android.content.pm.action.CONFIRM_PIN_APPWIDGET" />
+ </intent-filter>
+ </activity>
+
+ <!--
+ Should point to the content provider which can be used to dump Launcher3 compatible
+ worspace configuration to the dump's file descriptor by using launcher_dump.proto
+ -->
+ <meta-data
+ android:name="com.android.launcher3.launcher_dump_provider"
+ android:value="com.android.launcher3.LauncherProvider" />
+
</application>
</manifest>
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6c5990d..bcb522b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -54,8 +54,9 @@
android:fullBackupOnly="true"
android:fullBackupContent="@xml/backupscheme"
android:hardwareAccelerated="true"
- android:icon="@mipmap/ic_launcher_home"
+ android:icon="@drawable/ic_launcher_home"
android:label="@string/derived_app_name"
+ android:theme="@style/LauncherTheme"
android:largeHeap="@bool/config_largeHeap"
android:restoreAnyVersion="true"
android:supportsRtl="true" >
@@ -69,10 +70,10 @@
android:launchMode="singleTask"
android:clearTaskOnLaunch="true"
android:stateNotNeeded="true"
- android:theme="@style/LauncherTheme"
- android:windowSoftInputMode="adjustPan"
+ android:windowSoftInputMode="adjustPan|stateUnchanged"
android:screenOrientation="nosensor"
android:configChanges="keyboard|keyboardHidden|navigation"
+ android:resizeableActivity="true"
android:resumeWhilePausing="true"
android:taskAffinity=""
android:enabled="true">
@@ -90,6 +91,7 @@
<activity
android:name="com.android.launcher3.SettingsActivity"
android:label="@string/settings_button_text"
+ android:theme="@android:style/Theme.DeviceDefault.Settings"
android:autoRemoveFromRecents="true">
<intent-filter>
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
@@ -133,7 +135,7 @@
android:theme="@android:style/Theme.NoDisplay"
android:label="* HPROF"
android:excludeFromRecents="true"
- android:icon="@mipmap/ic_launcher_home"
+ android:icon="@drawable/ic_launcher_home"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -145,7 +147,7 @@
<activity
android:name="com.android.launcher3.testing.ToggleWeightWatcher"
android:label="Show Mem"
- android:icon="@mipmap/ic_launcher_home">
+ android:icon="@drawable/ic_launcher_home">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
diff --git a/build.gradle b/build.gradle
index e103d79..9c71693 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,9 +1,10 @@
buildscript {
repositories {
mavenCentral()
+ jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.1.3'
+ classpath 'com.android.tools.build:gradle:2.3.1'
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.0'
}
}
@@ -12,12 +13,12 @@
apply plugin: 'com.google.protobuf'
android {
- compileSdkVersion 25
- buildToolsVersion '24.0.0'
+ compileSdkVersion 26
+ buildToolsVersion '26.0.0'
defaultConfig {
minSdkVersion 21
- targetSdkVersion 25
+ targetSdkVersion 26
versionCode 1
versionName "1.0"
@@ -45,29 +46,39 @@
androidTest {
java.srcDirs = ['tests/src']
- manifest.srcFile "tests/AndroidManifest.xml"
+ res.srcDirs = ['tests/res']
+ manifest.srcFile "tests/AndroidManifest-common.xml"
}
aosp {
manifest.srcFile "AndroidManifest.xml"
}
+
+ aospAndroidTest {
+ manifest.srcFile "tests/AndroidManifest.xml"
+ }
}
}
repositories {
mavenCentral()
+ jcenter()
}
+final String SUPPORT_LIBS_VERSION = '26.0.0-SNAPSHOT'
dependencies {
- compile 'com.android.support:support-v4:23.1.1'
- compile 'com.android.support:recyclerview-v7:23.1.1'
- compile 'com.android.support:palette-v7:23.2.0'
+ compile "com.android.support:support-v4:${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-2'
testCompile 'junit:junit:4.12'
+ androidTestCompile "org.mockito:mockito-core:1.+"
+ 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.uiautomator:uiautomator-v18:2.1.2'
- androidTestCompile 'com.android.support:support-annotations:23.2.0'
+ androidTestCompile "com.android.support:support-annotations:${SUPPORT_LIBS_VERSION}"
}
protobuf {
@@ -80,7 +91,8 @@
task.builtins {
remove java
javanano {
- option 'ignore_services=false'
+ option "java_package=launcher_log.proto|com.android.launcher3.userevent.nano"
+ option "enum_style=java"
}
}
}
diff --git a/proguard.flags b/proguard.flags
index c5e9db1..6cbab08 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -82,3 +82,15 @@
*;
}
+# 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_dump.proto b/protos/launcher_dump.proto
new file mode 100644
index 0000000..dc8fbda
--- /dev/null
+++ b/protos/launcher_dump.proto
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+syntax = "proto2";
+
+option java_package = "com.android.launcher3.model";
+option java_outer_classname = "LauncherDumpProto";
+
+package model;
+
+message DumpTarget {
+ enum Type {
+ NONE = 0;
+ ITEM = 1;
+ CONTAINER = 2;
+ }
+
+ optional Type type = 1;
+ optional int32 page_id = 2;
+ optional int32 grid_x = 3;
+ optional int32 grid_y = 4;
+
+ // For container types only
+ optional ContainerType container_type = 5;
+
+ // For item types only
+ optional ItemType item_type = 6;
+
+ optional string package_name = 7; // All ItemTypes except UNKNOWN type
+ optional string component = 8; // All ItemTypes except UNKNOWN type
+ optional string item_id = 9; // For Pinned Shortcuts and appWidgetId
+
+ optional int32 span_x = 10 [default = 1];// Used for ItemType.WIDGET
+ optional int32 span_y = 11 [default = 1];// Used for ItemType.WIDGET
+ optional UserType user_type = 12;
+}
+
+// Used to define what type of item a Target would represent.
+enum ItemType {
+ UNKNOWN_ITEMTYPE = 0; // Launcher specific items
+ APP_ICON = 1; // Regular app icons
+ WIDGET = 2; // Elements from AppWidgetManager
+ SHORTCUT = 3; // ShortcutManager
+}
+
+// Used to define what type of container a Target would represent.
+enum ContainerType {
+ UNKNOWN_CONTAINERTYPE = 0;
+ WORKSPACE = 1;
+ HOTSEAT = 2;
+ FOLDER = 3;
+}
+
+// Used to define what type of control a Target would represent.
+enum UserType {
+ DEFAULT = 0;
+ WORK = 1;
+}
+
+// Main message;
+message LauncherImpression {
+ repeated DumpTarget targets = 1;
+}
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 6b27559..909a429 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -63,6 +63,8 @@
FOLDER_ICON = 4;
DEEPSHORTCUT = 5;
SEARCHBOX = 6;
+ EDITTEXT = 7;
+ NOTIFICATION = 8;
}
// Used to define what type of container a Target would represent.
@@ -77,6 +79,7 @@
PREDICTION = 7;
SEARCHRESULT = 8;
DEEPSHORTCUTS = 9;
+ PINITEM = 10; // confirmation screen
}
// Used to define what type of control a Target would represent.
@@ -91,7 +94,9 @@
APPINFO_TARGET = 7;
RESIZE_HANDLE = 8;
VERTICAL_SCROLL = 9;
- // HOME, BACK, GO_TO_PLAYSTORE
+ HOME_INTENT = 10; // Deprecated, use enum Command instead
+ BACK_BUTTON = 11; // Deprecated, use enum Command instead
+ // GO_TO_PLAYSTORE
}
// Used to define the action component of the LauncherEvent.
@@ -99,6 +104,7 @@
enum Type {
TOUCH = 0;
AUTOMATED = 1;
+ COMMAND = 2;
// SOFT_KEYBOARD, HARD_KEYBOARD, ASSIST
}
enum Touch {
@@ -116,9 +122,20 @@
LEFT = 3;
RIGHT = 4;
}
+ enum Command {
+ HOME_INTENT = 0;
+ BACK = 1;
+ ENTRY = 2; // Indicates entry to one of Launcher container type target
+ // not using the HOME_INTENT
+ CANCEL = 3; // Indicates that a confirmation screen was cancelled
+ CONFIRM = 4; // Indicates thata confirmation screen was accepted
+ }
optional Type type = 1;
optional Touch touch = 2;
optional Direction dir = 3;
+ optional Command command = 4;
+ // Log if the action was performed on outside of the container
+ optional bool is_outside = 5;
}
//
@@ -137,4 +154,6 @@
optional int64 action_duration_millis = 4;
optional int64 elapsed_container_millis = 5;
optional int64 elapsed_session_millis = 6;
+
+ optional bool is_in_multi_window_mode = 7;
}
diff --git a/res/animator/all_apps_fastscroll_icon_anim.xml b/res/animator/all_apps_fastscroll_icon_anim.xml
new file mode 100644
index 0000000..380b009
--- /dev/null
+++ b/res/animator/all_apps_fastscroll_icon_anim.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2017, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_activated="true">
+ <set>
+ <objectAnimator
+ android:duration="225"
+ android:propertyName="scaleX"
+ android:valueTo="1.15"
+ android:valueType="floatType" />
+ <objectAnimator
+ android:duration="225"
+ android:propertyName="scaleY"
+ android:valueTo="1.15"
+ android:valueType="floatType" />
+ </set>
+ </item>
+
+ <item>
+ <set>
+ <objectAnimator
+ android:duration="275"
+ android:propertyName="scaleX"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ <objectAnimator
+ android:duration="275"
+ android:propertyName="scaleY"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </item>
+
+</selector>
\ No newline at end of file
diff --git a/res/animator-v21/overview_button_anim.xml b/res/animator/overview_button_anim.xml
similarity index 81%
rename from res/animator-v21/overview_button_anim.xml
rename to res/animator/overview_button_anim.xml
index aac3d26..585f165 100644
--- a/res/animator-v21/overview_button_anim.xml
+++ b/res/animator/overview_button_anim.xml
@@ -24,13 +24,6 @@
android:valueType="floatType" />
</item>
- <item android:state_focused="true">
- <objectAnimator
- android:duration="@android:integer/config_shortAnimTime"
- android:propertyName="alpha"
- android:valueTo="0.5"
- android:valueType="floatType" />
- </item>
<item>
<objectAnimator
android:duration="@android:integer/config_shortAnimTime"
diff --git a/res/drawable-hdpi/ic_all_apps_bg_hand.png b/res/drawable-hdpi/ic_all_apps_bg_hand.png
index dff2f54..437fd37 100644
--- a/res/drawable-hdpi/ic_all_apps_bg_hand.png
+++ b/res/drawable-hdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-hdpi/page_hover_left.9.png b/res/drawable-hdpi/page_hover_left.9.png
deleted file mode 100644
index 3f11d0b..0000000
--- a/res/drawable-hdpi/page_hover_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/page_hover_left_active.9.png b/res/drawable-hdpi/page_hover_left_active.9.png
deleted file mode 100644
index abe4c31..0000000
--- a/res/drawable-hdpi/page_hover_left_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/page_hover_right.9.png b/res/drawable-hdpi/page_hover_right.9.png
deleted file mode 100644
index 3bcf191..0000000
--- a/res/drawable-hdpi/page_hover_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/page_hover_right_active.9.png b/res/drawable-hdpi/page_hover_right_active.9.png
deleted file mode 100644
index 101e4dc..0000000
--- a/res/drawable-hdpi/page_hover_right_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/quantum_panel_bitmap.9.png b/res/drawable-hdpi/quantum_panel_bitmap.9.png
deleted file mode 100644
index d2aee73..0000000
--- a/res/drawable-hdpi/quantum_panel_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-hdpi/quantum_panel_dark_bitmap.9.png
deleted file mode 100644
index 78345b8..0000000
--- a/res/drawable-hdpi/quantum_panel_dark_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/screenpanel.9.png b/res/drawable-hdpi/screenpanel.9.png
deleted file mode 100644
index 5bccd33..0000000
--- a/res/drawable-hdpi/screenpanel.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/screenpanel_hover.9.png b/res/drawable-hdpi/screenpanel_hover.9.png
deleted file mode 100644
index f6b8c62..0000000
--- a/res/drawable-hdpi/screenpanel_hover.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/virtual_preload.9.png b/res/drawable-hdpi/virtual_preload.9.png
deleted file mode 100644
index 670088f..0000000
--- a/res/drawable-hdpi/virtual_preload.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/virtual_preload_folder.9.png b/res/drawable-hdpi/virtual_preload_folder.9.png
deleted file mode 100644
index 68e2afe..0000000
--- a/res/drawable-hdpi/virtual_preload_folder.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/widget_tile.png b/res/drawable-hdpi/widget_tile.png
deleted file mode 100644
index 572bf6f..0000000
--- a/res/drawable-hdpi/widget_tile.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_all_apps_bg_hand.png b/res/drawable-mdpi/ic_all_apps_bg_hand.png
index 0d1d7bb..0a00241 100644
--- a/res/drawable-mdpi/ic_all_apps_bg_hand.png
+++ b/res/drawable-mdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-mdpi/page_hover_left.9.png b/res/drawable-mdpi/page_hover_left.9.png
deleted file mode 100644
index 2b6094c..0000000
--- a/res/drawable-mdpi/page_hover_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/page_hover_left_active.9.png b/res/drawable-mdpi/page_hover_left_active.9.png
deleted file mode 100644
index 9eb00a2..0000000
--- a/res/drawable-mdpi/page_hover_left_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/page_hover_right.9.png b/res/drawable-mdpi/page_hover_right.9.png
deleted file mode 100644
index c2e59835..0000000
--- a/res/drawable-mdpi/page_hover_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/page_hover_right_active.9.png b/res/drawable-mdpi/page_hover_right_active.9.png
deleted file mode 100644
index d2771a1..0000000
--- a/res/drawable-mdpi/page_hover_right_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/quantum_panel_bitmap.9.png b/res/drawable-mdpi/quantum_panel_bitmap.9.png
deleted file mode 100644
index 9325d49..0000000
--- a/res/drawable-mdpi/quantum_panel_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-mdpi/quantum_panel_dark_bitmap.9.png
deleted file mode 100644
index bf74fa0..0000000
--- a/res/drawable-mdpi/quantum_panel_dark_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/screenpanel.9.png b/res/drawable-mdpi/screenpanel.9.png
deleted file mode 100644
index 9603c12..0000000
--- a/res/drawable-mdpi/screenpanel.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/screenpanel_hover.9.png b/res/drawable-mdpi/screenpanel_hover.9.png
deleted file mode 100644
index 7f28ce0..0000000
--- a/res/drawable-mdpi/screenpanel_hover.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/virtual_preload.9.png b/res/drawable-mdpi/virtual_preload.9.png
deleted file mode 100644
index c4a01fe..0000000
--- a/res/drawable-mdpi/virtual_preload.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/virtual_preload_folder.9.png b/res/drawable-mdpi/virtual_preload_folder.9.png
deleted file mode 100644
index 2f3e420..0000000
--- a/res/drawable-mdpi/virtual_preload_folder.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/widget_tile.png b/res/drawable-mdpi/widget_tile.png
deleted file mode 100644
index 9652ace..0000000
--- a/res/drawable-mdpi/widget_tile.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-v21/quantum_panel.xml b/res/drawable-v21/quantum_panel.xml
deleted file mode 100644
index d1c0783..0000000
--- a/res/drawable-v21/quantum_panel.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/quantum_panel_shape"
- android:insetBottom="@dimen/quantum_panel_outer_padding"
- android:insetLeft="@dimen/quantum_panel_outer_padding"
- android:insetRight="@dimen/quantum_panel_outer_padding"
- android:insetTop="@dimen/quantum_panel_outer_padding" />
diff --git a/res/drawable-v21/quantum_panel_dark.xml b/res/drawable-v21/quantum_panel_dark.xml
deleted file mode 100644
index 405ad51..0000000
--- a/res/drawable-v21/quantum_panel_dark.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/quantum_panel_shape_dark"
- android:insetBottom="@dimen/quantum_panel_outer_padding"
- android:insetLeft="@dimen/quantum_panel_outer_padding"
- android:insetRight="@dimen/quantum_panel_outer_padding"
- android:insetTop="@dimen/quantum_panel_outer_padding" />
diff --git a/res/drawable/quantum_panel_shape_dark.xml b/res/drawable-v26/adaptive_icon_drawable_wrapper.xml
similarity index 66%
copy from res/drawable/quantum_panel_shape_dark.xml
copy to res/drawable-v26/adaptive_icon_drawable_wrapper.xml
index b299eb8..2d78b69 100644
--- a/res/drawable/quantum_panel_shape_dark.xml
+++ b/res/drawable-v26/adaptive_icon_drawable_wrapper.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2015 The Android Open Source Project
+ 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.
@@ -14,9 +14,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid android:color="?attr/colorSecondary" />
- <corners
- android:radius="2dp" />
-</shape>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@color/legacy_icon_background"/>
+ <foreground>
+ <com.android.launcher3.graphics.FixedScaleDrawable />
+ </foreground>
+</adaptive-icon>
diff --git a/res/values-sw720dp-port/dimens.xml b/res/drawable-v26/ic_launcher_home.xml
similarity index 60%
copy from res/values-sw720dp-port/dimens.xml
copy to res/drawable-v26/ic_launcher_home.xml
index 6f594d5..7038775 100644
--- a/res/values-sw720dp-port/dimens.xml
+++ b/res/drawable-v26/ic_launcher_home.xml
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
+<!-- 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
+ 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,
@@ -13,10 +13,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-<resources>
-<!-- Workspace -->
- <!-- the area at the edge of the screen that makes the workspace go left
- or right while you're dragging. -->
- <dimen name="scroll_zone">40dp</dimen>
-</resources>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@color/icon_background" />
+ <foreground>
+ <bitmap android:src="@mipmap/ic_launcher_home_foreground"/>
+ </foreground>
+</adaptive-icon>
diff --git a/res/drawable-xhdpi/ic_all_apps_bg_hand.png b/res/drawable-xhdpi/ic_all_apps_bg_hand.png
index e727d37..1acb378 100644
--- a/res/drawable-xhdpi/ic_all_apps_bg_hand.png
+++ b/res/drawable-xhdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-xhdpi/page_hover_left.9.png b/res/drawable-xhdpi/page_hover_left.9.png
deleted file mode 100644
index dbcc0ab..0000000
--- a/res/drawable-xhdpi/page_hover_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/page_hover_left_active.9.png b/res/drawable-xhdpi/page_hover_left_active.9.png
deleted file mode 100644
index 3233efe..0000000
--- a/res/drawable-xhdpi/page_hover_left_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/page_hover_right.9.png b/res/drawable-xhdpi/page_hover_right.9.png
deleted file mode 100644
index d82f809..0000000
--- a/res/drawable-xhdpi/page_hover_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/page_hover_right_active.9.png b/res/drawable-xhdpi/page_hover_right_active.9.png
deleted file mode 100644
index 819ea19..0000000
--- a/res/drawable-xhdpi/page_hover_right_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/quantum_panel_bitmap.9.png b/res/drawable-xhdpi/quantum_panel_bitmap.9.png
deleted file mode 100644
index b89e8b4..0000000
--- a/res/drawable-xhdpi/quantum_panel_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-xhdpi/quantum_panel_dark_bitmap.9.png
deleted file mode 100644
index 1d17136..0000000
--- a/res/drawable-xhdpi/quantum_panel_dark_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/screenpanel.9.png b/res/drawable-xhdpi/screenpanel.9.png
deleted file mode 100644
index 75343f7..0000000
--- a/res/drawable-xhdpi/screenpanel.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/screenpanel_hover.9.png b/res/drawable-xhdpi/screenpanel_hover.9.png
deleted file mode 100644
index 55b4d6e..0000000
--- a/res/drawable-xhdpi/screenpanel_hover.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/virtual_preload.9.png b/res/drawable-xhdpi/virtual_preload.9.png
deleted file mode 100644
index 2afade1..0000000
--- a/res/drawable-xhdpi/virtual_preload.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/virtual_preload_folder.9.png b/res/drawable-xhdpi/virtual_preload_folder.9.png
deleted file mode 100644
index cb3fdca..0000000
--- a/res/drawable-xhdpi/virtual_preload_folder.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/widget_tile.png b/res/drawable-xhdpi/widget_tile.png
deleted file mode 100644
index be1748d..0000000
--- a/res/drawable-xhdpi/widget_tile.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_all_apps_bg_hand.png b/res/drawable-xxhdpi/ic_all_apps_bg_hand.png
index fffcc6b..09c6c8d 100644
--- a/res/drawable-xxhdpi/ic_all_apps_bg_hand.png
+++ b/res/drawable-xxhdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-xxhdpi/page_hover_left.9.png b/res/drawable-xxhdpi/page_hover_left.9.png
deleted file mode 100644
index c81f86c..0000000
--- a/res/drawable-xxhdpi/page_hover_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/page_hover_left_active.9.png b/res/drawable-xxhdpi/page_hover_left_active.9.png
deleted file mode 100644
index 858a3b2..0000000
--- a/res/drawable-xxhdpi/page_hover_left_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/page_hover_right.9.png b/res/drawable-xxhdpi/page_hover_right.9.png
deleted file mode 100644
index c529770..0000000
--- a/res/drawable-xxhdpi/page_hover_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/page_hover_right_active.9.png b/res/drawable-xxhdpi/page_hover_right_active.9.png
deleted file mode 100644
index 9900553..0000000
--- a/res/drawable-xxhdpi/page_hover_right_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/quantum_panel_bitmap.9.png b/res/drawable-xxhdpi/quantum_panel_bitmap.9.png
deleted file mode 100644
index 1dd1f6d..0000000
--- a/res/drawable-xxhdpi/quantum_panel_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-xxhdpi/quantum_panel_dark_bitmap.9.png
deleted file mode 100644
index 48d584b..0000000
--- a/res/drawable-xxhdpi/quantum_panel_dark_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/screenpanel.9.png b/res/drawable-xxhdpi/screenpanel.9.png
deleted file mode 100644
index b221b37..0000000
--- a/res/drawable-xxhdpi/screenpanel.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/screenpanel_hover.9.png b/res/drawable-xxhdpi/screenpanel_hover.9.png
deleted file mode 100644
index 418cf0a..0000000
--- a/res/drawable-xxhdpi/screenpanel_hover.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/virtual_preload.9.png b/res/drawable-xxhdpi/virtual_preload.9.png
deleted file mode 100644
index 03c6e7f..0000000
--- a/res/drawable-xxhdpi/virtual_preload.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/virtual_preload_folder.9.png b/res/drawable-xxhdpi/virtual_preload_folder.9.png
deleted file mode 100644
index 052a72e..0000000
--- a/res/drawable-xxhdpi/virtual_preload_folder.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_tile.png b/res/drawable-xxhdpi/widget_tile.png
deleted file mode 100644
index c6237db..0000000
--- a/res/drawable-xxhdpi/widget_tile.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_all_apps_bg_hand.png b/res/drawable-xxxhdpi/ic_all_apps_bg_hand.png
index 4d065d8..49c004d 100644
--- a/res/drawable-xxxhdpi/ic_all_apps_bg_hand.png
+++ b/res/drawable-xxxhdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/quantum_panel_bitmap.9.png b/res/drawable-xxxhdpi/quantum_panel_bitmap.9.png
deleted file mode 100644
index 915177d..0000000
--- a/res/drawable-xxxhdpi/quantum_panel_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-xxxhdpi/quantum_panel_dark_bitmap.9.png
deleted file mode 100644
index 27b8466..0000000
--- a/res/drawable-xxxhdpi/quantum_panel_dark_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/all_apps_divider.xml b/res/drawable/all_apps_divider.xml
index 3fe5295..4bd274d 100644
--- a/res/drawable/all_apps_divider.xml
+++ b/res/drawable/all_apps_divider.xml
@@ -15,6 +15,6 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
- <solid android:color="@color/all_apps_divider_color" />
+ <solid android:color="?android:attr/colorControlHighlight" />
<size android:height="1dp" />
</shape>
\ No newline at end of file
diff --git a/res/drawable/bg_celllayout.xml b/res/drawable/bg_celllayout.xml
index d2219b3..b81b37f 100644
--- a/res/drawable/bg_celllayout.xml
+++ b/res/drawable/bg_celllayout.xml
@@ -18,20 +18,18 @@
*/
-->
-<transition xmlns:android="http://schemas.android.com/apk/res/android" >
-
- <item>
- <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_active="true" >
+ <shape android:shape="rectangle" >
+ <stroke
+ android:width="@dimen/spring_loaded_panel_border"
+ android:color="@color/spring_loaded_highlighted_panel_border_color" />
<solid android:color="@color/spring_loaded_panel_color" />
</shape>
</item>
<item>
- <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
- <stroke
- android:width="@dimen/spring_loaded_panel_border"
- android:color="@color/spring_loaded_highlighted_panel_border_color" />
- <solid android:color="@android:color/transparent" />
+ <shape android:shape="rectangle" >
+ <solid android:color="@color/spring_loaded_panel_color" />
</shape>
</item>
-
-</transition>
\ No newline at end of file
+</selector>
diff --git a/res/drawable/bg_pill_focused.xml b/res/drawable/bg_pill_focused.xml
deleted file mode 100644
index 37afad0..0000000
--- a/res/drawable/bg_pill_focused.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_focused="true">
- <shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <stroke android:color="#616161" android:width="2dp"/>
- <corners android:radius="@dimen/bg_pill_radius" />
- </shape>
- </item>
-</selector>
\ No newline at end of file
diff --git a/res/drawable/bg_white_pill.xml b/res/drawable/bg_white_round_rect.xml
similarity index 86%
rename from res/drawable/bg_white_pill.xml
rename to res/drawable/bg_white_round_rect.xml
index f92f739..c7f786f 100644
--- a/res/drawable/bg_white_pill.xml
+++ b/res/drawable/bg_white_round_rect.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- 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.
@@ -17,5 +17,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFFFFF" />
- <corners android:radius="@dimen/bg_pill_radius" />
+ <corners android:radius="@dimen/bg_round_rect_radius" />
</shape>
\ No newline at end of file
diff --git a/res/drawable/deep_shortcuts_drag_handle.xml b/res/drawable/deep_shortcuts_drag_handle.xml
index 99d2b07..82e844d 100644
--- a/res/drawable/deep_shortcuts_drag_handle.xml
+++ b/res/drawable/deep_shortcuts_drag_handle.xml
@@ -18,9 +18,10 @@
android:width="@dimen/deep_shortcut_drag_handle_size"
android:height="@dimen/deep_shortcut_drag_handle_size"
android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/textColorHint" >
<path
android:pathData="M20 9H4v2h16V9zM4 15h16v-2H4v2z"
- android:fillColor="#4D000000"/>
+ android:fillColor="@android:color/white" />
</vector>
\ No newline at end of file
diff --git a/res/drawable/horizontal_ellipsis.xml b/res/drawable/horizontal_ellipsis.xml
new file mode 100644
index 0000000..ad08529
--- /dev/null
+++ b/res/drawable/horizontal_ellipsis.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="@dimen/horizontal_ellipsis_size"
+ android:height="@dimen/horizontal_ellipsis_size"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/textColorSecondary" >
+
+ <path
+ android:pathData="M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
+ android:fillColor="@android:color/white" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_info_no_shadow.xml b/res/drawable/ic_info_no_shadow.xml
new file mode 100644
index 0000000..3d0c6d6
--- /dev/null
+++ b/res/drawable/ic_info_no_shadow.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="@color/workspace_icon_text_color"
+ android:pathData="M11 17h2v-6h-2v6zm1-15C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zM11 9h2V7h-2v2z"/>
+</vector>
diff --git a/res/drawable/quantum_panel.xml b/res/drawable/ic_launcher_home.xml
similarity index 70%
copy from res/drawable/quantum_panel.xml
copy to res/drawable/ic_launcher_home.xml
index 1f4fb71..a6f2519 100644
--- a/res/drawable/quantum_panel.xml
+++ b/res/drawable/ic_launcher_home.xml
@@ -1,12 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 The Android Open Source Project
+<!-- 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
+ 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,
@@ -14,5 +13,6 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"
- android:src="@drawable/quantum_panel_bitmap" />
+<bitmap
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:src="@mipmap/ic_launcher_home" />
diff --git a/res/drawable/ic_setting.xml b/res/drawable/ic_setting.xml
index 8a50c0c..e89c158 100644
--- a/res/drawable/ic_setting.xml
+++ b/res/drawable/ic_setting.xml
@@ -19,6 +19,6 @@
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
- android:fillColor="#FFFFFFFF"
+ android:fillColor="@color/workspace_icon_text_color"
android:pathData="M38.86 25.95c.08-.64 .14-1.29 .14-1.95s-.06-1.31-.14-1.95l4.23-3.31c.38-.3 .49-.84 .24-1.28l-4-6.93c-.25-.43-.77-.61-1.22-.43l-4.98 2.01c-1.03-.79-2.16-1.46-3.38-1.97L29 4.84c-.09-.47-.5-.84-1-.84h-8c-.5 0-.91 .37-.99 .84l-.75 5.3c-1.22 .51-2.35 1.17-3.38 1.97L9.9 10.1c-.45-.17-.97 0-1.22 .43l-4 6.93c-.25 .43-.14 .97 .24 1.28l4.22 3.31C9.06 22.69 9 23.34 9 24s.06 1.31 .14 1.95l-4.22 3.31c-.38 .3-.49 .84-.24 1.28l4 6.93c.25 .43 .77 .61 1.22 .43l4.98-2.01c1.03 .79 2.16 1.46 3.38 1.97l.75 5.3c.08 .47 .49 .84 .99 .84h8c.5 0 .91-.37 .99-.84l.75-5.3c1.22-.51 2.35-1.17 3.38-1.97l4.98 2.01c.45 .17 .97 0 1.22-.43l4-6.93c.25-.43 .14-.97-.24-1.28l-4.22-3.31zM24 31c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z"/>
</vector>
diff --git a/res/drawable/ic_star_rating.xml b/res/drawable/ic_star_rating.xml
new file mode 100644
index 0000000..4e34fa3
--- /dev/null
+++ b/res/drawable/ic_star_rating.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="12dp"
+ android:height="12dp"
+ android:viewportWidth="12"
+ android:viewportHeight="12">
+
+ <path
+ android:fillColor="#FB8C00"
+ android:fillType="evenOdd"
+ android:strokeWidth="1"
+ android:pathData="M 9.76511755 11.9348136 L 8.33665684 7.16088817 L 12.080006 4.41656311 L 7.49967039 4.41856896 L 6.03138903 0 L 4.57932894 4.41856896 L -1.34115008e-16 4.41656311 L 3.72612122 7.16088817 L 2.29967385 11.9348136 L 6.03138903 8.82574452 Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_wallpaper.xml b/res/drawable/ic_wallpaper.xml
index 7af4b2a..b7fcfbf 100644
--- a/res/drawable/ic_wallpaper.xml
+++ b/res/drawable/ic_wallpaper.xml
@@ -19,6 +19,6 @@
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
- android:fillColor="#FFFFFFFF"
+ android:fillColor="@color/workspace_icon_text_color"
android:pathData="M8 8h14V4H8C5.79 4 4 5.79 4 8v14h4V8zm12 18l-8 10h24l-6-8-4.06 5.42L20 26zm14-9c0-1.66-1.34-3-3-3s-3 1.34-3 3 1.34 3 3 3 3-1.34 3-3zm6-13H26v4h14v14h4V8c0-2.21-1.79-4-4-4zm0 36H26v4h14c2.21 0 4-1.79 4-4V26h-4v14zM8 26H4v14c0 2.21 1.79 4 4 4h14v-4H8V26z"/>
</vector>
diff --git a/res/drawable/ic_widget.xml b/res/drawable/ic_widget.xml
index 3e7bd7b..97706e3 100644
--- a/res/drawable/ic_widget.xml
+++ b/res/drawable/ic_widget.xml
@@ -19,6 +19,6 @@
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
- android:fillColor="#FFFFFFFF"
+ android:fillColor="@color/workspace_icon_text_color"
android:pathData="M26 26v16h16V26H26zM6 42h16V26H6v16zM6 6v16h16V6H6zm27.31-2.63L22 14.69 33.31 26l11.31-11.31L33.31 3.37z"/>
</vector>
diff --git a/res/drawable/quantum_panel.xml b/res/drawable/pending_widget_bg.xml
similarity index 72%
rename from res/drawable/quantum_panel.xml
rename to res/drawable/pending_widget_bg.xml
index 1f4fb71..cf29f90 100644
--- a/res/drawable/quantum_panel.xml
+++ b/res/drawable/pending_widget_bg.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2015 The Android Open Source Project
+ 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.
@@ -14,5 +14,6 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"
- android:src="@drawable/quantum_panel_bitmap" />
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/round_rect_primary"
+ android:inset="@dimen/pending_widget_min_padding" />
diff --git a/res/drawable/quantum_panel_dark.xml b/res/drawable/quantum_panel_dark.xml
deleted file mode 100644
index 6642e78..0000000
--- a/res/drawable/quantum_panel_dark.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"
- android:src="@drawable/quantum_panel_dark_bitmap" />
diff --git a/res/drawable/quantum_panel_shape.xml b/res/drawable/quantum_panel_shape.xml
deleted file mode 100644
index 1083615..0000000
--- a/res/drawable/quantum_panel_shape.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid android:color="@color/quantum_panel_bg_color" />
- <corners
- android:radius="2dp" />
-</shape>
\ No newline at end of file
diff --git a/res/drawable/quantum_panel_shape_dark.xml b/res/drawable/round_rect_primary.xml
similarity index 88%
rename from res/drawable/quantum_panel_shape_dark.xml
rename to res/drawable/round_rect_primary.xml
index b299eb8..2c47e06 100644
--- a/res/drawable/quantum_panel_shape_dark.xml
+++ b/res/drawable/round_rect_primary.xml
@@ -16,7 +16,6 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
- <solid android:color="?attr/colorSecondary" />
- <corners
- android:radius="2dp" />
+ <solid android:color="?android:attr/colorPrimary" />
+ <corners android:radius="2dp" />
</shape>
diff --git a/res/drawable/widgets_row_divider.xml b/res/drawable/widgets_row_divider.xml
deleted file mode 100644
index 2c3c7a2..0000000
--- a/res/drawable/widgets_row_divider.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android" >
- <size android:width="@dimen/widget_row_divider" />
- <solid android:color="?attr/colorSecondary" />
-</shape>
diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml
index a2e2f9b..dd981dd 100644
--- a/res/layout-port/launcher.xml
+++ b/res/layout-port/launcher.xml
@@ -77,8 +77,7 @@
android:id="@+id/apps_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:visibility="invisible"
- launcher:layout_ignoreInsets="true" />
+ android:visibility="invisible" />
</com.android.launcher3.dragndrop.DragLayer>
</com.android.launcher3.LauncherRootView>
diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml
index 12c01b7..06cb550 100644
--- a/res/layout-sw720dp/launcher.xml
+++ b/res/layout-sw720dp/launcher.xml
@@ -76,8 +76,7 @@
android:id="@+id/apps_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:visibility="invisible"
- launcher:layout_ignoreInsets="true" />
+ android:visibility="invisible" />
</com.android.launcher3.dragndrop.DragLayer>
</com.android.launcher3.LauncherRootView>
diff --git a/res/layout/add_item_confirmation_activity.xml b/res/layout/add_item_confirmation_activity.xml
new file mode 100644
index 0000000..35bcac3
--- /dev/null
+++ b/res/layout/add_item_confirmation_activity.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:paddingLeft="24dp"
+ android:paddingRight="24dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="20dp"
+ android:text="@string/add_item_request_drag_hint"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <FrameLayout
+ android:theme="@style/WidgetContainerTheme"
+ android:layout_width="match_parent"
+ android:background="?android:attr/colorPrimaryDark"
+ android:layout_height="wrap_content">
+
+ <com.android.launcher3.dragndrop.LivePreviewWidgetCell
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:focusable="true"
+ android:background="?android:attr/colorPrimaryDark"
+ android:gravity="center_horizontal"
+ android:id="@+id/widget_cell"
+ android:layout_gravity="center_horizontal" >
+
+ <include layout="@layout/widget_cell_content" />
+
+ </com.android.launcher3.dragndrop.LivePreviewWidgetCell>
+ </FrameLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="?android:attr/buttonBarStyle"
+ android:gravity="end" >
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@android:string/cancel"
+ android:onClick="onCancelClick"
+ style="?android:attr/buttonBarButtonStyle" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/place_automatically"
+ android:onClick="onPlaceAutomaticallyClick"
+ style="?android:attr/buttonBarButtonStyle" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 4909eb3..d6bdac2 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -22,7 +22,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
- launcher:revealBackground="@drawable/quantum_panel_shape">
+ launcher:revealBackground="@drawable/round_rect_primary">
<View
android:id="@+id/reveal_view"
@@ -46,21 +46,29 @@
<!-- DO NOT CHANGE THE ID -->
<com.android.launcher3.allapps.AllAppsRecyclerView
android:id="@+id/apps_list_view"
+ android:layout_below="@+id/search_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal|top"
- android:layout_marginTop="@dimen/all_apps_search_bar_height"
android:clipToPadding="false"
android:descendantFocusability="afterDescendants"
android:focusable="true"
- android:theme="@style/CustomOverscroll.Light" />
+ android:paddingStart="@dimen/container_fastscroll_thumb_max_width"
+ android:paddingEnd="@dimen/container_fastscroll_thumb_max_width" />
+
+ <!-- Fast scroller popup -->
+ <TextView
+ style="@style/FastScrollerPopup"
+ android:layout_below="@+id/search_container"
+ android:id="@+id/fast_scroller_popup"
+ android:layout_alignParentEnd="true"
+ android:layout_marginEnd="@dimen/container_fastscroll_popup_margin" />
<FrameLayout
android:id="@+id/search_container"
android:layout_width="match_parent"
android:layout_height="@dimen/all_apps_search_bar_height"
android:layout_gravity="center|top"
- android:paddingTop="@dimen/all_apps_search_bar_margin_top"
android:gravity="center|bottom"
android:orientation="horizontal"
android:saveEnabled="false">
@@ -68,8 +76,9 @@
<com.android.launcher3.ExtendedEditText
android:id="@+id/search_box_input"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="@dimen/all_apps_search_bar_field_height"
android:background="@android:color/transparent"
+ android:layout_gravity="bottom"
android:focusableInTouchMode="true"
android:gravity="center"
android:imeOptions="actionSearch|flagNoExtractUi"
@@ -77,7 +86,7 @@
android:maxLines="1"
android:scrollHorizontally="true"
android:singleLine="true"
- android:textColor="#4c4c4c"
+ android:textColor="?android:attr/textColorSecondary"
android:hint="@string/all_apps_search_bar_hint"
android:textColorHint="@drawable/all_apps_search_hint"
android:textSize="16sp" />
@@ -85,11 +94,11 @@
</com.android.launcher3.allapps.AllAppsRecyclerViewContainerView>
<View
+ style="@style/AllAppsNavBarProtection"
android:id="@+id/nav_bar_bg"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="bottom"
- android:background="@color/all_apps_navbar_color"
android:focusable="false"
android:visibility="invisible" />
</com.android.launcher3.allapps.AllAppsContainerView>
\ No newline at end of file
diff --git a/res/layout/all_apps_button.xml b/res/layout/all_apps_button.xml
index 68cc109..4bc780a 100644
--- a/res/layout/all_apps_button.xml
+++ b/res/layout/all_apps_button.xml
@@ -14,6 +14,4 @@
limitations under the License.
-->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- style="@style/Icon"
- android:focusable="true" />
+<TextView style="@style/BaseIcon" />
diff --git a/res/layout/all_apps_discovery_item.xml b/res/layout/all_apps_discovery_item.xml
new file mode 100644
index 0000000..1a7eaa7
--- /dev/null
+++ b/res/layout/all_apps_discovery_item.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<com.android.launcher3.discovery.AppDiscoveryItemView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clickable="true"
+ android:background="?android:selectableItemBackground">
+
+ <ImageView
+ android:id="@+id/image"
+ android:layout_width="56dp"
+ android:layout_height="56dp"
+ android:padding="8dp"
+ android:scaleType="fitCenter"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_centerVertical="true"
+ android:layout_toRightOf="@id/image">
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textColor="?android:textColorSecondary"
+ android:textSize="15sp"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <TextView
+ android:id="@+id/rating"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="?android:textColorSecondary"
+ android:textSize="14sp"
+ android:layout_gravity="center_vertical"/>
+
+ <com.android.launcher3.discovery.RatingView
+ android:id="@+id/rating_view"
+ android:layout_width="80dp"
+ android:layout_height="16dp"
+ android:layout_marginLeft="5dp"
+ android:layout_marginRight="5dp"
+ android:layout_gravity="center_vertical"/>
+
+ <TextView
+ android:id="@+id/review_count"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="5dp"
+ android:textColor="?android:textColorHint"
+ android:textSize="14sp"
+ android:layout_gravity="center_vertical"/>
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1"/>
+
+ <TextView
+ android:id="@+id/price"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="?android:textColorHint"
+ android:textSize="14sp"
+ android:layout_marginRight="12dp"
+ android:textAllCaps="true"/>
+ </LinearLayout>
+ </LinearLayout>
+
+ <ImageView
+ android:importantForAccessibility="no"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:paddingLeft="@dimen/container_fastscroll_thumb_max_width"
+ android:paddingRight="@dimen/container_fastscroll_thumb_max_width"
+ android:src="@drawable/all_apps_divider"
+ android:scaleType="fitXY"
+ android:focusable="false" />
+</com.android.launcher3.discovery.AppDiscoveryItemView>
\ No newline at end of file
diff --git a/res/layout/all_apps_discovery_loading_divider.xml b/res/layout/all_apps_discovery_loading_divider.xml
new file mode 100644
index 0000000..c7b5ad2
--- /dev/null
+++ b/res/layout/all_apps_discovery_loading_divider.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="20dp"
+ android:paddingLeft="@dimen/container_fastscroll_thumb_max_width"
+ android:paddingRight="@dimen/container_fastscroll_thumb_max_width">
+
+ <ProgressBar
+ android:id="@+id/loadingProgressBar"
+ style="@android:style/Widget.Material.ProgressBar.Horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="6dp"
+ android:maxHeight="6dp"
+ android:indeterminate="true"
+ android:layout_gravity="center"/>
+
+ <View
+ android:id="@+id/loadedDivider"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="@drawable/all_apps_divider"
+ android:layout_gravity="center"
+ android:visibility="invisible"/>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/all_apps_empty_search.xml b/res/layout/all_apps_empty_search.xml
index e1635d6..463adac 100644
--- a/res/layout/all_apps_empty_search.xml
+++ b/res/layout/all_apps_empty_search.xml
@@ -25,7 +25,6 @@
android:paddingRight="16dp"
android:fontFamily="sans-serif-medium"
android:textSize="14sp"
- android:textColor="#212121"
- android:alpha="0.56"
+ android:textColor="?android:attr/textColorTertiary"
android:focusable="false" />
diff --git a/res/layout/all_apps_icon.xml b/res/layout/all_apps_icon.xml
index 3d4bef7..ca0cbcc 100644
--- a/res/layout/all_apps_icon.xml
+++ b/res/layout/all_apps_icon.xml
@@ -16,12 +16,14 @@
<com.android.launcher3.BubbleTextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
- style="@style/Icon.AllApps"
+ style="@style/BaseIcon"
android:id="@+id/icon"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:focusable="true"
+ android:stateListAnimator="@animator/all_apps_fastscroll_icon_anim"
launcher:iconDisplay="all_apps"
- launcher:centerVertically="true" />
+ launcher:centerVertically="true"
+ android:paddingLeft="4dp"
+ android:paddingRight="4dp"
+ android:drawablePadding="@dimen/dynamic_grid_icon_drawable_padding" />
diff --git a/res/layout/app_icon.xml b/res/layout/app_icon.xml
index 831cee5..fa6eb89 100644
--- a/res/layout/app_icon.xml
+++ b/res/layout/app_icon.xml
@@ -14,6 +14,4 @@
limitations under the License.
-->
-<com.android.launcher3.BubbleTextView xmlns:android="http://schemas.android.com/apk/res/android"
- style="@style/Icon"
- android:focusable="true" />
+<com.android.launcher3.BubbleTextView style="@style/BaseIcon.Workspace" />
diff --git a/res/layout/app_widget_resize_frame.xml b/res/layout/app_widget_resize_frame.xml
new file mode 100644
index 0000000..91a1e45
--- /dev/null
+++ b/res/layout/app_widget_resize_frame.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<com.android.launcher3.AppWidgetResizeFrame
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/widget_resize_shadow"
+ android:foreground="@drawable/widget_resize_frame"
+ android:padding="0dp" >
+
+ <!-- Left -->
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_widget_resize_handle"
+ android:layout_gravity="left|center_vertical"
+ android:layout_marginLeft="@dimen/widget_handle_margin" />
+
+ <!-- Top -->
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_widget_resize_handle"
+ android:layout_gravity="top|center_horizontal"
+ android:layout_marginTop="@dimen/widget_handle_margin" />
+
+ <!-- Right -->
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_widget_resize_handle"
+ android:layout_gravity="right|center_vertical"
+ android:layout_marginRight="@dimen/widget_handle_margin" />
+
+ <!-- Bottom -->
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_widget_resize_handle"
+ android:layout_gravity="bottom|center_horizontal"
+ android:layout_marginBottom="@dimen/widget_handle_margin" />
+
+</com.android.launcher3.AppWidgetResizeFrame>
\ No newline at end of file
diff --git a/res/layout/appwidget_error.xml b/res/layout/appwidget_error.xml
index 708ece4..6a459c3 100644
--- a/res/layout/appwidget_error.xml
+++ b/res/layout/appwidget_error.xml
@@ -19,8 +19,10 @@
android:layout_height="match_parent"
android:gravity="center"
android:elevation="2dp"
- android:background="@drawable/quantum_panel_dark"
+ android:padding="4dp"
+ android:theme="@style/WidgetContainerTheme"
+ android:background="@drawable/round_rect_primary"
android:textAppearance="?android:attr/textAppearanceMediumInverse"
- android:textColor="@color/widgets_view_item_text_color"
+ android:textColor="?android:attr/textColorSecondary"
android:text="@string/gadget_error_text"
/>
diff --git a/res/layout/deep_shortcut.xml b/res/layout/deep_shortcut.xml
index 7b42ec7..85caba4 100644
--- a/res/layout/deep_shortcut.xml
+++ b/res/layout/deep_shortcut.xml
@@ -16,24 +16,40 @@
<com.android.launcher3.shortcuts.DeepShortcutView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/bg_pill_width"
- android:layout_height="@dimen/bg_pill_height"
- android:elevation="@dimen/deep_shortcuts_elevation"
- android:background="@drawable/bg_white_pill">
+ xmlns:launcher="http://schemas.android.com/apk/res-auto"
+ android:layout_width="@dimen/bg_popup_item_width"
+ android:layout_height="@dimen/bg_popup_item_height" >
-<com.android.launcher3.shortcuts.DeepShortcutTextView
- android:id="@+id/deep_shortcut"
- style="@style/Icon.DeepShortcut"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:focusable="true"
- android:background="@drawable/bg_pill_focused" />
+ <com.android.launcher3.shortcuts.DeepShortcutTextView
+ style="@style/BaseIcon"
+ android:id="@+id/bubble_text"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="start|center_vertical"
+ android:textAlignment="viewStart"
+ android:paddingStart="@dimen/deep_shortcuts_text_padding_start"
+ android:paddingEnd="@dimen/popup_padding_end"
+ android:drawableEnd="@drawable/deep_shortcuts_drag_handle"
+ android:drawablePadding="@dimen/deep_shortcut_drawable_padding"
+ android:textSize="14sp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="sans-serif"
+ launcher:layoutHorizontal="true"
+ launcher:iconDisplay="shortcut_popup"
+ launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size" />
<View
- android:id="@+id/deep_shortcut_icon"
+ android:id="@+id/icon"
android:layout_width="@dimen/deep_shortcut_icon_size"
android:layout_height="@dimen/deep_shortcut_icon_size"
- android:layout_margin="@dimen/deep_shortcut_padding_start"
- android:layout_gravity="start" />
+ android:layout_marginStart="@dimen/popup_padding_start"
+ android:layout_gravity="start|center_vertical" />
+
+ <View
+ android:id="@+id/divider"
+ android:layout_width="@dimen/deep_shortcuts_divider_width"
+ android:layout_height="@dimen/popup_item_divider_height"
+ android:layout_gravity="end|bottom"
+ android:visibility="gone"
+ android:background="?android:attr/listDivider" />
</com.android.launcher3.shortcuts.DeepShortcutView>
diff --git a/res/layout/folder_application.xml b/res/layout/folder_application.xml
index 4d00331..de861a0 100644
--- a/res/layout/folder_application.xml
+++ b/res/layout/folder_application.xml
@@ -14,6 +14,9 @@
limitations under the License.
-->
-<com.android.launcher3.BubbleTextView xmlns:android="http://schemas.android.com/apk/res/android"
- style="@style/Icon.Folder"
- android:focusable="true" />
+<com.android.launcher3.BubbleTextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto"
+ style="@style/BaseIcon"
+ android:includeFontPadding="false"
+ launcher:iconDisplay="folder" />
diff --git a/res/layout/folder_icon.xml b/res/layout/folder_icon.xml
index 9eb8c9a..ccc6b01 100644
--- a/res/layout/folder_icon.xml
+++ b/res/layout/folder_icon.xml
@@ -21,8 +21,9 @@
android:orientation="vertical"
android:focusable="true" >
<com.android.launcher3.BubbleTextView
- style="@style/Icon"
+ style="@style/BaseIcon.Workspace"
android:id="@+id/folder_icon_name"
+ android:focusable="false"
android:layout_gravity="top"
android:layout_width="match_parent"
android:layout_height="match_parent" />
diff --git a/res/layout/deep_shortcuts_container.xml b/res/layout/folder_page.xml
similarity index 68%
copy from res/layout/deep_shortcuts_container.xml
copy to res/layout/folder_page.xml
index 68bb60f..084e8fd 100644
--- a/res/layout/deep_shortcuts_container.xml
+++ b/res/layout/folder_page.xml
@@ -14,16 +14,11 @@
limitations under the License.
-->
-<com.android.launcher3.shortcuts.DeepShortcutsContainer
+<com.android.launcher3.CellLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/deep_shortcuts_container"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
- android:clipToPadding="false"
- android:clipChildren="false"
- android:elevation="@dimen/deep_shortcuts_elevation"
- android:orientation="vertical">
-
-</com.android.launcher3.shortcuts.DeepShortcutsContainer>
\ No newline at end of file
+ android:hapticFeedbackEnabled="false"
+ android:importantForAccessibility="no"
+ launcher:containerType="folder" />
diff --git a/res/layout/hotseat.xml b/res/layout/hotseat.xml
index 7bef889..f5b5bbf 100644
--- a/res/layout/hotseat.xml
+++ b/res/layout/hotseat.xml
@@ -20,5 +20,6 @@
android:id="@+id/layout"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_gravity="center" />
+ android:layout_gravity="center"
+ launcher:containerType="hotseat" />
</com.android.launcher3.Hotseat>
diff --git a/res/layout/notification.xml b/res/layout/notification.xml
new file mode 100644
index 0000000..f955c6b
--- /dev/null
+++ b/res/layout/notification.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.launcher3.notification.NotificationItemView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/notification_view"
+ android:layout_width="@dimen/bg_popup_item_width"
+ android:layout_height="wrap_content"
+ android:elevation="@dimen/deep_shortcuts_elevation"
+ android:background="@drawable/bg_white_round_rect"
+ android:backgroundTint="@color/notification_color_beneath">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:clipChildren="false">
+
+ <FrameLayout
+ android:id="@+id/header"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_header_height"
+ android:paddingStart="@dimen/notification_padding_start"
+ android:paddingEnd="@dimen/notification_padding_end"
+ android:background="@color/popup_header_background_color"
+ android:elevation="@dimen/notification_elevation">
+ <TextView
+ android:id="@+id/notification_text"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:gravity="center_vertical"
+ android:text="@string/notifications_header"
+ android:textSize="@dimen/notification_header_text_size"
+ android:textColor="?android:attr/textColorSecondary" />
+ <TextView
+ android:id="@+id/notification_count"
+ android:layout_width="@dimen/notification_icon_size"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
+ android:gravity="center"
+ android:textSize="@dimen/notification_header_count_text_size"
+ android:fontFamily="sans-serif-medium"
+ android:textColor="?android:attr/textColorPrimary" />
+ </FrameLayout>
+
+ <include layout="@layout/notification_main"
+ android:id="@+id/main_view"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_main_height"
+ android:layout_below="@id/header" />
+
+ <View
+ android:id="@+id/divider"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/popup_item_divider_height"
+ android:background="?android:attr/listDivider"
+ android:layout_below="@id/main_view"/>
+
+ <include layout="@layout/notification_footer"
+ android:id="@+id/footer"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_footer_height"
+ android:layout_below="@id/divider" />
+
+ </RelativeLayout>
+
+</com.android.launcher3.notification.NotificationItemView>
diff --git a/res/layout/notification_footer.xml b/res/layout/notification_footer.xml
new file mode 100644
index 0000000..ed2212b
--- /dev/null
+++ b/res/layout/notification_footer.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+
+<com.android.launcher3.notification.NotificationFooterLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:elevation="@dimen/notification_elevation"
+ android:clipChildren="false"
+ android:layout_gravity="center_vertical"
+ android:background="@color/popup_background_color">
+
+ <LinearLayout
+ android:id="@+id/icon_row"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:gravity="end|center_vertical"
+ android:padding="@dimen/notification_footer_icon_row_padding"
+ android:clipToPadding="false"
+ android:clipChildren="false"/>
+
+ <View
+ android:id="@+id/overflow"
+ android:layout_width="@dimen/horizontal_ellipsis_size"
+ android:layout_height="@dimen/horizontal_ellipsis_size"
+ android:background="@drawable/horizontal_ellipsis"
+ android:layout_marginStart="@dimen/horizontal_ellipsis_offset"
+ android:layout_gravity="start|center_vertical" />
+
+</com.android.launcher3.notification.NotificationFooterLayout>
+
diff --git a/res/layout/notification_main.xml b/res/layout/notification_main.xml
new file mode 100644
index 0000000..ce4e137
--- /dev/null
+++ b/res/layout/notification_main.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+
+<com.android.launcher3.notification.NotificationMainView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusable="true"
+ android:elevation="@dimen/notification_elevation" >
+
+ <LinearLayout
+ android:id="@+id/text_and_background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:gravity="center_vertical"
+ android:background="@color/popup_background_color"
+ android:paddingStart="@dimen/notification_padding_start"
+ android:paddingEnd="@dimen/notification_main_text_padding_end">
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAlignment="viewStart"
+ android:fontFamily="sans-serif"
+ android:textSize="@dimen/notification_main_text_size"
+ android:textColor="?android:attr/textColorPrimary"
+ android:lines="1"
+ android:ellipsize="end" />
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fontFamily="sans-serif"
+ android:textSize="@dimen/notification_main_text_size"
+ android:textColor="?android:attr/textColorSecondary"
+ android:lines="1"
+ android:ellipsize="end" />
+ </LinearLayout>
+
+ <View
+ android:id="@+id/popup_item_icon"
+ android:layout_width="@dimen/notification_icon_size"
+ android:layout_height="@dimen/notification_icon_size"
+ android:layout_marginEnd="@dimen/notification_padding_end"
+ android:layout_gravity="center_vertical|end" />
+
+</com.android.launcher3.notification.NotificationMainView>
+
diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml
index 9ba3f09..2091721 100644
--- a/res/layout/overview_panel.xml
+++ b/res/layout/overview_panel.xml
@@ -15,11 +15,13 @@
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto"
+ launcher:layout_ignoreInsets="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"
android:gravity="top"
- android:orientation="horizontal" >
+ android:orientation="horizontal">
<TextView
android:id="@+id/wallpaper_button"
diff --git a/res/layout/deep_shortcuts_container.xml b/res/layout/popup_container.xml
similarity index 90%
rename from res/layout/deep_shortcuts_container.xml
rename to res/layout/popup_container.xml
index 68bb60f..e9cfe24 100644
--- a/res/layout/deep_shortcuts_container.xml
+++ b/res/layout/popup_container.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
-<com.android.launcher3.shortcuts.DeepShortcutsContainer
+<com.android.launcher3.popup.PopupContainerWithArrow
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/deep_shortcuts_container"
android:layout_width="wrap_content"
@@ -26,4 +26,4 @@
android:elevation="@dimen/deep_shortcuts_elevation"
android:orientation="vertical">
-</com.android.launcher3.shortcuts.DeepShortcutsContainer>
\ No newline at end of file
+</com.android.launcher3.popup.PopupContainerWithArrow>
\ No newline at end of file
diff --git a/res/layout/qsb_blocker_view.xml b/res/layout/qsb_blocker_view.xml
index 58a148e..453eebe 100644
--- a/res/layout/qsb_blocker_view.xml
+++ b/res/layout/qsb_blocker_view.xml
@@ -14,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.launcher3.QsbBlockerView
+<com.android.launcher3.qsb.QsbBlockerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" />
\ No newline at end of file
diff --git a/res/layout/qsb_container.xml b/res/layout/qsb_container.xml
index b75e3b5..6fa843d 100644
--- a/res/layout/qsb_container.xml
+++ b/res/layout/qsb_container.xml
@@ -14,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.launcher3.QsbContainerView
+<com.android.launcher3.qsb.QsbContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
@@ -23,8 +23,8 @@
android:padding="0dp" >
<fragment
- android:name="com.android.launcher3.QsbContainerView$QsbFragment"
+ android:name="com.android.launcher3.qsb.QsbContainerView$QsbFragment"
android:layout_width="match_parent"
android:tag="qsb_view"
android:layout_height="match_parent"/>
-</com.android.launcher3.QsbContainerView>
\ No newline at end of file
+</com.android.launcher3.qsb.QsbContainerView>
\ No newline at end of file
diff --git a/res/layout/qsb_default_view.xml b/res/layout/qsb_default_view.xml
index 82bdea5..3075f80 100644
--- a/res/layout/qsb_default_view.xml
+++ b/res/layout/qsb_default_view.xml
@@ -23,7 +23,7 @@
android:layout_height="48dp"
android:layout_margin="16dp"
android:layout_gravity="center_vertical"
- android:background="@drawable/quantum_panel_shape"
+ android:background="@drawable/round_rect_primary"
android:elevation="2dp"
android:orientation="horizontal">
@@ -35,7 +35,7 @@
android:gravity="center_vertical"
android:paddingStart="16dp"
android:text="@string/abandoned_search"
- android:textColor="@color/quantum_panel_text_color"
+ android:textColor="?android:attr/textColorSecondary"
android:textAppearance="?android:textAppearanceMedium"
android:clickable="true"
android:background="?android:attr/selectableItemBackground" />
@@ -46,7 +46,7 @@
android:visibility="gone"
android:layout_height="match_parent"
android:src="@drawable/ic_setting"
- android:tint="@color/quantum_panel_text_color"
+ android:tint="?android:attr/textColorSecondary"
android:contentDescription="@string/gadget_setup_text"
android:padding="8dp"
android:background="?android:attr/selectableItemBackground" />
diff --git a/res/layout/deep_shortcuts_container.xml b/res/layout/shortcuts_item.xml
similarity index 60%
copy from res/layout/deep_shortcuts_container.xml
copy to res/layout/shortcuts_item.xml
index 68bb60f..8b20bcb 100644
--- a/res/layout/deep_shortcuts_container.xml
+++ b/res/layout/shortcuts_item.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- 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.
@@ -14,16 +14,19 @@
limitations under the License.
-->
-<com.android.launcher3.shortcuts.DeepShortcutsContainer
+<com.android.launcher3.shortcuts.ShortcutsItemView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/deep_shortcuts_container"
- android:layout_width="wrap_content"
+ android:id="@+id/shortcuts_view"
+ android:layout_width="@dimen/bg_popup_item_width"
android:layout_height="wrap_content"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
- android:clipToPadding="false"
- android:clipChildren="false"
android:elevation="@dimen/deep_shortcuts_elevation"
- android:orientation="vertical">
+ android:background="@drawable/bg_white_round_rect">
-</com.android.launcher3.shortcuts.DeepShortcutsContainer>
\ No newline at end of file
+ <LinearLayout
+ android:id="@+id/deep_shortcuts"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ </LinearLayout>
+
+</com.android.launcher3.shortcuts.ShortcutsItemView>
diff --git a/res/layout/system_shortcut.xml b/res/layout/system_shortcut.xml
new file mode 100644
index 0000000..83ad9f2
--- /dev/null
+++ b/res/layout/system_shortcut.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.launcher3.shortcuts.DeepShortcutView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto"
+ android:layout_width="@dimen/bg_popup_item_width"
+ android:layout_height="@dimen/bg_popup_item_height" >
+
+ <com.android.launcher3.BubbleTextView
+ style="@style/BaseIcon"
+ android:id="@+id/bubble_text"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="start|center_vertical"
+ android:textAlignment="viewStart"
+ android:paddingStart="@dimen/deep_shortcuts_text_padding_start"
+ android:paddingEnd="@dimen/popup_padding_end"
+ android:textSize="14sp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="sans-serif"
+ launcher:iconDisplay="shortcut_popup"
+ launcher:layoutHorizontal="true" />
+
+ <View
+ android:id="@+id/icon"
+ android:layout_width="@dimen/system_shortcut_icon_size"
+ android:layout_height="@dimen/system_shortcut_icon_size"
+ android:layout_marginStart="@dimen/system_shortcut_margin_start"
+ android:layout_gravity="start|center_vertical" />
+
+ <View
+ android:id="@+id/divider"
+ android:layout_width="@dimen/deep_shortcuts_divider_width"
+ android:layout_height="@dimen/popup_item_divider_height"
+ android:layout_gravity="end|bottom"
+ android:visibility="gone"
+ android:background="?android:attr/listDivider" />
+
+</com.android.launcher3.shortcuts.DeepShortcutView>
diff --git a/res/layout/system_shortcut_icon_only.xml b/res/layout/system_shortcut_icon_only.xml
new file mode 100644
index 0000000..313c69c
--- /dev/null
+++ b/res/layout/system_shortcut_icon_only.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<ImageView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/system_shortcut_header_icon_touch_size"
+ android:layout_height="@dimen/system_shortcut_header_icon_touch_size"
+ android:background="?android:attr/selectableItemBackgroundBorderless"
+ android:padding="@dimen/system_shortcut_header_icon_padding" />
diff --git a/res/layout/system_shortcut_icons.xml b/res/layout/system_shortcut_icons.xml
new file mode 100644
index 0000000..676be8e
--- /dev/null
+++ b/res/layout/system_shortcut_icons.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/system_shortcut_icons"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/system_shortcut_header_height"
+ android:orientation="horizontal"
+ android:gravity="end|center_vertical"
+ android:background="@color/popup_header_background_color" />
diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml
index da2a861..cde6540 100644
--- a/res/layout/user_folder.xml
+++ b/res/layout/user_folder.xml
@@ -18,7 +18,7 @@
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:background="@drawable/quantum_panel"
+ android:background="@drawable/round_rect_primary"
android:elevation="5dp"
android:orientation="vertical" >
@@ -46,7 +46,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
- android:background="#00000000"
+ android:background="@android:color/transparent"
android:fontFamily="sans-serif-condensed"
android:gravity="center_horizontal"
android:hint="@string/folder_hint_text"
@@ -54,10 +54,10 @@
android:paddingBottom="@dimen/folder_label_padding_bottom"
android:paddingTop="@dimen/folder_label_padding_top"
android:singleLine="true"
- android:textColor="#ff777777"
+ android:textColor="?android:attr/textColorTertiary"
android:includeFontPadding="false"
- android:textColorHighlight="#ffCCCCCC"
- android:textColorHint="#ff808080"
+ android:textColorHighlight="?android:attr/colorControlHighlight"
+ android:textColorHint="?android:attr/textColorHint"
android:textSize="@dimen/folder_label_text_size" />
<com.android.launcher3.pageindicators.PageIndicatorDots
diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml
index a8af201..2063f32 100644
--- a/res/layout/user_folder_icon_normalized.xml
+++ b/res/layout/user_folder_icon_normalized.xml
@@ -18,7 +18,7 @@
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:background="@drawable/quantum_panel"
+ android:background="@drawable/round_rect_primary"
android:elevation="5dp"
android:orientation="vertical" >
@@ -46,7 +46,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
- android:background="#00000000"
+ android:background="@android:color/transparent"
android:fontFamily="sans-serif-condensed"
android:textStyle="bold"
android:gravity="center_horizontal"
@@ -55,9 +55,9 @@
android:paddingBottom="@dimen/folder_label_padding_bottom"
android:paddingTop="@dimen/folder_label_padding_top"
android:singleLine="true"
- android:textColor="#EE777777"
- android:textColorHighlight="#ffCCCCCC"
- android:textColorHint="#ff808080"
+ android:textColor="?android:attr/textColorTertiary"
+ android:textColorHighlight="?android:attr/colorControlHighlight"
+ android:textColorHint="?android:attr/textColorHint"
android:textSize="@dimen/folder_label_text_size" />
<com.android.launcher3.pageindicators.PageIndicatorDots
diff --git a/res/layout/widget_cell.xml b/res/layout/widget_cell.xml
index 15f369f..148a99b 100644
--- a/res/layout/widget_cell.xml
+++ b/res/layout/widget_cell.xml
@@ -15,60 +15,14 @@
-->
<com.android.launcher3.widget.WidgetCell
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:focusable="true"
- android:background="?android:attr/colorPrimary"
+ android:background="?android:attr/colorPrimaryDark"
android:gravity="center_horizontal">
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingTop="@dimen/widget_preview_label_vertical_padding"
- android:paddingBottom="@dimen/widget_preview_label_vertical_padding"
- android:paddingLeft="@dimen/widget_preview_label_horizontal_padding"
- android:paddingRight="@dimen/widget_preview_label_horizontal_padding"
- android:orientation="horizontal">
+ <include layout="@layout/widget_cell_content" />
- <!-- The name of the widget. -->
- <TextView
- android:id="@+id/widget_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:ellipsize="end"
- android:fadingEdge="horizontal"
- android:fontFamily="sans-serif-condensed"
- android:gravity="start"
- android:shadowColor="#B0000000"
- android:shadowRadius="2.0"
- android:singleLine="true"
- android:textColor="@color/widgets_view_item_text_color"
- android:textSize="14sp" />
-
- <!-- The original dimensions of the widget (can't be the same text as above due to different
- style. -->
- <TextView
- android:id="@+id/widget_dims"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="5dp"
- android:layout_marginLeft="5dp"
- android:textColor="@color/widgets_view_item_text_color"
- android:textSize="14sp"
- android:fontFamily="sans-serif-condensed"
- android:shadowRadius="2.0"
- android:shadowColor="#B0000000" />
- </LinearLayout>
-
- <!-- The image of the widget. This view does not support padding. Any placement adjustment
- should be done using margins. -->
- <com.android.launcher3.widget.WidgetImageView
- android:id="@+id/widget_preview"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1" />
</com.android.launcher3.widget.WidgetCell>
\ No newline at end of file
diff --git a/res/layout/widget_cell_content.xml b/res/layout/widget_cell_content.xml
new file mode 100644
index 0000000..c77b0b9
--- /dev/null
+++ b/res/layout/widget_cell_content.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="@dimen/widget_preview_label_vertical_padding"
+ android:paddingBottom="@dimen/widget_preview_label_vertical_padding"
+ android:paddingLeft="@dimen/widget_preview_label_horizontal_padding"
+ android:paddingRight="@dimen/widget_preview_label_horizontal_padding"
+ android:orientation="horizontal">
+
+ <!-- The name of the widget. -->
+ <TextView
+ android:id="@+id/widget_name"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:ellipsize="end"
+ android:fadingEdge="horizontal"
+ android:fontFamily="sans-serif-condensed"
+ android:gravity="start"
+ android:singleLine="true"
+ android:maxLines="1"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textSize="14sp" />
+
+ <!-- The original dimensions of the widget (can't be the same text as above due to different
+ style. -->
+ <TextView
+ android:id="@+id/widget_dims"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="5dp"
+ android:layout_marginLeft="5dp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textSize="14sp"
+ android:fontFamily="sans-serif-condensed"
+ android:alpha="0.8" />
+ </LinearLayout>
+
+ <!-- The image of the widget. This view does not support padding. Any placement adjustment
+ should be done using margins. -->
+ <com.android.launcher3.widget.WidgetImageView
+ android:id="@+id/widget_preview"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+</merge>
\ No newline at end of file
diff --git a/res/values-sw720dp-port/dimens.xml b/res/layout/widget_list_divider.xml
similarity index 69%
rename from res/values-sw720dp-port/dimens.xml
rename to res/layout/widget_list_divider.xml
index 6f594d5..68c9a45 100644
--- a/res/values-sw720dp-port/dimens.xml
+++ b/res/layout/widget_list_divider.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
+<!-- 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.
@@ -13,10 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-<resources>
-<!-- Workspace -->
- <!-- the area at the edge of the screen that makes the workspace go left
- or right while you're dragging. -->
- <dimen name="scroll_zone">40dp</dimen>
-</resources>
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/widget_row_divider"
+ android:layout_height="match_parent"
+ android:background="?android:attr/colorPrimary" />
\ No newline at end of file
diff --git a/res/layout/widgets_bottom_sheet.xml b/res/layout/widgets_bottom_sheet.xml
new file mode 100644
index 0000000..c2270d2
--- /dev/null
+++ b/res/layout/widgets_bottom_sheet.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.launcher3.widget.WidgetsBottomSheet
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="28dp"
+ android:background="?android:attr/colorPrimary"
+ android:elevation="@dimen/deep_shortcuts_elevation"
+ android:layout_gravity="bottom">
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:fontFamily="sans-serif"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="24sp"/>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:paddingTop="4dp"
+ android:fontFamily="sans-serif"
+ android:textColor="?android:attr/textColorTertiary"
+ android:textSize="14sp"
+ android:text="@string/long_press_widget_to_add"/>
+
+ <include layout="@layout/widgets_scroll_container"
+ android:id="@+id/widgets"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="45dp"
+ android:layout_marginBottom="40dp"/>
+
+</com.android.launcher3.widget.WidgetsBottomSheet>
\ No newline at end of file
diff --git a/res/layout/widgets_list_row_view.xml b/res/layout/widgets_list_row_view.xml
index 30a34d4..b6e0a0b 100644
--- a/res/layout/widgets_list_row_view.xml
+++ b/res/layout/widgets_list_row_view.xml
@@ -19,7 +19,6 @@
android:id="@+id/widgets_cell_list_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="?android:attr/colorPrimary"
android:orientation="vertical"
android:focusable="true"
android:descendantFocusability="afterDescendants">
@@ -30,18 +29,17 @@
android:id="@+id/section"
android:layout_width="match_parent"
android:layout_height="@dimen/widget_section_height"
- android:background="?attr/colorSecondary"
+ android:background="?android:attr/colorPrimary"
android:drawablePadding="@dimen/widget_section_horizontal_padding"
android:ellipsize="end"
android:focusable="true"
android:gravity="start|center_vertical"
- android:importantForAccessibility="no"
android:paddingBottom="@dimen/widget_section_vertical_padding"
android:paddingLeft="@dimen/widget_section_horizontal_padding"
android:paddingRight="@dimen/widget_section_horizontal_padding"
android:paddingTop="@dimen/widget_section_vertical_padding"
android:singleLine="true"
- android:textColor="@color/widgets_view_section_text_color"
+ android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
launcher:customShadows="false"
launcher:deferShadowGeneration="true"
@@ -49,20 +47,5 @@
launcher:iconSizeOverride="@dimen/widget_section_icon_size"
launcher:layoutHorizontal="true" />
- <HorizontalScrollView
- android:id="@+id/widgets_scroll_container"
- android:theme="@style/CustomOverscroll.Dark"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:scrollbars="none">
- <LinearLayout
- android:id="@+id/widgets_cell_list"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingStart="@dimen/widget_row_padding"
- android:paddingEnd="0dp"
- android:orientation="horizontal"
- android:divider="@drawable/widgets_row_divider"
- android:showDividers="middle"/>
- </HorizontalScrollView>
+ <include layout="@layout/widgets_scroll_container" />
</LinearLayout>
diff --git a/res/layout/widgets_scroll_container.xml b/res/layout/widgets_scroll_container.xml
new file mode 100644
index 0000000..33c981a
--- /dev/null
+++ b/res/layout/widgets_scroll_container.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<HorizontalScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/widgets_scroll_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/colorPrimaryDark"
+ android:scrollbars="none">
+ <LinearLayout
+ android:id="@+id/widgets_cell_list"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingStart="0dp"
+ android:paddingEnd="0dp"
+ android:orientation="horizontal"
+ android:showDividers="none"/>
+</HorizontalScrollView>
\ No newline at end of file
diff --git a/res/layout/widgets_view.xml b/res/layout/widgets_view.xml
index c4431be..2f11c28 100644
--- a/res/layout/widgets_view.xml
+++ b/res/layout/widgets_view.xml
@@ -23,7 +23,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="afterDescendants"
- launcher:revealBackground="@drawable/quantum_panel_shape_dark"
+ launcher:revealBackground="@drawable/round_rect_primary"
android:theme="@style/WidgetContainerTheme">
<View
@@ -45,10 +45,17 @@
<com.android.launcher3.widget.WidgetsRecyclerView
android:id="@+id/widgets_list_view"
- android:theme="@style/CustomOverscroll.Dark"
android:layout_width="match_parent"
android:layout_height="match_parent" />
+ <!-- Fast scroller popup -->
+ <TextView
+ style="@style/FastScrollerPopup"
+ android:layout_below="@+id/search_container"
+ android:id="@+id/fast_scroller_popup"
+ android:layout_gravity="top|end"
+ android:layout_marginEnd="@dimen/container_fastscroll_popup_margin" />
+
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/res/layout/workspace_screen.xml b/res/layout/workspace_screen.xml
index faf6885..94bdedb 100644
--- a/res/layout/workspace_screen.xml
+++ b/res/layout/workspace_screen.xml
@@ -19,4 +19,5 @@
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:hapticFeedbackEnabled="false" />
+ android:hapticFeedbackEnabled="false"
+ launcher:containerType="workspace" />
diff --git a/res/mipmap-hdpi/ic_launcher_home_foreground.png b/res/mipmap-hdpi/ic_launcher_home_foreground.png
new file mode 100644
index 0000000..d068d92
--- /dev/null
+++ b/res/mipmap-hdpi/ic_launcher_home_foreground.png
Binary files differ
diff --git a/res/mipmap-mdpi/ic_launcher_home_foreground.png b/res/mipmap-mdpi/ic_launcher_home_foreground.png
new file mode 100644
index 0000000..0ed9f4d
--- /dev/null
+++ b/res/mipmap-mdpi/ic_launcher_home_foreground.png
Binary files differ
diff --git a/res/mipmap-xhdpi/ic_launcher_home_foreground.png b/res/mipmap-xhdpi/ic_launcher_home_foreground.png
new file mode 100644
index 0000000..7a9daf5
--- /dev/null
+++ b/res/mipmap-xhdpi/ic_launcher_home_foreground.png
Binary files differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_home_foreground.png b/res/mipmap-xxhdpi/ic_launcher_home_foreground.png
new file mode 100644
index 0000000..03b493e
--- /dev/null
+++ b/res/mipmap-xxhdpi/ic_launcher_home_foreground.png
Binary files differ
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 9152ea5..cfc2787 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Afgelaaide program in veiligmodus gedeaktiveer"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Legstukke gedeaktiveer in Veiligmodus"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Kortpad is nie beskikbaar nie"</string>
+ <string name="home_screen" msgid="806512411299847073">"Tuisskerm"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Gepasmaakte handelinge"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Raak en hou om \'n legstuk op te tel."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dubbeltik en hou om \'n legstuk op te tel of gebruik gepasmaakte handelinge."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d breed by %2$d hoog"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Raak en hou om self te plaas"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Voeg outomaties by"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Deursoek programme"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Laai tans programme …"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Geen programme gevind wat met \"<xliff:g id="QUERY">%1$s</xliff:g>\" ooreenstem nie"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Soek meer programme"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Kennisgewings"</string>
<string name="out_of_space" msgid="4691004494942118364">"Niks meer spasie op die tuisskerm nie."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Geen plek meer in die Gunstelinge-laai nie"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Programmelys"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Laat toe dat tuisskerm gedraai word"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Wanneer foon gedraai word"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Huidige vertooninstelling laat nie rotasie toe nie"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Voeg ikoon by tuisskerm"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Vir nuwe programme"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Onbekend"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Verwyder"</string>
<string name="abandoned_search" msgid="891119232568284442">"Soek"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Die program vir hierdie ikoon is nie geïnstalleer nie. Jy kan dit verwyder of die program soek en dit self installeer."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> laai tans af, <xliff:g id="PROGRESS">%2$s</xliff:g> voltooid"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> wag tans om te installeer"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>-legstukke"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Voeg by tuisskerm"</string>
<string name="action_move_here" msgid="2170188780612570250">"Skuif item hierheen"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Item is by tuisskerm gevoeg"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index c20dc12..3ae9666 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"የወረደው መተግበሪያ ደህንነቱ በተጠበቀ ሁኔታ ውስጥ ተሰናክሏል"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"ምግብሮች በደህንነቱ የተጠበቀ ሁኔታ ተሰናክለዋል"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"አቋራጭ አይገኝም"</string>
+ <string name="home_screen" msgid="806512411299847073">"መነሻ ገጽ"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"ብጁ እርምጃዎች"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"ፍርግም ለማንሳት ይንኩ እና ይያዙት"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"አንድ ንዑስ ፕሮግራም ለመምረጥ ወይም ብጁ እርምጃዎችን ለመጠቀም ሁለቴ መታ አድርገው ይያዙ።"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d ስፋት በ%2$d ከፍታ"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"ራስዎ ለማስቀመጥ ነክተው ይያዙት"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"በራስ-ሰር አክል"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"መተግበሪያዎችን ይፈልጉ"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"መተግበሪያዎችን በመጫን ላይ..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"ከ«<xliff:g id="QUERY">%1$s</xliff:g>» ጋር የሚዛመዱ ምንም መተግበሪያዎች አልተገኙም"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"ተጨማሪ መተግበሪያዎች ይፈልጉ"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"ማሳወቂያዎች"</string>
<string name="out_of_space" msgid="4691004494942118364">"በዚህ መነሻ ማያ ገጽ ላይ ምንም ቦታ የለም።"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"በተወዳጆች መሣቢያ ውስጥ ተጨማሪ ቦታ የለም"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"የመተግበሪያዎች ዝርዝር"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"የመነሻ ማያ ገጽ ማሽከርከርን ይፍቀዱ"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"ስልኩ ሲዞር"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"የአሁኑ የማሳያ ቅንብር ማሽከርከርን አይፈቅድም"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"አዶ ወደ የመነሻ ማያ ገጽ አክል"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"ለአዲስ መተግበሪያዎች"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"የማይታወቅ"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"አስወግድ"</string>
<string name="abandoned_search" msgid="891119232568284442">"ፈልግ"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"የዚህ አዶ መተግበሪያ አልተጫነም። ማስወገድ ወይም መተግበሪያውን መፈለግና ራስዎ መጫን ይችላሉ።"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> በመውረድ ላይ፣ <xliff:g id="PROGRESS">%2$s</xliff:g> ተጠናቋል"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ለመጫን በመጠበቅ ላይ"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> ንዑስ ፕሮግራሞች"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"ወደ መነሻ ማያ ገጽ ያክሉ"</string>
<string name="action_move_here" msgid="2170188780612570250">"ንጥልን ወደዚህ ውሰድ"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"ወደ መነሻ ማያ ገጽ ንጥል ታክሏል"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index c80d162..9c2167a 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"تم تعطيل التطبيق الذي تم تنزيله في الوضع الآمن"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"الأدوات معطلة في الوضع الآمن"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"الاختصار غير متاح"</string>
+ <string name="home_screen" msgid="806512411299847073">"الشاشة الرئيسية"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"الإجراءات المخصّصة"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"المس مع الاستمرار لاختيار إحدى الأدوات."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"انقر نقرًا مزدوجًا مع الاستمرار لاختيار أداة أو استخدم الإجراءات المخصصة."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"العرض %1$d الطول %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"المس مع الاستمرار للإضافة يدويًا"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"إضافة تلقائيًا"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"البحث في التطبيقات"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"جارٍ تحميل التطبيقات…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"لم يتم العثور على أية تطبيقات تتطابق مع \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"البحث عن مزيد من التطبيقات"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"الإشعارات"</string>
<string name="out_of_space" msgid="4691004494942118364">"ليس هناك مساحة أخرى في هذه الشاشة الرئيسية."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"لا يوجد المزيد من الحقول في علبة المفضلة"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"قائمة التطبيقات"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"السماح بتدوير الشاشة الرئيسية"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"عند تدوير الهاتف"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"لا يسمح إعداد العرض الحالي بالتدوير"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"إضافة رمز إلى الشاشة الرئيسية"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"للتطبيقات الجديدة"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"غير معروفة"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"إزالة"</string>
<string name="abandoned_search" msgid="891119232568284442">"بحث"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"لم يتم تثبيت تطبيق لهذا الرمز. يمكنك إزالته أو البحث عن التطبيق وتثبيته يدويًا."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"جارٍ تنزيل <xliff:g id="NAME">%1$s</xliff:g>، اكتمل <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> في انتظار التثبيت"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"أدوات <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"إضافة إلى الشاشة الرئيسية"</string>
<string name="action_move_here" msgid="2170188780612570250">"نقل العنصر إلى هنا"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"تمت إضافة العنصر إلى الشاشة الرئيسية"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 1a4f31b..5cbe408 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Güvənli rejimdə icazə verilməyən tətbiq endirildi"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Vidcetlər Güvənli rejimdə deaktiv edilib"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Qısayol əlçatan deyil"</string>
+ <string name="home_screen" msgid="806512411299847073">"Əsas ekran"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Fərdi əməliyyatlar"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Vidceti götürmək üçün toxunub saxlayın."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Vidceti götürmək üçün & iki dəfə toxunub saxlayın və ya fərdi fəaliyyətləri istifadə edin."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%2$d hündürlük %1$d enində"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Manual olaraq yerləşdirmək üçün toxunaraq basıb saxlayın"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Avtomatik əlavə edin"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Tətbiq Axtarın"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Tətbiqlər endirilir..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" sorğusuna uyğun Tətbiqlər tapılmadı"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Daha çox tətbiq üçün axtarış edin"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Bildirişlər"</string>
<string name="out_of_space" msgid="4691004494942118364">"Bu Əsas ekranda boş yer yoxdur."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Favoritlər-də yer yoxdur"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Tətbiq siyahısı"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Əsas ekranın firlanmağına icazə verin"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Telefon çevrilən zaman"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Cari Ekran ayarı fırlatmağa icazə vermir"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Əsas ekrana ikona əlavə edin"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Yeni tətbiqlər üçün"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Naməlum"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Yığışdır"</string>
<string name="abandoned_search" msgid="891119232568284442">"Axtarış"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Bu ikona üçün tətbiq quraşdırılmayıb. Onu silə bilərsiniz, və ya tətbiqi taparaq manual yol ilə quraşdıra bilərsiniz."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> endirilir, <xliff:g id="PROGRESS">%2$s</xliff:g> tamamlandı"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> yüklənmək üçün gözləyir"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> vidcetləri"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Əsas ekrana əlavə edin"</string>
<string name="action_move_here" msgid="2170188780612570250">"Elementi bura köçürün"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Element əsas ekrana əlavə edildi"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index a9344f9..1a9ad2a 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Preuzeta aplikacija je onemogućena u Bezbednom režimu"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Vidžeti su onemogućeni u Bezbednom režimu"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Prečica nije dostupna"</string>
+ <string name="home_screen" msgid="806512411299847073">"Početni ekran"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Prilagođene radnje"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Dodirnite i zadržite da biste izabrali vidžet."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dvaput dodirnite i zadržite da biste izabrali vidžet ili koristite prilagođene radnje."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d×%2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"širina od %1$d i visina od %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Dodirnite i zadržite da biste postavili ručno"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Automatski dodaj"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Pretražite aplikacije"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Aplikacije se učitavaju..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Nije pronađena nijedna aplikacija za „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Pretraži još aplikacija"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Obaveštenja"</string>
<string name="out_of_space" msgid="4691004494942118364">"Nema više prostora na ovom početnom ekranu."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Nema više prostora na traci Omiljeno"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Lista aplikacija"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Dozvoli rotaciju početnog ekrana"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Kada se telefon rotira"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Aktuelno podešavanje prikaza ne dozvoljava rotaciju"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Dodaj ikonu na početni ekran"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Za nove aplikacije"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Nepoznato"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Ukloni"</string>
<string name="abandoned_search" msgid="891119232568284442">"Pretraži"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Aplikacija za ovu ikonu nije instalirana. Možete da je uklonite ili da potražite aplikaciju i instalirate je ručno."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> se preuzima, završeno je <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> čeka na instaliranje"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Vidžeti za <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Dodaj na početni ekran"</string>
<string name="action_move_here" msgid="2170188780612570250">"Premesti stavku ovde"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Stavka je dodata na početni ekran"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 2c6d3d4..8dc1e10 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Изтегленото приложение е деактивирано в безопасния режим"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Приспособленията са деактивирани в безопасния режим"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Няма достъп до прекия път"</string>
+ <string name="home_screen" msgid="806512411299847073">"Начален екран"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Персонализирани действия"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Докоснете и задръжте за избор на приспособление."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Докоснете двукратно и задръжте за избор на приспособление или използвайте персонализирани действия."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Ширина %1$d и височина %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Докоснете и задръжте, за да поставите ръчно"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Автоматично добавяне"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Търсене в приложенията"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Приложенията се зареждат…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Няма намерени приложения, съответстващи на „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Търсене на още приложения"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Известия"</string>
<string name="out_of_space" msgid="4691004494942118364">"На този начален екран няма повече място."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Няма повече място в областта с любимите"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Списък с приложения"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Разрешаване на завъртането на началния екран"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"При завъртане на телефона"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Текущата настройка на екрана не разрешава завъртане"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Добавяне на икона към началния екран"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"За нови приложения"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Няма информация"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Премахване"</string>
<string name="abandoned_search" msgid="891119232568284442">"Търсене"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Приложението за тази икона не е инсталирано. Можете да я премахнете или да потърсите приложението и да го инсталирате ръчно."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> се изтегля. Завършено: <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> изчаква инсталиране"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Приспособления за <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Добавяне към началния екран"</string>
<string name="action_move_here" msgid="2170188780612570250">"Преместване на елемента тук"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Елементът е добавен към началния екран"</string>
diff --git a/res/values-bn-rBD/config.xml b/res/values-bn-rBD/config.xml
new file mode 100644
index 0000000..56f98c3
--- /dev/null
+++ b/res/values-bn-rBD/config.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="icon_shape_override_paths_values">
+ <item msgid="6640290598899495380"></item>
+ <item msgid="4009824731445917273">"M50,0L100,0 100,100 0,100 0,0z"</item>
+ <item msgid="3964229851574011244">"M50,0L80,0 A20,20,0,0 1 100,20 L100,80 A20,20,0,0 1 80,100 L20,100 A20,20,0,0 1 0,80 L 0,20 A20,20,0,0 1 20,0z"</item>
+ <item msgid="363553284746233331">"M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z"</item>
+ <item msgid="4319038504053267455">"M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0"</item>
+ <item msgid="483370082941112059">"M50,0A50,30 0,0,1 100,30V70A50,30 0,0,1 0,70V30A50,30 0,0,1 50,0z"</item>
+ </string-array>
+ <!-- no translation found for icon_shape_override_paths_names:0 (4837899951986816538) -->
+</resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index d5108e9..065019a 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"ডাউনলোড করা অ্যাপ্লিকেশান নিরাপদ মোডে অক্ষম রয়েছে"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"সুরক্ষিত মোডে উইজেট নিষ্ক্রিয় থাকে"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"শর্টকাটগুলি অনুপলব্ধ"</string>
+ <string name="home_screen" msgid="806512411299847073">"হোম স্ক্রীন"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"কাস্টম অ্যাকশন"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"একটি উইজেট তুলতে তা স্পর্শ করে ধরে রাখুন৷"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"কোনো উইজেট বেছে নিতে দুবার-আলতো চেপে ধরে থাকুন অথবা কাস্টম ক্রিয়াগুলি ব্যবহার করুন৷"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%2$d উচ্চতা অনুযায়ী %1$d প্রস্থ"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"নিজে যোগ করতে টাচ করে ধরে রাখুন"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"স্বয়ংক্রিয়ভাবে যোগ করুন"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"অ্যাপ্লিকেশানগুলি অনুসন্ধান করুন"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"অ্যাপ্লিকেশানগুলি লোড হচ্ছে..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" এর সাথে মেলে এমন কোনো অ্যাপ্লিকেশান পাওয়া যায়নি"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"আরো অ্যাপ্লিকেশানের জন্য অনুসন্ধান করুন"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"বিজ্ঞপ্তি"</string>
<string name="out_of_space" msgid="4691004494942118364">"এই হোম স্ক্রীনে আর কোনো জায়গা নেই৷"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"পছন্দসই ট্রে-তে আর কোনো জায়গা নেই"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"অ্যাপ্লিকেশানগুলির তালিকা"</string>
@@ -71,6 +76,11 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"হোমস্ক্রীন ঘোরানোর অনুমতি দিন"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"যখন ফোনটি ঘোরানো হয়"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"বর্তমান প্রদর্শনের সেটিংস ঘোরানোর মঞ্জুরি দেয় না"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"হোম স্ক্রিনে আইকন যোগ করুন"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"নতুন অ্যাপ্লিকেশানগুলির জন্যে"</string>
+ <string name="icon_shape_override_label" msgid="2977264953998281004">"আইকনের আকৃতি পরিবর্তন করুন"</string>
+ <string name="icon_shape_no_override" msgid="3678524428085518367">"পরিবর্তন করবেন না"</string>
+ <string name="icon_shape_override_progress" msgid="3461735694970239908">"আইকনের আকৃতি পরিবর্তন করা হচ্ছে"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"অজানা"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"সরান"</string>
<string name="abandoned_search" msgid="891119232568284442">"অনুসন্ধান"</string>
@@ -78,6 +88,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"এই আইকনের অ্যাপ্লিকেশানটি ইন্সটল করা নাই। আপনি এটি সরাতে পারেন বা অ্যাপ্লিকেশানটি অনুসন্ধান করে এটি নিজে ইন্সটল করতে পারেন।"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ডাউনলোড হচ্ছে <xliff:g id="PROGRESS">%2$s</xliff:g> সম্পন্ন হয়েছে"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ইনস্টলের অপেক্ষায় রয়েছে"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> উইজেট"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"হোম স্ক্রীনে যোগ করুন"</string>
<string name="action_move_here" msgid="2170188780612570250">"এখানে আইটেম সরান"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"হোম স্ক্রীনে আইটেম যোগ করা হয়েছে"</string>
@@ -93,10 +104,10 @@
<string name="create_folder_with" msgid="4050141361160214248">"এর সাথে ফোল্ডার তৈরি করুন: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_created" msgid="6409794597405184510">"ফোল্ডার তৈরি করা হয়েছে"</string>
<string name="action_move_to_workspace" msgid="1603837886334246317">"হোম স্ক্রীনে সরান"</string>
- <string name="action_move_screen_left" msgid="8854216831569401665">"স্ক্রীন বাম দিকে সরান"</string>
+ <string name="action_move_screen_left" msgid="8854216831569401665">"স্ক্রীন বাঁ দিকে সরান"</string>
<string name="action_move_screen_right" msgid="329334910274311123">"স্ক্রীন ডান দিকে সরান"</string>
<string name="screen_moved" msgid="266230079505650577">"স্ক্রীন সরানো হয়েছে"</string>
- <string name="action_resize" msgid="1802976324781771067">"পুনরায় আকার দিন"</string>
+ <string name="action_resize" msgid="1802976324781771067">"আবার আকার দিন"</string>
<string name="action_increase_width" msgid="8773715375078513326">"প্রস্থ বাড়ান"</string>
<string name="action_increase_height" msgid="459390020612501122">"উচ্চতা বাড়ান"</string>
<string name="action_decrease_width" msgid="1374549771083094654">"প্রস্থ কমান"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 579b95a..63aaaba 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -27,18 +27,23 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Preuzeta aplikacija je onemogućena u sigurnom načinu rada"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Vidžeti su onemogućeni u sigurnom načinu rada."</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Prečica nije dostupna"</string>
+ <string name="home_screen" msgid="806512411299847073">"Početni ekran"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Prilagođene akcije"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Dodirnite & i držite da biste uzeli dodatak."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dvaput dodirnite & i držite da biste uzeli vidžet ili koristite prilagođene radnje."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Širina %1$d, visina %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Dodirnite i držite da postavite ručno"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Dodaj automatski"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Pretraži aplikacije"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Aplikacije se učitavaju…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Nije pronađena nijedna aplikacija koja odgovara upitu \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Pretraži više aplikacija"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Obavještenja"</string>
<string name="out_of_space" msgid="4691004494942118364">"Na ovom početnom ekranu nema više prostora."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Nema više prostora u ladici Favoriti"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Spisak aplikacija"</string>
- <string name="all_apps_home_button_label" msgid="252062713717058851">"Tipka za početak"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Početna"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"Ukloni"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Deinstaliraj"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Informacije o aplikaciji"</string>
@@ -64,13 +69,21 @@
<string name="folder_renamed" msgid="1794088362165669656">"Ime fascikle je promijenjeno u <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format" msgid="6629239338071103179">"Fascikla: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="widget_button_text" msgid="2880537293434387943">"Dodaci"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Pozadine"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Pozadinske slike"</string>
<string name="settings_button_text" msgid="8119458837558863227">"Postavke"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"Onemogućio vaš administrator"</string>
<string name="accessibility_action_overview" msgid="6257665857640347026">"Pregled"</string>
<string name="allow_rotation_title" msgid="7728578836261442095">"Dozvoli rotiranje početnog ekrana"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Kada se telefon zarotira"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Trenutne postavke ekrana ne dozvoljavaju rotiranje"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Dodajte ikonu na početni ekran"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Za nove aplikacije"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Nepoznato"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Ukloni"</string>
<string name="abandoned_search" msgid="891119232568284442">"Traži"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Aplikacija za ovu ikonu nije instalirana. Možete je ukloniti ili potražiti aplikaciju i ručno je instalirati."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> se preuzima, završeno <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> čeka da se instalira"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Vidžeti za aplikaciju <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Dodaj na početni ekran"</string>
<string name="action_move_here" msgid="2170188780612570250">"Premjesti stavku ovdje"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Stavka je dodana na Početni ekran."</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 4ba0498..bfbd31e 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"L\'aplicació que has baixat està desactivada al mode segur."</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"En Mode segur, els widgets estan desactivats."</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"La drecera no està disponible"</string>
+ <string name="home_screen" msgid="806512411299847073">"Pantalla d\'inici"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Accions personalitzades"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Mantén premut un widget per triar-lo."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Fes doble toc i mantén premut per seleccionar un widget o per utilitzar les accions personalitzades."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d d\'amplada per %2$d d\'alçada"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Toca i mantén premut l\'element per col·locar-lo manualment"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Afegeix automàticament"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Cerca a les aplicacions"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"S\'estan carregant les aplicacions..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"No s\'ha trobat cap aplicació que coincideixi amb <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Cerca més aplicacions"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Notificacions"</string>
<string name="out_of_space" msgid="4691004494942118364">"Ja no queda espai en aquesta pantalla d\'inici."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"No hi ha més espai a la safata Preferits."</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Llista d\'aplicacions"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Permet la rotació de la pantalla d\'inici"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"En girar el telèfon"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"La configuració actual de la pantalla no permet la rotació"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Afegeix la icona a la pantalla d\'inici"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Per a les aplicacions noves"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Desconegut"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Suprimeix"</string>
<string name="abandoned_search" msgid="891119232568284442">"Cerca"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"L\'aplicació d\'aquesta icona no està instal·lada. Pots suprimir-la o cercar l\'aplicació i instal·lar-la manualment."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"S\'està baixant <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> completat"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"S\'està esperant per instal·lar <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widgets de: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Afegeix a la pantalla d\'inici"</string>
<string name="action_move_here" msgid="2170188780612570250">"Mou l\'element aquí"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"S\'ha afegit l\'element a la pantalla d\'inici"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 373920c..7c61023 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Stažená aplikace je v nouzovém režimu zakázána"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"V nouzovém režimu jsou widgety zakázány."</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Zkratka není k dispozici"</string>
+ <string name="home_screen" msgid="806512411299847073">"Plocha"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Vlastní akce"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Widget vyberete dotykem a podržením."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dvojitým klepnutím a podržením vyberte widget, případně použijte vlastní akce."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"šířka %1$d, výška %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Chcete-li položku umístit ručně, klepněte na ni a podržte ji"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Přidat automaticky"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Hledat aplikace"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Načítání aplikací…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Dotazu „<xliff:g id="QUERY">%1$s</xliff:g>“ neodpovídají žádné aplikace"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Vyhledat další aplikace"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Oznámení"</string>
<string name="out_of_space" msgid="4691004494942118364">"Na této ploše již není místo."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Na panelu Oblíbené položky již není místo."</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Seznam aplikací"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Povolit otáčení plochy"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Při otočení telefonu"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Aktuální nastavení displeje neumožňuje otáčení"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Přidat ikonu na plochu"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Pro nové aplikace"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Neznámé"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Odstranit"</string>
<string name="abandoned_search" msgid="891119232568284442">"Hledat"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Aplikace pro tuto ikonu není nainstalována. Můžete ikonu odstranit nebo zkusit aplikaci vyhledat a nainstalovat ručně."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Stahování aplikace <xliff:g id="NAME">%1$s</xliff:g> (dokončeno <xliff:g id="PROGRESS">%2$s</xliff:g>)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Instalace aplikace <xliff:g id="NAME">%1$s</xliff:g> čeká na zahájení"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widgety <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Přidat na plochu"</string>
<string name="action_move_here" msgid="2170188780612570250">"Přesunout položku sem"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Položka byla přidána na plochu"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 9304140..65e5134 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Downloadet app er deaktiveret i sikker tilstand"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets er deaktiveret i Beskyttet tilstand"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Genvejen er ikke tilgængelig"</string>
+ <string name="home_screen" msgid="806512411299847073">"Startskærm"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Brugerdefinerede handlinger"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Tryk på en widget, og hold den nede for at vælge."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Tryk to gange, og hold fingeren nede for at vælge en widget eller bruge tilpassede handlinger."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d i bredden og %2$d i højden"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Tryk, og hold fingeren nede for at placere manuelt"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Tilføj automatisk"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Søg i Apps"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Indlæser apps…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Der blev ikke fundet nogen apps, som matcher \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Søg efter flere apps"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Underretninger"</string>
<string name="out_of_space" msgid="4691004494942118364">"Der er ikke mere plads på denne startskærm."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Der er ikke mere plads i bakken Foretrukne"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Liste med apps"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Tillad rotation af startskærmen"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Når telefonen roteres"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Den aktuelle indstilling for visning tillader ikke rotation"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Føj ikon til startskærmen"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"For nye apps"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Ukendt"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Fjern"</string>
<string name="abandoned_search" msgid="891119232568284442">"Søg"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Appen, der hører til dette ikon, er ikke installeret. Du kan fjerne den eller prøve at søge efter appen og installere den manuelt."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloades. <xliff:g id="PROGRESS">%2$s</xliff:g> er gennemført"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> venter på at installere"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>-widgets"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Føj til startskærm"</string>
<string name="action_move_here" msgid="2170188780612570250">"Flyt elementet hertil"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Elementet er føjet til startskærmen"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index e461d46..59ce70c 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Heruntergeladene App im abgesicherten Modus deaktiviert"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets im abgesicherten Modus deaktiviert"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Verknüpfung nicht verfügbar"</string>
+ <string name="home_screen" msgid="806512411299847073">"Startbildschirm"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Benutzerdefinierte Aktionen"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Zum Hinzufügen Widget berühren und halten"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Zum Hinzufügen auf Widget doppeltippen und gedrückt halten oder benutzerdefinierte Aktionen verwenden."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d breit und %2$d hoch"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Zum manuellen Hinzufügen gedrückt halten"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Automatisch hinzufügen"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"In Apps suchen"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Apps werden geladen..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Keine Apps für \"<xliff:g id="QUERY">%1$s</xliff:g>\" gefunden"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Weitere Apps suchen"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Benachrichtigungen"</string>
<string name="out_of_space" msgid="4691004494942118364">"Auf diesem Startbildschirm ist kein Platz mehr vorhanden."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Ablage \"Favoriten\" ist voll."</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Liste der Apps"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Drehung des Startbildschirms zulassen"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Bei Drehung des Smartphones"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Die aktuelle \"Display\"-Einstellung verhindert eine Drehung der Anzeige"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Symbol zu Startbildschirm hinzufügen"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Für neue Apps"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Unbekannt"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Entfernen"</string>
<string name="abandoned_search" msgid="891119232568284442">"Suchen"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Die App für dieses Symbol ist nicht installiert. Du kannst das Symbol entfernen oder die App lokalisieren und dann manuell installieren."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> wird heruntergeladen, <xliff:g id="PROGRESS">%2$s</xliff:g> abgeschlossen"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Warten auf Installation von <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>-Widgets"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Zum Startbildschirm hinzufügen"</string>
<string name="action_move_here" msgid="2170188780612570250">"Element hierhin verschieben"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Element zum Startbildschirm hinzugefügt"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 119232d..d8da34b 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Η λήψη εφαρμογών απενεργοποήθηκε στην Ασφαλή λειτουργία"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Τα γραφικά στοιχεία απενεργοποιήθηκαν στην ασφαλή λειτουργία"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Η συντόμευση δεν είναι διαθέσιμη"</string>
+ <string name="home_screen" msgid="806512411299847073">"Αρχική οθόνη"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Προσαρμοσμένες ενέργειες"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Αγγίξτε παρατεταμένα για να πάρετε ένα γραφ.στοιχ."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Πατήστε δύο φορές παρατεταμένα για επιλογή γραφικού στοιχείου ή χρήση προσαρμοσμένων ενεργειών."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Πλάτος %1$d επί ύψος %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Αγγίξτε παρατεταμένα για μη αυτόματη τοποθέτηση"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Αυτόματη προσθήκη"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Αναζήτηση εφαρμογών"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Φόρτωση εφαρμογών…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Δεν βρέθηκαν εφαρμογές για το ερώτημα \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Αναζήτηση περισσότερων εφαρμογών"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Ειδοποιήσεις"</string>
<string name="out_of_space" msgid="4691004494942118364">"Δεν υπάρχει χώρος σε αυτήν την αρχική οθόνη."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Δεν υπάρχει επιπλέον χώρος στην περιοχή Αγαπημένα"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Λίστα εφαρμογών"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Να επιτρέπεται η περιστροφή της αρχικής οθόνης"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Όταν το τηλέφωνο περιστρέφεται"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Η τρέχουσα ρύθμιση οθόνης δεν επιτρέπει την περιστροφή"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Προσθήκη εικονιδίου στην Αρχική οθόνη"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Για νέες εφαρμογές"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Άγνωστο"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Κατάργηση"</string>
<string name="abandoned_search" msgid="891119232568284442">"Αναζήτηση"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Η εφαρμογή γι\' αυτό το εικονίδιο δεν είναι εγκατεστημένη. Μπορείτε να το καταργήσετε ή να αναζητήσετε την εφαρμογή και να την εγκαταστήσετε με μη αυτόματο τρόπο."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Λήψη <xliff:g id="NAME">%1$s</xliff:g>, ολοκληρώθηκε <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> σε αναμονή για εγκατάσταση"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Γραφικά στοιχεία <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Προσθήκη στην αρχική οθόνη"</string>
<string name="action_move_here" msgid="2170188780612570250">"Μετακίνηση στοιχείου εδώ"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Το στοιχείο προστέθηκε στην αρχική οθόνη"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 44daaa8..ecfb0f5 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Downloaded app disabled in Safe mode"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
+ <string name="home_screen" msgid="806512411299847073">"Home screen"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Customised actions"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Touch & hold to pick up a widget."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Double-tap & hold to pick up a widget or use customised actions."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d wide by %2$d high"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Touch & hold to place manually"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Add automatically"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Search Apps"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Loading Apps…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"No Apps found matching \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Search for more apps"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
<string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"No more room in the Favourites tray"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Apps list"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Allow Homescreen rotation"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"When phone is rotated"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Current display setting doesn\'t permit rotation"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Add icon to Home screen"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"For new apps"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Unknown"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Remove"</string>
<string name="abandoned_search" msgid="891119232568284442">"Search"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"The app for this icon isn\'t installed. You can remove it, or search for the app and install it manually."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloading, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> waiting to install"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> widgets"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Add to Home screen"</string>
<string name="action_move_here" msgid="2170188780612570250">"Move item here"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Item added to home screen"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 44daaa8..ecfb0f5 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Downloaded app disabled in Safe mode"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
+ <string name="home_screen" msgid="806512411299847073">"Home screen"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Customised actions"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Touch & hold to pick up a widget."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Double-tap & hold to pick up a widget or use customised actions."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d wide by %2$d high"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Touch & hold to place manually"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Add automatically"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Search Apps"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Loading Apps…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"No Apps found matching \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Search for more apps"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
<string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"No more room in the Favourites tray"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Apps list"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Allow Homescreen rotation"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"When phone is rotated"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Current display setting doesn\'t permit rotation"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Add icon to Home screen"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"For new apps"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Unknown"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Remove"</string>
<string name="abandoned_search" msgid="891119232568284442">"Search"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"The app for this icon isn\'t installed. You can remove it, or search for the app and install it manually."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloading, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> waiting to install"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> widgets"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Add to Home screen"</string>
<string name="action_move_here" msgid="2170188780612570250">"Move item here"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Item added to home screen"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 44daaa8..ecfb0f5 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Downloaded app disabled in Safe mode"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
+ <string name="home_screen" msgid="806512411299847073">"Home screen"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Customised actions"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Touch & hold to pick up a widget."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Double-tap & hold to pick up a widget or use customised actions."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d wide by %2$d high"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Touch & hold to place manually"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Add automatically"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Search Apps"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Loading Apps…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"No Apps found matching \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Search for more apps"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
<string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"No more room in the Favourites tray"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Apps list"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Allow Homescreen rotation"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"When phone is rotated"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Current display setting doesn\'t permit rotation"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Add icon to Home screen"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"For new apps"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Unknown"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Remove"</string>
<string name="abandoned_search" msgid="891119232568284442">"Search"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"The app for this icon isn\'t installed. You can remove it, or search for the app and install it manually."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloading, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> waiting to install"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> widgets"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Add to Home screen"</string>
<string name="action_move_here" msgid="2170188780612570250">"Move item here"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Item added to home screen"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 4bd6c88..4a57396 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplicación descargada inhabilitada en modo seguro"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets inhabilitados en modo seguro"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"El acceso directo no está disponible"</string>
+ <string name="home_screen" msgid="806512411299847073">"Pantalla principal"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Acciones personalizadas"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Mantén presionado el widget que desees elegir."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Presiona dos veces y mantén presionado para elegir un widget o usa una acción personalizada."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de ancho por %2$d de alto"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Mantén presionado para ubicarlo manualmente"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Agregar automáticamente"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Buscar aplicaciones"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Cargando aplicaciones…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"No hay aplicaciones que coincidan con <xliff:g id="QUERY">%1$s</xliff:g>."</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Buscar más apps"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Notificaciones"</string>
<string name="out_of_space" msgid="4691004494942118364">"No hay más espacio en esta pantalla principal."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"La bandeja de favoritos está llena."</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Lista de apps"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Permitir la rotación de la pantalla principal"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Al girar el teléfono"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"La configuración actual no permite la rotación de la pantalla"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Agregar ícono a la pantalla principal"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Para nuevas apps"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Desconocido"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Eliminar"</string>
<string name="abandoned_search" msgid="891119232568284442">"Buscar"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"La aplicación para este ícono no está instalada. Puedes eliminar el ícono o buscar la aplicación e instarla manualmente."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Se completó el <xliff:g id="PROGRESS">%2$s</xliff:g> de la descarga de <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Instalación de <xliff:g id="NAME">%1$s</xliff:g> en espera"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widgets de <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Agregar a la pantalla principal"</string>
<string name="action_move_here" msgid="2170188780612570250">"Mover elemento aquí"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Se agregó el elemento a la pantalla principal."</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index a4fc620..3db1aa8 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplicación descargada inhabilitada en modo seguro"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets inhabilitados en modo seguro"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Acceso directo no disponible"</string>
+ <string name="home_screen" msgid="806512411299847073">"Pantalla de inicio"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Acciones personalizadas"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Mantén pulsado el widget que quieras seleccionar."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Toca dos veces y mantén pulsado el widget que quieras seleccionar o utiliza acciones personalizadas."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de ancho por %2$d de alto"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Mantenlo pulsado para añadirlo manualmente"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Añadir automáticamente"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Busca aplicaciones"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Cargando aplicaciones…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"No se han encontrado aplicaciones que contengan \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Buscar más aplicaciones"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Notificaciones"</string>
<string name="out_of_space" msgid="4691004494942118364">"No queda espacio en la pantalla de inicio."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"La bandeja de favoritos está completa"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Lista de aplicaciones"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Permitir rotación de la pantalla de inicio"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Al girar el teléfono"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"La configuración de pantalla actual no permite girar la pantalla"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Añadir icono a la pantalla de inicio"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Para nuevas aplicaciones"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Desconocido"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Quitar"</string>
<string name="abandoned_search" msgid="891119232568284442">"Buscar"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"La aplicación de este icono no está instalada. Puedes quitar el icono o buscar la aplicación e instalarla manualmente."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Descargando <xliff:g id="NAME">%1$s</xliff:g> (<xliff:g id="PROGRESS">%2$s</xliff:g> completado)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Esperando para instalar <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widgets de <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Añadir a la pantalla de inicio"</string>
<string name="action_move_here" msgid="2170188780612570250">"Mover elemento aquí"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Elemento añadido a la pantalla de inicio"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 76ef5c6..1a4d809 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Deskargatutako aplikazioa modu seguruan desgaitu da"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgetak desgaitu egin dira modu seguruan"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Lasterbideak ez daude erabilgarri"</string>
+ <string name="home_screen" msgid="806512411299847073">"Hasierako pantaila"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Ekintza pertsonalizatuak"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Eduki sakatuta widgeta aukeratzeko."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Sakatu birritan eta eduki sakatuta widgeta aukeratzeko edo ekintza pertsonalizatuak erabiltzeko."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d zabal eta %2$d luze"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Eduki sakatuta eskuz gehitzeko"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Gehitu automatikoki"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Bilatu aplikazioetan"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Aplikazioak kargatzen…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Ez da aurkitu \"<xliff:g id="QUERY">%1$s</xliff:g>\" bilaketarekin bat datorren aplikaziorik"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Bilatu aplikazio gehiago"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Jakinarazpenak"</string>
<string name="out_of_space" msgid="4691004494942118364">"Hasierako pantaila honetan ez dago toki gehiago."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Ez dago toki gehiago Gogokoak erretiluan"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Aplikazioen zerrenda"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Baimendu hasierako pantaila biratzea"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Telefonoa biratzen denean"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Uneko pantaila-ezarpenak ez du onartzen ikuspegia biratzea"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Gehitu ikonoa hasierako pantailan"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Aplikazio berrietan"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Ezezaguna"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Kendu"</string>
<string name="abandoned_search" msgid="891119232568284442">"Bilatu"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Ikono honen aplikazioa ez dago instalatuta. Ikonoa ken dezakezu, edo aplikazioa bilatu eta eskuz instalatu."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> deskargatzen, <xliff:g id="PROGRESS">%2$s</xliff:g> osatuta"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> instalatzeko zain"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> widgetak"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Gehitu hasierako pantailan"</string>
<string name="action_move_here" msgid="2170188780612570250">"Ekarri elementua hona"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Gehitu da elementua hasierako pantailan"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 95675a7..3a9a85a 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"برنامه بارگیری شده در حالت ایمن غیرفعال شد"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"ابزارکها در حالت ایمن غیرفعال هستند"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"میانبر دردسترس نیست"</string>
+ <string name="home_screen" msgid="806512411299847073">"صفحه اصلی"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"عملکردهای سفارشی"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"برای انتخاب ابزارک لمس کنید و نگه دارید."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"برای انتخاب یک ابزارک، دو ضربه سریع بزنید و نگهدارید یا از اقدامات سفارشی استفاده کنید."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d عرض در %2$d طول"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"برای قرار دادن بهصورت دستی لمس کنید و بکشید"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"افزودن خودکار"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"جستجوی برنامهها"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"در حال بارگیری برنامهها..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"هیچ برنامهای مطابق با «<xliff:g id="QUERY">%1$s</xliff:g>» پیدا نشد"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"جستجوی برنامههای بیشتر"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"اعلانها"</string>
<string name="out_of_space" msgid="4691004494942118364">"فضای بیشتری در این صفحه اصلی موجود نیست."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"فضای بیشتری در سینی موارد دلخواه وجود ندارد"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"فهرست برنامهها"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"امکان دادن به چرخش صفحه اصلی"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"وقتی تلفن چرخانده میشود"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"تنظیم نمایشگر کنونی اجازه چرخش نمیدهد"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"افزودن نماد به صفحه اصلی"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"برای برنامههای جدید"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"نامشخص"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"حذف"</string>
<string name="abandoned_search" msgid="891119232568284442">"جستجو"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"برنامه برای این نماد نصب نشده است. میتوانید آن را حذف کنید یا سعی کنید برنامه را جستجو کنید و آن را به صورت دستی نصب کنید."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"درحال بارگیری <xliff:g id="NAME">%1$s</xliff:g>، <xliff:g id="PROGRESS">%2$s</xliff:g> کامل شد"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> درانتظار نصب"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"ابزارکهای <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"افزودن به صفحه اصلی"</string>
<string name="action_move_here" msgid="2170188780612570250">"انتقال مورد به اینجا"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"مورد به صفحه اصلی اضافه شد"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 40e6d96..6e4bc93 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Ladattu sovellus poistettiin käytöstä suojatussa tilassa"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgetit poistettu käytöstä vikasietotilassa"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Pikakuvake ei ole käytettävissä."</string>
+ <string name="home_screen" msgid="806512411299847073">"Aloitusnäyttö"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Muokatut toiminnot"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Valitse widget painamalla sitä pitkään."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Valitse widget tai käytä muokattuja toimintoja kaksoisnapauttamalla ja painamalla kohdetta pitkään."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Leveys: %1$d, korkeus: %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Sijoita manuaalisesti koskettamalla pitkään."</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Lisää automaattisesti"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Sovellushaku"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Ladataan sovelluksia…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"”<xliff:g id="QUERY">%1$s</xliff:g>” ei palauttanut sovelluksia."</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Hae lisää sovelluksia"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Ilmoitukset"</string>
<string name="out_of_space" msgid="4691004494942118364">"Tässä aloitusruudussa ei ole enää tilaa."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Suosikit-valikossa ei ole enää tilaa"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Sovellusluettelo"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Salli aloitusnäytön kiertäminen"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Kun puhelinta kierretään"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Nykyiset näyttöasetukset eivät salli näytön kiertämistä."</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Lisää kuvake aloitusruutuun"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Uusille sovelluksille"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Tuntematon"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Poista"</string>
<string name="abandoned_search" msgid="891119232568284442">"Haku"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Kuvakkeen sovellusta ei ole asennettu. Voit poistaa kuvakkeen tai etsiä sovelluksen ja asentaa sen manuaalisesti."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> latautuu, valmiina <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> odottaa asennusta"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widgetit: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Lisää aloitusnäytölle"</string>
<string name="action_move_here" msgid="2170188780612570250">"Siirrä kohde tänne"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Kohde lisättiin aloitusnäytölle."</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 01d6b27..a9f2947 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"L\'application téléchargée est désactivée en mode sécurisé."</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets désactivés en mode sans échec"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Le raccourci n\'est pas disponible"</string>
+ <string name="home_screen" msgid="806512411299847073">"Écran d\'accueil"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Actions personnalisées"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Maintenez un doigt sur le widget pour l\'ajouter."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Touchez 2x un widget et maintenez doigt dessus pour l’ajouter ou utiliser des actions personnalisées"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de largeur sur %2$d de hauteur"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Maintenez le doigt sur l\'élément pour le placer manuellement"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Ajouter automatiquement"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Rechercher des applications"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Chargement des applications en cours..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Aucune application trouvée correspondant à « <xliff:g id="QUERY">%1$s</xliff:g> »"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Rechercher plus d\'applications"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
<string name="out_of_space" msgid="4691004494942118364">"Pas d\'espace libre sur l\'écran d\'accueil."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Il n\'y a plus d\'espace dans la zone des favoris"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Liste des applications"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Autoriser la rotation de l\'écran d\'accueil"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Lorsque vous faites pivoter le téléphone"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Le mode d\'affichage actuel ne permet pas le pivotement"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Ajouter l\'icône à l\'écran d\'accueil"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Pour les nouvelles applications"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Inconnu"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Supprimer"</string>
<string name="abandoned_search" msgid="891119232568284442">"Rechercher"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"L\'application liée à cette icône n\'est pas installée. Vous pouvez la supprimer ou rechercher l\'application et l\'installer manuellement."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Téléchargement de <xliff:g id="NAME">%1$s</xliff:g> : <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> en attente d\'installation"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widgets pour <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Ajouter à l\'écran d\'accueil"</string>
<string name="action_move_here" msgid="2170188780612570250">"Déplacer l\'élément ici"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Élément ajouté à l\'écran d\'accueil"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index f42748c..2d7a38c 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"L\'application téléchargée est désactivée en mode sécurisé."</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Les widgets sont désactivés en mode sécurisé."</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Raccourci non disponible"</string>
+ <string name="home_screen" msgid="806512411299847073">"Écran d\'accueil"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Actions personnalisées"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"App. de manière prolongée pour sélectionner widget."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Appuyez 2 fois et maintenez la pression pour sélectionner widget ou utilisez actions personnalisées."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d x %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de largeur et %2$d de hauteur"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Appuyez de manière prolongée pour placer l\'élément manuellement"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Ajouter automatiquement"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Rechercher dans les applications"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Chargement des applications en cours…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Aucune application ne correspond à la requête \"<xliff:g id="QUERY">%1$s</xliff:g>\"."</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Rechercher plus d\'applications"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
<string name="out_of_space" msgid="4691004494942118364">"Pas d\'espace libre sur cet écran d\'accueil."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Plus d\'espace disponible dans la zone de favoris."</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Liste d\'applications"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Autoriser la rotation de l\'écran d\'accueil"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Lorsque vous faites pivoter le téléphone"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Le paramètre d\'affichage actuel n\'autorise pas la rotation."</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Ajouter l\'icône à l\'écran d\'accueil"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Pour les nouvelles applications"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Inconnu"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Supprimer"</string>
<string name="abandoned_search" msgid="891119232568284442">"Rechercher"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"L\'application correspondant à cette icône n\'est pas installée. Vous pouvez supprimer cette dernière, ou essayer de rechercher l\'application et de l\'installer manuellement."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> en cours de téléchargement, <xliff:g id="PROGRESS">%2$s</xliff:g> effectué(s)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> en attente d\'installation"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widgets <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Ajouter à l\'écran d\'accueil"</string>
<string name="action_move_here" msgid="2170188780612570250">"Déplacer l\'élément ici"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"L\'élément a bien été ajouté à l\'écran d\'accueil."</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index c03ecdc..e8213b5 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"A aplicación que descargaches está desactivada no modo seguro"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Os widgets están desactivados no modo seguro"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"O atallo non está dispoñible"</string>
+ <string name="home_screen" msgid="806512411299847073">"Pantalla de inicio"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Accións personalizadas"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Mantén premido un widget para seleccionalo."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Toca dúas veces e mantén premido para seleccionar un widget ou utiliza accións personalizadas."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de largo por %2$d de alto"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Mantén premido o elemento para colocalo manualmente"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Engadir automaticamente"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Aplicacións de busca"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Cargando aplicacións..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Non se atoparon aplicacións que coincidan con \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Buscar máis aplicacións"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Notificacións"</string>
<string name="out_of_space" msgid="4691004494942118364">"Non hai máis espazo nesta pantalla de inicio."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Non hai máis espazo na bandexa de favoritos"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Lista de aplicacións"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Permitir xirar a pantalla de inicio"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Ao xirar o teléfono"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"A configuración de visualización actual non permite xirar a pantalla"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Engadir icona á pantalla de inicio"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Para novas aplicacións"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Descoñecido"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Eliminar"</string>
<string name="abandoned_search" msgid="891119232568284442">"Buscar"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"A aplicación para esta icona non está instalada. Podes eliminala ou buscar a aplicación e instalala manualmente."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Descargando <xliff:g id="NAME">%1$s</xliff:g> (<xliff:g id="PROGRESS">%2$s</xliff:g> completado)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Esperando para instalar <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widgets de: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Engadir á pantalla de inicio"</string>
<string name="action_move_here" msgid="2170188780612570250">"Mover elemento aquí"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Engadiuse o elemento á pantalla de inicio"</string>
diff --git a/res/values-gu-rIN/config.xml b/res/values-gu-rIN/config.xml
new file mode 100644
index 0000000..56f98c3
--- /dev/null
+++ b/res/values-gu-rIN/config.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="icon_shape_override_paths_values">
+ <item msgid="6640290598899495380"></item>
+ <item msgid="4009824731445917273">"M50,0L100,0 100,100 0,100 0,0z"</item>
+ <item msgid="3964229851574011244">"M50,0L80,0 A20,20,0,0 1 100,20 L100,80 A20,20,0,0 1 80,100 L20,100 A20,20,0,0 1 0,80 L 0,20 A20,20,0,0 1 20,0z"</item>
+ <item msgid="363553284746233331">"M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z"</item>
+ <item msgid="4319038504053267455">"M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0"</item>
+ <item msgid="483370082941112059">"M50,0A50,30 0,0,1 100,30V70A50,30 0,0,1 0,70V30A50,30 0,0,1 50,0z"</item>
+ </string-array>
+ <!-- no translation found for icon_shape_override_paths_names:0 (4837899951986816538) -->
+</resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 39a176c..aa4992d 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"સુરક્ષિત મોડમાં ડાઉનલોડ કરેલ ઍપ્લિકેશન અક્ષમ કરી"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"સુરક્ષિત મોડમાં વિજેટ્સ અક્ષમ કર્યા"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"શૉર્ટકટ ઉપલબ્ધ નથી"</string>
+ <string name="home_screen" msgid="806512411299847073">"હોમ સ્ક્રીન"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"કસ્ટમ ક્રિયાઓ"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"વિજેટ ચૂંટવા માટે ટચ કરો અને પકડી રાખો."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"વિજેટ ચૂંટવા અથવા કસ્ટમ ક્રિયાઓનો ઉપયોગ કરવા માટે બે વાર ટેપ કરો અને પકડી રાખો."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d પહોળાઈ X %2$d ઊંચાઈ"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"મેન્યુઅલી મૂકવા માટે ટચ કરી દબાવી રાખો"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"આપમેળે ઉમેરો"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"શોધ ઍપ્લિકેશનો"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"ઍપ્લિકેશનો લોડ કરી રહ્યું છે…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" થી મેળ ખાતી કોઈ ઍપ્લિકેશનો મળી નથી"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"વધુ ઍપ્લિકેશનો શોધો"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"સૂચનાઓ"</string>
<string name="out_of_space" msgid="4691004494942118364">"આ હોમ સ્ક્રીન પર વધુ જગ્યા નથી."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"મનપસંદ ટ્રે પર વધુ જગ્યા નથી"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"ઍપ્લિકેશનોની સૂચિ"</string>
@@ -71,6 +76,11 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"હોમ સ્ક્રીનને ફેરવવાની મંજૂરી આપો"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"જ્યારે ફોન ફેરવવામાં આવે ત્યારે"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"વર્તમાન પ્રદર્શન સેટિંગ ફેરવવાની પરવાનગી આપતી નથી"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"હોમ સ્ક્રીન પર આઇકન ઉમેરો"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"નવી ઍપ્લિકેશનો માટે"</string>
+ <string name="icon_shape_override_label" msgid="2977264953998281004">"આઇકનનો આકાર બદલો"</string>
+ <string name="icon_shape_no_override" msgid="3678524428085518367">"બદલશો નહીં"</string>
+ <string name="icon_shape_override_progress" msgid="3461735694970239908">"આઇકનના આકારમાં કરેલ ફેરફારો લાગુ કરી રહ્યા છીએ"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"અજાણ્યો"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"દૂર કરો"</string>
<string name="abandoned_search" msgid="891119232568284442">"શોધો"</string>
@@ -78,6 +88,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"આ આયકન માટેની ઍપ્લિકેશન ઇન્સ્ટોલ થયેલ નથી. તમે તેને દૂર કરી શકો છો અથવા ઍપ્લિકેશન માટે શોધ કરી અને તેને મેન્યુઅલી ઇન્સ્ટોલ કરી શકો છો."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ડાઉનલોડ કરી રહ્યાં છે, <xliff:g id="PROGRESS">%2$s</xliff:g> પૂર્ણ"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g>, ઇન્સ્ટૉલ થવાની રાહ જોઈ રહ્યું છે"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> વિજેટ"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"હોમ સ્ક્રીન પર ઉમેરો"</string>
<string name="action_move_here" msgid="2170188780612570250">"આઇટમ અહીં ખસેડો"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"હોમ સ્ક્રીનમાં આઇટમ ઉમેરી"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index f534e1e..ba03a92 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"डाउनलोड किए गए ऐप्स सुरक्षित मोड में अक्षम है"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"विजेट सुरक्षित मोड में अक्षम हैं"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"शॉर्टकट उपलब्ध नहीं है"</string>
+ <string name="home_screen" msgid="806512411299847073">"होम स्क्रीन"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"कस्टम कार्रवाई"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"विजेट को चुनने के लिए स्पर्श करके रखें."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"कोई विजेट चुनने के लिए डबल टैप करके रखें या कस्टम कार्रवाइयां चुनें."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d चौड़ाई गुणा %2$d ऊंचाई"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"मैन्युअल रूप से जोड़ने के लिए स्पर्श करके रखें"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"अपने आप जोड़ें"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ऐप्स खोजें"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"ऐप्स लोड हो रहे हैं..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" से मिलान करने वाला कोई ऐप नहीं मिला"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"अधिक ऐप्लिकेशन खोजें"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"नोटिफ़िकेशन"</string>
<string name="out_of_space" msgid="4691004494942118364">"इस होम स्क्रीन पर स्थान शेष नहीं है."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"पसंदीदा ट्रे में और स्थान नहीं है"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"ऐप्लिकेशन सूची"</string>
@@ -54,7 +59,7 @@
<string name="uninstall_system_app_text" msgid="4172046090762920660">"यह एक सिस्टम ऐप्लिकेशन है और इसे अनइंस्टॉल नहीं किया जा सकता."</string>
<string name="folder_hint_text" msgid="6617836969016293992">"अनामित फ़ोल्डर"</string>
<string name="disabled_app_label" msgid="6673129024321402780">"<xliff:g id="APP_NAME">%1$s</xliff:g> अक्षम है"</string>
- <string name="default_scroll_format" msgid="7475544710230993317">"पृष्ठ %2$d में से %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"पेज %2$d में से %1$d"</string>
<string name="workspace_scroll_format" msgid="8458889198184077399">"होम स्क्रीन %2$d में से %1$d"</string>
<string name="workspace_new_page" msgid="257366611030256142">"नया होम स्क्रीन पृष्ठ"</string>
<string name="folder_opened" msgid="94695026776264709">"फ़ोल्डर खोला गया, <xliff:g id="WIDTH">%1$d</xliff:g> गुणा <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"होमस्क्रीन घुमाने की अनुमति दें"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"फ़ोन घुुमाए जाने पर"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"वर्तमान प्रदर्शन सेटिंग घुमाने की अनुमति नहीं देती"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"होम स्क्रीन में आइकन जोड़ें"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"नए ऐप्लिकेशन के लिए"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"अज्ञात"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"निकालें"</string>
<string name="abandoned_search" msgid="891119232568284442">"खोजें"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"इस आइकन का ऐप्स इंस्टॉल नहीं है. आप उसे निकाल सकते हैं या ऐप्स की खोज करके उसे मैन्युअल रूप से इंस्टॉल कर सकते हैं."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> डाउनलोड हो रहा है, <xliff:g id="PROGRESS">%2$s</xliff:g> पूर्ण"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> के इंस्टॉल होने की प्रतीक्षा की जा रही है"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> विजेट"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"होम स्क्रीन में जोड़ें"</string>
<string name="action_move_here" msgid="2170188780612570250">"आइटम यहां ले जाएं"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"होम स्क्रीन में आइटम जोड़ा गया"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index b4fb50e..b6448f1 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Preuzeta aplikacija onemogućena je u Sigurnom načinu rada"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgeti su onemogućeni u Sigurnom načinu rada"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Prečac nije dostupan"</string>
+ <string name="home_screen" msgid="806512411299847073">"Početni zaslon"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Prilagođene radnje"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Dodirnite i držite kako biste podigli widget."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dodirnite dvaput i držite kako biste podigli widget ili pokušajte prilagođenim radnjama."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d širine i %2$d visine"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Dodirnite i zadržite stavku da biste je postavili ručno"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Dodaj automatski"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Pretraži aplikacije"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Učitavanje aplikacija…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Nema aplikacija podudarnih s upitom \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Traži više aplikacija"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Obavijesti"</string>
<string name="out_of_space" msgid="4691004494942118364">"Na ovom početnom zaslonu više nema mjesta."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Nema više prostora na traci Favoriti"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Popis aplikacija"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Dopusti zakretanje početnog zaslona"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Kada se telefon zakrene"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Trenutačna postavka zaslona ne dopušta zakretanje"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Dodaj ikonu na početni zaslon"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Za nove aplikacije"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Nepoznato"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Ukloni"</string>
<string name="abandoned_search" msgid="891119232568284442">"Traži"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Aplikacija ove ikone nije instalirana. Možete je ukloniti ili potražiti aplikaciju i instalirati je ručno."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Preuzimanje aplikacije <xliff:g id="NAME">%1$s</xliff:g>, dovršeno <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Čekanje na instaliranje aplikacije <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> widgeti"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Dodavanje na početni zaslon"</string>
<string name="action_move_here" msgid="2170188780612570250">"Premjesti stavku ovdje"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Stavka je dodana na početni zaslon"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 279749b..45a5054 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"A letöltött alkalmazás Csökkentett módban ki van kapcsolva"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"A modulok ki vannak kapcsolva Csökkentett módban"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"A gyorsparancs nem áll rendelkezésre"</string>
+ <string name="home_screen" msgid="806512411299847073">"Kezdőképernyő"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Egyéni műveletek"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Modul felvételéhez érintse meg, és tartsa lenyomva"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Modul mozgatásához koppintson rá duplán és tartsa lenyomva, vagy használjon egyéni műveleteket."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d széles és %2$d magas"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Tartsa lenyomva a manuális hozzáadáshoz"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Automatikus hozzáadás"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Alkalmazások keresése"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Alkalmazások betöltése…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Egy alkalmazás sem található a(z) „<xliff:g id="QUERY">%1$s</xliff:g>” lekérdezésre."</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"További alkalmazások keresése"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Értesítések"</string>
<string name="out_of_space" msgid="4691004494942118364">"Nincs több hely ezen a kezdőképernyőn."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Nincs több hely a Kedvencek tálcán"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Alkalmazások listája"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"A kezdőképernyő elforgatásának engedélyezése"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"A telefon elforgatásakor"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"A jelenlegi kijelzőbeállítások nem teszik lehetővé az elforgatást"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Ikon hozzáadása a kezdőképernyőhöz"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Új alkalmazásoknál"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Ismeretlen"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Eltávolítás"</string>
<string name="abandoned_search" msgid="891119232568284442">"Keresés"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Az ikonhoz tartozó alkalmazás nincs telepítve. Törölheti az ikont, vagy az alkalmazás megkeresése után manuálisan telepítheti azt."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"A(z) <xliff:g id="NAME">%1$s</xliff:g> letöltése, <xliff:g id="PROGRESS">%2$s</xliff:g> kész"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"A(z) <xliff:g id="NAME">%1$s</xliff:g> telepítésre vár"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>-modulok"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Hozzáadás a kezdőképernyőhöz"</string>
<string name="action_move_here" msgid="2170188780612570250">"Elem áthelyezése ide"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Elem hozzáadva a kezdőképernyőhöz"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index c7401a9..7c39a3f 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Ներբեռնված ծրագիրն անջատված է Անվտանգ ռեժիմում"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Վիջեթներն անջատված են անվտանգ ռեժիմում"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Դյուրանցումն անհասանելի է"</string>
+ <string name="home_screen" msgid="806512411299847073">"Հիմնական էկրան"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Հատուկ գործողություններ"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Հպեք և պահեք՝ վիջեթն ընտրելու համար:"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Կրկնակի հպեք և պահեք՝ վիջեթ ավելացնելու համար կամ օգտվեք հարմարեցրած գործողություններից:"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Լայնությունը՝ %1$d, բարձրությունը՝ %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Հպեք և պահեք՝ ձեռքով տեղադրելու համար"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Ավելացնել ավտոմատ կերպով"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Հավելվածների որոնում"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Հավելվածների բեռնում…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"«<xliff:g id="QUERY">%1$s</xliff:g>» հարցմանը համապատասխանող հավելվածներ չեն գտնվել"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Որոնել այլ հավելվածներ"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Ծանուցումներ"</string>
<string name="out_of_space" msgid="4691004494942118364">"Այլևս տեղ չկա այս հիմնական էկրանին:"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Ընտրյալների ցուցակում այլևս ազատ տեղ չկա"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Հավելվածների ցանկ"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Թույլ տալ հիմնական էկրանի պտտումը"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Հեռախոսը պտտելու դեպքում"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Ցուցադրման ընթացիկ կարգավորումներն արգելում են պտտումը"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Ավելացնել պատկերակը Հիմնական էկրանին"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Նոր հավելվածների համար"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Անհայտ է"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Հեռացնել"</string>
<string name="abandoned_search" msgid="891119232568284442">"Գտնել"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Այս պատկերակի ծրագիրը տեղադրված չէ: Դուք կարող եք հեռացնել այն կամ գտնել ծրագիրը և տեղադրել այն ձեռքով:"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g>–ի ներբեռնում (<xliff:g id="PROGRESS">%2$s</xliff:g>)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g>-ի տեղադրման սպասում"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> վիջեթներ"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Ավելացնել Հիմնական էկրանին"</string>
<string name="action_move_here" msgid="2170188780612570250">"Տեղափոխել տարրն այստեղ"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Տարրն ավելացվեց հիմնական էկրանին"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 00e8d63..4d26ab3 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -24,17 +24,22 @@
<string name="work_folder_name" msgid="3753320833950115786">"Kantor"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Aplikasi tidak dipasang."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Aplikasi tidak tersedia"</string>
- <string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplikasi yang diunduh dinonaktifkan dalam mode Aman"</string>
+ <string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplikasi yang didownload dinonaktifkan dalam mode Aman"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widget dinonaktifkan dalam mode Aman"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Pintasan tidak tersedia"</string>
+ <string name="home_screen" msgid="806512411299847073">"Layar utama"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Tindakan khusus"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Sentuh lama untuk memilih widget."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Ketuk dua kalip & tahan untuk mengambil widget atau menggunakan tindakan khusus."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"lebar %1$d x tinggi %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Sentuh & tahan untuk menempatkan secara manual"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Tambahkan otomatis"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Telusuri Apps"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Memuat Aplikasi..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Tidak ditemukan Aplikasi yang cocok dengan \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Telusuri aplikasi lainnya"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Notifikasi"</string>
<string name="out_of_space" msgid="4691004494942118364">"Tidak ada ruang lagi pada layar Utama ini."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Tidak ada ruang tersisa di baki Favorit"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Daftar aplikasi"</string>
@@ -71,14 +76,23 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Izinkan layar Utama diputar"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Saat ponsel diputar"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Setelan Tampilan Saat Ini tidak memungkinkan putaran"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Tambahkan ikon ke layar Utama"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Untuk aplikasi baru"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Tidak dikenal"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Buang"</string>
<string name="abandoned_search" msgid="891119232568284442">"Telusuri"</string>
<string name="abandoned_promises_title" msgid="7096178467971716750">"Aplikasi ini belum terpasang"</string>
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Aplikasi untuk ikon ini belum dipasang. Anda dapat membuangnya, atau menelusuri aplikasi dan memasangnya secara manual."</string>
- <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> sedang diunduh, <xliff:g id="PROGRESS">%2$s</xliff:g> selesai"</string>
+ <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> sedang didownload, <xliff:g id="PROGRESS">%2$s</xliff:g> selesai"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> menunggu dipasang"</string>
- <string name="action_add_to_workspace" msgid="8902165848117513641">"Tambahkan ke layar Utama"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widget <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="action_add_to_workspace" msgid="8902165848117513641">"Tambahkan ke Layar Utama"</string>
<string name="action_move_here" msgid="2170188780612570250">"Pindahkan item ke sini"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Item ditambahkan ke layar utama"</string>
<string name="item_removed" msgid="851119963877842327">"Item dihapus"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 7586ae3..3694a10 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Sótt forrit er óvirkt í öryggisstillingu"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Græjur eru óvirkar í öruggri stillingu"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Flýtileið er ekki tiltæk"</string>
+ <string name="home_screen" msgid="806512411299847073">"Heimaskjár"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Sérsniðnar aðgerðir"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Haltu fingri á græju til að grípa hana."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Ýttu tvisvar og haltu fingri á græju til að grípa hana eða notaðu sérsniðnar aðgerðir."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d á breidd og %2$d á hæð"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Haltu inni til að staðsetja handvirkt"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Bæta sjálfkrafa við"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Leita í forritum"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Hleður forrit…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Ekki fundust forrit sem samsvara „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Leita að fleiri forritum"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Tilkynningar"</string>
<string name="out_of_space" msgid="4691004494942118364">"Ekki meira pláss á þessum heimaskjá."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Ekki meira pláss í bakka fyrir uppáhald"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Forritalisti"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Leyfa snúning fyrir heimaskjá"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Þegar símanum er snúið"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Núverandi skjástilling leyfir ekki snúning"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Bæta tákni á heimaskjáinn"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Fyrir ný forrit"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Óþekkt"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Fjarlægja"</string>
<string name="abandoned_search" msgid="891119232568284442">"Leita"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Forritið fyrir þetta tákn er ekki uppsett. Þú getur fjarlægt það eða leitað að forritinu og sett það upp handvirkt."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> í niðurhali, <xliff:g id="PROGRESS">%2$s</xliff:g> lokið"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> bíður uppsetningar"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>-græjur"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Bæta á heimaskjá"</string>
<string name="action_move_here" msgid="2170188780612570250">"Færa atriði hingað"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Atriði bætt á heimaskjáinn"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index b821222..1208874 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"L\'app scaricata è stata disattivata in modalità provvisoria"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widget disabilitati in modalità provvisoria"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"La scorciatoia non è disponibile"</string>
+ <string name="home_screen" msgid="806512411299847073">"Schermata Home"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Azioni personalizzate"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Tocca e tieni premuto per scegliere un widget."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Tocca due volte e tieni premuto per scegliere un widget o per utilizzare azioni personalizzate."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d di larghezza per %2$d di altezza"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Tieni premuto per posizionare l\'elemento manualmente"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Aggiungi automaticamente"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Cerca app"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Caricamento di app…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Nessuna app trovata corrispondente a \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Cerca altre app"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Notifiche"</string>
<string name="out_of_space" msgid="4691004494942118364">"Spazio nella schermata Home esaurito."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Spazio esaurito nella barra dei Preferiti"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Elenco di app"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Consenti rotazione della schermata Home"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Con il telefono ruotato"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"L\'impostazione corrente del display non consente la rotazione"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Aggiungi icone alla schermata Home"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Per le nuove app"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Sconosciuto"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Rimuovi"</string>
<string name="abandoned_search" msgid="891119232568284442">"Cerca"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"L\'app per questa icona non è installata. Puoi rimuoverla o cercare l\'app e installarla manualmente."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Download di <xliff:g id="NAME">%1$s</xliff:g> in corso, <xliff:g id="PROGRESS">%2$s</xliff:g> completato"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> in attesa di installazione"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widget di <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Aggiungi a schermata Home"</string>
<string name="action_move_here" msgid="2170188780612570250">"Sposta elemento qui"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Elemento aggiunto alla schermata Home"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index ffea51f..858abfd 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"אפליקציה שהורדת הושבתה במצב בטוח"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"ווידג\'טים מושבתים במצב בטוח"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"קיצור הדרך אינו זמין"</string>
- <string name="long_press_widget_to_add" msgid="7699152356777458215">"גע נגיעה רציפה בווידג\'ט כדי לבחור בו."</string>
+ <string name="home_screen" msgid="806512411299847073">"מסך דף הבית"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"פעולות מותאמות אישית"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"אפשר לבחור את הווידג\'ט אם נוגעים בו נגיעה רציפה."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"הקש פעמיים וגע נגיעה רציפה בווידג\'ט כדי לבחור בו, או השתמש בפעולות מותאמות אישית."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"רוחב %1$d על גובה %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"גע והחזק כדי להוסיף ידנית"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"הוסף באופן אוטומטי"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"חפש אפליקציות"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"טוען אפליקציות…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"לא נמצאו אפליקציות התואמות ל-\"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"חפש אפליקציות נוספות"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"הודעות"</string>
<string name="out_of_space" msgid="4691004494942118364">"אין עוד מקום במסך דף הבית הזה."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"אין עוד מקום במגש המועדפים"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"רשימת אפליקציות"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"אפשרות סיבוב של מסך דף הבית"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"כאשר הטלפון מסובב"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"הגדרת התצוגה הנוכחית אינה מאפשרת סיבוב"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"הוספת סמל במסך דף הבית"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"לאפליקציות חדשות"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"לא ידוע"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"הסר"</string>
<string name="abandoned_search" msgid="891119232568284442">"חפש"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"האפליקציה של סמל זה אינה מותקנת. ניתן להסיר אותו, או לחפש את האפליקציה ולהתקין אותה ידנית."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"מוריד את <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> הושלמו"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"מחכה להתקנה של <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"ווידג\'טים של <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"הוסף למסך דף הבית"</string>
<string name="action_move_here" msgid="2170188780612570250">"העבר את הפריט לכאן"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"הפריט הועבר אל מסך דף הבית"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 8729aba..846b884 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"ダウンロードしたアプリは、セーフモードでは無効です"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"セーフモードではウィジェットは無効です"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ショートカットは使用できません"</string>
+ <string name="home_screen" msgid="806512411299847073">"ホーム画面"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"カスタム操作"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"ウィジェットを追加するには押し続けます。"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"ダブルタップ後に押し続けてウィジェットを選択するか、カスタム操作を使用してください。"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$dx%2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"幅 %1$d、高さ %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"押し続けると、手動で追加できます"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"自動的に追加"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"アプリを検索"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"アプリを読み込んでいます…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"「<xliff:g id="QUERY">%1$s</xliff:g>」に一致するアプリは見つかりませんでした"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"他のアプリを検索"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"通知"</string>
<string name="out_of_space" msgid="4691004494942118364">"このホーム画面に空きスペースがありません。"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"お気に入りトレイに空きスペースがありません"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"アプリのリスト"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"ホーム画面の回転を許可"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"スマートフォンが回転したとき"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"現在の [ディスプレイ] 設定では回転を使用できません"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"ホーム画面にアイコンを追加"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"新しいアプリをダウンロードしたときに"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"不明"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"削除"</string>
<string name="abandoned_search" msgid="891119232568284442">"検索"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"このアイコンのアプリはインストールされていません。このアイコンは削除できます。または、手動でアプリを検索してインストールしください。"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g>をダウンロード中、<xliff:g id="PROGRESS">%2$s</xliff:g>完了"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g>のインストール待ち"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>のウィジェット"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"ホーム画面に追加"</string>
<string name="action_move_here" msgid="2170188780612570250">"アイテムをここに移動"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"アイテムをホーム画面に追加しました"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 1a200d7..1979449 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"უსაფრთხო რეჟიმში ჩამოტვირთული აპი გაუქმებულია"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"უსაფრთხო რეჟიმში ვიჯეტი გამორთულია"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"მალსახმობი მიუწვდომელია"</string>
+ <string name="home_screen" msgid="806512411299847073">"მთავარი ეკრანი"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"მორგებული ქმედებები"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"შეეხეთ და დააყოვნეთ ვიჯეტის ასარჩევად."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"ორმაგად შეეხეთ და გეჭიროთ ვიჯეტის ასარჩევად ან მორგებული მოქმედებების გამოსაყენებლად."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"სიგრძე: %1$d, სიგანე: %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"ხანგრძლივად შეეხეთ ხელით განსათავსებლად"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"ავტომატურად დამატება"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"აპების ძიება"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"აპები იტვირთება..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"„<xliff:g id="QUERY">%1$s</xliff:g>“-ის თანხვედრი აპები არ მოიძებნა"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"მეტი აპის პოვნა"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"შეტყობინებები"</string>
<string name="out_of_space" msgid="4691004494942118364">"ამ მთავარ ეკრანზე ადგილი აღარ არის."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"რჩეულების თაროზე ადგილი არ არის"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"აპების სია"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"მთავარი ეკრანის შეტრიალების დაშვება"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"ტელეფონის შეტრიალებისას"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"ბრუნვა დაუშვებელია ჩვენების მიმდინარე პარამეტრებით"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"ხატულას მთავარ ეკრანზე დამატება"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"ახალი აპებისთვის"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"უცნობი"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"ამოშლა"</string>
<string name="abandoned_search" msgid="891119232568284442">"ძიება"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"ამ ხატულის აპი დაყენებული არ არის. შეგიძლიათ ამოშალოთ, ან მოიძიოთ აპი და ხელით მოახდინოთ მისი ინსტალაცია."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"მიმდინარეობს <xliff:g id="NAME">%1$s</xliff:g>-ის ჩამოტვირთვა, <xliff:g id="PROGRESS">%2$s</xliff:g> დასრულდა"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ელოდება ინსტალაციას"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>-ის ვიჯეტები"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"მთავარ ეკრანზე დამატება"</string>
<string name="action_move_here" msgid="2170188780612570250">"ერთეულის გადაადგილება აქ"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"ერთეული დაემატა მთავარ ეკრანს"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 009fa4a..5df503a 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Жүктелген қолданба қауіпсіз режимде өшірілген"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Қауіпсіз режимде виджеттер өшіріледі"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Таңбаша қолжетімді емес"</string>
+ <string name="home_screen" msgid="806512411299847073">"Негізгі экран"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Арнаулы әрекеттер"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Виджетті таңдау үшін түртіп, мықтап ұстаңыз."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Виджетті таңдау немесе арнаулы әрекеттерді таңдау үшін екі рет түртіп, ұстап тұрыңыз."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Ені: %1$d, биіктігі: %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Қолмен қою үшін басып тұрыңыз"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Автоматты енгізу"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Қолданбаларды іздеу"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Қолданбалар жүктелуде…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"«<xliff:g id="QUERY">%1$s</xliff:g>» сұрауына сәйкес келетін қолданбалар жоқ"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Қосымша қолданбалар іздеу"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Хабарландырулар"</string>
<string name="out_of_space" msgid="4691004494942118364">"Бұл Негізгі экранда орын қалмады."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Қалаулылар науасында орын қалмады"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Қолданбалар тізімі"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Негізгі экранның бұрылуына рұқсат ету"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Телефон бұрылғанда"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Экранның ағымдағы параметрі айналуға рұқсат бермейді"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Негізгі экранға белгіше енгізу"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Жаңа қолданбаларға арналған"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Белгісіз"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Алып тастау"</string>
<string name="abandoned_search" msgid="891119232568284442">"Іздеу"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Осы белгіше үшін қолданба орнатылмаған. Оны жоюға болады немесе қолданбаны іздеп, қолмен орнатуға болады."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> жүктелуде, <xliff:g id="PROGRESS">%2$s</xliff:g> аяқталды"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> орнату күтілуде"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> виджеті"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Негізгі экранға қосу"</string>
<string name="action_move_here" msgid="2170188780612570250">"Элементті мұнда жылжыту"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Элемент негізгі экранға қосылды"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 864c979..a64b604 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"បានបិទកម្មវិធីដែលបានទាញយកក្នុងរបៀបសុវត្ថិភាព"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"បានបិទធាតុក្រាហ្វិកក្នុងរបៀបសុវត្ថិភាព"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ផ្លូវកាត់មិនអាចប្រើបានទេ"</string>
+ <string name="home_screen" msgid="806512411299847073">"អេក្រង់ដើម"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"សកម្មភាពផ្ទាល់ខ្លួន"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"ប៉ះ & សង្កត់ ដើម្បីជ្រើសធាតុក្រាហ្វិក។"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"ប៉ះពីរដង ហើយចុចឲ្យជាប់ដើម្បីជ្រើសយកធាតុក្រាហ្វិក ឬប្រើសកម្មភាពផ្ទាល់ខ្លួន។"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"ទទឺង %1$d គុណនឹងកម្ពស់ %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"ចុចឲ្យជាប់ដើម្បីបញ្ចូលវាដោយផ្ទាល់"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"បញ្ចូលដោយស្វ័យប្រវត្តិ"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ស្វែងរកកម្មវិធី"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"កំពុងដំណើរការកម្មវិធី..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"គ្មានកម្មវិធីដែលត្រូវជាមួយ \"<xliff:g id="QUERY">%1$s</xliff:g>\" ទេ"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"ស្វែងរកកម្មវិធីច្រើនទៀត"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"ការជូនដំណឹង"</string>
<string name="out_of_space" msgid="4691004494942118364">"គ្មានបន្ទប់នៅលើអេក្រង់ដើមនេះទៀតទេ។"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"គ្មានបន្ទប់ក្នុងថាសនិយមប្រើ"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"បញ្ជីកម្មវិធី"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"អនុញ្ញាតការបងិ្វលអេក្រង់ដើម"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"នៅពេលដែលបង្វិលទូរស័ព្ទរបស់អ្នក"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"ការកំណត់អេក្រង់បច្ចុប្បន្នមិនអនុញ្ញាតការបង្វិលទេ"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"បញ្ចូលរូបតំណាងទៅអេក្រង់ដើម"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"សម្រាប់កម្មវិធីថ្មី"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"មិនស្គាល់"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"លុបចេញ"</string>
<string name="abandoned_search" msgid="891119232568284442">"ស្វែងរក"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"មិនបានដំឡើងកម្មវិធីសម្រាប់រូបតំណាងនេះ។ អ្នកអាចលុបវា ឬស្វែងរកកម្មវិធី និងដំឡើងវាដោយដៃ។"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"កំពុងដោនឡូត <xliff:g id="NAME">%1$s</xliff:g> បានបញ្ចប់ <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> កំពុងរង់ចាំការដំឡើង"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"ធាតុក្រាហ្វិក <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"បន្ថែមទៅអេក្រង់ដើម"</string>
<string name="action_move_here" msgid="2170188780612570250">"ផ្លាស់ធាតុមកទីនេះ"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"ធាតុដែលត្រូវបានបន្ថែមទៅអេក្រង់ដើម"</string>
diff --git a/res/values-kn-rIN/config.xml b/res/values-kn-rIN/config.xml
new file mode 100644
index 0000000..56f98c3
--- /dev/null
+++ b/res/values-kn-rIN/config.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="icon_shape_override_paths_values">
+ <item msgid="6640290598899495380"></item>
+ <item msgid="4009824731445917273">"M50,0L100,0 100,100 0,100 0,0z"</item>
+ <item msgid="3964229851574011244">"M50,0L80,0 A20,20,0,0 1 100,20 L100,80 A20,20,0,0 1 80,100 L20,100 A20,20,0,0 1 0,80 L 0,20 A20,20,0,0 1 20,0z"</item>
+ <item msgid="363553284746233331">"M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z"</item>
+ <item msgid="4319038504053267455">"M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0"</item>
+ <item msgid="483370082941112059">"M50,0A50,30 0,0,1 100,30V70A50,30 0,0,1 0,70V30A50,30 0,0,1 50,0z"</item>
+ </string-array>
+ <!-- no translation found for icon_shape_override_paths_names:0 (4837899951986816538) -->
+</resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 2c50567..b22af0d 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"ಡೌನ್ಲೋಡ್ ಮಾಡಲಾದ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಸುರಕ್ಷಿತ ಮೋಡ್ನಲ್ಲಿ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"ಸುರಕ್ಷಿತ ಮೋಡ್ನಲ್ಲಿ ವಿಜೆಟ್ಗಳನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ಶಾರ್ಟ್ಕಟ್ ಲಭ್ಯವಿಲ್ಲ"</string>
+ <string name="home_screen" msgid="806512411299847073">"ಮುಖಪುಟದ ಪರದೆ"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"ಕಸ್ಟಮ್ ಕ್ರಿಯೆಗಳು"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"ವಿಜೆಟ್ ಅನ್ನು ಆರಿಸಿಕೊಳ್ಳಲು ಸ್ಪರ್ಶಿಸಿ & ಹಿಡಿದುಕೊಳ್ಳಿ."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"ಡಬಲ್ ಟ್ಯಾಪ್ ಮಾಡಿ ಮತ್ತು ವಿಜೆಟ್ ಆರಿಸಿಕೊಳ್ಳಲು ಹೋಲ್ಡ್ ಮಾಡಿ ಅಥವಾ ಕಸ್ಟಮ್ ಕ್ರಿಯೆಗಳನ್ನು ಬಳಸಿ"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d ಅಗಲ ಮತ್ತು %2$d ಎತ್ತರ"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"ಹಸ್ತಚಾಲಿತವಾಗಿ ಸೇರಿಸಲು ಸ್ಪರ್ಶಿಸಿ ಮತ್ತು ಹೋಲ್ಡ್ ಮಾಡಿ"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಸೇರಿಸಿ"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ಅಪ್ಲಿಕೇಷನ್ಗಳನ್ನು ಹುಡುಕಿ"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"ಅಪ್ಲಿಕೇಶನ್ಗಳನ್ನು ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ಹೊಂದಿಕೆಯ ಯಾವುದೇ ಅಪ್ಲಿಕೇಶನ್ಗಳು ಕಂಡುಬಂದಿಲ್ಲ"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"ಮತ್ತಷ್ಟು ಅಪ್ಲಿಕೇಶನ್ಗಳನ್ನು ಹುಡುಕಿ"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"ಅಧಿಸೂಚನೆಗಳು"</string>
<string name="out_of_space" msgid="4691004494942118364">"ಈ ಮುಖಪುಟದ ಪರದೆಯಲ್ಲಿ ಹೆಚ್ಚು ಸ್ಥಳಾವಕಾಶವಿಲ್ಲ."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"ಮೆಚ್ಚಿನವುಗಳ ಟ್ರೇನಲ್ಲಿ ಹೆಚ್ಚಿನ ಸ್ಥಳಾವಕಾಶವಿಲ್ಲ"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"ಅಪ್ಲಿಕೇಶನ್ಗಳ ಪಟ್ಟಿ"</string>
@@ -71,6 +76,11 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"ಮುಖಪುಟ ತಿರುಗುವಿಕೆಯನ್ನು ಅನುಮತಿಸಿ"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"ಫೋನ್ ತಿರುಗಿಸಿದಾಗ"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"ಪ್ರಸ್ತುತ ಪ್ರದರ್ಶನ ಸೆಟ್ಟಿಂಗ್ ತಿರುಗುವಿಕೆಯನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"ಮುಖಪುಟದ ಪರದೆಗೆ ಐಕಾನ್ ಸೇರಿಸಿ"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"ಹೊಸ ಅಪ್ಲಿಕೇಶನ್ಗಳಿಗೆ"</string>
+ <string name="icon_shape_override_label" msgid="2977264953998281004">"ಐಕಾನ್ ಆಕಾರವನ್ನು ಬದಲಿಸಿ"</string>
+ <string name="icon_shape_no_override" msgid="3678524428085518367">"ಬದಲಿಸಬೇಡಿ"</string>
+ <string name="icon_shape_override_progress" msgid="3461735694970239908">"ಐಕಾನ್ ಆಕಾರ ಬದಲಾವಣೆಯನ್ನು ಅನ್ವಯಿಸಲಾಗುತ್ತಿದೆ"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"ಅಪರಿಚಿತ"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"ತೆಗೆದುಹಾಕಿ"</string>
<string name="abandoned_search" msgid="891119232568284442">"ಹುಡುಕಿ"</string>
@@ -78,6 +88,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"ಈ ಐಕಾನ್ ಅಪ್ಲಿಕೇಶನ್ ಸ್ಥಾಪನೆಗೊಂಡಿಲ್ಲ. ನೀವು ಅದನ್ನು ತೆಗೆದುಹಾಕಬಹುದು ಅಥವಾ ಅಪ್ಲಿಕೇಶನ್ ಹುಡುಕಬಹುದು ಮತ್ತು ಹಸ್ತಚಾಲಿತವಾಗಿ ಅದನ್ನು ಸ್ಥಾಪಿಸಬಹುದು."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ಡೌನ್ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ, <xliff:g id="PROGRESS">%2$s</xliff:g> ಪೂರ್ಣಗೊಂಡಿದೆ"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ಸ್ಥಾಪಿಸಲು ಕಾಯಲಾಗುತ್ತಿದೆ"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> ವಿಜೆಟ್ಗಳು"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"ಮುಖಪುಟಕ್ಕೆ ಸೇರಿಸು"</string>
<string name="action_move_here" msgid="2170188780612570250">"ಐಟಂ ಇಲ್ಲಿಗೆ ಸರಿಸಿ"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"ಮುಖಪುಟ ಪರದೆಗೆ ಐಟಂ ಸೇರಿಸಲಾಗಿದೆ"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 1ef438f..9e1eba4 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"다운로드한 앱은 안전 모드에서 사용할 수 없습니다."</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"안전 모드에서 위젯 사용 중지됨"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"바로가기를 사용할 수 없음"</string>
+ <string name="home_screen" msgid="806512411299847073">"홈 화면"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"맞춤 작업"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"위젯을 선택하려면 길게 터치하세요."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"위젯을 선택하려면 두 번 탭한 다음 길게 터치하거나 맞춤 액션을 사용합니다."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d×%2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"너비 %1$d, 높이 %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"길게 터치하여 직접 장소 추가"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"자동으로 추가"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"앱 검색"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"앱 로드 중..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\'<xliff:g id="QUERY">%1$s</xliff:g>\'와(과) 일치하는 앱이 없습니다."</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"더 많은 앱 검색"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"알림"</string>
<string name="out_of_space" msgid="4691004494942118364">"홈 화면에 더 이상 공간이 없습니다."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"즐겨찾기 트레이에 더 이상 공간이 없습니다."</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"앱 목록"</string>
@@ -56,7 +61,7 @@
<string name="disabled_app_label" msgid="6673129024321402780">"<xliff:g id="APP_NAME">%1$s</xliff:g> 사용 안함"</string>
<string name="default_scroll_format" msgid="7475544710230993317">"페이지 %1$d/%2$d"</string>
<string name="workspace_scroll_format" msgid="8458889198184077399">"홈 화면 %1$d/%2$d"</string>
- <string name="workspace_new_page" msgid="257366611030256142">"새로운 메인 스크린 페이지"</string>
+ <string name="workspace_new_page" msgid="257366611030256142">"새로운 홈 화면 페이지"</string>
<string name="folder_opened" msgid="94695026776264709">"폴더 열림(<xliff:g id="WIDTH">%1$d</xliff:g>X<xliff:g id="HEIGHT">%2$d</xliff:g>)"</string>
<string name="folder_tap_to_close" msgid="4625795376335528256">"탭하여 폴더 닫기"</string>
<string name="folder_tap_to_rename" msgid="4017685068016979677">"탭하여 변경된 이름 저장"</string>
@@ -68,9 +73,17 @@
<string name="settings_button_text" msgid="8119458837558863227">"설정"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"관리자가 사용 중지함"</string>
<string name="accessibility_action_overview" msgid="6257665857640347026">"개요"</string>
- <string name="allow_rotation_title" msgid="7728578836261442095">"메인 스크린 회전 허용"</string>
+ <string name="allow_rotation_title" msgid="7728578836261442095">"홈 화면 회전 허용"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"휴대전화 회전 시"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"현재 표시 설정에는 회전 기능이 허용되지 않습니다."</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"홈 화면에 아이콘 추가"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"새로 설치한 앱에 적용"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"알 수 없음"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"삭제"</string>
<string name="abandoned_search" msgid="891119232568284442">"검색"</string>
@@ -78,9 +91,10 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"이 아이콘의 앱이 설치되어 있지 않습니다. 아이콘을 삭제하거나 앱을 검색하여 수동으로 설치하세요."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> 다운로드 중, <xliff:g id="PROGRESS">%2$s</xliff:g> 완료"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> 설치 대기 중"</string>
- <string name="action_add_to_workspace" msgid="8902165848117513641">"메인 스크린에 추가"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> 위젯"</string>
+ <string name="action_add_to_workspace" msgid="8902165848117513641">"홈 화면에 추가"</string>
<string name="action_move_here" msgid="2170188780612570250">"여기에 항목을 이동"</string>
- <string name="item_added_to_workspace" msgid="4211073925752213539">"메인 스크린에 항목 추가됨"</string>
+ <string name="item_added_to_workspace" msgid="4211073925752213539">"홈 화면에 항목 추가됨"</string>
<string name="item_removed" msgid="851119963877842327">"항목 삭제됨"</string>
<string name="action_move" msgid="4339390619886385032">"항목 이동"</string>
<string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g>행 <xliff:g id="NUMBER_1">%2$s</xliff:g>열로 이동"</string>
@@ -92,7 +106,7 @@
<string name="added_to_folder" msgid="4793259502305558003">"항목이 폴더에 추가되었습니다."</string>
<string name="create_folder_with" msgid="4050141361160214248">"다음이 포함된 폴더 만들기: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_created" msgid="6409794597405184510">"폴더를 만들었습니다."</string>
- <string name="action_move_to_workspace" msgid="1603837886334246317">"메인 스크린으로 이동"</string>
+ <string name="action_move_to_workspace" msgid="1603837886334246317">"홈 화면으로 이동"</string>
<string name="action_move_screen_left" msgid="8854216831569401665">"화면을 왼쪽으로 이동"</string>
<string name="action_move_screen_right" msgid="329334910274311123">"화면을 오른쪽으로 이동"</string>
<string name="screen_moved" msgid="266230079505650577">"화면 이동됨"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 71e02d0..cfdcc18 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Жүктөп алынган колдонмо Коопсуз режиминде иштен чыгарылды"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Виджеттер Коопсуз режимде өчүрүлгөн"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Кыска жол жок"</string>
+ <string name="home_screen" msgid="806512411299847073">"Башкы экран"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Ыңгайлаштырылган аракеттер"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Виджетти тандаш үчүн, басып туруңуз"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Виджет тандоо үчүн эки жолу таптап, кармап туруңуз же ыңгайлаштырылган аракеттерди колдонуңуз."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Туурасы: %1$d, бийиктиги: %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Кол менен жайгаштыруу үчүн басып туруп, таштаңыз"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Автоматтык түрдө кошуу"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Колдонмолорду издөө"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Колдонмолор жүктөлүүдө…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" дал келген колдонмолор табылган жок"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Көбүрөөк колдонмолорду издөө"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Эскертмелер"</string>
<string name="out_of_space" msgid="4691004494942118364">"Бул Үй экранында бош орун жок."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Тандамалдар тайпасында орун калган жок"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Колдонмолор тизмеси"</string>
@@ -65,12 +70,20 @@
<string name="folder_name_format" msgid="6629239338071103179">"Фолдер: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="widget_button_text" msgid="2880537293434387943">"Виджеттер"</string>
<string name="wallpaper_button_text" msgid="8404103075899945851">"Тушкагаздар"</string>
- <string name="settings_button_text" msgid="8119458837558863227">"Тууралоолор"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Жөндөөлөр"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"Администраторуңуз өчүрүп койгон"</string>
<string name="accessibility_action_overview" msgid="6257665857640347026">"Көз жүгүртүү"</string>
<string name="allow_rotation_title" msgid="7728578836261442095">"Башкы экранды айлантууга уруксат берүү"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Телефон айланганда"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Экранды айлантуу параметри өчүрүлгөн"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Башкы экранга сүрөтчө кошуу"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Жаңы колдонмолор үчүн"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Белгисиз"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Алып салуу"</string>
<string name="abandoned_search" msgid="891119232568284442">"Издөө"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Бул сүрөтчөнүн колдонмосу орнотулган эмес. Аны алып салсаңыз же колдонмону издеп, кол менен орнотсоңуз болот."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> жүктөлүп алынууда, <xliff:g id="PROGRESS">%2$s</xliff:g> аяктады"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> орнотулушу күтүлүүдө"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> виджеттери"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Башкы экранга кошуу"</string>
<string name="action_move_here" msgid="2170188780612570250">"Бул нерсени бул жерге жылдыруу"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Башкы экранга кошулду"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index e231102..334305a 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"ແອັບຯທີ່ດາວໂຫລດແລ້ວຖືກປິດການນຳໃຊ້ໃນ Safe mode"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"ວິດເຈັດຖືກປິດໃນ Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ບໍ່ສາມາດໃຊ້ທາງລັດໄດ້"</string>
+ <string name="home_screen" msgid="806512411299847073">"ໜ້າຈໍຫຼັກ"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"ຄຳສັ່ງແບບກຳນົດເອງ"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"ສຳພັດຄ້າງໄວ້ ເພື່ອຈັບວິດເຈັດ."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"ແຕະຄ້າງໄວ້ ເພື່ອເລືອກວິດເຈັດ ຫຼື ໃຊ້ການດຳເນີນການກຳນົດເອງ."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"ກວ້າງ %1$d ຄູນສູງ %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"ແຕະຄ້າງໄວ້ເພື່ອວາງດ້ວຍຕົນເອງ"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"ເພີ່ມໂດຍອັດຕະໂນມັດ"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ຊອກຫາແອັບ"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"ກຳລັງໂຫລດແອັບ..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"ບໍ່ພົບແອັບໃດທີ່ກົງກັນ \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"ຊອກຫາແອັບເພີ່ມເຕີມ"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"ການແຈ້ງເຕືອນ"</string>
<string name="out_of_space" msgid="4691004494942118364">"ບໍ່ມີຫ້ອງເຫຼືອໃນໜ້າຈໍຫຼັກນີ້."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"ບໍ່ມີບ່ອນຫວ່າງໃນຖາດສຳລັບເກັບສິ່ງທີ່ໃຊ້ເປັນປະຈຳ"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"ລາຍຊື່ແອັບ"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"ອະນຸຍາດໃຫ້ໝຸນໜ້າຈໍທຳອິດໄດ້"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"ເມື່ອໝຸນໂທລະສັບ"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"ການຕັ້ງຄ່າສະແດງຜົນປັດຈຸບັນບໍ່ອະນຸຍາດໃຫ້ໝຸນໄດ້"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"ເພີ່ມໄອຄອນໃສ່ໜ້າຈໍຫຼັກ"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"ສຳລັບແອັບໃໝ່"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"ບໍ່ຮູ້ຈັກ"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"ລຶບ"</string>
<string name="abandoned_search" msgid="891119232568284442">"ຊອກຫາ"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"ແອັບຯສຳລັບໄອຄອນນີ້ຍັງບໍ່ໄດ້ຕິດຕັ້ງເທື່ອ. ທ່ານສາມາດລຶບມັນອອກ ຫຼືຊອກຫາແອັບຯ ແລ້ວຕິດຕັ້ງມັນໄດ້ດ້ວຍຕົນເອງ."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ກຳລັງດາວໂຫຼດ, <xliff:g id="PROGRESS">%2$s</xliff:g> ສຳເລັດ"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ກຳລັງລໍຖ້າຕິດຕັ້ງ"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"ວິດເຈັດ <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"ເພີ່ມໃສ່ໜ້າຈໍຫຼັກ"</string>
<string name="action_move_here" msgid="2170188780612570250">"Move item here"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"ເພີ່ມລາຍການໃສ່ໜ້າຈໍຫຼັກແລ້ວ"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 13ebaa3..558e1a9 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Atsisiųsta programa išjungta Saugos režimu"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Valdikliai išjungti Saugiame režime"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Sparčiojo klavišo negalima naudoti"</string>
+ <string name="home_screen" msgid="806512411299847073">"Pagrindinis ekranas"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Tinkinti veiksmai"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Palieskite ir laikykite, kad pasirinkt. valdiklį."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dukart palieskite ir laikykite, kad pasirinktumėte valdiklį ar naudotumėte tinkintus veiksmus."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d plotis ir %2$d aukštis"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Palieskite ir palaikykite, kad padėtumėte patys"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Pridėti automatiškai"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Ieškoti programų"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Įkeliamos programos..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Nerasta jokių užklausą „<xliff:g id="QUERY">%1$s</xliff:g>“ atitinkančių programų"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Ieškoti daugiau programų"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Pranešimai"</string>
<string name="out_of_space" msgid="4691004494942118364">"Šiame pagrindiniame ekrane vietos nebėra."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Mėgstamiausių dėkle nebėra vietos"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Programų sąrašas"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Leisti pasukti pagrindinį ekraną"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Kai telefonas pasukamas"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Naudojant dabartinį pateikties nustatymą neleidžiama pasukti"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Pridėti piktogr. prie pagrindinio ekrano"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Skirta naujoms programoms"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Nežinoma"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Pašalinti"</string>
<string name="abandoned_search" msgid="891119232568284442">"Ieškoti"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Šios piktogramos programa neįdiegta. Galite ją pašalinti arba bandyti ieškoti programos ir ją įdiegti patys."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Atsisiunčiama programa „<xliff:g id="NAME">%1$s</xliff:g>“, <xliff:g id="PROGRESS">%2$s</xliff:g> baigta"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Laukiama, kol bus įdiegta programa „<xliff:g id="NAME">%1$s</xliff:g>“"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"„<xliff:g id="NAME">%1$s</xliff:g>“ valdikliai"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Pridėti prie pagrind. ekrano"</string>
<string name="action_move_here" msgid="2170188780612570250">"Perkelti elementą čia"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Elementas pridėtas prie pagrindinio ekrano"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 924b399..733d647 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Lejupielādētā lietotne ir atspējota drošajā režīmā."</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Logrīki atspējoti drošajā režīmā"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Saīsne nav pieejama."</string>
+ <string name="home_screen" msgid="806512411299847073">"Sākuma ekrāns"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Pielāgotās darbības"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Lai izvēlētos logrīku, pieskarieties un turiet to."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Lai atlasītu logrīku, veiciet dubultskārienu uz tā un turiet to vai arī veiciet pielāgotas darbības."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d plats un %2$d augsts"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Pieskarieties un turiet, lai manuāli pievienotu"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Pievienot automātiski"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Meklēt lietotnes"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Notiek lietotņu ielāde…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Vaicājumam “<xliff:g id="QUERY">%1$s</xliff:g>” neatbilda neviena lietotne."</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Meklēt citas lietotnes"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Paziņojumi"</string>
<string name="out_of_space" msgid="4691004494942118364">"Šajā sākuma ekrānā vairs nav vietas."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Izlases joslā vairs nav vietas."</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Lietotņu saraksts"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Atļaut sākuma ekrāna pagriešanu"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Pagriežot tālruni"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Pašreizējā displeja iestatījumā nav atļauta pagriešana."</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Pievienot ikonu sākuma ekrānā"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Jaunām lietotnēm"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Nezināma"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Noņemt"</string>
<string name="abandoned_search" msgid="891119232568284442">"Meklēt"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Šai ikonai paredzētā lietotne nav instalēta. Varat noņemt ikonu vai meklēt lietotni un instalēt to manuāli."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Lietotnes <xliff:g id="NAME">%1$s</xliff:g> lejupielāde (<xliff:g id="PROGRESS">%2$s</xliff:g> pabeigti)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Notiek <xliff:g id="NAME">%1$s</xliff:g> instalēšana"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> logrīki"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Pievienot sākuma ekrānam"</string>
<string name="action_move_here" msgid="2170188780612570250">"Pārvietot vienumu šeit"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Vienums pievienots sākuma ekrānam"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 7e44707..267e75b 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Преземената апликација е оневозможена во безбеден режим"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Додатоците се оневозможени во безбеден режим"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Кратенката не е достапна"</string>
+ <string name="home_screen" msgid="806512411299847073">"Почетен екран"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Приспособени дејства"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Допри и задржи за да се избере виџетот."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Допрете двапати и задржете за да изберете додаток или да користите приспособени дејства."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d широк на %2$d висок"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Допрете и задржете за рачно поставување"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Додај автоматски"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Пребарување апликации"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Се вчитуваат апликации…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Не се најдени апликации што одговараат на „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Пребарај други апликации"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Известувања"</string>
<string name="out_of_space" msgid="4691004494942118364">"Нема повеќе простор на овој екран на почетната страница."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Нема повеќе простор на лентата „Омилени“"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Список со апликации"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Дозволете ротација на Почетниот екран"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Кога телефонот се ротира"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Тековната поставка на Екранот не дозволува ротација"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Додајте икона на почетниот екран"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"За нови апликации"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Непознато"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Отстрани"</string>
<string name="abandoned_search" msgid="891119232568284442">"Барај"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Апликацијата за оваа икона не е инсталирана. Може да ја отстраните или да се обидете да ја најдете апликацијата и да ја инсталирате рачно."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Се презема <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> завршено"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> чека да се инсталира"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Виџети за <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Додај на Почетен екран"</string>
<string name="action_move_here" msgid="2170188780612570250">"Премести ја ставката овде"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Ставката е додадена на почетниот екран"</string>
diff --git a/res/values-ml-rIN/config.xml b/res/values-ml-rIN/config.xml
new file mode 100644
index 0000000..56f98c3
--- /dev/null
+++ b/res/values-ml-rIN/config.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="icon_shape_override_paths_values">
+ <item msgid="6640290598899495380"></item>
+ <item msgid="4009824731445917273">"M50,0L100,0 100,100 0,100 0,0z"</item>
+ <item msgid="3964229851574011244">"M50,0L80,0 A20,20,0,0 1 100,20 L100,80 A20,20,0,0 1 80,100 L20,100 A20,20,0,0 1 0,80 L 0,20 A20,20,0,0 1 20,0z"</item>
+ <item msgid="363553284746233331">"M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z"</item>
+ <item msgid="4319038504053267455">"M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0"</item>
+ <item msgid="483370082941112059">"M50,0A50,30 0,0,1 100,30V70A50,30 0,0,1 0,70V30A50,30 0,0,1 50,0z"</item>
+ </string-array>
+ <!-- no translation found for icon_shape_override_paths_names:0 (4837899951986816538) -->
+</resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 941d1ac..a92fc03 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"ഡൗൺലോഡുചെയ്ത അപ്ലിക്കേഷൻ സുരക്ഷാ മോഡിൽ പ്രവർത്തനരഹിതമാക്കി"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"സുരക്ഷിത മോഡിൽ വിജറ്റുകൾ പ്രവർത്തനരഹിതമാക്കി"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"കുറുക്കുവഴി ലഭ്യമല്ല"</string>
+ <string name="home_screen" msgid="806512411299847073">"ഹോം സ്ക്രീൻ"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"ഇഷ്ടാനുസൃത പ്രവർത്തനങ്ങൾ"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"ഒരു വിജറ്റ് ചേർക്കുന്നതിന് അത് സ്പർശിച്ച് പിടിക്കുക."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"വിജറ്റ് തിരഞ്ഞെടുക്കാനോ ഇഷ്ടാനുസൃത പ്രവർത്തനങ്ങൾ ഉപയോഗിക്കാനോ രണ്ടുതവണ ടാപ്പുചെയ്ത് പിടിക്കുക."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d വീതിയും %2$d ഉയരവും"</string>
- <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ആപ്പ്സ് തിരയുക"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"സ്വമേധയാ സ്ഥാപിക്കുന്നതിന് സ്പർശിച്ചുപിടിക്കുക"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"സ്വയമേവ ചേർക്കുക"</string>
+ <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ആപ്പുകളെ തിരയുക"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"ആപ്പ്സ് ലോഡുചെയ്യുന്നു..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" എന്നതുമായി പൊരുത്തപ്പെടുന്ന ആപ്പ്സൊന്നും കണ്ടെത്തിയില്ല"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"കൂടുതൽ ആപ്പുകൾക്ക് തിരയുക"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"അറിയിപ്പുകൾ"</string>
<string name="out_of_space" msgid="4691004494942118364">"ഈ ഹോം സ്ക്രീനിൽ ഒഴിവൊന്നുമില്ല."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"പ്രിയപ്പെട്ടവയുടെ ട്രേയിൽ ഒഴിവൊന്നുമില്ല"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"അപ്ലിക്കേഷനുകളുടെ ലിസ്റ്റ്"</string>
@@ -71,6 +76,11 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"ഹോം സ്ക്രീൻ തിരിക്കൽ അനുവദിക്കുക"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"ഫോൺ തിരിച്ച നിലയിലായിരിക്കുമ്പോൾ"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"നിലവിലെ ഡിസ്പ്ലേ ക്രമീകരണം തിരിക്കൽ അനുവദിക്കുന്നില്ല"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"ഹോം സ്ക്രീനിലേക്ക് ഐക്കൺ ചേർക്കുക"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"പുതിയ ആപ്പുകൾക്ക്"</string>
+ <string name="icon_shape_override_label" msgid="2977264953998281004">"ഐക്കണിന്റെ ആകാരം മാറ്റുക"</string>
+ <string name="icon_shape_no_override" msgid="3678524428085518367">"മാറ്റരുത്"</string>
+ <string name="icon_shape_override_progress" msgid="3461735694970239908">"ഐക്കൺ ആകാര മാറ്റങ്ങൾ പ്രയോഗിക്കുന്നു"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"അജ്ഞാതം"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"നീക്കംചെയ്യുക"</string>
<string name="abandoned_search" msgid="891119232568284442">"തിരയുക"</string>
@@ -78,6 +88,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"ഈ ഐക്കണുവേണ്ടി അപ്ലിക്കേഷൻ ഇൻസ്റ്റാളുചെയ്തിട്ടില്ല. നിങ്ങൾക്കത് നീക്കംചെയ്യാനാകും അല്ലെങ്കിൽ അപ്ലിക്കേഷനുവേണ്ടി തിരഞ്ഞുകൊണ്ട് അത് സ്വമേധയാ ഇൻസ്റ്റാളുചെയ്യുക."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ഡൗൺലോഡ് ചെയ്യുന്നു, <xliff:g id="PROGRESS">%2$s</xliff:g> പൂർത്തിയായി"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"ഇൻസ്റ്റാൾ ചെയ്യാൻ <xliff:g id="NAME">%1$s</xliff:g> കാക്കുന്നു"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> വിജറ്റുകൾ"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"ഹോം സ്ക്രീനിൽ ചേർക്കുക"</string>
<string name="action_move_here" msgid="2170188780612570250">"ഇനം ഇവിടേക്ക് നീക്കുക"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"ഹോം സ്ക്രീനിൽ ഇനം ചേർത്തു"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 45e1856..5b74fd2 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Татаж авсан апп-г Аюулгүй горим дотроос идэвхгүйжүүлсэн"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Safe горимд виджетүүдийг идэвхгүйжүүлсэн"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Товчлол алга"</string>
+ <string name="home_screen" msgid="806512411299847073">"Үндсэн нүүр"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Захиалгат үйлдэл"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Виджетийг авах бол хүрээд барина уу."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Жижиг хэрэгсэл авах болон тохируулсан үйлдлийг ашиглахын тулд 2 удаа товшоод барина уу."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d өргөн %2$d өндөр"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Гараар байршуулахын тулд дараад хүлээнэ үү"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Автоматаар нэмэх"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Апп хайх"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Аппликейшныг ачаалж байна..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"-д нийцэх апп олдсонгүй"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Бусад апп-г хайх"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Мэдэгдэл"</string>
<string name="out_of_space" msgid="4691004494942118364">"Энэ Нүүр дэлгэц зайгүй."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"\"Дуртай\" трей дээр өөр зай байхгүй байна"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Апп-н жагсаалт"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Нүүр дэлгэцийг эргүүлэхийг зөвшөөрөх"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Утсыг эргүүлсэн үед"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Дэлгэцийн одоогийн тохиргоогоор эргүүлэх боломжгүй"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Нүүр хуудаст дүрс тэмдэг нэмэх"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Шинэ аппад зориулсан"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Тодорхойгүй"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Устгах"</string>
<string name="abandoned_search" msgid="891119232568284442">"Хайх"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Энэ дүрсний апп-г суулгаагүй байна. Та үүнийг устгах буюу апп-г хайж суулгах боломжтой."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g>-г татаж байна, <xliff:g id="PROGRESS">%2$s</xliff:g> татсан"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> нь суулгахыг хүлээж байна"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> жижиг хэрэгсэл"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Нүүр дэлгэц нэмэх"</string>
<string name="action_move_here" msgid="2170188780612570250">"Энд байршуулах"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Нүүр дэлгэцэнд нэмсэн зүйл"</string>
diff --git a/res/values-mr-rIN/config.xml b/res/values-mr-rIN/config.xml
new file mode 100644
index 0000000..56f98c3
--- /dev/null
+++ b/res/values-mr-rIN/config.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="icon_shape_override_paths_values">
+ <item msgid="6640290598899495380"></item>
+ <item msgid="4009824731445917273">"M50,0L100,0 100,100 0,100 0,0z"</item>
+ <item msgid="3964229851574011244">"M50,0L80,0 A20,20,0,0 1 100,20 L100,80 A20,20,0,0 1 80,100 L20,100 A20,20,0,0 1 0,80 L 0,20 A20,20,0,0 1 20,0z"</item>
+ <item msgid="363553284746233331">"M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z"</item>
+ <item msgid="4319038504053267455">"M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0"</item>
+ <item msgid="483370082941112059">"M50,0A50,30 0,0,1 100,30V70A50,30 0,0,1 0,70V30A50,30 0,0,1 50,0z"</item>
+ </string-array>
+ <!-- no translation found for icon_shape_override_paths_names:0 (4837899951986816538) -->
+</resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 1ec443c..1a10e4b 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"डाउनलोड केलेला अॅप सुरक्षित मोड मध्ये अक्षम केला"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"विजेट सुरक्षित मोडमध्ये अक्षम झाले"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"शॉर्टकट उपलब्ध नाही"</string>
+ <string name="home_screen" msgid="806512411299847073">"मुख्यपृष्ठ"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"सानुकूल क्रिया"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"विजेट निवडण्यासाठी स्पर्श करा आणि धरून ठेवा."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"एक विजेट निवडण्यासाठी दोनदा टॅप करा आणि धरून ठेवा किंवा सानुकूल क्रिया वापरा."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d रूंद बाय %2$d उंच"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"स्वतः ठेवण्यासाठी स्पर्श करा आणि धरून ठेवा"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"स्वयंचलितपणे जोडा"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"अॅप्स शोधा"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"अॅप्स लोड करीत आहे..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" शी जुळणारे कोणतेही अॅप्स आढळले नाहीत"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"अधिक अॅप्स शोधा"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"सूचना"</string>
<string name="out_of_space" msgid="4691004494942118364">"या मुख्य स्क्रीनवर आणखी जागा नाही."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"आवडीच्या ट्रे मध्ये आणखी जागा नाही"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"अॅप्स सूची"</string>
@@ -71,6 +76,11 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"मुख्यस्क्रीन फिरविण्यास अनुमती द्या"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"फोन फिरविला जातो तेव्हा"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"वर्तमान प्रदर्शन सेटिंग फिरविण्यास परवानगी देत नाही"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"मुख्य स्क्रीनवर चिन्ह जोडा"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"नवीन अॅप्ससाठी"</string>
+ <string name="icon_shape_override_label" msgid="2977264953998281004">"चिन्हाचा आकार बदला"</string>
+ <string name="icon_shape_no_override" msgid="3678524428085518367">"बदलू नका"</string>
+ <string name="icon_shape_override_progress" msgid="3461735694970239908">"चिन्हाचा आकार बदल लागू करत आहे"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"अज्ञात"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"काढा"</string>
<string name="abandoned_search" msgid="891119232568284442">"शोधा"</string>
@@ -78,6 +88,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"या चिन्हासाठी अॅप स्थापित केलेला नाही. आपण ते काढू शकता किंवा अॅपचा शोध घेऊ शकता आणि त्यास व्यक्तिचलितपणे स्थापित करू शकता."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> डाउनलोड होत आहे , <xliff:g id="PROGRESS">%2$s</xliff:g> पूर्ण झाले"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> स्थापित करण्याची प्रतिक्षा करीत आहे"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> विजेट"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"मुख्य स्क्रीनवर जोडा"</string>
<string name="action_move_here" msgid="2170188780612570250">"आयटम येथे हलवा"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"आयटम मुख्य स्क्रीनवर जोडला"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index bc4e484..449aae8 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"ဒေါင်းလုဒ် အက်ပ်ကို လုံခြုံရေး မုဒ်ထဲမှာ ပိတ်ထား"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"လုံခြုံရေး မုဒ်ထဲမှာ ဝီဂျက်များကို ပိတ်ထား"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ဖြတ်လမ်း မရနိုင်ပါ"</string>
+ <string name="home_screen" msgid="806512411299847073">"ပင်မစာမျက်နှာ"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"စိတ်ကြိုက် လုပ်ဆောင်ချက်များ"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"ဝဒ်ဂျက်တစ်ခုကို ကောက်ယူရန် ဖိနှိပ်ထားပါ"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"ဝစ်ဂျက်တစ်ခုကိုရယူရန် သို့မဟုတ် စိတ်ကြိုက်လုပ်ဆောင်မှုများကို အသုံးပြုရန် နှစ်ချက်တို့ပြီး ကိုင်ထားပါ။"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"အလျား %1$d နှင့် အမြင့် %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"ကိုယ်တိုင်ထည့်ရန် ထိထားပါ"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"အလိုအလျောက် ထည့်ရန်"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ရှာဖွေမှု အက်ပ်များ"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"အက်ပ်များ ရယူနေစဉ်..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" နှင့်ကိုက်ညီသည့် အပ်ဖ်များမတွေ့ပါ"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"နောက်ထပ် အက်ပ်များကို ရှာပါ"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"အကြောင်းကြားချက်များ"</string>
<string name="out_of_space" msgid="4691004494942118364">"ဤပင်မမျက်နှာစာတွင် နေရာလွတ် မကျန်တော့ပါ"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"အနှစ်သက်ဆုံးများ ထားရာတွင် နေရာလွတ် မကျန်တော့ပါ"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"အက်ပ်စာရင်း"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"ပင်မစာမျက်နှာလှည့်ခြင်းကို ခွင့်ပြုပါ"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"ဖုန်းကိုလှည့်ထားစဉ်"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"လက်ရှိ မြင်ကွင်းဆက်တင်တွင် မြင်ကွင်းကို လှည့်ခွင့်မပေးပါ"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"ပင်မစာမျက်နှာသို့ သင်္ကေတပုံ ထည့်ရန်"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"အက်ပ်အသစ်များအတွက်"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"မသိရ"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"ဖယ်ရှားရန်"</string>
<string name="abandoned_search" msgid="891119232568284442">"ရှာဖွေရန်"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"ဤအိုင်ကွန်အတွက် အက်ပ်အားမထည့်သွင်းထားပါ။ You can remove it, or search for the အက်ပ်and install it manually."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ဒေါင်းလုဒ်လုပ်နေသည်၊ <xliff:g id="PROGRESS">%2$s</xliff:g> ပြီးပါပြီ"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ကိုထည့်သွင်းရန်စောင့်နေသည်"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> ဝိဂျက်များ"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"ပင်မမျက်နှာစာသို့ ထည့်ပါ"</string>
<string name="action_move_here" msgid="2170188780612570250">"၎င်းအား ဤသို့ ရွှေ့ပါ"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"ပင်မ ဖန်မျက်နှာပြင်သို့ ထည့်ပြီး၏"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 449094d..6c70038 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"En nedlastet app er deaktivert i sikker modus"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Moduler er deaktivert i sikker modus"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Snarveien er ikke tilgjengelig"</string>
+ <string name="home_screen" msgid="806512411299847073">"Startskjerm"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Tilpassede handlinger"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Trykk og hold inne for å plukke opp en modul."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dobbelttrykk og hold inne for å velge en modul eller bruke tilpassede handlinger."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d bredde x %2$d høyde"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Trykk og hold for å plassere manuelt"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Legg til automatisk"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Søk i apper"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Laster inn apper …"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Fant ingen apper som samsvarer med «<xliff:g id="QUERY">%1$s</xliff:g>»"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Søk etter flere apper"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Varsler"</string>
<string name="out_of_space" msgid="4691004494942118364">"Denne startsiden er full."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Favoritter-skuffen er full"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"App-liste"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Tillat rotasjon av startskjermen"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Når telefonen roteres"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Med den nåværende skjerminnstillingen støttes ikke rotasjon"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Legg til ikon på startsiden"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"For nye apper"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Ukjent"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Fjern"</string>
<string name="abandoned_search" msgid="891119232568284442">"Søk"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Appen for dette ikonet er ikke installert. Du kan fjerne det, eller prøve å søke etter appen og installere den manuelt."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Laster ned <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> er fullført"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Venter på å installere <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>-moduler"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Legg til på startskjermen"</string>
<string name="action_move_here" msgid="2170188780612570250">"Flytt elementet hit"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Elementet er lagt til på startskjermen"</string>
diff --git a/res/values-ne-rNP/config.xml b/res/values-ne-rNP/config.xml
new file mode 100644
index 0000000..56f98c3
--- /dev/null
+++ b/res/values-ne-rNP/config.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="icon_shape_override_paths_values">
+ <item msgid="6640290598899495380"></item>
+ <item msgid="4009824731445917273">"M50,0L100,0 100,100 0,100 0,0z"</item>
+ <item msgid="3964229851574011244">"M50,0L80,0 A20,20,0,0 1 100,20 L100,80 A20,20,0,0 1 80,100 L20,100 A20,20,0,0 1 0,80 L 0,20 A20,20,0,0 1 20,0z"</item>
+ <item msgid="363553284746233331">"M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z"</item>
+ <item msgid="4319038504053267455">"M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0"</item>
+ <item msgid="483370082941112059">"M50,0A50,30 0,0,1 100,30V70A50,30 0,0,1 0,70V30A50,30 0,0,1 50,0z"</item>
+ </string-array>
+ <!-- no translation found for icon_shape_override_paths_names:0 (4837899951986816538) -->
+</resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 8accfcc..46c8f9c 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"सुरक्षित मोडमा डाउनलोड गरेको अनुप्रयोग अक्षम गरिएको छ"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"सुरक्षित मोडमा विगेटहरू अक्षम गरियो"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"सर्टकट उपलब्ध छैन"</string>
+ <string name="home_screen" msgid="806512411299847073">"गृह स्क्रिन"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"आफू अनुकूलका कारबाहीहरू"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"एउटा विजेटलाई टिप्नको लागि टच गरेर होल्ड गर्नुहोस्।"</string>
- <string name="long_accessible_way_to_add" msgid="4289502106628154155">"विजेटलाई छान्न वा अनुकूलन कार्यहरू प्रयोग गर्न डबल ट्याप गरी होल्ड गर्नुहोस्।"</string>
+ <string name="long_accessible_way_to_add" msgid="4289502106628154155">"विजेटलाई छान्न वा आफू अनुकूल कार्यहरू प्रयोग गर्न डबल ट्याप गरी होल्ड गर्नुहोस्।"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d चौडाइ गुणा %2$d उचाइ"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"म्यानुअल तरिकाले थप्न छुनुहोस् र थिची राख्नुहोस्"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"स्वतः थप्नुहोस्"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"अनुप्रयोगहरू खोज्नुहोस्"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"अनुप्रयोगहरू लोड गरिँदै..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" सँग मिल्दो कुनै अनुप्रयोगहरू फेला परेनन्"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"थप अनुप्रयोगहरू खोज्नुहोस्"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"सूचनाहरू"</string>
<string name="out_of_space" msgid="4691004494942118364">"यो गृह स्क्रिनमा कुनै थप ठाउँ छैन।"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"मनपर्ने ट्रे अब कुनै ठाँउ छैन"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"अनुप्रयोगको सूची"</string>
@@ -71,13 +76,19 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"गृह स्क्रिनलाई घुम्ने अनुमति दिनुहोस्"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"फोनलाई घुमाइँदा"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"हालको प्रदर्शन सम्बन्धी सेटिङले घुमाउने सुविधालाई अनुमति दिँदैन"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"गृह स्क्रिनमा आइकन थप्नुहोस्"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"नयाँ अनुप्रयोगका लागि"</string>
+ <string name="icon_shape_override_label" msgid="2977264953998281004">"आइकनको आकार परिवर्तन गर्नुहोस्"</string>
+ <string name="icon_shape_no_override" msgid="3678524428085518367">"परिवर्तन नगर्नुहोस्"</string>
+ <string name="icon_shape_override_progress" msgid="3461735694970239908">"आइकनको आकारमा गरिएका परिवर्तनहरू लागू गरिँदैछन्"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"अज्ञात"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"हटाउनुहोस्"</string>
<string name="abandoned_search" msgid="891119232568284442">"खोजी गर्नुहोस्"</string>
<string name="abandoned_promises_title" msgid="7096178467971716750">"यो अनुप्रयोग स्थापित छैन"</string>
- <string name="abandoned_promise_explanation" msgid="3990027586878167529">"यो प्रतिमाका लागि अनुप्रयोगलाई स्थापना गरिएको छैन। तपाईँ यसलाई हटाउन, वा अनुप्रयोग खोजी र स्वयं यो स्थापित गर्न सक्नुहुन्छ।"</string>
+ <string name="abandoned_promise_explanation" msgid="3990027586878167529">"यो प्रतिमाका लागि अनुप्रयोगलाई स्थापना गरिएको छैन। तपाईं यसलाई हटाउन, वा अनुप्रयोग खोजी र स्वयं यो स्थापित गर्न सक्नुहुन्छ।"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> डाउनलोड गर्दै, <xliff:g id="PROGRESS">%2$s</xliff:g> सम्पन्न"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> स्थापना गर्न प्रतीक्षा गर्दै"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> विजेटहरू"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"गृह स्क्रिनमा थप्नुहोस्"</string>
<string name="action_move_here" msgid="2170188780612570250">"वस्तु यहाँ सार्नुहोस्"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"वस्तु गृह स्क्रिनमा थपियो"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 0134ae1..e8caeb4 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -27,27 +27,32 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Gedownloade app uitgeschakeld in veilige modus"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets uitgeschakeld in Veilige modus"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Snelkoppeling is niet beschikbaar"</string>
+ <string name="home_screen" msgid="806512411299847073">"Startscherm"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Aangepaste acties"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Blijf aanraken om een widget toe te voegen."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dubbeltik en blijf aanraken om een widget toe te voegen of aangepaste acties te gebruiken."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d breed en %2$d hoog"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Tik op een item en houd dit vast om het handmatig te plaatsen"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Automatisch toevoegen"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Apps zoeken"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Apps laden…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Er zijn geen apps gevonden die overeenkomen met \'<xliff:g id="QUERY">%1$s</xliff:g>\'"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Zoeken naar meer apps"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Meldingen"</string>
<string name="out_of_space" msgid="4691004494942118364">"Er is geen ruimte meer op dit startscherm."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Geen ruimte meer in het vak \'Favorieten\'"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Lijst met apps"</string>
- <string name="all_apps_home_button_label" msgid="252062713717058851">"Startpagina"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Homepage"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"Verwijderen"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Deïnstalleren"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"App-info"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"Snelle links instellen"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"Een app toestaan snelkoppelingen toe te voegen zonder tussenkomst van de gebruiker."</string>
- <string name="permlab_read_settings" msgid="1941457408239617576">"instellingen en snelkoppelingen op de startpagina lezen"</string>
- <string name="permdesc_read_settings" msgid="5833423719057558387">"De app toestaan de instellingen en snelkoppelingen op de startpagina te lezen."</string>
- <string name="permlab_write_settings" msgid="3574213698004620587">"instellingen en snelkoppelingen op de startpagina schrijven"</string>
- <string name="permdesc_write_settings" msgid="5440712911516509985">"De app toestaan de instellingen en snelkoppelingen op de startpagina te wijzigen."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"instellingen en snelkoppelingen op de homepage lezen"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"De app toestaan de instellingen en snelkoppelingen op de homepage te lezen."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"instellingen en snelkoppelingen op de homepage schrijven"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"De app toestaan de instellingen en snelkoppelingen op de homepage te wijzigen."</string>
<string name="msg_no_phone_permission" msgid="9208659281529857371">"<xliff:g id="APP_NAME">%1$s</xliff:g> mag niet bellen"</string>
<string name="gadget_error_text" msgid="6081085226050792095">"Probleem bij het laden van widget"</string>
<string name="gadget_setup_text" msgid="8274003207686040488">"Configuratie"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Draaien van startscherm toestaan"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Wanneer de telefoon gedraaid is"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Huidige scherminstelling staat draaien niet toe"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Pictogram toevoegen aan startscherm"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Voor nieuwe apps"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Onbekend"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Verwijderen"</string>
<string name="abandoned_search" msgid="891119232568284442">"Zoeken"</string>
@@ -78,7 +91,8 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"De app voor dit pictogram is niet geïnstalleerd. Je kunt het pictogram verwijderen of de app zoeken en handmatig installeren."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> wordt gedownload, <xliff:g id="PROGRESS">%2$s</xliff:g> voltooid"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> wacht op installatie"</string>
- <string name="action_add_to_workspace" msgid="8902165848117513641">"Toevoegen aan startpagina"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>-widgets"</string>
+ <string name="action_add_to_workspace" msgid="8902165848117513641">"Toevoegen aan homepage"</string>
<string name="action_move_here" msgid="2170188780612570250">"Item hier naartoe verplaatsen"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Item toegevoegd aan startscherm"</string>
<string name="item_removed" msgid="851119963877842327">"Item verwijderd"</string>
diff --git a/res/values-pa-rIN/config.xml b/res/values-pa-rIN/config.xml
new file mode 100644
index 0000000..56f98c3
--- /dev/null
+++ b/res/values-pa-rIN/config.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="icon_shape_override_paths_values">
+ <item msgid="6640290598899495380"></item>
+ <item msgid="4009824731445917273">"M50,0L100,0 100,100 0,100 0,0z"</item>
+ <item msgid="3964229851574011244">"M50,0L80,0 A20,20,0,0 1 100,20 L100,80 A20,20,0,0 1 80,100 L20,100 A20,20,0,0 1 0,80 L 0,20 A20,20,0,0 1 20,0z"</item>
+ <item msgid="363553284746233331">"M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z"</item>
+ <item msgid="4319038504053267455">"M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0"</item>
+ <item msgid="483370082941112059">"M50,0A50,30 0,0,1 100,30V70A50,30 0,0,1 0,70V30A50,30 0,0,1 50,0z"</item>
+ </string-array>
+ <!-- no translation found for icon_shape_override_paths_names:0 (4837899951986816538) -->
+</resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 3be9bec..c4c11ce 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"ਡਾਊਨਲੋਡ ਕੀਤਾ ਐਪ ਸੁਰੱਖਿਅਤ ਮੋਡ ਵਿੱਚ ਅਸਮਰਥਿਤ"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"ਵਿਜਿਟ ਸੁਰੱਖਿਅਤ ਮੋਡ ਵਿੱਚ ਅਸਮਰਥਿਤ"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ਸ਼ਾਰਟਕੱਟ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
+ <string name="home_screen" msgid="806512411299847073">"ਮੁੱਖ ਸਕ੍ਰੀਨ"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"ਵਿਸ਼ੇਸ਼-ਵਿਉਂਤਬੱਧ ਕਾਰਵਾਈਆਂ"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"ਇੱਕ ਵਿਜੇਟ ਚੁਣਨ ਲਈ ਛੋਹਵੋT & ਹੋਲਡ ਕਰੋ।"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"ਡਬਲ-ਟੈਪ & ਇੱਕ ਵਿਜੇਟ ਚੁਣਨ ਲਈ ਹੋਲਡ ਕਰੋ ਅਤੇ ਕਸਟਮ ਕਿਰਿਆਵਾਂ ਵਰਤੋ।"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d ਚੌੜਾਈ ਅਤੇ %2$d ਲੰਬਾਈ"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"ਹੱਥੀਂ ਰੱਖਣ ਲਈ ਸਪੱਰਸ਼ ਕਰੋ ਅਤੇ ਦਬਾਈ ਰੱਖੋ"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"ਸਵੈਚਲਿਤ ਤਰੀਕੇ ਨਾਲ ਸ਼ਾਮਲ ਕਰੋ"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ਐਪਸ ਖੋਜੋ"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"ਐਪਾਂ ਨੂੰ ਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ਨਾਲ ਮਿਲਦੀਆਂ ਕੋਈ ਵੀ ਐਪਾਂ ਨਹੀਂ ਮਿਲੀਆਂ"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"ਹੋਰ ਐਪਾਂ ਖੋਜੋ"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"ਸੂਚਨਾਵਾਂ"</string>
<string name="out_of_space" msgid="4691004494942118364">"ਇਸ ਹੋਮ ਸਕ੍ਰੀਨ ਲਈ ਹੋਰ ਖਾਲੀ ਸਥਾਨ ਨਹੀਂ ਹੈ।"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"ਮਨਪਸੰਦ ਟ੍ਰੇ ਵਿੱਚ ਹੋਰ ਖਾਲੀ ਸਥਾਨ ਨਹੀਂ।"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"ਐਪ ਸੂਚੀ"</string>
@@ -71,6 +76,11 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"ਮੁੱਖ ਸਕ੍ਰੀਨ ਨੂੰ ਘੁੰਮਾਉਣ ਦੀ ਆਗਿਆ ਦਿਓ"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"ਜਦੋਂ ਫ਼ੋਨ ਘੁੰਮਾਇਆ ਜਾਂਦਾ ਹੈ"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"ਵਰਤਮਾਨ ਡਿਸਪਲੇ ਸੈਟਿੰਗ ਘੁੰਮਾਉਣ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਦਿੰਦੀ ਹੈ"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"ਮੁੱਖ ਸਕ੍ਰੀਨ \'ਤੇ ਪ੍ਰਤੀਕ ਸ਼ਾਮਲ ਕਰੋ"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"ਨਵੀਆਂ ਐਪਾਂ ਲਈ"</string>
+ <string name="icon_shape_override_label" msgid="2977264953998281004">"ਆਈਕਨ ਦੀ ਆਕ੍ਰਿਤੀ ਬਦਲੋ"</string>
+ <string name="icon_shape_no_override" msgid="3678524428085518367">"ਨਾ ਬਦਲੋ"</string>
+ <string name="icon_shape_override_progress" msgid="3461735694970239908">"ਆਈਕਨ ਦੀ ਆਕ੍ਰਿਤੀ ਵਿੱਚ ਤਬਦੀਲੀਆਂ ਨੂੰ ਲਾਗੂ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"ਅਗਿਆਤ"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"ਹਟਾਓ"</string>
<string name="abandoned_search" msgid="891119232568284442">"ਖੋਜੋ"</string>
@@ -78,6 +88,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"ਇਸ ਆਈਕਨ ਲਈ ਐਪ ਇੰਸਟੌਲ ਨਹੀਂ ਕੀਤਾ ਹੋਇਆ ਹੈ। ਤੁਸੀਂ ਇਸਨੂੰ ਹਟਾ ਸਕਦੇ ਹੋ ਜਾਂ ਐਪ ਖੋਜ ਸਕਦੇ ਹੋ ਅਤੇ ਇਸਨੂੰ ਮੈਨੂਅਲੀ ਇੰਸਟੌਲ ਕਰ ਸਕਦੇ ਹੋ।"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ਡਾਉਨਲੋਡ ਹੋਰ ਰਿਹਾ ਹੈ, <xliff:g id="PROGRESS">%2$s</xliff:g> ਸੰਪੂਰਣ"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ਸਥਾਪਿਤ ਕਰਨ ਦੀ ਉਡੀਕ ਕਰ ਰਿਹਾ ਹੈ"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> ਵਿਜਿਟ"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"ਹੋਮ ਸਕ੍ਰੀਨ ਵਿੱਚ ਜੋੜੋ"</string>
<string name="action_move_here" msgid="2170188780612570250">"ਆਈਟਮ ਨੂੰ ਇੱਥੇ ਮੂਵ ਕਰੋ"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"ਆਈਟਮ ਨੂੰ ਮੁੱਖ ਸਕ੍ਰੀਨ ਵਿੱਚ ਜੋੜਿਆ ਗਿਆ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index feb2a36..e611fcd 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Pobrana aplikacja została wyłączona w trybie awaryjnym"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widżety są wyłączone w trybie bezpiecznym"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Skrót nie jest dostępny"</string>
+ <string name="home_screen" msgid="806512411299847073">"Ekran główny"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Działania niestandardowe"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Aby dodać widżet, kliknij go i przytrzymaj."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Kliknij dwukrotnie i przytrzymaj, by wybrać widżet lub użyć działań niestandardowych."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Szerokość %1$d, wysokość %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Kliknij i przytrzymaj, by umieścić ręcznie"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Dodaj automatycznie"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Szukaj w aplikacjach"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Wczytuję aplikacje…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Nie znaleziono aplikacji pasujących do zapytania „<xliff:g id="QUERY">%1$s</xliff:g>”"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Wyszukaj więcej aplikacji"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Powiadomienia"</string>
<string name="out_of_space" msgid="4691004494942118364">"Brak miejsca na tym ekranie głównym."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Brak miejsca w Ulubionych"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Lista aplikacji"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Zezwalaj na obrót ekranu głównego"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Po obróceniu telefonu"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Obecne ustawienia wyświetlania nie pozwalają na obrót ekranu"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Dodaj ikonę do ekranu głównego"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"W przypadku nowych aplikacji"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Brak informacji"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Usuń"</string>
<string name="abandoned_search" msgid="891119232568284442">"Szukaj"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Aplikacja, której odpowiada ta ikona, nie jest zainstalowana. Możesz usunąć ikonę lub wyszukać aplikację i zainstalować ją ręcznie."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Pobieranie elementu <xliff:g id="NAME">%1$s</xliff:g>, ukończono: <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> oczekuje na instalację"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> – widżety"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Dodaj do strony głównej"</string>
<string name="action_move_here" msgid="2170188780612570250">"Przenieś element tutaj"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Element został dodany do ekranu głównego"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 6d684fa..0aa987b 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplicação transferida desativada no Modo de segurança"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets desativados no Modo de segurança"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"O atalho não está disponível"</string>
+ <string name="home_screen" msgid="806512411299847073">"Ecrã principal"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Ações personalizadas"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Prima sem soltar para escolher um widget."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Toque duas vezes sem soltar para escolher um widget ou utilize ações personalizadas."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de largura por %2$d de altura"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Toque sem soltar para colocar manualmente"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Adicionar automaticamente"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Pesquisar aplicações"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"A carregar aplicações..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Não foram encontradas aplic. que correspondam a \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Pesquisar mais aplicações"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Notificações"</string>
<string name="out_of_space" msgid="4691004494942118364">"Sem espaço suficiente neste Ecrã principal."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Não existe mais espaço no tabuleiro de Favoritos"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Lista de aplicações"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Permitir rotação do ecrã principal"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Quando o telemóvel é rodado"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"A definição de visualização atual não permite a rotação"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Adicionar ícone ao ecrã principal"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Para novas aplicações"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Desconhecido"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Remover"</string>
<string name="abandoned_search" msgid="891119232568284442">"Pesquisar"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"A aplicação deste ícone não está instalada. Pode removê-lo ou pesquisar a aplicação e instalá-la manualmente."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"A transferir o <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> concluído"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"A aguardar a instalação do <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widgets de <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Adicionar ao Ecrã principal"</string>
<string name="action_move_here" msgid="2170188780612570250">"Mover o item para aqui"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Item adicionado ao ecrã principal"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 82a756f..39a1651 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"App transferido por download desativado no modo de segurança"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets desativados no modo de segurança"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"O atalho não está disponível"</string>
+ <string name="home_screen" msgid="806512411299847073">"Tela inicial"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Ações personalizadas"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Toque e pressione para selecionar um widget."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Toque duas vezes e segure para selecionar um widget ou usar ações personalizadas."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de largura por %2$d de altura"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Toque e mantenha pressionado para posicionar manualmente"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Adicionar automaticamente"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Pesquisar apps"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Carregando apps…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Nenhum app encontrado que corresponda a \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Pesquisar mais apps"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Notificações"</string>
<string name="out_of_space" msgid="4691004494942118364">"Não há mais espaço na tela inicial."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Sem espaço na bandeja de favoritos"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Lista de apps"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Permitir rotação da tela inicial"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Quando o smartphone for girado"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"A configuração atual de exibição não permite rotação"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Adicionar ícone à tela inicial"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Para novos apps"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Desconhecido"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Remover"</string>
<string name="abandoned_search" msgid="891119232568284442">"Pesquisar"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"O app deste ícone não está instalado. Você pode remover o ícone, ou procurar o app e instalá-lo manualmente."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Fazendo download de <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> concluído"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Aguardando instalação de <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widgets do <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Adicionar à tela inicial"</string>
<string name="action_move_here" msgid="2170188780612570250">"Mover item para cá"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Item adicionado à tela inicial"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 5905bcf..c7c0994 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplicația descărcată este dezactivată în modul de siguranță"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgeturile sunt dezactivate în modul de siguranță"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Comanda rapidă nu este disponibilă"</string>
+ <string name="home_screen" msgid="806512411299847073">"Ecran de pornire"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Acțiuni personalizate"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Atingeți lung un widget pentru a-l alege."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Atingeți de două ori și mențineți apăsat ca să alegeți un widget sau folosiți acțiuni personalizate."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d lățime și %2$d înălțime"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Atingeți lung pentru a plasa manual"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Adăugați automat"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Căutați aplicații"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Se încarcă aplicațiile..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Nu s-a găsit nicio aplicație pentru „<xliff:g id="QUERY">%1$s</xliff:g>”"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Căutați mai multe aplicații"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Notificări"</string>
<string name="out_of_space" msgid="4691004494942118364">"Nu mai este loc pe acest Ecran de pornire."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Spațiu epuizat în bara Preferate"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Lista de aplicații"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Permiteți rotirea ecranului de pornire"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Când telefonul este rotit"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Setarea actuală a afișajului nu permite rotirea"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Adaugă pictograme în ecranul de pornire"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Pentru aplicații noi"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Necunoscut"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Eliminați"</string>
<string name="abandoned_search" msgid="891119232568284442">"Căutați"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Aplicația pentru această pictogramă nu este instalată. Puteți să ștergeți pictograma sau să căutați aplicația și s-o instalați manual."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> se descarcă (finalizat <xliff:g id="PROGRESS">%2$s</xliff:g>)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> așteaptă instalarea"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widgeturi <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Adăugați pe ecranul de pornire"</string>
<string name="action_move_here" msgid="2170188780612570250">"Mutați elementul aici"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Element adăugat pe ecranul de pornire"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index ad30c97..acb49d5 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Скачанное приложение отключено в безопасном режиме"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Виджеты отключены в безопасном режиме"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Ярлык недоступен"</string>
+ <string name="home_screen" msgid="806512411299847073">"Главный экран"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Пользовательские действия"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Чтобы выбрать виджет, нажмите на значок и удерживайте его."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Чтобы выбрать виджет, нажмите на него дважды и не отпускайте или выполните предложенные действия."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d x %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Ширина %1$d, высота %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Нажмите и удерживайте, чтобы добавить вручную"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Добавить автоматически"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Поиск приложений"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Загрузка…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"По запросу \"<xliff:g id="QUERY">%1$s</xliff:g>\" ничего не найдено"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Искать другие приложения"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Уведомления"</string>
<string name="out_of_space" msgid="4691004494942118364">"На этом экране все занято"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"В разделе \"Избранное\" больше нет места"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Список приложений"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Разрешить поворачивать главный экран"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Когда телефон повернут"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"В настройках отключен поворот экрана"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Добавлять значки"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Добавлять значки установленных приложений на главный экран."</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Неизвестно"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Удалить"</string>
<string name="abandoned_search" msgid="891119232568284442">"Найти"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Приложение не установлено. Вы можете удалить значок или найти приложение и установить его вручную."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Скачивается \"<xliff:g id="NAME">%1$s</xliff:g>\" (<xliff:g id="PROGRESS">%2$s</xliff:g>)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Ожидание установки \"<xliff:g id="NAME">%1$s</xliff:g>\""</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>: виджеты"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Добавить на главный экран"</string>
<string name="action_move_here" msgid="2170188780612570250">"Переместить элемент сюда"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Элемент добавлен на главный экран"</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 1e0ed28..569a77f 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"ආරක්ෂිත ආකාරය තුළ බාගන්න ලද යෙදුම් අබල කරන්න"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"සුරක්ෂිත ආකාරය තුළ විජටය අබල කරන ලදි"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"කෙටි මග ලබා ගත නොහැකිය"</string>
+ <string name="home_screen" msgid="806512411299847073">"මුල් පිටු තිරය"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"අභිරුචි ක්රියා"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"විජට් එක ස්පර්ශ කර අහුලා ගැනීමට අල්ලාගෙන සිටින්න."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"විජට් එකක් අහුලා ගැනීමට හෝ අභිරුචි ක්රියා කිරීමට ඩබල් ටැප් කර අල්ලා ගෙන සිටින්න."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"පළල %1$d උස %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"අතින් ස්ථානගත කිරීමට ස්පර්ශ කර අල්ලාගෙන සිටින්න"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"ස්වයංක්රියව එක් කරන්න"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"යෙදුම් සෙවීම"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"යෙදුම් පූරණය වෙමින්…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" සමග ගැළපෙන යෙදුම් හමු නොවිණි"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"තව යෙදුම් සඳහා සොයන්න"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"දැනුම්දීම්"</string>
<string name="out_of_space" msgid="4691004494942118364">"මෙම මුල් පිටු තිරය මත තවත් අවසර නැත."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"ප්රියතම දෑ ඇති තැටියේ තවත් ඉඩ නොමැත"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"යෙදුම් ලැයිස්තුව"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"මුල් පිටු තිරය කරකැවීමට ඉඩ දෙන්න"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"දුරකථනය කරකවන විට"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"වත්මන් සංදර්ශක සැකසීම් කරකැවීමට සහාය නොදක්වයි"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"මුල් පිටු තිරය වෙත අයිකනය එක් කරන්න"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"නව යෙදුම් සඳහා"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"නොදනී"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"ඉවත් කරන්න"</string>
<string name="abandoned_search" msgid="891119232568284442">"සොයන්න"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"මෙම නිරුපකයට යෙදුම ස්ථාපනය කර නොමැත. ඔබට එය ඉවත් කළ හැක, හෝ යෙදුම් සඳහා සොයන්න සහ අතින් ස්ථාපනය කරන්න."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> බාගත කරමින්, <xliff:g id="PROGRESS">%2$s</xliff:g> සම්පූර්ණයි"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ස්ථාපනය කිරීමට බලා සිටිමින්"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> විජට්"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"මුල් තිරය වෙත එක් කරන්න"</string>
<string name="action_move_here" msgid="2170188780612570250">"මෙතනට අයිතමය ගෙන එන්න"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"අයිතමය මුල් තිරය වෙත එකතු කරන ලදි"</string>
@@ -88,7 +102,7 @@
<string name="move_to_hotseat_position" msgid="6295412897075147808">"ප්රියතම ස්ථානය <xliff:g id="NUMBER">%1$s</xliff:g> වෙත ගෙන යන්න"</string>
<string name="item_moved" msgid="4606538322571412879">"අයිතමය ගෙන යන ලදි"</string>
<string name="add_to_folder" msgid="9040534766770853243">"ෆෝල්ඩරය එක් කරන්න: <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="add_to_folder_with_app" msgid="4534929978967147231">"<xliff:g id="NAME">%1$s</xliff:g> සමග ෆෝල්ඩරය වෙත එකතු කරන්න"</string>
+ <string name="add_to_folder_with_app" msgid="4534929978967147231">"<xliff:g id="NAME">%1$s</xliff:g> සමඟ ෆෝල්ඩරය වෙත එක් කරන්න"</string>
<string name="added_to_folder" msgid="4793259502305558003">"අයිතමය ෆෝඩරය වෙතට එක් කරන ලදි"</string>
<string name="create_folder_with" msgid="4050141361160214248">"මේ සමග ෆෝල්ඩරය සාදන්න: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_created" msgid="6409794597405184510">"ෆෝල්ඩරය සාදන ලදි"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 7210d79..dd03fd6 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Stiahnutá aplikácia je v núdzovom režime zakázaná"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Miniaplikácie sú v núdzovom režime zakázané"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Skratky nie sú k dispozícii"</string>
+ <string name="home_screen" msgid="806512411299847073">"Plocha"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Vlastné akcie"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Miniaplikáciu pridáte stlačením a podržaním."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Miniaplikáciu pridáte dvojitým klepnutím a pridržaním alebo pomocou vlastných akcií."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"šírka %1$d, výška %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Položku umiestnite ručne klepnutím a podržaním"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Pridať automaticky"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Hľadať aplikácie"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Načítavajú sa aplikácie..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Nenašli sa žiadne aplikácie zodpovedajúce dopytu <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Hľadať ďalšie aplikácie"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Upozornenia"</string>
<string name="out_of_space" msgid="4691004494942118364">"Na tejto ploche už nie je miesto"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Na paneli Obľúbené položky už nie je miesto"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Zoznam aplikácií"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Povoliť otáčanie plochy"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Pri otočení telefónu"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Aktuálne nastavenie obrazovky nepovoľuje otáčanie"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Pridať ikonu na plochu"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Pri inštalácii novej aplikácie"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Neznáme"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Odstrániť"</string>
<string name="abandoned_search" msgid="891119232568284442">"Vyhľadať"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Aplikácia, ktorú zastupuje táto ikona, nie je nainštalovaná. Ikonu môžete odstrániť alebo vyhľadajte aplikáciu a ručne ju nainštalujte."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Sťahuje sa aplikácia <xliff:g id="NAME">%1$s</xliff:g>. Stiahnuté: <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Aplikácia <xliff:g id="NAME">%1$s</xliff:g> čaká na inštaláciu"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Miniaplikácie <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Pridať na plochu"</string>
<string name="action_move_here" msgid="2170188780612570250">"Presunúť položku sem"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Položka bola pridaná na plochu"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index b55cb0d..f8761d2 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Prenesena aplikacija je onemogočena v Varnem načinu"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Pripomočki so onemogočeni v varnem načinu"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Bližnjica ni na voljo"</string>
+ <string name="home_screen" msgid="806512411299847073">"Začetni zaslon"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Dejanja po meri"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Za izbiro pripomočka se ga dotaknite in pridržite."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Če želite izbrati pripomoček ali uporabiti dejanja po meri, se ga dvakrat dotaknite in ga pridržite."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Širina %1$d, višina %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Dotaknite se elementa in ga pridržite, da ga ročno dodate"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Samodejno dodaj"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Iskanje po aplikacijah"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Nalaganje aplikacij …"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Ni aplikacij, ki bi ustrezale poizvedbi »<xliff:g id="QUERY">%1$s</xliff:g>«"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Iskanje več aplikacij"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Obvestila"</string>
<string name="out_of_space" msgid="4691004494942118364">"Na tem začetnem zaslonu ni več prostora."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"V vrstici za priljubljene ni več prostora"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Seznam aplikacij"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Omogočanje sukanja začetnega zaslona"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Ko se telefon zasuka"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Trenutna nastavitev zaslona ne dovoljuje sukanja"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Dodaj ikono na začetni zaslon"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Za nove aplikacije"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Neznano"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Odstrani"</string>
<string name="abandoned_search" msgid="891119232568284442">"Iskanje"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Aplikacija za to ikono ni nameščena. Lahko jo odstranite ali poiščete aplikacijo in to namestite ročno."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Prenašanje aplikacije <xliff:g id="NAME">%1$s</xliff:g>; preneseno <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Aplikacija <xliff:g id="NAME">%1$s</xliff:g> čaka na namestitev"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Pripomočki za <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Dodajanje na začetni zaslon"</string>
<string name="action_move_here" msgid="2170188780612570250">"Premik elementa sem"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Element je bil dodan na začetni zaslon"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index c4fbaf3..2b65684 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplikacioni i shkarkuar është i çaktivizuar në modalitetin e sigurt"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Miniaplikacionet janë të çaktivizuara në modalitetin e sigurt"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Shkurtorja nuk është e disponueshme"</string>
+ <string name="home_screen" msgid="806512411299847073">"Ekrani bazë"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Veprimet e personalizuara"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Prek dhe mbaj shtypur për të zgjedhur një miniaplikacion."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Prek dy herë dhe mbaj shtypur për të zgjedhur një miniaplikacion ose për të përdorur veprimet e personalizuara."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d i gjerë me %2$d i lartë"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Prek dhe mbaj të shtypur për të vendosur në mënyrë manuale"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Shto automatikisht"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Kërko për aplikacione"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Po ngarkon aplikacionet..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Nuk u gjet asnjë aplikacion që përputhet me \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Kërko për më shumë aplikacione"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Njoftimet"</string>
<string name="out_of_space" msgid="4691004494942118364">"Nuk ka më hapësirë në këtë ekran bazë."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Nuk ka më hapësirë në tabakanë \"Të preferuarat\""</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Lista e aplikacioneve"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Lejo rrotullimin e ekranit kryesor"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Kur telefoni rrotullohet"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Cilësimi aktuali i afishimit nuk lejon rrotullimin"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Shto ikonë në ekranin bazë"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Për aplikacionet e reja"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"I panjohur"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Hiq"</string>
<string name="abandoned_search" msgid="891119232568284442">"Kërko"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Aplikacioni për këtë ikonë nuk është i instaluar. Mund ta heqësh ose të kërkosh aplikacionin dhe ta instalosh atë në mënyrë manuale."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> po shkarkohet, <xliff:g id="PROGRESS">%2$s</xliff:g> të përfunduara"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> po pret të instalohet"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Miniaplikacionet e <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Shto në Ekranin bazë"</string>
<string name="action_move_here" msgid="2170188780612570250">"Zhvendose artikullin këtu"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Artikulli u shtua tek ekrani bazë"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 67a63cf..ac21142 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Преузета апликација је онемогућена у Безбедном режиму"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Виџети су онемогућени у Безбедном режиму"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Пречица није доступна"</string>
+ <string name="home_screen" msgid="806512411299847073">"Почетни екран"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Прилагођене радње"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Додирните и задржите да бисте изабрали виџет."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Двапут додирните и задржите да бисте изабрали виџет или користите прилагођене радње."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d×%2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"ширина од %1$d и висина од %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Додирните и задржите да бисте поставили ручно"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Аутоматски додај"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Претражите апликације"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Апликације се учитавају..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Није пронађена ниједна апликација за „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Претражи још апликација"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Обавештења"</string>
<string name="out_of_space" msgid="4691004494942118364">"Нема више простора на овом почетном екрану."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Нема више простора на траци Омиљено"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Листа апликација"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Дозволи ротацију почетног екрана"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Када се телефон ротира"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Актуелно подешавање приказа не дозвољава ротацију"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Додај икону на почетни екран"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"За нове апликације"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Непознато"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Уклони"</string>
<string name="abandoned_search" msgid="891119232568284442">"Претражи"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Апликација за ову икону није инсталирана. Можете да је уклоните или да потражите апликацију и инсталирате је ручно."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> се преузима, завршено је <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> чека на инсталирање"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Виџети за <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Додај на почетни екран"</string>
<string name="action_move_here" msgid="2170188780612570250">"Премести ставку овде"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Ставка је додата на почетни екран"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index dfcc1b0..7d6e053 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Den hämtade appen inaktiverades i säkert läge"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets är inaktiverade i felsäkert läge"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Genvägen är inte tillgänglig"</string>
+ <string name="home_screen" msgid="806512411299847073">"Startskärm"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Anpassade åtgärder"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Tryck länge om du vill flytta en widget."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Tryck två gånger och håll kvar om du vill ta upp en widget eller använda anpassade åtgärder."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d bred gånger %2$d hög"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Placera manuellt genom att trycka länge"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Lägg till automatiskt"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Sök efter appar"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Läser in appar …"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Det gick inte att hitta några appar som matchar <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Sök efter fler appar"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Aviseringar"</string>
<string name="out_of_space" msgid="4691004494942118364">"Det finns inte plats för mer på den här startskärmen."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Favoritfältet är fullt"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Applista"</string>
@@ -58,8 +63,8 @@
<string name="workspace_scroll_format" msgid="8458889198184077399">"Startskärmen %1$d av %2$d"</string>
<string name="workspace_new_page" msgid="257366611030256142">"Ny sida på startskärmen"</string>
<string name="folder_opened" msgid="94695026776264709">"Mappen är öppen, <xliff:g id="WIDTH">%1$d</xliff:g> gånger <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
- <string name="folder_tap_to_close" msgid="4625795376335528256">"Tryck om du vill stänga mappen"</string>
- <string name="folder_tap_to_rename" msgid="4017685068016979677">"Tryck om du vill spara namnändringen"</string>
+ <string name="folder_tap_to_close" msgid="4625795376335528256">"Tryck för att stänga mappen"</string>
+ <string name="folder_tap_to_rename" msgid="4017685068016979677">"Tryck för att spara namnändringen"</string>
<string name="folder_closed" msgid="4100806530910930934">"Mappen är stängd"</string>
<string name="folder_renamed" msgid="1794088362165669656">"Mappen har bytt namn till <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format" msgid="6629239338071103179">"Mapp: <xliff:g id="NAME">%1$s</xliff:g>"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Tillåt rotering av startskärmen"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"När mobilen vrids"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Rotering tillåts inte i de nuvarande skärminställningarna"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Lägg till ikonen på startskärmen"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"För nya appar"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Okänt"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Ta bort"</string>
<string name="abandoned_search" msgid="891119232568284442">"Sök"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Appen för den här ikonen har inte installerats. Du kan ta bort den eller söka efter appen och installera den manuellt."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> laddas ned, <xliff:g id="PROGRESS">%2$s</xliff:g> klart"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> väntar på installation"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> widgetar"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Lägg till på startskärmen"</string>
<string name="action_move_here" msgid="2170188780612570250">"Flytta objekt hit"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Objektet har lagts till på startskärmen"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 03c8f06..62e3385 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Programu iliyopakuliwa imezimwa katika Hali Salama"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Wijeti zimezimwa katika hali ya Usalama"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Hakuna njia ya mkato"</string>
+ <string name="home_screen" msgid="806512411299847073">"Skrini ya kwanza"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Vitendo maalum"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Gusa na ushikilie ili kuteua wijeti."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Gonga mara mbili na ushikilie ile uchague wijeti au utumie vitendo maalum."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Upana wa %1$d na kimo cha %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Gusa na ushikilie ili uweke mwenyewe"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Ongeza kiotomatiki"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Tafuta Programu"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Inapakia Programu..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Haikupata programu zinazolingana na \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Tafuta programu zaidi"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Arifa"</string>
<string name="out_of_space" msgid="4691004494942118364">"Hakuna nafasi katika skrini hii ya Mwanzo."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Hakuna nafasi zaidi katika treya ya Vipendeleo"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Orodha ya programu"</string>
@@ -73,6 +78,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Ruhusu kuzungusha skrini ya Kwanza"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Simu inapozungushwa"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Mipangilio ya sasa ya sehemu ya Onyesho hairuhusu kuzungusha"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Ongeza aikoni kwenye Skrini ya kwanza"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Kwa ajili ya programu mpya"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Yasiyojulikana"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Ondoa"</string>
<string name="abandoned_search" msgid="891119232568284442">"Tafuta"</string>
@@ -80,6 +93,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Programu ya ikoni hii haijasakinishwa. Unaweza kuiondoa, au utafute programu na uisakinishe wewe mwenyewe."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> inapakuliwa, <xliff:g id="PROGRESS">%2$s</xliff:g> imekamilika"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> inasubiri kusakinisha"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Wijeti za <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Ongeza kwenye skrini ya Kwanza"</string>
<string name="action_move_here" msgid="2170188780612570250">"Hamishia kipengee hapa"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Kipengee kimeongezwa kwenye skrini ya kwanza"</string>
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index 2838088..ead666c 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -16,8 +16,6 @@
<resources>
<!-- All Apps -->
- <dimen name="all_apps_grid_view_start_margin">0dp</dimen>
- <dimen name="all_apps_grid_section_text_size">26sp</dimen>
<dimen name="all_apps_background_canvas_width">850dp</dimen>
<dimen name="all_apps_background_canvas_height">525dp</dimen>
diff --git a/res/values-sw720dp-land/dimens.xml b/res/values-sw720dp-land/dimens.xml
deleted file mode 100644
index 514980f..0000000
--- a/res/values-sw720dp-land/dimens.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
- <!-- the area at the edge of the screen that makes the workspace go left
- or right while you're dragging. -->
- <dimen name="scroll_zone">100dip</dimen>
-</resources>
diff --git a/res/values-sw720dp/styles.xml b/res/values-sw720dp/styles.xml
index 674edaa..de809b1 100644
--- a/res/values-sw720dp/styles.xml
+++ b/res/values-sw720dp/styles.xml
@@ -25,6 +25,7 @@
<item name="android:windowShowWallpaper">true</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowActionModeOverlay">true</item>
+ <item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
</style>
<!-- Workspace -->
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 7db0ae4..a1e00a1 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"இறக்கிய பயன்பாடு பாதுகாப்பு முறையில் முடக்கப்பட்டது"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"பாதுகாப்புப் பயன்முறையில் விட்ஜெட்கள் முடக்கப்பட்டுள்ளன"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"குறுக்குவழி இல்லை"</string>
+ <string name="home_screen" msgid="806512411299847073">"முகப்புத் திரை"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"தனிப்பயன் செயல்கள்"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"விட்ஜெட்டைத் தேர்வுசெய்ய தொட்டுப் பிடிக்கவும்."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"விட்ஜெட்டைத் தேர்ந்தெடுக்க இருமுறை தட்டிப் பிடிக்கவும் அல்லது தனிப்பயன் செயல்களைப் பயன்படுத்தவும்."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d அகலத்திற்கு %2$d உயரம்"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"நீங்களே சேர்க்க, தொட்டுப் பிடித்திருக்கவும்"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"தானாகவே சேர்"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"பயன்பாடுகளில் தேடுக"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"பயன்பாடுகளை ஏற்றுகிறது..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" உடன் பொருந்தும் பயன்பாடுகள் இல்லை"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"கூடுதல் பயன்பாடுகளைத் தேடு"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"அறிவிப்புகள்"</string>
<string name="out_of_space" msgid="4691004494942118364">"முகப்புத் திரையில் இடமில்லை."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"பிடித்தவை ட்ரேயில் இடமில்லை"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"பயன்பாடுகளின் பட்டியல்"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"முகப்புத் திரை சுழற்சியை அனுமதி"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"மொபைலைச் சுழற்றும் போது"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"தற்போதைய திரை அமைப்பு சுழற்றுவதை அனுமதிக்கவில்லை"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"முகப்புத் திரையில் ஐகானைச் சேர்"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"புதிய பயன்பாடுகளுக்கு"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"தெரியாதது"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"அகற்று"</string>
<string name="abandoned_search" msgid="891119232568284442">"தேடு"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"ஐகானுக்கான பயன்பாடு நிறுவப்படவில்லை. இதை அகற்றலாம் அல்லது பயன்பாட்டைத் தேடி கைமுறையாக நிறுவலாம்."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g>ஐப் பதிவிறக்குகிறது, <xliff:g id="PROGRESS">%2$s</xliff:g> முடிந்தது"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g>ஐ நிறுவுவதற்காகக் காத்திருக்கிறது"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> விட்ஜெட்டுகள்"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"முகப்புத் திரையில் சேர்"</string>
<string name="action_move_here" msgid="2170188780612570250">"இங்கு நகர்த்து"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"முகப்புத் திரையில் சேர்க்கப்பட்டது"</string>
diff --git a/res/values-te-rIN/config.xml b/res/values-te-rIN/config.xml
new file mode 100644
index 0000000..56f98c3
--- /dev/null
+++ b/res/values-te-rIN/config.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="icon_shape_override_paths_values">
+ <item msgid="6640290598899495380"></item>
+ <item msgid="4009824731445917273">"M50,0L100,0 100,100 0,100 0,0z"</item>
+ <item msgid="3964229851574011244">"M50,0L80,0 A20,20,0,0 1 100,20 L100,80 A20,20,0,0 1 80,100 L20,100 A20,20,0,0 1 0,80 L 0,20 A20,20,0,0 1 20,0z"</item>
+ <item msgid="363553284746233331">"M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z"</item>
+ <item msgid="4319038504053267455">"M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0"</item>
+ <item msgid="483370082941112059">"M50,0A50,30 0,0,1 100,30V70A50,30 0,0,1 0,70V30A50,30 0,0,1 50,0z"</item>
+ </string-array>
+ <!-- no translation found for icon_shape_override_paths_names:0 (4837899951986816538) -->
+</resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 4e8b86f..65ca927 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"డౌన్లోడ్ చేసిన అనువర్తనం సురక్షిత మోడ్లో నిలిపివేయబడింది"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"సురక్షిత మోడ్లో విడ్జెట్లు నిలిపివేయబడ్డాయి"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"సత్వరమార్గం అందుబాటులో లేదు"</string>
+ <string name="home_screen" msgid="806512411299847073">"హోమ్ స్క్రీన్"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"అనుకూల చర్యలు"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"విడ్జెట్ను ఎంచుకోవడానికి తాకి & నొక్కి పెట్టండి."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"విడ్జెట్ను ఎంచుకోవడానికి లేదా అనుకూల చర్యలను ఉపయోగించడానికి రెండుసార్లు నొక్కి, ఉంచండి."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d వెడల్పు X %2$d ఎత్తు"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"మాన్యువల్గా ఉంచడానికి నొక్కి &amp పట్టుకోండి"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"స్వయంచాలకంగా జోడించు"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"అనువర్తనాలను శోధించండి"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"అనువర్తనాలను లోడ్ చేస్తోంది…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"కి సరిపోలే అనువర్తనాలేవీ కనుగొనబడలేదు"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"మరిన్ని అనువర్తనాల కోసం శోధించు"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"నోటిఫికేషన్లు"</string>
<string name="out_of_space" msgid="4691004494942118364">"ఈ హోమ్ స్క్రీన్లో ఖాళీ లేదు."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"ఇష్టమైనవి ట్రేలో ఖాళీ లేదు"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"అనువర్తనాల జాబితా"</string>
@@ -71,6 +76,11 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"హోమ్ స్క్రీన్ భ్రమణాన్ని అనుమతించండి"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"ఫోన్ను తిప్పినప్పుడు"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"ప్రస్తుత డిస్ప్లే సెట్టింగ్ భ్రమణాన్ని అనుమతించలేదు"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"హోమ్ స్క్రీన్కి చిహ్నాన్ని జోడించు"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"కొత్త అనువర్తనాల కోసం"</string>
+ <string name="icon_shape_override_label" msgid="2977264953998281004">"చిహ్న ఆకారాన్ని మార్చు"</string>
+ <string name="icon_shape_no_override" msgid="3678524428085518367">"మార్చవద్దు"</string>
+ <string name="icon_shape_override_progress" msgid="3461735694970239908">"చిహ్న ఆకార మార్పులను వర్తింపజేస్తోంది"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"తెలియదు"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"తీసివేయి"</string>
<string name="abandoned_search" msgid="891119232568284442">"శోధించు"</string>
@@ -78,6 +88,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"ఈ చిహ్నం యొక్క అనువర్తనం ఇన్స్టాల్ చేయబడలేదు. మీరు దీన్ని తీసివేయవచ్చు లేదా ఆ అనువర్తనం కోసం శోధించి దాన్ని మాన్యువల్గా ఇన్స్టాల్ చేయవచ్చు."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> డౌన్లోడ్ అవుతోంది, <xliff:g id="PROGRESS">%2$s</xliff:g> పూర్తయింది"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ఇన్స్టాల్ కావడానికి వేచి ఉంది"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> విడ్జెట్లు"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"హోమ్ స్క్రీన్కు జోడించు"</string>
<string name="action_move_here" msgid="2170188780612570250">"అంశాన్ని ఇక్కడికి తరలించు"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"అంశం హోమ్స్క్రీన్కి జోడించబడింది"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 90dec72..fb7b587 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"แอปที่ดาวน์โหลดถูกปิดในโหมดปลอดภัย"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"มีการปิดใช้งานวิดเจ็ตในเซฟโหมด"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ทางลัดไม่พร้อมใช้งาน"</string>
+ <string name="home_screen" msgid="806512411299847073">"หน้าจอหลัก"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"การทำงานที่กำหนดเอง"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"แตะค้างเพื่อรับวิดเจ็ต"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"แตะ 2 ครั้งค้างไว้เพื่อเลือกวิดเจ็ตหรือใช้การกระทำที่กำหนดเอง"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"กว้าง %1$d x สูง %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"แตะค้างไว้เพื่อวางด้วยตัวเอง"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"เพิ่มโดยอัตโนมัติ"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ค้นหาแอป"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"กำลังโหลดแอป…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"ไม่พบแอปที่ตรงกับ \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"ค้นหาแอปเพิ่มเติม"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"การแจ้งเตือน"</string>
<string name="out_of_space" msgid="4691004494942118364">"ไม่มีที่ว่างในหน้าจอหลักนี้"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"ไม่มีพื้นที่เหลือในถาดรายการโปรด"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"รายชื่อแอป"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"อนุญาตให้หมุนหน้าจอหลัก"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"เมื่อหมุนโทรศัพท์"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"การตั้งค่าการแสดงผลปัจจุบันไม่อนุญาตให้มีการหมุน"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"เพิ่มไอคอนในหน้าจอหลัก"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"สำหรับแอปใหม่"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"ไม่รู้จัก"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"ลบ"</string>
<string name="abandoned_search" msgid="891119232568284442">"ค้นหา"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"ยังไม่ได้ติดตั้งแอปสำหรับไอคอนนี้ คุณสามารถนำไอคอนออก หรือค้นหาแอปดังกล่าวแล้วติดตั้งด้วยตนเอง"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"กำลังดาวน์โหลด <xliff:g id="NAME">%1$s</xliff:g> เสร็จแล้ว <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> กำลังรอติดตั้ง"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"วิดเจ็ตของ <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"เพิ่มลงในหน้าแรก"</string>
<string name="action_move_here" msgid="2170188780612570250">"ย้ายรายการมาที่นี่"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"เพิ่มรายการไปยังหน้าจอหลักแล้ว"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index b944ebc..d18bd1e 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Naka-disable ang na-download na app sa Safe mode"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Naka-disable ang mga widget sa Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Hindi available ang shortcut"</string>
+ <string name="home_screen" msgid="806512411299847073">"Home screen"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Mga custom na pagkilos"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Pindutin nang matagal upang kumuha ng widget."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"I-double tap nang matagal upang pumili ng widget o gumamit ng mga custom na pagkilos."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d ang lapad at %2$d ang taas"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Pindutin nang matagal upang manual na ilagay"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Awtomatikong idagdag"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Mga App sa Paghahanap"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Nilo-load ang Mga App…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Walang nakitang Mga App na tumutugma sa \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Maghanap ng higit pang mga app"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Mga Notification"</string>
<string name="out_of_space" msgid="4691004494942118364">"Wala nang lugar sa Home screen na ito."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Wala nang lugar sa tray ng Mga Paborito"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Listahan ng mga app"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Payagan ang pag-rotate ng Home screen"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Kailan maro-rotate ang telepono"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Hindi pinahihintulutan ng kasalukuyang setting ng Display ang pag-rotate"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Idagdag ang icon sa Home screen"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Para sa mga bagong app"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Hindi kilala"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Alisin"</string>
<string name="abandoned_search" msgid="891119232568284442">"Maghanap"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Hindi naka-install ang app para sa icon na ito. Maaari mo itong alisin, o maaari mong hanapin ang app at i-install ito nang manu-mano."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Dina-download na ang <xliff:g id="NAME">%1$s</xliff:g>, tapos na ang <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Hinihintay nang mag-install ang <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Mga widget ng <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Idagdag sa Home screen"</string>
<string name="action_move_here" msgid="2170188780612570250">"Ilipat ang item dito"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Naidagdag sa home screen ang item"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index e95ee2d..0c655fd 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"İndirilen uygulama Güvenli modda devre dışı bırakıldı"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Güvenli modda widget\'lar devre dışı"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Kısayol kullanılamıyor"</string>
+ <string name="home_screen" msgid="806512411299847073">"Ana ekran"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Özel işlemler"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Widget seçmek için dokunun ve basılı tutun."</string>
- <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Bir widget\'ı seçmek veya özel işlemleri kullanmak için iki kez hafifçe dokunun ve basılı tutun."</string>
+ <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Bir widget\'ı seçmek veya özel işlemleri kullanmak için iki kez dokunun ve basılı tutun."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"genişlik: %1$d, yükseklik: %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Manuel olarak yerleştirmek için dokunun ve basılı tutun"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Otomatik olarak ekle"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Uygulamalarda Ara"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Uygulamalar Yükleniyor…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ile eşleşen uygulama bulunamadı"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Başka uygulamalar ara"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Bildirimler"</string>
<string name="out_of_space" msgid="4691004494942118364">"Bu Ana ekranda yer kalmadı."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Favoriler tepsisinde başka yer kalmadı"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Uygulamalar listesi"</string>
@@ -58,8 +63,8 @@
<string name="workspace_scroll_format" msgid="8458889198184077399">"Ana ekran %1$d / %2$d"</string>
<string name="workspace_new_page" msgid="257366611030256142">"Yeni ana ekran sayfası"</string>
<string name="folder_opened" msgid="94695026776264709">"Klasör açıldı, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
- <string name="folder_tap_to_close" msgid="4625795376335528256">"Klasörü kapatmak için hafifçe dokunun"</string>
- <string name="folder_tap_to_rename" msgid="4017685068016979677">"Yeni adın kaydedilmesi için hafifçe dokunun"</string>
+ <string name="folder_tap_to_close" msgid="4625795376335528256">"Klasörü kapatmak için dokunun"</string>
+ <string name="folder_tap_to_rename" msgid="4017685068016979677">"Yeni adın kaydedilmesi için dokunun"</string>
<string name="folder_closed" msgid="4100806530910930934">"Klasör kapatıldı"</string>
<string name="folder_renamed" msgid="1794088362165669656">"Klasörün adı <xliff:g id="NAME">%1$s</xliff:g> olarak değiştirildi"</string>
<string name="folder_name_format" msgid="6629239338071103179">"Klasör: <xliff:g id="NAME">%1$s</xliff:g>"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Ana ekranı döndürmeye izin ver"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Telefon döndürüldüğünde"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Mevcut Ekran ayarı, döndürmeye izin vermiyor"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Ana ekrana simge ekle"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Yeni uygulamalar için"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Bilinmiyor"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Kaldır"</string>
<string name="abandoned_search" msgid="891119232568284442">"Ara"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Bu simgenin uygulaması yüklü değil. Uygulamayı kaldırabilir veya arayıp manuel olarak yükleyebilirsiniz."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> indiriliyor, <xliff:g id="PROGRESS">%2$s</xliff:g> tamamlandı"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> uygulaması yüklenmek için bekliyor"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> widget\'ları"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Ana ekrana ekle"</string>
<string name="action_move_here" msgid="2170188780612570250">"Öğeyi buraya taşı"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Öğe ana ekrana eklendi"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 1f73f04..8679628 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Завантажений додаток вимкнено в безпечному режимі"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"У безпечному режимі віджети вимкнено"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Ярлик недоступний"</string>
+ <string name="home_screen" msgid="806512411299847073">"Головний екран"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Спеціальні дії"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Натисніть і утримуйте, щоб вибрати віджет."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Двічі натисніть і утримуйте, щоб вибрати віджет, або виконайте іншу дію."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Ширина – %1$d, висота – %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Натисніть і утримуйте, щоб додати вручну"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Додати автоматично"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Пошук додатків"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Завантаження додатків…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Немає додатків для запиту \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Шукати ще додатки"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Сповіщення"</string>
<string name="out_of_space" msgid="4691004494942118364">"На цьому головному екрані більше немає місця."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"В області \"Вибране\" немає місця"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Список додатків"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Дозволити обертання головного екрана"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Коли телефон обертається"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Поточні налаштування дисплея не підтримують обертання"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Додати значок на головний екран"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Для нових додатків"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Невідомо"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Видалити"</string>
<string name="abandoned_search" msgid="891119232568284442">"Шукати"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Додаток для цього значка не встановлено. Можна видалити значок або знайти додаток і встановити його вручну."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> завантажується, <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> очікує на завантаження"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Віджети додатка <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Додати на головний екран"</string>
<string name="action_move_here" msgid="2170188780612570250">"Перемістити елемент сюди"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Елемент додано на головний екран"</string>
diff --git a/res/values-ur-rPK/config.xml b/res/values-ur-rPK/config.xml
new file mode 100644
index 0000000..56f98c3
--- /dev/null
+++ b/res/values-ur-rPK/config.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="icon_shape_override_paths_values">
+ <item msgid="6640290598899495380"></item>
+ <item msgid="4009824731445917273">"M50,0L100,0 100,100 0,100 0,0z"</item>
+ <item msgid="3964229851574011244">"M50,0L80,0 A20,20,0,0 1 100,20 L100,80 A20,20,0,0 1 80,100 L20,100 A20,20,0,0 1 0,80 L 0,20 A20,20,0,0 1 20,0z"</item>
+ <item msgid="363553284746233331">"M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z"</item>
+ <item msgid="4319038504053267455">"M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0"</item>
+ <item msgid="483370082941112059">"M50,0A50,30 0,0,1 100,30V70A50,30 0,0,1 0,70V30A50,30 0,0,1 50,0z"</item>
+ </string-array>
+ <!-- no translation found for icon_shape_override_paths_names:0 (4837899951986816538) -->
+</resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index e4c637d..29d1cd5 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"ڈاؤن لوڈ کردہ ایپ کو محفوظ وضع میں غیر فعال کر دیا گیا"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"ویجیٹس کو محفوظ وضع میں غیر فعال کر دیا گیا"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"شارٹ کٹ دستیاب نہیں ہے"</string>
+ <string name="home_screen" msgid="806512411299847073">"ہوم اسکرین"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"حسب ضرورت کارروائیاں"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"کوئی ویجیٹ منتخب کرنے کیلئے ٹچ کریں اور پکڑے رہیں۔"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"کوئی ویجٹ منتخب کرنے یا حسب ضرورت کاروائیاں استعمال کرنے کیلئے دو بار تھپتھپائیں اور پکڑے رکھیں۔"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d چوڑا اور %2$d اونچا"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"دستی طور پر رکھنے کیلئے & ٹچ کرکے ہولڈ کریں"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"خود کار طور پر شامل کریں"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ایپس تلاش کریں"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"ایپس لوڈ ہو رہی ہیں…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" سے مماثل کوئی ایپس نہیں ملیں"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"مزید ایپس تلاش کریں"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"اطلاعات"</string>
<string name="out_of_space" msgid="4691004494942118364">"اس ہوم اسکرین پر مزید کوئی گنجائش نہیں ہے۔"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"پسندیدہ ٹرے میں مزید کوئی گنجائش نہیں ہے"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"ایپس کی فہرست"</string>
@@ -71,6 +76,11 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"ہوم اسکرین گھمانے کی اجازت دیں"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"جب فون گھمایا جاتا ہے"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"موجودہ ڈسپلے ترتیب گھمانے کی اجازت نہیں دیتی"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"آئیکن کو ہوم اسکرین میں شامل کریں"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"نئی ایپس کیلئے"</string>
+ <string name="icon_shape_override_label" msgid="2977264953998281004">"آئیکن کی شکل تبدیل کریں"</string>
+ <string name="icon_shape_no_override" msgid="3678524428085518367">"تبدیل نہ کریں"</string>
+ <string name="icon_shape_override_progress" msgid="3461735694970239908">"آئيکن کی شکل کی تبدیلیاں لاگو ہو رہی ہیں"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"نامعلوم"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"ہٹائیں"</string>
<string name="abandoned_search" msgid="891119232568284442">"تلاش کریں"</string>
@@ -78,6 +88,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"اس آئیکن کیلئے ایپ انسٹال کردہ نہیں ہے۔ آپ اسے ہٹا سکتے ہیں یا ایپ کو تلاش کر سکتے اور دستی طور پر اسے انسٹال کر سکتے ہیں۔"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ڈاؤن لوڈ ہو رہا ہے، <xliff:g id="PROGRESS">%2$s</xliff:g> مکمل ہو گیا"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> انسٹال ہونے کا انتظار کر رہی ہے"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> ویجیٹس"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"ہوم اسکرین میں شامل کریں"</string>
<string name="action_move_here" msgid="2170188780612570250">"آئٹم یہاں منتقل کریں"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"آئٹم کو ہوم اسکرین میں شامل کر دیا گیا"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 0e541d3..81be979 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Yuklab olingan ilova xavfsiz rejimda o‘chirib qo‘yildi"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Xavfsiz rejimda vidjetlar o‘chirib qo‘yilgan"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Tezkor tugmadan foydalanib bo‘lmaydi"</string>
+ <string name="home_screen" msgid="806512411299847073">"Bosh ekran"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Maxsus amallar"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Vidjetni tanlash uchun bosib turing."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Ikki marta bosib va bosib turgan holatda vidjetni tanlang yoki maxsus amaldan foydalaning."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Eni %1$d, bo‘yi %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Qo‘lda joylashtirish uchun bosib turing"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Avtomatik chiqarish"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Ilovalar ichidan qidirish"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Ilovalar yuklanmoqda…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"“<xliff:g id="QUERY">%1$s</xliff:g>” so‘rovi bo‘yicha hech narsa topilmadi"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Boshqa ilovalarni qidirish"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Bildirishnomalar"</string>
<string name="out_of_space" msgid="4691004494942118364">"Uy ekranida bitta ham xona yo‘q."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Ajratilganlarda birorta ham xona yo‘q"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Ilovalar ro‘yxati"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Asosiy ekranni aylantirishga ruxsat berish"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Telefon burilganda"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Ekran sozlamalariga ko‘ra uni aylantirib bo‘lmaydi"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Bosh ekranga ikonka qo‘shish"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Yangi o‘rnatilgan ilovalar ikonkasini bosh ekranga chiqarish"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Noma’lum"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"O‘chirish"</string>
<string name="abandoned_search" msgid="891119232568284442">"Qidirish"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Ilova o‘rnatilmagan. Belgini o‘chirib tashlashingiz yoki ilovani topib, uni qo‘lda o‘rnatishingiz mumkin."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> yuklab olinmoqda, <xliff:g id="PROGRESS">%2$s</xliff:g> bajarildi"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ilovasi o‘rnatilishi kutilmoqda"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> vidjetlari"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Bosh ekranga qo‘shish"</string>
<string name="action_move_here" msgid="2170188780612570250">"Obyektni bu yerga ko‘chirish"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Obyekt bosh ekranga qo‘shildi"</string>
diff --git a/res/drawable/bg_screenpanel.xml b/res/values-v26/bools.xml
similarity index 64%
rename from res/drawable/bg_screenpanel.xml
rename to res/values-v26/bools.xml
index 346fca0..30537fe 100644
--- a/res/drawable/bg_screenpanel.xml
+++ b/res/values-v26/bools.xml
@@ -1,8 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
-/*
-**
-** Copyright 2015, The Android Open Source Project
+/* Copyright 2017, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
@@ -18,10 +16,8 @@
*/
-->
-<!-- TODO(twickham): Remove this file and the screenpanel drawables -->
-<transition xmlns:android="http://schemas.android.com/apk/res/android" >
+<resources>
+ <bool name="notification_badging_enabled">true</bool>
- <item android:drawable="@drawable/screenpanel"/>
- <item android:drawable="@drawable/screenpanel_hover"/>
-
-</transition>
\ No newline at end of file
+ <bool name="enable_install_shortcut_api">false</bool>
+</resources>
\ No newline at end of file
diff --git a/res/values-v25/styles.xml b/res/values-v26/styles.xml
similarity index 67%
rename from res/values-v25/styles.xml
rename to res/values-v26/styles.xml
index ed670a9..cb18409 100644
--- a/res/values-v25/styles.xml
+++ b/res/values-v26/styles.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
-* Copyright (C) 2016 The Android Open Source Project
+* 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.
@@ -19,6 +19,13 @@
<resources>
<!-- Theme for the widget container. -->
<style name="WidgetContainerTheme" parent="@android:style/Theme.DeviceDefault.Settings">
- <item name="colorSecondary">?android:attr/colorSecondary</item>
+ <item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
</style>
+
+ <!-- From O and above, we show a dark nav bar in all-apps -->
+ <style name="AllAppsNavBarProtection">
+ <item name="android:alpha">0.6</item>
+ <item name="android:background">?android:attr/colorPrimary</item>
+ </style>
+
</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 2bf5ad6..5fc9542 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Ứng dụng đã tải xuống bị tắt ở chế độ An toàn"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Tiện ích con bị vô hiệu hóa ở chế độ an toàn"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Lối tắt không khả dụng"</string>
+ <string name="home_screen" msgid="806512411299847073">"Màn hình chính"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Tác vụ tùy chỉnh"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Chạm và giữ để chọn tiện ích con."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Nhấn đúp và giữ để chọn tiện ích hoặc sử dụng tác vụ tùy chỉnh."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Rộng %1$d x cao %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Chạm và giữ để đặt theo cách thủ công"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Tự động thêm"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Tìm kiếm ứng dụng"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Đang tải ứng dụng..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Không tìm thấy ứng dụng nào phù hợp với \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Tìm kiếm thêm ứng dụng"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Thông báo"</string>
<string name="out_of_space" msgid="4691004494942118364">"Không còn chỗ trên Màn hình chính này."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Không còn chỗ trong khay Mục yêu thích"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Danh sách ứng dụng"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Cho phép xoay Màn hình chính"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Khi xoay điện thoại"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Cài đặt Hiển thị hiện tại không cho phép xoay"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Thêm biểu tượng vào màn hình chính"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Cho ứng dụng mới"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Không xác định"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Xóa"</string>
<string name="abandoned_search" msgid="891119232568284442">"Tìm kiếm"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Ứng dụng cho biểu tượng này chưa được cài đặt. Bạn có thể xóa ứng dụng hoặc tìm kiếm và cài đặt ứng dụng theo cách thủ công."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Đang tải xuống <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> hoàn tất"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Đang chờ cài đặt <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Tiện ích của <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Thêm vào màn hình chính"</string>
<string name="action_move_here" msgid="2170188780612570250">"Di chuyển mục vào đây"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Đã thêm mục vào màn hình chính"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index e3ed354..a6f8bf4 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -25,16 +25,21 @@
<string name="activity_not_found" msgid="8071924732094499514">"未安装该应用。"</string>
<string name="activity_not_available" msgid="7456344436509528827">"应用不可用"</string>
<string name="safemode_shortcut_error" msgid="9160126848219158407">"安全模式下不允许使用下载的此应用"</string>
- <string name="safemode_widget_error" msgid="4863470563535682004">"安全模式下不允许使用小部件"</string>
+ <string name="safemode_widget_error" msgid="4863470563535682004">"安全模式下不允许使用微件"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"无法使用快捷方式"</string>
+ <string name="home_screen" msgid="806512411299847073">"主屏幕"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"自定义操作"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"触摸并按住小部件即可选择。"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"点按两次并按住小部件即可选择小部件,您也可以使用自定义操作。"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"宽 %1$d,高 %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"触摸并按住即可手动放置"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"自动添加"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"搜索应用"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"正在加载应用…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"未找到与“<xliff:g id="QUERY">%1$s</xliff:g>”相符的应用"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"搜索更多应用"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"通知"</string>
<string name="out_of_space" msgid="4691004494942118364">"此主屏幕上已没有空间。"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"收藏栏已满"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"应用列表"</string>
@@ -63,7 +68,7 @@
<string name="folder_closed" msgid="4100806530910930934">"文件夹已关闭"</string>
<string name="folder_renamed" msgid="1794088362165669656">"已将文件夹重命名为“<xliff:g id="NAME">%1$s</xliff:g>”"</string>
<string name="folder_name_format" msgid="6629239338071103179">"文件夹:<xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="widget_button_text" msgid="2880537293434387943">"小部件"</string>
+ <string name="widget_button_text" msgid="2880537293434387943">"微件"</string>
<string name="wallpaper_button_text" msgid="8404103075899945851">"壁纸"</string>
<string name="settings_button_text" msgid="8119458837558863227">"设置"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"已被您的管理员停用"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"允许旋转主屏幕"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"手机旋转时"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"当前的显示设置不允许旋转设备"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"将图标添加到主屏幕"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"适用于新应用"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"未知"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"移除"</string>
<string name="abandoned_search" msgid="891119232568284442">"搜索"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"未安装此图标对应的应用。您可以移除此图标,也可以尝试搜索相应的应用并手动安装。"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"正在下载<xliff:g id="NAME">%1$s</xliff:g>,已完成 <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g>正在等待安装"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>微件"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"添加到主屏幕"</string>
<string name="action_move_here" msgid="2170188780612570250">"将项目移至此处"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"已将项目添加到主屏幕"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index ad0f99a..867c350 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"在安全模式中無法使用「已下載的應用程式」功能"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"在安全模式中無法使用小工具"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"沒有可用的捷徑"</string>
+ <string name="home_screen" msgid="806512411299847073">"主畫面"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"自訂操作"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"輕觸並按住小工具即可選取。"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"連扲兩下,然後扲住,就可以新增小工具,或者執行自訂操作。"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d 闊,%2$d 高"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"按住即可手動新增"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"自動新增"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"搜尋應用程式"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"正在載入應用程式…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"無法找到與「<xliff:g id="QUERY">%1$s</xliff:g>」相符的應用程式"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"搜尋更多應用程式"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"通知"</string>
<string name="out_of_space" msgid="4691004494942118364">"主畫面已無空間。"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"我的收藏寄存區沒有足夠空間"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"應用程式清單"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"允許主畫面旋轉"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"當手機旋轉時"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"「目前顯示屏」設定不允許旋轉"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"將圖示加到主畫面"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"適用於新安裝的應用程式"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"不明"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"移除"</string>
<string name="abandoned_search" msgid="891119232568284442">"搜尋"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"您尚未安裝這個圖示代表的應用程式。您可以移除這個圖示,也可以搜尋該應用程式並手動安裝。"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"正在下載 <xliff:g id="NAME">%1$s</xliff:g>,已完成 <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"正在等待安裝 <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>小工具"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"新增至主畫面"</string>
<string name="action_move_here" msgid="2170188780612570250">"移動項目至這裡"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"已將項目加入至主畫面"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 24f391d..ee92cf1 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"在安全模式中無法使用「已下載的應用程式」功能"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"在安全模式下無法使用小工具"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"目前無法使用捷徑"</string>
+ <string name="home_screen" msgid="806512411299847073">"主螢幕"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"自訂動作"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"輕觸並按住小工具即可選取。"</string>
- <string name="long_accessible_way_to_add" msgid="4289502106628154155">"輕觸兩下並按住小工具即可選取,您也可以使用自訂動作。"</string>
+ <string name="long_accessible_way_to_add" msgid="4289502106628154155">"輕觸兩下並按住小工具即可選取,你也可以使用自訂動作。"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"寬度為 %1$d,高度為 %2$d"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"按住即可手動放置"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"自動新增"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"搜尋應用程式"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"正在載入應用程式…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"找不到符合「<xliff:g id="QUERY">%1$s</xliff:g>」的應用程式"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"搜尋更多應用程式"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"通知"</string>
<string name="out_of_space" msgid="4691004494942118364">"這個主螢幕已無空間。"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"「我的最愛」匣已無可用空間"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"應用程式清單"</string>
@@ -66,18 +71,27 @@
<string name="widget_button_text" msgid="2880537293434387943">"小工具"</string>
<string name="wallpaper_button_text" msgid="8404103075899945851">"桌布"</string>
<string name="settings_button_text" msgid="8119458837558863227">"設定"</string>
- <string name="msg_disabled_by_admin" msgid="6898038085516271325">"已由您的管理員停用"</string>
+ <string name="msg_disabled_by_admin" msgid="6898038085516271325">"已由你的管理員停用"</string>
<string name="accessibility_action_overview" msgid="6257665857640347026">"總覽"</string>
<string name="allow_rotation_title" msgid="7728578836261442095">"允許旋轉主螢幕"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"當手機旋轉時"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"目前的顯示設定不允許旋轉畫面"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"將圖示加到主螢幕"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"適用於新安裝的應用程式"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"不明"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"移除"</string>
<string name="abandoned_search" msgid="891119232568284442">"搜尋"</string>
<string name="abandoned_promises_title" msgid="7096178467971716750">"尚未安裝這個應用程式"</string>
- <string name="abandoned_promise_explanation" msgid="3990027586878167529">"您尚未安裝這個圖示代表的應用程式。您可以移除這個圖示,也可以搜尋該應用程式並手動安裝。"</string>
+ <string name="abandoned_promise_explanation" msgid="3990027586878167529">"你尚未安裝這個圖示代表的應用程式。你可以移除這個圖示,也可以搜尋該應用程式並手動安裝。"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"正在下載「<xliff:g id="NAME">%1$s</xliff:g>」,已完成 <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"正在等待安裝「<xliff:g id="NAME">%1$s</xliff:g>」"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"「<xliff:g id="NAME">%1$s</xliff:g>」小工具"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"新增至主畫面"</string>
<string name="action_move_here" msgid="2170188780612570250">"將項目移至這裡"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"已將項目新增到主畫面"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 10fbef4..aa918b3 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -27,14 +27,19 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Uhlelo lokusebenza olulandiwe lukhutshaziwe kumodi ephephile"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Amawijethi akhutshaziwe kwimodi yokuphepha"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Isinqamuleli asitholakali"</string>
+ <string name="home_screen" msgid="806512411299847073">"Isikrini sasekhaya"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Izenzo zangokwezifiso"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Thinta uphinde ubambe ukuze uphakamise iwijethi."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Thepha kabili bese uyabamba ukuze uthathe iwijethi noma sebenzisa izenzo ezingokwezifiso."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d ububanzi ngokungu-%2$d ukuya phezulu"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Thinta futhi ubambe ukuze ubeke ngokwenza"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Engeza ngokuzenzakalelayo"</string>
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Sesha Izinhlelo Zokusebenza"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Ilayisha izinhlelo zokusebenza..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Azikho izinhlelo zokusebenza ezitholakele ezifana ne-\"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Sesha izinhlelo zokusebenza eziningi"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Izaziso"</string>
<string name="out_of_space" msgid="4691004494942118364">"Asisekho isikhala kulesi sikrini Sasekhaya."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Asisekho isikhala kwitreyi lezintandokazi"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Uhlu lwezinhlelo zokusebenza"</string>
@@ -71,6 +76,14 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Vumela ukuphendukiswa kwesikrini sasekhaya"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Uma ifoni iphendukiswa"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Isilungiselelo sesiboniso samanje asivumeli ukuzungezisa"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Engeza isithonjana eskrinini sasekhaya"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Kwezinhlelo zokusebenza ezintsha"</string>
+ <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+ <skip />
+ <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+ <skip />
+ <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+ <skip />
<string name="package_state_unknown" msgid="7592128424511031410">"Akwaziwa"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Susa"</string>
<string name="abandoned_search" msgid="891119232568284442">"Sesha"</string>
@@ -78,6 +91,7 @@
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"Uhlelo lokusebenza lalesi sithonjana alufakiwe. Ungalisusa, noma sesha uhlelo lokusebenza bese uzifakela lona ngokuzenzela."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"I-<xliff:g id="NAME">%1$s</xliff:g> iyalandwa, <xliff:g id="PROGRESS">%2$s</xliff:g> kuqediwe"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ilinde ukufakwa"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> amawijethi"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"Faka kusikrini sasekhaya"</string>
<string name="action_move_here" msgid="2170188780612570250">"Hambisa into lapha"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Into ingezwe kusikrini sasekhaya"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 3423835..18f409f 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -27,6 +27,7 @@
<enum name="all_apps" value="1" />
<enum name="folder" value="2" />
<enum name="widget_section" value="3" />
+ <enum name="shortcut_popup" value="4" />
</attr>
<attr name="deferShadowGeneration" format="boolean" />
<attr name="customShadows" format="boolean" />
@@ -71,12 +72,6 @@
<attr name="folderItems" format="reference" />
</declare-styleable>
- <declare-styleable name="PreloadIconDrawable">
- <attr name="background" format="reference" />
- <attr name="ringOutset" format="dimension" />
- <attr name="indicatorSize" format="dimension" />
- </declare-styleable>
-
<declare-styleable name="InsettableFrameLayout_Layout">
<attr name="layout_ignoreInsets" format="boolean" />
</declare-styleable>
@@ -85,9 +80,6 @@
<attr name="hideParentOnDisable" format="boolean" />
</declare-styleable>
- <!-- Fallback attr for pre-API 25 support -->
- <attr name="colorSecondary" format="reference|color" />
-
<declare-styleable name="InvariantDeviceProfile">
<attr name="name" format="string" />
<attr name="minWidthDps" format="float" />
@@ -111,4 +103,12 @@
<attr name="defaultLayoutId" format="reference" />
</declare-styleable>
+ <declare-styleable name="CellLayout">
+ <attr name="containerType" format="integer">
+ <enum name="workspace" value="0" />
+ <enum name="hotseat" value="1" />
+ <enum name="folder" value="2" />
+ </attr>
+ </declare-styleable>
+
</resources>
diff --git a/res/drawable/bg_screenpanel.xml b/res/values/bools.xml
similarity index 64%
copy from res/drawable/bg_screenpanel.xml
copy to res/values/bools.xml
index 346fca0..53c67e2 100644
--- a/res/drawable/bg_screenpanel.xml
+++ b/res/values/bools.xml
@@ -1,8 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
-/*
-**
-** Copyright 2015, The Android Open Source Project
+/* Copyright 2017, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
@@ -18,10 +16,8 @@
*/
-->
-<!-- TODO(twickham): Remove this file and the screenpanel drawables -->
-<transition xmlns:android="http://schemas.android.com/apk/res/android" >
+<resources>
+ <bool name="notification_badging_enabled">false</bool>
- <item android:drawable="@drawable/screenpanel"/>
- <item android:drawable="@drawable/screenpanel_hover"/>
-
-</transition>
\ No newline at end of file
+ <bool name="enable_install_shortcut_api">true</bool>
+</resources>
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index d5ce786..58717c2 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -26,28 +26,24 @@
<color name="focused_background">#80c6c5c5</color>
<color name="workspace_icon_text_color">#FFF</color>
-
<color name="workspace_edge_effect_color">#FFFFFFFF</color>
- <color name="folder_edge_effect_color">#FF757575</color>
- <color name="page_indicator_dot_color">#FFDDDDDD</color>
- <color name="quantum_panel_text_color">#FF666666</color>
- <color name="quantum_panel_bg_color">#FFF5F5F5</color>
+ <color name="default_shadow_color_no_alpha">#FF000000</color>
<color name="outline_color">#FFFFFFFF</color>
- <color name="all_apps_divider_color">#1E000000</color>
- <color name="all_apps_caret_color">#FFFFFFFF</color>
- <color name="all_apps_caret_shadow_color">#22000000</color>
- <color name="all_apps_container_color">#FFF2F2F2</color>
- <color name="all_apps_navbar_color">#28000000</color>
<color name="spring_loaded_panel_color">#40FFFFFF</color>
<color name="spring_loaded_highlighted_panel_border_color">#FFF</color>
- <!-- Widgets view -->
- <color name="widgets_view_section_text_color">#FFFFFF</color>
- <color name="widgets_view_item_text_color">#C4C4C4</color>
+ <!-- Popup container -->
+ <color name="popup_header_background_color">#EEEEEE</color> <!-- Gray 200 -->
+ <color name="popup_background_color">#FFF</color>
+ <color name="notification_icon_default_color">#757575</color> <!-- Gray 600 -->
+ <color name="notification_color_beneath">#E0E0E0</color> <!-- Gray 300 -->
- <!-- Used as a fallback since colorSecondary doesn't exist pre-API 25 -->
- <color name="fallback_secondary_color">#FF37474F</color>
+ <!-- System shortcuts -->
+ <color name="system_shortcuts_icon_color">@android:color/tertiary_text_light</color>
+
+ <color name="legacy_icon_background">#FFFFFF</color>
+ <color name="icon_background">#E0E0E0</color> <!-- Gray 300 -->
</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index a942f02..19966f6 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -13,6 +13,26 @@
easily override the app name without providing all translations -->
<string name="derived_app_name" translatable="false">@string/app_name</string>
+ <!-- Values for icon shape overrides. These should correspond to entries defined
+ in icon_shape_override_paths_names -->
+ <string-array name="icon_shape_override_paths_values">
+ <item></item>
+ <item translatable="false">M50,0L100,0 100,100 0,100 0,0z</item>
+ <item translatable="false">M50,0L80,0 A20,20,0,0 1 100,20 L100,80 A20,20,0,0 1 80,100 L20,100 A20,20,0,0 1 0,80 L 0,20 A20,20,0,0 1 20,0z</item>
+ <item translatable="false">M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z</item>
+ <item translatable="false">M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0</item>
+ <item translatable="false">M50,0A50,30 0,0,1 100,30V70A50,30 0,0,1 0,70V30A50,30 0,0,1 50,0z</item>
+ </string-array>
+
+ <string-array name="icon_shape_override_paths_names">
+ <!-- Option to not change the icon shape on home screen. [CHAR LIMIT=50] -->
+ <item>@string/icon_shape_no_override</item>
+ <item translatable="false">Square</item>
+ <item translatable="false">Rounded corner rect</item>
+ <item translatable="false">Squircle</item>
+ <item translatable="false">Circle</item>
+ <item translatable="false">Cylinder</item>
+ </string-array>
<!-- DragController -->
<item type="id" name="drag_event_parity" />
@@ -72,13 +92,15 @@
filter the activities shown in the launcher. Can be empty. -->
<string name="app_filter_class" translatable="false"></string>
- <!-- List of package names that com.android.launcher3.action.LAUNCH
- should be targeting. Can be empty. -->
- <array name="launch_broadcast_targets" translatable="false"></array>
-
<!-- Name of an icon provider class. -->
<string name="icon_provider_class" translatable="false"></string>
+ <!-- Name of a drawable factory class. -->
+ <string name="drawable_factory_class" translatable="false"></string>
+
+ <!-- Name of a user event dispatcher class. -->
+ <string name="user_event_dispatcher_class" translatable="false"></string>
+
<!-- Package name of the default wallpaper picker. -->
<string name="wallpaper_picker_package" translatable="false"></string>
@@ -91,12 +113,16 @@
<!-- View ID used by cell layout to jail its content -->
<item type="id" name="cell_layout_jail_id" />
-<!-- Deep shortcuts -->
+ <!-- View ID used by PreviewImageView to cache its instance -->
+ <item type="id" name="preview_image_id" />
+
+<!-- Popup items -->
<integer name="config_deepShortcutOpenDuration">220</integer>
<integer name="config_deepShortcutArrowOpenDuration">80</integer>
<integer name="config_deepShortcutOpenStagger">40</integer>
<integer name="config_deepShortcutCloseDuration">150</integer>
<integer name="config_deepShortcutCloseStagger">20</integer>
+ <integer name="config_removeNotificationViewDuration">300</integer>
<!-- Accessibility actions -->
<item type="id" name="action_remove" />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index eff9d21..bd6466b 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -33,8 +33,6 @@
<dimen name="dynamic_grid_workspace_page_spacing">8dp</dimen>
<!-- Minimum space between workspace and hotseat in spring loaded mode -->
<dimen name="dynamic_grid_min_spring_loaded_space">8dp</dimen>
- <dimen name="dynamic_grid_container_land_left_padding">118dp</dimen>
- <dimen name="dynamic_grid_container_land_right_padding">66dp</dimen>
<!-- Drop target bar -->
<dimen name="dynamic_grid_drop_target_size">48dp</dimen>
@@ -54,6 +52,7 @@
<dimen name="container_fastscroll_thumb_min_width">5dp</dimen>
<dimen name="container_fastscroll_thumb_max_width">9dp</dimen>
+ <dimen name="container_fastscroll_popup_margin">18dp</dimen>
<dimen name="container_fastscroll_thumb_height">72dp</dimen>
<dimen name="container_fastscroll_thumb_touch_inset">-24dp</dimen>
<dimen name="container_fastscroll_popup_size">72dp</dimen>
@@ -61,11 +60,8 @@
<!-- All Apps -->
<dimen name="all_apps_button_scale_down">0dp</dimen>
- <dimen name="all_apps_grid_view_start_margin">0dp</dimen>
- <dimen name="all_apps_grid_section_y_offset">8dp</dimen>
- <dimen name="all_apps_grid_section_text_size">24sp</dimen>
+ <dimen name="all_apps_search_bar_field_height">48dp</dimen>
<dimen name="all_apps_search_bar_height">60dp</dimen>
- <dimen name="all_apps_search_bar_margin_top">12dp</dimen>
<dimen name="all_apps_search_bar_icon_margin_right">4dp</dimen>
<dimen name="all_apps_search_bar_icon_margin_top">1dp</dimen>
<dimen name="all_apps_list_bottom_padding">8dp</dimen>
@@ -85,11 +81,15 @@
<dimen name="all_apps_divider_margin_vertical">8dp</dimen>
- <dimen name="all_apps_bezel_swipe_height">24dp</dimen>
-
<!-- Widget tray -->
<dimen name="widget_preview_label_vertical_padding">8dp</dimen>
- <dimen name="widget_preview_label_horizontal_padding">8dp</dimen>
+ <dimen name="widget_preview_label_horizontal_padding">16dp</dimen>
+
+ <dimen name="widget_preview_shadow_blur">0.5dp</dimen>
+ <dimen name="widget_preview_key_shadow_distance">1dp</dimen>
+ <dimen name="widget_preview_corner_radius">2dp</dimen>
+ <dimen name="widget_preview_cell_divider_width">0.5dp</dimen>
+ <dimen name="widget_preview_shortcut_padding">8dp</dimen>
<dimen name="widget_section_height">56dp</dimen>
<dimen name="widget_section_icon_size">40dp</dimen>
@@ -113,14 +113,6 @@
<!-- the distance an icon must be dragged before button drop targets accept it -->
<dimen name="drag_distanceThreshold">30dp</dimen>
- <!-- the area at the edge of the screen that makes the workspace go left
- or right while you're dragging. -->
- <dimen name="scroll_zone">20dp</dimen>
-
- <!-- When dragging an item, how much bigger (fixed dps) the dragged view
- should be. If 0, it will not be scaled at all. -->
- <dimen name="dragViewScale">12dp</dimen>
-
<!-- Elevation for the drag view. It should be larger than elevation of all other drag sources
and drop targets like all-apps and folders -->
<dimen name="drag_elevation">30dp</dimen>
@@ -129,9 +121,6 @@
<dimen name="spring_loaded_panel_border">1dp</dimen>
-<!-- Theme -->
- <dimen name="quantum_panel_outer_padding">4dp</dimen>
-
<!-- Folders -->
<!-- The size of the padding on the preview background drawable -->
<dimen name="folder_preview_padding">6dp</dimen>
@@ -161,30 +150,69 @@
<!-- Deep shortcuts -->
<dimen name="deep_shortcuts_elevation">9dp</dimen>
- <dimen name="bg_pill_width">208dp</dimen>
- <dimen name="bg_pill_height">48dp</dimen>
- <dimen name="bg_pill_radius">24dp</dimen>
- <dimen name="deep_shortcuts_spacing">4dp</dimen>
- <dimen name="deferred_drag_view_scale">6dp</dimen>
+ <dimen name="bg_popup_item_width">220dp</dimen>
+ <dimen name="bg_popup_item_height">56dp</dimen>
+ <dimen name="popup_items_spacing">4dp</dimen>
+ <dimen name="pre_drag_view_scale">6dp</dimen>
<!-- an icon with shortcuts must be dragged this far before the container is removed. -->
<dimen name="deep_shortcuts_start_drag_threshold">16dp</dimen>
<dimen name="deep_shortcut_icon_size">36dp</dimen>
- <dimen name="deep_shortcut_padding_start">6dp</dimen>
- <dimen name="deep_shortcut_padding_end">16dp</dimen>
<dimen name="deep_shortcut_drawable_padding">8dp</dimen>
- <dimen name="deep_shortcut_anim_translation_y">5dp</dimen>
<dimen name="deep_shortcut_drag_handle_size">16dp</dimen>
- <dimen name="deep_shortcuts_arrow_width">10dp</dimen>
- <dimen name="deep_shortcuts_arrow_height">8dp</dimen>
- <dimen name="deep_shortcuts_arrow_vertical_offset">-2dp</dimen>
- <!-- deep_shortcut_padding_start + deep_shortcut_icon_size / 2 - deep_shortcuts_arrow_width / 2-->
- <!-- Note that this works for right-aligned shortcuts, too, because
- deep_shortcut_padding_end + deep_shortcut_drag_handle_size / 2 - deep_shortcuts_arrow_width / 2
- also happens to equal 19dp-->
- <dimen name="deep_shortcuts_arrow_horizontal_offset">19dp</dimen>
+ <dimen name="popup_padding_start">10dp</dimen>
+ <dimen name="popup_padding_end">16dp</dimen>
+ <dimen name="popup_arrow_width">10dp</dimen>
+ <dimen name="popup_arrow_height">8dp</dimen>
+ <dimen name="popup_arrow_vertical_offset">-2dp</dimen>
+ <!-- popup_padding_start + deep_shortcut_icon_size / 2 -->
+ <dimen name="popup_arrow_horizontal_center_start">28dp</dimen>
+ <!-- popup_padding_end + deep_shortcut_drag_handle_size / 2 -->
+ <dimen name="popup_arrow_horizontal_center_end">24dp</dimen>
+ <!-- popup_arrow_center_start - popup_arrow_width / 2-->
+ <dimen name="popup_arrow_horizontal_offset_start">23dp</dimen>
+ <!-- popup_arrow_center_end - popup_arrow_width / 2-->
+ <dimen name="popup_arrow_horizontal_offset_end">19dp</dimen>
+ <dimen name="popup_arrow_corner_radius">2dp</dimen>
+ <!-- popup_padding_start + icon_size + 10dp -->
+ <dimen name="deep_shortcuts_text_padding_start">56dp</dimen>
+ <!-- popup_item_width - deep_shortcuts_text_padding_start -->
+ <dimen name="deep_shortcuts_divider_width">164dp</dimen>
+ <dimen name="system_shortcut_icon_size">24dp</dimen>
+ <!-- popup_arrow_center_start - system_shortcut_icon_size / 2 -->
+ <dimen name="system_shortcut_margin_start">16dp</dimen>
+ <dimen name="system_shortcut_header_height">40dp</dimen>
+ <dimen name="system_shortcut_header_icon_touch_size">48dp</dimen>
+ <!-- (touch_size - icon_size) / 2 -->
+ <dimen name="system_shortcut_header_icon_padding">12dp</dimen>
+
+<!-- Icon badges (with notification counts) -->
+ <dimen name="badge_small_padding">0dp</dimen>
+ <dimen name="badge_large_padding">3dp</dimen>
+
+<!-- Notifications -->
+ <dimen name="bg_round_rect_radius">12dp</dimen>
+ <dimen name="notification_padding_start">16dp</dimen>
+ <dimen name="notification_padding_end">12dp</dimen>
+ <!-- notification_padding_end + (icon_size - footer_icon_size) / 2 -->
+ <dimen name="notification_footer_icon_row_padding">15dp</dimen>
+ <dimen name="notification_header_height">32dp</dimen>
+ <dimen name="notification_main_height">80dp</dimen>
+ <dimen name="notification_footer_height">32dp</dimen>
+ <dimen name="notification_header_text_size">13sp</dimen>
+ <dimen name="notification_header_count_text_size">12sp</dimen>
+ <dimen name="notification_main_text_size">14sp</dimen>
+ <dimen name="notification_icon_size">24dp</dimen>
+ <dimen name="notification_footer_icon_size">18dp</dimen>
+ <!-- notification_icon_size + notification_padding_end + 16dp padding between icon and text -->
+ <dimen name="notification_main_text_padding_end">52dp</dimen>
+ <dimen name="notification_elevation">2dp</dimen>
+ <dimen name="horizontal_ellipsis_size">18dp</dimen>
+ <!-- arrow_horizontal_offset_start - (ellipsis_size - arrow_width) / 2 -->
+ <dimen name="horizontal_ellipsis_offset">19dp</dimen>
+ <dimen name="popup_item_divider_height">0.5dp</dimen>
+ <dimen name="swipe_helper_falsing_threshold">70dp</dimen>
<!-- Other -->
<!-- Approximates the system status bar height. Not guaranteed to be always be correct. -->
<dimen name="status_bar_height">24dp</dimen>
-
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 60a37e5..4bee87d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -37,6 +37,10 @@
<string name="safemode_widget_error">Widgets disabled in Safe mode</string>
<!-- Message shown when a shortcut is not available. It could have been temporarily disabled and may start working again after some time. -->
<string name="shortcut_not_available">Shortcut isn\'t available</string>
+ <!-- User visible name for the launcher/home screen. [CHAR_LIMIT=30] -->
+ <string name="home_screen">Home screen</string>
+ <!-- Label for showing custom action list of a shortcut or widget. [CHAR_LIMIT=30] -->
+ <string name="custom_actions">Custom actions</string>
<!-- Widgets -->
<!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
@@ -48,6 +52,10 @@
<string name="widget_dims_format">%1$d \u00d7 %2$d</string>
<!-- Accessibility spoken message format for the dimensions of a widget in the drawer -->
<string name="widget_accessible_dims_format">%1$d wide by %2$d high</string>
+ <!-- Message to tell the user to press and hold a widget/icon to add it -->
+ <string name="add_item_request_drag_hint">Touch & hold to place manually</string>
+ <!-- Button label to automatically add icon on home screen [CHAR_LIMIT=50] -->
+ <string name="place_automatically">Add automatically</string>
<!-- All Apps -->
<!-- Search bar text in the apps view. [CHAR_LIMIT=50] -->
@@ -59,6 +67,10 @@
<!-- Label for the button which allows the user to get app search results. [CHAR_LIMIT=50] -->
<string name="all_apps_search_market_message">Search for more apps</string>
+ <!-- Popup items -->
+ <!-- Text to display as the header above notifications. [CHAR_LIMIT=30] -->
+ <string name="notifications_header">Notifications</string>
+
<!-- Drag and drop -->
<skip />
<!-- Error message when user has filled a home screen -->
@@ -161,6 +173,18 @@
<!-- Text explaining that rotation is disabled in Display settings. 'Display' refers to the Display section in system settings [CHAR LIMIT=100] -->
<string name="allow_rotation_blocked_desc">Current Display setting doesn\'t permit rotation</string>
+ <!-- Label for the setting that allows the automatic placement of launcher shortcuts for applications and games installed on the device [CHAR LIMIT=40] -->
+ <string name="auto_add_shortcuts_label">Add icon to Home screen</string>
+ <!-- Text description of the setting that allows the automatic placement of launcher shortcuts for applications and games installed on the device [CHAR LIMIT=NONE] -->
+ <string name="auto_add_shortcuts_description">For new apps</string>
+
+ <!-- Developer setting to change the shape of icons on home screen. [CHAR LIMIT=50] -->
+ <string name="icon_shape_override_label">Change icon shape</string>
+ <!-- Option to not change the icon shape on home screen. [CHAR LIMIT=50] -->
+ <string name="icon_shape_no_override">Do not change</string>
+ <!-- Message shown in the progress dialog when the icon shape override is being applied [CHAR LIMIT=100]-->
+ <string name="icon_shape_override_progress">Applying icon shape changes</string>
+
<!-- Label on an icon that references an uninstalled package, for which we have no information about when it might be installed. [CHAR_LIMIT=15] -->
<string name="package_state_unknown">Unknown</string>
@@ -180,6 +204,10 @@
<!-- Title for an app whose download has been started. -->
<string name="app_waiting_download_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> waiting to install</string>
+ <!-- Strings for widgets & more in the popup container/bottom sheet -->
+ <!-- Title for a bottom sheet that shows widgets for a particular app -->
+ <string name="widgets_bottom_sheet_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> widgets</string>
+
<!-- Strings for accessibility actions -->
<!-- Accessibility action to add an app to workspace. [CHAR_LIMIT=30] -->
<string name="action_add_to_workspace">Add to Home screen</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index cd06b75..8a46e83 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -24,78 +24,61 @@
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowShowWallpaper">true</item>
<item name="android:windowNoTitle">true</item>
+ <item name="android:colorEdgeEffect">#FF757575</item>
</style>
<style name="LauncherTheme" parent="@style/BaseLauncherTheme"></style>
- <style name="Theme" parent="@style/LauncherTheme"></style>
-
- <!-- Theme for the widget container. Overridden on API 25. -->
+ <!-- Theme for the widget container. Overridden on API 26. -->
<style name="WidgetContainerTheme" parent="@android:style/Theme.DeviceDefault.Settings">
- <item name="colorSecondary">@color/fallback_secondary_color</item>
+ <item name="android:colorEdgeEffect">?android:attr/textColorSecondaryInverse</item>
+ <item name="android:textColorPrimary">?android:attr/textColorPrimaryInverse</item>
+ <item name="android:textColorSecondary">?android:attr/textColorSecondaryInverse</item>
</style>
- <!-- Overscroll effect -->
- <style name="CustomOverscroll" />
-
- <style name="CustomOverscroll.Light" parent="@android:style/Theme.DeviceDefault.Light">
- <item name="android:colorEdgeEffect">@color/folder_edge_effect_color</item>
+ <style name="FastScrollerPopup" >
+ <item name="android:background">@drawable/container_fastscroll_popup_bg</item>
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:minWidth">@dimen/container_fastscroll_popup_size</item>
+ <item name="android:layout_height">@dimen/container_fastscroll_popup_size</item>
+ <item name="android:textSize">@dimen/container_fastscroll_popup_text_size</item>
+ <item name="android:gravity">center</item>
+ <item name="android:alpha">0</item>
+ <item name="android:elevation">3dp</item>
+ <item name="android:saveEnabled">false</item>
+ <item name="android:textColor">@android:color/white</item>
+ <item name="android:includeFontPadding">false</item>
</style>
- <style name="CustomOverscroll.Dark">
- <item name="android:colorEdgeEffect">@color/workspace_edge_effect_color</item>
+ <!-- Style for nav bar background in all-apps screen -->
+ <style name="AllAppsNavBarProtection">
+ <item name="android:alpha">?android:attr/spotShadowAlpha</item>
+ <item name="android:background">@color/default_shadow_color_no_alpha</item>
</style>
- <!-- Different icons -->
- <style name="Icon">
+ <!-- Base theme for BubbleTextView and sub classes -->
+ <style name="BaseIcon">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">match_parent</item>
<item name="android:layout_gravity">center</item>
+ <item name="android:focusable">true</item>
<item name="android:gravity">center_horizontal</item>
<item name="android:singleLine">true</item>
<item name="android:ellipsize">marquee</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
+ <item name="android:fontFamily">sans-serif-condensed</item>
+
+ <!-- No shadows in the base theme -->
+ <item name="android:shadowRadius">0</item>
+ <item name="customShadows">false</item>
+ </style>
+
+ <!-- Icon displayed on the worksapce -->
+ <style name="BaseIcon.Workspace">
+ <item name="customShadows">true</item>
<item name="android:textColor">@color/workspace_icon_text_color</item>
<item name="android:shadowRadius">2.0</item>
<item name="android:shadowColor">#B0000000</item>
- <item name="android:fontFamily">sans-serif-condensed</item>
- </style>
-
- <style name="Icon.AllApps">
- <item name="android:background">@null</item>
- <item name="android:textColor">@color/quantum_panel_text_color</item>
- <item name="android:drawablePadding">@dimen/dynamic_grid_icon_drawable_padding</item>
- <item name="android:shadowRadius">0</item>
- <item name="android:paddingLeft">4dp</item>
- <item name="android:paddingRight">4dp</item>
- <item name="customShadows">false</item>
- </style>
-
- <style name="Icon.Folder">
- <item name="android:background">@null</item>
- <item name="android:textColor">@color/quantum_panel_text_color</item>
- <item name="android:shadowRadius">0</item>
- <item name="android:textSize">@dimen/folder_child_text_size</item>
- <item name="android:gravity">center_horizontal</item>
- <item name="android:includeFontPadding">false</item>
- <item name="customShadows">false</item>
- <item name="iconDisplay">folder</item>
- </style>
-
- <style name="Icon.DeepShortcut">
- <item name="android:gravity">start|center_vertical</item>
- <item name="android:textAlignment">viewStart</item>
- <item name="android:elevation">@dimen/deep_shortcuts_elevation</item>
- <item name="android:paddingStart">@dimen/bg_pill_height</item>
- <item name="android:paddingEnd">@dimen/deep_shortcut_padding_end</item>
- <item name="android:drawableEnd">@drawable/deep_shortcuts_drag_handle</item>
- <item name="android:drawablePadding">@dimen/deep_shortcut_drawable_padding</item>
- <item name="android:textColor">#FF212121</item>
- <item name="android:textSize">14sp</item>
- <item name="android:fontFamily">sans-serif</item>
- <item name="android:shadowRadius">0</item>
- <item name="customShadows">false</item>
- <item name="layoutHorizontal">true</item>
- <item name="iconSizeOverride">@dimen/deep_shortcut_icon_size</item>
</style>
<!-- Drop targets -->
@@ -103,28 +86,15 @@
<item name="android:drawablePadding">7.5dp</item>
<item name="android:paddingLeft">16dp</item>
<item name="android:paddingRight">16dp</item>
- <item name="android:textColor">#FFFFFFFF</item>
+ <item name="android:textColor">@color/workspace_icon_text_color</item>
<item name="android:textSize">@dimen/drop_target_text_size</item>
<item name="android:singleLine">true</item>
<item name="android:ellipsize">end</item>
- <item name="android:shadowColor">#FF000000</item>
+ <item name="android:shadowColor">@color/default_shadow_color_no_alpha</item>
<item name="android:shadowDx">0.0</item>
<item name="android:shadowDy">1.0</item>
<item name="android:shadowRadius">4.0</item>
</style>
<style name="DropTargetButton" parent="DropTargetButtonBase" />
-
- <!-- Virtual preloaders -->
- <style name="PreloadIcon">
- <item name="background">@drawable/virtual_preload</item>
- <item name="indicatorSize">4dp</item>
- <item name="ringOutset">4dp</item>
- </style>
-
- <style name="PreloadIcon.Folder">
- <item name="background">@drawable/virtual_preload_folder</item>
- <item name="indicatorSize">4dp</item>
- <item name="ringOutset">4dp</item>
- </style>
</resources>
diff --git a/res/xml/app_target_browser.xml b/res/xml/app_target_browser.xml
deleted file mode 100644
index d7c3ed5..0000000
--- a/res/xml/app_target_browser.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-
-<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
-
- <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
- <favorite launcher:uri="http://www.example.com/" />
-
-</resolve>
\ No newline at end of file
diff --git a/res/xml/app_target_camera.xml b/res/xml/app_target_camera.xml
deleted file mode 100644
index f65a2b1..0000000
--- a/res/xml/app_target_camera.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-
-<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
-
- <favorite launcher:uri="#Intent;action=android.media.action.STILL_IMAGE_CAMERA;end" />
- <favorite launcher:uri="#Intent;action=android.intent.action.CAMERA_BUTTON;end" />
-
-</resolve>
\ No newline at end of file
diff --git a/res/xml/app_target_email.xml b/res/xml/app_target_email.xml
deleted file mode 100644
index 44f0a40..0000000
--- a/res/xml/app_target_email.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-
-<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
-
- <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
- <favorite launcher:uri="mailto:" />
-
-</resolve>
\ No newline at end of file
diff --git a/res/xml/app_target_gallery.xml b/res/xml/app_target_gallery.xml
deleted file mode 100644
index c9d3492..0000000
--- a/res/xml/app_target_gallery.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-
-<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
-
- <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
- <favorite launcher:uri="#Intent;type=images/*;end" />
-
-</resolve>
\ No newline at end of file
diff --git a/res/xml/app_target_messenger.xml b/res/xml/app_target_messenger.xml
deleted file mode 100644
index 278eb5c..0000000
--- a/res/xml/app_target_messenger.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-
-<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
-
- <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
- <favorite launcher:uri="sms:" />
- <favorite launcher:uri="smsto:" />
- <favorite launcher:uri="mms:" />
- <favorite launcher:uri="mmsto:" />
-
-</resolve>
\ No newline at end of file
diff --git a/res/xml/app_target_phone.xml b/res/xml/app_target_phone.xml
deleted file mode 100644
index 5d6ca31..0000000
--- a/res/xml/app_target_phone.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-
-<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
-
- <favorite launcher:uri="#Intent;action=android.intent.action.DIAL;end" />
- <favorite launcher:uri="tel:123" />
- <favorite launcher:uri="#Intent;action=android.intent.action.CALL_BUTTON;end" />
-
-</resolve>
\ No newline at end of file
diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml
index 624d9eb..301bef1 100644
--- a/res/xml/launcher_preferences.xml
+++ b/res/xml/launcher_preferences.xml
@@ -23,4 +23,21 @@
android:persistent="true"
/>
+ <SwitchPreference
+ android:key="pref_add_icon_to_home"
+ android:title="@string/auto_add_shortcuts_label"
+ android:summary="@string/auto_add_shortcuts_description"
+ android:defaultValue="true"
+ android:persistent="true"
+ />
+
+ <ListPreference
+ android:key="pref_override_icon_shape"
+ android:title="@string/icon_shape_override_label"
+ android:summary="%s"
+ android:entries="@array/icon_shape_override_paths_names"
+ android:entryValues="@array/icon_shape_override_paths_values"
+ android:defaultValue=""
+ android:persistent="false" />
+
</PreferenceScreen>
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
new file mode 100644
index 0000000..597e937
--- /dev/null
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -0,0 +1,146 @@
+/*
+ * 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.annotation.SuppressLint;
+import android.content.Context;
+import android.support.annotation.IntDef;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.launcher3.dragndrop.DragLayer;
+
+import java.lang.annotation.Retention;
+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 {
+
+ @IntDef(flag = true, value = {
+ TYPE_FOLDER,
+ TYPE_POPUP_CONTAINER_WITH_ARROW,
+ TYPE_WIDGETS_BOTTOM_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_WIDGETS_BOTTOM_SHEET = 1 << 2;
+
+ protected boolean mIsOpen;
+
+ public AbstractFloatingView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AbstractFloatingView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ /**
+ * We need to handle touch events to prevent them from falling through to the workspace below.
+ */
+ @SuppressLint("ClickableViewAccessibility")
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ return true;
+ }
+
+ public final void close(boolean animate) {
+ animate &= !Utilities.isPowerSaverOn(getContext());
+ handleClose(animate);
+ Launcher.getLauncher(getContext()).getUserEventDispatcher().resetElapsedContainerMillis();
+ }
+
+ 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 final boolean isOpen() {
+ return mIsOpen;
+ }
+
+ protected void onWidgetsBound() {
+ }
+
+ protected abstract boolean isOfType(@FloatingViewType int type);
+
+ protected static <T extends AbstractFloatingView> T getOpenView(
+ Launcher launcher, @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 view = (AbstractFloatingView) child;
+ if (view.isOfType(type) && view.isOpen()) {
+ return (T) view;
+ }
+ }
+ }
+ return null;
+ }
+
+ public static void closeOpenContainer(Launcher launcher, @FloatingViewType int type) {
+ AbstractFloatingView view = getOpenView(launcher, type);
+ if (view != null) {
+ view.close(true);
+ }
+ }
+
+ public static void closeAllOpenViews(Launcher launcher, boolean animate) {
+ 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);
+ }
+ }
+ }
+
+ 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);
+ }
+
+ public abstract int getLogContainerType();
+}
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index c431593..5b42cad 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -18,12 +18,12 @@
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.LauncherActivityInfo;
+import android.os.UserHandle;
-import com.android.launcher3.compat.LauncherActivityInfoCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.util.FlagOp;
-import com.android.launcher3.util.StringFilter;
+import com.android.launcher3.util.ItemInfoMatcher;
import java.util.ArrayList;
import java.util.HashSet;
@@ -33,7 +33,7 @@
/**
* Stores the list of all applications for the all apps view.
*/
-class AllAppsList {
+public class AllAppsList {
public static final int DEFAULT_APPLICATIONS_NUMBER = 42;
/** The list off all apps. */
@@ -65,13 +65,15 @@
*
* If the app is already in the list, doesn't add it.
*/
- public void add(AppInfo info) {
- if (mAppFilter != null && !mAppFilter.shouldShowApp(info.componentName)) {
+ public void add(AppInfo info, LauncherActivityInfo activityInfo) {
+ if (!mAppFilter.shouldShowApp(info.componentName)) {
return;
}
if (findActivity(data, info.componentName, info.user)) {
return;
}
+ mIconCache.getTitleAndIcon(info, activityInfo, true /* useLowResIcon */);
+
data.add(info);
added.add(info);
}
@@ -95,25 +97,24 @@
/**
* Add the icons for the supplied apk called packageName.
*/
- public void addPackage(Context context, String packageName, UserHandleCompat user) {
+ public void addPackage(Context context, String packageName, UserHandle user) {
final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
- final List<LauncherActivityInfoCompat> matches = launcherApps.getActivityList(packageName,
+ final List<LauncherActivityInfo> matches = launcherApps.getActivityList(packageName,
user);
- for (LauncherActivityInfoCompat info : matches) {
- add(new AppInfo(context, info, user, mIconCache));
+ for (LauncherActivityInfo info : matches) {
+ add(new AppInfo(context, info, user), info);
}
}
/**
* Remove the apps for the given apk identified by packageName.
*/
- public void removePackage(String packageName, UserHandleCompat user) {
+ public void removePackage(String packageName, UserHandle user) {
final List<AppInfo> data = this.data;
for (int i = data.size() - 1; i >= 0; i--) {
AppInfo info = data.get(i);
- final ComponentName component = info.intent.getComponent();
- if (info.user.equals(user) && packageName.equals(component.getPackageName())) {
+ if (info.user.equals(user) && packageName.equals(info.componentName.getPackageName())) {
removed.add(info);
data.remove(i);
}
@@ -121,21 +122,20 @@
}
/**
- * Updates the apps for the given packageName and user based on {@param op}.
+ * Updates the disabled flags of apps matching {@param matcher} based on {@param op}.
*/
- public void updatePackageFlags(StringFilter pkgFilter, UserHandleCompat user, FlagOp op) {
+ public void updateDisabledFlags(ItemInfoMatcher matcher, FlagOp op) {
final List<AppInfo> data = this.data;
for (int i = data.size() - 1; i >= 0; i--) {
AppInfo info = data.get(i);
- final ComponentName component = info.intent.getComponent();
- if (info.user.equals(user) && pkgFilter.matches(component.getPackageName())) {
+ if (matcher.matches(info, info.componentName)) {
info.isDisabled = op.apply(info.isDisabled);
modified.add(info);
}
}
}
- public void updateIconsAndLabels(HashSet<String> packages, UserHandleCompat user,
+ public void updateIconsAndLabels(HashSet<String> packages, UserHandle user,
ArrayList<AppInfo> outUpdates) {
for (AppInfo info : data) {
if (info.user.equals(user) && packages.contains(info.componentName.getPackageName())) {
@@ -148,19 +148,18 @@
/**
* Add and remove icons for this package which has been updated.
*/
- public void updatePackage(Context context, String packageName, UserHandleCompat user) {
+ public void updatePackage(Context context, String packageName, UserHandle user) {
final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
- final List<LauncherActivityInfoCompat> matches = launcherApps.getActivityList(packageName,
+ final List<LauncherActivityInfo> matches = launcherApps.getActivityList(packageName,
user);
if (matches.size() > 0) {
// Find disabled/removed activities and remove them from data and add them
// to the removed list.
for (int i = data.size() - 1; i >= 0; i--) {
final AppInfo applicationInfo = data.get(i);
- final ComponentName component = applicationInfo.intent.getComponent();
if (user.equals(applicationInfo.user)
- && packageName.equals(component.getPackageName())) {
- if (!findActivity(matches, component)) {
+ && packageName.equals(applicationInfo.componentName.getPackageName())) {
+ if (!findActivity(matches, applicationInfo.componentName)) {
removed.add(applicationInfo);
data.remove(i);
}
@@ -169,12 +168,12 @@
// Find enabled activities and add them to the adapter
// Also updates existing activities with new labels/icons
- for (final LauncherActivityInfoCompat info : matches) {
+ for (final LauncherActivityInfo info : matches) {
AppInfo applicationInfo = findApplicationInfoLocked(
info.getComponentName().getPackageName(), user,
info.getComponentName().getClassName());
if (applicationInfo == null) {
- add(new AppInfo(context, info, user, mIconCache));
+ add(new AppInfo(context, info, user), info);
} else {
mIconCache.getTitleAndIcon(applicationInfo, info, true /* useLowResIcon */);
modified.add(applicationInfo);
@@ -184,11 +183,10 @@
// Remove all data for this package.
for (int i = data.size() - 1; i >= 0; i--) {
final AppInfo applicationInfo = data.get(i);
- final ComponentName component = applicationInfo.intent.getComponent();
if (user.equals(applicationInfo.user)
- && packageName.equals(component.getPackageName())) {
+ && packageName.equals(applicationInfo.componentName.getPackageName())) {
removed.add(applicationInfo);
- mIconCache.remove(component, user);
+ mIconCache.remove(applicationInfo.componentName, user);
data.remove(i);
}
}
@@ -199,9 +197,9 @@
/**
* Returns whether <em>apps</em> contains <em>component</em>.
*/
- private static boolean findActivity(List<LauncherActivityInfoCompat> apps,
+ private static boolean findActivity(List<LauncherActivityInfo> apps,
ComponentName component) {
- for (LauncherActivityInfoCompat info : apps) {
+ for (LauncherActivityInfo info : apps) {
if (info.getComponentName().equals(component)) {
return true;
}
@@ -210,20 +208,10 @@
}
/**
- * Query the launcher apps service for whether the supplied package has
- * MAIN/LAUNCHER activities in the supplied package.
- */
- static boolean packageHasActivities(Context context, String packageName,
- UserHandleCompat user) {
- final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
- return launcherApps.getActivityList(packageName, user).size() > 0;
- }
-
- /**
* Returns whether <em>apps</em> contains <em>component</em>.
*/
private static boolean findActivity(ArrayList<AppInfo> apps, ComponentName component,
- UserHandleCompat user) {
+ UserHandle user) {
final int N = apps.size();
for (int i = 0; i < N; i++) {
final AppInfo info = apps.get(i);
@@ -237,12 +225,11 @@
/**
* Find an ApplicationInfo object for the given packageName and className.
*/
- private AppInfo findApplicationInfoLocked(String packageName, UserHandleCompat user,
+ private AppInfo findApplicationInfoLocked(String packageName, UserHandle user,
String className) {
for (AppInfo info: data) {
- final ComponentName component = info.intent.getComponent();
- if (user.equals(info.user) && packageName.equals(component.getPackageName())
- && className.equals(component.getClassName())) {
+ if (user.equals(info.user) && packageName.equals(info.componentName.getPackageName())
+ && className.equals(info.componentName.getClassName())) {
return info;
}
}
diff --git a/src/com/android/launcher3/AnotherWindowDropTarget.java b/src/com/android/launcher3/AnotherWindowDropTarget.java
deleted file mode 100644
index 0e18874..0000000
--- a/src/com/android/launcher3/AnotherWindowDropTarget.java
+++ /dev/null
@@ -1,64 +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.content.Context;
-import android.graphics.PointF;
-import android.graphics.Rect;
-
-/**
- * Drop target used when another window (i.e. another process) has accepted a global system drag.
- * If the accepted item was a shortcut, we delete it from Launcher.
- */
-public class AnotherWindowDropTarget implements DropTarget {
- final Launcher mLauncher;
-
- public AnotherWindowDropTarget (Context context) { mLauncher = (Launcher) context; }
-
- @Override
- public boolean isDropEnabled() { return true; }
-
- @Override
- public void onDrop(DragObject dragObject) {
- dragObject.deferDragViewCleanupPostAnimation = false;
- LauncherModel.deleteItemFromDatabase(mLauncher, (ShortcutInfo) dragObject.dragInfo);
- }
-
- @Override
- public void onDragEnter(DragObject dragObject) {}
-
- @Override
- public void onDragOver(DragObject dragObject) {}
-
- @Override
- public void onDragExit(DragObject dragObject) {}
-
- @Override
- public void onFlingToDelete(DragObject dragObject, PointF vec) {}
-
- @Override
- public boolean acceptDrop(DragObject dragObject) {
- return dragObject.dragInfo instanceof ShortcutInfo;
- }
-
- @Override
- public void prepareAccessibilityDrop() {}
-
- // These methods are implemented in Views
- @Override
- public void getHitRectRelativeToDragLayer(Rect outRect) {}
-}
diff --git a/src/com/android/launcher3/AppFilter.java b/src/com/android/launcher3/AppFilter.java
index e01436d..db8f5dd 100644
--- a/src/com/android/launcher3/AppFilter.java
+++ b/src/com/android/launcher3/AppFilter.java
@@ -1,35 +1,10 @@
package com.android.launcher3;
import android.content.ComponentName;
-import android.text.TextUtils;
-import android.util.Log;
-public abstract class AppFilter {
+public class AppFilter {
- private static final boolean DBG = false;
- private static final String TAG = "AppFilter";
-
- public abstract boolean shouldShowApp(ComponentName app);
-
- public static AppFilter loadByName(String className) {
- if (TextUtils.isEmpty(className)) return null;
- if (DBG) Log.d(TAG, "Loading AppFilter: " + className);
- try {
- Class<?> cls = Class.forName(className);
- return (AppFilter) cls.newInstance();
- } catch (ClassNotFoundException e) {
- Log.e(TAG, "Bad AppFilter class", e);
- return null;
- } catch (InstantiationException e) {
- Log.e(TAG, "Bad AppFilter class", e);
- return null;
- } catch (IllegalAccessException e) {
- Log.e(TAG, "Bad AppFilter class", e);
- return null;
- } catch (ClassCastException e) {
- Log.e(TAG, "Bad AppFilter class", e);
- return null;
- }
+ public boolean shouldShowApp(ComponentName app) {
+ return true;
}
-
}
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index 4c4d67c..9ec26e2 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -19,51 +19,32 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.graphics.Bitmap;
-import android.util.Log;
+import android.content.pm.LauncherActivityInfo;
+import android.os.UserHandle;
-import com.android.launcher3.compat.LauncherActivityInfoCompat;
-import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageManagerHelper;
-import java.util.ArrayList;
-
/**
* Represents an app in AllAppsView.
*/
-public class AppInfo extends ItemInfo {
+public class AppInfo extends ItemInfoWithIcon {
/**
* The intent used to start the application.
*/
public Intent intent;
- /**
- * A bitmap version of the application icon.
- */
- public Bitmap iconBitmap;
-
- /**
- * Indicates whether we're using a low res icon
- */
- boolean usingLowResIcon;
-
public ComponentName componentName;
- static final int DOWNLOADED_FLAG = 1;
- static final int UPDATED_SYSTEM_APP_FLAG = 2;
-
- int flags = 0;
-
/**
* {@see ShortcutInfo#isDisabled}
*/
- int isDisabled = ShortcutInfo.DEFAULT;
+ public int isDisabled = ShortcutInfo.DEFAULT;
public AppInfo() {
- itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
+ itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
}
@Override
@@ -71,24 +52,17 @@
return intent;
}
- protected Intent getRestoredIntent() {
- return null;
- }
-
/**
* Must not hold the Context.
*/
- public AppInfo(Context context, LauncherActivityInfoCompat info, UserHandleCompat user,
- IconCache iconCache) {
- this(context, info, user, iconCache,
- UserManagerCompat.getInstance(context).isQuietModeEnabled(user));
+ public AppInfo(Context context, LauncherActivityInfo info, UserHandle user) {
+ this(info, user, UserManagerCompat.getInstance(context).isQuietModeEnabled(user));
}
- public AppInfo(Context context, LauncherActivityInfoCompat info, UserHandleCompat user,
- IconCache iconCache, boolean quietModeEnabled) {
+ public AppInfo(LauncherActivityInfo info, UserHandle user, boolean quietModeEnabled) {
this.componentName = info.getComponentName();
this.container = ItemInfo.NO_ID;
- flags = initFlags(info);
+ this.user = user;
if (PackageManagerHelper.isAppSuspended(info.getApplicationInfo())) {
isDisabled |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
}
@@ -96,22 +70,7 @@
isDisabled |= ShortcutInfo.FLAG_DISABLED_QUIET_USER;
}
- iconCache.getTitleAndIcon(this, info, true /* useLowResIcon */);
- intent = makeLaunchIntent(context, info, user);
- this.user = user;
- }
-
- public static int initFlags(LauncherActivityInfoCompat info) {
- int appFlags = info.getApplicationInfo().flags;
- int flags = 0;
- if ((appFlags & android.content.pm.ApplicationInfo.FLAG_SYSTEM) == 0) {
- flags |= DOWNLOADED_FLAG;
-
- if ((appFlags & android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
- flags |= UPDATED_SYSTEM_APP_FLAG;
- }
- }
- return flags;
+ intent = makeLaunchIntent(info);
}
public AppInfo(AppInfo info) {
@@ -119,9 +78,7 @@
componentName = info.componentName;
title = Utilities.trim(info.title);
intent = new Intent(info.intent);
- flags = info.flags;
isDisabled = info.isDisabled;
- iconBitmap = info.iconBitmap;
}
@Override
@@ -129,17 +86,6 @@
return super.dumpProperties() + " componentName=" + componentName;
}
- /**
- * Helper method used for debugging.
- */
- public static void dumpApplicationInfoList(String tag, String label, ArrayList<AppInfo> list) {
- Log.d(tag, label + " size=" + list.size());
- for (AppInfo info: list) {
- Log.d(tag, " title=\"" + info.title + "\" iconBitmap=" + info.iconBitmap
- + " componentName=" + info.componentName.getPackageName());
- }
- }
-
public ShortcutInfo makeShortcut() {
return new ShortcutInfo(this);
}
@@ -148,14 +94,11 @@
return new ComponentKey(componentName, user);
}
- public static Intent makeLaunchIntent(Context context, LauncherActivityInfoCompat info,
- UserHandleCompat user) {
- long serialNumber = UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
+ public static Intent makeLaunchIntent(LauncherActivityInfo info) {
return new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_LAUNCHER)
.setComponent(info.getComponentName())
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
- .putExtra(EXTRA_PROFILE, serialNumber);
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
}
@Override
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index cd27b4c..1a405f9 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -1,7 +1,5 @@
package com.android.launcher3;
-import com.android.launcher3.dragndrop.DragLayer;
-
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
@@ -13,16 +11,19 @@
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
-import android.view.Gravity;
+import android.util.AttributeSet;
import android.view.KeyEvent;
+import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
-import android.widget.ImageView;
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 {
+public class AppWidgetResizeFrame extends FrameLayout
+ implements View.OnKeyListener, TouchController {
private static final int SNAP_DURATION = 150;
private static final float DIMMED_HANDLE_ALPHA = 0f;
private static final float RESIZE_THRESHOLD = 0.66f;
@@ -32,17 +33,22 @@
// Represents the cell size on the grid in the two orientations.
private static Point[] sCellSize;
+ private static final int HANDLE_COUNT = 4;
+ private static final int INDEX_LEFT = 0;
+ private static final int INDEX_TOP = 1;
+ private static final int INDEX_RIGHT = 2;
+ private static final int INDEX_BOTTOM = 3;
+
private final Launcher mLauncher;
- private final LauncherAppWidgetHostView mWidgetView;
- private final CellLayout mCellLayout;
- private final DragLayer mDragLayer;
+ private final DragViewStateAnnouncer mStateAnnouncer;
- private final ImageView mLeftHandle;
- private final ImageView mRightHandle;
- private final ImageView mTopHandle;
- private final ImageView mBottomHandle;
+ private final View[] mDragHandles = new View[HANDLE_COUNT];
- private final Rect mWidgetPadding;
+ private LauncherAppWidgetHostView mWidgetView;
+ private CellLayout mCellLayout;
+ private DragLayer mDragLayer;
+
+ private Rect mWidgetPadding;
private final int mBackgroundPadding;
private final int mTouchTargetWidth;
@@ -51,17 +57,20 @@
private final int[] mLastDirectionVector = new int[2];
private final int[] mTmpPt = new int[2];
- private final DragViewStateAnnouncer mStateAnnouncer;
+ private final IntRange mTempRange1 = new IntRange();
+ private final IntRange mTempRange2 = new IntRange();
+
+ private final IntRange mDeltaXRange = new IntRange();
+ private final IntRange mBaselineX = new IntRange();
+
+ private final IntRange mDeltaYRange = new IntRange();
+ private final IntRange mBaselineY = new IntRange();
private boolean mLeftBorderActive;
private boolean mRightBorderActive;
private boolean mTopBorderActive;
private boolean mBottomBorderActive;
- private int mBaselineWidth;
- private int mBaselineHeight;
- private int mBaselineX;
- private int mBaselineY;
private int mResizeMode;
private int mRunningHInc;
@@ -76,11 +85,38 @@
private int mTopTouchRegionAdjustment = 0;
private int mBottomTouchRegionAdjustment = 0;
- public AppWidgetResizeFrame(Context context,
- LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) {
+ private int mXDown, mYDown;
- super(context);
+ public AppWidgetResizeFrame(Context context) {
+ this(context, null);
+ }
+
+ public AppWidgetResizeFrame(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AppWidgetResizeFrame(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
mLauncher = Launcher.getLauncher(context);
+ mStateAnnouncer = DragViewStateAnnouncer.createFor(this);
+
+ mBackgroundPadding = getResources()
+ .getDimensionPixelSize(R.dimen.resize_frame_background_padding);
+ mTouchTargetWidth = 2 * mBackgroundPadding;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ for (int i = 0; i < HANDLE_COUNT; i ++) {
+ mDragHandles[i] = getChildAt(i);
+ }
+ }
+
+ public void setupForWidget(LauncherAppWidgetHostView widgetView, CellLayout cellLayout,
+ DragLayer dragLayer) {
mCellLayout = cellLayout;
mWidgetView = widgetView;
LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo)
@@ -91,63 +127,23 @@
mMinHSpan = info.minSpanX;
mMinVSpan = info.minSpanY;
- mStateAnnouncer = DragViewStateAnnouncer.createFor(this);
-
- setBackgroundResource(R.drawable.widget_resize_shadow);
- setForeground(getResources().getDrawable(R.drawable.widget_resize_frame));
- setPadding(0, 0, 0, 0);
-
- final int handleMargin = getResources().getDimensionPixelSize(R.dimen.widget_handle_margin);
- LayoutParams lp;
- mLeftHandle = new ImageView(context);
- mLeftHandle.setImageResource(R.drawable.ic_widget_resize_handle);
- lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
- Gravity.LEFT | Gravity.CENTER_VERTICAL);
- lp.leftMargin = handleMargin;
- addView(mLeftHandle, lp);
-
- mRightHandle = new ImageView(context);
- mRightHandle.setImageResource(R.drawable.ic_widget_resize_handle);
- lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
- Gravity.RIGHT | Gravity.CENTER_VERTICAL);
- lp.rightMargin = handleMargin;
- addView(mRightHandle, lp);
-
- mTopHandle = new ImageView(context);
- mTopHandle.setImageResource(R.drawable.ic_widget_resize_handle);
- lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
- Gravity.CENTER_HORIZONTAL | Gravity.TOP);
- lp.topMargin = handleMargin;
- addView(mTopHandle, lp);
-
- mBottomHandle = new ImageView(context);
- mBottomHandle.setImageResource(R.drawable.ic_widget_resize_handle);
- lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
- Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
- lp.bottomMargin = handleMargin;
- addView(mBottomHandle, lp);
-
if (!info.isCustomWidget) {
- mWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context,
+ mWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(getContext(),
widgetView.getAppWidgetInfo().provider, null);
} else {
- Resources r = context.getResources();
+ Resources r = getContext().getResources();
int padding = r.getDimensionPixelSize(R.dimen.default_widget_padding);
mWidgetPadding = new Rect(padding, padding, padding, padding);
}
if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
- mTopHandle.setVisibility(GONE);
- mBottomHandle.setVisibility(GONE);
+ mDragHandles[INDEX_TOP].setVisibility(GONE);
+ mDragHandles[INDEX_BOTTOM].setVisibility(GONE);
} else if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
- mLeftHandle.setVisibility(GONE);
- mRightHandle.setVisibility(GONE);
+ mDragHandles[INDEX_LEFT].setVisibility(GONE);
+ mDragHandles[INDEX_RIGHT].setVisibility(GONE);
}
- mBackgroundPadding = getResources()
- .getDimensionPixelSize(R.dimen.resize_frame_background_padding);
- mTouchTargetWidth = 2 * mBackgroundPadding;
-
// When we create the resize frame, we first mark all cells as unoccupied. The appropriate
// cells (same if not resized, or different) will be marked as occupied when the resize
// frame is dismissed.
@@ -169,101 +165,91 @@
boolean anyBordersActive = mLeftBorderActive || mRightBorderActive
|| mTopBorderActive || mBottomBorderActive;
- mBaselineWidth = getMeasuredWidth();
- mBaselineHeight = getMeasuredHeight();
- mBaselineX = getLeft();
- mBaselineY = getTop();
-
if (anyBordersActive) {
- mLeftHandle.setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
- mRightHandle.setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
- mTopHandle.setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
- mBottomHandle.setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
+ mDragHandles[INDEX_LEFT].setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
+ mDragHandles[INDEX_RIGHT].setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
+ mDragHandles[INDEX_TOP].setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
+ mDragHandles[INDEX_BOTTOM].setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
}
+
+ if (mLeftBorderActive) {
+ mDeltaXRange.set(-getLeft(), getWidth() - 2 * mTouchTargetWidth);
+ } else if (mRightBorderActive) {
+ mDeltaXRange.set(2 * mTouchTargetWidth - getWidth(), mDragLayer.getWidth() - getRight());
+ } else {
+ mDeltaXRange.set(0, 0);
+ }
+ mBaselineX.set(getLeft(), getRight());
+
+ if (mTopBorderActive) {
+ mDeltaYRange.set(-getTop(), getHeight() - 2 * mTouchTargetWidth);
+ } else if (mBottomBorderActive) {
+ mDeltaYRange.set(2 * mTouchTargetWidth - getHeight(), mDragLayer.getHeight() - getBottom());
+ } else {
+ mDeltaYRange.set(0, 0);
+ }
+ mBaselineY.set(getTop(), getBottom());
+
return anyBordersActive;
}
/**
- * Here we bound the deltas such that the frame cannot be stretched beyond the extents
- * of the CellLayout, and such that the frame's borders can't cross.
+ * Based on the deltas, we resize the frame.
*/
- public void updateDeltas(int deltaX, int deltaY) {
- if (mLeftBorderActive) {
- mDeltaX = Math.max(-mBaselineX, deltaX);
- mDeltaX = Math.min(mBaselineWidth - 2 * mTouchTargetWidth, mDeltaX);
- } else if (mRightBorderActive) {
- mDeltaX = Math.min(mDragLayer.getWidth() - (mBaselineX + mBaselineWidth), deltaX);
- mDeltaX = Math.max(-mBaselineWidth + 2 * mTouchTargetWidth, mDeltaX);
- }
-
- if (mTopBorderActive) {
- mDeltaY = Math.max(-mBaselineY, deltaY);
- mDeltaY = Math.min(mBaselineHeight - 2 * mTouchTargetWidth, mDeltaY);
- } else if (mBottomBorderActive) {
- mDeltaY = Math.min(mDragLayer.getHeight() - (mBaselineY + mBaselineHeight), deltaY);
- mDeltaY = Math.max(-mBaselineHeight + 2 * mTouchTargetWidth, mDeltaY);
- }
- }
-
public void visualizeResizeForDelta(int deltaX, int deltaY) {
- visualizeResizeForDelta(deltaX, deltaY, false);
+ mDeltaX = mDeltaXRange.clamp(deltaX);
+ mDeltaY = mDeltaYRange.clamp(deltaY);
+
+ DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+ mDeltaX = mDeltaXRange.clamp(deltaX);
+ mBaselineX.applyDelta(mLeftBorderActive, mRightBorderActive, mDeltaX, mTempRange1);
+ lp.x = mTempRange1.start;
+ lp.width = mTempRange1.size();
+
+ mDeltaY = mDeltaYRange.clamp(deltaY);
+ mBaselineY.applyDelta(mTopBorderActive, mBottomBorderActive, mDeltaY, mTempRange1);
+ lp.y = mTempRange1.start;
+ lp.height = mTempRange1.size();
+
+ resizeWidgetIfNeeded(false);
+
+ // When the widget resizes in multi-window mode, the translation value changes to maintain
+ // a center fit. These overrides ensure the resize frame always aligns with the widget view.
+ getSnappedRectRelativeToDragLayer(sTmpRect);
+ if (mLeftBorderActive) {
+ lp.width = sTmpRect.width() + sTmpRect.left - lp.x;
+ }
+ if (mTopBorderActive) {
+ lp.height = sTmpRect.height() + sTmpRect.top - lp.y;
+ }
+ if (mRightBorderActive) {
+ lp.x = sTmpRect.left;
+ }
+ if (mBottomBorderActive) {
+ lp.y = sTmpRect.top;
+ }
+
+ requestLayout();
}
- /**
- * Based on the deltas, we resize the frame, and, if needed, we resize the widget.
- */
- private void visualizeResizeForDelta(int deltaX, int deltaY, boolean onDismiss) {
- updateDeltas(deltaX, deltaY);
- DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
-
- if (mLeftBorderActive) {
- lp.x = mBaselineX + mDeltaX;
- lp.width = mBaselineWidth - mDeltaX;
- } else if (mRightBorderActive) {
- lp.width = mBaselineWidth + mDeltaX;
- }
-
- if (mTopBorderActive) {
- lp.y = mBaselineY + mDeltaY;
- lp.height = mBaselineHeight - mDeltaY;
- } else if (mBottomBorderActive) {
- lp.height = mBaselineHeight + mDeltaY;
- }
-
- resizeWidgetIfNeeded(onDismiss);
- requestLayout();
+ private static int getSpanIncrement(float deltaFrac) {
+ return Math.abs(deltaFrac) > RESIZE_THRESHOLD ? Math.round(deltaFrac) : 0;
}
/**
* Based on the current deltas, we determine if and how to resize the widget.
*/
private void resizeWidgetIfNeeded(boolean onDismiss) {
- int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
- int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
+ float xThreshold = mCellLayout.getCellWidth();
+ float yThreshold = mCellLayout.getCellHeight();
- int deltaX = mDeltaX + mDeltaXAddOn;
- int deltaY = mDeltaY + mDeltaYAddOn;
-
- float hSpanIncF = 1.0f * deltaX / xThreshold - mRunningHInc;
- float vSpanIncF = 1.0f * deltaY / yThreshold - mRunningVInc;
-
- int hSpanInc = 0;
- int vSpanInc = 0;
- int cellXInc = 0;
- int cellYInc = 0;
-
- int countX = mCellLayout.getCountX();
- int countY = mCellLayout.getCountY();
-
- if (Math.abs(hSpanIncF) > RESIZE_THRESHOLD) {
- hSpanInc = Math.round(hSpanIncF);
- }
- if (Math.abs(vSpanIncF) > RESIZE_THRESHOLD) {
- vSpanInc = Math.round(vSpanIncF);
- }
+ int hSpanInc = getSpanIncrement((mDeltaX + mDeltaXAddOn) / xThreshold - mRunningHInc);
+ int vSpanInc = getSpanIncrement((mDeltaY + mDeltaYAddOn) / yThreshold - mRunningVInc);
if (!onDismiss && (hSpanInc == 0 && vSpanInc == 0)) return;
+ mDirectionVector[0] = 0;
+ mDirectionVector[1] = 0;
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
@@ -272,55 +258,24 @@
int cellX = lp.useTmpCoords ? lp.tmpCellX : lp.cellX;
int cellY = lp.useTmpCoords ? lp.tmpCellY : lp.cellY;
- int hSpanDelta = 0;
- int vSpanDelta = 0;
-
// For each border, we bound the resizing based on the minimum width, and the maximum
// expandability.
- if (mLeftBorderActive) {
- cellXInc = Math.max(-cellX, hSpanInc);
- cellXInc = Math.min(lp.cellHSpan - mMinHSpan, cellXInc);
- hSpanInc *= -1;
- hSpanInc = Math.min(cellX, hSpanInc);
- hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
- hSpanDelta = -hSpanInc;
-
- } else if (mRightBorderActive) {
- hSpanInc = Math.min(countX - (cellX + spanX), hSpanInc);
- hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
- hSpanDelta = hSpanInc;
+ mTempRange1.set(cellX, spanX + cellX);
+ int hSpanDelta = mTempRange1.applyDeltaAndBound(mLeftBorderActive, mRightBorderActive,
+ hSpanInc, mMinHSpan, mCellLayout.getCountX(), mTempRange2);
+ cellX = mTempRange2.start;
+ spanX = mTempRange2.size();
+ if (hSpanDelta != 0) {
+ mDirectionVector[0] = mLeftBorderActive ? -1 : 1;
}
- if (mTopBorderActive) {
- cellYInc = Math.max(-cellY, vSpanInc);
- cellYInc = Math.min(lp.cellVSpan - mMinVSpan, cellYInc);
- vSpanInc *= -1;
- vSpanInc = Math.min(cellY, vSpanInc);
- vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
- vSpanDelta = -vSpanInc;
- } else if (mBottomBorderActive) {
- vSpanInc = Math.min(countY - (cellY + spanY), vSpanInc);
- vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
- vSpanDelta = vSpanInc;
- }
-
- mDirectionVector[0] = 0;
- mDirectionVector[1] = 0;
- // Update the widget's dimensions and position according to the deltas computed above
- if (mLeftBorderActive || mRightBorderActive) {
- spanX += hSpanInc;
- cellX += cellXInc;
- if (hSpanDelta != 0) {
- mDirectionVector[0] = mLeftBorderActive ? -1 : 1;
- }
- }
-
- if (mTopBorderActive || mBottomBorderActive) {
- spanY += vSpanInc;
- cellY += cellYInc;
- if (vSpanDelta != 0) {
- mDirectionVector[1] = mTopBorderActive ? -1 : 1;
- }
+ mTempRange1.set(cellY, spanY + cellY);
+ int vSpanDelta = mTempRange1.applyDeltaAndBound(mTopBorderActive, mBottomBorderActive,
+ vSpanInc, mMinVSpan, mCellLayout.getCountY(), mTempRange2);
+ cellY = mTempRange2.start;
+ spanY = mTempRange2.size();
+ if (vSpanDelta != 0) {
+ mDirectionVector[1] = mTopBorderActive ? -1 : 1;
}
if (!onDismiss && vSpanDelta == 0 && hSpanDelta == 0) return;
@@ -365,7 +320,7 @@
public static Rect getWidgetSizeRanges(Context context, int spanX, int spanY, Rect rect) {
if (sCellSize == null) {
- InvariantDeviceProfile inv = LauncherAppState.getInstance().getInvariantDeviceProfile();
+ InvariantDeviceProfile inv = LauncherAppState.getIDP(context);
// Initiate cell sizes.
sCellSize = new Point[2];
@@ -398,12 +353,12 @@
requestLayout();
}
- public void onTouchUp() {
- int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
- int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
+ private void onTouchUp() {
+ int xThreshold = mCellLayout.getCellWidth();
+ int yThreshold = mCellLayout.getCellHeight();
- mDeltaXAddOn = mRunningHInc * xThreshold;
- mDeltaYAddOn = mRunningVInc * yThreshold;
+ mDeltaXAddOn = mRunningHInc * xThreshold;
+ mDeltaYAddOn = mRunningVInc * yThreshold;
mDeltaX = 0;
mDeltaY = 0;
@@ -415,19 +370,35 @@
});
}
+ /**
+ * Returns the rect of this view when the frame is snapped around the widget, with the bounds
+ * relative to the {@link DragLayer}.
+ */
+ private void getSnappedRectRelativeToDragLayer(Rect out) {
+ float scale = mWidgetView.getScaleToFit();
+
+ mDragLayer.getViewRectRelativeToSelf(mWidgetView, out);
+
+ int width = 2 * mBackgroundPadding
+ + (int) (scale * (out.width() - mWidgetPadding.left - mWidgetPadding.right));
+ int height = 2 * mBackgroundPadding
+ + (int) (scale * (out.height() - mWidgetPadding.top - mWidgetPadding.bottom));
+
+ int x = (int) (out.left - mBackgroundPadding + scale * mWidgetPadding.left);
+ int y = (int) (out.top - mBackgroundPadding + scale * mWidgetPadding.top);
+
+ out.left = x;
+ out.top = y;
+ out.right = out.left + width;
+ out.bottom = out.top + height;
+ }
+
public void snapToWidget(boolean animate) {
- final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
- int newWidth = mWidgetView.getWidth() + 2 * mBackgroundPadding
- - mWidgetPadding.left - mWidgetPadding.right;
- int newHeight = mWidgetView.getHeight() + 2 * mBackgroundPadding
- - mWidgetPadding.top - mWidgetPadding.bottom;
-
- mTmpPt[0] = mWidgetView.getLeft();
- mTmpPt[1] = mWidgetView.getTop();
- mDragLayer.getDescendantCoordRelativeToSelf(mCellLayout.getShortcutsAndWidgets(), mTmpPt);
-
- int newX = mTmpPt[0] - mBackgroundPadding + mWidgetPadding.left;
- int newY = mTmpPt[1] - mBackgroundPadding + mWidgetPadding.top;
+ getSnappedRectRelativeToDragLayer(sTmpRect);
+ int newWidth = sTmpRect.width();
+ int newHeight = sTmpRect.height();
+ int newX = sTmpRect.left;
+ int newY = sTmpRect.top;
// We need to make sure the frame's touchable regions lie fully within the bounds of the
// DragLayer. We allow the actual handles to be clipped, but we shift the touch regions
@@ -445,15 +416,15 @@
mBottomTouchRegionAdjustment = 0;
}
+ final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
if (!animate) {
lp.width = newWidth;
lp.height = newHeight;
lp.x = newX;
lp.y = newY;
- mLeftHandle.setAlpha(1.0f);
- mRightHandle.setAlpha(1.0f);
- mTopHandle.setAlpha(1.0f);
- mBottomHandle.setAlpha(1.0f);
+ for (int i = 0; i < HANDLE_COUNT; i++) {
+ mDragHandles[i].setAlpha(1.0f);
+ }
requestLayout();
} else {
PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth);
@@ -463,22 +434,15 @@
PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY);
ObjectAnimator oa =
LauncherAnimUtils.ofPropertyValuesHolder(lp, this, width, height, x, y);
- ObjectAnimator leftOa = LauncherAnimUtils.ofFloat(mLeftHandle, ALPHA, 1.0f);
- ObjectAnimator rightOa = LauncherAnimUtils.ofFloat(mRightHandle, ALPHA, 1.0f);
- ObjectAnimator topOa = LauncherAnimUtils.ofFloat(mTopHandle, ALPHA, 1.0f);
- ObjectAnimator bottomOa = LauncherAnimUtils.ofFloat(mBottomHandle, ALPHA, 1.0f);
oa.addUpdateListener(new AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
requestLayout();
}
});
AnimatorSet set = LauncherAnimUtils.createAnimatorSet();
- if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
- set.playTogether(oa, topOa, bottomOa);
- } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
- set.playTogether(oa, leftOa, rightOa);
- } else {
- set.playTogether(oa, leftOa, rightOa, topOa, bottomOa);
+ set.play(oa);
+ for (int i = 0; i < HANDLE_COUNT; i++) {
+ set.play(LauncherAnimUtils.ofFloat(mDragHandles[i], ALPHA, 1.0f));
}
set.setDuration(SNAP_DURATION);
@@ -493,10 +457,115 @@
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.clearAllResizeFrames();
+ mDragLayer.clearResizeFrame();
mWidgetView.requestFocus();
return true;
}
return false;
}
+
+ private boolean handleTouchDown(MotionEvent ev) {
+ Rect hitRect = new Rect();
+ int x = (int) ev.getX();
+ int y = (int) ev.getY();
+
+ getHitRect(hitRect);
+ if (hitRect.contains(x, y)) {
+ if (beginResizeIfPointInRegion(x - getLeft(), y - getTop())) {
+ mXDown = x;
+ mYDown = y;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onControllerTouchEvent(MotionEvent ev) {
+ int action = ev.getAction();
+ int x = (int) ev.getX();
+ int y = (int) ev.getY();
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ return handleTouchDown(ev);
+ case MotionEvent.ACTION_MOVE:
+ visualizeResizeForDelta(x - mXDown, y - mYDown);
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ visualizeResizeForDelta(x - mXDown, y - mYDown);
+ onTouchUp();
+ mXDown = mYDown = 0;
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN && handleTouchDown(ev)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * A mutable class for describing the range of two int values.
+ */
+ private static class IntRange {
+
+ public int start, end;
+
+ public int clamp(int value) {
+ return Utilities.boundToRange(value, start, end);
+ }
+
+ public void set(int s, int e) {
+ start = s;
+ end = e;
+ }
+
+ public int size() {
+ return end - start;
+ }
+
+ /**
+ * Moves either the start or end edge (but never both) by {@param delta} and sets the
+ * result in {@param out}
+ */
+ public void applyDelta(boolean moveStart, boolean moveEnd, int delta, IntRange out) {
+ out.start = moveStart ? start + delta : start;
+ out.end = moveEnd ? end + delta : end;
+ }
+
+ /**
+ * Applies delta similar to {@link #applyDelta(boolean, boolean, int, IntRange)},
+ * with extra conditions.
+ * @param minSize minimum size after with the moving edge should not be shifted any further.
+ * For eg, if delta = -3 when moving the endEdge brings the size to less than
+ * minSize, only delta = -2 will applied
+ * @param maxEnd The maximum value to the end edge (start edge is always restricted to 0)
+ * @return the amount of increase when endEdge was moves and the amount of decrease when
+ * the start edge was moved.
+ */
+ public int applyDeltaAndBound(boolean moveStart, boolean moveEnd, int delta,
+ int minSize, int maxEnd, IntRange out) {
+ applyDelta(moveStart, moveEnd, delta, out);
+ if (out.start < 0) {
+ out.start = 0;
+ }
+ if (out.end > maxEnd) {
+ out.end = maxEnd;
+ }
+ if (out.size() < minSize) {
+ if (moveStart) {
+ out.start = out.end - minSize;
+ } else if (moveEnd) {
+ out.end = out.start + minSize;
+ }
+ }
+ return moveEnd ? out.size() - size() : size() - out.size();
+ }
+ }
}
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index c5b3104..84a8bce 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -5,25 +5,33 @@
import android.appwidget.AppWidgetProviderInfo;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
-import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
+import android.os.Handler;
import android.util.Log;
import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.util.ContentWriter;
public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
private static final String TAG = "AWRestoredReceiver";
@Override
- public void onReceive(Context context, Intent intent) {
+ public void onReceive(final Context context, Intent intent) {
if (AppWidgetManager.ACTION_APPWIDGET_HOST_RESTORED.equals(intent.getAction())) {
- int[] oldIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS);
- int[] newIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
+ final int[] oldIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS);
+ final int[] newIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (oldIds.length == newIds.length) {
- restoreAppWidgetIds(context, oldIds, newIds);
+ final PendingResult asyncResult = goAsync();
+ new Handler(LauncherModel.getWorkerLooper())
+ .postAtFrontOfQueue(new Runnable() {
+ @Override
+ public void run() {
+ restoreAppWidgetIds(context, asyncResult, oldIds, newIds);
+ }
+ });
} else {
Log.e(TAG, "Invalid host restored received");
}
@@ -33,7 +41,8 @@
/**
* Updates the app widgets whose id has changed during the restore process.
*/
- static void restoreAppWidgetIds(Context context, int[] oldWidgetIds, int[] newWidgetIds) {
+ static void restoreAppWidgetIds(Context context, PendingResult asyncResult,
+ int[] oldWidgetIds, int[] newWidgetIds) {
final ContentResolver cr = context.getContentResolver();
final AppWidgetManager widgets = AppWidgetManager.getInstance(context);
AppWidgetHost appWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
@@ -50,14 +59,13 @@
state = LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
}
- ContentValues values = new ContentValues();
- values.put(LauncherSettings.Favorites.APPWIDGET_ID, newWidgetIds[i]);
- values.put(LauncherSettings.Favorites.RESTORED, state);
-
String[] widgetIdParams = new String[] { Integer.toString(oldWidgetIds[i]) };
+ int result = new ContentWriter(context, new ContentWriter.CommitParams(
+ "appWidgetId=? and (restored & 1) = 1", widgetIdParams))
+ .put(LauncherSettings.Favorites.APPWIDGET_ID, newWidgetIds[i])
+ .put(LauncherSettings.Favorites.RESTORED, state)
+ .commit();
- int result = cr.update(Favorites.CONTENT_URI, values,
- "appWidgetId=? and (restored & 1) = 1", widgetIdParams);
if (result == 0) {
Cursor cursor = cr.query(Favorites.CONTENT_URI,
new String[] {Favorites.APPWIDGET_ID},
@@ -75,7 +83,8 @@
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
if (app != null) {
- app.reloadWorkspace();
+ app.getModel().forceReload();
}
+ asyncResult.finish();
}
}
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index d5309b4..c4086a8 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -17,7 +17,6 @@
package com.android.launcher3;
import android.appwidget.AppWidgetHost;
-import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
@@ -38,6 +37,7 @@
import com.android.launcher3.LauncherProvider.SqlArguments;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.LauncherIcons;
import com.android.launcher3.util.Thunk;
import org.xmlpull.v1.XmlPullParser;
@@ -79,7 +79,7 @@
static AutoInstallsLayout get(Context context, String pkg, Resources targetRes,
AppWidgetHost appWidgetHost, LayoutParserCallback callback) {
- InvariantDeviceProfile grid = LauncherAppState.getInstance().getInvariantDeviceProfile();
+ InvariantDeviceProfile grid = LauncherAppState.getIDP(context);
// Try with grid size and hotseat count
String layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES_WITH_HOSTEAT,
@@ -182,7 +182,7 @@
mSourceRes = res;
mLayoutId = layoutId;
- mIdp = LauncherAppState.getInstance().getInvariantDeviceProfile();
+ mIdp = LauncherAppState.getIDP(context);
mRowCount = mIdp.numRows;
mColumnCount = mIdp.numColumns;
}
@@ -195,7 +195,7 @@
try {
return parseLayout(mLayoutId, screenIds);
} catch (Exception e) {
- Log.w(TAG, "Got exception parsing layout.", e);
+ Log.e(TAG, "Error parsing layout: " + e);
return -1;
}
}
@@ -362,7 +362,7 @@
return addShortcut(info.loadLabel(mPackageManager).toString(),
intent, Favorites.ITEM_TYPE_APPLICATION);
} catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Unable to add favorite: " + packageName + "/" + className, e);
+ Log.e(TAG, "Favorite not found: " + packageName + "/" + className);
}
return -1;
} else {
@@ -436,7 +436,8 @@
return -1;
}
- ItemInfo.writeBitmap(mValues, Utilities.createIconBitmap(icon, mContext));
+ mValues.put(LauncherSettings.Favorites.ICON,
+ Utilities.flattenBitmap(LauncherIcons.createIconBitmap(icon, mContext)));
mValues.put(Favorites.ICON_PACKAGE, mIconRes.getResourcePackageName(iconId));
mValues.put(Favorites.ICON_RESOURCE, mIconRes.getResourceName(iconId));
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
new file mode 100644
index 0000000..410d590
--- /dev/null
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -0,0 +1,57 @@
+/*
+ * 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 android.app.Activity;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.view.View.AccessibilityDelegate;
+
+import com.android.launcher3.logging.UserEventDispatcher;
+
+public abstract class BaseActivity extends Activity {
+
+ protected DeviceProfile mDeviceProfile;
+ protected UserEventDispatcher mUserEventDispatcher;
+
+ public DeviceProfile getDeviceProfile() {
+ return mDeviceProfile;
+ }
+
+ public AccessibilityDelegate getAccessibilityDelegate() {
+ return null;
+ }
+
+ public final UserEventDispatcher getUserEventDispatcher() {
+ if (mUserEventDispatcher == null) {
+ mUserEventDispatcher = UserEventDispatcher.newInstance(this,
+ isInMultiWindowModeCompat());
+ }
+ return mUserEventDispatcher;
+ }
+
+ public boolean isInMultiWindowModeCompat() {
+ return Utilities.ATLEAST_NOUGAT && isInMultiWindowMode();
+ }
+
+ public static BaseActivity fromContext(Context context) {
+ if (context instanceof BaseActivity) {
+ return (BaseActivity) context;
+ }
+ return ((BaseActivity) ((ContextWrapper) context).getBaseContext());
+ }
+}
diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java
index 96942ee..ac7cbaf 100644
--- a/src/com/android/launcher3/BaseContainerView.java
+++ b/src/com/android/launcher3/BaseContainerView.java
@@ -16,17 +16,23 @@
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.
@@ -34,17 +40,17 @@
public abstract class BaseContainerView extends FrameLayout
implements DeviceProfile.LauncherLayoutChangeListener {
- protected int mContainerPaddingLeft;
- protected int mContainerPaddingRight;
- protected int mContainerPaddingTop;
- protected int mContainerPaddingBottom;
+ private static final Rect sBgPaddingRect = new Rect();
- private InsetDrawable mRevealDrawable;
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);
}
@@ -72,6 +78,12 @@
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
@@ -97,6 +109,65 @@
updatePaddings();
}
+ /**
+ * Calculate the background padding as it can change due to insets/content padding change.
+ */
+ private void updatePaddings() {
+ Context context = getContext();
+ int paddingLeft;
+ int paddingRight;
+ int paddingTop;
+ int paddingBottom;
+
+ DeviceProfile grid = Launcher.getLauncher(context).getDeviceProfile();
+ int[] padding = grid.getContainerPadding();
+ paddingLeft = padding[0] + grid.edgeMarginPx;
+ paddingRight = padding[1] + grid.edgeMarginPx;
+ if (!grid.isVerticalBarLayout()) {
+ paddingTop = paddingBottom = grid.edgeMarginPx;
+ } else {
+ paddingTop = paddingBottom = 0;
+ }
+ 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);
}
@@ -109,35 +180,41 @@
return mRevealView;
}
- private void updatePaddings() {
- Context context = getContext();
- Launcher launcher = Launcher.getLauncher(context);
- if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
- this instanceof AllAppsContainerView &&
- !launcher.getDeviceProfile().isVerticalBarLayout()) {
- mContainerPaddingLeft = mContainerPaddingRight = 0;
- mContainerPaddingTop = mContainerPaddingBottom = 0;
- } else {
- DeviceProfile grid = launcher.getDeviceProfile();
- int[] padding = grid.getContainerPadding(context);
- mContainerPaddingLeft = padding[0] + grid.edgeMarginPx;
- mContainerPaddingRight = padding[1] + grid.edgeMarginPx;
- if (!launcher.getDeviceProfile().isVerticalBarLayout()) {
- mContainerPaddingTop = mContainerPaddingBottom = grid.edgeMarginPx;
- } else {
- mContainerPaddingTop = mContainerPaddingBottom = 0;
- }
+ /**
+ * 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;
}
-
- mRevealDrawable = new InsetDrawable(mBaseDrawable,
- mContainerPaddingLeft, mContainerPaddingTop, mContainerPaddingRight,
- mContainerPaddingBottom);
- mRevealView.setBackground(mRevealDrawable);
- if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && this instanceof AllAppsContainerView) {
- // Skip updating the content background
- } else {
- mContent.setBackground(mRevealDrawable);
- }
+ return false;
}
+
+ public abstract View getTouchDelegateTargetView();
}
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 45bc940..6fdf454 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -18,10 +18,11 @@
import android.content.Context;
import android.graphics.Canvas;
-import android.graphics.Rect;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.MotionEvent;
+import android.view.ViewGroup;
+
import com.android.launcher3.util.Thunk;
@@ -41,12 +42,11 @@
@Thunk int mDy = 0;
private float mDeltaThreshold;
- protected BaseRecyclerViewFastScrollBar mScrollbar;
+ protected final BaseRecyclerViewFastScrollBar mScrollbar;
private int mDownX;
private int mDownY;
private int mLastY;
- protected Rect mBackgroundPadding = new Rect();
public BaseRecyclerView(Context context) {
this(context, null);
@@ -92,6 +92,12 @@
addOnItemTouchListener(this);
}
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mScrollbar.setPopupView(((ViewGroup) getParent()).findViewById(R.id.fast_scroller_popup));
+ }
+
/**
* We intercept the touch handling only to support fast scrolling when initiated from the
* scroll bar. Otherwise, we fall back to the default RecyclerView touch handling.
@@ -156,28 +162,11 @@
return false;
}
- public void updateBackgroundPadding(Rect padding) {
- mBackgroundPadding.set(padding);
- }
-
- public Rect getBackgroundPadding() {
- return mBackgroundPadding;
- }
-
/**
- * Returns the scroll bar width when the user is scrolling.
+ * Returns the height of the fast scroll bar
*/
- public int getMaxScrollbarWidth() {
- return mScrollbar.getThumbMaxWidth();
- }
-
- /**
- * Returns the visible height of the recycler view:
- * VisibleHeight = View height - top padding - bottom padding
- */
- protected int getVisibleHeight() {
- int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom;
- return visibleHeight;
+ protected int getScrollbarTrackHeight() {
+ return getHeight();
}
/**
@@ -191,7 +180,7 @@
* AvailableScrollBarHeight = Total height of the visible view - thumb height
*/
protected int getAvailableScrollBarHeight() {
- int availableScrollBarHeight = getVisibleHeight() - mScrollbar.getThumbHeight();
+ int availableScrollBarHeight = getScrollbarTrackHeight() - mScrollbar.getThumbHeight();
return availableScrollBarHeight;
}
@@ -203,13 +192,6 @@
}
/**
- * Returns the inactive thumb color, can be overridden by each subclass.
- */
- public int getFastScrollerThumbInactiveColor(int defaultInactiveThumbColor) {
- return defaultInactiveThumbColor;
- }
-
- /**
* Returns the scrollbar for this recycler view.
*/
public BaseRecyclerViewFastScrollBar getScrollBar() {
@@ -233,31 +215,19 @@
protected void synchronizeScrollBarThumbOffsetToViewScroll(int scrollY,
int availableScrollHeight) {
// Only show the scrollbar if there is height to be scrolled
- int availableScrollBarHeight = getAvailableScrollBarHeight();
if (availableScrollHeight <= 0) {
- mScrollbar.setThumbOffset(-1, -1);
+ mScrollbar.setThumbOffsetY(-1);
return;
}
// Calculate the current scroll position, the scrollY of the recycler view accounts for the
// view padding, while the scrollBarY is drawn right up to the background padding (ignoring
// padding)
- int scrollBarY = mBackgroundPadding.top +
- (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
+ int scrollBarY =
+ (int) (((float) scrollY / availableScrollHeight) * getAvailableScrollBarHeight());
// Calculate the position and size of the scroll bar
- mScrollbar.setThumbOffset(getScrollBarX(), scrollBarY);
- }
-
- /**
- * @return the x position for the scrollbar thumb
- */
- protected int getScrollBarX() {
- if (Utilities.isRtl(getResources())) {
- return mBackgroundPadding.left;
- } else {
- return getWidth() - mBackgroundPadding.right - mScrollbar.getThumbWidth();
- }
+ mScrollbar.setThumbOffsetY(scrollBarY);
}
/**
diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
index 3d71632..5feb42e 100644
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
+++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
@@ -15,54 +15,67 @@
*/
package com.android.launcher3;
-import android.animation.AnimatorSet;
-import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
-import android.graphics.Point;
import android.graphics.Rect;
+import android.util.Property;
import android.view.MotionEvent;
+import android.view.View;
import android.view.ViewConfiguration;
+import android.widget.TextView;
-import com.android.launcher3.util.Thunk;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.Themes;
/**
* The track and scrollbar that shows when you scroll the list.
*/
public class BaseRecyclerViewFastScrollBar {
- public interface FastScrollFocusableView {
- void setFastScrollFocusState(final FastBitmapDrawable.State focusState, boolean animated);
- }
+ private static final Property<BaseRecyclerViewFastScrollBar, Integer> TRACK_WIDTH =
+ new Property<BaseRecyclerViewFastScrollBar, Integer>(Integer.class, "width") {
+
+ @Override
+ public Integer get(BaseRecyclerViewFastScrollBar scrollBar) {
+ return scrollBar.mWidth;
+ }
+
+ @Override
+ public void set(BaseRecyclerViewFastScrollBar scrollBar, Integer value) {
+ scrollBar.setTrackWidth(value);
+ }
+ };
private final static int MAX_TRACK_ALPHA = 30;
private final static int SCROLL_BAR_VIS_DURATION = 150;
+ private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f;
- @Thunk BaseRecyclerView mRv;
- private BaseRecyclerViewFastScrollPopup mPopup;
+ private final Rect mTmpRect = new Rect();
+ private final BaseRecyclerView mRv;
- private AnimatorSet mScrollbarAnimator;
+ private final boolean mIsRtl;
- private int mThumbInactiveColor;
- private int mThumbActiveColor;
- @Thunk Point mThumbOffset = new Point(-1, -1);
- @Thunk Paint mThumbPaint;
- private int mThumbMinWidth;
- private int mThumbMaxWidth;
- @Thunk int mThumbWidth;
- @Thunk int mThumbHeight;
- private int mThumbCurvature;
- private Path mThumbPath = new Path();
- private Paint mTrackPaint;
- private int mTrackWidth;
- private float mLastTouchY;
// The inset is the buffer around which a point will still register as a click on the scrollbar
- private int mTouchInset;
+ private final int mTouchInset;
+
+ private final int mMinWidth;
+ private final int mMaxWidth;
+
+ // Current width of the track
+ private int mWidth;
+ private ObjectAnimator mWidthAnimator;
+
+ private final Path mThumbPath = new Path();
+ private final Paint mThumbPaint;
+ private final int mThumbHeight;
+
+ private final Paint mTrackPaint;
+
+ private float mLastTouchY;
private boolean mIsDragging;
private boolean mIsThumbDetached;
private boolean mCanThumbDetach;
@@ -70,27 +83,35 @@
// This is the offset from the top of the scrollbar when the user first starts touching. To
// prevent jumping, this offset is applied as the user scrolls.
- private int mTouchOffset;
+ private int mTouchOffsetY;
+ private int mThumbOffsetY;
- private Rect mInvalidateRect = new Rect();
- private Rect mTmpRect = new Rect();
+ // Fast scroller popup
+ private TextView mPopupView;
+ private boolean mPopupVisible;
+ private String mPopupSectionName;
public BaseRecyclerViewFastScrollBar(BaseRecyclerView rv, Resources res) {
mRv = rv;
- mPopup = new BaseRecyclerViewFastScrollPopup(rv, res);
mTrackPaint = new Paint();
mTrackPaint.setColor(rv.getFastScrollerTrackColor(Color.BLACK));
mTrackPaint.setAlpha(MAX_TRACK_ALPHA);
- mThumbActiveColor = mThumbInactiveColor = Utilities.getColorAccent(rv.getContext());
+
mThumbPaint = new Paint();
mThumbPaint.setAntiAlias(true);
- mThumbPaint.setColor(mThumbInactiveColor);
+ mThumbPaint.setColor(Themes.getColorAccent(rv.getContext()));
mThumbPaint.setStyle(Paint.Style.FILL);
- mThumbWidth = mThumbMinWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_min_width);
- mThumbMaxWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_max_width);
+
+ mWidth = mMinWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_min_width);
+ mMaxWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_max_width);
mThumbHeight = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_height);
- mThumbCurvature = mThumbMaxWidth - mThumbMinWidth;
mTouchInset = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_touch_inset);
+ mIsRtl = Utilities.isRtl(res);
+ updateThumbPath();
+ }
+
+ public void setPopupView(View popup) {
+ mPopupView = (TextView) popup;
}
public void setDetachThumbOnFastScroll() {
@@ -101,61 +122,60 @@
mIsThumbDetached = false;
}
- public void setThumbOffset(int x, int y) {
- if (mThumbOffset.x == x && mThumbOffset.y == y) {
+ private int getDrawLeft() {
+ return mIsRtl ? 0 : (mRv.getWidth() - mMaxWidth);
+ }
+
+ public void setThumbOffsetY(int y) {
+ if (mThumbOffsetY == y) {
return;
}
- mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
- mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
- mThumbOffset.set(x, y);
+
+ // Invalidate the previous and new thumb area
+ int drawLeft = getDrawLeft();
+ mTmpRect.set(drawLeft, mThumbOffsetY, drawLeft + mMaxWidth, mThumbOffsetY + mThumbHeight);
+ mThumbOffsetY = y;
+ mTmpRect.union(drawLeft, mThumbOffsetY, drawLeft + mMaxWidth, mThumbOffsetY + mThumbHeight);
+ mRv.invalidate(mTmpRect);
+ }
+
+ public int getThumbOffsetY() {
+ return mThumbOffsetY;
+ }
+
+ private void setTrackWidth(int width) {
+ if (mWidth == width) {
+ return;
+ }
+ int left = getDrawLeft();
+ // Invalidate the whole scroll bar area.
+ mRv.invalidate(left, 0, left + mMaxWidth, mRv.getScrollbarTrackHeight());
+
+ mWidth = width;
updateThumbPath();
- mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
- mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
- mRv.invalidate(mInvalidateRect);
}
- public Point getThumbOffset() {
- return mThumbOffset;
- }
+ /**
+ * Updates the path for the thumb drawable.
+ */
+ private void updateThumbPath() {
+ int smallWidth = mIsRtl ? mWidth : -mWidth;
+ int largeWidth = mIsRtl ? mMaxWidth : -mMaxWidth;
- // Setter/getter for the thumb bar width for animations
- public void setThumbWidth(int width) {
- mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
- mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
- mThumbWidth = width;
- updateThumbPath();
- mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
- mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
- mRv.invalidate(mInvalidateRect);
- }
-
- public int getThumbWidth() {
- return mThumbWidth;
- }
-
- // Setter/getter for the track bar width for animations
- public void setTrackWidth(int width) {
- mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
- mRv.getVisibleHeight());
- mTrackWidth = width;
- updateThumbPath();
- mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
- mRv.getVisibleHeight());
- mRv.invalidate(mInvalidateRect);
- }
-
- public int getTrackWidth() {
- return mTrackWidth;
+ mThumbPath.reset();
+ mThumbPath.moveTo(0, 0);
+ mThumbPath.lineTo(0, mThumbHeight); // Left edge
+ mThumbPath.lineTo(smallWidth, mThumbHeight); // bottom edge
+ mThumbPath.cubicTo(smallWidth, mThumbHeight, // right edge
+ largeWidth, mThumbHeight / 2,
+ smallWidth, 0);
+ mThumbPath.close();
}
public int getThumbHeight() {
return mThumbHeight;
}
- public int getThumbMaxWidth() {
- return mThumbMaxWidth;
- }
-
public boolean isDraggingThumb() {
return mIsDragging;
}
@@ -176,7 +196,12 @@
switch (action) {
case MotionEvent.ACTION_DOWN:
if (isNearThumb(downX, downY)) {
- mTouchOffset = downY - mThumbOffset.y;
+ mTouchOffsetY = downY - mThumbOffsetY;
+ } else if (FeatureFlags.LAUNCHER3_DIRECT_SCROLL
+ && mRv.supportsFastScrolling()
+ && isNearScrollBar(downX)) {
+ calcTouchOffsetAndPrepToFastScroll(downY, lastY);
+ updateFastScrollSectionNameAndThumbOffset(lastY, y);
}
break;
case MotionEvent.ACTION_MOVE:
@@ -186,112 +211,113 @@
if (!mIsDragging && !mIgnoreDragGesture && mRv.supportsFastScrolling() &&
isNearThumb(downX, lastY) &&
Math.abs(y - downY) > config.getScaledTouchSlop()) {
- mRv.getParent().requestDisallowInterceptTouchEvent(true);
- mIsDragging = true;
- if (mCanThumbDetach) {
- mIsThumbDetached = true;
- }
- mTouchOffset += (lastY - downY);
- mPopup.animateVisibility(true);
- showActiveScrollbar(true);
+ calcTouchOffsetAndPrepToFastScroll(downY, lastY);
}
if (mIsDragging) {
- // Update the fastscroller section name at this touch position
- int top = mRv.getBackgroundPadding().top;
- int bottom = top + mRv.getVisibleHeight() - mThumbHeight;
- float boundedY = (float) Math.max(top, Math.min(bottom, y - mTouchOffset));
- String sectionName = mRv.scrollToPositionAtProgress((boundedY - top) /
- (bottom - top));
- mPopup.setSectionName(sectionName);
- mPopup.animateVisibility(!sectionName.isEmpty());
- mRv.invalidate(mPopup.updateFastScrollerBounds(lastY));
- mLastTouchY = boundedY;
- setThumbOffset(mRv.getScrollBarX(), (int) mLastTouchY);
+ updateFastScrollSectionNameAndThumbOffset(lastY, y);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
- mTouchOffset = 0;
+ mTouchOffsetY = 0;
mLastTouchY = 0;
mIgnoreDragGesture = false;
if (mIsDragging) {
mIsDragging = false;
- mPopup.animateVisibility(false);
+ animatePopupVisibility(false);
showActiveScrollbar(false);
}
break;
}
}
+ private void calcTouchOffsetAndPrepToFastScroll(int downY, int lastY) {
+ mRv.getParent().requestDisallowInterceptTouchEvent(true);
+ mIsDragging = true;
+ if (mCanThumbDetach) {
+ mIsThumbDetached = true;
+ }
+ mTouchOffsetY += (lastY - downY);
+ animatePopupVisibility(true);
+ showActiveScrollbar(true);
+ }
+
+ private void updateFastScrollSectionNameAndThumbOffset(int lastY, int y) {
+ // Update the fastscroller section name at this touch position
+ int bottom = mRv.getScrollbarTrackHeight() - mThumbHeight;
+ float boundedY = (float) Math.max(0, Math.min(bottom, y - mTouchOffsetY));
+ String sectionName = mRv.scrollToPositionAtProgress(boundedY / bottom);
+ if (!sectionName.equals(mPopupSectionName)) {
+ mPopupSectionName = sectionName;
+ mPopupView.setText(sectionName);
+ }
+ animatePopupVisibility(!sectionName.isEmpty());
+ updatePopupY(lastY);
+ mLastTouchY = boundedY;
+ setThumbOffsetY((int) mLastTouchY);
+ }
+
public void draw(Canvas canvas) {
- if (mThumbOffset.x < 0 || mThumbOffset.y < 0) {
+ if (mThumbOffsetY < 0) {
return;
}
-
- // Draw the scroll bar track and thumb
- if (mTrackPaint.getAlpha() > 0) {
- canvas.drawRect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
- mRv.getVisibleHeight(), mTrackPaint);
+ int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ if (!mIsRtl) {
+ canvas.translate(mRv.getWidth(), 0);
}
- canvas.drawPath(mThumbPath, mThumbPaint);
+ // Draw the track
+ int thumbWidth = mIsRtl ? mWidth : -mWidth;
+ canvas.drawRect(0, 0, thumbWidth, mRv.getScrollbarTrackHeight(), mTrackPaint);
- // Draw the popup
- mPopup.draw(canvas);
+ canvas.translate(0, mThumbOffsetY);
+ canvas.drawPath(mThumbPath, mThumbPaint);
+ canvas.restoreToCount(saveCount);
}
/**
- * Animates the width and color of the scrollbar.
+ * Animates the width of the scrollbar.
*/
private void showActiveScrollbar(boolean isScrolling) {
- if (mScrollbarAnimator != null) {
- mScrollbarAnimator.cancel();
+ if (mWidthAnimator != null) {
+ mWidthAnimator.cancel();
}
- mScrollbarAnimator = new AnimatorSet();
- ObjectAnimator trackWidthAnim = ObjectAnimator.ofInt(this, "trackWidth",
- isScrolling ? mThumbMaxWidth : mThumbMinWidth);
- ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "thumbWidth",
- isScrolling ? mThumbMaxWidth : mThumbMinWidth);
- mScrollbarAnimator.playTogether(trackWidthAnim, thumbWidthAnim);
- if (mThumbActiveColor != mThumbInactiveColor) {
- ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(),
- mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor);
- colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animator) {
- mThumbPaint.setColor((Integer) animator.getAnimatedValue());
- mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
- mThumbOffset.y + mThumbHeight);
- }
- });
- mScrollbarAnimator.play(colorAnimation);
- }
- mScrollbarAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
- mScrollbarAnimator.start();
+ mWidthAnimator = ObjectAnimator.ofInt(this, TRACK_WIDTH,
+ isScrolling ? mMaxWidth : mMinWidth);
+ mWidthAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
+ mWidthAnimator.start();
}
/**
- * Updates the path for the thumb drawable.
- */
- private void updateThumbPath() {
- mThumbCurvature = mThumbMaxWidth - mThumbWidth;
- mThumbPath.reset();
- mThumbPath.moveTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y); // tr
- mThumbPath.lineTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight); // br
- mThumbPath.lineTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight); // bl
- mThumbPath.cubicTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight,
- mThumbOffset.x - mThumbCurvature, mThumbOffset.y + mThumbHeight / 2,
- mThumbOffset.x, mThumbOffset.y); // bl2tl
- mThumbPath.close();
- }
-
- /**
- * Returns whether the specified points are near the scroll bar bounds.
+ * Returns whether the specified point is inside the thumb bounds.
*/
public boolean isNearThumb(int x, int y) {
- mTmpRect.set(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
- mThumbOffset.y + mThumbHeight);
+ int left = getDrawLeft();
+ mTmpRect.set(left, mThumbOffsetY, left + mMaxWidth, mThumbOffsetY + mThumbHeight);
mTmpRect.inset(mTouchInset, mTouchInset);
return mTmpRect.contains(x, y);
}
+
+ /**
+ * Returns whether the specified x position is near the scroll bar.
+ */
+ public boolean isNearScrollBar(int x) {
+ int left = getDrawLeft();
+ return x >= left && x <= left + mMaxWidth;
+ }
+
+ private void animatePopupVisibility(boolean visible) {
+ if (mPopupVisible != visible) {
+ mPopupVisible = visible;
+ mPopupView.animate().cancel();
+ mPopupView.animate().alpha(visible ? 1f : 0f).setDuration(visible ? 200 : 150).start();
+ }
+ }
+
+ private void updatePopupY(int lastTouchY) {
+ int height = mPopupView.getHeight();
+ float top = lastTouchY - (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * height);
+ top = Math.max(mMaxWidth, Math.min(top, mRv.getScrollbarTrackHeight() - mMaxWidth - height));
+ mPopupView.setTranslationY(top);
+ }
}
diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java
deleted file mode 100644
index b9e6277..0000000
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java
+++ /dev/null
@@ -1,196 +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.ObjectAnimator;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-
-/**
- * The fast scroller popup that shows the section name the list will jump to.
- */
-public class BaseRecyclerViewFastScrollPopup {
-
- private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f;
-
- private static final int SHADOW_INSET = 3;
- private static final int SHADOW_SHIFT_Y = 2;
- private static final float SHADOW_ALPHA_MULTIPLIER = 0.67f;
-
- private Resources mRes;
- private BaseRecyclerView mRv;
-
- private Bitmap mShadow;
- private Paint mShadowPaint;
-
- private Drawable mBg;
- // The absolute bounds of the fast scroller bg
- private Rect mBgBounds = new Rect();
- private int mBgOriginalSize;
- private Rect mInvalidateRect = new Rect();
- private Rect mTmpRect = new Rect();
-
- private String mSectionName;
- private Paint mTextPaint;
- private Rect mTextBounds = new Rect();
- private float mAlpha;
-
- private Animator mAlphaAnimator;
- private boolean mVisible;
-
- public BaseRecyclerViewFastScrollPopup(BaseRecyclerView rv, Resources res) {
- mRes = res;
- mRv = rv;
-
- mBgOriginalSize = res.getDimensionPixelSize(R.dimen.container_fastscroll_popup_size);
- mBg = rv.getContext().getDrawable(R.drawable.container_fastscroll_popup_bg);
- mBg.setBounds(0, 0, mBgOriginalSize, mBgOriginalSize);
-
- mTextPaint = new Paint();
- mTextPaint.setColor(Color.WHITE);
- mTextPaint.setAntiAlias(true);
- mTextPaint.setTextSize(res.getDimensionPixelSize(R.dimen.container_fastscroll_popup_text_size));
-
- mShadowPaint = new Paint();
- mShadowPaint.setAntiAlias(true);
- mShadowPaint.setFilterBitmap(true);
- mShadowPaint.setDither(true);
- }
-
- /**
- * Sets the section name.
- */
- public void setSectionName(String sectionName) {
- if (!sectionName.equals(mSectionName)) {
- mSectionName = sectionName;
- mTextPaint.getTextBounds(sectionName, 0, sectionName.length(), mTextBounds);
- // Update the width to use measureText since that is more accurate
- mTextBounds.right = (int) (mTextBounds.left + mTextPaint.measureText(sectionName));
- }
- }
-
- /**
- * Updates the bounds for the fast scroller.
- *
- * @return the invalidation rect for this update.
- */
- public Rect updateFastScrollerBounds(int lastTouchY) {
- mInvalidateRect.set(mBgBounds);
-
- if (isVisible()) {
- // Calculate the dimensions and position of the fast scroller popup
- int edgePadding = mRv.getMaxScrollbarWidth();
- int bgPadding = (mBgOriginalSize - mTextBounds.height()) / 2;
- int bgHeight = mBgOriginalSize;
- int bgWidth = Math.max(mBgOriginalSize, mTextBounds.width() + (2 * bgPadding));
- if (Utilities.isRtl(mRes)) {
- mBgBounds.left = mRv.getBackgroundPadding().left + (2 * mRv.getMaxScrollbarWidth());
- mBgBounds.right = mBgBounds.left + bgWidth;
- } else {
- mBgBounds.right = mRv.getWidth() - mRv.getBackgroundPadding().right -
- (2 * mRv.getMaxScrollbarWidth());
- mBgBounds.left = mBgBounds.right - bgWidth;
- }
- mBgBounds.top = lastTouchY - (int) (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * bgHeight);
- mBgBounds.top = Math.max(edgePadding,
- Math.min(mBgBounds.top, mRv.getVisibleHeight() - edgePadding - bgHeight));
- mBgBounds.bottom = mBgBounds.top + bgHeight;
-
- // Generate a bitmap for a shadow matching these bounds
- mShadow = HolographicOutlineHelper.obtain(
- mRv.getContext()).createMediumDropShadow(mBg, false /* shouldCache */);
- } else {
- mShadow = null;
- mBgBounds.setEmpty();
- }
-
- // Combine the old and new fast scroller bounds to create the full invalidate rect
- mInvalidateRect.union(mBgBounds);
- return mInvalidateRect;
- }
-
- /**
- * Animates the visibility of the fast scroller popup.
- */
- public void animateVisibility(boolean visible) {
- if (mVisible != visible) {
- mVisible = visible;
- if (mAlphaAnimator != null) {
- mAlphaAnimator.cancel();
- }
- mAlphaAnimator = ObjectAnimator.ofFloat(this, "alpha", visible ? 1f : 0f);
- mAlphaAnimator.setDuration(visible ? 200 : 150);
- mAlphaAnimator.start();
- }
- }
-
- // Setter/getter for the popup alpha for animations
- public void setAlpha(float alpha) {
- mAlpha = alpha;
- mRv.invalidate(mBgBounds);
- }
-
- public float getAlpha() {
- return mAlpha;
- }
-
- public int getHeight() {
- return mBgOriginalSize;
- }
-
- public void draw(Canvas c) {
- if (isVisible()) {
- // Determine the alpha and prepare the canvas
- final int alpha = (int) (mAlpha * 255);
- int restoreCount = c.save(Canvas.MATRIX_SAVE_FLAG);
- c.translate(mBgBounds.left, mBgBounds.top);
- mTmpRect.set(mBgBounds);
- mTmpRect.offsetTo(0, 0);
-
- // Expand the rect (with a negative inset), translate it, and draw the shadow
- if (mShadow != null) {
- mTmpRect.inset(-SHADOW_INSET * 2, -SHADOW_INSET * 2);
- mTmpRect.offset(0, SHADOW_SHIFT_Y);
- mShadowPaint.setAlpha((int) (alpha * SHADOW_ALPHA_MULTIPLIER));
- c.drawBitmap(mShadow, null, mTmpRect, mShadowPaint);
- mTmpRect.inset(SHADOW_INSET * 2, SHADOW_INSET * 2);
- mTmpRect.offset(0, -SHADOW_SHIFT_Y);
- }
-
- // Draw the background
- mBg.setBounds(mTmpRect);
- mBg.setAlpha(alpha);
- mBg.draw(c);
-
- // Draw the text
- mTextPaint.setAlpha(alpha);
- c.drawText(mSectionName, (mBgBounds.width() - mTextBounds.width()) / 2,
- mBgBounds.height() - (mBgBounds.height() / 2) - mTextBounds.exactCenterY(),
- mTextPaint);
- c.restoreToCount(restoreCount);
- }
- }
-
- public boolean isVisible() {
- return (mAlpha > 0f) && (mSectionName != null);
- }
-}
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index a294fa5..cb40d3d 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -16,21 +16,20 @@
package com.android.launcher3;
-import android.annotation.TargetApi;
+import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
-import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.Region;
-import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.util.AttributeSet;
-import android.util.SparseArray;
+import android.util.Property;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -41,7 +40,14 @@
import android.widget.TextView;
import com.android.launcher3.IconCache.IconLoadRequest;
+import com.android.launcher3.IconCache.ItemInfoUpdateReceiver;
+import com.android.launcher3.badge.BadgeInfo;
+import com.android.launcher3.badge.BadgeRenderer;
import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.graphics.DrawableFactory;
+import com.android.launcher3.graphics.HolographicOutlineHelper;
+import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.model.PackageItemInfo;
import java.text.NumberFormat;
@@ -51,10 +57,7 @@
* because we want to make the bubble taller than the text and TextView's clip is
* too aggressive.
*/
-public class BubbleTextView extends TextView
- implements BaseRecyclerViewFastScrollBar.FastScrollFocusableView {
-
- private static SparseArray<Theme> sPreloaderThemes = new SparseArray<Theme>(2);
+public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver {
// Dimensions in DP
private static final float AMBIENT_SHADOW_RADIUS = 2.5f;
@@ -67,6 +70,8 @@
private static final int DISPLAY_ALL_APPS = 1;
private static final int DISPLAY_FOLDER = 2;
+ private static final int[] STATE_PRESSED = new int[] {android.R.attr.state_pressed};
+
private final Launcher mLauncher;
private Drawable mIcon;
private final boolean mCenterVertically;
@@ -89,6 +94,28 @@
@ViewDebug.ExportedProperty(category = "launcher")
private int mTextColor;
+ private BadgeInfo mBadgeInfo;
+ private BadgeRenderer mBadgeRenderer;
+ private IconPalette mIconPalette;
+ private float mBadgeScale;
+ private boolean mForceHideBadge;
+ private Point mTempSpaceForBadgeOffset = new Point();
+ private Rect mTempIconBounds = new Rect();
+
+ private static final Property<BubbleTextView, Float> BADGE_SCALE_PROPERTY
+ = new Property<BubbleTextView, Float>(Float.TYPE, "badgeScale") {
+ @Override
+ public Float get(BubbleTextView bubbleTextView) {
+ return bubbleTextView.mBadgeScale;
+ }
+
+ @Override
+ public void set(BubbleTextView bubbleTextView, Float value) {
+ bubbleTextView.mBadgeScale = value;
+ bubbleTextView.invalidate();
+ }
+ };
+
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mStayPressed;
@ViewDebug.ExportedProperty(category = "launcher")
@@ -113,7 +140,7 @@
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.BubbleTextView, defStyle, 0);
- mCustomShadowsEnabled = a.getBoolean(R.styleable.BubbleTextView_customShadows, true);
+ mCustomShadowsEnabled = a.getBoolean(R.styleable.BubbleTextView_customShadows, false);
mLayoutHorizontal = a.getBoolean(R.styleable.BubbleTextView_layoutHorizontal, false);
mDeferShadowGenerationOnTouch =
a.getBoolean(R.styleable.BubbleTextView_deferShadowGeneration, false);
@@ -127,7 +154,9 @@
setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx);
defaultIconSize = grid.allAppsIconSizePx;
} else if (display == DISPLAY_FOLDER) {
+ setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.folderChildTextSizePx);
setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx);
+ defaultIconSize = grid.folderChildIconSizePx;
}
mCenterVertically = a.getBoolean(R.styleable.BubbleTextView_centerVertically, false);
@@ -150,21 +179,22 @@
mLongPressHelper = new CheckLongPressHelper(this);
mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
- mOutlineHelper = HolographicOutlineHelper.obtain(getContext());
+ mOutlineHelper = HolographicOutlineHelper.getInstance(getContext());
setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
}
- public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache) {
- applyFromShortcutInfo(info, iconCache, false);
+ public void applyFromShortcutInfo(ShortcutInfo info) {
+ applyFromShortcutInfo(info, false);
}
- public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache,
- boolean promiseStateChanged) {
- applyIconAndLabel(info.getIcon(iconCache), info);
+ public void applyFromShortcutInfo(ShortcutInfo info, boolean promiseStateChanged) {
+ applyIconAndLabel(info.iconBitmap, info);
setTag(info);
if (promiseStateChanged || info.isPromise()) {
- applyState(promiseStateChanged);
+ applyPromiseState(promiseStateChanged);
}
+
+ applyBadgeState(info, false /* animate */);
}
public void applyFromApplicationInfo(AppInfo info) {
@@ -175,6 +205,8 @@
// Verify high res immediately
verifyHighRes();
+
+ applyBadgeState(info, false /* animate */);
}
public void applyFromPackageItemInfo(PackageItemInfo info) {
@@ -187,10 +219,8 @@
}
private void applyIconAndLabel(Bitmap icon, ItemInfo info) {
- FastBitmapDrawable iconDrawable = mLauncher.createIconDrawable(icon);
- if (info.isDisabled()) {
- iconDrawable.setState(FastBitmapDrawable.State.DISABLED);
- }
+ FastBitmapDrawable iconDrawable = DrawableFactory.get(getContext()).newIcon(icon, info);
+ iconDrawable.setIsDisabled(info.isDisabled());
setIcon(iconDrawable);
setText(info.title);
if (info.contentDescription != null) {
@@ -201,15 +231,6 @@
}
/**
- * Used for measurement only, sets some dummy values on this view.
- */
- public void applyDummyInfo() {
- ColorDrawable d = new ColorDrawable();
- setIcon(mLauncher.resizeIconDrawable(d));
- setText("");
- }
-
- /**
* Overrides the default long press timeout.
*/
public void setLongPressTimeout(int longPressTimeout) {
@@ -238,14 +259,21 @@
}
@Override
- public void setPressed(boolean pressed) {
- super.setPressed(pressed);
-
+ public void refreshDrawableState() {
if (!mIgnorePressedStateChange) {
- updateIconState();
+ super.refreshDrawableState();
}
}
+ @Override
+ protected int[] onCreateDrawableState(int extraSpace) {
+ final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+ if (mStayPressed) {
+ mergeDrawableStates(drawableState, STATE_PRESSED);
+ }
+ return drawableState;
+ }
+
/** Returns the icon for this view. */
public Drawable getIcon() {
return mIcon;
@@ -256,20 +284,6 @@
return mLayoutHorizontal;
}
- private void updateIconState() {
- if (mIcon instanceof FastBitmapDrawable) {
- FastBitmapDrawable d = (FastBitmapDrawable) mIcon;
- if (getTag() instanceof ItemInfo
- && ((ItemInfo) getTag()).isDisabled()) {
- d.animateState(FastBitmapDrawable.State.DISABLED);
- } else if (isPressed() || mStayPressed) {
- d.animateState(FastBitmapDrawable.State.PRESSED);
- } else {
- d.animateState(FastBitmapDrawable.State.NORMAL);
- }
- }
- }
-
@Override
public void setOnLongClickListener(OnLongClickListener l) {
super.setOnLongClickListener(l);
@@ -328,7 +342,7 @@
void setStayPressed(boolean stayPressed) {
mStayPressed = stayPressed;
if (!stayPressed) {
- HolographicOutlineHelper.obtain(getContext()).recycleShadowBitmap(mPressedBackground);
+ HolographicOutlineHelper.getInstance(getContext()).recycleShadowBitmap(mPressedBackground);
mPressedBackground = null;
} else {
if (mPressedBackground == null) {
@@ -343,7 +357,7 @@
this, mPressedBackground);
}
- updateIconState();
+ refreshDrawableState();
}
void clearPressedBackground() {
@@ -373,7 +387,7 @@
mPressedBackground = null;
mIgnorePressedStateChange = false;
- updateIconState();
+ refreshDrawableState();
return result;
}
@@ -381,6 +395,7 @@
public void draw(Canvas canvas) {
if (!mCustomShadowsEnabled) {
super.draw(canvas);
+ drawBadgeIfNecessary(canvas);
return;
}
@@ -404,9 +419,10 @@
}
// If text is transparent, don't draw any shadow
- if (getCurrentTextColor() == getResources().getColor(android.R.color.transparent)) {
+ if ((getCurrentTextColor() >> 24) == 0) {
getPaint().clearShadowLayer();
super.draw(canvas);
+ drawBadgeIfNecessary(canvas);
return;
}
@@ -422,6 +438,50 @@
density * KEY_SHADOW_RADIUS, 0.0f, density * KEY_SHADOW_OFFSET, KEY_SHADOW_COLOR);
super.draw(canvas);
canvas.restore();
+
+ drawBadgeIfNecessary(canvas);
+ }
+
+ /**
+ * Draws the icon badge in the top right corner of the icon bounds.
+ * @param canvas The canvas to draw to.
+ */
+ private void drawBadgeIfNecessary(Canvas canvas) {
+ if (!mForceHideBadge && (hasBadge() || mBadgeScale > 0)) {
+ getIconBounds(mTempIconBounds);
+ mTempSpaceForBadgeOffset.set((getWidth() - mIconSize) / 2, getPaddingTop());
+ final int scrollX = getScrollX();
+ final int scrollY = getScrollY();
+ canvas.translate(scrollX, scrollY);
+ mBadgeRenderer.draw(canvas, mIconPalette, mBadgeInfo, mTempIconBounds, mBadgeScale,
+ mTempSpaceForBadgeOffset);
+ canvas.translate(-scrollX, -scrollY);
+ }
+ }
+
+ public void forceHideBadge(boolean forceHideBadge) {
+ if (mForceHideBadge == forceHideBadge) {
+ return;
+ }
+ mForceHideBadge = forceHideBadge;
+
+ if (forceHideBadge) {
+ invalidate();
+ } else if (hasBadge()) {
+ ObjectAnimator.ofFloat(this, BADGE_SCALE_PROPERTY, 0, 1).start();
+ }
+ }
+
+ private boolean hasBadge() {
+ return mBadgeInfo != null;
+ }
+
+ public void getIconBounds(Rect outBounds) {
+ int top = getPaddingTop();
+ int left = (getWidth() - mIconSize) / 2;
+ int right = left + mIconSize;
+ int bottom = top + mIconSize;
+ outBounds.set(left, top, right, bottom);
}
@Override
@@ -429,10 +489,6 @@
super.onAttachedToWindow();
if (mBackground != null) mBackground.setCallback(this);
-
- if (mIcon instanceof PreloadIconDrawable) {
- ((PreloadIconDrawable) mIcon).applyPreloaderTheme(getPreloaderTheme());
- }
mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
@@ -483,7 +539,7 @@
mLongPressHelper.cancelLongPress();
}
- public void applyState(boolean promiseStateChanged) {
+ public void applyPromiseState(boolean promiseStateChanged) {
if (getTag() instanceof ShortcutInfo) {
ShortcutInfo info = (ShortcutInfo) getTag();
final boolean isPromise = info.isPromise();
@@ -492,8 +548,8 @@
info.getInstallProgress() : 0)) : 100;
setContentDescription(progressLevel > 0 ?
- getContext().getString(R.string.app_downloading_title, info.title,
- NumberFormat.getPercentInstance().format(progressLevel * 0.01)) :
+ 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 (mIcon != null) {
@@ -501,7 +557,8 @@
if (mIcon instanceof PreloadIconDrawable) {
preloadDrawable = (PreloadIconDrawable) mIcon;
} else {
- preloadDrawable = new PreloadIconDrawable(mIcon, getPreloaderTheme());
+ preloadDrawable = DrawableFactory.get(getContext())
+ .newPendingIcon(info.iconBitmap, getContext());
setIcon(preloadDrawable);
}
@@ -513,39 +570,38 @@
}
}
- private Theme getPreloaderTheme() {
- Object tag = getTag();
- int style = ((tag != null) && (tag instanceof ShortcutInfo) &&
- (((ShortcutInfo) tag).container >= 0)) ? R.style.PreloadIcon_Folder
- : R.style.PreloadIcon;
- Theme theme = sPreloaderThemes.get(style);
- if (theme == null) {
- theme = getResources().newTheme();
- theme.applyStyle(style, true);
- sPreloaderThemes.put(style, theme);
+ public void applyBadgeState(ItemInfo itemInfo, boolean animate) {
+ if (mIcon instanceof FastBitmapDrawable) {
+ boolean wasBadged = mBadgeInfo != null;
+ mBadgeInfo = mLauncher.getPopupDataProvider().getBadgeInfoForItem(itemInfo);
+ boolean isBadged = mBadgeInfo != null;
+ float newBadgeScale = isBadged ? 1f : 0;
+ mBadgeRenderer = mLauncher.getDeviceProfile().mBadgeRenderer;
+ if (wasBadged || isBadged) {
+ mIconPalette = ((FastBitmapDrawable) mIcon).getIconPalette();
+ // Animate when a badge is first added or when it is removed.
+ if (animate && (wasBadged ^ isBadged) && isShown()) {
+ ObjectAnimator.ofFloat(this, BADGE_SCALE_PROPERTY, newBadgeScale).start();
+ } else {
+ mBadgeScale = newBadgeScale;
+ invalidate();
+ }
+ }
}
- return theme;
}
/**
* Sets the icon for this view based on the layout direction.
*/
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private void setIcon(Drawable icon) {
mIcon = icon;
- if (mIconSize != -1) {
- mIcon.setBounds(0, 0, mIconSize, mIconSize);
- }
+ mIcon.setBounds(0, 0, mIconSize, mIconSize);
applyCompoundDrawables(mIcon);
}
protected void applyCompoundDrawables(Drawable icon) {
if (mLayoutHorizontal) {
- if (Utilities.ATLEAST_JB_MR1) {
- setCompoundDrawablesRelative(icon, null, null, null);
- } else {
- setCompoundDrawables(icon, null, null, null);
- }
+ setCompoundDrawablesRelative(icon, null, null, null);
} else {
setCompoundDrawables(null, icon, null, null);
}
@@ -561,20 +617,16 @@
/**
* Applies the item info if it is same as what the view is pointing to currently.
*/
- public void reapplyItemInfo(final ItemInfo info) {
+ @Override
+ public void reapplyItemInfo(ItemInfoWithIcon info) {
if (getTag() == info) {
- FastBitmapDrawable.State prevState = FastBitmapDrawable.State.NORMAL;
- if (mIcon instanceof FastBitmapDrawable) {
- prevState = ((FastBitmapDrawable) mIcon).getCurrentState();
- }
mIconLoadRequest = null;
mDisableRelayout = true;
if (info instanceof AppInfo) {
applyFromApplicationInfo((AppInfo) info);
} else if (info instanceof ShortcutInfo) {
- applyFromShortcutInfo((ShortcutInfo) info,
- LauncherAppState.getInstance().getIconCache());
+ applyFromShortcutInfo((ShortcutInfo) info);
if ((info.rank < FolderIcon.NUM_ITEMS_IN_PREVIEW) && (info.container >= 0)) {
View folderIcon =
mLauncher.getWorkspace().getHomescreenIconByItemId(info.container);
@@ -586,12 +638,6 @@
applyFromPackageItemInfo((PackageItemInfo) info);
}
- // If we are reapplying over an old icon, then we should update the new icon to the same
- // state as the old icon
- if (mIcon instanceof FastBitmapDrawable) {
- ((FastBitmapDrawable) mIcon).setState(prevState);
- }
-
mDisableRelayout = false;
}
}
@@ -604,52 +650,12 @@
mIconLoadRequest.cancel();
mIconLoadRequest = null;
}
- if (getTag() instanceof AppInfo) {
- AppInfo info = (AppInfo) getTag();
+ if (getTag() instanceof ItemInfoWithIcon) {
+ ItemInfoWithIcon info = (ItemInfoWithIcon) getTag();
if (info.usingLowResIcon) {
- mIconLoadRequest = LauncherAppState.getInstance().getIconCache()
+ mIconLoadRequest = LauncherAppState.getInstance(getContext()).getIconCache()
.updateIconInBackground(BubbleTextView.this, info);
}
- } else if (getTag() instanceof ShortcutInfo) {
- ShortcutInfo info = (ShortcutInfo) getTag();
- if (info.usingLowResIcon) {
- mIconLoadRequest = LauncherAppState.getInstance().getIconCache()
- .updateIconInBackground(BubbleTextView.this, info);
- }
- } else if (getTag() instanceof PackageItemInfo) {
- PackageItemInfo info = (PackageItemInfo) getTag();
- if (info.usingLowResIcon) {
- mIconLoadRequest = LauncherAppState.getInstance().getIconCache()
- .updateIconInBackground(BubbleTextView.this, info);
- }
- }
- }
-
- @Override
- public void setFastScrollFocusState(final FastBitmapDrawable.State focusState, boolean animated) {
- // We can only set the fast scroll focus state on a FastBitmapDrawable
- if (!(mIcon instanceof FastBitmapDrawable)) {
- return;
- }
-
- FastBitmapDrawable d = (FastBitmapDrawable) mIcon;
- if (animated) {
- FastBitmapDrawable.State prevState = d.getCurrentState();
- if (d.animateState(focusState)) {
- // If the state was updated, then update the view accordingly
- animate().scaleX(focusState.viewScale)
- .scaleY(focusState.viewScale)
- .setStartDelay(getStartDelayForStateChange(prevState, focusState))
- .setDuration(d.getDurationForStateChange(prevState, focusState))
- .start();
- }
- } else {
- if (d.setState(focusState)) {
- // If the state was updated, then update the view accordingly
- animate().cancel();
- setScaleX(focusState.viewScale);
- setScaleY(focusState.viewScale);
- }
}
}
@@ -657,22 +663,8 @@
* Returns true if the view can show custom shortcuts.
*/
public boolean hasDeepShortcuts() {
- return !mLauncher.getShortcutIdsForItem((ItemInfo) getTag()).isEmpty();
- }
-
- /**
- * Returns the start delay when animating between certain {@link FastBitmapDrawable} states.
- */
- private static int getStartDelayForStateChange(final FastBitmapDrawable.State fromState,
- final FastBitmapDrawable.State toState) {
- switch (toState) {
- case NORMAL:
- switch (fromState) {
- case FAST_SCROLL_HIGHLIGHTED:
- return FastBitmapDrawable.FAST_SCROLL_INACTIVE_DURATION / 4;
- }
- }
- return 0;
+ return !mLauncher.getPopupDataProvider().getShortcutIdsForItem((ItemInfo) getTag())
+ .isEmpty();
}
/**
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 60a2cc3..8a477d8 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -21,17 +21,14 @@
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
-import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
@@ -45,6 +42,7 @@
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.util.Themes;
import com.android.launcher3.util.Thunk;
/**
@@ -101,17 +99,11 @@
mOriginalTextColor = getTextColors();
}
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
protected void setDrawable(int resId) {
// We do not set the drawable in the xml as that inflates two drawables corresponding to
// drawableLeft and drawableStart.
mDrawable = getResources().getDrawable(resId);
-
- if (Utilities.ATLEAST_JB_MR1) {
- setCompoundDrawablesRelativeWithIntrinsicBounds(mDrawable, null, null, null);
- } else {
- setCompoundDrawablesWithIntrinsicBounds(mDrawable, null, null, null);
- }
+ setCompoundDrawablesRelativeWithIntrinsicBounds(mDrawable, null, null, null);
}
public void setDropTargetBar(DropTargetBar dropTargetBar) {
@@ -119,21 +111,9 @@
}
@Override
- public void onFlingToDelete(DragObject d, PointF vec) { }
-
- @Override
public final void onDragEnter(DragObject d) {
d.dragView.setColor(mHoverColor);
- if (Utilities.ATLEAST_LOLLIPOP) {
- animateTextColor(mHoverColor);
- } else {
- if (mCurrentFilter == null) {
- mCurrentFilter = new ColorMatrix();
- }
- DragView.setColorScale(mHoverColor, mCurrentFilter);
- mDrawable.setColorFilter(new ColorMatrixColorFilter(mCurrentFilter));
- setTextColor(mHoverColor);
- }
+ animateTextColor(mHoverColor);
if (d.stateAnnouncer != null) {
d.stateAnnouncer.cancel();
}
@@ -146,15 +126,9 @@
}
protected void resetHoverColor() {
- if (Utilities.ATLEAST_LOLLIPOP) {
- animateTextColor(mOriginalTextColor.getDefaultColor());
- } else {
- mDrawable.setColorFilter(null);
- setTextColor(mOriginalTextColor);
- }
+ animateTextColor(mOriginalTextColor.getDefaultColor());
}
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void animateTextColor(int targetColor) {
if (mCurrentColorAnim != null) {
mCurrentColorAnim.cancel();
@@ -169,8 +143,8 @@
mCurrentFilter = new ColorMatrix();
}
- DragView.setColorScale(getTextColor(), mSrcFilter);
- DragView.setColorScale(targetColor, mDstFilter);
+ Themes.setColorScaleOnMatrix(getTextColor(), mSrcFilter);
+ Themes.setColorScaleOnMatrix(targetColor, mDstFilter);
ValueAnimator anim1 = ValueAnimator.ofObject(
new FloatArrayEvaluator(mCurrentFilter.getArray()),
mSrcFilter.getArray(), mDstFilter.getArray());
@@ -243,10 +217,7 @@
final Rect from = new Rect();
dragLayer.getViewRectRelativeToSelf(d.dragView, from);
- int width = mDrawable.getIntrinsicWidth();
- int height = mDrawable.getIntrinsicHeight();
- final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
- width, height);
+ final Rect to = getIconRect(d);
final float scale = (float) to.width() / from.width();
mDropTargetBar.deferOnDragEnd();
@@ -259,7 +230,7 @@
}
};
dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f,
- mLauncher.getDragController().isExternalDrag() ? 1 : DRAG_VIEW_DROP_DURATION,
+ DRAG_VIEW_DROP_DURATION,
new DecelerateInterpolator(2),
new LinearInterpolator(), onAnimationEndRunnable,
DragLayer.ANIMATION_END_DISAPPEAR, null);
@@ -268,7 +239,7 @@
@Override
public void prepareAccessibilityDrop() { }
- @Thunk abstract void completeDrop(DragObject d);
+ public abstract void completeDrop(DragObject d);
@Override
public void getHitRectRelativeToDragLayer(android.graphics.Rect outRect) {
@@ -280,7 +251,11 @@
outRect.offsetTo(coords[0], coords[1]);
}
- protected Rect getIconRect(int viewWidth, int viewHeight, int drawableWidth, int drawableHeight) {
+ public Rect getIconRect(DragObject dragObject) {
+ int viewWidth = dragObject.dragView.getMeasuredWidth();
+ int viewHeight = dragObject.dragView.getMeasuredHeight();
+ int drawableWidth = mDrawable.getIntrinsicWidth();
+ int drawableHeight = mDrawable.getIntrinsicHeight();
DragLayer dragLayer = mLauncher.getDragLayer();
// Find the rect to animate to (the view is center aligned)
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 57fd0e7..8179dad 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -21,9 +21,9 @@
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -32,9 +32,8 @@
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.TransitionDrawable;
-import android.os.Build;
import android.os.Parcelable;
+import android.support.annotation.IntDef;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.Log;
@@ -51,7 +50,7 @@
import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
import com.android.launcher3.accessibility.FolderAccessibilityHelper;
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.DragPreviewProvider;
@@ -60,6 +59,8 @@
import com.android.launcher3.util.ParcelableSparseArray;
import com.android.launcher3.util.Thunk;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -87,13 +88,6 @@
@ViewDebug.ExportedProperty(category = "launcher")
private int mCountY;
- private int mOriginalWidthGap;
- private int mOriginalHeightGap;
- @ViewDebug.ExportedProperty(category = "launcher")
- @Thunk int mWidthGap;
- @ViewDebug.ExportedProperty(category = "launcher")
- @Thunk int mHeightGap;
- private int mMaxGap;
private boolean mDropPending = false;
private boolean mIsDragTarget = true;
private boolean mJailContent = true;
@@ -111,13 +105,12 @@
private ArrayList<FolderIcon.PreviewBackground> mFolderBackgrounds = new ArrayList<FolderIcon.PreviewBackground>();
FolderIcon.PreviewBackground mFolderLeaveBehind = new FolderIcon.PreviewBackground();
- Paint mFolderBgPaint = new Paint();
private float mBackgroundAlpha;
- private static final int BACKGROUND_ACTIVATE_DURATION =
- FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND ? 120 : 0;
- private final TransitionDrawable mBackground;
+ 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;
// These values allow a fixed measurement to be set on the CellLayout.
private int mFixedWidth = -1;
@@ -152,8 +145,16 @@
private TimeInterpolator mEaseOutInterpolator;
private ShortcutAndWidgetContainer mShortcutsAndWidgets;
- private boolean mIsHotseat = false;
- private float mHotseatScale = 1f;
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({WORKSPACE, HOTSEAT, FOLDER})
+ public @interface ContainerType{}
+ public static final int WORKSPACE = 0;
+ public static final int HOTSEAT = 1;
+ public static final int FOLDER = 2;
+
+ @ContainerType private final int mContainerType;
+
+ private final float mChildScale;
public static final int MODE_SHOW_REORDER_HINT = 0;
public static final int MODE_DRAG_OVER = 1;
@@ -165,7 +166,7 @@
private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
private static final int REORDER_ANIMATION_DURATION = 150;
- @Thunk float mReorderPreviewAnimationMagnitude;
+ @Thunk final float mReorderPreviewAnimationMagnitude;
private ArrayList<View> mIntersectingViews = new ArrayList<View>();
private Rect mOccupiedRect = new Rect();
@@ -191,6 +192,9 @@
public CellLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
+ mContainerType = a.getInteger(R.styleable.CellLayout_containerType, WORKSPACE);
+ a.recycle();
// A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
// the user where a dragged item will land when dropped.
@@ -202,9 +206,6 @@
mCellWidth = mCellHeight = -1;
mFixedCellWidth = mFixedCellHeight = -1;
- mWidthGap = mOriginalWidthGap = 0;
- mHeightGap = mOriginalHeightGap = 0;
- mMaxGap = Integer.MAX_VALUE;
mCountX = grid.inv.numColumns;
mCountY = grid.inv.numRows;
@@ -217,18 +218,16 @@
mFolderLeaveBehind.delegateCellX = -1;
mFolderLeaveBehind.delegateCellY = -1;
+ mChildScale = mContainerType == HOTSEAT ? grid.inv.hotseatScale : 1f;
+
setAlwaysDrawnWithCacheEnabled(false);
final Resources res = getResources();
- mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
- mBackground = (TransitionDrawable) res.getDrawable(
- FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND ? R.drawable.bg_screenpanel
- : R.drawable.bg_celllayout);
+ mBackground = res.getDrawable(R.drawable.bg_celllayout);
mBackground.setCallback(this);
mBackground.setAlpha((int) (mBackgroundAlpha * 255));
- mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
- grid.iconSizePx);
+ mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * grid.iconSizePx);
// Initialize the data structures used for the drag visualization.
mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
@@ -286,9 +285,8 @@
mDragOutlineAnims[i] = anim;
}
- mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
- mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
- mCountX, mCountY);
+ mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context, mContainerType);
+ mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
@@ -297,7 +295,6 @@
addView(mShortcutsAndWidgets);
}
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void enableAccessibleDrag(boolean enable, int dragType) {
mUseTouchHelper = enable;
if (!enable) {
@@ -366,15 +363,10 @@
mShortcutsAndWidgets.buildLayer();
}
- public float getChildrenScale() {
- return mIsHotseat ? mHotseatScale : 1.0f;
- }
-
public void setCellDimensions(int width, int height) {
mFixedCellWidth = mCellWidth = width;
mFixedCellHeight = mCellHeight = height;
- mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
- mCountX, mCountY);
+ mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
}
public void setGridSize(int x, int y) {
@@ -383,8 +375,7 @@
mOccupied = new GridOccupancy(mCountX, mCountY);
mTmpOccupied = new GridOccupancy(mCountX, mCountY);
mTempRectStack.clear();
- mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
- mCountX, mCountY);
+ mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
requestLayout();
}
@@ -426,15 +417,8 @@
void setIsDragOverlapping(boolean isDragOverlapping) {
if (mIsDragOverlapping != isDragOverlapping) {
mIsDragOverlapping = isDragOverlapping;
- if (mIsDragOverlapping) {
- mBackground.startTransition(BACKGROUND_ACTIVATE_DURATION);
- } else {
- if (mBackgroundAlpha > 0f) {
- mBackground.reverseTransition(BACKGROUND_ACTIVATE_DURATION);
- } else {
- mBackground.resetTransition();
- }
- }
+ mBackground.setState(mIsDragOverlapping
+ ? BACKGROUND_STATE_ACTIVE : BACKGROUND_STATE_DEFAULT);
invalidate();
}
}
@@ -516,9 +500,9 @@
cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
canvas.save();
canvas.translate(mTempLocation[0], mTempLocation[1]);
- bg.drawBackground(canvas, mFolderBgPaint);
+ bg.drawBackground(canvas);
if (!bg.isClipping) {
- bg.drawBackgroundStroke(canvas, mFolderBgPaint);
+ bg.drawBackgroundStroke(canvas);
}
canvas.restore();
}
@@ -528,7 +512,7 @@
mFolderLeaveBehind.delegateCellY, mTempLocation);
canvas.save();
canvas.translate(mTempLocation[0], mTempLocation[1]);
- mFolderLeaveBehind.drawLeaveBehind(canvas, mFolderBgPaint);
+ mFolderLeaveBehind.drawLeaveBehind(canvas);
canvas.restore();
}
}
@@ -543,7 +527,7 @@
cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
canvas.save();
canvas.translate(mTempLocation[0], mTempLocation[1]);
- bg.drawBackgroundStroke(canvas, mFolderBgPaint);
+ bg.drawBackgroundStroke(canvas);
canvas.restore();
}
}
@@ -616,13 +600,8 @@
return mCountY;
}
- public void setIsHotseat(boolean isHotseat) {
- mIsHotseat = isHotseat;
- mShortcutsAndWidgets.setIsHotseat(isHotseat);
- }
-
- public boolean isHotseat() {
- return mIsHotseat;
+ public boolean acceptsWidget() {
+ return mContainerType == WORKSPACE;
}
public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
@@ -632,11 +611,11 @@
// Hotseat icons - remove text
if (child instanceof BubbleTextView) {
BubbleTextView bubbleChild = (BubbleTextView) child;
- bubbleChild.setTextVisibility(!mIsHotseat);
+ bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
}
- child.setScaleX(getChildrenScale());
- child.setScaleY(getChildrenScale());
+ child.setScaleX(mChildScale);
+ child.setScaleY(mChildScale);
// Generate an id for each view, this assumes we have at most 256x256 cells
// per workspace screen
@@ -717,8 +696,8 @@
final int hStartPadding = getPaddingLeft();
final int vStartPadding = getPaddingTop();
- result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
- result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
+ result[0] = (x - hStartPadding) / mCellWidth;
+ result[1] = (y - vStartPadding) / mCellHeight;
final int xAxis = mCountX;
final int yAxis = mCountY;
@@ -751,8 +730,8 @@
final int hStartPadding = getPaddingLeft();
final int vStartPadding = getPaddingTop();
- result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
- result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
+ result[0] = hStartPadding + cellX * mCellWidth;
+ result[1] = vStartPadding + cellY * mCellHeight;
}
/**
@@ -778,10 +757,8 @@
void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
final int hStartPadding = getPaddingLeft();
final int vStartPadding = getPaddingTop();
- result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
- (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
- result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
- (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
+ result[0] = hStartPadding + cellX * mCellWidth + (spanX * mCellWidth) / 2;
+ result[1] = vStartPadding + cellY * mCellHeight + (spanY * mCellHeight) / 2;
}
/**
@@ -794,10 +771,9 @@
void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
final int hStartPadding = getPaddingLeft();
final int vStartPadding = getPaddingTop();
- final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
- final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
- result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
- top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
+ final int left = hStartPadding + cellX * mCellWidth;
+ final int top = vStartPadding + cellY * mCellHeight;
+ result.set(left, top, left + (spanX * mCellWidth), top + (spanY * mCellHeight));
}
public float getDistanceFromCell(float x, float y, int[] cell) {
@@ -813,14 +789,6 @@
return mCellHeight;
}
- int getWidthGap() {
- return mWidthGap;
- }
-
- int getHeightGap() {
- return mHeightGap;
- }
-
public void setFixedSize(int width, int height) {
mFixedWidth = width;
mFixedHeight = height;
@@ -840,8 +808,7 @@
if (cw != mCellWidth || ch != mCellHeight) {
mCellWidth = cw;
mCellHeight = ch;
- mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
- mHeightGap, mCountX, mCountY);
+ mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
}
}
@@ -854,23 +821,6 @@
throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
}
- int numWidthGaps = mCountX - 1;
- int numHeightGaps = mCountY - 1;
-
- if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
- int hSpace = childWidthSize;
- int vSpace = childHeightSize;
- int hFreeSpace = hSpace - (mCountX * mCellWidth);
- int vFreeSpace = vSpace - (mCountY * mCellHeight);
- mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
- mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
- mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
- mHeightGap, mCountX, mCountY);
- } else {
- mWidthGap = mOriginalWidthGap;
- mHeightGap = mOriginalHeightGap;
- }
-
// Make the feedback view large enough to hold the blur bitmap.
mTouchFeedbackView.measure(
MeasureSpec.makeMeasureSpec(mCellWidth + mTouchFeedbackView.getExtraSize(),
@@ -930,16 +880,6 @@
return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth);
}
- @Override
- protected void setChildrenDrawingCacheEnabled(boolean enabled) {
- mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
- }
-
- @Override
- protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
- mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
- }
-
public float getBackgroundAlpha() {
return mBackgroundAlpha;
}
@@ -997,7 +937,7 @@
lp.tmpCellX = cellX;
lp.tmpCellY = cellY;
}
- clc.setupLp(lp);
+ clc.setupLp(child);
lp.isLockedToGrid = false;
final int newX = lp.x;
final int newY = lp.y;
@@ -1011,14 +951,14 @@
return true;
}
- ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
+ ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
va.setDuration(duration);
mReorderAnimators.put(lp, va);
va.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
- float r = ((Float) animation.getAnimatedValue()).floatValue();
+ float r = (Float) animation.getAnimatedValue();
lp.x = (int) ((1 - r) * oldX + r * newX);
lp.y = (int) ((1 - r) * oldY + r * newY);
child.requestLayout();
@@ -1054,11 +994,11 @@
final int oldDragCellX = mDragCell[0];
final int oldDragCellY = mDragCell[1];
- if (outlineProvider == null || outlineProvider.gerenatedDragOutline == null) {
+ if (outlineProvider == null || outlineProvider.generatedDragOutline == null) {
return;
}
- Bitmap dragOutline = outlineProvider.gerenatedDragOutline;
+ Bitmap dragOutline = outlineProvider.generatedDragOutline;
if (cellX != oldDragCellX || cellY != oldDragCellY) {
Point dragOffset = dragObject.dragView.getDragVisualizeOffset();
Rect dragRegion = dragObject.dragView.getDragRegion();
@@ -1073,6 +1013,10 @@
if (resize) {
cellToRect(cellX, cellY, spanX, spanY, r);
+ if (v instanceof LauncherAppWidgetHostView) {
+ DeviceProfile profile = mLauncher.getDeviceProfile();
+ Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
+ }
} else {
// Find the top left corner of the rect the object will occupy
final int[] topLeft = mTmpPoint;
@@ -1091,48 +1035,46 @@
// Offsets due to the size difference between the View and the dragOutline.
// There is a size difference to account for the outer blur, which may lie
// outside the bounds of the view.
- top += (v.getHeight() - dragOutline.getHeight()) / 2;
+ top += ((mCellHeight * spanY) - dragOutline.getHeight()) / 2;
// We center about the x axis
- left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
- - dragOutline.getWidth()) / 2;
+ left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
} else {
if (dragOffset != null && dragRegion != null) {
// Center the drag region *horizontally* in the cell and apply a drag
// outline offset
- left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
- - dragRegion.width()) / 2;
+ left += dragOffset.x + ((mCellWidth * spanX) - dragRegion.width()) / 2;
int cHeight = getShortcutsAndWidgets().getCellContentHeight();
int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
top += dragOffset.y + cellPaddingY;
} else {
// Center the drag outline in the cell
- left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
- - dragOutline.getWidth()) / 2;
- top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
- - dragOutline.getHeight()) / 2;
+ left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
+ top += ((mCellHeight * spanY) - dragOutline.getHeight()) / 2;
}
}
r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
}
- Utilities.scaleRectAboutCenter(r, getChildrenScale());
+ Utilities.scaleRectAboutCenter(r, mChildScale);
mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
mDragOutlineAnims[mDragOutlineCurrent].animateIn();
if (dragObject.stateAnnouncer != null) {
- String msg;
- if (isHotseat()) {
- msg = getContext().getString(R.string.move_to_hotseat_position,
- Math.max(cellX, cellY) + 1);
- } else {
- msg = getContext().getString(R.string.move_to_empty_cell,
- cellY + 1, cellX + 1);
- }
- dragObject.stateAnnouncer.announce(msg);
+ dragObject.stateAnnouncer.announce(getItemMoveDescription(cellX, cellY));
}
}
}
+ public String getItemMoveDescription(int cellX, int cellY) {
+ if (mContainerType == HOTSEAT) {
+ return getContext().getString(R.string.move_to_hotseat_position,
+ Math.max(cellX, cellY) + 1);
+ } else {
+ return getContext().getString(R.string.move_to_empty_cell,
+ cellY + 1, cellX + 1);
+ }
+ }
+
public void clearDragOutlines() {
final int oldIndex = mDragOutlineCurrent;
mDragOutlineAnims[oldIndex].animateOut();
@@ -1198,8 +1140,8 @@
// For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
// to the center of the item, but we are searching based on the top-left cell, so
// we translate the point over to correspond to the top-left.
- pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
- pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
+ pixelX -= mCellWidth * (spanX - 1) / 2f;
+ pixelY -= mCellHeight * (spanY - 1) / 2f;
// Keep track of best-scoring drop area
final int[] bestXY = result != null ? result : new int[2];
@@ -1884,7 +1826,7 @@
* the provided point and the provided cell
*/
private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
- double angle = Math.atan(((float) deltaY) / deltaX);
+ double angle = Math.atan(deltaY / deltaX);
result[0] = 0;
result[1] = 0;
@@ -2030,6 +1972,8 @@
private static final int PREVIEW_DURATION = 300;
private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
+ private static final float CHILD_DIVIDEND = 4.0f;
+
public static final int MODE_HINT = 0;
public static final int MODE_PREVIEW = 1;
@@ -2045,45 +1989,65 @@
final int y1 = mTmpPoint[1];
final int dX = x1 - x0;
final int dY = y1 - y0;
- finalDeltaX = 0;
- finalDeltaY = 0;
+
+ this.child = child;
+ this.mode = mode;
+ setInitialAnimationValues(false);
+ finalScale = (mChildScale - (CHILD_DIVIDEND / child.getWidth())) * initScale;
+ finalDeltaX = initDeltaX;
+ finalDeltaY = initDeltaY;
int dir = mode == MODE_HINT ? -1 : 1;
if (dX == dY && dX == 0) {
} else {
if (dY == 0) {
- finalDeltaX = - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
+ finalDeltaX += - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
} else if (dX == 0) {
- finalDeltaY = - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
+ finalDeltaY += - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
} else {
double angle = Math.atan( (float) (dY) / dX);
- finalDeltaX = (int) (- dir * Math.signum(dX) *
+ finalDeltaX += (int) (- dir * Math.signum(dX) *
Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
- finalDeltaY = (int) (- dir * Math.signum(dY) *
+ finalDeltaY += (int) (- dir * Math.signum(dY) *
Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
}
}
- this.mode = mode;
- initDeltaX = child.getTranslationX();
- initDeltaY = child.getTranslationY();
- finalScale = getChildrenScale() - 4.0f / child.getWidth();
- initScale = child.getScaleX();
- this.child = child;
+ }
+
+ void setInitialAnimationValues(boolean restoreOriginalValues) {
+ if (restoreOriginalValues) {
+ if (child instanceof LauncherAppWidgetHostView) {
+ LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child;
+ initScale = lahv.getScaleToFit();
+ initDeltaX = lahv.getTranslationForCentering().x;
+ initDeltaY = lahv.getTranslationForCentering().y;
+ } else {
+ initScale = mChildScale;
+ initDeltaX = 0;
+ initDeltaY = 0;
+ }
+ } else {
+ initScale = child.getScaleX();
+ initDeltaX = child.getTranslationX();
+ initDeltaY = child.getTranslationY();
+ }
}
void animate() {
+ boolean noMovement = (finalDeltaX == initDeltaX) && (finalDeltaY == initDeltaY);
+
if (mShakeAnimators.containsKey(child)) {
ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
oldAnimation.cancel();
mShakeAnimators.remove(child);
- if (finalDeltaX == 0 && finalDeltaY == 0) {
+ if (noMovement) {
completeAnimationImmediately();
return;
}
}
- if (finalDeltaX == 0 && finalDeltaY == 0) {
+ if (noMovement) {
return;
}
- ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
+ ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
a = va;
// Animations are disabled in power save mode, causing the repeated animation to jump
@@ -2099,7 +2063,7 @@
va.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
- float r = ((Float) animation.getAnimatedValue()).floatValue();
+ float r = (Float) animation.getAnimatedValue();
float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
@@ -2113,9 +2077,7 @@
va.addListener(new AnimatorListenerAdapter() {
public void onAnimationRepeat(Animator animation) {
// We make sure to end only after a full period
- initDeltaX = 0;
- initDeltaY = 0;
- initScale = getChildrenScale();
+ setInitialAnimationValues(true);
repeating = true;
}
});
@@ -2134,12 +2096,14 @@
a.cancel();
}
- a = new LauncherViewPropertyAnimator(child)
- .scaleX(getChildrenScale())
- .scaleY(getChildrenScale())
- .translationX(0)
- .translationY(0)
- .setDuration(REORDER_ANIMATION_DURATION);
+ setInitialAnimationValues(true);
+ a = LauncherAnimUtils.ofPropertyValuesHolder(child,
+ new PropertyListBuilder()
+ .scale(initScale)
+ .translationX(initDeltaX)
+ .translationY(initDeltaY)
+ .build())
+ .setDuration(REORDER_ANIMATION_DURATION);
a.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
a.start();
}
@@ -2158,7 +2122,7 @@
long screenId = mLauncher.getWorkspace().getIdForScreen(this);
int container = Favorites.CONTAINER_DESKTOP;
- if (mLauncher.isHotseatLayout(this)) {
+ if (mContainerType == HOTSEAT) {
screenId = -1;
container = Favorites.CONTAINER_HOTSEAT;
}
@@ -2181,7 +2145,7 @@
info.spanY = lp.cellVSpan;
if (requiresDbUpdate) {
- LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId,
+ mLauncher.getModelWriter().modifyItemInDatabase(info, container, screenId,
info.cellX, info.cellY, info.spanX, info.spanY);
}
}
@@ -2215,10 +2179,6 @@
return solution;
}
- public void prepareChildForDrag(View child) {
- markCellsAsUnoccupiedForView(child);
- }
-
/* This seems like it should be obvious and straight-forward, but when the direction vector
needs to match with the notion of the dragView pushing other views, we have to employ
a slightly more subtle notion of the direction vector. The question is what two points is
@@ -2598,17 +2558,14 @@
public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
final int cellWidth = mCellWidth;
final int cellHeight = mCellHeight;
- final int widthGap = mWidthGap;
- final int heightGap = mHeightGap;
final int hStartPadding = getPaddingLeft();
final int vStartPadding = getPaddingTop();
- int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
- int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
-
- int x = hStartPadding + cellX * (cellWidth + widthGap);
- int y = vStartPadding + cellY * (cellHeight + heightGap);
+ int width = cellHSpan * cellWidth;
+ int height = cellVSpan * cellHeight;
+ int x = hStartPadding + cellX * cellWidth;
+ int y = vStartPadding + cellY * cellHeight;
resultRect.set(x, y, x + width, y + height);
}
@@ -2626,13 +2583,11 @@
}
public int getDesiredWidth() {
- return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
- (Math.max((mCountX - 1), 0) * mWidthGap);
+ return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth);
}
public int getDesiredHeight() {
- return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
- (Math.max((mCountY - 1), 0) * mHeightGap);
+ return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight);
}
public boolean isOccupied(int x, int y) {
@@ -2752,8 +2707,19 @@
this.cellVSpan = cellVSpan;
}
- public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
- boolean invertHorizontally, int colCount) {
+ public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount) {
+ setup(cellWidth, cellHeight, invertHorizontally, colCount, 1.0f, 1.0f);
+ }
+
+ /**
+ * Use this method, as opposed to {@link #setup(int, int, boolean, int)}, if the view needs
+ * to be scaled.
+ *
+ * ie. In multi-window mode, we setup widgets so that they are measured and laid out
+ * using their full/invariant device profile sizes.
+ */
+ public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
+ float cellScaleX, float cellScaleY) {
if (isLockedToGrid) {
final int myCellHSpan = cellHSpan;
final int myCellVSpan = cellVSpan;
@@ -2764,12 +2730,10 @@
myCellX = colCount - myCellX - cellHSpan;
}
- width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
- leftMargin - rightMargin;
- height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
- topMargin - bottomMargin;
- x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
- y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
+ width = (int) (myCellHSpan * cellWidth / cellScaleX - leftMargin - rightMargin);
+ height = (int) (myCellVSpan * cellHeight / cellScaleY - topMargin - bottomMargin);
+ x = (myCellX * cellWidth + leftMargin);
+ y = (myCellY * cellHeight + topMargin);
}
}
diff --git a/src/com/android/launcher3/CommonAppTypeParser.java b/src/com/android/launcher3/CommonAppTypeParser.java
deleted file mode 100644
index c2bd883..0000000
--- a/src/com/android/launcher3/CommonAppTypeParser.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.XmlResourceParser;
-import android.database.sqlite.SQLiteDatabase;
-import android.util.Log;
-
-import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
-import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.util.Thunk;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-
-/**
- * A class that parses content values corresponding to some common app types.
- */
-public class CommonAppTypeParser implements LayoutParserCallback {
- private static final String TAG = "CommonAppTypeParser";
-
- // Including TARGET_NONE
- public static final int SUPPORTED_TYPE_COUNT = 7;
-
- private static final int RESTORE_FLAG_BIT_SHIFT = 4;
-
- public static final int TARGET_PHONE = 1;
- public static final int TARGET_MESSENGER = 2;
- public static final int TARGET_EMAIL = 3;
- public static final int TARGET_BROWSER = 4;
- public static final int TARGET_GALLERY = 5;
- public static final int TARGET_CAMERA = 6;
-
- private final long mItemId;
- @Thunk final int mResId;
- @Thunk final Context mContext;
-
- ContentValues parsedValues;
- Intent parsedIntent;
- String parsedTitle;
-
- public CommonAppTypeParser(long itemId, int itemType, Context context) {
- mItemId = itemId;
- mContext = context;
- mResId = getResourceForItemType(itemType);
- }
-
- @Override
- public long generateNewItemId() {
- return mItemId;
- }
-
- @Override
- public long insertAndCheck(SQLiteDatabase db, ContentValues values) {
- parsedValues = values;
-
- // Remove unwanted values
- values.put(Favorites.ICON_PACKAGE, (String) null);
- values.put(Favorites.ICON_RESOURCE, (String) null);
- values.put(Favorites.ICON, (byte[]) null);
- return 1;
- }
-
- /**
- * Tries to find a suitable app to the provided app type.
- */
- public boolean findDefaultApp() {
- if (mResId == 0) {
- return false;
- }
-
- parsedIntent = null;
- parsedValues = null;
- new MyLayoutParser().parseValues();
- return (parsedValues != null) && (parsedIntent != null);
- }
-
- private class MyLayoutParser extends DefaultLayoutParser {
-
- public MyLayoutParser() {
- super(CommonAppTypeParser.this.mContext, null, CommonAppTypeParser.this,
- CommonAppTypeParser.this.mContext.getResources(), mResId, TAG_RESOLVE);
- }
-
- @Override
- protected long addShortcut(String title, Intent intent, int type) {
- if (type == Favorites.ITEM_TYPE_APPLICATION) {
- parsedIntent = intent;
- parsedTitle = title;
- }
- return super.addShortcut(title, intent, type);
- }
-
- public void parseValues() {
- XmlResourceParser parser = mSourceRes.getXml(mLayoutId);
- try {
- beginDocument(parser, mRootTag);
- new ResolveParser().parseAndAdd(parser);
- } catch (IOException | XmlPullParserException e) {
- Log.e(TAG, "Unable to parse default app info", e);
- }
- parser.close();
- }
- }
-
- public static int getResourceForItemType(int type) {
- switch (type) {
- case TARGET_PHONE:
- return R.xml.app_target_phone;
-
- case TARGET_MESSENGER:
- return R.xml.app_target_messenger;
-
- case TARGET_EMAIL:
- return R.xml.app_target_email;
-
- case TARGET_BROWSER:
- return R.xml.app_target_browser;
-
- case TARGET_GALLERY:
- return R.xml.app_target_gallery;
-
- case TARGET_CAMERA:
- return R.xml.app_target_camera;
-
- default:
- return 0;
- }
- }
-
- public static int encodeItemTypeToFlag(int itemType) {
- return itemType << RESTORE_FLAG_BIT_SHIFT;
- }
-
- public static int decodeItemTypeFromFlag(int flag) {
- return (flag & ShortcutInfo.FLAG_RESTORED_APP_TYPE) >> RESTORE_FLAG_BIT_SHIFT;
- }
-
-}
diff --git a/src/com/android/launcher3/DefaultLayoutParser.java b/src/com/android/launcher3/DefaultLayoutParser.java
index ef28d1e..05911ab 100644
--- a/src/com/android/launcher3/DefaultLayoutParser.java
+++ b/src/com/android/launcher3/DefaultLayoutParser.java
@@ -54,11 +54,6 @@
super(context, appWidgetHost, callback, sourceRes, layoutId, TAG_FAVORITES);
}
- public DefaultLayoutParser(Context context, AppWidgetHost appWidgetHost,
- LayoutParserCallback callback, Resources sourceRes, int layoutId, String rootTag) {
- super(context, appWidgetHost, callback, sourceRes, layoutId, rootTag);
- }
-
@Override
protected HashMap<String, TagParser> getFolderElementsMap() {
return getFolderElementsMap(mSourceRes);
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 705f841..9097ed2 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -16,19 +16,13 @@
package com.android.launcher3;
-import android.animation.TimeInterpolator;
import android.content.Context;
-import android.graphics.PointF;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
-import android.view.animation.AnimationUtils;
-import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.folder.Folder;
-import com.android.launcher3.util.FlingAnimation;
-import com.android.launcher3.util.Thunk;
public class DeleteDropTarget extends ButtonDropTarget {
@@ -78,7 +72,7 @@
}
@Override
- @Thunk void completeDrop(DragObject d) {
+ public void completeDrop(DragObject d) {
ItemInfo item = d.dragInfo;
if ((d.dragSource instanceof Workspace) || (d.dragSource instanceof Folder)) {
removeWorkspaceOrFolderItem(mLauncher, item, null);
@@ -96,53 +90,4 @@
launcher.getWorkspace().stripEmptyScreens();
launcher.getDragLayer().announceForAccessibility(launcher.getString(R.string.item_removed));
}
-
- @Override
- public void onFlingToDelete(final DragObject d, PointF vel) {
- // Don't highlight the icon as it's animating
- d.dragView.setColor(0);
-
- final DragLayer dragLayer = mLauncher.getDragLayer();
- FlingAnimation fling = new FlingAnimation(d, vel,
- getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
- mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()),
- dragLayer);
-
- final int duration = fling.getDuration();
- final long startTime = AnimationUtils.currentAnimationTimeMillis();
-
- // NOTE: Because it takes time for the first frame of animation to actually be
- // called and we expect the animation to be a continuation of the fling, we have
- // to account for the time that has elapsed since the fling finished. And since
- // we don't have a startDelay, we will always get call to update when we call
- // start() (which we want to ignore).
- final TimeInterpolator tInterpolator = new TimeInterpolator() {
- private int mCount = -1;
- private float mOffset = 0f;
-
- @Override
- public float getInterpolation(float t) {
- if (mCount < 0) {
- mCount++;
- } else if (mCount == 0) {
- mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() -
- startTime) / duration);
- mCount++;
- }
- return Math.min(1f, mOffset + t);
- }
- };
-
- Runnable onAnimationEndRunnable = new Runnable() {
- @Override
- public void run() {
- mLauncher.exitSpringLoadedDragMode();
- completeDrop(d);
- mLauncher.getDragController().onDeferredEndFling(d);
- }
- };
-
- dragLayer.animateView(d.dragView, fling, duration, tInterpolator, onAnimationEndRunnable,
- DragLayer.ANIMATION_END_DISAPPEAR, null);
- }
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index f9f8e80..e47031a 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.Gravity;
@@ -29,6 +30,8 @@
import android.view.ViewGroup.LayoutParams;
import android.widget.FrameLayout;
+import com.android.launcher3.CellLayout.ContainerType;
+import com.android.launcher3.badge.BadgeRenderer;
import com.android.launcher3.config.FeatureFlags;
import java.util.ArrayList;
@@ -73,7 +76,6 @@
public final Rect defaultWidgetPadding;
private final int defaultPageSpacingPx;
private final int topWorkspacePadding;
- private float dragViewScale;
public float workspaceSpringLoadShrinkFactor;
public final int workspaceSpringLoadedBottomSpace;
@@ -96,16 +98,23 @@
public int folderBackgroundOffset;
public int folderIconSizePx;
public int folderIconPreviewPadding;
+
+ // Folder cell
public int folderCellWidthPx;
public int folderCellHeightPx;
+
+ // Folder child
+ public int folderChildIconSizePx;
+ public int folderChildTextSizePx;
public int folderChildDrawablePaddingPx;
// Hotseat
public int hotseatCellWidthPx;
public int hotseatCellHeightPx;
public int hotseatIconSizePx;
- private int hotseatBarHeightPx;
+ public int hotseatBarHeightPx;
private int hotseatBarTopPaddingPx;
+ private int hotseatBarBottomPaddingPx;
private int hotseatLandGutterPx;
// All apps
@@ -116,9 +125,8 @@
public int allAppsIconDrawablePaddingPx;
public float allAppsIconTextSizePx;
- // Containers
- private final int containerLeftPaddingPx;
- private final int containerRightPaddingPx;
+ // Widgets
+ public final PointF appWidgetScale = new PointF(1.0f, 1.0f);
// Drop Target
public int dropTargetBarSizePx;
@@ -129,6 +137,9 @@
// Listeners
private ArrayList<LauncherLayoutChangeListener> mListeners = new ArrayList<>();
+ // Icon badges
+ public BadgeRenderer mBadgeRenderer;
+
public DeviceProfile(Context context, InvariantDeviceProfile inv,
Point minSize, Point maxSize,
int width, int height, boolean isLandscape) {
@@ -183,11 +194,8 @@
hotseatBarHeightPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_height);
hotseatBarTopPaddingPx =
res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding);
+ hotseatBarBottomPaddingPx = 0;
hotseatLandGutterPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_gutter_width);
- containerLeftPaddingPx =
- res.getDimensionPixelSize(R.dimen.dynamic_grid_container_land_left_padding);
- containerRightPaddingPx =
- res.getDimensionPixelSize(R.dimen.dynamic_grid_container_land_right_padding);
// Determine sizes.
widthPx = width;
@@ -203,6 +211,33 @@
// Calculate the remaining vars
updateAvailableDimensions(dm, res);
computeAllAppsButtonSize(context);
+
+ // This is done last, after iconSizePx is calculated above.
+ mBadgeRenderer = new BadgeRenderer(context, iconSizePx);
+ }
+
+ DeviceProfile getMultiWindowProfile(Context context, Point mwSize) {
+ // In multi-window mode, we can have widthPx = availableWidthPx
+ // and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles'
+ // widthPx and heightPx values where it's needed.
+ DeviceProfile profile = new DeviceProfile(context, inv, mwSize, mwSize, mwSize.x, mwSize.y,
+ isLandscape);
+
+ // Hide labels on the workspace.
+ profile.iconTextSizePx = 0;
+ profile.cellHeightPx = profile.iconSizePx + profile.iconDrawablePaddingPx
+ + Utilities.calculateTextHeight(profile.iconTextSizePx);
+
+ // The nav bar is black so we add bottom padding to visually center hotseat icons.
+ profile.hotseatBarBottomPaddingPx = profile.hotseatBarTopPaddingPx;
+
+ // 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.
+ float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x;
+ float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y;
+ profile.appWidgetScale.set(appWidgetScaleX, appWidgetScaleY);
+
+ return profile;
}
public void addLauncherLayoutChangedListener(LauncherLayoutChangeListener listener) {
@@ -229,19 +264,17 @@
}
private void updateAvailableDimensions(DisplayMetrics dm, Resources res) {
- // Check to see if the icons fit in the new available height. If not, then we need to
- // shrink the icon size.
- float scale = 1f;
- int drawablePadding = iconDrawablePaddingOriginalPx;
- updateIconSize(1f, drawablePadding, res, dm);
- float usedHeight = (cellHeightPx * inv.numRows);
+ updateIconSize(1f, iconDrawablePaddingOriginalPx, res, dm);
+ // Check to see if the icons fit within the available height. If not, then scale down.
+ float usedHeight = (cellHeightPx * inv.numRows);
int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y);
if (usedHeight > maxHeight) {
- scale = maxHeight / usedHeight;
- drawablePadding = 0;
+ float scale = maxHeight / usedHeight;
+ updateIconSize(scale, 0, res, dm);
}
- updateIconSize(scale, drawablePadding, res, dm);
+
+ updateAvailableFolderCellDimensions(dm, res);
}
private void updateIconSize(float scale, int drawablePadding, Resources res,
@@ -257,9 +290,6 @@
cellWidthPx = iconSizePx;
cellHeightPx = iconSizePx + iconDrawablePaddingPx
+ Utilities.calculateTextHeight(iconTextSizePx);
- final float scaleDps = !FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND ? 0f
- : res.getDimensionPixelSize(R.dimen.dragViewScale);
- dragViewScale = (iconSizePx + scaleDps) / iconSizePx;
// Hotseat
hotseatCellWidthPx = iconSizePx;
@@ -277,31 +307,53 @@
res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
}
- // Folder cell
- int cellPaddingX = res.getDimensionPixelSize(R.dimen.folder_cell_x_padding);
- int cellPaddingY = res.getDimensionPixelSize(R.dimen.folder_cell_y_padding);
- final int folderChildTextSize =
- Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_child_text_size));
-
- final int folderBottomPanelSize =
- res.getDimensionPixelSize(R.dimen.folder_label_padding_top)
- + res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom)
- + Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size));
-
- // Don't let the folder get too close to the edges of the screen.
- folderCellWidthPx = Math.min(iconSizePx + 2 * cellPaddingX,
- (availableWidthPx - 4 * edgeMarginPx) / inv.numFolderColumns);
- folderCellHeightPx = Math.min(iconSizePx + 3 * cellPaddingY + folderChildTextSize,
- (availableHeightPx - 4 * edgeMarginPx - folderBottomPanelSize) / inv.numFolderRows);
- folderChildDrawablePaddingPx = Math.max(0,
- (folderCellHeightPx - iconSizePx - folderChildTextSize) / 3);
-
// Folder icon
folderBackgroundOffset = -edgeMarginPx;
folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
folderIconPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding);
}
+ private void updateAvailableFolderCellDimensions(DisplayMetrics dm, Resources res) {
+ int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_padding_top)
+ + res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom)
+ + Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size));
+
+ updateFolderCellSize(1f, dm, res);
+
+ // Don't let the folder get too close to the edges of the screen.
+ int folderMargin = 4 * edgeMarginPx;
+
+ // Check if the icons fit within the available height.
+ float usedHeight = folderCellHeightPx * inv.numFolderRows + folderBottomPanelSize;
+ int maxHeight = availableHeightPx - getTotalWorkspacePadding().y - folderMargin;
+ float scaleY = maxHeight / usedHeight;
+
+ // Check if the icons fit within the available width.
+ float usedWidth = folderCellWidthPx * inv.numFolderColumns;
+ int maxWidth = availableWidthPx - getTotalWorkspacePadding().x - folderMargin;
+ float scaleX = maxWidth / usedWidth;
+
+ float scale = Math.min(scaleX, scaleY);
+ if (scale < 1f) {
+ updateFolderCellSize(scale, dm, res);
+ }
+ }
+
+ private void updateFolderCellSize(float scale, DisplayMetrics dm, Resources res) {
+ folderChildIconSizePx = (int) (Utilities.pxFromDp(inv.iconSize, dm) * scale);
+ folderChildTextSizePx =
+ (int) (res.getDimensionPixelSize(R.dimen.folder_child_text_size) * scale);
+
+ int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
+ int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding) * scale);
+ int cellPaddingY = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_y_padding) * scale);
+
+ folderCellWidthPx = folderChildIconSizePx + 2 * cellPaddingX;
+ folderCellHeightPx = folderChildIconSizePx + 2 * cellPaddingY + textHeight;
+ folderChildDrawablePaddingPx = Math.max(0,
+ (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3);
+ }
+
public void updateInsets(Rect insets) {
mInsets.set(insets);
}
@@ -368,16 +420,16 @@
if (isTablet) {
// Pad the left and right of the workspace to ensure consistent spacing
// between all icons
- float gapScale = 1f + (dragViewScale - 1f) / 2f;
int width = getCurrentWidth();
int height = getCurrentHeight();
// The amount of screen space available for left/right padding.
- int availablePaddingX = Math.max(0, width - (int) ((inv.numColumns * cellWidthPx) +
- ((inv.numColumns - 1) * gapScale * cellWidthPx)));
+ int availablePaddingX = Math.max(0, width - ((inv.numColumns * cellWidthPx) +
+ ((inv.numColumns - 1) * cellWidthPx)));
availablePaddingX = (int) Math.min(availablePaddingX,
width * MAX_HORIZONTAL_PADDING_PERCENT);
int availablePaddingY = Math.max(0, height - topWorkspacePadding - paddingBottom
- - (int) (2 * inv.numRows * cellHeightPx));
+ - (2 * inv.numRows * cellHeightPx) - hotseatBarTopPaddingPx
+ - hotseatBarBottomPaddingPx);
padding.set(availablePaddingX / 2, topWorkspacePadding + availablePaddingY / 2,
availablePaddingX / 2, paddingBottom + availablePaddingY / 2);
} else {
@@ -463,7 +515,6 @@
public void layout(Launcher launcher, boolean notifyListeners) {
FrameLayout.LayoutParams lp;
boolean hasVerticalBarLayout = isVerticalBarLayout();
- final boolean isLayoutRtl = Utilities.isRtl(launcher.getResources());
// Layout the search bar space
Point searchBarBounds = getSearchBarDimensForWidgetOpts();
@@ -481,10 +532,13 @@
workspacePadding.bottom);
workspace.setPageSpacing(getWorkspacePageSpacing());
- View qsbContainer = launcher.getQsbContainer();
- lp = (FrameLayout.LayoutParams) qsbContainer.getLayoutParams();
- lp.topMargin = mInsets.top + workspacePadding.top;
- qsbContainer.setLayoutParams(lp);
+ // Only display when enabled
+ if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
+ View qsbContainer = launcher.getQsbContainer();
+ lp = (FrameLayout.LayoutParams) qsbContainer.getLayoutParams();
+ lp.topMargin = mInsets.top + workspacePadding.top;
+ qsbContainer.setLayoutParams(lp);
+ }
// Layout the hotseat
Hotseat hotseat = (Hotseat) launcher.findViewById(R.id.hotseat);
@@ -511,7 +565,7 @@
lp.height = hotseatBarHeightPx + mInsets.bottom;
hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left,
hotseatBarTopPaddingPx, hotseatAdjustment + workspacePadding.right,
- mInsets.bottom);
+ hotseatBarBottomPaddingPx + mInsets.bottom);
} else {
// For phones, layout the hotseat without any bottom margin
// to ensure that we have space for the folders
@@ -520,7 +574,7 @@
lp.height = hotseatBarHeightPx + mInsets.bottom;
hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left,
hotseatBarTopPaddingPx, hotseatAdjustment + workspacePadding.right,
- mInsets.bottom);
+ hotseatBarBottomPaddingPx + mInsets.bottom);
}
hotseat.setLayoutParams(lp);
@@ -549,18 +603,13 @@
// Layout the Overview Mode
ViewGroup overviewMode = launcher.getOverviewPanel();
if (overviewMode != null) {
- lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
- lp.gravity = Gravity.LEFT | Gravity.BOTTOM;
-
int visibleChildCount = getVisibleChildCount(overviewMode);
int totalItemWidth = visibleChildCount * overviewModeBarItemWidthPx;
- int maxWidth = totalItemWidth + (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
+ int maxWidth = totalItemWidth + (visibleChildCount - 1) * overviewModeBarSpacerWidthPx;
+ lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
lp.width = Math.min(availableWidthPx, maxWidth);
- lp.height = getOverviewModeButtonBarHeight();
- // Center the overview buttons on the workspace page
- lp.leftMargin = workspacePadding.left + (availableWidthPx -
- workspacePadding.left - workspacePadding.right - lp.width) / 2;
+ lp.height = getOverviewModeButtonBarHeight() + mInsets.bottom;
overviewMode.setLayoutParams(lp);
}
@@ -583,13 +632,24 @@
: Math.max(widthPx, heightPx);
}
+ public int getCellHeight(@ContainerType int containerType) {
+ switch (containerType) {
+ case CellLayout.WORKSPACE:
+ return cellHeightPx;
+ case CellLayout.FOLDER:
+ return folderCellHeightPx;
+ case CellLayout.HOTSEAT:
+ return hotseatCellHeightPx;
+ default:
+ // ??
+ return 0;
+ }
+ }
/**
* @return the left/right paddings for all containers.
*/
- public final int[] getContainerPadding(Context context) {
- Resources res = context.getResources();
-
+ public final int[] getContainerPadding() {
// No paddings for portrait phone
if (isPhone && !isVerticalBarLayout()) {
return new int[] {0, 0};
@@ -600,4 +660,11 @@
hotseatBarHeightPx + hotseatLandGutterPx + mInsets.left) / 2;
return new int[]{ padding, padding };
}
+
+ public boolean shouldIgnoreLongPressToOverview(float touchX) {
+ boolean inMultiWindowMode = this != inv.landscapeProfile && this != inv.portraitProfile;
+ boolean touchedLhsEdge = mInsets.left == 0 && touchX < edgeMarginPx;
+ boolean touchedRhsEdge = mInsets.right == 0 && touchX > (widthPx - edgeMarginPx);
+ return !inMultiWindowMode && (touchedLhsEdge || touchedRhsEdge);
+ }
}
diff --git a/src/com/android/launcher3/DragSource.java b/src/com/android/launcher3/DragSource.java
index efbb9d7..dcd8f58 100644
--- a/src/com/android/launcher3/DragSource.java
+++ b/src/com/android/launcher3/DragSource.java
@@ -19,17 +19,12 @@
import android.view.View;
import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.logging.UserEventDispatcher.LaunchSourceProvider;
+import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
/**
* Interface defining an object that can originate a drag.
*/
-public interface DragSource extends LaunchSourceProvider {
-
- /**
- * @return whether items dragged from this source supports
- */
- boolean supportsFlingToDelete();
+public interface DragSource extends LogContainerProvider {
/**
* @return whether items dragged from this source supports 'App Info'
@@ -48,13 +43,6 @@
float getIntrinsicIconScaleFactor();
/**
- * A callback specifically made back to the source after an item from this source has been flung
- * to be deleted on a DropTarget. In such a situation, this method will be called after
- * onDropCompleted, and more importantly, after the fling animation has completed.
- */
- void onFlingToDeleteCompleted();
-
- /**
* A callback made back to the source after an item from this source has been dropped on a
* DropTarget.
*/
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index efdeb1f..7d047d7 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -16,12 +16,10 @@
package com.android.launcher3;
-import com.android.launcher3.dragndrop.DragView;
-
-import android.graphics.PointF;
import android.graphics.Rect;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
+import com.android.launcher3.dragndrop.DragView;
/**
* Interface defining an object that can receive a drag.
@@ -107,17 +105,6 @@
/**
* Handle an object being dropped on the DropTarget
- *
- * @param source DragSource where the drag started
- * @param x X coordinate of the drop location
- * @param y Y coordinate of the drop location
- * @param xOffset Horizontal offset with the object being dragged where the original
- * touch happened
- * @param yOffset Vertical offset with the object being dragged where the original
- * touch happened
- * @param dragView The DragView that's being dragged around on screen.
- * @param dragInfo Data associated with the object being dragged
- *
*/
void onDrop(DragObject dragObject);
@@ -128,25 +115,8 @@
void onDragExit(DragObject dragObject);
/**
- * Handle an object being dropped as a result of flinging to delete and will be called in place
- * of onDrop(). (This is only called on objects that are set as the DragController's
- * fling-to-delete target.
- */
- void onFlingToDelete(DragObject dragObject, PointF vec);
-
- /**
* Check if a drop action can occur at, or near, the requested location.
* This will be called just before onDrop.
- *
- * @param source DragSource where the drag started
- * @param x X coordinate of the drop location
- * @param y Y coordinate of the drop location
- * @param xOffset Horizontal offset with the object being dragged where the
- * original touch happened
- * @param yOffset Vertical offset with the object being dragged where the
- * original touch happened
- * @param dragView The DragView that's being dragged around on screen.
- * @param dragInfo Data associated with the object being dragged
* @return True if the drop will be accepted, false otherwise.
*/
boolean acceptDrop(DragObject dragObject);
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index 42bab47..0840b70 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -21,6 +21,7 @@
import android.util.AttributeSet;
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;
@@ -56,11 +57,6 @@
private ViewPropertyAnimator mCurrentAnimation;
- // Drop targets
- private ButtonDropTarget mDeleteDropTarget;
- private ButtonDropTarget mAppInfoDropTarget;
- private ButtonDropTarget mUninstallDropTarget;
-
public DropTargetBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -73,30 +69,27 @@
protected void onFinishInflate() {
super.onFinishInflate();
- // Get the individual components
- mDeleteDropTarget = (ButtonDropTarget) findViewById(R.id.delete_target_text);
- mAppInfoDropTarget = (ButtonDropTarget) findViewById(R.id.info_target_text);
- mUninstallDropTarget = (ButtonDropTarget) findViewById(R.id.uninstall_target_text);
-
- mDeleteDropTarget.setDropTargetBar(this);
- mAppInfoDropTarget.setDropTargetBar(this);
- mUninstallDropTarget.setDropTargetBar(this);
-
// Initialize with hidden state
setAlpha(0f);
}
public void setup(DragController dragController) {
dragController.addDragListener(this);
- dragController.setFlingToDeleteDropTarget(mDeleteDropTarget);
+ setupButtonDropTarget(this, dragController);
+ }
- dragController.addDragListener(mDeleteDropTarget);
- dragController.addDragListener(mAppInfoDropTarget);
- dragController.addDragListener(mUninstallDropTarget);
-
- dragController.addDropTarget(mDeleteDropTarget);
- dragController.addDropTarget(mAppInfoDropTarget);
- dragController.addDropTarget(mUninstallDropTarget);
+ 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) {
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index c06f727..596aa8f 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -29,6 +29,7 @@
public class ExtendedEditText extends EditText {
private boolean mShowImeAfterFirstLayout;
+ private boolean mForceDisableSuggestions = false;
/**
* Implemented by listeners of the back key.
@@ -99,4 +100,25 @@
((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE))
.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT);
}
+
+ public void dispatchBackKey() {
+ ((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE))
+ .hideSoftInputFromWindow(getWindowToken(), 0);
+ if (mBackKeyListener != null) {
+ mBackKeyListener.onBackKey();
+ }
+ }
+
+ /**
+ * Set to true when you want isSuggestionsEnabled to return false.
+ * Use this to disable the red underlines that appear under typos when suggestions is enabled.
+ */
+ public void forceDisableSuggestions(boolean forceDisableSuggestions) {
+ mForceDisableSuggestions = forceDisableSuggestions;
+ }
+
+ @Override
+ public boolean isSuggestionsEnabled() {
+ return !mForceDisableSuggestions && super.isSuggestionsEnabled();
+ }
}
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 3870080..a096a1d 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -16,7 +16,6 @@
package com.android.launcher3;
-import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.graphics.Bitmap;
@@ -30,37 +29,18 @@
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
+import android.util.Property;
import android.util.SparseArray;
-import android.view.animation.DecelerateInterpolator;
+
+import com.android.launcher3.graphics.IconPalette;
public class FastBitmapDrawable extends Drawable {
- /**
- * The possible states that a FastBitmapDrawable can be in.
- */
- public enum State {
+ private static final int[] STATE_PRESSED = new int[] {android.R.attr.state_pressed};
- NORMAL (0f, 0f, 1f, new DecelerateInterpolator()),
- PRESSED (0f, 100f / 255f, 1f, CLICK_FEEDBACK_INTERPOLATOR),
- FAST_SCROLL_HIGHLIGHTED (0f, 0f, 1.15f, new DecelerateInterpolator()),
- FAST_SCROLL_UNHIGHLIGHTED (0f, 0f, 1f, new DecelerateInterpolator()),
- DISABLED (1f, 0.5f, 1f, new DecelerateInterpolator());
-
- public final float desaturation;
- public final float brightness;
- /**
- * Used specifically by the view drawing this FastBitmapDrawable.
- */
- public final float viewScale;
- public final TimeInterpolator interpolator;
-
- State(float desaturation, float brightness, float viewScale, TimeInterpolator interpolator) {
- this.desaturation = desaturation;
- this.brightness = brightness;
- this.viewScale = viewScale;
- this.interpolator = interpolator;
- }
- }
+ private static final float PRESSED_BRIGHTNESS = 100f / 255f;
+ private static final float DISABLED_DESATURATION = 1f;
+ private static final float DISABLED_BRIGHTNESS = 0.5f;
public static final TimeInterpolator CLICK_FEEDBACK_INTERPOLATOR = new TimeInterpolator() {
@@ -76,10 +56,6 @@
}
};
public static final int CLICK_FEEDBACK_DURATION = 2000;
- public static final int FAST_SCROLL_HIGHLIGHT_DURATION = 225;
- public static final int FAST_SCROLL_UNHIGHLIGHT_DURATION = 150;
- public static final int FAST_SCROLL_UNHIGHLIGHT_FROM_NORMAL_DURATION = 225;
- public static final int FAST_SCROLL_INACTIVE_DURATION = 275;
// Since we don't need 256^2 values for combinations of both the brightness and saturation, we
// reduce the value space to a smaller value V, which reduces the number of cached
@@ -93,9 +69,26 @@
private static final ColorMatrix sTempBrightnessMatrix = new ColorMatrix();
private static final ColorMatrix sTempFilterMatrix = new ColorMatrix();
- private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
+ protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
private final Bitmap mBitmap;
- private State mState = State.NORMAL;
+
+ private boolean mIsPressed;
+ private boolean mIsDisabled;
+
+ private IconPalette mIconPalette;
+
+ private static final Property<FastBitmapDrawable, Float> BRIGHTNESS
+ = new Property<FastBitmapDrawable, Float>(Float.TYPE, "brightness") {
+ @Override
+ public Float get(FastBitmapDrawable fastBitmapDrawable) {
+ return fastBitmapDrawable.getBrightness();
+ }
+
+ @Override
+ public void set(FastBitmapDrawable fastBitmapDrawable, Float value) {
+ fastBitmapDrawable.setBrightness(value);
+ }
+ };
// The saturation and brightness are values that are mapped to REDUCED_FILTER_VALUE_SPACE and
// as a result, can be used to compose the key for the cached ColorMatrixColorFilters
@@ -104,19 +97,38 @@
private int mAlpha = 255;
private int mPrevUpdateKey = Integer.MAX_VALUE;
- // Animators for the fast bitmap drawable's properties
- private AnimatorSet mPropertyAnimator;
+ // Animators for the fast bitmap drawable's brightness
+ private ObjectAnimator mBrightnessAnimator;
public FastBitmapDrawable(Bitmap b) {
mBitmap = b;
- setBounds(0, 0, b.getWidth(), b.getHeight());
+ setFilterBitmap(true);
}
@Override
public void draw(Canvas canvas) {
+ drawInternal(canvas);
+ }
+
+ public void drawWithBrightness(Canvas canvas, float brightness) {
+ float oldBrightness = getBrightness();
+ setBrightness(brightness);
+ drawInternal(canvas);
+ setBrightness(oldBrightness);
+ }
+
+ protected void drawInternal(Canvas canvas) {
canvas.drawBitmap(mBitmap, null, getBounds(), mPaint);
}
+ public IconPalette getIconPalette() {
+ if (mIconPalette == null) {
+ mIconPalette = IconPalette.fromDominantColor(Utilities
+ .findDominantColorByHue(mBitmap, 20));
+ }
+ return mIconPalette;
+ }
+
@Override
public void setColorFilter(ColorFilter cf) {
// No op
@@ -167,104 +179,63 @@
return mBitmap;
}
- /**
- * Animates this drawable to a new state.
- *
- * @return whether the state has changed.
- */
- public boolean animateState(State newState) {
- State prevState = mState;
- if (mState != newState) {
- mState = newState;
+ @Override
+ public boolean isStateful() {
+ return true;
+ }
- mPropertyAnimator = cancelAnimator(mPropertyAnimator);
- mPropertyAnimator = new AnimatorSet();
- mPropertyAnimator.playTogether(
- ObjectAnimator
- .ofFloat(this, "desaturation", newState.desaturation),
- ObjectAnimator
- .ofFloat(this, "brightness", newState.brightness));
- mPropertyAnimator.setInterpolator(newState.interpolator);
- mPropertyAnimator.setDuration(getDurationForStateChange(prevState, newState));
- mPropertyAnimator.setStartDelay(getStartDelayForStateChange(prevState, newState));
- mPropertyAnimator.start();
+ @Override
+ protected boolean onStateChange(int[] state) {
+ boolean isPressed = false;
+ for (int s : state) {
+ if (s == android.R.attr.state_pressed) {
+ isPressed = true;
+ break;
+ }
+ }
+ if (mIsPressed != isPressed) {
+ mIsPressed = isPressed;
+
+ if (mBrightnessAnimator != null) {
+ mBrightnessAnimator.cancel();
+ }
+
+ if (mIsPressed) {
+ // Animate when going to pressed state
+ mBrightnessAnimator = ObjectAnimator.ofFloat(
+ this, BRIGHTNESS, getExpectedBrightness());
+ mBrightnessAnimator.setDuration(CLICK_FEEDBACK_DURATION);
+ mBrightnessAnimator.setInterpolator(CLICK_FEEDBACK_INTERPOLATOR);
+ mBrightnessAnimator.start();
+ } else {
+ setBrightness(getExpectedBrightness());
+ }
return true;
}
return false;
}
- /**
- * Immediately sets this drawable to a new state.
- *
- * @return whether the state has changed.
- */
- public boolean setState(State newState) {
- if (mState != newState) {
- mState = newState;
-
- mPropertyAnimator = cancelAnimator(mPropertyAnimator);
-
- setDesaturation(newState.desaturation);
- setBrightness(newState.brightness);
- return true;
- }
- return false;
+ private void invalidateDesaturationAndBrightness() {
+ setDesaturation(mIsDisabled ? DISABLED_DESATURATION : 0);
+ setBrightness(getExpectedBrightness());
}
- /**
- * Returns the current state.
- */
- public State getCurrentState() {
- return mState;
+ private float getExpectedBrightness() {
+ return mIsDisabled ? DISABLED_BRIGHTNESS :
+ (mIsPressed ? PRESSED_BRIGHTNESS : 0);
}
- /**
- * Returns the duration for the state change animation.
- */
- public static int getDurationForStateChange(State fromState, State toState) {
- switch (toState) {
- case NORMAL:
- switch (fromState) {
- case PRESSED:
- return 0;
- case FAST_SCROLL_HIGHLIGHTED:
- case FAST_SCROLL_UNHIGHLIGHTED:
- return FAST_SCROLL_INACTIVE_DURATION;
- }
- case PRESSED:
- return CLICK_FEEDBACK_DURATION;
- case FAST_SCROLL_HIGHLIGHTED:
- return FAST_SCROLL_HIGHLIGHT_DURATION;
- case FAST_SCROLL_UNHIGHLIGHTED:
- switch (fromState) {
- case NORMAL:
- // When animating from normal state, take a little longer
- return FAST_SCROLL_UNHIGHLIGHT_FROM_NORMAL_DURATION;
- default:
- return FAST_SCROLL_UNHIGHLIGHT_DURATION;
- }
+ public void setIsDisabled(boolean isDisabled) {
+ if (mIsDisabled != isDisabled) {
+ mIsDisabled = isDisabled;
+ invalidateDesaturationAndBrightness();
}
- return 0;
- }
-
- /**
- * Returns the start delay when animating between certain fast scroll states.
- */
- public static int getStartDelayForStateChange(State fromState, State toState) {
- switch (toState) {
- case FAST_SCROLL_UNHIGHLIGHTED:
- switch (fromState) {
- case NORMAL:
- return FAST_SCROLL_UNHIGHLIGHT_DURATION / 4;
- }
- }
- return 0;
}
/**
* Sets the saturation of this icon, 0 [full color] -> 1 [desaturated]
*/
- public void setDesaturation(float desaturation) {
+ private void setDesaturation(float desaturation) {
int newDesaturation = (int) Math.floor(desaturation * REDUCED_FILTER_VALUE_SPACE);
if (mDesaturation != newDesaturation) {
mDesaturation = newDesaturation;
@@ -279,7 +250,7 @@
/**
* Sets the brightness of this icon, 0 [no add. brightness] -> 1 [2bright2furious]
*/
- public void setBrightness(float brightness) {
+ private void setBrightness(float brightness) {
int newBrightness = (int) Math.floor(brightness * REDUCED_FILTER_VALUE_SPACE);
if (mBrightness != newBrightness) {
mBrightness = newBrightness;
@@ -287,7 +258,7 @@
}
}
- public float getBrightness() {
+ private float getBrightness() {
return (float) mBrightness / REDUCED_FILTER_VALUE_SPACE;
}
@@ -347,12 +318,4 @@
}
invalidateSelf();
}
-
- private AnimatorSet cancelAnimator(AnimatorSet animator) {
- if (animator != null) {
- animator.removeAllListeners();
- animator.cancel();
- }
- return null;
- }
}
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index 789c3f9..b36734b 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -250,14 +250,6 @@
} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
profile.isVerticalBarLayout()) {
keyCode = KeyEvent.KEYCODE_PAGE_DOWN;
- } else if (isUninstallKeyChord(e)) {
- matrix = FocusLogic.createSparseMatrix(iconLayout);
- if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) {
- UninstallDropTarget.startUninstallActivity(launcher, itemInfo);
- }
- } else if (isDeleteKeyChord(e)) {
- matrix = FocusLogic.createSparseMatrix(iconLayout);
- launcher.removeItem(v, itemInfo, true /* deleteFromDb */);
} else {
// For other KEYCODE_DPAD_LEFT and KEYCODE_DPAD_RIGHT navigation, do not use the
// matrix extended with hotseat.
@@ -374,14 +366,6 @@
} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
profile.isVerticalBarLayout()) {
matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile);
- } else if (isUninstallKeyChord(e)) {
- matrix = FocusLogic.createSparseMatrix(iconLayout);
- if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) {
- UninstallDropTarget.startUninstallActivity(launcher, itemInfo);
- }
- } else if (isDeleteKeyChord(e)) {
- matrix = FocusLogic.createSparseMatrix(iconLayout);
- launcher.removeItem(v, itemInfo, true /* deleteFromDb */);
} else {
matrix = FocusLogic.createSparseMatrix(iconLayout);
}
@@ -532,24 +516,6 @@
}
}
- /**
- * Returns whether the key event represents a valid uninstall key chord.
- */
- private static boolean isUninstallKeyChord(KeyEvent event) {
- int keyCode = event.getKeyCode();
- return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) &&
- event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON);
- }
-
- /**
- * Returns whether the key event represents a valid delete key chord.
- */
- private static boolean isDeleteKeyChord(KeyEvent event) {
- int keyCode = event.getKeyCode();
- return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) &&
- event.hasModifiers(KeyEvent.META_CTRL_ON);
- }
-
private static View handlePreviousPageLastItem(Workspace workspace, CellLayout hotseatLayout,
int pageIndex, boolean isRtl) {
if (pageIndex - 1 < 0) {
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index c0a8caa..0041bb4 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -16,10 +16,10 @@
package com.android.launcher3;
-import android.content.ContentValues;
-import android.content.Context;
+import android.os.Process;
-import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.model.ModelWriter;
+import com.android.launcher3.util.ContentWriter;
import java.util.ArrayList;
@@ -45,11 +45,6 @@
*/
public static final int FLAG_MULTI_PAGE_ANIMATION = 0x00000004;
- /**
- * Whether this folder has been opened
- */
- public boolean opened;
-
public int options;
/**
@@ -61,7 +56,7 @@
public FolderInfo() {
itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
- user = UserHandleCompat.myUserHandle();
+ user = Process.myUserHandle();
}
/**
@@ -98,10 +93,10 @@
}
@Override
- void onAddToDatabase(Context context, ContentValues values) {
- super.onAddToDatabase(context, values);
- values.put(LauncherSettings.Favorites.TITLE, title.toString());
- values.put(LauncherSettings.Favorites.OPTIONS, options);
+ public void onAddToDatabase(ContentWriter writer) {
+ super.onAddToDatabase(writer);
+ writer.put(LauncherSettings.Favorites.TITLE, title)
+ .put(LauncherSettings.Favorites.OPTIONS, options);
}
@@ -119,11 +114,18 @@
}
}
+ public void prepareAutoUpdate() {
+ for (int i = 0; i < listeners.size(); i++) {
+ listeners.get(i).prepareAutoUpdate();
+ }
+ }
+
public interface FolderListener {
public void onAdd(ShortcutInfo item);
public void onRemove(ShortcutInfo item);
public void onTitleChanged(CharSequence title);
public void onItemsChanged(boolean animate);
+ public void prepareAutoUpdate();
}
public boolean hasOption(int optionFlag) {
@@ -133,17 +135,17 @@
/**
* @param option flag to set or clear
* @param isEnabled whether to set or clear the flag
- * @param context if not null, save changes to the db.
+ * @param writer if not null, save changes to the db.
*/
- public void setOption(int option, boolean isEnabled, Context context) {
+ public void setOption(int option, boolean isEnabled, ModelWriter writer) {
int oldOptions = options;
if (isEnabled) {
options |= option;
} else {
options &= ~option;
}
- if (context != null && oldOptions != options) {
- LauncherModel.updateItemInDatabase(context, this);
+ if (writer != null && oldOptions != options) {
+ writer.updateItemInDatabase(this);
}
}
}
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 0fbbc19..47052a7 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -25,7 +25,6 @@
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
-import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.ColorUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
@@ -38,11 +37,12 @@
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;
+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.LaunchSourceProvider {
+ implements UserEventDispatcher.LogContainerProvider {
private CellLayout mContent;
@@ -70,7 +70,7 @@
mLauncher = Launcher.getLauncher(context);
mHasVerticalHotseat = mLauncher.getDeviceProfile().isVerticalBarLayout();
mBackgroundColor = ColorUtils.setAlphaComponent(
- ContextCompat.getColor(context, R.color.all_apps_container_color), 0);
+ Themes.getAttrColor(context, android.R.attr.colorPrimary), 0);
mBackground = new ColorDrawable(mBackgroundColor);
setBackground(mBackground);
}
@@ -113,12 +113,11 @@
super.onFinishInflate();
DeviceProfile grid = mLauncher.getDeviceProfile();
mContent = (CellLayout) findViewById(R.id.layout);
- if (grid.isLandscape && !grid.isLargeTablet) {
- mContent.setGridSize(1, (int) grid.inv.numHotseatIcons);
+ if (grid.isVerticalBarLayout()) {
+ mContent.setGridSize(1, grid.inv.numHotseatIcons);
} else {
- mContent.setGridSize((int) grid.inv.numHotseatIcons, 1);
+ mContent.setGridSize(grid.inv.numHotseatIcons, 1);
}
- mContent.setIsHotseat(true);
resetLayout();
}
@@ -129,14 +128,15 @@
if (!FeatureFlags.NO_ALL_APPS_ICON) {
// Add the Apps button
Context context = getContext();
- int allAppsButtonRank = mLauncher.getDeviceProfile().inv.getAllAppsButtonRank();
+ DeviceProfile grid = mLauncher.getDeviceProfile();
+ int allAppsButtonRank = grid.inv.getAllAppsButtonRank();
LayoutInflater inflater = LayoutInflater.from(context);
TextView allAppsButton = (TextView)
inflater.inflate(R.layout.all_apps_button, mContent, false);
Drawable d = context.getResources().getDrawable(R.drawable.all_apps_button_icon);
+ d.setBounds(0, 0, grid.iconSizePx, grid.iconSizePx);
- mLauncher.resizeIconDrawable(d);
int scaleDownPx = getResources().getDimensionPixelSize(R.dimen.all_apps_button_scale_down);
Rect bounds = d.getBounds();
d.setBounds(bounds.left, bounds.top + scaleDownPx / 2, bounds.right - scaleDownPx,
@@ -167,15 +167,15 @@
public boolean onInterceptTouchEvent(MotionEvent ev) {
// We don't want any clicks to go through to the hotseat unless the workspace is in
// the normal state or an accessible drag is in progress.
- return mLauncher.getWorkspace().workspaceInModalState() &&
+ return !mLauncher.getWorkspace().workspaceIconsCanBeDragged() &&
!mLauncher.getAccessibilityDelegate().isInAccessibleDrag();
}
@Override
- public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
+ public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
target.gridX = info.cellX;
target.gridY = info.cellY;
- targetParent.containerType = LauncherLogProto.HOTSEAT;
+ targetParent.containerType = ContainerType.HOTSEAT;
}
public void updateColor(ExtractedColors extractedColors, boolean animate) {
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 5c86b6b..1217030 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -22,11 +22,11 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
@@ -37,19 +37,25 @@
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.os.Handler;
+import android.os.Process;
import android.os.SystemClock;
+import android.os.UserHandle;
+import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
-import com.android.launcher3.compat.LauncherActivityInfoCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.LauncherIcons;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.Provider;
import com.android.launcher3.util.SQLiteCacheHelper;
+import com.android.launcher3.util.Themes;
import com.android.launcher3.util.Thunk;
import java.util.Collections;
@@ -78,14 +84,14 @@
@Thunk static final Object ICON_UPDATE_TOKEN = new Object();
- @Thunk static class CacheEntry {
+ public static class CacheEntry {
public Bitmap icon;
public CharSequence title = "";
public CharSequence contentDescription = "";
public boolean isLowResIcon;
}
- private final HashMap<UserHandleCompat, Bitmap> mDefaultIcons = new HashMap<>();
+ private final HashMap<UserHandle, Bitmap> mDefaultIcons = new HashMap<>();
@Thunk final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
private final Context mContext;
@@ -122,15 +128,13 @@
mLowResCanvas = new Canvas();
mLowResPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
- mIconProvider = IconProvider.loadByName(context.getString(R.string.icon_provider_class),
- context);
-
+ mIconProvider = Utilities.getOverrideObject(
+ IconProvider.class, context, R.string.icon_provider_class);
mWorkerHandler = new Handler(LauncherModel.getWorkerLooper());
- mActivityBgColor = context.getResources().getColor(R.color.quantum_panel_bg_color);
- TypedArray ta = context.obtainStyledAttributes(new int[]{R.attr.colorSecondary});
- mPackageBgColor = ta.getColor(0, 0);
- ta.recycle();
+ mActivityBgColor = Themes.getColorPrimary(context, R.style.LauncherTheme);
+ mPackageBgColor = Themes.getColorPrimary(context, R.style.WidgetContainerTheme);
+
mLowResOptions = new BitmapFactory.Options();
// Always prefer RGB_565 config for low res. If the bitmap has transparency, it will
// automatically be loaded as ALPHA_8888.
@@ -185,22 +189,26 @@
return getFullResDefaultActivityIcon();
}
- private Bitmap makeDefaultIcon(UserHandleCompat user) {
+ public Drawable getFullResIcon(LauncherActivityInfo info) {
+ return mIconProvider.getIcon(info, mIconDpi);
+ }
+
+ protected Bitmap makeDefaultIcon(UserHandle user) {
Drawable unbadged = getFullResDefaultActivityIcon();
- return Utilities.createBadgedIconBitmap(unbadged, user, mContext);
+ return LauncherIcons.createBadgedIconBitmap(unbadged, user, mContext, Build.VERSION_CODES.O);
}
/**
* Remove any records for the supplied ComponentName.
*/
- public synchronized void remove(ComponentName componentName, UserHandleCompat user) {
+ public synchronized void remove(ComponentName componentName, UserHandle user) {
mCache.remove(new ComponentKey(componentName, user));
}
/**
* Remove any records for the supplied package name from memory.
*/
- private void removeFromMemCacheLocked(String packageName, UserHandleCompat user) {
+ private void removeFromMemCacheLocked(String packageName, UserHandle user) {
HashSet<ComponentKey> forDeletion = new HashSet<ComponentKey>();
for (ComponentKey key: mCache.keySet()) {
if (key.componentName.getPackageName().equals(packageName)
@@ -216,14 +224,14 @@
/**
* Updates the entries related to the given package in memory and persistent DB.
*/
- public synchronized void updateIconsForPkg(String packageName, UserHandleCompat user) {
+ public synchronized void updateIconsForPkg(String packageName, UserHandle user) {
removeIconsForPkg(packageName, user);
try {
PackageInfo info = mPackageManager.getPackageInfo(packageName,
PackageManager.GET_UNINSTALLED_PACKAGES);
long userSerial = mUserManager.getSerialNumberForUser(user);
- for (LauncherActivityInfoCompat app : mLauncherApps.getActivityList(packageName, user)) {
- addIconToDBAndMemCache(app, info, userSerial);
+ for (LauncherActivityInfo app : mLauncherApps.getActivityList(packageName, user)) {
+ addIconToDBAndMemCache(app, info, userSerial, false /*replace existing*/);
}
} catch (NameNotFoundException e) {
Log.d(TAG, "Package not found", e);
@@ -234,7 +242,7 @@
/**
* Removes the entries related to the given package in memory and persistent DB.
*/
- public synchronized void removeIconsForPkg(String packageName, UserHandleCompat user) {
+ public synchronized void removeIconsForPkg(String packageName, UserHandle user) {
removeFromMemCacheLocked(packageName, user);
long userSerial = mUserManager.getSerialNumberForUser(user);
mIconDb.delete(
@@ -247,9 +255,9 @@
mWorkerHandler.removeCallbacksAndMessages(ICON_UPDATE_TOKEN);
mIconProvider.updateSystemStateString();
- for (UserHandleCompat user : mUserManager.getUserProfiles()) {
+ for (UserHandle user : mUserManager.getUserProfiles()) {
// Query for the set of apps
- final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
+ final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user);
// Fail if we don't have any apps
// TODO: Fix this. Only fail for the current user.
if (apps == null || apps.isEmpty()) {
@@ -258,7 +266,7 @@
// Update icon cache. This happens in segments and {@link #onPackageIconsUpdated}
// is called by the icon cache when the job is complete.
- updateDBIcons(user, apps, UserHandleCompat.myUserHandle().equals(user)
+ updateDBIcons(user, apps, Process.myUserHandle().equals(user)
? ignorePackagesForMainUser : Collections.<String>emptySet());
}
}
@@ -268,7 +276,7 @@
* the DB and are updated.
* @return The set of packages for which icons have updated.
*/
- private void updateDBIcons(UserHandleCompat user, List<LauncherActivityInfoCompat> apps,
+ private void updateDBIcons(UserHandle user, List<LauncherActivityInfo> apps,
Set<String> ignorePackages) {
long userSerial = mUserManager.getSerialNumberForUser(user);
PackageManager pm = mContext.getPackageManager();
@@ -277,13 +285,13 @@
pkgInfoMap.put(info.packageName, info);
}
- HashMap<ComponentName, LauncherActivityInfoCompat> componentMap = new HashMap<>();
- for (LauncherActivityInfoCompat app : apps) {
+ HashMap<ComponentName, LauncherActivityInfo> componentMap = new HashMap<>();
+ for (LauncherActivityInfo app : apps) {
componentMap.put(app.getComponentName(), app);
}
HashSet<Integer> itemsToRemove = new HashSet<Integer>();
- Stack<LauncherActivityInfoCompat> appsToUpdate = new Stack<>();
+ Stack<LauncherActivityInfo> appsToUpdate = new Stack<>();
Cursor c = null;
try {
@@ -318,7 +326,7 @@
long updateTime = c.getLong(indexLastUpdate);
int version = c.getInt(indexVersion);
- LauncherActivityInfoCompat app = componentMap.remove(component);
+ LauncherActivityInfo app = componentMap.remove(component);
if (version == info.versionCode && updateTime == info.lastUpdateTime &&
TextUtils.equals(c.getString(systemStateIndex),
mIconProvider.getIconSystemState(info.packageName))) {
@@ -346,36 +354,21 @@
// Insert remaining apps.
if (!componentMap.isEmpty() || !appsToUpdate.isEmpty()) {
- Stack<LauncherActivityInfoCompat> appsToAdd = new Stack<>();
+ Stack<LauncherActivityInfo> appsToAdd = new Stack<>();
appsToAdd.addAll(componentMap.values());
new SerializedIconUpdateTask(userSerial, pkgInfoMap,
appsToAdd, appsToUpdate).scheduleNext();
}
}
- @Thunk void addIconToDBAndMemCache(LauncherActivityInfoCompat app, PackageInfo info,
- long userSerial) {
- // Reuse the existing entry if it already exists in the DB. This ensures that we do not
- // create bitmap if it was already created during loader.
- ContentValues values = updateCacheAndGetContentValues(app, false);
- addIconToDB(values, app.getComponentName(), info, userSerial);
- }
-
/**
- * Updates {@param values} to contain versoning information and adds it to the DB.
- * @param values {@link ContentValues} containing icon & title
+ * Adds an entry into the DB and the in-memory cache.
+ * @param replaceExisting if true, it will recreate the bitmap even if it already exists in
+ * the memory. This is useful then the previous bitmap was created using
+ * old data.
*/
- private void addIconToDB(ContentValues values, ComponentName key,
- PackageInfo info, long userSerial) {
- values.put(IconDB.COLUMN_COMPONENT, key.flattenToString());
- values.put(IconDB.COLUMN_USER, userSerial);
- values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime);
- values.put(IconDB.COLUMN_VERSION, info.versionCode);
- mIconDb.insertOrReplace(values);
- }
-
- @Thunk ContentValues updateCacheAndGetContentValues(LauncherActivityInfoCompat app,
- boolean replaceExisting) {
+ @Thunk synchronized void addIconToDBAndMemCache(LauncherActivityInfo app,
+ PackageInfo info, long userSerial, boolean replaceExisting) {
final ComponentKey key = new ComponentKey(app.getComponentName(), app.getUser());
CacheEntry entry = null;
if (!replaceExisting) {
@@ -387,38 +380,46 @@
}
if (entry == null) {
entry = new CacheEntry();
- entry.icon = Utilities.createBadgedIconBitmap(
- mIconProvider.getIcon(app, mIconDpi), app.getUser(),
- mContext);
+ entry.icon = LauncherIcons.createBadgedIconBitmap(getFullResIcon(app), app.getUser(),
+ mContext, app.getApplicationInfo().targetSdkVersion);
}
entry.title = app.getLabel();
entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser());
- mCache.put(new ComponentKey(app.getComponentName(), app.getUser()), entry);
+ mCache.put(key, entry);
Bitmap lowResIcon = generateLowResIcon(entry.icon, mActivityBgColor);
- return newContentValues(entry.icon, lowResIcon, entry.title.toString(),
+ ContentValues values = newContentValues(entry.icon, lowResIcon, entry.title.toString(),
app.getApplicationInfo().packageName);
+ addIconToDB(values, app.getComponentName(), info, userSerial);
+ }
+
+ /**
+ * Updates {@param values} to contain versioning information and adds it to the DB.
+ * @param values {@link ContentValues} containing icon & title
+ */
+ private void addIconToDB(ContentValues values, ComponentName key,
+ PackageInfo info, long userSerial) {
+ values.put(IconDB.COLUMN_COMPONENT, key.flattenToString());
+ values.put(IconDB.COLUMN_USER, userSerial);
+ values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime);
+ values.put(IconDB.COLUMN_VERSION, info.versionCode);
+ mIconDb.insertOrReplace(values);
}
/**
* Fetches high-res icon for the provided ItemInfo and updates the caller when done.
* @return a request ID that can be used to cancel the request.
*/
- public IconLoadRequest updateIconInBackground(final BubbleTextView caller, final ItemInfo info) {
+ public IconLoadRequest updateIconInBackground(final ItemInfoUpdateReceiver caller,
+ final ItemInfoWithIcon info) {
Runnable request = new Runnable() {
@Override
public void run() {
- if (info instanceof AppInfo) {
- getTitleAndIcon((AppInfo) info, null, false);
- } else if (info instanceof ShortcutInfo) {
- ShortcutInfo st = (ShortcutInfo) info;
- getTitleAndIcon(st,
- st.promisedIntent != null ? st.promisedIntent : st.intent,
- st.user, false);
+ if (info instanceof AppInfo || info instanceof ShortcutInfo) {
+ getTitleAndIcon(info, false);
} else if (info instanceof PackageItemInfo) {
- PackageItemInfo pti = (PackageItemInfo) info;
- getTitleAndIconForApp(pti, false);
+ getTitleAndIconForApp((PackageItemInfo) info, false);
}
mMainThreadExecutor.execute(new Runnable() {
@@ -433,87 +434,55 @@
return new IconLoadRequest(request, mWorkerHandler);
}
- private Bitmap getNonNullIcon(CacheEntry entry, UserHandleCompat user) {
- return entry.icon == null ? getDefaultIcon(user) : entry.icon;
- }
-
- /**
- * Fill in "application" with the icon and label for "info."
- */
- public synchronized void getTitleAndIcon(AppInfo application,
- LauncherActivityInfoCompat info, boolean useLowResIcon) {
- UserHandleCompat user = info == null ? application.user : info.getUser();
- CacheEntry entry = cacheLocked(application.componentName, info, user,
- false, useLowResIcon);
- application.title = Utilities.trim(entry.title);
- application.contentDescription = entry.contentDescription;
- application.iconBitmap = getNonNullIcon(entry, user);
- application.usingLowResIcon = entry.isLowResIcon;
- }
-
/**
* Updates {@param application} only if a valid entry is found.
*/
public synchronized void updateTitleAndIcon(AppInfo application) {
- CacheEntry entry = cacheLocked(application.componentName, null, application.user,
- false, application.usingLowResIcon);
+ CacheEntry entry = cacheLocked(application.componentName,
+ Provider.<LauncherActivityInfo>of(null),
+ application.user, false, application.usingLowResIcon);
if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) {
- application.title = Utilities.trim(entry.title);
- application.contentDescription = entry.contentDescription;
- application.iconBitmap = entry.icon;
- application.usingLowResIcon = entry.isLowResIcon;
+ applyCacheEntry(entry, application);
}
}
/**
- * Returns a high res icon for the given intent and user
+ * Fill in {@param info} with the icon and label for {@param activityInfo}
*/
- public synchronized Bitmap getIcon(Intent intent, UserHandleCompat user) {
- ComponentName component = intent.getComponent();
- // null info means not installed, but if we have a component from the intent then
- // we should still look in the cache for restored app icons.
- if (component == null) {
- return getDefaultIcon(user);
- }
-
- LauncherActivityInfoCompat launcherActInfo = mLauncherApps.resolveActivity(intent, user);
- CacheEntry entry = cacheLocked(component, launcherActInfo, user, true, false /* useLowRes */);
- return entry.icon;
+ public synchronized void getTitleAndIcon(ItemInfoWithIcon info,
+ LauncherActivityInfo activityInfo, boolean useLowResIcon) {
+ // If we already have activity info, no need to use package icon
+ getTitleAndIcon(info, Provider.of(activityInfo), false, useLowResIcon);
}
/**
- * Fill in {@param shortcutInfo} with the icon and label for {@param intent}. If the
+ * Fill in {@param info} with the icon and label. If the
* corresponding activity is not found, it reverts to the package icon.
*/
- public synchronized void getTitleAndIcon(ShortcutInfo shortcutInfo, Intent intent,
- UserHandleCompat user, boolean useLowResIcon) {
- ComponentName component = intent.getComponent();
+ public synchronized void getTitleAndIcon(ItemInfoWithIcon info, boolean useLowResIcon) {
// null info means not installed, but if we have a component from the intent then
// we should still look in the cache for restored app icons.
- if (component == null) {
- shortcutInfo.setIcon(getDefaultIcon(user));
- shortcutInfo.title = "";
- shortcutInfo.contentDescription = "";
- shortcutInfo.usingFallbackIcon = true;
- shortcutInfo.usingLowResIcon = false;
+ if (info.getTargetComponent() == null) {
+ info.iconBitmap = getDefaultIcon(info.user);
+ info.title = "";
+ info.contentDescription = "";
+ info.usingLowResIcon = false;
} else {
- LauncherActivityInfoCompat info = mLauncherApps.resolveActivity(intent, user);
- getTitleAndIcon(shortcutInfo, component, info, user, true, useLowResIcon);
+ getTitleAndIcon(info, new ActivityInfoProvider(info.getIntent(), info.user),
+ true, useLowResIcon);
}
}
/**
* Fill in {@param shortcutInfo} with the icon and label for {@param info}
*/
- public synchronized void getTitleAndIcon(
- ShortcutInfo shortcutInfo, ComponentName component, LauncherActivityInfoCompat info,
- UserHandleCompat user, boolean usePkgIcon, boolean useLowResIcon) {
- CacheEntry entry = cacheLocked(component, info, user, usePkgIcon, useLowResIcon);
- shortcutInfo.setIcon(getNonNullIcon(entry, user));
- shortcutInfo.title = Utilities.trim(entry.title);
- shortcutInfo.contentDescription = entry.contentDescription;
- shortcutInfo.usingFallbackIcon = isDefaultIcon(entry.icon, user);
- shortcutInfo.usingLowResIcon = entry.isLowResIcon;
+ private synchronized void getTitleAndIcon(
+ @NonNull ItemInfoWithIcon infoInOut,
+ @NonNull Provider<LauncherActivityInfo> activityInfoProvider,
+ boolean usePkgIcon, boolean useLowResIcon) {
+ CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), activityInfoProvider,
+ infoInOut.user, usePkgIcon, useLowResIcon);
+ applyCacheEntry(entry, infoInOut);
}
/**
@@ -523,20 +492,24 @@
PackageItemInfo infoInOut, boolean useLowResIcon) {
CacheEntry entry = getEntryForPackageLocked(
infoInOut.packageName, infoInOut.user, useLowResIcon);
- infoInOut.title = Utilities.trim(entry.title);
- infoInOut.contentDescription = entry.contentDescription;
- infoInOut.iconBitmap = getNonNullIcon(entry, infoInOut.user);
- infoInOut.usingLowResIcon = entry.isLowResIcon;
+ applyCacheEntry(entry, infoInOut);
}
- public synchronized Bitmap getDefaultIcon(UserHandleCompat user) {
+ private void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) {
+ info.title = Utilities.trim(entry.title);
+ info.contentDescription = entry.contentDescription;
+ info.iconBitmap = entry.icon == null ? getDefaultIcon(info.user) : entry.icon;
+ info.usingLowResIcon = entry.isLowResIcon;
+ }
+
+ public synchronized Bitmap getDefaultIcon(UserHandle user) {
if (!mDefaultIcons.containsKey(user)) {
mDefaultIcons.put(user, makeDefaultIcon(user));
}
return mDefaultIcons.get(user);
}
- public boolean isDefaultIcon(Bitmap icon, UserHandleCompat user) {
+ public boolean isDefaultIcon(Bitmap icon, UserHandle user) {
return mDefaultIcons.get(user) == icon;
}
@@ -544,8 +517,11 @@
* Retrieves the entry from the cache. If the entry is not present, it creates a new entry.
* This method is not thread safe, it must be called from a synchronized method.
*/
- private CacheEntry cacheLocked(ComponentName componentName, LauncherActivityInfoCompat info,
- UserHandleCompat user, boolean usePackageIcon, boolean useLowResIcon) {
+ protected CacheEntry cacheLocked(
+ @NonNull ComponentName componentName,
+ @NonNull Provider<LauncherActivityInfo> infoProvider,
+ UserHandle user, boolean usePackageIcon, boolean useLowResIcon) {
+ Preconditions.assertWorkerThread();
ComponentKey cacheKey = new ComponentKey(componentName, user);
CacheEntry entry = mCache.get(cacheKey);
if (entry == null || (entry.isLowResIcon && !useLowResIcon)) {
@@ -553,11 +529,17 @@
mCache.put(cacheKey, entry);
// Check the DB first.
+ LauncherActivityInfo info = null;
+ boolean providerFetchedOnce = false;
+
if (!getEntryFromDB(cacheKey, entry, useLowResIcon) || DEBUG_IGNORE_CACHE) {
+ info = infoProvider.get();
+ providerFetchedOnce = true;
+
if (info != null) {
- entry.icon = Utilities.createBadgedIconBitmap(
- mIconProvider.getIcon(info, mIconDpi), info.getUser(),
- mContext);
+ entry.icon = LauncherIcons.createBadgedIconBitmap(
+ getFullResIcon(info), info.getUser(), mContext,
+ infoProvider.get().getApplicationInfo().targetSdkVersion);
} else {
if (usePackageIcon) {
CacheEntry packageEntry = getEntryForPackageLocked(
@@ -578,19 +560,30 @@
}
}
- if (TextUtils.isEmpty(entry.title) && info != null) {
- entry.title = info.getLabel();
- entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
+ if (TextUtils.isEmpty(entry.title)) {
+ if (info == null && !providerFetchedOnce) {
+ info = infoProvider.get();
+ providerFetchedOnce = true;
+ }
+ if (info != null) {
+ entry.title = info.getLabel();
+ entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
+ }
}
}
return entry;
}
+ public synchronized void clear() {
+ Preconditions.assertWorkerThread();
+ mIconDb.clear();
+ }
+
/**
* Adds a default package entry in the cache. This entry is not persisted and will be removed
* when the cache is flushed.
*/
- public synchronized void cachePackageInstallInfo(String packageName, UserHandleCompat user,
+ public synchronized void cachePackageInstallInfo(String packageName, UserHandle user,
Bitmap icon, CharSequence title) {
removeFromMemCacheLocked(packageName, user);
@@ -606,11 +599,11 @@
entry.title = title;
}
if (icon != null) {
- entry.icon = Utilities.createIconBitmap(icon, mContext);
+ entry.icon = LauncherIcons.createIconBitmap(icon, mContext);
}
}
- private static ComponentKey getPackageKey(String packageName, UserHandleCompat user) {
+ private static ComponentKey getPackageKey(String packageName, UserHandle user) {
ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME);
return new ComponentKey(cn, user);
}
@@ -619,8 +612,9 @@
* Gets an entry for the package, which can be used as a fallback entry for various components.
* This method is not thread safe, it must be called from a synchronized method.
*/
- private CacheEntry getEntryForPackageLocked(String packageName, UserHandleCompat user,
+ private CacheEntry getEntryForPackageLocked(String packageName, UserHandle user,
boolean useLowResIcon) {
+ Preconditions.assertWorkerThread();
ComponentKey cacheKey = getPackageKey(packageName, user);
CacheEntry entry = mCache.get(cacheKey);
@@ -631,7 +625,7 @@
// Check the DB first.
if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
try {
- int flags = UserHandleCompat.myUserHandle().equals(user) ? 0 :
+ int flags = Process.myUserHandle().equals(user) ? 0 :
PackageManager.GET_UNINSTALLED_PACKAGES;
PackageInfo info = mPackageManager.getPackageInfo(packageName, flags);
ApplicationInfo appInfo = info.applicationInfo;
@@ -641,8 +635,8 @@
// Load the full res icon for the application, but if useLowResIcon is set, then
// only keep the low resolution icon instead of the larger full-sized icon
- Bitmap icon = Utilities.createBadgedIconBitmap(
- appInfo.loadIcon(mPackageManager), user, mContext);
+ Bitmap icon = LauncherIcons.createBadgedIconBitmap(
+ appInfo.loadIcon(mPackageManager), user, mContext, appInfo.targetSdkVersion);
Bitmap lowResIcon = generateLowResIcon(icon, mPackageBgColor);
entry.title = appInfo.loadLabel(mPackageManager);
entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
@@ -670,37 +664,6 @@
return entry;
}
- /**
- * Pre-load an icon into the persistent cache.
- *
- * <P>Queries for a component that does not exist in the package manager
- * will be answered by the persistent cache.
- *
- * @param componentName the icon should be returned for this component
- * @param icon the icon to be persisted
- * @param dpi the native density of the icon
- */
- public void preloadIcon(ComponentName componentName, Bitmap icon, int dpi, String label,
- long userSerial, InvariantDeviceProfile idp) {
- // TODO rescale to the correct native DPI
- try {
- PackageManager packageManager = mContext.getPackageManager();
- packageManager.getActivityIcon(componentName);
- // component is present on the system already, do nothing
- return;
- } catch (PackageManager.NameNotFoundException e) {
- // pass
- }
-
- icon = Bitmap.createScaledBitmap(icon, idp.iconBitmapSize, idp.iconBitmapSize, true);
- Bitmap lowResIcon = generateLowResIcon(icon, Color.TRANSPARENT);
- ContentValues values = newContentValues(icon, lowResIcon, label,
- componentName.getPackageName());
- values.put(IconDB.COLUMN_COMPONENT, componentName.flattenToString());
- values.put(IconDB.COLUMN_USER, userSerial);
- mIconDb.insertOrReplace(values);
- }
-
private boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) {
Cursor c = null;
try {
@@ -749,19 +712,19 @@
/**
* A runnable that updates invalid icons and adds missing icons in the DB for the provided
- * LauncherActivityInfoCompat list. Items are updated/added one at a time, so that the
+ * LauncherActivityInfo list. Items are updated/added one at a time, so that the
* worker thread doesn't get blocked.
*/
@Thunk class SerializedIconUpdateTask implements Runnable {
private final long mUserSerial;
private final HashMap<String, PackageInfo> mPkgInfoMap;
- private final Stack<LauncherActivityInfoCompat> mAppsToAdd;
- private final Stack<LauncherActivityInfoCompat> mAppsToUpdate;
+ private final Stack<LauncherActivityInfo> mAppsToAdd;
+ private final Stack<LauncherActivityInfo> mAppsToUpdate;
private final HashSet<String> mUpdatedPackages = new HashSet<String>();
@Thunk SerializedIconUpdateTask(long userSerial, HashMap<String, PackageInfo> pkgInfoMap,
- Stack<LauncherActivityInfoCompat> appsToAdd,
- Stack<LauncherActivityInfoCompat> appsToUpdate) {
+ Stack<LauncherActivityInfo> appsToAdd,
+ Stack<LauncherActivityInfo> appsToUpdate) {
mUserSerial = userSerial;
mPkgInfoMap = pkgInfoMap;
mAppsToAdd = appsToAdd;
@@ -771,31 +734,27 @@
@Override
public void run() {
if (!mAppsToUpdate.isEmpty()) {
- LauncherActivityInfoCompat app = mAppsToUpdate.pop();
+ LauncherActivityInfo app = mAppsToUpdate.pop();
String pkg = app.getComponentName().getPackageName();
PackageInfo info = mPkgInfoMap.get(pkg);
- if (info != null) {
- synchronized (IconCache.this) {
- ContentValues values = updateCacheAndGetContentValues(app, true);
- addIconToDB(values, app.getComponentName(), info, mUserSerial);
- }
- mUpdatedPackages.add(pkg);
- }
+ addIconToDBAndMemCache(app, info, mUserSerial, true /*replace existing*/);
+ mUpdatedPackages.add(pkg);
+
if (mAppsToUpdate.isEmpty() && !mUpdatedPackages.isEmpty()) {
// No more app to update. Notify model.
- LauncherAppState.getInstance().getModel().onPackageIconsUpdated(
+ LauncherAppState.getInstance(mContext).getModel().onPackageIconsUpdated(
mUpdatedPackages, mUserManager.getUserForSerialNumber(mUserSerial));
}
// Let it run one more time.
scheduleNext();
} else if (!mAppsToAdd.isEmpty()) {
- LauncherActivityInfoCompat app = mAppsToAdd.pop();
+ LauncherActivityInfo app = mAppsToAdd.pop();
PackageInfo info = mPkgInfoMap.get(app.getComponentName().getPackageName());
+ // We do not check the mPkgInfoMap when generating the mAppsToAdd. Although every
+ // app should have package info, this is not guaranteed by the api
if (info != null) {
- synchronized (IconCache.this) {
- addIconToDBAndMemCache(app, info, mUserSerial);
- }
+ addIconToDBAndMemCache(app, info, mUserSerial, false /*replace existing*/);
}
if (!mAppsToAdd.isEmpty()) {
@@ -810,7 +769,7 @@
}
private static final class IconDB extends SQLiteCacheHelper {
- private final static int DB_VERSION = 10;
+ private final static int DB_VERSION = 13;
private final static int RELEASE_VERSION = DB_VERSION +
(FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ? 0 : 1);
@@ -891,4 +850,28 @@
return null;
}
}
+
+ private class ActivityInfoProvider extends Provider<LauncherActivityInfo> {
+
+ private final Intent mIntent;
+ private final UserHandle mUser;
+
+ public ActivityInfoProvider(Intent intent, UserHandle user) {
+ mIntent = intent;
+ mUser = user;
+ }
+
+ @Override
+ public LauncherActivityInfo get() {
+ return mLauncherApps.resolveActivity(mIntent, mUser);
+ }
+ }
+
+ /**
+ * Interface for receiving itemInfo with high-res icon.
+ */
+ public interface ItemInfoUpdateReceiver {
+
+ void reapplyItemInfo(ItemInfoWithIcon info);
+ }
}
diff --git a/src/com/android/launcher3/IconProvider.java b/src/com/android/launcher3/IconProvider.java
index 0a273bb..a5d3990 100644
--- a/src/com/android/launcher3/IconProvider.java
+++ b/src/com/android/launcher3/IconProvider.java
@@ -1,13 +1,8 @@
package com.android.launcher3;
-import android.content.Context;
+import android.content.pm.LauncherActivityInfo;
import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
-import android.util.Log;
-import com.android.launcher3.compat.LauncherActivityInfoCompat;
-
-import java.lang.reflect.InvocationTargetException;
import java.util.Locale;
public class IconProvider {
@@ -21,19 +16,6 @@
updateSystemStateString();
}
- public static IconProvider loadByName(String className, Context context) {
- if (TextUtils.isEmpty(className)) return new IconProvider();
- if (DBG) Log.d(TAG, "Loading IconProvider: " + className);
- try {
- Class<?> cls = Class.forName(className);
- return (IconProvider) cls.getDeclaredConstructor(Context.class).newInstance(context);
- } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
- | ClassCastException | NoSuchMethodException | InvocationTargetException e) {
- Log.e(TAG, "Bad IconProvider class", e);
- return new IconProvider();
- }
- }
-
public void updateSystemStateString() {
mSystemState = Locale.getDefault().toString();
}
@@ -43,7 +25,7 @@
}
- public Drawable getIcon(LauncherActivityInfoCompat info, int iconDpi) {
+ public Drawable getIcon(LauncherActivityInfo info, int iconDpi) {
return info.getIcon(iconDpi);
}
}
diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java
index 398c9d2..0608fdd 100644
--- a/src/com/android/launcher3/InfoDropTarget.java
+++ b/src/com/android/launcher3/InfoDropTarget.java
@@ -18,14 +18,16 @@
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
import android.provider.Settings;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.Toast;
import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.util.Themes;
public class InfoDropTarget extends UninstallDropTarget {
@@ -43,13 +45,13 @@
protected void onFinishInflate() {
super.onFinishInflate();
// Get the hover color
- mHoverColor = Utilities.getColorAccent(getContext());
+ mHoverColor = Themes.getColorAccent(getContext());
setDrawable(R.drawable.ic_info_launcher);
}
@Override
- void completeDrop(DragObject d) {
+ public void completeDrop(DragObject d) {
DropTargetResultCallback callback = d.dragSource instanceof DropTargetResultCallback
? (DropTargetResultCallback) d.dragSource : null;
startDetailsActivityForInfo(d.dragInfo, mLauncher, callback);
@@ -60,6 +62,11 @@
*/
public static boolean startDetailsActivityForInfo(
ItemInfo info, Launcher launcher, DropTargetResultCallback callback) {
+ return startDetailsActivityForInfo(info, launcher, callback, null, null);
+ }
+
+ public static boolean startDetailsActivityForInfo(ItemInfo info, Launcher launcher,
+ DropTargetResultCallback callback, Rect sourceBounds, Bundle opts) {
boolean result = false;
ComponentName componentName = null;
if (info instanceof AppInfo) {
@@ -74,7 +81,7 @@
if (componentName != null) {
try {
LauncherAppsCompat.getInstance(launcher)
- .showAppDetailsForProfile(componentName, info.user);
+ .showAppDetailsForProfile(componentName, info.user, sourceBounds, opts);
result = true;
} catch (SecurityException | ActivityNotFoundException e) {
Toast.makeText(launcher, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
@@ -90,17 +97,21 @@
@Override
protected boolean supportsDrop(DragSource source, ItemInfo info) {
- return source.supportsAppInfoDropTarget() && supportsDrop(info);
+ return source.supportsAppInfoDropTarget() && supportsDrop(getContext(), info);
}
- public static boolean supportsDrop(ItemInfo info) {
+ public static boolean supportsDrop(Context context, ItemInfo info) {
// Only show the App Info drop target if developer settings are enabled.
- ContentResolver resolver = LauncherAppState.getInstance().getContext().getContentResolver();
- boolean developmentSettingsEnabled = Settings.Global.getInt(resolver,
+ boolean developmentSettingsEnabled = Settings.Global.getInt(context.getContentResolver(),
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1;
- return developmentSettingsEnabled
- && (info instanceof AppInfo || info instanceof ShortcutInfo
- || info instanceof PendingAddItemInfo || info instanceof LauncherAppWidgetInfo)
- && info.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+ if (!developmentSettingsEnabled) {
+ return false;
+ }
+ return info.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT &&
+ (info instanceof AppInfo ||
+ (info instanceof ShortcutInfo && !((ShortcutInfo) info).isPromise()) ||
+ (info instanceof LauncherAppWidgetInfo &&
+ ((LauncherAppWidgetInfo) info).restoreStatus == 0) ||
+ info instanceof PendingAddItemInfo);
}
}
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index d8e58d8..ce85570 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -16,35 +16,46 @@
package com.android.launcher3;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
+import android.content.pm.LauncherActivityInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
-import com.android.launcher3.compat.LauncherActivityInfoCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.Provider;
import com.android.launcher3.util.Thunk;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONStringer;
-import org.json.JSONTokener;
import java.net.URISyntaxException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
import java.util.Set;
public class InstallShortcutReceiver extends BroadcastReceiver {
@@ -61,6 +72,8 @@
private static final String ICON_RESOURCE_PACKAGE_NAME_KEY = "iconResourcePackage";
private static final String APP_SHORTCUT_TYPE_KEY = "isAppShortcut";
+ private static final String DEEPSHORTCUT_TYPE_KEY = "isDeepShortcut";
+ private static final String APP_WIDGET_TYPE_KEY = "isAppWidget";
private static final String USER_HANDLE_KEY = "userHandle";
// The set of shortcuts that are pending install
@@ -77,11 +90,7 @@
String encoded = info.encodeToString();
if (encoded != null) {
Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
- if (strings == null) {
- strings = new HashSet<String>(1);
- } else {
- strings = new HashSet<String>(strings);
- }
+ strings = (strings != null) ? new HashSet<>(strings) : new HashSet<String>(1);
strings.add(encoded);
sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
}
@@ -89,7 +98,7 @@
}
public static void removeFromInstallQueue(Context context, HashSet<String> packageNames,
- UserHandleCompat user) {
+ UserHandle user) {
if (packageNames.isEmpty()) {
return;
}
@@ -100,32 +109,37 @@
Log.d(TAG, "APPS_PENDING_INSTALL: " + strings
+ ", removing packages: " + packageNames);
}
- if (strings != null) {
- Set<String> newStrings = new HashSet<String>(strings);
- Iterator<String> newStringsIter = newStrings.iterator();
- while (newStringsIter.hasNext()) {
- String encoded = newStringsIter.next();
- PendingInstallShortcutInfo info = decode(encoded, context);
- if (info == null || (packageNames.contains(info.getTargetPackage())
- && user.equals(info.user))) {
+ if (Utilities.isEmpty(strings)) {
+ return;
+ }
+ Set<String> newStrings = new HashSet<>(strings);
+ Iterator<String> 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();
}
+ sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply();
}
}
- private static ArrayList<PendingInstallShortcutInfo> getAndClearInstallQueue(
- SharedPreferences sharedPrefs, Context context) {
+ private static ArrayList<PendingInstallShortcutInfo> getAndClearInstallQueue(Context context) {
+ SharedPreferences sharedPrefs = Utilities.getPrefs(context);
synchronized(sLock) {
+ ArrayList<PendingInstallShortcutInfo> infos = new ArrayList<>();
Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
if (strings == null) {
- return new ArrayList<PendingInstallShortcutInfo>();
+ return infos;
}
- ArrayList<PendingInstallShortcutInfo> infos =
- new ArrayList<PendingInstallShortcutInfo>();
for (String encoded : strings) {
PendingInstallShortcutInfo info = decode(encoded, context);
if (info != null) {
@@ -149,8 +163,8 @@
if (info != null) {
if (!info.isLauncherActivity()) {
// Since its a custom shortcut, verify that it is safe to launch.
- if (!PackageManagerHelper.hasPermissionForActivity(
- context, info.launchIntent, null)) {
+ if (!new PackageManagerHelper(context).hasPermissionForActivity(
+ info.launchIntent, null)) {
// Target cannot be launched, or requires some special permission to launch
Log.e(TAG, "Ignoring malicious intent " + info.launchIntent.toUri(0));
return;
@@ -181,7 +195,8 @@
return null;
}
- PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, context);
+ PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(
+ data, Process.myUserHandle(), context);
if (info.launchIntent == null || info.label == null) {
if (DBG) Log.e(TAG, "Invalid install shortcut intent");
return null;
@@ -192,12 +207,45 @@
public static ShortcutInfo fromShortcutIntent(Context context, Intent data) {
PendingInstallShortcutInfo info = createPendingInfo(context, data);
- return info == null ? null : info.getShortcutInfo();
+ return info == null ? null : (ShortcutInfo) info.getItemInfo();
+ }
+
+ public static void queueShortcut(ShortcutInfoCompat info, Context context) {
+ queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, context), context);
+ }
+
+ public static void queueWidget(AppWidgetProviderInfo info, int widgetId, Context context) {
+ queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, widgetId, context), context);
+ }
+
+ public static void queueActivityInfo(LauncherActivityInfo activity, Context context) {
+ queuePendingShortcutInfo(new PendingInstallShortcutInfo(activity, context), context);
+ }
+
+ public static HashSet<ShortcutKey> getPendingShortcuts(Context context) {
+ HashSet<ShortcutKey> result = new HashSet<>();
+
+ Set<String> strings = Utilities.getPrefs(context).getStringSet(APPS_PENDING_INSTALL, null);
+ if (Utilities.isEmpty(strings)) {
+ return result;
+ }
+
+ for (String encoded : strings) {
+ try {
+ Decoder decoder = new Decoder(encoded, context);
+ if (decoder.optBoolean(DEEPSHORTCUT_TYPE_KEY)) {
+ result.add(ShortcutKey.fromIntent(decoder.launcherIntent, decoder.user));
+ }
+ } catch (JSONException | URISyntaxException e) {
+ Log.d(TAG, "Exception reading shortcut to add: " + e);
+ }
+ }
+ return result;
}
private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) {
// Queue the item up for adding if launcher has not loaded properly yet
- LauncherAppState app = LauncherAppState.getInstance();
+ LauncherAppState app = LauncherAppState.getInstance(context);
boolean launcherNotLoaded = app.getModel().getCallback() == null;
addToInstallQueue(Utilities.getPrefs(context), info);
@@ -213,35 +261,12 @@
mUseInstallQueue = false;
flushInstallQueue(context);
}
+
static void flushInstallQueue(Context context) {
- SharedPreferences sp = Utilities.getPrefs(context);
- ArrayList<PendingInstallShortcutInfo> installQueue = getAndClearInstallQueue(sp, context);
- if (!installQueue.isEmpty()) {
- Iterator<PendingInstallShortcutInfo> iter = installQueue.iterator();
- ArrayList<ItemInfo> addShortcuts = new ArrayList<ItemInfo>();
- while (iter.hasNext()) {
- final PendingInstallShortcutInfo pendingInfo = iter.next();
-
- // If the intent specifies a package, make sure the package exists
- String packageName = pendingInfo.getTargetPackage();
- if (!TextUtils.isEmpty(packageName)) {
- UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
- if (!LauncherModel.isValidPackage(context, packageName, myUserHandle)) {
- if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: "
- + pendingInfo.launchIntent);
- continue;
- }
- }
-
- // Generate a shortcut info to add into the model
- addShortcuts.add(pendingInfo.getShortcutInfo());
- }
-
- // Add the new apps to the model and bind them
- if (!addShortcuts.isEmpty()) {
- LauncherAppState app = LauncherAppState.getInstance();
- app.getModel().addAndBindAddedWorkspaceItems(context, addShortcuts);
- }
+ ArrayList<PendingInstallShortcutInfo> items = getAndClearInstallQueue(context);
+ if (!items.isEmpty()) {
+ LauncherAppState.getInstance(context).getModel().addAndBindAddedWorkspaceItems(
+ new LazyShortcutsProvider(context.getApplicationContext(), items));
}
}
@@ -264,43 +289,86 @@
private static class PendingInstallShortcutInfo {
- final LauncherActivityInfoCompat activityInfo;
+ final LauncherActivityInfo activityInfo;
+ final ShortcutInfoCompat shortcutInfo;
+ final AppWidgetProviderInfo providerInfo;
final Intent data;
final Context mContext;
final Intent launchIntent;
final String label;
- final UserHandleCompat user;
+ final UserHandle user;
/**
* Initializes a PendingInstallShortcutInfo received from a different app.
*/
- public PendingInstallShortcutInfo(Intent data, Context context) {
+ public PendingInstallShortcutInfo(Intent data, UserHandle user, Context context) {
+ activityInfo = null;
+ shortcutInfo = null;
+ providerInfo = null;
+
this.data = data;
+ this.user = user;
mContext = context;
launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
- user = UserHandleCompat.myUserHandle();
- activityInfo = null;
+
}
/**
* Initializes a PendingInstallShortcutInfo to represent a launcher target.
*/
- public PendingInstallShortcutInfo(LauncherActivityInfoCompat info, Context context) {
- this.data = null;
- mContext = context;
+ public PendingInstallShortcutInfo(LauncherActivityInfo info, Context context) {
activityInfo = info;
- user = info.getUser();
+ shortcutInfo = null;
+ providerInfo = null;
- launchIntent = AppInfo.makeLaunchIntent(context, info, user);
+ data = null;
+ user = info.getUser();
+ mContext = context;
+
+ launchIntent = AppInfo.makeLaunchIntent(info);
label = info.getLabel().toString();
}
+ /**
+ * Initializes a PendingInstallShortcutInfo to represent a launcher target.
+ */
+ public PendingInstallShortcutInfo(ShortcutInfoCompat info, Context context) {
+ activityInfo = null;
+ shortcutInfo = info;
+ providerInfo = null;
+
+ data = null;
+ mContext = context;
+ user = info.getUserHandle();
+
+ launchIntent = info.makeIntent();
+ label = info.getShortLabel().toString();
+ }
+
+ /**
+ * Initializes a PendingInstallShortcutInfo to represent a launcher target.
+ */
+ public PendingInstallShortcutInfo(
+ AppWidgetProviderInfo info, int widgetId, Context context) {
+ activityInfo = null;
+ shortcutInfo = null;
+ providerInfo = info;
+
+ data = null;
+ mContext = context;
+ user = info.getProfile();
+
+ launchIntent = new Intent().setComponent(info.provider)
+ .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
+ label = info.label;
+ }
+
public String encodeToString() {
- if (activityInfo != null) {
- try {
+ try {
+ if (activityInfo != null) {
// If it a launcher target, we only need component name, and user to
// recreate this.
return new JSONStringer()
@@ -310,30 +378,45 @@
.key(USER_HANDLE_KEY).value(UserManagerCompat.getInstance(mContext)
.getSerialNumberForUser(user))
.endObject().toString();
- } catch (JSONException e) {
- Log.d(TAG, "Exception when adding shortcut: " + e);
- return null;
+ } else if (shortcutInfo != null) {
+ // If it a launcher target, we only need component name, and user to
+ // recreate this.
+ return new JSONStringer()
+ .object()
+ .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
+ .key(DEEPSHORTCUT_TYPE_KEY).value(true)
+ .key(USER_HANDLE_KEY).value(UserManagerCompat.getInstance(mContext)
+ .getSerialNumberForUser(user))
+ .endObject().toString();
+ } else if (providerInfo != null) {
+ // If it a launcher target, we only need component name, and user to
+ // recreate this.
+ return new JSONStringer()
+ .object()
+ .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
+ .key(APP_WIDGET_TYPE_KEY).value(true)
+ .key(USER_HANDLE_KEY).value(UserManagerCompat.getInstance(mContext)
+ .getSerialNumberForUser(user))
+ .endObject().toString();
}
- }
- if (launchIntent.getAction() == null) {
- launchIntent.setAction(Intent.ACTION_VIEW);
- } else if (launchIntent.getAction().equals(Intent.ACTION_MAIN) &&
- launchIntent.getCategories() != null &&
- launchIntent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
- launchIntent.addFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
- }
+ if (launchIntent.getAction() == null) {
+ launchIntent.setAction(Intent.ACTION_VIEW);
+ } else if (launchIntent.getAction().equals(Intent.ACTION_MAIN) &&
+ launchIntent.getCategories() != null &&
+ launchIntent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
+ launchIntent.addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ }
- // This name is only used for comparisons and notifications, so fall back to activity
- // name if not supplied
- String name = ensureValidName(mContext, launchIntent, label).toString();
- Bitmap icon = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
- Intent.ShortcutIconResource iconResource =
- data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
+ // This name is only used for comparisons and notifications, so fall back to activity
+ // name if not supplied
+ String name = ensureValidName(mContext, launchIntent, label).toString();
+ Bitmap icon = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
+ Intent.ShortcutIconResource iconResource =
+ data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
- // Only encode the parameters which are supported by the API.
- try {
+ // Only encode the parameters which are supported by the API.
JSONStringer json = new JSONStringer()
.object()
.key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
@@ -352,57 +435,100 @@
return json.endObject().toString();
} catch (JSONException e) {
Log.d(TAG, "Exception when adding shortcut: " + e);
+ return null;
}
- return null;
}
- public ShortcutInfo getShortcutInfo() {
+ public ItemInfo getItemInfo() {
if (activityInfo != null) {
- return new ShortcutInfo(activityInfo, mContext);
+ AppInfo appInfo = new AppInfo(mContext, activityInfo, user);
+ final LauncherAppState app = LauncherAppState.getInstance(mContext);
+ // Set default values until proper values is loaded.
+ appInfo.title = "";
+ appInfo.iconBitmap = app.getIconCache().getDefaultIcon(user);
+ final ShortcutInfo si = appInfo.makeShortcut();
+ if (Looper.myLooper() == LauncherModel.getWorkerLooper()) {
+ app.getIconCache().getTitleAndIcon(si, activityInfo, false /* useLowResIcon */);
+ } else {
+ app.getModel().updateAndBindShortcutInfo(new Provider<ShortcutInfo>() {
+ @Override
+ public ShortcutInfo get() {
+ app.getIconCache().getTitleAndIcon(
+ si, activityInfo, false /* useLowResIcon */);
+ return si;
+ }
+ });
+ }
+ return si;
+ } else if (shortcutInfo != null) {
+ ShortcutInfo si = new ShortcutInfo(shortcutInfo, mContext);
+ si.iconBitmap = LauncherIcons.createShortcutIcon(shortcutInfo, mContext);
+ return si;
+ } else if (providerInfo != null) {
+ LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo
+ .fromProviderInfo(mContext, providerInfo);
+ LauncherAppWidgetInfo widgetInfo = new LauncherAppWidgetInfo(
+ launchIntent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0),
+ info.provider);
+ InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
+ widgetInfo.minSpanX = info.minSpanX;
+ widgetInfo.minSpanY = info.minSpanY;
+ widgetInfo.spanX = Math.min(info.spanX, idp.numColumns);
+ widgetInfo.spanY = Math.min(info.spanY, idp.numRows);
+ return widgetInfo;
} else {
- return LauncherAppState.getInstance().getModel().infoFromShortcutIntent(mContext, data);
+ return createShortcutInfo(data, LauncherAppState.getInstance(mContext));
}
}
- public String getTargetPackage() {
- String packageName = launchIntent.getPackage();
- if (packageName == null) {
- packageName = launchIntent.getComponent() == null ? null :
- launchIntent.getComponent().getPackageName();
- }
- return packageName;
- }
-
public boolean isLauncherActivity() {
return activityInfo != null;
}
}
+ private static String getIntentPackage(Intent intent) {
+ return intent.getComponent() == null
+ ? intent.getPackage() : intent.getComponent().getPackageName();
+ }
+
private static PendingInstallShortcutInfo decode(String encoded, Context context) {
try {
- JSONObject object = (JSONObject) new JSONTokener(encoded).nextValue();
- Intent launcherIntent = Intent.parseUri(object.getString(LAUNCH_INTENT_KEY), 0);
-
- if (object.optBoolean(APP_SHORTCUT_TYPE_KEY)) {
- // The is an internal launcher target shortcut.
- UserHandleCompat user = UserManagerCompat.getInstance(context)
- .getUserForSerialNumber(object.getLong(USER_HANDLE_KEY));
- if (user == null) {
+ Decoder decoder = new Decoder(encoded, context);
+ if (decoder.optBoolean(APP_SHORTCUT_TYPE_KEY)) {
+ LauncherActivityInfo info = LauncherAppsCompat.getInstance(context)
+ .resolveActivity(decoder.launcherIntent, decoder.user);
+ return info == null ? null : new PendingInstallShortcutInfo(info, context);
+ } else if (decoder.optBoolean(DEEPSHORTCUT_TYPE_KEY)) {
+ DeepShortcutManager sm = DeepShortcutManager.getInstance(context);
+ List<ShortcutInfoCompat> si = sm.queryForFullDetails(
+ decoder.launcherIntent.getPackage(),
+ Arrays.asList(decoder.launcherIntent.getStringExtra(
+ ShortcutInfoCompat.EXTRA_SHORTCUT_ID)),
+ decoder.user);
+ if (si.isEmpty()) {
+ return null;
+ } else {
+ return new PendingInstallShortcutInfo(si.get(0), context);
+ }
+ } else if (decoder.optBoolean(APP_WIDGET_TYPE_KEY)) {
+ int widgetId = decoder.launcherIntent
+ .getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0);
+ AppWidgetProviderInfo info = AppWidgetManager.getInstance(context)
+ .getAppWidgetInfo(widgetId);
+ if (info == null || !info.provider.equals(decoder.launcherIntent.getComponent()) ||
+ !info.getProfile().equals(decoder.user)) {
return null;
}
-
- LauncherActivityInfoCompat info = LauncherAppsCompat.getInstance(context)
- .resolveActivity(launcherIntent, user);
- return info == null ? null : new PendingInstallShortcutInfo(info, context);
+ return new PendingInstallShortcutInfo(info, widgetId, context);
}
Intent data = new Intent();
- data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, launcherIntent);
- data.putExtra(Intent.EXTRA_SHORTCUT_NAME, object.getString(NAME_KEY));
+ data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, decoder.launcherIntent);
+ data.putExtra(Intent.EXTRA_SHORTCUT_NAME, decoder.getString(NAME_KEY));
- String iconBase64 = object.optString(ICON_KEY);
- String iconResourceName = object.optString(ICON_RESOURCE_NAME_KEY);
- String iconResourcePackageName = object.optString(ICON_RESOURCE_PACKAGE_NAME_KEY);
+ String iconBase64 = decoder.optString(ICON_KEY);
+ String iconResourceName = decoder.optString(ICON_RESOURCE_NAME_KEY);
+ String iconResourcePackageName = decoder.optString(ICON_RESOURCE_PACKAGE_NAME_KEY);
if (iconBase64 != null && !iconBase64.isEmpty()) {
byte[] iconArray = Base64.decode(iconBase64, Base64.DEFAULT);
Bitmap b = BitmapFactory.decodeByteArray(iconArray, 0, iconArray.length);
@@ -415,13 +541,29 @@
data.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
}
- return new PendingInstallShortcutInfo(data, context);
+ return new PendingInstallShortcutInfo(data, decoder.user, context);
} catch (JSONException | URISyntaxException e) {
Log.d(TAG, "Exception reading shortcut to add: " + e);
}
return null;
}
+ private static class Decoder extends JSONObject {
+ public final Intent launcherIntent;
+ public final UserHandle user;
+
+ private Decoder(String encoded, Context context) throws JSONException, URISyntaxException {
+ super(encoded);
+ launcherIntent = Intent.parseUri(getString(LAUNCH_INTENT_KEY), 0);
+ user = has(USER_HANDLE_KEY) ? UserManagerCompat.getInstance(context)
+ .getUserForSerialNumber(getLong(USER_HANDLE_KEY))
+ : Process.myUserHandle();
+ if (user == null) {
+ throw new JSONException("Invalid user");
+ }
+ }
+ }
+
/**
* Tries to create a new PendingInstallShortcutInfo which represents the same target,
* but is an app target and not a shortcut.
@@ -433,22 +575,90 @@
// Already an activity target
return original;
}
- if (!Utilities.isLauncherAppTarget(original.launchIntent)
- || !original.user.equals(UserHandleCompat.myUserHandle())) {
- // We can only convert shortcuts which point to a main activity in the current user.
+ if (!Utilities.isLauncherAppTarget(original.launchIntent)) {
return original;
}
- PackageManager pm = original.mContext.getPackageManager();
- ResolveInfo info = pm.resolveActivity(original.launchIntent, 0);
-
+ LauncherActivityInfo info = LauncherAppsCompat.getInstance(original.mContext)
+ .resolveActivity(original.launchIntent, original.user);
if (info == null) {
return original;
}
-
// Ignore any conflicts in the label name, as that can change based on locale.
- LauncherActivityInfoCompat launcherInfo = LauncherActivityInfoCompat
- .fromResolveInfo(info, original.mContext);
- return new PendingInstallShortcutInfo(launcherInfo, original.mContext);
+ return new PendingInstallShortcutInfo(info, original.mContext);
}
+
+ private static class LazyShortcutsProvider extends Provider<List<ItemInfo>> {
+
+ private final Context mContext;
+ private final ArrayList<PendingInstallShortcutInfo> mPendingItems;
+
+ public LazyShortcutsProvider(Context context, ArrayList<PendingInstallShortcutInfo> 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<ItemInfo> get() {
+ Preconditions.assertNonUiThread();
+ ArrayList<ItemInfo> 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);
+ Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
+
+ if (intent == null) {
+ // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
+ Log.e(TAG, "Can't construct ShorcutInfo with null intent");
+ return null;
+ }
+
+ final ShortcutInfo info = new ShortcutInfo();
+
+ // Only support intents for current user for now. Intents sent from other
+ // users wouldn't get here without intent forwarding anyway.
+ info.user = Process.myUserHandle();
+
+ if (bitmap instanceof Bitmap) {
+ info.iconBitmap = LauncherIcons.createIconBitmap((Bitmap) bitmap, app.getContext());
+ } else {
+ Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
+ if (extra instanceof Intent.ShortcutIconResource) {
+ info.iconResource = (Intent.ShortcutIconResource) extra;
+ info.iconBitmap = LauncherIcons.createIconBitmap(info.iconResource, app.getContext());
+ }
+ }
+ if (info.iconBitmap == null) {
+ info.iconBitmap = app.getIconCache().getDefaultIcon(info.user);
+ }
+
+ info.title = Utilities.trim(name);
+ info.contentDescription = UserManagerCompat.getInstance(app.getContext())
+ .getBadgedLabelForUser(info.title, info.user);
+ info.intent = intent;
+ return info;
+ }
+
}
diff --git a/src/com/android/launcher3/InterruptibleInOutAnimator.java b/src/com/android/launcher3/InterruptibleInOutAnimator.java
index 29df38b..8501e24 100644
--- a/src/com/android/launcher3/InterruptibleInOutAnimator.java
+++ b/src/com/android/launcher3/InterruptibleInOutAnimator.java
@@ -48,7 +48,7 @@
@Thunk int mDirection = STOPPED;
public InterruptibleInOutAnimator(View view, long duration, float fromValue, float toValue) {
- mAnimator = LauncherAnimUtils.ofFloat(view, fromValue, toValue).setDuration(duration);
+ mAnimator = LauncherAnimUtils.ofFloat(fromValue, toValue).setDuration(duration);
mOriginalDuration = duration;
mOriginalFromValue = fromValue;
mOriginalToValue = toValue;
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 38545e2..9e214d1 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -18,6 +18,7 @@
import android.annotation.TargetApi;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.Point;
@@ -85,10 +86,11 @@
*/
public int numHotseatIcons;
float hotseatIconSize;
+ public float hotseatScale;
int defaultLayoutId;
- DeviceProfile landscapeProfile;
- DeviceProfile portraitProfile;
+ public DeviceProfile landscapeProfile;
+ public DeviceProfile portraitProfile;
public Point defaultWallpaperSize;
@@ -117,6 +119,8 @@
numHotseatIcons = hs;
hotseatIconSize = his;
defaultLayoutId = dlId;
+
+ hotseatScale = hotseatIconSize / iconSize;
}
@TargetApi(23)
@@ -158,6 +162,8 @@
// Supported overrides: numRows, numColumns, iconSize
applyPartnerDeviceProfileOverrides(context, dm);
+ hotseatScale = hotseatIconSize / iconSize;
+
Point realSize = new Point();
display.getRealSize(realSize);
// The real size never changes. smallSide and largeSide will remain the
@@ -321,6 +327,11 @@
return rank == getAllAppsButtonRank();
}
+ public DeviceProfile getDeviceProfile(Context context) {
+ return context.getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE ? landscapeProfile : portraitProfile;
+ }
+
private float weight(float x0, float y0, float x1, float y1, float pow) {
float d = dist(x0, y0, x1, y1);
if (Float.compare(d, 0f) == 0) {
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index c0c22a3..0779a3d 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -18,23 +18,17 @@
import android.content.ComponentName;
import android.content.ContentValues;
-import android.content.Context;
import android.content.Intent;
-import android.graphics.Bitmap;
+import android.os.Process;
+import android.os.UserHandle;
-import com.android.launcher3.compat.UserHandleCompat;
-import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.util.ContentWriter;
/**
* Represents an item in the launcher.
*/
public class ItemInfo {
- /**
- * Intent extra to store the profile. Format: UserHandle
- */
- public static final String EXTRA_PROFILE = "profile";
-
public static final int NO_ID = -1;
/**
@@ -59,7 +53,7 @@
public long container = NO_ID;
/**
- * Iindicates the screen in which the shortcut appears.
+ * Indicates the screen in which the shortcut appears.
*/
public long screenId = -1;
@@ -108,10 +102,10 @@
*/
public CharSequence contentDescription;
- public UserHandleCompat user;
+ public UserHandle user;
public ItemInfo() {
- user = UserHandleCompat.myUserHandle();
+ user = Process.myUserHandle();
}
ItemInfo(ItemInfo info) {
@@ -142,15 +136,15 @@
return getIntent() == null ? null : getIntent().getComponent();
}
- public void writeToValues(ContentValues values) {
- values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType);
- values.put(LauncherSettings.Favorites.CONTAINER, container);
- values.put(LauncherSettings.Favorites.SCREEN, screenId);
- values.put(LauncherSettings.Favorites.CELLX, cellX);
- values.put(LauncherSettings.Favorites.CELLY, cellY);
- values.put(LauncherSettings.Favorites.SPANX, spanX);
- values.put(LauncherSettings.Favorites.SPANY, spanY);
- values.put(LauncherSettings.Favorites.RANK, rank);
+ public void writeToValues(ContentWriter writer) {
+ writer.put(LauncherSettings.Favorites.ITEM_TYPE, itemType)
+ .put(LauncherSettings.Favorites.CONTAINER, container)
+ .put(LauncherSettings.Favorites.SCREEN, screenId)
+ .put(LauncherSettings.Favorites.CELLX, cellX)
+ .put(LauncherSettings.Favorites.CELLY, cellY)
+ .put(LauncherSettings.Favorites.SPANX, spanX)
+ .put(LauncherSettings.Favorites.SPANY, spanY)
+ .put(LauncherSettings.Favorites.RANK, rank);
}
public void readFromValues(ContentValues values) {
@@ -166,26 +160,15 @@
/**
* Write the fields of this item to the DB
- *
- * @param context A context object to use for getting UserManagerCompat
- * @param values
*/
- void onAddToDatabase(Context context, ContentValues values) {
- writeToValues(values);
- long serialNumber = UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
- values.put(LauncherSettings.Favorites.PROFILE_ID, serialNumber);
-
+ public void onAddToDatabase(ContentWriter writer) {
if (screenId == Workspace.EXTRA_EMPTY_SCREEN_ID) {
// We should never persist an item on the extra empty screen.
throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
}
- }
- static void writeBitmap(ContentValues values, Bitmap bitmap) {
- if (bitmap != null) {
- byte[] data = Utilities.flattenBitmap(bitmap);
- values.put(LauncherSettings.Favorites.ICON, data);
- }
+ writeToValues(writer);
+ writer.put(LauncherSettings.Favorites.PROFILE_ID, user);
}
@Override
diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java
new file mode 100644
index 0000000..1e020e2
--- /dev/null
+++ b/src/com/android/launcher3/ItemInfoWithIcon.java
@@ -0,0 +1,43 @@
+/*
+ * 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 android.graphics.Bitmap;
+
+/**
+ * Represents an ItemInfo which also holds an icon.
+ */
+public abstract class ItemInfoWithIcon extends ItemInfo {
+
+ /**
+ * A bitmap version of the application icon.
+ */
+ public Bitmap iconBitmap;
+
+ /**
+ * Indicates whether we're using a low res icon
+ */
+ public boolean usingLowResIcon;
+
+ protected ItemInfoWithIcon() { }
+
+ protected ItemInfoWithIcon(ItemInfoWithIcon info) {
+ super(info);
+ iconBitmap = info.iconBitmap;
+ usingLowResIcon = info.usingLowResIcon;
+ }
+}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 4672e08..eef578d 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -18,19 +18,15 @@
import android.Manifest;
import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
-import android.app.Activity;
import android.app.ActivityOptions;
import android.app.AlertDialog;
import android.app.SearchManager;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
@@ -45,22 +41,20 @@
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
-import android.content.res.Configuration;
import android.database.sqlite.SQLiteDatabase;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.PorterDuff;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
-import android.os.Message;
+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;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
@@ -69,20 +63,19 @@
import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
+import android.view.KeyboardShortcutInfo;
import android.view.Menu;
import android.view.MotionEvent;
-import android.view.Surface;
import android.view.View;
-import android.view.View.OnClickListener;
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.Advanceable;
-import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
@@ -92,39 +85,50 @@
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DefaultAppSearchController;
+import com.android.launcher3.anim.AnimationLayerSet;
import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.LauncherActivityInfoCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.UserHandleCompat;
-import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.compat.PinItemRequestCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dragndrop.PinItemDragListener;
import com.android.launcher3.dynamicui.ExtractedColors;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.keyboard.CustomActionsPopup;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.model.WidgetsModel;
+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.PopupContainerWithArrow;
+import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.shortcuts.DeepShortcutsContainer;
import com.android.launcher3.shortcuts.ShortcutKey;
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.util.ActivityResultInfo;
import com.android.launcher3.util.ComponentKey;
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.TestingUtils;
import com.android.launcher3.util.Thunk;
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;
@@ -132,15 +136,15 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Default launcher application.
*/
-public class Launcher extends Activity
+public class Launcher extends BaseActivity
implements LauncherExterns, View.OnClickListener, OnLongClickListener,
LauncherModel.Callbacks, View.OnTouchListener, LauncherProviderChangeListener,
AccessibilityManager.AccessibilityStateChangeListener {
@@ -170,17 +174,15 @@
*/
protected static final int REQUEST_LAST = 100;
- // To turn on these properties, type
- // adb shell setprop logTap.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
- static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
+ 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";
- public static final String ACTION_APPWIDGET_HOST_RESET =
- "com.android.launcher3.intent.ACTION_APPWIDGET_HOST_RESET";
-
// Type: int
private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
// Type: int
@@ -201,7 +203,7 @@
private boolean mIsSafeModeEnabled;
- static final int APPWIDGET_HOST_ID = 1024;
+ public static final int APPWIDGET_HOST_ID = 1024;
public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 500;
private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
private static final int ACTIVITY_START_DELAY = 1000;
@@ -211,18 +213,6 @@
private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
@Thunk static int NEW_APPS_ANIMATION_DELAY = 500;
- private final BroadcastReceiver mUiBroadcastReceiver = new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (ACTION_APPWIDGET_HOST_RESET.equals(intent.getAction())) {
- if (mAppWidgetHost != null) {
- mAppWidgetHost.startListening();
- }
- }
- }
- };
-
@Thunk Workspace mWorkspace;
private View mLauncherView;
@Thunk DragLayer mDragLayer;
@@ -250,9 +240,8 @@
// Main container view and the model for the widget tray screen.
@Thunk WidgetsContainerView mWidgetsView;
- @Thunk WidgetsModel mWidgetsModel;
+ @Thunk MultiHashMap<PackageItemInfo, WidgetItem> mAllWidgets;
- private Bundle mSavedState;
// 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.
@@ -270,30 +259,19 @@
private ViewOnDrawExecutor mPendingExecutor;
private LauncherModel mModel;
+ private ModelWriter mModelWriter;
private IconCache mIconCache;
private ExtractedColors mExtractedColors;
private LauncherAccessibilityDelegate mAccessibilityDelegate;
+ private Handler mHandler = new Handler();
private boolean mIsResumeFromActionScreenOff;
- @Thunk boolean mUserPresent = true;
- private boolean mVisible;
- private boolean mHasFocus;
- private boolean mAttached;
+ private boolean mHasFocus = false;
+ private boolean mAttached = false;
- /** Maps launcher activity components to their list of shortcut ids. */
- private MultiHashMap<ComponentKey, String> mDeepShortcutMap = new MultiHashMap<>();
+ private PopupDataProvider mPopupDataProvider;
private View.OnTouchListener mHapticFeedbackTouchListener;
- // Related to the auto-advancing of widgets
- private final int ADVANCE_MSG = 1;
- private static final int ADVANCE_INTERVAL = 20000;
- private static final int ADVANCE_STAGGER = 250;
-
- private boolean mAutoAdvanceRunning = false;
- private long mAutoAdvanceSentTime;
- private long mAutoAdvanceTimeLeft = -1;
- @Thunk HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance = new HashMap<>();
-
// 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;
@@ -304,15 +282,6 @@
// it from the context.
private SharedPreferences mSharedPrefs;
- // Holds the page that we need to animate to, and the icon views that we need to animate up
- // when we scroll to that page on resume.
- @Thunk ImageView mFolderIconImageView;
- private Bitmap mFolderIconBitmap;
- private Canvas mFolderIconCanvas;
- private Rect mRectForFolderAnimation = new Rect();
-
- private DeviceProfile mDeviceProfile;
-
private boolean mMoveToDefaultScreenFromNewIntent;
// This is set to the view that launched the activity that navigated the user away from
@@ -350,7 +319,7 @@
*/
private PendingRequestArgs mPendingRequestArgs;
- private UserEventDispatcher mUserEventDispatcher;
+ private float mLastDispatchTouchEventX = 0.0f;
public ViewGroupFocusHelper mFocusHandler;
private boolean mRotationEnabled = false;
@@ -392,17 +361,21 @@
super.onCreate(savedInstanceState);
- LauncherAppState app = LauncherAppState.getInstance();
+ LauncherAppState app = LauncherAppState.getInstance(this);
// Load configuration-specific DeviceProfile
- mDeviceProfile = getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE ?
- app.getInvariantDeviceProfile().landscapeProfile
- : app.getInvariantDeviceProfile().portraitProfile;
+ mDeviceProfile = app.getInvariantDeviceProfile().getDeviceProfile(this);
+ if (isInMultiWindowModeCompat()) {
+ Display display = getWindowManager().getDefaultDisplay();
+ Point mwSize = new Point();
+ display.getSize(mwSize);
+ mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize);
+ }
mSharedPrefs = Utilities.getPrefs(this);
mIsSafeModeEnabled = getPackageManager().isSafeMode();
mModel = app.setLauncher(this);
+ mModelWriter = mModel.getWriter(mDeviceProfile.isVerticalBarLayout());
mIconCache = app.getIconCache();
mAccessibilityDelegate = new LauncherAccessibilityDelegate(this);
@@ -420,20 +393,21 @@
// LauncherModel load.
mPaused = false;
- setContentView(R.layout.launcher);
+ mLauncherView = getLayoutInflater().inflate(R.layout.launcher, null);
setupViews();
mDeviceProfile.layout(this, false /* notifyListeners */);
mExtractedColors = new ExtractedColors();
loadExtractedColorsAndColorItems();
+ mPopupDataProvider = new PopupDataProvider(this);
+
((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
.addAccessibilityStateChangeListener(this);
lockAllApps();
- mSavedState = savedInstanceState;
- restoreState(mSavedState);
+ restoreState(savedInstanceState);
if (LauncherAppState.PROFILE_STARTUP) {
Trace.endSection();
@@ -441,11 +415,18 @@
// We only load the page synchronously if the user rotates (or triggers a
// configuration change) while launcher is in the foreground
- if (!mModel.startLoader(mWorkspace.getRestorePage())) {
+ int currentScreen = PagedView.INVALID_RESTORE_PAGE;
+ if (savedInstanceState != null) {
+ currentScreen = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN, currentScreen);
+ }
+ if (!mModel.startLoader(currentScreen)) {
// If we are not binding synchronously, show a fade in animation when
// the first page bind completes.
mDragLayer.setAlpha(0);
} else {
+ // Pages bound synchronously.
+ mWorkspace.setCurrentPage(currentScreen);
+
setWorkspaceLoading(true);
}
@@ -453,9 +434,6 @@
mDefaultKeySsb = new SpannableStringBuilder();
Selection.setSelection(mDefaultKeySsb, 0);
- IntentFilter filter = new IntentFilter(ACTION_APPWIDGET_HOST_RESET);
- registerReceiver(mUiBroadcastReceiver, filter);
-
mRotationEnabled = getResources().getBoolean(R.bool.allow_rotation);
// In case we are on a device with locked rotation, we should look at preferences to check
// if the user has specifically allowed rotation.
@@ -465,47 +443,82 @@
mSharedPrefs.registerOnSharedPreferenceChangeListener(mRotationPrefChangeHandler);
}
+ if (PinItemDragListener.handleDragRequest(this, getIntent())) {
+ // Temporarily enable the rotation
+ mRotationEnabled = true;
+ }
+
// On large interfaces, or on devices that a user has specifically enabled screen rotation,
// we want the screen to auto-rotate based on the current orientation
setOrientation();
+ setContentView(mLauncherView);
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onCreate(savedInstanceState);
}
}
@Override
+ public View findViewById(int id) {
+ return mLauncherView.findViewById(id);
+ }
+
+ @Override
public void onExtractedColorsChanged() {
loadExtractedColorsAndColorItems();
}
- private void loadExtractedColorsAndColorItems() {
- // TODO: do this in pre-N as well, once the extraction part is complete.
- if (Utilities.isNycOrAbove()) {
- mExtractedColors.load(this);
- mHotseat.updateColor(mExtractedColors, !mPaused);
- mWorkspace.getPageIndicator().updateColor(mExtractedColors);
- // It's possible that All Apps is visible when this is run,
- // so always use light status bar in that case.
- activateLightStatusBar(isAllAppsVisible());
+ @Override
+ public void onAppWidgetHostReset() {
+ if (mAppWidgetHost != null) {
+ mAppWidgetHost.startListening();
}
}
+ 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);
+ boolean lightStatusBar = (FeatureFlags.LIGHT_STATUS_BAR
+ && mExtractedColors.getColor(ExtractedColors.STATUS_BAR_INDEX,
+ ExtractedColors.DEFAULT_DARK) == ExtractedColors.DEFAULT_LIGHT);
+ // It's possible that All Apps is visible when this is run,
+ // so always use light status bar in that case. Only change nav bar color to status bar
+ // color when All Apps is visible.
+ activateLightSystemBars(lightStatusBar || isAllAppsVisible(), true, isAllAppsVisible());
+ }
+ }
+
+ // TODO: use platform flag on API >= 26
+ private static final int SYSTEM_UI_FLAG_LIGHT_NAV_BAR = 0x10;
+
/**
- * Sets the status bar to be light or not. Light status bar means dark icons.
- * @param activate if true, make sure the status bar is light, otherwise base on wallpaper.
+ * Sets the status and/or nav bar to be light or not. Light status bar means dark icons.
+ * @param isLight make sure the system bar is light.
+ * @param statusBar if true, make the status bar theme match the isLight param.
+ * @param navBar if true, make the nav bar theme match the isLight param.
*/
- public void activateLightStatusBar(boolean activate) {
- boolean lightStatusBar = activate || (FeatureFlags.LIGHT_STATUS_BAR
- && mExtractedColors.getColor(ExtractedColors.STATUS_BAR_INDEX,
- ExtractedColors.DEFAULT_DARK) == ExtractedColors.DEFAULT_LIGHT);
+ public void activateLightSystemBars(boolean isLight, boolean statusBar, boolean navBar) {
int oldSystemUiFlags = getWindow().getDecorView().getSystemUiVisibility();
int newSystemUiFlags = oldSystemUiFlags;
- if (lightStatusBar) {
- newSystemUiFlags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+ if (isLight) {
+ if (statusBar) {
+ newSystemUiFlags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+ }
+ if (navBar && Utilities.isAtLeastO()) {
+ newSystemUiFlags |= SYSTEM_UI_FLAG_LIGHT_NAV_BAR;
+ }
} else {
- newSystemUiFlags &= ~(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+ if (statusBar) {
+ newSystemUiFlags &= ~(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+ }
+ if (navBar && Utilities.isAtLeastO()) {
+ newSystemUiFlags &= ~(SYSTEM_UI_FLAG_LIGHT_NAV_BAR);
+ }
}
+
if (newSystemUiFlags != oldSystemUiFlags) {
getWindow().getDecorView().setSystemUiVisibility(newSystemUiFlags);
}
@@ -582,7 +595,7 @@
}
@Override
- public void onLauncherProviderChange() {
+ public void onLauncherProviderChanged() {
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onLauncherProviderChange();
}
@@ -608,7 +621,7 @@
}
/**
- * Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to
+ * 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() {
@@ -626,23 +639,6 @@
}
}
- public UserEventDispatcher getUserEventDispatcher() {
- if (mLauncherCallbacks != null) {
- UserEventDispatcher dispatcher = mLauncherCallbacks.getUserEventDispatcher();
- if (dispatcher != null) {
- return dispatcher;
- }
- }
-
- // Logger object is a singleton and does not have to be coupled with the foreground
- // activity. Since most user event logging is done on the UI, the object is retrieved
- // from the callback for convenience.
- if (mUserEventDispatcher == null) {
- mUserEventDispatcher = new UserEventDispatcher();
- }
- return mUserEventDispatcher;
- }
-
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().
@@ -657,6 +653,10 @@
return (int) info.id;
}
+ public PopupDataProvider getPopupDataProvider() {
+ return mPopupDataProvider;
+ }
+
/**
* Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
* a configuration step, this allows the proper animations to run after other transitions.
@@ -688,8 +688,9 @@
// Since the view was just bound, also launch the configure activity if needed
LauncherAppWidgetProviderInfo provider = mAppWidgetManager
.getLauncherAppWidgetInfo(widgetId);
- if (provider != null && provider.configure != null) {
- startRestoredWidgetReconfigActivity(provider, widgetInfo);
+ if (provider != null) {
+ new WidgetAddFlowHandler(provider)
+ .startConfigActivity(this, widgetInfo, REQUEST_RECONFIGURE_APPWIDGET);
}
}
break;
@@ -736,7 +737,7 @@
} else if (resultCode == RESULT_OK) {
addAppWidgetImpl(
appWidgetId, requestArgs, null,
- requestArgs.getWidgetProvider(),
+ requestArgs.getWidgetHandler(),
ON_ACTIVITY_RESULT_ANIMATION_DELAY);
}
return;
@@ -895,7 +896,7 @@
if (resultCode == RESULT_OK) {
animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId,
- requestArgs.getWidgetProvider());
+ requestArgs.getWidgetHandler().getProviderInfo(this));
boundWidget = layout;
onCompleteRunnable = new Runnable() {
@Override
@@ -928,9 +929,11 @@
mLauncherCallbacks.onStop();
}
- if (Utilities.isNycMR1OrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT_MR1) {
mAppWidgetHost.stopListening();
}
+
+ NotificationListener.removeNotificationsChangedListener();
}
@Override
@@ -942,9 +945,13 @@
mLauncherCallbacks.onStart();
}
- if (Utilities.isNycMR1OrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT_MR1) {
mAppWidgetHost.startListening();
}
+
+ if (!isWorkspaceLoading()) {
+ NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
+ }
}
@Override
@@ -1059,6 +1066,7 @@
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onResume();
}
+
}
@Override
@@ -1151,9 +1159,10 @@
if (mLauncherCallbacks != null) {
return mLauncherCallbacks.hasSettings();
} else {
- // On devices with a locked orientation, we will at least have the allow rotation
- // setting.
- return !getResources().getBoolean(R.bool.allow_rotation);
+ // On O and above we there is always some setting present settings (add icon to
+ // home screen or icon badging). On earlier APIs we will have the allow rotation
+ // setting, on devices with a locked orientation,
+ return Utilities.isAtLeastO() || !getResources().getBoolean(R.bool.allow_rotation);
}
}
@@ -1227,11 +1236,8 @@
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 folders
- closeFolder();
-
- // Close any shortcuts containers
- closeShortcutsContainer();
+ // Close any open floating view
+ AbstractFloatingView.closeAllOpenViews(this);
// Stop resizing any widgets
mWorkspace.exitWidgetResizeMode();
@@ -1260,22 +1266,6 @@
}
/**
- * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
- * State
- */
- private static State intToState(int stateOrdinal) {
- State state = State.WORKSPACE;
- final State[] stateValues = State.values();
- for (int i = 0; i < stateValues.length; i++) {
- if (stateValues[i].ordinal() == stateOrdinal) {
- state = stateValues[i];
- break;
- }
- }
- return state;
- }
-
- /**
* Restores the previous state, if it exists.
*
* @param savedState The previous state.
@@ -1285,17 +1275,14 @@
return;
}
- State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
+ 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 currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN,
- PagedView.INVALID_RESTORE_PAGE);
- if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
- mWorkspace.setRestorePage(currentScreen);
- }
-
PendingRequestArgs requestArgs = savedState.getParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS);
if (requestArgs != null) {
setWaitingForResult(requestArgs);
@@ -1308,7 +1295,6 @@
* Finds all the views we need and configure them properly.
*/
private void setupViews() {
- mLauncherView = findViewById(R.id.launcher);
mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
mFocusHandler = mDragLayer.getFocusIndicatorHelper();
mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
@@ -1355,8 +1341,6 @@
}
// Setup the drag controller (drop targets have to be added in reverse order in priority)
- mDragController.setDragScoller(mWorkspace);
- mDragController.setScrollView(mDragLayer);
mDragController.setMoveTarget(mWorkspace);
mDragController.addDropTarget(mWorkspace);
mDropTargetBar.setup(mDragController);
@@ -1373,53 +1357,36 @@
private void setupOverviewPanel() {
mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel);
- // Long-clicking buttons in the overview panel does the same thing as clicking them.
- OnLongClickListener performClickOnLongClick = new OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- return v.performClick();
- }
- };
-
// Bind wallpaper button actions
View wallpaperButton = findViewById(R.id.wallpaper_button);
- wallpaperButton.setOnClickListener(new OnClickListener() {
+ new OverviewButtonClickListener(ControlType.WALLPAPER_BUTTON) {
@Override
- public void onClick(View view) {
- if (!mWorkspace.isSwitchingState()) {
- onClickWallpaperPicker(view);
- }
+ public void handleViewClick(View view) {
+ onClickWallpaperPicker(view);
}
- });
- wallpaperButton.setOnLongClickListener(performClickOnLongClick);
+ }.attachTo(wallpaperButton);
wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
// Bind widget button actions
mWidgetsButton = findViewById(R.id.widget_button);
- mWidgetsButton.setOnClickListener(new OnClickListener() {
+ new OverviewButtonClickListener(ControlType.WIDGETS_BUTTON) {
@Override
- public void onClick(View view) {
- if (!mWorkspace.isSwitchingState()) {
- onClickAddWidgetButton(view);
- }
+ public void handleViewClick(View view) {
+ onClickAddWidgetButton(view);
}
- });
- mWidgetsButton.setOnLongClickListener(performClickOnLongClick);
+ }.attachTo(mWidgetsButton);
mWidgetsButton.setOnTouchListener(getHapticFeedbackTouchListener());
// Bind settings actions
View settingsButton = findViewById(R.id.settings_button);
boolean hasSettings = hasSettings();
if (hasSettings) {
- settingsButton.setOnClickListener(new OnClickListener() {
+ new OverviewButtonClickListener(ControlType.SETTINGS_BUTTON) {
@Override
- public void onClick(View view) {
- if (!mWorkspace.isSwitchingState()) {
- onClickSettingsButton(view);
- }
+ public void handleViewClick(View view) {
+ onClickSettingsButton(view);
}
- });
- settingsButton.setOnLongClickListener(performClickOnLongClick);
+ }.attachTo(settingsButton);
settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
} else {
settingsButton.setVisibility(View.GONE);
@@ -1464,7 +1431,7 @@
public View createShortcut(ViewGroup parent, ShortcutInfo info) {
BubbleTextView favorite = (BubbleTextView) getLayoutInflater().inflate(R.layout.app_icon,
parent, false);
- favorite.applyFromShortcutInfo(info, mIconCache);
+ favorite.applyFromShortcutInfo(info);
favorite.setCompoundDrawablePadding(mDeviceProfile.iconDrawablePaddingPx);
favorite.setOnClickListener(this);
favorite.setOnFocusChangeListener(mFocusHandler);
@@ -1481,19 +1448,34 @@
int[] cellXY = mTmpAddItemCellCoordinates;
CellLayout layout = getCellLayout(container, screenId);
- ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(this, data);
- if (info == null || args.getRequestCode() != REQUEST_CREATE_SHORTCUT ||
+ if (args.getRequestCode() != REQUEST_CREATE_SHORTCUT ||
args.getPendingIntent().getComponent() == null) {
return;
}
- if (!PackageManagerHelper.hasPermissionForActivity(
- this, info.intent, args.getPendingIntent().getComponent().getPackageName())) {
- // The app is trying to add a shortcut without sufficient permissions
- Log.e(TAG, "Ignoring malicious intent " + info.intent.toUri(0));
- return;
- }
- final View view = createShortcut(info);
+ ShortcutInfo info = null;
+ if (Utilities.isAtLeastO()) {
+ info = LauncherAppsCompat.createShortcutInfoFromPinItemRequest(
+ this, PinItemRequestCompat.getPinItemRequest(data), 0);
+ }
+
+ if (info == null) {
+ // Legacy shortcuts are only supported for primary profile.
+ info = Process.myUserHandle().equals(args.user)
+ ? InstallShortcutReceiver.fromShortcutIntent(this, data) : null;
+
+ if (info == null) {
+ Log.e(TAG, "Unable to parse a valid custom shortcut result");
+ return;
+ } else if (!new PackageManagerHelper(this).hasPermissionForActivity(
+ info.intent, args.getPendingIntent().getComponent().getPackageName())) {
+ // The app is trying to add a shortcut without sufficient permissions
+ Log.e(TAG, "Ignoring malicious intent " + info.intent.toUri(0));
+ return;
+ }
+ }
+
+ final View view = createShortcut(info);
boolean foundCellSpan = false;
// First we check if we already know the exact location where we want to add this item.
if (cellX >= 0 && cellY >= 0) {
@@ -1517,14 +1499,12 @@
}
if (!foundCellSpan) {
- showOutOfSpaceMessage(isHotseatLayout(layout));
+ mWorkspace.onNoCellFound(layout);
return;
}
- LauncherModel.addItemToDatabase(this, info, container, screenId, cellXY[0], cellXY[1]);
-
- mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1,
- isWorkspaceLocked());
+ getModelWriter().addItemToDatabase(info, container, screenId, cellXY[0], cellXY[1]);
+ mWorkspace.addInScreen(view, info);
}
/**
@@ -1549,9 +1529,9 @@
launcherInfo.spanY = itemInfo.spanY;
launcherInfo.minSpanX = itemInfo.minSpanX;
launcherInfo.minSpanY = itemInfo.minSpanY;
- launcherInfo.user = mAppWidgetManager.getUser(appWidgetInfo);
+ launcherInfo.user = appWidgetInfo.getUser();
- LauncherModel.addItemToDatabase(this, launcherInfo,
+ getModelWriter().addItemToDatabase(launcherInfo,
itemInfo.container, itemInfo.screenId, itemInfo.cellX, itemInfo.cellY);
if (hostView == null) {
@@ -1559,24 +1539,15 @@
hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
}
hostView.setVisibility(View.VISIBLE);
- addAppWidgetToWorkspace(hostView, launcherInfo, appWidgetInfo, isWorkspaceLocked());
+ prepareAppWidget(hostView, launcherInfo);
+ mWorkspace.addInScreen(hostView, launcherInfo);
}
- private void addAppWidgetToWorkspace(
- AppWidgetHostView hostView, LauncherAppWidgetInfo item,
- LauncherAppWidgetProviderInfo appWidgetInfo, boolean insert) {
+ private void prepareAppWidget(AppWidgetHostView hostView, LauncherAppWidgetInfo item) {
hostView.setTag(item);
item.onBindAppWidget(this, hostView);
-
hostView.setFocusable(true);
hostView.setOnFocusChangeListener(mFocusHandler);
-
- mWorkspace.addInScreen(hostView, item.container, item.screenId,
- item.cellX, item.cellY, item.spanX, item.spanY, insert);
-
- if (!item.isCustomWidget()) {
- addWidgetToAutoAdvanceIfNeeded(hostView, appWidgetInfo);
- }
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -1584,9 +1555,7 @@
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (Intent.ACTION_SCREEN_OFF.equals(action)) {
- mUserPresent = false;
- mDragLayer.clearAllResizeFrames();
- updateAutoAdvanceState();
+ mDragLayer.clearResizeFrame();
// Reset AllApps to its initial state only if we are not in the middle of
// processing a multi-step drop
@@ -1597,13 +1566,28 @@
}
}
mIsResumeFromActionScreenOff = true;
- } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
- mUserPresent = true;
- updateAutoAdvanceState();
}
}
};
+ public void updateIconBadges(final Set<PackageUserKey> updatedBadges) {
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ mWorkspace.updateIconBadges(updatedBadges);
+ mAppsView.updateIconBadges(updatedBadges);
+
+ PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(Launcher.this);
+ if (popup != null) {
+ popup.updateNotificationHeader(updatedBadges);
+ }
+ }
+ };
+ if (!waitUntilResume(r)) {
+ r.run();
+ }
+ }
+
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -1611,11 +1595,9 @@
// Listen for broadcasts related to user-presence
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
- filter.addAction(Intent.ACTION_USER_PRESENT);
registerReceiver(mReceiver, filter);
FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
mAttached = true;
- mVisible = true;
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onAttachedToWindow();
@@ -1625,13 +1607,10 @@
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
- mVisible = false;
-
if (mAttached) {
unregisterReceiver(mReceiver);
mAttached = false;
}
- updateAutoAdvanceState();
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onDetachedFromWindow();
@@ -1639,12 +1618,10 @@
}
public void onWindowVisibilityChanged(int visibility) {
- mVisible = visibility == View.VISIBLE;
- updateAutoAdvanceState();
// 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 (mVisible) {
+ 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
@@ -1679,77 +1656,6 @@
}
}
- @Thunk void sendAdvanceMessage(long delay) {
- mHandler.removeMessages(ADVANCE_MSG);
- Message msg = mHandler.obtainMessage(ADVANCE_MSG);
- mHandler.sendMessageDelayed(msg, delay);
- mAutoAdvanceSentTime = System.currentTimeMillis();
- }
-
- @Thunk void updateAutoAdvanceState() {
- boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty();
- if (autoAdvanceRunning != mAutoAdvanceRunning) {
- mAutoAdvanceRunning = autoAdvanceRunning;
- if (autoAdvanceRunning) {
- long delay = mAutoAdvanceTimeLeft == -1 ? ADVANCE_INTERVAL : mAutoAdvanceTimeLeft;
- sendAdvanceMessage(delay);
- } else {
- if (!mWidgetsToAdvance.isEmpty()) {
- mAutoAdvanceTimeLeft = Math.max(0, ADVANCE_INTERVAL -
- (System.currentTimeMillis() - mAutoAdvanceSentTime));
- }
- mHandler.removeMessages(ADVANCE_MSG);
- mHandler.removeMessages(0); // Remove messages sent using postDelayed()
- }
- }
- }
-
- @Thunk final Handler mHandler = new Handler(new Handler.Callback() {
-
- @Override
- public boolean handleMessage(Message msg) {
- if (msg.what == ADVANCE_MSG) {
- int i = 0;
- for (View key: mWidgetsToAdvance.keySet()) {
- final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
- final int delay = ADVANCE_STAGGER * i;
- if (v instanceof Advanceable) {
- mHandler.postDelayed(new Runnable() {
- public void run() {
- ((Advanceable) v).advance();
- }
- }, delay);
- }
- i++;
- }
- sendAdvanceMessage(ADVANCE_INTERVAL);
- }
- return true;
- }
- });
-
- private void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
- if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return;
- View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
- if (v instanceof Advanceable) {
- mWidgetsToAdvance.put(hostView, appWidgetInfo);
- ((Advanceable) v).fyiWillBeAdvancedByHostKThx();
- updateAutoAdvanceState();
- }
- }
-
- private void removeWidgetToAutoAdvance(View hostView) {
- if (mWidgetsToAdvance.containsKey(hostView)) {
- mWidgetsToAdvance.remove(hostView);
- updateAutoAdvanceState();
- }
- }
-
- public void showOutOfSpaceMessage(boolean isHotseatLayout) {
- int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
- Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
- }
-
public DragLayer getDragLayer() {
return mDragLayer;
}
@@ -1790,21 +1696,14 @@
return mModel;
}
+ public ModelWriter getModelWriter() {
+ return mModelWriter;
+ }
+
public SharedPreferences getSharedPrefs() {
return mSharedPrefs;
}
- public DeviceProfile getDeviceProfile() {
- return mDeviceProfile;
- }
-
- public void closeSystemDialogs() {
- getWindow().closeAllPanels();
-
- // Whatever we were doing is hereby canceled.
- setWaitingForResult(null);
- }
-
@Override
protected void onNewIntent(Intent intent) {
long startTime = 0;
@@ -1819,22 +1718,36 @@
// Check this condition before handling isActionMain, as this will get reset.
boolean shouldMoveToDefaultScreen = alreadyOnHome &&
- mState == State.WORKSPACE && getTopFloatingView() == null;
+ mState == State.WORKSPACE && AbstractFloatingView.getTopOpenView(this) == null;
boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction());
if (isActionMain) {
- // also will cancel mWaitingForResult.
- closeSystemDialogs();
-
if (mWorkspace == null) {
// Can be cases where mWorkspace is null, this prevents a NPE
return;
}
- // In all these cases, only animate if we're already on home
+
+ // 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();
- closeFolder(alreadyOnHome);
- closeShortcutsContainer(alreadyOnHome);
+ 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);
+ } else if (alreadyOnHome) {
+ ued.logActionCommand(Action.Command.HOME_INTENT,
+ mWorkspace.getState().containerType, mWorkspace.getCurrentPage());
+ }
+
+ // 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,
@@ -1866,6 +1779,7 @@
mLauncherCallbacks.onHomeIntent();
}
}
+ PinItemDragListener.handleDragRequest(this, intent);
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onNewIntent(intent);
@@ -1917,11 +1831,9 @@
super.onSaveInstanceState(outState);
outState.putInt(RUNTIME_STATE, mState.ordinal());
- // We close any open folder since it will not be re-opened, and we need to make sure
- // this state is reflected.
- // TODO: Move folderInfo.isOpened out of the model and make it a UI state.
- closeFolder(false);
- closeShortcutsContainer(false);
+ // 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);
if (mPendingRequestArgs != null) {
outState.putParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS, mPendingRequestArgs);
@@ -1939,9 +1851,6 @@
public void onDestroy() {
super.onDestroy();
- // Remove all pending runnables
- mHandler.removeMessages(ADVANCE_MSG);
- mHandler.removeMessages(0);
mWorkspace.removeCallbacks(mBuildLayersRunnable);
mWorkspace.removeFolderListeners();
@@ -1950,7 +1859,7 @@
// been created. In this case, don't interfere with the new Launcher.
if (mModel.isCurrentCallbacks(this)) {
mModel.stopLoader();
- LauncherAppState.getInstance().setLauncher(null);
+ LauncherAppState.getInstance(this).setLauncher(null);
}
if (mRotationPrefChangeHandler != null) {
@@ -1964,15 +1873,11 @@
}
mAppWidgetHost = null;
- mWidgetsToAdvance.clear();
-
TextKeyListener.getInstance().release();
((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
.removeAccessibilityStateChangeListener(this);
- unregisterReceiver(mUiBroadcastReceiver);
-
LauncherAnimUtils.onDestroyActivity();
if (mLauncherCallbacks != null) {
@@ -2107,7 +2012,7 @@
}
}
- private void setWaitingForResult(PendingRequestArgs args) {
+ public void setWaitingForResult(PendingRequestArgs args) {
boolean isLocked = isWorkspaceLocked();
mPendingRequestArgs = args;
if (isLocked != isWorkspaceLocked()) {
@@ -2122,24 +2027,18 @@
}
void addAppWidgetFromDropImpl(int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget,
- LauncherAppWidgetProviderInfo appWidgetInfo) {
+ WidgetAddFlowHandler addFlowHandler) {
if (LOGD) {
Log.d(TAG, "Adding widget from drop");
}
- addAppWidgetImpl(appWidgetId, info, boundWidget, appWidgetInfo, 0);
+ addAppWidgetImpl(appWidgetId, info, boundWidget, addFlowHandler, 0);
}
void addAppWidgetImpl(int appWidgetId, ItemInfo info,
- AppWidgetHostView boundWidget, LauncherAppWidgetProviderInfo appWidgetInfo,
- int delay) {
- if (appWidgetInfo.configure != null) {
- setWaitingForResult(PendingRequestArgs.forWidgetInfo(appWidgetId, appWidgetInfo, info));
+ AppWidgetHostView boundWidget, WidgetAddFlowHandler addFlowHandler, int delay) {
+ if (!addFlowHandler.startConfigActivity(this, appWidgetId, info, REQUEST_CREATE_APPWIDGET)) {
+ // If the configuration flow was not started, add the widget
- // Launch over to configure widget, if needed
- mAppWidgetManager.startConfigActivity(appWidgetInfo, appWidgetId, this,
- mAppWidgetHost, REQUEST_CREATE_APPWIDGET);
- } else {
- // Otherwise just add it
Runnable onComplete = new Runnable() {
@Override
public void run() {
@@ -2148,14 +2047,14 @@
null);
}
};
- completeAddAppWidget(appWidgetId, info, boundWidget, appWidgetInfo);
+ completeAddAppWidget(appWidgetId, info, boundWidget, addFlowHandler.getProviderInfo(this));
mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
}
}
protected void moveToCustomContentScreen(boolean animate) {
// Close any folders that may be open.
- closeFolder();
+ AbstractFloatingView.closeAllOpenViews(this, animate);
mWorkspace.moveToCustomContentScreen(animate);
}
@@ -2176,7 +2075,7 @@
addAppWidgetFromDrop((PendingAddWidgetInfo) info);
break;
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
- processShortcutFromDrop(info);
+ processShortcutFromDrop((PendingAddShortcutInfo) info);
break;
default:
throw new IllegalStateException("Unknown item type: " + info.itemType);
@@ -2186,10 +2085,12 @@
/**
* Process a shortcut drop.
*/
- private void processShortcutFromDrop(PendingAddItemInfo info) {
+ private void processShortcutFromDrop(PendingAddShortcutInfo info) {
Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setComponent(info.componentName);
setWaitingForResult(PendingRequestArgs.forIntent(REQUEST_CREATE_SHORTCUT, intent, info));
- Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT);
+ if (!info.activityInfo.startConfigActivity(this, REQUEST_CREATE_SHORTCUT)) {
+ handleActivityResult(REQUEST_CREATE_SHORTCUT, RESULT_CANCELED, null);
+ }
}
/**
@@ -2198,6 +2099,7 @@
private void addAppWidgetFromDrop(PendingAddWidgetInfo info) {
AppWidgetHostView hostView = info.boundWidget;
int appWidgetId;
+ WidgetAddFlowHandler addFlowHandler = info.getHandler();
if (hostView != null) {
// In the case where we've prebound the widget, we remove it from the DragLayer
if (LOGD) {
@@ -2206,7 +2108,7 @@
getDragLayer().removeView(hostView);
appWidgetId = hostView.getAppWidgetId();
- addAppWidgetFromDropImpl(appWidgetId, info, hostView, info.info);
+ addAppWidgetFromDropImpl(appWidgetId, info, hostView, addFlowHandler);
// Clear the boundWidget so that it doesn't get destroyed.
info.boundWidget = null;
@@ -2219,17 +2121,9 @@
boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
appWidgetId, info.info, options);
if (success) {
- addAppWidgetFromDropImpl(appWidgetId, info, null, info.info);
+ addAppWidgetFromDropImpl(appWidgetId, info, null, addFlowHandler);
} else {
- setWaitingForResult(PendingRequestArgs.forWidgetInfo(appWidgetId, info.info, info));
- Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName);
- mAppWidgetManager.getUser(info.info)
- .addToIntent(intent, AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);
- // TODO: we need to make sure that this accounts for the options bundle.
- // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
- startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
+ addFlowHandler.startBindFlow(this, appWidgetId, info, REQUEST_BIND_APPWIDGET);
}
}
}
@@ -2240,14 +2134,11 @@
folderInfo.title = getText(R.string.folder_name);
// Update the model
- LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screenId,
- cellX, cellY);
+ getModelWriter().addItemToDatabase(folderInfo, container, screenId, cellX, cellY);
// Create the view
- FolderIcon newFolder =
- FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache);
- mWorkspace.addInScreen(newFolder, container, screenId, cellX, cellY, 1, 1,
- isWorkspaceLocked());
+ FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo);
+ mWorkspace.addInScreen(newFolder, folderInfo);
// Force measure the new folder icon
CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
parent.getShortcutsAndWidgets().measureChild(newFolder);
@@ -2271,7 +2162,7 @@
mWorkspace.removeWorkspaceItem(v);
}
if (deleteFromDb) {
- LauncherModel.deleteItemFromDatabase(this, itemInfo);
+ getModelWriter().deleteItemFromDatabase(itemInfo);
}
} else if (itemInfo instanceof FolderInfo) {
final FolderInfo folderInfo = (FolderInfo) itemInfo;
@@ -2280,12 +2171,11 @@
}
mWorkspace.removeWorkspaceItem(v);
if (deleteFromDb) {
- LauncherModel.deleteFolderAndContentsFromDatabase(this, folderInfo);
+ getModelWriter().deleteFolderAndContentsFromDatabase(folderInfo);
}
} else if (itemInfo instanceof LauncherAppWidgetInfo) {
final LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) itemInfo;
mWorkspace.removeWorkspaceItem(v);
- removeWidgetToAutoAdvance(v);
if (deleteFromDb) {
deleteWidgetInfo(widgetInfo);
}
@@ -2310,30 +2200,12 @@
}
}.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR);
}
- LauncherModel.deleteItemFromDatabase(this, widgetInfo);
+ getModelWriter().deleteItemFromDatabase(widgetInfo);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_HOME:
- return true;
- case KeyEvent.KEYCODE_VOLUME_DOWN:
- if (Utilities.isPropertyEnabled(DUMP_STATE_PROPERTY)) {
- dumpState();
- return true;
- }
- break;
- }
- } else if (event.getAction() == KeyEvent.ACTION_UP) {
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_HOME:
- return true;
- }
- }
-
- return super.dispatchKeyEvent(event);
+ return (event.getKeyCode() == KeyEvent.KEYCODE_HOME) || super.dispatchKeyEvent(event);
}
@Override
@@ -2347,22 +2219,34 @@
return;
}
- if (getOpenShortcutsContainer() != null) {
- closeShortcutsContainer();
+ // Note: There should be at most one log per method call. This is enforced implicitly
+ // by using if-else statements.
+ 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);
- } else if (mWorkspace.getOpenFolder() != null) {
- Folder openFolder = mWorkspace.getOpenFolder();
- if (openFolder.isEditingName()) {
- openFolder.dismissEditingName();
- } else {
- closeFolder();
- }
} else {
+ // TODO: Log this case.
mWorkspace.exitWidgetResizeMode();
// Back button is a no-op here, but give at least some feedback for the button press
@@ -2388,6 +2272,9 @@
if (v instanceof Workspace) {
if (mWorkspace.isInOverviewMode()) {
+ getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Type.TOUCH,
+ LauncherLogProto.Action.Direction.NONE,
+ LauncherLogProto.ContainerType.OVERVIEW, mWorkspace.getCurrentPage());
showWorkspace(true);
}
return;
@@ -2395,7 +2282,11 @@
if (v instanceof CellLayout) {
if (mWorkspace.isInOverviewMode()) {
- mWorkspace.snapToPageFromOverView(mWorkspace.indexOfChild(v));
+ int page = mWorkspace.indexOfChild(v);
+ getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Type.TOUCH,
+ LauncherLogProto.Action.Direction.NONE,
+ LauncherLogProto.ContainerType.OVERVIEW, page);
+ mWorkspace.snapToPageFromOverView(page);
showWorkspace(true);
}
return;
@@ -2436,54 +2327,29 @@
final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
if (v.isReadyForClickSetup()) {
+ LauncherAppWidgetProviderInfo appWidgetInfo =
+ mAppWidgetManager.findProvider(info.providerName, info.user);
+ if (appWidgetInfo == null) {
+ return;
+ }
+ WidgetAddFlowHandler addFlowHandler = new WidgetAddFlowHandler(appWidgetInfo);
+
if (info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
if (!info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
// This should not happen, as we make sure that an Id is allocated during bind.
return;
}
- LauncherAppWidgetProviderInfo appWidgetInfo =
- mAppWidgetManager.findProvider(info.providerName, info.user);
- if (appWidgetInfo != null) {
- setWaitingForResult(PendingRequestArgs
- .forWidgetInfo(info.appWidgetId, appWidgetInfo, info));
-
- Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, info.appWidgetId);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, appWidgetInfo.provider);
- mAppWidgetManager.getUser(appWidgetInfo)
- .addToIntent(intent, AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);
- startActivityForResult(intent, REQUEST_BIND_PENDING_APPWIDGET);
- }
+ addFlowHandler.startBindFlow(this, info.appWidgetId, info,
+ REQUEST_BIND_PENDING_APPWIDGET);
} else {
- LauncherAppWidgetProviderInfo appWidgetInfo =
- mAppWidgetManager.getLauncherAppWidgetInfo(info.appWidgetId);
- if (appWidgetInfo != null) {
- startRestoredWidgetReconfigActivity(appWidgetInfo, info);
- }
+ addFlowHandler.startConfigActivity(this, info, REQUEST_RECONFIGURE_APPWIDGET);
}
- } else if (info.installProgress < 0) {
- // The install has not been queued
- final String packageName = info.providerName.getPackageName();
- showBrokenAppInstallDialog(packageName,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int id) {
- startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
- }
- });
} else {
- // Download has started.
final String packageName = info.providerName.getPackageName();
- startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
+ onClickPendingAppItem(v, packageName, info.installProgress >= 0);
}
}
- private void startRestoredWidgetReconfigActivity(
- LauncherAppWidgetProviderInfo provider, LauncherAppWidgetInfo info) {
- setWaitingForResult(PendingRequestArgs.forWidgetInfo(info.appWidgetId, provider, info));
- mAppWidgetManager.startConfigActivity(provider,
- info.appWidgetId, this, mAppWidgetHost, REQUEST_RECONFIGURE_APPWIDGET);
- }
-
/**
* Event handler for the "grid" button that appears on the home screen, which
* enters all apps mode.
@@ -2493,8 +2359,8 @@
protected void onClickAllAppsButton(View v) {
if (LOGD) Log.d(TAG, "onClickAllAppsButton");
if (!isAppsViewVisible()) {
- getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.TAP,
- LauncherLogProto.ALL_APPS_BUTTON);
+ getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
+ ControlType.ALL_APPS_BUTTON);
showAppsView(true /* animated */, true /* updatePredictedApps */,
false /* focusSearchBar */);
}
@@ -2503,28 +2369,47 @@
protected void onLongClickAllAppsButton(View v) {
if (LOGD) Log.d(TAG, "onLongClickAllAppsButton");
if (!isAppsViewVisible()) {
- getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.LONGPRESS,
- LauncherLogProto.ALL_APPS_BUTTON);
+ getUserEventDispatcher().logActionOnControl(Action.Touch.LONGPRESS,
+ ControlType.ALL_APPS_BUTTON);
showAppsView(true /* animated */,
true /* updatePredictedApps */, true /* focusSearchBar */);
}
}
- private void showBrokenAppInstallDialog(final String packageName,
- DialogInterface.OnClickListener onSearchClickListener) {
+ private void onClickPendingAppItem(final View v, final String packageName,
+ boolean downloadStarted) {
+ if (downloadStarted) {
+ // If the download has started, simply direct to the market app.
+ startMarketIntentForPackage(v, packageName);
+ return;
+ }
new AlertDialog.Builder(this)
.setTitle(R.string.abandoned_promises_title)
.setMessage(R.string.abandoned_promise_explanation)
- .setPositiveButton(R.string.abandoned_search, onSearchClickListener)
+ .setPositiveButton(R.string.abandoned_search, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ startMarketIntentForPackage(v, packageName);
+ }
+ })
.setNeutralButton(R.string.abandoned_clean_this,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
- final UserHandleCompat user = UserHandleCompat.myUserHandle();
+ final UserHandle user = Process.myUserHandle();
mWorkspace.removeAbandonedPromise(packageName, user);
}
})
.create().show();
- return;
+ }
+
+ 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);
+ }
}
/**
@@ -2568,17 +2453,14 @@
}
// Check for abandoned promise
- if ((v instanceof BubbleTextView)
- && shortcut.isPromise()
- && !shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE)) {
- showBrokenAppInstallDialog(
- shortcut.getTargetComponent().getPackageName(),
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int id) {
- startAppShortcutOrInfoActivity(v);
- }
- });
- return;
+ if ((v instanceof BubbleTextView) && shortcut.isPromise()) {
+ String packageName = shortcut.intent.getComponent() != null ?
+ shortcut.intent.getComponent().getPackageName() : shortcut.intent.getPackage();
+ if (!TextUtils.isEmpty(packageName)) {
+ onClickPendingAppItem(v, packageName,
+ shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE));
+ return;
+ }
}
// Start activities
@@ -2592,7 +2474,7 @@
throw new IllegalArgumentException("Input must have a valid intent");
}
boolean success = startActivitySafely(v, intent, item);
- getUserEventDispatcher().logAppLaunch(v, intent);
+ getUserEventDispatcher().logAppLaunch(v, intent); // TODO for discovered apps b/35802115
if (success && v instanceof BubbleTextView) {
mWaitingForResume = (BubbleTextView) v;
@@ -2611,10 +2493,10 @@
throw new IllegalArgumentException("Input must be a FolderIcon");
}
- FolderIcon folderIcon = (FolderIcon) v;
- if (!folderIcon.getFolderInfo().opened && !folderIcon.getFolder().isDestroyed()) {
+ Folder folder = ((FolderIcon) v).getFolder();
+ if (!folder.isOpen() && !folder.isDestroyed()) {
// Open the requested folder
- openFolder(folderIcon);
+ folder.animateOpen();
}
}
@@ -2636,25 +2518,32 @@
* on the home screen.
*/
public void onClickWallpaperPicker(View v) {
- if (!Utilities.isWallapaperAllowed(this)) {
+ if (!Utilities.isWallpaperAllowed(this)) {
Toast.makeText(this, R.string.msg_disabled_by_admin, Toast.LENGTH_SHORT).show();
return;
}
- String pickerPackage = getString(R.string.wallpaper_picker_package);
- if (TextUtils.isEmpty(pickerPackage)) {
- pickerPackage = PackageManagerHelper.getWallpaperPickerPackage(getPackageManager());
- }
-
int pageScroll = mWorkspace.getScrollForPage(mWorkspace.getPageNearestToCenterOfScreen());
float offset = mWorkspace.mWallpaperOffset.wallpaperOffsetForScroll(pageScroll);
-
setWaitingForResult(new PendingRequestArgs(new ItemInfo()));
Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER)
- .setPackage(pickerPackage)
.putExtra(Utilities.EXTRA_WALLPAPER_OFFSET, offset);
+
+ String pickerPackage = getString(R.string.wallpaper_picker_package);
+ boolean hasTargetPackage = TextUtils.isEmpty(pickerPackage);
+ if (!hasTargetPackage) {
+ intent.setPackage(pickerPackage);
+ }
+
intent.setSourceBounds(getViewBounds(v));
- startActivityForResult(intent, REQUEST_PICK_WALLPAPER, getActivityLaunchOptions(v));
+ try {
+ startActivityForResult(intent, REQUEST_PICK_WALLPAPER,
+ // If there is no target package, use the default intent chooser animation
+ hasTargetPackage ? getActivityLaunchOptions(v) : null);
+ } catch (ActivityNotFoundException e) {
+ setWaitingForResult(null);
+ Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+ }
}
/**
@@ -2666,6 +2555,7 @@
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));
}
@@ -2749,7 +2639,7 @@
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
String id = ((ShortcutInfo) info).getDeepShortcutId();
String packageName = intent.getPackage();
- LauncherAppState.getInstance().getShortcutManager().startShortcut(
+ DeepShortcutManager.getInstance(this).startShortcut(
packageName, id, intent.getSourceBounds(), optsBundle, info.user);
} else {
// Could be launching some bookkeeping activity
@@ -2778,7 +2668,8 @@
}
}
- private Bundle getActivityLaunchOptions(View v) {
+ @TargetApi(Build.VERSION_CODES.M)
+ public Bundle getActivityLaunchOptions(View v) {
if (Utilities.ATLEAST_MARSHMALLOW) {
int left = 0, top = 0;
int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
@@ -2804,7 +2695,7 @@
return null;
}
- private Rect getViewBounds(View v) {
+ public Rect getViewBounds(View v) {
int[] pos = new int[2];
v.getLocationOnScreen(pos);
return new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight());
@@ -2821,11 +2712,7 @@
!intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
Bundle optsBundle = useLaunchAnimation ? getActivityLaunchOptions(v) : null;
- UserHandleCompat user = null;
- if (intent.hasExtra(AppInfo.EXTRA_PROFILE)) {
- long serialNumber = intent.getLongExtra(AppInfo.EXTRA_PROFILE, -1);
- user = UserManagerCompat.getInstance(this).getUserForSerialNumber(serialNumber);
- }
+ UserHandle user = item == null ? null : item.user;
// Prepare intent
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -2833,13 +2720,14 @@
intent.setSourceBounds(getViewBounds(v));
}
try {
- if (Utilities.ATLEAST_MARSHMALLOW && item != null
+ if (Utilities.ATLEAST_MARSHMALLOW
+ && (item instanceof ShortcutInfo)
&& (item.itemType == Favorites.ITEM_TYPE_SHORTCUT
- || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
- && ((ShortcutInfo) item).promisedIntent == null) {
+ || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
+ && !((ShortcutInfo) item).isPromise()) {
// Shortcuts need some special checks due to legacy reasons.
startShortcutIntentSafely(intent, optsBundle, item);
- } else if (user == null || user.equals(UserHandleCompat.myUserHandle())) {
+ } else if (user == null || user.equals(Process.myUserHandle())) {
// Could be launching some bookkeeping activity
startActivity(intent, optsBundle);
} else {
@@ -2854,227 +2742,10 @@
return false;
}
- /**
- * 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 copyFolderIconToImage(FolderIcon fi) {
- final int width = fi.getMeasuredWidth();
- final int height = fi.getMeasuredHeight();
-
- // Lazy load ImageView, Bitmap and Canvas
- if (mFolderIconImageView == null) {
- mFolderIconImageView = new ImageView(this);
- }
- if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width ||
- mFolderIconBitmap.getHeight() != height) {
- mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- mFolderIconCanvas = new Canvas(mFolderIconBitmap);
- }
-
- DragLayer.LayoutParams lp;
- if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) {
- lp = (DragLayer.LayoutParams) mFolderIconImageView.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 = mDragLayer.getDescendantRectRelativeToSelf(fi, mRectForFolderAnimation);
- lp.customPosition = true;
- lp.x = mRectForFolderAnimation.left;
- lp.y = mRectForFolderAnimation.top;
- lp.width = (int) (scale * width);
- lp.height = (int) (scale * height);
-
- mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
- fi.draw(mFolderIconCanvas);
- mFolderIconImageView.setImageBitmap(mFolderIconBitmap);
- if (fi.getFolder() != null) {
- mFolderIconImageView.setPivotX(fi.getFolder().getPivotXForIconAnimation());
- mFolderIconImageView.setPivotY(fi.getFolder().getPivotYForIconAnimation());
- }
- // Just in case this image view is still in the drag layer from a previous animation,
- // we remove it and re-add it.
- if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) {
- mDragLayer.removeView(mFolderIconImageView);
- }
- mDragLayer.addView(mFolderIconImageView, lp);
- if (fi.getFolder() != null) {
- fi.getFolder().bringToFront();
- }
- }
-
- private void growAndFadeOutFolderIcon(FolderIcon fi) {
- if (fi == null) return;
- FolderInfo info = (FolderInfo) fi.getTag();
- if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- CellLayout cl = (CellLayout) fi.getParent().getParent();
- CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams();
- cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
- }
-
- // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
- copyFolderIconToImage(fi);
- fi.setVisibility(View.INVISIBLE);
-
- ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(
- mFolderIconImageView, 0, 1.5f, 1.5f);
- if (Utilities.ATLEAST_LOLLIPOP) {
- oa.setInterpolator(new LogDecelerateInterpolator(100, 0));
- }
- oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
- oa.start();
- }
-
- private void shrinkAndFadeInFolderIcon(final FolderIcon fi, boolean animate) {
- if (fi == null) return;
- final CellLayout cl = (CellLayout) fi.getParent().getParent();
-
- // We remove and re-draw the FolderIcon in-case it has changed
- mDragLayer.removeView(mFolderIconImageView);
- copyFolderIconToImage(fi);
-
- if (cl != null) {
- cl.clearFolderLeaveBehind();
- }
-
- ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(mFolderIconImageView, 1, 1, 1);
- oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
- oa.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (cl != null) {
- // Remove the ImageView copy of the FolderIcon and make the original visible.
- mDragLayer.removeView(mFolderIconImageView);
- fi.setVisibility(View.VISIBLE);
- }
- }
- });
- oa.start();
- if (!animate) {
- oa.end();
- }
- }
-
- /**
- * 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
- * is played.
- *
- * @param folderIcon The FolderIcon describing the folder to open.
- */
- public void openFolder(FolderIcon folderIcon) {
-
- Folder folder = folderIcon.getFolder();
- Folder openFolder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
- if (openFolder != null && openFolder != folder) {
- // Close any open folder before opening a folder.
- closeFolder();
- }
-
- FolderInfo info = folder.mInfo;
-
- info.opened = true;
-
- // While the folder is open, the position of the icon cannot change.
- ((CellLayout.LayoutParams) folderIcon.getLayoutParams()).canReorder = false;
-
- // Just verify that the folder hasn't already been added to the DragLayer.
- // There was a one-off crash where the folder had a parent already.
- if (folder.getParent() == null) {
- mDragLayer.addView(folder);
- mDragController.addDropTarget(folder);
- } else {
- Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" +
- folder.getParent() + ").");
- }
- folder.animateOpen();
-
- growAndFadeOutFolderIcon(folderIcon);
-
- // Notify the accessibility manager that this folder "window" has appeared and occluded
- // the workspace items
- folder.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- }
-
- public void closeFolder() {
- closeFolder(true);
- }
-
- public void closeFolder(boolean animate) {
- Folder folder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
- if (folder != null) {
- if (folder.isEditingName()) {
- folder.dismissEditingName();
- }
- closeFolder(folder, animate);
- }
- }
-
- public void closeFolder(Folder folder, boolean animate) {
- animate &= !Utilities.isPowerSaverOn(this);
-
- folder.getInfo().opened = false;
-
- ViewGroup parent = (ViewGroup) folder.getParent().getParent();
- if (parent != null) {
- FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo);
- shrinkAndFadeInFolderIcon(fi, animate);
- if (fi != null) {
- ((CellLayout.LayoutParams) fi.getLayoutParams()).canReorder = true;
- }
- }
- if (animate) {
- folder.animateClosed();
- } else {
- folder.close(false);
- }
-
- // Notify the accessibility manager that this folder "window" has disappeared and no
- // longer occludes the workspace items
- getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- }
-
- public void closeShortcutsContainer() {
- closeShortcutsContainer(true);
- }
-
- public void closeShortcutsContainer(boolean animate) {
- DeepShortcutsContainer deepShortcutsContainer = getOpenShortcutsContainer();
- if (deepShortcutsContainer != null) {
- if (animate) {
- deepShortcutsContainer.animateClose();
- } else {
- deepShortcutsContainer.close();
- }
- }
- }
-
- public View getTopFloatingView() {
- View topView = getOpenShortcutsContainer();
- if (topView == null) {
- topView = getWorkspace().getOpenFolder();
- }
- return topView;
- }
-
- /**
- * @return The open shortcuts container, or null if there is none
- */
- public DeepShortcutsContainer getOpenShortcutsContainer() {
- // Iterate in reverse order. Shortcuts container is added later to the dragLayer,
- // and will be one of the last views.
- for (int i = mDragLayer.getChildCount() - 1; i >= 0; i--) {
- View child = mDragLayer.getChildAt(i);
- if (child instanceof DeepShortcutsContainer
- && ((DeepShortcutsContainer) child).isOpen()) {
- return (DeepShortcutsContainer) child;
- }
- }
- return null;
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ mLastDispatchTouchEventX = ev.getX();
+ return super.dispatchTouchEvent(ev);
}
@Override
@@ -3089,9 +2760,16 @@
return true;
}
+
+ boolean ignoreLongPressToOverview =
+ mDeviceProfile.shouldIgnoreLongPressToOverview(mLastDispatchTouchEventX);
+
if (v instanceof Workspace) {
if (!mWorkspace.isInOverviewMode()) {
- if (!mWorkspace.isTouchActive()) {
+ if (!mWorkspace.isTouchActive() && !ignoreLongPressToOverview) {
+ getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
+ Action.Direction.NONE, ContainerType.WORKSPACE,
+ mWorkspace.getCurrentPage());
showOverviewMode(true);
mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
@@ -3118,13 +2796,21 @@
if (!mDragController.isDragging()) {
if (itemUnderLongClick == null) {
// User long pressed on empty space
- mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
- HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
if (mWorkspace.isInOverviewMode()) {
mWorkspace.startReordering(v);
+ getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
+ Action.Direction.NONE, ContainerType.OVERVIEW);
} else {
+ if (ignoreLongPressToOverview) {
+ return false;
+ }
+ getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
+ Action.Direction.NONE, ContainerType.WORKSPACE,
+ mWorkspace.getCurrentPage());
showOverviewMode(true);
}
+ mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
} else {
final boolean isAllAppsButton =
!FeatureFlags.NO_ALL_APPS_ICON && isHotseatLayout(v) &&
@@ -3132,17 +2818,7 @@
longClickCellInfo.cellX, longClickCellInfo.cellY));
if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
// User long pressed on an item
- DragOptions dragOptions = new DragOptions();
- if (itemUnderLongClick instanceof BubbleTextView) {
- BubbleTextView icon = (BubbleTextView) itemUnderLongClick;
- if (icon.hasDeepShortcuts()) {
- DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon(icon);
- if (dsc != null) {
- dragOptions.deferDragCondition = dsc.createDeferDragCondition(null);
- }
- }
- }
- mWorkspace.startDrag(longClickCellInfo, dragOptions);
+ mWorkspace.startDrag(longClickCellInfo, new DragOptions());
}
}
}
@@ -3150,6 +2826,7 @@
}
boolean isHotseatLayout(View layout) {
+ // TODO: Remove this method
return mHotseat != null && layout != null &&
(layout instanceof CellLayout) && (layout == mHotseat.getLayout());
}
@@ -3219,11 +2896,7 @@
}
// Change the state *after* we've called all the transition code
- mState = State.WORKSPACE;
-
- // Resume the auto-advance of widgets
- mUserPresent = true;
- updateAutoAdvanceState();
+ setState(State.WORKSPACE);
if (changed) {
// Send an accessibility event to announce the context change
@@ -3260,12 +2933,30 @@
mWorkspace.setVisibility(View.VISIBLE);
mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
Workspace.State.OVERVIEW, animated, postAnimRunnable);
- mState = State.WORKSPACE;
+ 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.
*/
@@ -3321,20 +3012,14 @@
}
if (toState == State.APPS) {
- mStateTransitionAnimation.startAnimationToAllApps(mWorkspace.getState(), animated,
- focusSearchBar);
+ mStateTransitionAnimation.startAnimationToAllApps(animated, focusSearchBar);
} else {
- mStateTransitionAnimation.startAnimationToWidgets(mWorkspace.getState(), animated);
+ mStateTransitionAnimation.startAnimationToWidgets(animated);
}
// Change the state *after* we've called all the transition code
- mState = toState;
-
- // Pause the auto-advance of widgets until we are out of AllApps
- mUserPresent = false;
- updateAutoAdvanceState();
- closeFolder();
- closeShortcutsContainer();
+ setState(toState);
+ AbstractFloatingView.closeAllOpenViews(this);
// Send an accessibility event to announce the context change
getWindow().getDecorView()
@@ -3347,7 +3032,7 @@
* new state.
*/
public Animator startWorkspaceStateChangeAnimation(Workspace.State toState,
- boolean animated, HashMap<View, Integer> layerViews) {
+ boolean animated, AnimationLayerSet layerViews) {
Workspace.State fromState = mWorkspace.getState();
Animator anim = mWorkspace.setStateWithAnimation(toState, animated, layerViews);
updateInteraction(fromState, toState);
@@ -3363,16 +3048,7 @@
mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
Workspace.State.SPRING_LOADED, true /* animated */,
null /* onCompleteRunnable */);
-
- if (isAppsViewVisible()) {
- mState = State.APPS_SPRING_LOADED;
- } else if (isWidgetsViewVisible()) {
- mState = State.WIDGETS_SPRING_LOADED;
- } else if (!FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) {
- mState = State.WORKSPACE_SPRING_LOADED;
- } else {
- mState = State.WORKSPACE;
- }
+ setState(State.WORKSPACE_SPRING_LOADED);
}
public void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay,
@@ -3407,7 +3083,7 @@
|| mState == State.WIDGETS_SPRING_LOADED;
}
- void exitSpringLoadedDragMode() {
+ public void exitSpringLoadedDragMode() {
if (mState == State.APPS_SPRING_LOADED) {
showAppsView(true /* animated */,
false /* updatePredictedApps */, false /* focusSearchBar */);
@@ -3552,13 +3228,15 @@
if (LauncherAppState.PROFILE_STARTUP) {
Trace.beginSection("Starting page bind");
}
+
+ AbstractFloatingView.closeAllOpenViews(this);
+
setWorkspaceLoading(true);
// Clear the workspace because it's going to be rebound
mWorkspace.clearDropTargets();
mWorkspace.removeAllWorkspaceScreens();
- mWidgetsToAdvance.clear();
if (mHotseat != null) {
mHotseat.resetLayout();
}
@@ -3648,25 +3326,25 @@
* Implementation of the method from LauncherModel.Callbacks.
*/
@Override
- public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
+ public void bindItems(final ArrayList<ItemInfo> items, final int start, final int end,
final boolean forceAnimateIcons) {
Runnable r = new Runnable() {
public void run() {
- bindItems(shortcuts, start, end, forceAnimateIcons);
+ bindItems(items, start, end, forceAnimateIcons);
}
};
if (waitUntilResume(r)) {
return;
}
- // Get the list of added shortcuts and intersect them with the set of shortcuts here
+ // Get the list of added items and intersect them with the set of items here
final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
final Collection<Animator> bounceAnims = new ArrayList<Animator>();
final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
Workspace workspace = mWorkspace;
- long newShortcutsScreenId = -1;
+ long newItemsScreenId = -1;
for (int i = start; i < end; i++) {
- final ItemInfo item = shortcuts.get(i);
+ final ItemInfo item = items.get(i);
// Short circuit if we are loading dock items for a configuration which has no dock
if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
@@ -3678,15 +3356,33 @@
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
- case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+ case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
ShortcutInfo info = (ShortcutInfo) item;
view = createShortcut(info);
break;
- case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+ }
+ case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
view = FolderIcon.fromXml(R.layout.folder_icon, this,
(ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
- (FolderInfo) item, mIconCache);
+ (FolderInfo) item);
break;
+ }
+ case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: {
+ LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) item;
+ if (mIsSafeModeEnabled) {
+ view = new PendingAppWidgetHostView(this, info, mIconCache, true);
+ } else {
+ LauncherAppWidgetProviderInfo providerInfo =
+ mAppWidgetManager.getLauncherAppWidgetInfo(info.appWidgetId);
+ if (providerInfo == null) {
+ deleteWidgetInfo(info);
+ continue;
+ }
+ view = mAppWidgetHost.createView(this, info.appWidgetId, providerInfo);
+ }
+ prepareAppWidget((AppWidgetHostView) view, info);
+ break;
+ }
default:
throw new RuntimeException("Invalid Item Type");
}
@@ -3705,35 +3401,34 @@
throw (new RuntimeException(desc));
} else {
Log.d(TAG, desc);
- LauncherModel.deleteItemFromDatabase(this, item);
+ getModelWriter().deleteItemFromDatabase(item);
continue;
}
}
}
- workspace.addInScreenFromBind(view, item.container, item.screenId, item.cellX,
- item.cellY, 1, 1);
+ workspace.addInScreenFromBind(view, item);
if (animateIcons) {
// Animate all the applications up now
view.setAlpha(0f);
view.setScaleX(0f);
view.setScaleY(0f);
bounceAnims.add(createNewAppBounceAnimation(view, i));
- newShortcutsScreenId = item.screenId;
+ newItemsScreenId = item.screenId;
}
}
if (animateIcons) {
// Animate to the correct page
- if (newShortcutsScreenId > -1) {
+ if (newItemsScreenId > -1) {
long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
- final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId);
+ final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newItemsScreenId);
final Runnable startBounceAnimRunnable = new Runnable() {
public void run() {
anim.playTogether(bounceAnims);
anim.start();
}
};
- if (newShortcutsScreenId != currentScreenId) {
+ if (newItemsScreenId != currentScreenId) {
// We post the animation slightly delayed to prevent slowdowns
// when we are loading right after we return to launcher.
mWorkspace.postDelayed(new Runnable() {
@@ -3753,15 +3448,6 @@
workspace.requestLayout();
}
- private void bindSafeModeWidget(LauncherAppWidgetInfo item) {
- PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item, true);
- view.updateIcon(mIconCache);
- view.updateAppWidget(null);
- view.setOnClickListener(this);
- addAppWidgetToWorkspace(view, item, null, false);
- mWorkspace.requestLayout();
- }
-
/**
* Add the views for a widget to the workspace.
*
@@ -3778,7 +3464,11 @@
}
if (mIsSafeModeEnabled) {
- bindSafeModeWidget(item);
+ PendingAppWidgetHostView view =
+ new PendingAppWidgetHostView(this, item, mIconCache, true);
+ prepareAppWidget(view, item);
+ mWorkspace.addInScreen(view, item);
+ mWorkspace.requestLayout();
return;
}
@@ -3806,9 +3496,9 @@
if (DEBUG_WIDGETS) {
Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
+ " belongs to component " + item.providerName
- + ", as the povider is null");
+ + ", as the provider is null");
}
- LauncherModel.deleteItemFromDatabase(this, item);
+ getModelWriter().deleteItemFromDatabase(item);
return;
}
@@ -3821,7 +3511,7 @@
// Also try to bind the widget. If the bind fails, the user will be shown
// a click to setup UI, which will ask for the bind permission.
- PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(this, appWidgetInfo);
+ PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo);
pendingInfo.spanX = item.spanX;
pendingInfo.spanY = item.spanY;
pendingInfo.minSpanX = item.minSpanX;
@@ -3855,17 +3545,18 @@
: LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
}
- LauncherModel.updateItemInDatabase(this, item);
+ getModelWriter().updateItemInDatabase(item);
}
} else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY)
&& (appWidgetInfo.configure == null)) {
// The widget was marked as UI not ready, but there is no configure activity to
// update the UI.
item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
- LauncherModel.updateItemInDatabase(this, item);
+ getModelWriter().updateItemInDatabase(item);
}
}
+ final AppWidgetHostView view;
if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
if (DEBUG_WIDGETS) {
Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component "
@@ -3881,16 +3572,12 @@
item.minSpanX = appWidgetInfo.minSpanX;
item.minSpanY = appWidgetInfo.minSpanY;
- addAppWidgetToWorkspace(
- mAppWidgetHost.createView(this, item.appWidgetId, appWidgetInfo),
- item, appWidgetInfo, false);
+ view = mAppWidgetHost.createView(this, item.appWidgetId, appWidgetInfo);
} else {
- PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item, false);
- view.updateIcon(mIconCache);
- view.updateAppWidget(null);
- view.setOnClickListener(this);
- addAppWidgetToWorkspace(view, item, null, false);
+ view = new PendingAppWidgetHostView(this, item, mIconCache, false);
}
+ prepareAppWidget(view, item);
+ mWorkspace.addInScreen(view, item);
mWorkspace.requestLayout();
if (DEBUG_WIDGETS) {
@@ -3915,7 +3602,7 @@
info.restoreStatus = finalRestoreFlag;
mWorkspace.reinflateWidgetsIfNecessary();
- LauncherModel.updateItemInDatabase(this, info);
+ getModelWriter().updateItemInDatabase(info);
return info;
}
@@ -3981,14 +3668,6 @@
if (LauncherAppState.PROFILE_STARTUP) {
Trace.beginSection("Page bind completed");
}
- if (mSavedState != null) {
- if (!mWorkspace.hasFocus()) {
- mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
- }
-
- mSavedState = null;
- }
-
mWorkspace.restoreInstanceStateForRemainingPages();
setWorkspaceLoading(false);
@@ -4001,6 +3680,8 @@
InstallShortcutReceiver.disableAndFlushInstallQueue(this);
+ NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
+
if (mLauncherCallbacks != null) {
mLauncherCallbacks.finishBindingItems(false);
}
@@ -4070,21 +3751,7 @@
*/
@Override
public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy) {
- mDeepShortcutMap = deepShortcutMapCopy;
- if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap);
- }
-
- public List<String> getShortcutIdsForItem(ItemInfo info) {
- if (!DeepShortcutManager.supportsShortcuts(info)) {
- return Collections.EMPTY_LIST;
- }
- ComponentName component = info.getTargetComponent();
- if (component == null) {
- return Collections.EMPTY_LIST;
- }
-
- List<String> ids = mDeepShortcutMap.get(new ComponentKey(component, info.user));
- return ids == null ? Collections.EMPTY_LIST : ids;
+ mPopupDataProvider.setDeepShortcutMap(deepShortcutMapCopy);
}
/**
@@ -4131,7 +3798,7 @@
*/
@Override
public void bindShortcutsChanged(final ArrayList<ShortcutInfo> updated,
- final ArrayList<ShortcutInfo> removed, final UserHandleCompat user) {
+ final ArrayList<ShortcutInfo> removed, final UserHandle user) {
Runnable r = new Runnable() {
public void run() {
bindShortcutsChanged(updated, removed, user);
@@ -4151,7 +3818,7 @@
for (ShortcutInfo si : removed) {
if (si.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
- removedDeepShortcuts.add(ShortcutKey.fromShortcutInfo(si));
+ removedDeepShortcuts.add(ShortcutKey.fromItemInfo(si));
} else {
removedComponents.add(si.getTargetComponent());
}
@@ -4200,7 +3867,7 @@
@Override
public void bindWorkspaceComponentsRemoved(
final HashSet<String> packageNames, final HashSet<ComponentName> components,
- final UserHandleCompat user) {
+ final UserHandle user) {
Runnable r = new Runnable() {
public void run() {
bindWorkspaceComponentsRemoved(packageNames, components, user);
@@ -4236,76 +3903,56 @@
// Update AllApps
if (mAppsView != null) {
mAppsView.removeApps(appInfos);
+ tryAndUpdatePredictedApps();
}
}
- private Runnable mBindWidgetModelRunnable = new Runnable() {
+ private Runnable mBindAllWidgetsRunnable = new Runnable() {
public void run() {
- bindWidgetsModel(mWidgetsModel);
+ bindAllWidgets(mAllWidgets);
}
};
@Override
- public void bindWidgetsModel(WidgetsModel model) {
- if (waitUntilResume(mBindWidgetModelRunnable, true)) {
- mWidgetsModel = model;
+ public void bindAllWidgets(MultiHashMap<PackageItemInfo, WidgetItem> allWidgets) {
+ if (waitUntilResume(mBindAllWidgetsRunnable, true)) {
+ mAllWidgets = allWidgets;
return;
}
- if (mWidgetsView != null && model != null) {
- mWidgetsView.addWidgets(model);
- mWidgetsModel = null;
+ if (mWidgetsView != null && allWidgets != null) {
+ mWidgetsView.setWidgets(allWidgets);
+ mAllWidgets = null;
}
+
+ AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
+ if (topView != null) {
+ topView.onWidgetsBound();
+ }
+ }
+
+ public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
+ return mWidgetsView.getWidgetsForPackageUser(packageUserKey);
}
@Override
public void notifyWidgetProvidersChanged() {
if (mWorkspace.getState().shouldUpdateWidget) {
- mModel.refreshAndBindWidgetsAndShortcuts(this, mWidgetsView.isEmpty());
+ refreshAndBindWidgetsForPackageUser(null);
}
}
- private int mapConfigurationOriActivityInfoOri(int configOri) {
- final Display d = getWindowManager().getDefaultDisplay();
- int naturalOri = Configuration.ORIENTATION_LANDSCAPE;
- switch (d.getRotation()) {
- case Surface.ROTATION_0:
- case Surface.ROTATION_180:
- // We are currently in the same basic orientation as the natural orientation
- naturalOri = configOri;
- break;
- case Surface.ROTATION_90:
- case Surface.ROTATION_270:
- // We are currently in the other basic orientation to the natural orientation
- naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ?
- Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
- break;
- }
-
- int[] oriMap = {
- ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
- ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
- ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
- ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
- };
- // Since the map starts at portrait, we need to offset if this device's natural orientation
- // is landscape.
- int indexOffset = 0;
- if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) {
- indexOffset = 1;
- }
- return oriMap[(d.getRotation() + indexOffset) % 4];
+ /**
+ * @param packageUser if null, refreshes all widgets and shortcuts, otherwise only
+ * refreshes the widgets and shortcuts associated with the given package/user
+ */
+ public void refreshAndBindWidgetsForPackageUser(@Nullable PackageUserKey packageUser) {
+ mModel.refreshAndBindWidgetsAndShortcuts(this, mWidgetsView.isEmpty(), packageUser);
}
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public void lockScreenOrientation() {
if (mRotationEnabled) {
- if (Utilities.ATLEAST_JB_MR2) {
- setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
- } else {
- setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
- .getConfiguration().orientation));
- }
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
}
}
@@ -4346,117 +3993,118 @@
return true;
}
- // TODO: These method should be a part of LauncherSearchCallback
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
- // Called from search suggestion
- UserHandleCompat user = null;
- if (Utilities.ATLEAST_LOLLIPOP) {
- UserHandle userHandle = appLaunchIntent.getParcelableExtra(Intent.EXTRA_USER);
- if (userHandle != null) {
- user = UserHandleCompat.fromUser(userHandle);
- }
- }
- return createAppDragInfo(appLaunchIntent, user);
- }
-
- // TODO: This method should be a part of LauncherSearchCallback
- public ItemInfo createAppDragInfo(Intent intent, UserHandleCompat user) {
- if (user == null) {
- user = UserHandleCompat.myUserHandle();
- }
-
- // Called from search suggestion, add the profile extra to the intent to ensure that we
- // can launch it correctly
- LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
- LauncherActivityInfoCompat activityInfo = launcherApps.resolveActivity(intent, user);
- if (activityInfo == null) {
- return null;
- }
- return new AppInfo(this, activityInfo, user, mIconCache);
- }
-
- // TODO: This method should be a part of LauncherSearchCallback
- public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
- Bitmap icon) {
- return new ShortcutInfo(shortcutIntent, caption, caption, icon,
- UserHandleCompat.myUserHandle());
- }
-
protected void moveWorkspaceToDefaultScreen() {
mWorkspace.moveToDefaultScreen(false);
}
/**
- * Returns a FastBitmapDrawable with the icon, accurately sized.
+ * $ adb shell dumpsys activity com.android.launcher3.Launcher [--all]
*/
- public FastBitmapDrawable createIconDrawable(Bitmap icon) {
- FastBitmapDrawable d = new FastBitmapDrawable(icon);
- d.setFilterBitmap(true);
- resizeIconDrawable(d);
- return d;
- }
-
- /**
- * Resizes an icon drawable to the correct icon size.
- */
- public Drawable resizeIconDrawable(Drawable icon) {
- icon.setBounds(0, 0, mDeviceProfile.iconSizePx, mDeviceProfile.iconSizePx);
- return icon;
- }
-
- /**
- * Prints out out state for debugging.
- */
- public void dumpState() {
- Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this);
- Log.d(TAG, "mSavedState=" + mSavedState);
- Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
- Log.d(TAG, "mPendingRequestArgs=" + mPendingRequestArgs);
- Log.d(TAG, "mPendingActivityResult=" + mPendingActivityResult);
- mModel.dumpState();
- // TODO(hyunyoungs): add mWidgetsView.dumpState(); or mWidgetsModel.dumpState();
-
- Log.d(TAG, "END launcher3 dump state");
- }
-
@Override
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
super.dump(prefix, fd, writer, args);
- // Dump workspace
- writer.println(prefix + "Workspace Items");
- for (int i = mWorkspace.numCustomPages(); i < mWorkspace.getPageCount(); i++) {
- writer.println(prefix + " Homescreen " + i);
- ViewGroup layout = ((CellLayout) mWorkspace.getPageAt(i)).getShortcutsAndWidgets();
+ if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
+ writer.println(prefix + "Workspace Items");
+ for (int i = mWorkspace.numCustomPages(); i < mWorkspace.getPageCount(); i++) {
+ writer.println(prefix + " Homescreen " + i);
+
+ ViewGroup layout = ((CellLayout) mWorkspace.getPageAt(i)).getShortcutsAndWidgets();
+ for (int j = 0; j < layout.getChildCount(); j++) {
+ Object tag = layout.getChildAt(j).getTag();
+ if (tag != null) {
+ writer.println(prefix + " " + tag.toString());
+ }
+ }
+ }
+
+ writer.println(prefix + " Hotseat");
+ ViewGroup layout = mHotseat.getLayout().getShortcutsAndWidgets();
for (int j = 0; j < layout.getChildCount(); j++) {
Object tag = layout.getChildAt(j).getTag();
if (tag != null) {
writer.println(prefix + " " + tag.toString());
}
}
- }
- writer.println(prefix + " Hotseat");
- ViewGroup layout = mHotseat.getLayout().getShortcutsAndWidgets();
- for (int j = 0; j < layout.getChildCount(); j++) {
- Object tag = layout.getChildAt(j).getTag();
- if (tag != null) {
- writer.println(prefix + " " + tag.toString());
+ try {
+ FileLog.flushAll(writer);
+ } catch (Exception e) {
+ // Ignore
}
}
- try {
- FileLog.flushAll(writer);
- } catch (Exception e) {
- // Ignore
- }
+ writer.println(prefix + "Misc:");
+ writer.print(prefix + "\tmWorkspaceLoading=" + mWorkspaceLoading);
+ writer.print(" mPendingRequestArgs=" + mPendingRequestArgs);
+ writer.println(" mPendingActivityResult=" + mPendingActivityResult);
+
+ mModel.dumpState(prefix, fd, writer, args);
if (mLauncherCallbacks != null) {
mLauncherCallbacks.dump(prefix, fd, writer, args);
}
}
+ @Override
+ @TargetApi(Build.VERSION_CODES.N)
+ public void onProvideKeyboardShortcuts(
+ List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
+
+ ArrayList<KeyboardShortcutInfo> shortcutInfos = new ArrayList<>();
+ if (mState == State.WORKSPACE) {
+ shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.all_apps_button_label),
+ KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON));
+ }
+ View currentFocus = getCurrentFocus();
+ if (new CustomActionsPopup(this, currentFocus).canShow()) {
+ shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.custom_actions),
+ KeyEvent.KEYCODE_O, KeyEvent.META_CTRL_ON));
+ }
+ if (currentFocus instanceof BubbleTextView &&
+ ((BubbleTextView) currentFocus).hasDeepShortcuts()) {
+ shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.action_deep_shortcut),
+ KeyEvent.KEYCODE_S, KeyEvent.META_CTRL_ON));
+ }
+ if (!shortcutInfos.isEmpty()) {
+ data.add(new KeyboardShortcutGroup(getString(R.string.home_screen), shortcutInfos));
+ }
+
+ super.onProvideKeyboardShortcuts(data, menu, deviceId);
+ }
+
+ @Override
+ public boolean onKeyShortcut(int keyCode, KeyEvent event) {
+ if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_A:
+ if (mState == State.WORKSPACE) {
+ showAppsView(true, true, false);
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_S: {
+ View focusedView = getCurrentFocus();
+ if (focusedView instanceof BubbleTextView
+ && focusedView.getTag() instanceof ItemInfo
+ && mAccessibilityDelegate.performAction(focusedView,
+ (ItemInfo) focusedView.getTag(),
+ LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) {
+ PopupContainerWithArrow.getOpen(this).requestFocus();
+ return true;
+ }
+ break;
+ }
+ case KeyEvent.KEYCODE_O:
+ if (new CustomActionsPopup(this, getCurrentFocus()).show()) {
+ return true;
+ }
+ break;
+ }
+ }
+ return super.onKeyShortcut(keyCode, event);
+ }
+
public static CustomAppWidget getCustomAppWidget(String name) {
return sCustomAppWidgets.get(name);
}
@@ -4465,14 +4113,6 @@
return sCustomAppWidgets;
}
- public static List<View> getFolderContents(View icon) {
- if (icon instanceof FolderIcon) {
- return ((FolderIcon) icon).getFolder().getItemsInReadingOrder();
- } else {
- return Collections.EMPTY_LIST;
- }
- }
-
public static Launcher getLauncher(Context context) {
if (context instanceof Launcher) {
return (Launcher) context;
@@ -4480,22 +4120,16 @@
return ((Launcher) ((ContextWrapper) context).getBaseContext());
}
- private class RotationPrefChangeHandler implements OnSharedPreferenceChangeListener, Runnable {
+ private class RotationPrefChangeHandler implements OnSharedPreferenceChangeListener {
@Override
public void onSharedPreferenceChanged(
SharedPreferences sharedPreferences, String key) {
if (Utilities.ALLOW_ROTATION_PREFERENCE_KEY.equals(key)) {
- mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext());
- if (!waitUntilResume(this, true)) {
- run();
- }
+ // Finish this instance of the activity. When the activity is recreated,
+ // it will initialize the rotation preference again.
+ finish();
}
}
-
- @Override
- public void run() {
- setOrientation();
- }
}
}
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 01e73d4..aa7f5ee 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -23,7 +23,9 @@
import android.animation.ValueAnimator;
import android.util.Property;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewTreeObserver;
+import android.widget.ViewAnimator;
import java.util.HashSet;
import java.util.WeakHashMap;
@@ -92,7 +94,7 @@
return anim;
}
- public static ValueAnimator ofFloat(View target, float... values) {
+ public static ValueAnimator ofFloat(float... values) {
ValueAnimator anim = new ValueAnimator();
anim.setFloatValues(values);
cancelOnDestroyActivity(anim);
@@ -127,4 +129,5 @@
new FirstFrameAnimatorHelper(anim, view);
return anim;
}
+
}
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 7861a10..180c202 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -16,10 +16,11 @@
package com.android.launcher3;
-import android.content.BroadcastReceiver;
+import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.Looper;
import android.util.Log;
import com.android.launcher3.compat.LauncherAppsCompat;
@@ -27,37 +28,43 @@
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.dynamicui.ExtractionUtils;
-import com.android.launcher3.logging.FileLog;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.shortcuts.ShortcutCache;
import com.android.launcher3.util.ConfigMonitor;
+import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.TestingUtils;
-import com.android.launcher3.util.Thunk;
-import java.lang.ref.WeakReference;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
public class LauncherAppState {
public static final boolean PROFILE_STARTUP = ProviderConfig.IS_DOGFOOD_BUILD;
- private final AppFilter mAppFilter;
- @Thunk final LauncherModel mModel;
- private final IconCache mIconCache;
- private final WidgetPreviewLoader mWidgetCache;
- private final DeepShortcutManager mDeepShortcutManager;
-
- @Thunk boolean mWallpaperChangedSinceLastCheck;
-
- private static WeakReference<LauncherProvider> sLauncherProvider;
- private static Context sContext;
-
+ // We do not need any synchronization for this variable as its only written on UI thread.
private static LauncherAppState INSTANCE;
- private InvariantDeviceProfile mInvariantDeviceProfile;
+ private final Context mContext;
+ private final LauncherModel mModel;
+ private final IconCache mIconCache;
+ private final WidgetPreviewLoader mWidgetCache;
+ private final InvariantDeviceProfile mInvariantDeviceProfile;
- public static LauncherAppState getInstance() {
+
+ public static LauncherAppState getInstance(final Context context) {
if (INSTANCE == null) {
- INSTANCE = new LauncherAppState();
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ INSTANCE = new LauncherAppState(context.getApplicationContext());
+ } else {
+ try {
+ return new MainThreadExecutor().submit(new Callable<LauncherAppState>() {
+ @Override
+ public LauncherAppState call() throws Exception {
+ return LauncherAppState.getInstance(context);
+ }
+ }).get();
+ } catch (InterruptedException|ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
return INSTANCE;
}
@@ -67,43 +74,30 @@
}
public Context getContext() {
- return sContext;
+ return mContext;
}
- static void setLauncherProvider(LauncherProvider provider) {
- if (sLauncherProvider != null) {
- Log.w(Launcher.TAG, "setLauncherProvider called twice! old=" +
- sLauncherProvider.get() + " new=" + provider);
+ private LauncherAppState(Context context) {
+ if (getLocalProvider(context) == null) {
+ throw new RuntimeException(
+ "Initializing LauncherAppState in the absence of LauncherProvider");
}
- sLauncherProvider = new WeakReference<>(provider);
-
- // The content provider exists for the entire duration of the launcher main process and
- // is the first component to get created. Initializing application context here ensures
- // that LauncherAppState always exists in the main process.
- sContext = provider.getContext().getApplicationContext();
- FileLog.setDir(sContext.getFilesDir());
- }
-
- private LauncherAppState() {
- if (sContext == null) {
- throw new IllegalStateException("LauncherAppState inited before app context set");
- }
-
- Log.v(Launcher.TAG, "LauncherAppState inited");
+ Log.v(Launcher.TAG, "LauncherAppState initiated");
+ Preconditions.assertUIThread();
+ mContext = context;
if (TestingUtils.MEMORY_DUMP_ENABLED) {
- TestingUtils.startTrackingMemory(sContext);
+ TestingUtils.startTrackingMemory(mContext);
}
- mInvariantDeviceProfile = new InvariantDeviceProfile(sContext);
- mIconCache = new IconCache(sContext, mInvariantDeviceProfile);
- mWidgetCache = new WidgetPreviewLoader(sContext, mIconCache);
- mDeepShortcutManager = new DeepShortcutManager(sContext, new ShortcutCache());
+ mInvariantDeviceProfile = new InvariantDeviceProfile(mContext);
+ mIconCache = new IconCache(mContext, mInvariantDeviceProfile);
+ mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
- mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
- mModel = new LauncherModel(this, mIconCache, mAppFilter, mDeepShortcutManager);
+ mModel = new LauncherModel(this, mIconCache,
+ Utilities.getOverrideObject(AppFilter.class, mContext, R.string.app_filter_class));
- LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);
+ LauncherAppsCompat.getInstance(mContext).addOnAppsChangedCallback(mModel);
// Register intent receivers
IntentFilter filter = new IntentFilter();
@@ -115,48 +109,30 @@
filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
// For extracting colors from the wallpaper
- if (Utilities.isNycOrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT) {
// TODO: add a broadcast entry to the manifest for pre-N.
filter.addAction(Intent.ACTION_WALLPAPER_CHANGED);
}
- sContext.registerReceiver(mModel, filter);
- UserManagerCompat.getInstance(sContext).enableAndResetCache();
- if (!Utilities.ATLEAST_KITKAT) {
- sContext.registerReceiver(new BroadcastReceiver() {
+ mContext.registerReceiver(mModel, filter);
+ UserManagerCompat.getInstance(mContext).enableAndResetCache();
+ new ConfigMonitor(mContext).register();
- @Override
- public void onReceive(Context context, Intent intent) {
- mWallpaperChangedSinceLastCheck = true;
- }
- }, new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED));
- }
- new ConfigMonitor(sContext).register();
-
- ExtractionUtils.startColorExtractionServiceIfNecessary(sContext);
+ ExtractionUtils.startColorExtractionServiceIfNecessary(mContext);
}
/**
* Call from Application.onTerminate(), which is not guaranteed to ever be called.
*/
public void onTerminate() {
- sContext.unregisterReceiver(mModel);
- final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(sContext);
+ mContext.unregisterReceiver(mModel);
+ final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mContext);
launcherApps.removeOnAppsChangedCallback(mModel);
- PackageInstallerCompat.getInstance(sContext).onStop();
- }
-
- /**
- * Reloads the workspace items from the DB and re-binds the workspace. This should generally
- * not be called as DB updates are automatically followed by UI update
- */
- public void reloadWorkspace() {
- mModel.resetLoadedState(false, true);
- mModel.startLoaderFromBackground();
+ PackageInstallerCompat.getInstance(mContext).onStop();
}
LauncherModel setLauncher(Launcher launcher) {
- sLauncherProvider.get().setLauncherProviderChangeListener(launcher);
+ getLocalProvider(mContext).setLauncherProviderChangeListener(launcher);
mModel.initialize(launcher);
return mModel;
}
@@ -173,17 +149,21 @@
return mWidgetCache;
}
- public DeepShortcutManager getShortcutManager() {
- return mDeepShortcutManager;
- }
-
- public boolean hasWallpaperChangedSinceLastCheck() {
- boolean result = mWallpaperChangedSinceLastCheck;
- mWallpaperChangedSinceLastCheck = false;
- return result;
- }
-
public InvariantDeviceProfile getInvariantDeviceProfile() {
return mInvariantDeviceProfile;
}
+
+ /**
+ * Shorthand for {@link #getInvariantDeviceProfile()}
+ */
+ public static InvariantDeviceProfile getIDP(Context context) {
+ return LauncherAppState.getInstance(context).getInvariantDeviceProfile();
+ }
+
+ private static LauncherProvider getLocalProvider(Context context) {
+ try (ContentProviderClient cl = context.getContentResolver()
+ .acquireContentProviderClient(LauncherProvider.AUTHORITY)) {
+ return (LauncherProvider) cl.getLocalContentProvider();
+ }
+ }
}
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index d3e5350..6e8c59b 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -20,10 +20,8 @@
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
-import android.os.DeadObjectException;
-import android.os.TransactionTooLargeException;
+import android.util.SparseArray;
import android.view.LayoutInflater;
-import android.view.View;
import java.util.ArrayList;
@@ -36,6 +34,7 @@
public class LauncherAppWidgetHost extends AppWidgetHost {
private final ArrayList<Runnable> mProviderChangeListeners = new ArrayList<Runnable>();
+ private final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
private Launcher mLauncher;
@@ -45,9 +44,11 @@
}
@Override
- protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
+ protected LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId,
AppWidgetProviderInfo appWidget) {
- return new LauncherAppWidgetHostView(context);
+ LauncherAppWidgetHostView view = new LauncherAppWidgetHostView(context);
+ mViews.put(appWidgetId, view);
+ return view;
}
@Override
@@ -55,15 +56,13 @@
try {
super.startListening();
} catch (Exception e) {
- if (e.getCause() instanceof TransactionTooLargeException ||
- e.getCause() instanceof DeadObjectException) {
- // We're willing to let this slide. The exception is being caused by the list of
- // RemoteViews which is being passed back. The startListening relationship will
- // have been established by this point, and we will end up populating the
- // widgets upon bind anyway. See issue 14255011 for more context.
- } else {
+ if (!Utilities.isBinderSizeError(e)) {
throw new RuntimeException(e);
}
+ // We're willing to let this slide. The exception is being caused by the list of
+ // RemoteViews which is being passed back. The startListening relationship will
+ // have been established by this point, and we will end up populating the
+ // widgets upon bind anyway. See issue 14255011 for more context.
}
}
@@ -98,7 +97,24 @@
lahv.updateLastInflationOrientation();
return lahv;
} else {
- return super.createView(context, appWidgetId, appWidget);
+ try {
+ return super.createView(context, appWidgetId, appWidget);
+ } catch (Exception e) {
+ if (!Utilities.isBinderSizeError(e)) {
+ throw new RuntimeException(e);
+ }
+
+ // If the exception was thrown while fetching the remote views, let the view stay.
+ // This will ensure that if the widget posts a valid update later, the view
+ // will update.
+ LauncherAppWidgetHostView view = mViews.get(appWidgetId);
+ if (view == null) {
+ view = onCreateView(mLauncher, appWidgetId, appWidget);
+ }
+ view.setAppWidget(appWidgetId, appWidget);
+ view.switchToErrorView();
+ return view;
+ }
}
}
@@ -112,6 +128,18 @@
super.onProviderChanged(appWidgetId, info);
// The super method updates the dimensions of the providerInfo. Update the
// launcher spans accordingly.
- info.initSpans();
+ info.initSpans(mLauncher);
+ }
+
+ @Override
+ public void deleteAppWidgetId(int appWidgetId) {
+ super.deleteAppWidgetId(appWidgetId);
+ mViews.remove(appWidgetId);
+ }
+
+ @Override
+ protected void clearViews() {
+ super.clearViews();
+ mViews.clear();
}
}
diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java
index ed1079f..13cc7ba 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java
@@ -19,7 +19,12 @@
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
+import android.graphics.PointF;
import android.graphics.Rect;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.SparseBooleanArray;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -28,22 +33,38 @@
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.AdapterView;
+import android.widget.Advanceable;
import android.widget.RemoteViews;
+import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragLayer.TouchCompleteListener;
+import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.concurrent.Executor;
/**
* {@inheritDoc}
*/
-public class LauncherAppWidgetHostView extends AppWidgetHostView implements TouchCompleteListener {
+public class LauncherAppWidgetHostView extends AppWidgetHostView
+ implements TouchCompleteListener, View.OnLongClickListener {
- LayoutInflater mInflater;
+ private static final String TAG = "LauncherWidgetHostView";
- private CheckLongPressHelper mLongPressHelper;
- private StylusEventHelper mStylusEventHelper;
- private Context mContext;
+ // Related to the auto-advancing of widgets
+ private static final long ADVANCE_INTERVAL = 20000;
+ private static final long ADVANCE_STAGGER = 250;
+
+ // Maintains a list of widget ids which are supposed to be auto advanced.
+ private static final SparseBooleanArray sAutoAdvanceWidgetIds = new SparseBooleanArray();
+
+ protected final LayoutInflater mInflater;
+
+ private final CheckLongPressHelper mLongPressHelper;
+ private final StylusEventHelper mStylusEventHelper;
+ private final Context mContext;
+
@ViewDebug.ExportedProperty(category = "launcher")
private int mPreviousOrientation;
@@ -52,21 +73,54 @@
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mChildrenFocused;
- protected int mErrorViewId = R.layout.appwidget_error;
+ private boolean mIsScrollable;
+ private boolean mIsAttachedToWindow;
+ private boolean mIsAutoAdvanceRegistered;
+ private Runnable mAutoAdvanceRunnable;
+
+ /**
+ * The scaleX and scaleY value such that the widget fits within its cellspans, scaleX = scaleY.
+ */
+ private float mScaleToFit = 1f;
+
+ /**
+ * The translation values to center the widget within its cellspans.
+ */
+ private final PointF mTranslationForCentering = new PointF(0, 0);
public LauncherAppWidgetHostView(Context context) {
super(context);
mContext = context;
- mLongPressHelper = new CheckLongPressHelper(this);
+ mLongPressHelper = new CheckLongPressHelper(this, this);
mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
- mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mInflater = LayoutInflater.from(context);
setAccessibilityDelegate(Launcher.getLauncher(context).getAccessibilityDelegate());
setBackgroundResource(R.drawable.widget_internal_focus_bg);
+
+ if (Utilities.isAtLeastO()) {
+ try {
+ Method asyncMethod = AppWidgetHostView.class
+ .getMethod("setExecutor", Executor.class);
+ asyncMethod.invoke(this, Utilities.THREAD_POOL_EXECUTOR);
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to set async executor", e);
+ }
+ }
+ }
+
+ @Override
+ public boolean onLongClick(View view) {
+ if (mIsScrollable) {
+ DragLayer dragLayer = Launcher.getLauncher(getContext()).getDragLayer();
+ dragLayer.requestDisallowInterceptTouchEvent(false);
+ }
+ view.performLongClick();
+ return true;
}
@Override
protected View getErrorView() {
- return mInflater.inflate(mErrorViewId, this, false);
+ return mInflater.inflate(R.layout.appwidget_error, this, false);
}
public void updateLastInflationOrientation() {
@@ -78,6 +132,25 @@
// Store the orientation in which the widget was inflated
updateLastInflationOrientation();
super.updateAppWidget(remoteViews);
+
+ // The provider info or the views might have changed.
+ checkIfAutoAdvance();
+ }
+
+ private boolean checkScrollableRecursively(ViewGroup viewGroup) {
+ if (viewGroup instanceof AdapterView) {
+ return true;
+ } else {
+ for (int i=0; i < viewGroup.getChildCount(); i++) {
+ View child = viewGroup.getChildAt(i);
+ if (child instanceof ViewGroup) {
+ if (checkScrollableRecursively((ViewGroup) child)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
}
public boolean isReinflateRequired() {
@@ -108,12 +181,18 @@
mLongPressHelper.cancelLongPress();
return true;
}
+
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
+ DragLayer dragLayer = Launcher.getLauncher(getContext()).getDragLayer();
+
+ if (mIsScrollable) {
+ dragLayer.requestDisallowInterceptTouchEvent(true);
+ }
if (!mStylusEventHelper.inStylusButtonPressed()) {
mLongPressHelper.postCheckForLongPress();
}
- Launcher.getLauncher(getContext()).getDragLayer().setTouchCompleteListener(this);
+ dragLayer.setTouchCompleteListener(this);
break;
}
@@ -153,6 +232,19 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+
+ mIsAttachedToWindow = true;
+ checkIfAutoAdvance();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ // We can't directly use isAttachedToWindow() here, as this is called before the internal
+ // state is updated. So isAttachedToWindow() will return true until next frame.
+ mIsAttachedToWindow = false;
+ checkIfAutoAdvance();
}
@Override
@@ -171,10 +263,6 @@
return info;
}
- public LauncherAppWidgetProviderInfo getLauncherAppWidgetProviderInfo() {
- return (LauncherAppWidgetProviderInfo) getAppWidgetInfo();
- }
-
@Override
public void onTouchComplete() {
if (!mLongPressHelper.hasPerformedLongPress()) {
@@ -276,6 +364,11 @@
setSelected(childIsFocused);
}
+ public void switchToErrorView() {
+ // Update the widget with 0 Layout id, to reset the view to error view.
+ updateAppWidget(new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0));
+ }
+
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
try {
@@ -284,11 +377,12 @@
post(new Runnable() {
@Override
public void run() {
- // Update the widget with 0 Layout id, to reset the view to error view.
- updateAppWidget(new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0));
+ switchToErrorView();
}
});
}
+
+ mIsScrollable = checkScrollableRecursively(this);
}
@Override
@@ -296,4 +390,99 @@
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(getClass().getName());
}
+
+ @Override
+ protected void onWindowVisibilityChanged(int visibility) {
+ super.onWindowVisibilityChanged(visibility);
+ maybeRegisterAutoAdvance();
+ }
+
+ private void checkIfAutoAdvance() {
+ boolean isAutoAdvance = false;
+ Advanceable target = getAdvanceable();
+ if (target != null) {
+ isAutoAdvance = true;
+ target.fyiWillBeAdvancedByHostKThx();
+ }
+
+ boolean wasAutoAdvance = sAutoAdvanceWidgetIds.indexOfKey(getAppWidgetId()) >= 0;
+ if (isAutoAdvance != wasAutoAdvance) {
+ if (isAutoAdvance) {
+ sAutoAdvanceWidgetIds.put(getAppWidgetId(), true);
+ } else {
+ sAutoAdvanceWidgetIds.delete(getAppWidgetId());
+ }
+ maybeRegisterAutoAdvance();
+ }
+ }
+
+ private Advanceable getAdvanceable() {
+ AppWidgetProviderInfo info = getAppWidgetInfo();
+ if (info == null || info.autoAdvanceViewId == NO_ID || !mIsAttachedToWindow) {
+ return null;
+ }
+ View v = findViewById(info.autoAdvanceViewId);
+ return (v instanceof Advanceable) ? (Advanceable) v : null;
+ }
+
+ private void maybeRegisterAutoAdvance() {
+ Handler handler = getHandler();
+ boolean shouldRegisterAutoAdvance = getWindowVisibility() == VISIBLE && handler != null
+ && (sAutoAdvanceWidgetIds.indexOfKey(getAppWidgetId()) >= 0);
+ if (shouldRegisterAutoAdvance != mIsAutoAdvanceRegistered) {
+ mIsAutoAdvanceRegistered = shouldRegisterAutoAdvance;
+ if (mAutoAdvanceRunnable == null) {
+ mAutoAdvanceRunnable = new Runnable() {
+ @Override
+ public void run() {
+ runAutoAdvance();
+ }
+ };
+ }
+
+ handler.removeCallbacks(mAutoAdvanceRunnable);
+ scheduleNextAdvance();
+ }
+ }
+
+ private void scheduleNextAdvance() {
+ if (!mIsAutoAdvanceRegistered) {
+ return;
+ }
+ long now = SystemClock.uptimeMillis();
+ long advanceTime = now + (ADVANCE_INTERVAL - (now % ADVANCE_INTERVAL)) +
+ ADVANCE_STAGGER * sAutoAdvanceWidgetIds.indexOfKey(getAppWidgetId());
+ Handler handler = getHandler();
+ if (handler != null) {
+ handler.postAtTime(mAutoAdvanceRunnable, advanceTime);
+ }
+ }
+
+ private void runAutoAdvance() {
+ Advanceable target = getAdvanceable();
+ if (target != null) {
+ target.advance();
+ }
+ scheduleNextAdvance();
+ }
+
+ public void setScaleToFit(float scale) {
+ mScaleToFit = scale;
+ setScaleX(scale);
+ setScaleY(scale);
+ }
+
+ public float getScaleToFit() {
+ return mScaleToFit;
+ }
+
+ public void setTranslationForCentering(float x, float y) {
+ mTranslationForCentering.set(x, y);
+ setTranslationX(x);
+ setTranslationY(y);
+ }
+
+ public PointF getTranslationForCentering() {
+ return mTranslationForCentering;
+ }
}
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
index 66d8957..1e0f285 100644
--- a/src/com/android/launcher3/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -18,11 +18,10 @@
import android.appwidget.AppWidgetHostView;
import android.content.ComponentName;
-import android.content.ContentValues;
-import android.content.Context;
import android.content.Intent;
+import android.os.Process;
-import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.ContentWriter;
/**
* Represents a widget (either instantiated or about to be) in the Launcher.
@@ -66,7 +65,7 @@
/**
* Indicates that the widget hasn't been instantiated yet.
*/
- static final int NO_ID = -1;
+ public static final int NO_ID = -1;
/**
* Indicates that this is a locally defined widget and hence has no system allocated id.
@@ -77,19 +76,19 @@
* Identifier for this widget when talking with
* {@link android.appwidget.AppWidgetManager} for updates.
*/
- int appWidgetId = NO_ID;
+ public int appWidgetId = NO_ID;
public ComponentName providerName;
/**
* Indicates the restore status of the widget.
*/
- int restoreStatus;
+ public int restoreStatus;
/**
* Indicates the installation progress of the widget provider
*/
- int installProgress = -1;
+ public int installProgress = -1;
/**
* Optional extras sent during widget bind. See {@link #FLAG_DIRECT_CONFIG}.
@@ -98,7 +97,7 @@
private boolean mHasNotifiedInitialWidgetSizeChanged;
- LauncherAppWidgetInfo(int appWidgetId, ComponentName providerName) {
+ public LauncherAppWidgetInfo(int appWidgetId, ComponentName providerName) {
if (appWidgetId == CUSTOM_WIDGET_ID) {
itemType = LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
} else {
@@ -113,22 +112,26 @@
spanX = -1;
spanY = -1;
// We only support app widgets on current user.
- user = UserHandleCompat.myUserHandle();
+ user = Process.myUserHandle();
restoreStatus = RESTORE_COMPLETED;
}
+ /** Used for testing **/
+ public LauncherAppWidgetInfo() {
+ itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+ }
+
public boolean isCustomWidget() {
return appWidgetId == CUSTOM_WIDGET_ID;
}
@Override
- void onAddToDatabase(Context context, ContentValues values) {
- super.onAddToDatabase(context, values);
- values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId);
- values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, providerName.flattenToString());
- values.put(LauncherSettings.Favorites.RESTORED, restoreStatus);
- values.put(LauncherSettings.Favorites.INTENT,
- bindOptions == null ? null : bindOptions.toUri(0));
+ public void onAddToDatabase(ContentWriter writer) {
+ super.onAddToDatabase(writer);
+ writer.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId)
+ .put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, providerName.flattenToString())
+ .put(LauncherSettings.Favorites.RESTORED, restoreStatus)
+ .put(LauncherSettings.Favorites.INTENT, bindOptions);
}
/**
diff --git a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
index 1a4153f..6cb703b 100644
--- a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
@@ -1,6 +1,5 @@
package com.android.launcher3;
-import android.annotation.TargetApi;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
@@ -9,8 +8,9 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.os.Build;
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
@@ -29,22 +29,27 @@
public static LauncherAppWidgetProviderInfo fromProviderInfo(Context context,
AppWidgetProviderInfo info) {
+ final LauncherAppWidgetProviderInfo launcherInfo;
+ if (info instanceof LauncherAppWidgetProviderInfo) {
+ launcherInfo = (LauncherAppWidgetProviderInfo) info;
+ } else {
- // In lieu of a public super copy constructor, we first write the AppWidgetProviderInfo
- // into a parcel, and then construct a new LauncherAppWidgetProvider info from the
- // associated super parcel constructor. This allows us to copy non-public members without
- // using reflection.
- Parcel p = Parcel.obtain();
- info.writeToParcel(p, 0);
- p.setDataPosition(0);
- LauncherAppWidgetProviderInfo lawpi = new LauncherAppWidgetProviderInfo(p);
- p.recycle();
- return lawpi;
+ // In lieu of a public super copy constructor, we first write the AppWidgetProviderInfo
+ // into a parcel, and then construct a new LauncherAppWidgetProvider info from the
+ // associated super parcel constructor. This allows us to copy non-public members without
+ // using reflection.
+ Parcel p = Parcel.obtain();
+ info.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ launcherInfo = new LauncherAppWidgetProviderInfo(p);
+ p.recycle();
+ }
+ launcherInfo.initSpans(context);
+ return launcherInfo;
}
- public LauncherAppWidgetProviderInfo(Parcel in) {
+ private LauncherAppWidgetProviderInfo(Parcel in) {
super(in);
- initSpans();
}
public LauncherAppWidgetProviderInfo(Context context, CustomAppWidget widget) {
@@ -56,12 +61,11 @@
previewImage = widget.getPreviewImage();
initialLayout = widget.getWidgetLayout();
resizeMode = widget.getResizeMode();
- initSpans();
+ initSpans(context);
}
- public void initSpans() {
- LauncherAppState app = LauncherAppState.getInstance();
- InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
+ public void initSpans(Context context) {
+ InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
Point paddingLand = idp.landscapeProfile.getTotalWorkspacePadding();
Point paddingPort = idp.portraitProfile.getTotalWorkspacePadding();
@@ -80,7 +84,7 @@
// We want to account for the extra amount of padding that we are adding to the widget
// to ensure that it gets the full amount of space that it has requested.
Rect widgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(
- app.getContext(), provider, null);
+ context, provider, null);
spanX = Math.max(1, (int) Math.ceil(
(minWidth + widgetPadding.left + widgetPadding.right) / smallestCellWidth));
spanY = Math.max(1, (int) Math.ceil(
@@ -92,7 +96,6 @@
(minResizeHeight + widgetPadding.top + widgetPadding.bottom) / smallestCellHeight));
}
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
public String getLabel(PackageManager packageManager) {
if (isCustomWidget) {
return Utilities.trim(label);
@@ -100,13 +103,11 @@
return super.loadLabel(packageManager);
}
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
public Drawable getIcon(Context context, IconCache cache) {
if (isCustomWidget) {
return cache.getFullResIcon(provider.getPackageName(), icon);
}
- return super.loadIcon(context,
- LauncherAppState.getInstance().getInvariantDeviceProfile().fillResIconDpi);
+ return super.loadIcon(context, LauncherAppState.getIDP(context).fillResIconDpi);
}
public String toString(PackageManager pm) {
@@ -122,4 +123,8 @@
(resizeMode & RESIZE_HORIZONTAL) != 0 ? minSpanX : -1,
(resizeMode & RESIZE_VERTICAL) != 0 ? minSpanY : -1);
}
+
+ public UserHandle getUser() {
+ return isCustomWidget ? Process.myUserHandle() : getProfile();
+ }
}
diff --git a/src/com/android/launcher3/LauncherBackupAgent.java b/src/com/android/launcher3/LauncherBackupAgent.java
index b3e73f7..140794b 100644
--- a/src/com/android/launcher3/LauncherBackupAgent.java
+++ b/src/com/android/launcher3/LauncherBackupAgent.java
@@ -5,11 +5,19 @@
import android.app.backup.BackupDataOutput;
import android.os.ParcelFileDescriptor;
+import com.android.launcher3.logging.FileLog;
import com.android.launcher3.provider.RestoreDbTask;
public class LauncherBackupAgent extends BackupAgent {
@Override
+ public void onCreate() {
+ super.onCreate();
+ // Set the log dir as LauncherAppState is not initialized during restore.
+ FileLog.setDir(getFilesDir());
+ }
+
+ @Override
public void onRestore(
BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) {
// Doesn't do incremental backup/restore
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
index 6394b90..2bac11f 100644
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ b/src/com/android/launcher3/LauncherCallbacks.java
@@ -92,7 +92,6 @@
/*
* Extensions points for adding / replacing some other aspects of the Launcher experience.
*/
- public UserEventDispatcher getUserEventDispatcher();
public boolean shouldMoveToDefaultScreenOnHomeIntent();
public boolean hasSettings();
public AllAppsSearchBarController getAllAppsSearchBarController();
diff --git a/src/com/android/launcher3/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java
deleted file mode 100644
index c1282b5..0000000
--- a/src/com/android/launcher3/LauncherClings.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.content.Context;
-
-@Deprecated
-public class LauncherClings {
- private static final String WORKSPACE_CLING_DISMISSED_KEY = "cling_gel.workspace.dismissed";
-
- public static void markFirstRunClingDismissed(Context ctx) {
- Utilities.getPrefs(ctx).edit()
- .putBoolean(WORKSPACE_CLING_DISMISSED_KEY, true)
- .apply();
- }
-}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 0199d0c..2586764 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -24,41 +24,47 @@
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
-import android.content.Intent.ShortcutIconResource;
import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.database.Cursor;
-import android.graphics.Bitmap;
+import android.content.pm.LauncherActivityInfo;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
-import android.os.Parcelable;
import android.os.Process;
import android.os.SystemClock;
import android.os.Trace;
-import android.provider.BaseColumns;
+import android.os.UserHandle;
+import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.MutableInt;
-import android.util.Pair;
import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.LauncherActivityInfoCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
-import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.dynamicui.ExtractionUtils;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.graphics.LauncherIcons;
import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.model.AddWorkspaceItemsTask;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.CacheDataUpdatedTask;
+import com.android.launcher3.model.ExtendedModelTask;
import com.android.launcher3.model.GridSizeMigrationTask;
+import com.android.launcher3.model.LoaderCursor;
+import com.android.launcher3.model.ModelWriter;
+import com.android.launcher3.model.PackageInstallStateChangedTask;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.model.PackageUpdatedTask;
+import com.android.launcher3.model.SdCardAvailableReceiver;
+import com.android.launcher3.model.ShortcutsChangedTask;
+import com.android.launcher3.model.UserLockStateChangedTask;
+import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.provider.ImportDataTask;
import com.android.launcher3.provider.LauncherDbUtils;
@@ -66,23 +72,19 @@
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.CursorIconInfo;
-import com.android.launcher3.util.FlagOp;
-import com.android.launcher3.util.GridOccupancy;
-import com.android.launcher3.util.LongArrayMap;
import com.android.launcher3.util.ManagedProfileHeuristic;
import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.StringFilter;
+import com.android.launcher3.util.Provider;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.ViewOnDrawExecutor;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.lang.ref.WeakReference;
-import java.net.URISyntaxException;
-import java.security.InvalidParameterException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@@ -90,8 +92,8 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.Set;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor;
/**
@@ -123,12 +125,16 @@
}
@Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());
- // We start off with everything not loaded. After that, we assume that
+ // Indicates whether the current model data is valid or not.
+ // We start off with everything not loaded. After that, we assume that
// our monitoring of the package manager provides all updates and we never
- // need to do a requery. These are only ever touched from the loader thread.
- private boolean mWorkspaceLoaded;
- private boolean mAllAppsLoaded;
- private boolean mDeepShortcutsLoaded;
+ // need to do a requery. This is only ever touched from the loader thread.
+ private boolean mModelLoaded;
+ public boolean isModelLoaded() {
+ synchronized (mLock) {
+ return mModelLoaded && mLoaderTask == null;
+ }
+ }
/**
* Set of runnables to be called on the background thread after the workspace binding
@@ -143,60 +149,30 @@
// Entire list of widgets.
private final WidgetsModel mBgWidgetsModel;
- // Maps all launcher activities to the id's of their shortcuts (if they have any).
- private final MultiHashMap<ComponentKey, String> mBgDeepShortcutMap = new MultiHashMap<>();
-
private boolean mHasShortcutHostPermission;
// Runnable to check if the shortcuts permission has changed.
private final Runnable mShortcutPermissionCheckRunnable = new Runnable() {
@Override
public void run() {
- if (mDeepShortcutsLoaded) {
- boolean hasShortcutHostPermission = mDeepShortcutManager.hasHostPermission();
+ if (mModelLoaded) {
+ boolean hasShortcutHostPermission =
+ DeepShortcutManager.getInstance(mApp.getContext()).hasHostPermission();
if (hasShortcutHostPermission != mHasShortcutHostPermission) {
- mApp.reloadWorkspace();
+ forceReload();
}
}
}
};
- // The lock that must be acquired before referencing any static bg data structures. Unlike
- // other locks, this one can generally be held long-term because we never expect any of these
- // static data structures to be referenced outside of the worker thread except on the first
- // load after configuration change.
- static final Object sBgLock = new Object();
-
- // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
- // LauncherModel to their ids
- static final LongArrayMap<ItemInfo> sBgItemsIdMap = new LongArrayMap<>();
-
- // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
- // created by LauncherModel that are directly on the home screen (however, no widgets or
- // shortcuts within folders).
- static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
-
- // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
- static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
- new ArrayList<LauncherAppWidgetInfo>();
-
- // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
- static final LongArrayMap<FolderInfo> sBgFolders = new LongArrayMap<>();
-
- // sBgWorkspaceScreens is the ordered set of workspace screens.
- static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
-
- // sBgPinnedShortcutCounts is the ComponentKey representing a pinned shortcut to the number of
- // times it is pinned.
- static final Map<ShortcutKey, MutableInt> sBgPinnedShortcutCounts = new HashMap<>();
-
- // sPendingPackages is a set of packages which could be on sdcard and are not available yet
- static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages =
- new HashMap<UserHandleCompat, HashSet<String>>();
+ /**
+ * All the static data should be accessed on the background thread, A lock should be acquired
+ * on this object when accessing any data from this model.
+ */
+ static final BgDataModel sBgDataModel = new BgDataModel();
// </ only access in worker thread >
- private IconCache mIconCache;
- private DeepShortcutManager mDeepShortcutManager;
+ private final IconCache mIconCache;
private final LauncherAppsCompat mLauncherApps;
private final UserManagerCompat mUserManager;
@@ -219,32 +195,26 @@
ArrayList<AppInfo> addedApps);
public void bindAppsUpdated(ArrayList<AppInfo> apps);
public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,
- ArrayList<ShortcutInfo> removed, UserHandleCompat user);
+ ArrayList<ShortcutInfo> removed, UserHandle user);
public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
public void bindWorkspaceComponentsRemoved(
HashSet<String> packageNames, HashSet<ComponentName> components,
- UserHandleCompat user);
+ UserHandle user);
public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos);
public void notifyWidgetProvidersChanged();
- public void bindWidgetsModel(WidgetsModel model);
+ public void bindAllWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets);
public void onPageBoundSynchronously(int page);
public void executeOnNextDraw(ViewOnDrawExecutor executor);
public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap);
}
- public interface ItemInfoFilter {
- public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
- }
-
- LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter,
- DeepShortcutManager deepShortcutManager) {
+ LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
Context context = app.getContext();
mApp = app;
mBgAllAppsList = new AllAppsList(iconCache, appFilter);
- mBgWidgetsModel = new WidgetsModel(context, iconCache, appFilter);
+ mBgWidgetsModel = new WidgetsModel(iconCache, appFilter);
mIconCache = iconCache;
- mDeepShortcutManager = deepShortcutManager;
mLauncherApps = LauncherAppsCompat.getInstance(context);
mUserManager = UserManagerCompat.getInstance(context);
@@ -272,314 +242,42 @@
}
}
- public void setPackageState(final PackageInstallInfo installInfo) {
- Runnable updateRunnable = new Runnable() {
-
- @Override
- public void run() {
- synchronized (sBgLock) {
- final HashSet<ItemInfo> updates = new HashSet<>();
-
- if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
- // Ignore install success events as they are handled by Package add events.
- return;
- }
-
- for (ItemInfo info : sBgItemsIdMap) {
- if (info instanceof ShortcutInfo) {
- ShortcutInfo si = (ShortcutInfo) info;
- ComponentName cn = si.getTargetComponent();
- if (si.isPromise() && (cn != null)
- && installInfo.packageName.equals(cn.getPackageName())) {
- si.setInstallProgress(installInfo.progress);
-
- if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
- // Mark this info as broken.
- si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
- }
- updates.add(si);
- }
- }
- }
-
- for (LauncherAppWidgetInfo widget : sBgAppWidgets) {
- if (widget.providerName.getPackageName().equals(installInfo.packageName)) {
- widget.installProgress = installInfo.progress;
- updates.add(widget);
- }
- }
-
- if (!updates.isEmpty()) {
- // Push changes to the callback.
- Runnable r = new Runnable() {
- public void run() {
- Callbacks callbacks = getCallback();
- if (callbacks != null) {
- callbacks.bindRestoreItemsChange(updates);
- }
- }
- };
- mHandler.post(r);
- }
- }
- }
- };
- runOnWorkerThread(updateRunnable);
+ public void setPackageState(PackageInstallInfo installInfo) {
+ enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo));
}
/**
* Updates the icons and label of all pending icons for the provided package name.
*/
public void updateSessionDisplayInfo(final String packageName) {
- Runnable updateRunnable = new Runnable() {
-
- @Override
- public void run() {
- synchronized (sBgLock) {
- ArrayList<ShortcutInfo> updates = new ArrayList<>();
- UserHandleCompat user = UserHandleCompat.myUserHandle();
-
- for (ItemInfo info : sBgItemsIdMap) {
- if (info instanceof ShortcutInfo) {
- ShortcutInfo si = (ShortcutInfo) info;
- ComponentName cn = si.getTargetComponent();
- if (si.isPromise() && (cn != null)
- && packageName.equals(cn.getPackageName())) {
- if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
- // For auto install apps update the icon as well as label.
- mIconCache.getTitleAndIcon(si,
- si.promisedIntent, user,
- si.shouldUseLowResIcon());
- } else {
- // Only update the icon for restored apps.
- si.updateIcon(mIconCache);
- }
- updates.add(si);
- }
- }
- }
-
- bindUpdatedShortcuts(updates, user);
- }
- }
- };
- runOnWorkerThread(updateRunnable);
- }
-
- public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
- final Callbacks callbacks = getCallback();
-
- if (allAppsApps == null) {
- throw new RuntimeException("allAppsApps must not be null");
- }
- if (allAppsApps.isEmpty()) {
- return;
- }
-
- // Process the newly added applications and add them to the database first
- Runnable r = new Runnable() {
- public void run() {
- runOnMainThread(new Runnable() {
- public void run() {
- Callbacks cb = getCallback();
- if (callbacks == cb && cb != null) {
- callbacks.bindAppsAdded(null, null, null, allAppsApps);
- }
- }
- });
- }
- };
- runOnWorkerThread(r);
- }
-
- private static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> occupiedPos,
- int[] xy, int spanX, int spanY) {
- LauncherAppState app = LauncherAppState.getInstance();
- InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
-
- GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows);
- if (occupiedPos != null) {
- for (ItemInfo r : occupiedPos) {
- occupied.markCells(r, true);
- }
- }
- return occupied.findVacantCell(xy, spanX, spanY);
- }
-
- /**
- * Find a position on the screen for the given size or adds a new screen.
- * @return screenId and the coordinates for the item.
- */
- @Thunk Pair<Long, int[]> findSpaceForItem(
- Context context,
- ArrayList<Long> workspaceScreens,
- ArrayList<Long> addedWorkspaceScreensFinal,
- int spanX, int spanY) {
- LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
-
- // Use sBgItemsIdMap as all the items are already loaded.
- assertWorkspaceLoaded();
- synchronized (sBgLock) {
- for (ItemInfo info : sBgItemsIdMap) {
- if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
- ArrayList<ItemInfo> items = screenItems.get(info.screenId);
- if (items == null) {
- items = new ArrayList<>();
- screenItems.put(info.screenId, items);
- }
- items.add(info);
- }
- }
- }
-
- // Find appropriate space for the item.
- long screenId = 0;
- int[] cordinates = new int[2];
- boolean found = false;
-
- int screenCount = workspaceScreens.size();
- // First check the preferred screen.
- int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
- if (preferredScreenIndex < screenCount) {
- screenId = workspaceScreens.get(preferredScreenIndex);
- found = findNextAvailableIconSpaceInScreen(
- screenItems.get(screenId), cordinates, spanX, spanY);
- }
-
- if (!found) {
- // Search on any of the screens starting from the first screen.
- for (int screen = 1; screen < screenCount; screen++) {
- screenId = workspaceScreens.get(screen);
- if (findNextAvailableIconSpaceInScreen(
- screenItems.get(screenId), cordinates, spanX, spanY)) {
- // We found a space for it
- found = true;
- break;
- }
- }
- }
-
- if (!found) {
- // Still no position found. Add a new screen to the end.
- screenId = LauncherSettings.Settings.call(context.getContentResolver(),
- LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
- .getLong(LauncherSettings.Settings.EXTRA_VALUE);
-
- // Save the screen id for binding in the workspace
- workspaceScreens.add(screenId);
- addedWorkspaceScreensFinal.add(screenId);
-
- // If we still can't find an empty space, then God help us all!!!
- if (!findNextAvailableIconSpaceInScreen(
- screenItems.get(screenId), cordinates, spanX, spanY)) {
- throw new RuntimeException("Can't find space to add the item");
- }
- }
- return Pair.create(screenId, cordinates);
+ HashSet<String> packages = new HashSet<>();
+ packages.add(packageName);
+ enqueueModelUpdateTask(new CacheDataUpdatedTask(
+ CacheDataUpdatedTask.OP_SESSION_UPDATE, Process.myUserHandle(), packages));
}
/**
* Adds the provided items to the workspace.
*/
- public void addAndBindAddedWorkspaceItems(final Context context,
- final ArrayList<? extends ItemInfo> workspaceApps) {
- final Callbacks callbacks = getCallback();
- if (workspaceApps.isEmpty()) {
- return;
- }
- // Process the newly added applications and add them to the database first
- Runnable r = new Runnable() {
- public void run() {
- final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
- final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
-
- // Get the list of workspace screens. We need to append to this list and
- // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
- // called.
- ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
- synchronized(sBgLock) {
- for (ItemInfo item : workspaceApps) {
- if (item instanceof ShortcutInfo) {
- // Short-circuit this logic if the icon exists somewhere on the workspace
- if (shortcutExists(context, item.getIntent(), item.user)) {
- continue;
- }
- }
-
- // Find appropriate space for the item.
- Pair<Long, int[]> coords = findSpaceForItem(context,
- workspaceScreens, addedWorkspaceScreensFinal, 1, 1);
- long screenId = coords.first;
- int[] cordinates = coords.second;
-
- ItemInfo itemInfo;
- if (item instanceof ShortcutInfo || item instanceof FolderInfo) {
- itemInfo = item;
- } else if (item instanceof AppInfo) {
- itemInfo = ((AppInfo) item).makeShortcut();
- } else {
- throw new RuntimeException("Unexpected info type");
- }
-
- // Add the shortcut to the db
- addItemToDatabase(context, itemInfo,
- LauncherSettings.Favorites.CONTAINER_DESKTOP,
- screenId, cordinates[0], cordinates[1]);
- // Save the ShortcutInfo for binding in the workspace
- addedShortcutsFinal.add(itemInfo);
- }
- }
-
- // Update the workspace screens
- updateWorkspaceScreenOrder(context, workspaceScreens);
-
- if (!addedShortcutsFinal.isEmpty()) {
- runOnMainThread(new Runnable() {
- public void run() {
- Callbacks cb = getCallback();
- if (callbacks == cb && cb != null) {
- final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
- final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
- if (!addedShortcutsFinal.isEmpty()) {
- ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 1);
- long lastScreenId = info.screenId;
- for (ItemInfo i : addedShortcutsFinal) {
- if (i.screenId == lastScreenId) {
- addAnimated.add(i);
- } else {
- addNotAnimated.add(i);
- }
- }
- }
- callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
- addNotAnimated, addAnimated, null);
- }
- }
- });
- }
- }
- };
- runOnWorkerThread(r);
+ public void addAndBindAddedWorkspaceItems(List<ItemInfo> workspaceApps) {
+ addAndBindAddedWorkspaceItems(Provider.of(workspaceApps));
}
/**
- * Adds an item to the DB if it was not created previously, or move it to a new
- * <container, screen, cellX, cellY>
+ * Adds the provided items to the workspace.
*/
- public static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
- long screenId, int cellX, int cellY) {
- if (item.container == ItemInfo.NO_ID) {
- // From all apps
- addItemToDatabase(context, item, container, screenId, cellX, cellY);
- } else {
- // From somewhere else
- moveItemInDatabase(context, item, container, screenId, cellX, cellY);
- }
+ public void addAndBindAddedWorkspaceItems(
+ Provider<List<ItemInfo>> appsProvider) {
+ enqueueModelUpdateTask(new AddWorkspaceItemsTask(appsProvider));
+ }
+
+ public ModelWriter getWriter(boolean hasVerticalHotseat) {
+ return new ModelWriter(mApp.getContext(), sBgDataModel, hasVerticalHotseat);
}
static void checkItemInfoLocked(
final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
- ItemInfo modelItem = sBgItemsIdMap.get(itemId);
+ ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId);
if (modelItem != null && item != modelItem) {
// check all the data is consistent
if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
@@ -620,7 +318,7 @@
final long itemId = item.id;
Runnable r = new Runnable() {
public void run() {
- synchronized (sBgLock) {
+ synchronized (sBgDataModel) {
checkItemInfoLocked(itemId, item, stackTrace);
}
}
@@ -628,439 +326,11 @@
runOnWorkerThread(r);
}
- static void updateItemInDatabaseHelper(Context context, final ContentValues values,
- final ItemInfo item, final String callingFunction) {
- final long itemId = item.id;
- final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
- final ContentResolver cr = context.getContentResolver();
-
- final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
- Runnable r = new Runnable() {
- public void run() {
- cr.update(uri, values, null, null);
- updateItemArrays(item, itemId, stackTrace);
- }
- };
- runOnWorkerThread(r);
- }
-
- static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList,
- final ArrayList<ItemInfo> items, final String callingFunction) {
- final ContentResolver cr = context.getContentResolver();
-
- final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
- Runnable r = new Runnable() {
- public void run() {
- ArrayList<ContentProviderOperation> ops =
- new ArrayList<ContentProviderOperation>();
- int count = items.size();
- for (int i = 0; i < count; i++) {
- ItemInfo item = items.get(i);
- final long itemId = item.id;
- final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
- ContentValues values = valuesList.get(i);
-
- ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
- updateItemArrays(item, itemId, stackTrace);
-
- }
- try {
- cr.applyBatch(LauncherProvider.AUTHORITY, ops);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- };
- runOnWorkerThread(r);
- }
-
- static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
- // Lock on mBgLock *after* the db operation
- synchronized (sBgLock) {
- checkItemInfoLocked(itemId, item, stackTrace);
-
- if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
- item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- // Item is in a folder, make sure this folder exists
- if (!sBgFolders.containsKey(item.container)) {
- // An items container is being set to a that of an item which is not in
- // the list of Folders.
- String msg = "item: " + item + " container being set to: " +
- item.container + ", not in the list of folders";
- Log.e(TAG, msg);
- }
- }
-
- // Items are added/removed from the corresponding FolderInfo elsewhere, such
- // as in Workspace.onDrop. Here, we just add/remove them from the list of items
- // that are on the desktop, as appropriate
- ItemInfo modelItem = sBgItemsIdMap.get(itemId);
- if (modelItem != null &&
- (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
- modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
- switch (modelItem.itemType) {
- case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
- case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
- case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
- case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
- if (!sBgWorkspaceItems.contains(modelItem)) {
- sBgWorkspaceItems.add(modelItem);
- }
- break;
- default:
- break;
- }
- } else {
- sBgWorkspaceItems.remove(modelItem);
- }
- }
- }
-
- /**
- * Move an item in the DB to a new <container, screen, cellX, cellY>
- */
- public static void moveItemInDatabase(Context context, final ItemInfo item, final long container,
- final long screenId, final int cellX, final int cellY) {
- item.container = container;
- item.cellX = cellX;
- item.cellY = cellY;
-
- // We store hotseat items in canonical form which is this orientation invariant position
- // in the hotseat
- if (context instanceof Launcher && screenId < 0 &&
- container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- item.screenId = Launcher.getLauncher(context).getHotseat()
- .getOrderInHotseat(cellX, cellY);
- } else {
- item.screenId = screenId;
- }
-
- final ContentValues values = new ContentValues();
- values.put(LauncherSettings.Favorites.CONTAINER, item.container);
- values.put(LauncherSettings.Favorites.CELLX, item.cellX);
- values.put(LauncherSettings.Favorites.CELLY, item.cellY);
- values.put(LauncherSettings.Favorites.RANK, item.rank);
- values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
-
- updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
- }
-
- /**
- * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
- * cellX, cellY have already been updated on the ItemInfos.
- */
- public static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items,
- final long container, final int screen) {
-
- ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>();
- int count = items.size();
-
- for (int i = 0; i < count; i++) {
- ItemInfo item = items.get(i);
- item.container = container;
-
- // We store hotseat items in canonical form which is this orientation invariant position
- // in the hotseat
- if (context instanceof Launcher && screen < 0 &&
- container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- item.screenId = Launcher.getLauncher(context).getHotseat().getOrderInHotseat(item.cellX,
- item.cellY);
- } else {
- item.screenId = screen;
- }
-
- final ContentValues values = new ContentValues();
- values.put(LauncherSettings.Favorites.CONTAINER, item.container);
- values.put(LauncherSettings.Favorites.CELLX, item.cellX);
- values.put(LauncherSettings.Favorites.CELLY, item.cellY);
- values.put(LauncherSettings.Favorites.RANK, item.rank);
- values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
-
- contentValues.add(values);
- }
- updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase");
- }
-
- /**
- * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
- */
- static void modifyItemInDatabase(Context context, final ItemInfo item, final long container,
- final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) {
- item.container = container;
- item.cellX = cellX;
- item.cellY = cellY;
- item.spanX = spanX;
- item.spanY = spanY;
-
- // We store hotseat items in canonical form which is this orientation invariant position
- // in the hotseat
- if (context instanceof Launcher && screenId < 0 &&
- container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- item.screenId = Launcher.getLauncher(context).getHotseat()
- .getOrderInHotseat(cellX, cellY);
- } else {
- item.screenId = screenId;
- }
-
- final ContentValues values = new ContentValues();
- values.put(LauncherSettings.Favorites.CONTAINER, item.container);
- values.put(LauncherSettings.Favorites.CELLX, item.cellX);
- values.put(LauncherSettings.Favorites.CELLY, item.cellY);
- values.put(LauncherSettings.Favorites.RANK, item.rank);
- values.put(LauncherSettings.Favorites.SPANX, item.spanX);
- values.put(LauncherSettings.Favorites.SPANY, item.spanY);
- values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
-
- updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
- }
-
- /**
- * Update an item to the database in a specified container.
- */
- public static void updateItemInDatabase(Context context, final ItemInfo item) {
- final ContentValues values = new ContentValues();
- item.onAddToDatabase(context, values);
- updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
- }
-
- private void assertWorkspaceLoaded() {
- if (ProviderConfig.IS_DOGFOOD_BUILD) {
- synchronized (mLock) {
- if (!mHasLoaderCompletedOnce ||
- (mLoaderTask != null && mLoaderTask.mIsLoadingAndBindingWorkspace)) {
- throw new RuntimeException("Trying to add shortcut while loader is running");
- }
- }
- }
- }
-
- /**
- * Returns true if the shortcuts already exists on the workspace. This must be called after
- * the workspace has been loaded. We identify a shortcut by its intent.
- */
- @Thunk boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) {
- assertWorkspaceLoaded();
- final String intentWithPkg, intentWithoutPkg;
- if (intent.getComponent() != null) {
- // If component is not null, an intent with null package will produce
- // the same result and should also be a match.
- String packageName = intent.getComponent().getPackageName();
- if (intent.getPackage() != null) {
- intentWithPkg = intent.toUri(0);
- intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0);
- } else {
- intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0);
- intentWithoutPkg = intent.toUri(0);
- }
- } else {
- intentWithPkg = intent.toUri(0);
- intentWithoutPkg = intent.toUri(0);
- }
-
- synchronized (sBgLock) {
- for (ItemInfo item : sBgItemsIdMap) {
- if (item instanceof ShortcutInfo) {
- ShortcutInfo info = (ShortcutInfo) item;
- Intent targetIntent = info.promisedIntent == null
- ? info.intent : info.promisedIntent;
- if (targetIntent != null && info.user.equals(user)) {
- Intent copyIntent = new Intent(targetIntent);
- copyIntent.setSourceBounds(intent.getSourceBounds());
- String s = copyIntent.toUri(0);
- if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
- return true;
- }
- }
- }
- }
- }
- return false;
- }
-
- /**
- * Add an item to the database in a specified container. Sets the container, screen, cellX and
- * cellY fields of the item. Also assigns an ID to the item.
- */
- public static void addItemToDatabase(Context context, final ItemInfo item, final long container,
- final long screenId, final int cellX, final int cellY) {
- item.container = container;
- item.cellX = cellX;
- item.cellY = cellY;
- // We store hotseat items in canonical form which is this orientation invariant position
- // in the hotseat
- if (context instanceof Launcher && screenId < 0 &&
- container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- item.screenId = Launcher.getLauncher(context).getHotseat()
- .getOrderInHotseat(cellX, cellY);
- } else {
- item.screenId = screenId;
- }
-
- final ContentValues values = new ContentValues();
- final ContentResolver cr = context.getContentResolver();
- item.onAddToDatabase(context, values);
-
- item.id = LauncherSettings.Settings.call(cr, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
- .getLong(LauncherSettings.Settings.EXTRA_VALUE);
-
- values.put(LauncherSettings.Favorites._ID, item.id);
-
- final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
- Runnable r = new Runnable() {
- public void run() {
- cr.insert(LauncherSettings.Favorites.CONTENT_URI, values);
-
- // Lock on mBgLock *after* the db operation
- synchronized (sBgLock) {
- checkItemInfoLocked(item.id, item, stackTrace);
- sBgItemsIdMap.put(item.id, item);
- switch (item.itemType) {
- case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
- sBgFolders.put(item.id, (FolderInfo) item);
- // Fall through
- case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
- case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
- case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
- if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
- item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- sBgWorkspaceItems.add(item);
- } else {
- if (!sBgFolders.containsKey(item.container)) {
- // Adding an item to a folder that doesn't exist.
- String msg = "adding item: " + item + " to a folder that " +
- " doesn't exist";
- Log.e(TAG, msg);
- }
- }
- if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
- incrementPinnedShortcutCount(
- ShortcutKey.fromShortcutInfo((ShortcutInfo) item),
- true /* shouldPin */);
- }
- break;
- case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
- sBgAppWidgets.add((LauncherAppWidgetInfo) item);
- break;
- }
- }
- }
- };
- runOnWorkerThread(r);
- }
-
- private static ArrayList<ItemInfo> getItemsByPackageName(
- final String pn, final UserHandleCompat user) {
- ItemInfoFilter filter = new ItemInfoFilter() {
- @Override
- public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
- return cn.getPackageName().equals(pn) && info.user.equals(user);
- }
- };
- return filterItemInfos(sBgItemsIdMap, filter);
- }
-
- /**
- * Removes all the items from the database corresponding to the specified package.
- */
- static void deletePackageFromDatabase(Context context, final String pn,
- final UserHandleCompat user) {
- deleteItemsFromDatabase(context, getItemsByPackageName(pn, user));
- }
-
- /**
- * Removes the specified item from the database
- */
- public static void deleteItemFromDatabase(Context context, final ItemInfo item) {
- ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
- items.add(item);
- deleteItemsFromDatabase(context, items);
- }
-
- /**
- * Removes the specified items from the database
- */
- static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
- final ContentResolver cr = context.getContentResolver();
- Runnable r = new Runnable() {
- public void run() {
- for (ItemInfo item : items) {
- final Uri uri = LauncherSettings.Favorites.getContentUri(item.id);
- cr.delete(uri, null, null);
-
- // Lock on mBgLock *after* the db operation
- synchronized (sBgLock) {
- switch (item.itemType) {
- case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
- sBgFolders.remove(item.id);
- for (ItemInfo info: sBgItemsIdMap) {
- if (info.container == item.id) {
- // We are deleting a folder which still contains items that
- // think they are contained by that folder.
- String msg = "deleting a folder (" + item + ") which still " +
- "contains items (" + info + ")";
- Log.e(TAG, msg);
- }
- }
- sBgWorkspaceItems.remove(item);
- break;
- case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
- decrementPinnedShortcutCount(ShortcutKey.fromShortcutInfo(
- (ShortcutInfo) item));
- // Fall through.
- case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
- case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
- sBgWorkspaceItems.remove(item);
- break;
- case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
- sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
- break;
- }
- sBgItemsIdMap.remove(item.id);
- }
- }
- }
- };
- runOnWorkerThread(r);
- }
-
- /**
- * Decrement the count for the given pinned shortcut, unpinning it if the count becomes 0.
- */
- private static void decrementPinnedShortcutCount(final ShortcutKey pinnedShortcut) {
- synchronized (sBgLock) {
- MutableInt count = sBgPinnedShortcutCounts.get(pinnedShortcut);
- if (count == null || --count.value == 0) {
- LauncherAppState.getInstance().getShortcutManager().unpinShortcut(pinnedShortcut);
- }
- }
- }
-
- /**
- * Increment the count for the given shortcut, pinning it if the count becomes 1.
- *
- * As an optimization, the caller can pass shouldPin == false to avoid
- * unnecessary RPC's if the shortcut is already pinned.
- */
- private static void incrementPinnedShortcutCount(ShortcutKey pinnedShortcut, boolean shouldPin) {
- synchronized (sBgLock) {
- MutableInt count = sBgPinnedShortcutCounts.get(pinnedShortcut);
- if (count == null) {
- count = new MutableInt(1);
- sBgPinnedShortcutCounts.put(pinnedShortcut, count);
- } else {
- count.value++;
- }
- if (shouldPin && count.value == 1) {
- LauncherAppState.getInstance().getShortcutManager().pinShortcut(pinnedShortcut);
- }
- }
- }
-
/**
* Update the order of the workspace screens in the database. The array list contains
* a list of screen ids in the order that they should appear.
*/
- public void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
+ public static void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
final ContentResolver cr = context.getContentResolver();
final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
@@ -1095,38 +365,9 @@
throw new RuntimeException(ex);
}
- synchronized (sBgLock) {
- sBgWorkspaceScreens.clear();
- sBgWorkspaceScreens.addAll(screensCopy);
- }
- }
- };
- runOnWorkerThread(r);
- }
-
- /**
- * Remove the specified folder and all its contents from the database.
- */
- public static void deleteFolderAndContentsFromDatabase(Context context, final FolderInfo info) {
- final ContentResolver cr = context.getContentResolver();
-
- Runnable r = new Runnable() {
- public void run() {
- cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
- // Lock on mBgLock *after* the db operation
- synchronized (sBgLock) {
- sBgItemsIdMap.remove(info.id);
- sBgFolders.remove(info.id);
- sBgWorkspaceItems.remove(info);
- }
-
- cr.delete(LauncherSettings.Favorites.CONTENT_URI,
- LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
- // Lock on mBgLock *after* the db operation
- synchronized (sBgLock) {
- for (ItemInfo childInfo : info.contents) {
- sBgItemsIdMap.remove(childInfo.id);
- }
+ synchronized (sBgDataModel) {
+ sBgDataModel.workspaceScreens.clear();
+ sBgDataModel.workspaceScreens.addAll(screensCopy);
}
}
};
@@ -1146,66 +387,64 @@
}
@Override
- public void onPackageChanged(String packageName, UserHandleCompat user) {
+ public void onPackageChanged(String packageName, UserHandle user) {
int op = PackageUpdatedTask.OP_UPDATE;
- enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
- user));
+ enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
}
@Override
- public void onPackageRemoved(String packageName, UserHandleCompat user) {
+ public void onPackageRemoved(String packageName, UserHandle user) {
+ onPackagesRemoved(user, packageName);
+ }
+
+ public void onPackagesRemoved(UserHandle user, String... packages) {
int op = PackageUpdatedTask.OP_REMOVE;
- enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
- user));
+ enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages));
}
@Override
- public void onPackageAdded(String packageName, UserHandleCompat user) {
+ public void onPackageAdded(String packageName, UserHandle user) {
int op = PackageUpdatedTask.OP_ADD;
- enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
- user));
+ enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
}
@Override
- public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
+ public void onPackagesAvailable(String[] packageNames, UserHandle user,
boolean replacing) {
- enqueueItemUpdatedTask(
- new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, packageNames, user));
+ enqueueModelUpdateTask(
+ new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, user, packageNames));
}
@Override
- public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user,
+ public void onPackagesUnavailable(String[] packageNames, UserHandle user,
boolean replacing) {
if (!replacing) {
- enqueueItemUpdatedTask(new PackageUpdatedTask(
- PackageUpdatedTask.OP_UNAVAILABLE, packageNames,
- user));
+ enqueueModelUpdateTask(new PackageUpdatedTask(
+ PackageUpdatedTask.OP_UNAVAILABLE, user, packageNames));
}
}
@Override
- public void onPackagesSuspended(String[] packageNames, UserHandleCompat user) {
- enqueueItemUpdatedTask(new PackageUpdatedTask(
- PackageUpdatedTask.OP_SUSPEND, packageNames,
- user));
+ public void onPackagesSuspended(String[] packageNames, UserHandle user) {
+ enqueueModelUpdateTask(new PackageUpdatedTask(
+ PackageUpdatedTask.OP_SUSPEND, user, packageNames));
}
@Override
- public void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user) {
- enqueueItemUpdatedTask(new PackageUpdatedTask(
- PackageUpdatedTask.OP_UNSUSPEND, packageNames,
- user));
+ public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
+ enqueueModelUpdateTask(new PackageUpdatedTask(
+ PackageUpdatedTask.OP_UNSUSPEND, user, packageNames));
}
@Override
public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
- UserHandleCompat user) {
- enqueueItemUpdatedTask(new ShortcutsChangedTask(packageName, shortcuts, user, true));
+ UserHandle user) {
+ enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true));
}
public void updatePinnedShortcuts(String packageName, List<ShortcutInfoCompat> shortcuts,
- UserHandleCompat user) {
- enqueueItemUpdatedTask(new ShortcutsChangedTask(packageName, shortcuts, user, false));
+ UserHandle user) {
+ enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, false));
}
/**
@@ -1227,20 +466,19 @@
} else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
- UserHandleCompat user = UserHandleCompat.fromIntent(intent);
+ UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
if (user != null) {
if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
- enqueueItemUpdatedTask(new PackageUpdatedTask(
- PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE,
- new String[0], user));
+ enqueueModelUpdateTask(new PackageUpdatedTask(
+ PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
}
// ACTION_MANAGED_PROFILE_UNAVAILABLE sends the profile back to locked mode, so
// we need to run the state change task again.
if (Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
- enqueueItemUpdatedTask(new UserLockStateChangedTask(user));
+ enqueueModelUpdateTask(new UserLockStateChangedTask(user));
}
}
} else if (Intent.ACTION_WALLPAPER_CHANGED.equals(action)) {
@@ -1248,8 +486,16 @@
}
}
- void forceReload() {
- resetLoadedState(true, true);
+ /**
+ * Reloads the workspace items from the DB and re-binds the workspace. This should generally
+ * not be called as DB updates are automatically followed by UI update
+ */
+ public void forceReload() {
+ synchronized (mLock) {
+ // Stop any existing loaders first, so they don't set mModelLoaded to true later
+ stopLoaderLocked();
+ mModelLoaded = false;
+ }
// Do this here because if the launcher activity is running it will be restarted.
// If it's not running startLoaderFromBackground will merely tell it that it needs
@@ -1257,19 +503,6 @@
startLoaderFromBackground();
}
- public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
- synchronized (mLock) {
- // Stop any existing loaders first, so they don't set mAllAppsLoaded or
- // mWorkspaceLoaded to true later
- stopLoaderLocked();
- if (resetAllAppsLoaded) mAllAppsLoaded = false;
- if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
- // Always reset deep shortcuts loaded.
- // TODO: why?
- mDeepShortcutsLoaded = false;
- }
- }
-
/**
* When the launcher is in the background, it's possible for it to miss paired
* configuration changes. So whenever we trigger the loader from the background
@@ -1321,9 +554,8 @@
// If there is already one running, tell it to stop.
stopLoaderLocked();
mLoaderTask = new LoaderTask(mApp.getContext(), synchronousBindPage);
- // TODO: mDeepShortcutsLoaded does not need to be true for synchronous bind.
- if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE && mAllAppsLoaded
- && mWorkspaceLoaded && mDeepShortcutsLoaded && !mIsLoaderTaskRunning) {
+ if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
+ && mModelLoaded && !mIsLoaderTaskRunning) {
mLoaderTask.runBindSynchronousPage(synchronousBindPage);
return true;
} else {
@@ -1375,28 +607,6 @@
mPageToBindFirst = pageToBindFirst;
}
- private void loadAndBindWorkspace() {
- mIsLoadingAndBindingWorkspace = true;
-
- // Load the workspace
- if (DEBUG_LOADERS) {
- Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
- }
-
- if (!mWorkspaceLoaded) {
- loadWorkspace();
- synchronized (LoaderTask.this) {
- if (mStopped) {
- return;
- }
- mWorkspaceLoaded = true;
- }
- }
-
- // Bind the workspace
- bindWorkspace(mPageToBindFirst);
- }
-
private void waitForIdle() {
// Wait until the either we're stopped or the other threads are done.
// This way we don't start loading all apps until the workspace has settled
@@ -1439,7 +649,7 @@
throw new RuntimeException("Should not call runBindSynchronousPage() without " +
"valid page index");
}
- if (!mAllAppsLoaded || !mWorkspaceLoaded) {
+ if (!mModelLoaded) {
// Ensure that we don't try and bind a specified page when the pages have not been
// loaded already (we should load everything asynchronously in that case)
throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
@@ -1471,6 +681,14 @@
bindDeepShortcuts();
}
+ private void verifyNotStopped() throws CancellationException {
+ synchronized (LoaderTask.this) {
+ if (mStopped) {
+ throw new CancellationException("Loader stopped");
+ }
+ }
+ }
+
public void run() {
synchronized (mLock) {
if (mStopped) {
@@ -1478,41 +696,72 @@
}
mIsLoaderTaskRunning = true;
}
- // Optimize for end-user experience: if the Launcher is up and // running with the
- // All Apps interface in the foreground, load All Apps first. Otherwise, load the
- // workspace first (default).
- keep_running: {
- if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
- loadAndBindWorkspace();
- if (mStopped) {
- break keep_running;
- }
+ try {
+ if (DEBUG_LOADERS) Log.d(TAG, "step 1.1: loading workspace");
+ // Set to false in bindWorkspace()
+ mIsLoadingAndBindingWorkspace = true;
+ loadWorkspace();
+ verifyNotStopped();
+ if (DEBUG_LOADERS) Log.d(TAG, "step 1.2: bind workspace workspace");
+ bindWorkspace(mPageToBindFirst);
+
+ // Take a break
+ if (DEBUG_LOADERS) Log.d(TAG, "step 1 completed, wait for idle");
waitForIdle();
+ verifyNotStopped();
// second step
- if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
- loadAndBindAllApps();
+ if (DEBUG_LOADERS) Log.d(TAG, "step 2.1: loading all apps");
+ loadAllApps();
+ verifyNotStopped();
+ if (DEBUG_LOADERS) Log.d(TAG, "step 2.2: Update icon cache");
+ updateIconCache();
+
+ // Take a break
+ if (DEBUG_LOADERS) Log.d(TAG, "step 2 completed, wait for idle");
waitForIdle();
+ verifyNotStopped();
// third step
- if (DEBUG_LOADERS) Log.d(TAG, "step 3: loading deep shortcuts");
- loadAndBindDeepShortcuts();
- }
+ if (DEBUG_LOADERS) Log.d(TAG, "step 3.1: loading deep shortcuts");
+ loadDeepShortcuts();
- // Clear out this reference, otherwise we end up holding it until all of the
- // callback runnables are done.
- mContext = null;
+ verifyNotStopped();
+ if (DEBUG_LOADERS) Log.d(TAG, "step 3.2: bind deep shortcuts");
+ bindDeepShortcuts();
- synchronized (mLock) {
- // If we are still the last one to be scheduled, remove ourselves.
- if (mLoaderTask == this) {
- mLoaderTask = null;
+ // Take a break
+ if (DEBUG_LOADERS) Log.d(TAG, "step 3 completed, wait for idle");
+ waitForIdle();
+ verifyNotStopped();
+
+ // fourth step
+ if (DEBUG_LOADERS) Log.d(TAG, "step 4.1: loading widgets");
+ refreshAndBindWidgetsAndShortcuts(getCallback(), false /* bindFirst */,
+ null /* packageUser */);
+
+ synchronized (mLock) {
+ // Everything loaded bind the data.
+ mModelLoaded = true;
+ mHasLoaderCompletedOnce = true;
}
- mIsLoaderTaskRunning = false;
- mHasLoaderCompletedOnce = true;
+ } catch (CancellationException e) {
+ // Loader stopped, ignore
+ } finally {
+ // Clear out this reference, otherwise we end up holding it until all of the
+ // callback runnables are done.
+ mContext = null;
+
+ synchronized (mLock) {
+ // If we are still the last one to be scheduled, remove ourselves.
+ if (mLoaderTask == this) {
+ mLoaderTask = null;
+ }
+ mIsLoaderTaskRunning = false;
+ }
}
}
@@ -1553,125 +802,19 @@
}
}
- // check & update map of what's occupied; used to discard overlapping/invalid items
- private boolean checkItemPlacement(LongArrayMap<GridOccupancy> occupied, ItemInfo item,
- ArrayList<Long> workspaceScreens) {
- LauncherAppState app = LauncherAppState.getInstance();
- InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
-
- long containerIndex = item.screenId;
- if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- // Return early if we detect that an item is under the hotseat button
- if (!FeatureFlags.NO_ALL_APPS_ICON &&
- profile.isAllAppsButtonRank((int) item.screenId)) {
- Log.e(TAG, "Error loading shortcut into hotseat " + item
- + " into position (" + item.screenId + ":" + item.cellX + ","
- + item.cellY + ") occupied by all apps");
- return false;
- }
-
- final GridOccupancy hotseatOccupancy =
- occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
-
- if (item.screenId >= profile.numHotseatIcons) {
- Log.e(TAG, "Error loading shortcut " + item
- + " into hotseat position " + item.screenId
- + ", position out of bounds: (0 to " + (profile.numHotseatIcons - 1)
- + ")");
- return false;
- }
-
- if (hotseatOccupancy != null) {
- if (hotseatOccupancy.cells[(int) item.screenId][0]) {
- Log.e(TAG, "Error loading shortcut into hotseat " + item
- + " into position (" + item.screenId + ":" + item.cellX + ","
- + item.cellY + ") already occupied");
- return false;
- } else {
- hotseatOccupancy.cells[(int) item.screenId][0] = true;
- return true;
- }
- } else {
- final GridOccupancy occupancy = new GridOccupancy(profile.numHotseatIcons, 1);
- occupancy.cells[(int) item.screenId][0] = true;
- occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, occupancy);
- return true;
- }
- } else if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
- if (!workspaceScreens.contains((Long) item.screenId)) {
- // The item has an invalid screen id.
- return false;
- }
- } else {
- // Skip further checking if it is not the hotseat or workspace container
- return true;
- }
-
- final int countX = profile.numColumns;
- final int countY = profile.numRows;
- if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
- item.cellX < 0 || item.cellY < 0 ||
- item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) {
- Log.e(TAG, "Error loading shortcut " + item
- + " into cell (" + containerIndex + "-" + item.screenId + ":"
- + item.cellX + "," + item.cellY
- + ") out of screen bounds ( " + countX + "x" + countY + ")");
- return false;
- }
-
- if (!occupied.containsKey(item.screenId)) {
- GridOccupancy screen = new GridOccupancy(countX + 1, countY + 1);
- if (item.screenId == Workspace.FIRST_SCREEN_ID) {
- // Mark the first row as occupied (if the feature is enabled)
- // in order to account for the QSB.
- screen.markCells(0, 0, countX + 1, 1, FeatureFlags.QSB_ON_FIRST_SCREEN);
- }
- occupied.put(item.screenId, screen);
- }
- final GridOccupancy occupancy = occupied.get(item.screenId);
-
- // Check if any workspace icons overlap with each other
- if (occupancy.isRegionVacant(item.cellX, item.cellY, item.spanX, item.spanY)) {
- occupancy.markCells(item, true);
- return true;
- } else {
- Log.e(TAG, "Error loading shortcut " + item
- + " into cell (" + containerIndex + "-" + item.screenId + ":"
- + item.cellX + "," + item.cellX + "," + item.spanX + "," + item.spanY
- + ") already occupied");
- return false;
- }
- }
-
- /** Clears all the sBg data structures */
- private void clearSBgDataStructures() {
- synchronized (sBgLock) {
- sBgWorkspaceItems.clear();
- sBgAppWidgets.clear();
- sBgFolders.clear();
- sBgItemsIdMap.clear();
- sBgWorkspaceScreens.clear();
- sBgPinnedShortcutCounts.clear();
- }
- }
-
private void loadWorkspace() {
if (LauncherAppState.PROFILE_STARTUP) {
Trace.beginSection("Loading Workspace");
}
- final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
final Context context = mContext;
final ContentResolver contentResolver = context.getContentResolver();
- final PackageManager manager = context.getPackageManager();
- final boolean isSafeMode = manager.isSafeMode();
+ final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
+ final boolean isSafeMode = pmHelper.isSafeMode();
final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+ final DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(context);
final boolean isSdCardReady = Utilities.isBootCompleted();
-
- LauncherAppState app = LauncherAppState.getInstance();
- InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
- int countX = profile.numColumns;
- int countY = profile.numRows;
+ final MultiHashMap<UserHandle, String> pendingPackages = new MultiHashMap<>();
boolean clearDb = false;
try {
@@ -1690,68 +833,44 @@
if (clearDb) {
Log.d(TAG, "loadWorkspace: resetting launcher database");
LauncherSettings.Settings.call(contentResolver,
- LauncherSettings.Settings.METHOD_DELETE_DB);
+ LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
}
Log.d(TAG, "loadWorkspace: loading default favorites");
LauncherSettings.Settings.call(contentResolver,
LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES);
- synchronized (sBgLock) {
- clearSBgDataStructures();
+ synchronized (sBgDataModel) {
+ sBgDataModel.clear();
+
final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
.getInstance(mContext).updateAndGetActiveSessionCache();
- sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
+ sBgDataModel.workspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
- final ArrayList<Long> itemsToRemove = new ArrayList<>();
- final ArrayList<Long> restoredRows = new ArrayList<>();
Map<ShortcutKey, ShortcutInfoCompat> shortcutKeyToPinnedShortcuts = new HashMap<>();
- final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
- if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
- final Cursor c = contentResolver.query(contentUri, null, null, null, null);
+ final LoaderCursor c = new LoaderCursor(contentResolver.query(
+ LauncherSettings.Favorites.CONTENT_URI, null, null, null, null), mApp);
- // +1 for the hotseat (it can be larger than the workspace)
- // Load workspace in reverse order to ensure that latest items are loaded first (and
- // before any earlier duplicates)
- final LongArrayMap<GridOccupancy> occupied = new LongArrayMap<>();
HashMap<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null;
try {
- final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
- final int intentIndex = c.getColumnIndexOrThrow
- (LauncherSettings.Favorites.INTENT);
- final int containerIndex = c.getColumnIndexOrThrow(
- LauncherSettings.Favorites.CONTAINER);
- final int itemTypeIndex = c.getColumnIndexOrThrow(
- LauncherSettings.Favorites.ITEM_TYPE);
final int appWidgetIdIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.APPWIDGET_ID);
final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.APPWIDGET_PROVIDER);
- final int screenIndex = c.getColumnIndexOrThrow(
- LauncherSettings.Favorites.SCREEN);
- final int cellXIndex = c.getColumnIndexOrThrow
- (LauncherSettings.Favorites.CELLX);
- final int cellYIndex = c.getColumnIndexOrThrow
- (LauncherSettings.Favorites.CELLY);
final int spanXIndex = c.getColumnIndexOrThrow
(LauncherSettings.Favorites.SPANX);
final int spanYIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.SPANY);
final int rankIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.RANK);
- final int restoredIndex = c.getColumnIndexOrThrow(
- LauncherSettings.Favorites.RESTORED);
- final int profileIdIndex = c.getColumnIndexOrThrow(
- LauncherSettings.Favorites.PROFILE_ID);
final int optionsIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.OPTIONS);
- final CursorIconInfo cursorIconInfo = new CursorIconInfo(mContext, c);
- final LongSparseArray<UserHandleCompat> allUsers = new LongSparseArray<>();
+ final LongSparseArray<UserHandle> allUsers = c.allUsers;
final LongSparseArray<Boolean> quietMode = new LongSparseArray<>();
final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
- for (UserHandleCompat user : mUserManager.getUserProfiles()) {
+ for (UserHandle user : mUserManager.getUserProfiles()) {
long serialNo = mUserManager.getSerialNumberForUser(user);
allUsers.put(serialNo, user);
quietMode.put(serialNo, mUserManager.isQuietModeEnabled(user));
@@ -1761,8 +880,8 @@
// We can only query for shortcuts when the user is unlocked.
if (userUnlocked) {
List<ShortcutInfoCompat> pinnedShortcuts =
- mDeepShortcutManager.queryForPinnedShortcuts(null, user);
- if (mDeepShortcutManager.wasLastCallSuccess()) {
+ shortcutManager.queryForPinnedShortcuts(null, user);
+ if (shortcutManager.wasLastCallSuccess()) {
for (ShortcutInfoCompat shortcut : pinnedShortcuts) {
shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
shortcut);
@@ -1778,215 +897,174 @@
}
ShortcutInfo info;
- String intentDescription;
LauncherAppWidgetInfo appWidgetInfo;
- int container;
- long id;
- long serialNumber;
Intent intent;
- UserHandleCompat user;
- String targetPackage;
+ String targetPkg;
while (!mStopped && c.moveToNext()) {
try {
- int itemType = c.getInt(itemTypeIndex);
- boolean restored = 0 != c.getInt(restoredIndex);
- boolean allowMissingTarget = false;
- container = c.getInt(containerIndex);
+ if (c.user == null) {
+ // User has been deleted, remove the item.
+ c.markDeleted("User has been deleted");
+ continue;
+ }
- switch (itemType) {
- case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+ boolean allowMissingTarget = false;
+ switch (c.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
- id = c.getLong(idIndex);
- intentDescription = c.getString(intentIndex);
- serialNumber = c.getInt(profileIdIndex);
- user = allUsers.get(serialNumber);
- int promiseType = c.getInt(restoredIndex);
- int disabledState = 0;
- boolean itemReplaced = false;
- targetPackage = null;
- if (user == null) {
- // User has been deleted remove the item.
- itemsToRemove.add(id);
+ intent = c.parseIntent();
+ if (intent == null) {
+ c.markDeleted("Invalid or null intent");
continue;
}
- try {
- intent = Intent.parseUri(intentDescription, 0);
- ComponentName cn = intent.getComponent();
- if (cn != null && cn.getPackageName() != null) {
- boolean validPkg = launcherApps.isPackageEnabledForProfile(
- cn.getPackageName(), user);
- boolean validComponent = validPkg &&
- launcherApps.isActivityEnabledForProfile(cn, user);
- if (validPkg) {
- targetPackage = cn.getPackageName();
- }
- if (validComponent) {
- if (restored) {
- // no special handling necessary for this item
- restoredRows.add(id);
- restored = false;
- }
- if (quietMode.get(serialNumber)) {
- disabledState = ShortcutInfo.FLAG_DISABLED_QUIET_USER;
- }
- } else if (validPkg) {
- intent = null;
- if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
- // We allow auto install apps to have their intent
- // updated after an install.
- intent = manager.getLaunchIntentForPackage(
- cn.getPackageName());
- if (intent != null) {
- ContentValues values = new ContentValues();
- values.put(LauncherSettings.Favorites.INTENT,
- intent.toUri(0));
- updateItem(id, values);
- }
- }
+ int disabledState = quietMode.get(c.serialNumber) ?
+ ShortcutInfo.FLAG_DISABLED_QUIET_USER : 0;
+ ComponentName cn = intent.getComponent();
+ targetPkg = cn == null ? intent.getPackage() : cn.getPackageName();
- if (intent == null) {
- // The app is installed but the component is no
- // longer available.
- FileLog.d(TAG, "Invalid component removed: " + cn);
- itemsToRemove.add(id);
- continue;
+ if (!Process.myUserHandle().equals(c.user)) {
+ if (c.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
+ c.markDeleted("Legacy shortcuts are only allowed for default user");
+ continue;
+ } else if (c.restoreFlag != 0) {
+ // Don't restore items for other profiles.
+ c.markDeleted("Restore from managed profile not supported");
+ continue;
+ }
+ }
+ if (TextUtils.isEmpty(targetPkg) &&
+ c.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
+ c.markDeleted("Only legacy shortcuts can have null package");
+ continue;
+ }
+
+ // If there is no target package, its an implicit intent
+ // (legacy shortcut) which is always valid
+ boolean validTarget = TextUtils.isEmpty(targetPkg) ||
+ launcherApps.isPackageEnabledForProfile(targetPkg, c.user);
+
+ if (cn != null && validTarget) {
+ // If the apk is present and the shortcut points to a specific
+ // component.
+
+ // If the component is already present
+ if (launcherApps.isActivityEnabledForProfile(cn, c.user)) {
+ // no special handling necessary for this item
+ c.markRestored();
+ } else {
+ if (c.hasRestoreFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
+ // We allow auto install apps to have their intent
+ // updated after an install.
+ intent = pmHelper.getAppLaunchIntent(targetPkg, c.user);
+ if (intent != null) {
+ c.restoreFlag = 0;
+ c.updater().put(
+ LauncherSettings.Favorites.INTENT,
+ intent.toUri(0)).commit();
+ cn = intent.getComponent();
} else {
- // no special handling necessary for this item
- restoredRows.add(id);
- restored = false;
- }
- } else if (restored) {
- // Package is not yet available but might be
- // installed later.
- FileLog.d(TAG, "package not yet restored: " + cn);
-
- if ((promiseType & ShortcutInfo.FLAG_RESTORE_STARTED) != 0) {
- // Restore has started once.
- } else if (installingPkgs.containsKey(cn.getPackageName())) {
- // App restore has started. Update the flag
- promiseType |= ShortcutInfo.FLAG_RESTORE_STARTED;
- ContentValues values = new ContentValues();
- values.put(LauncherSettings.Favorites.RESTORED,
- promiseType);
- updateItem(id, values);
- } else if ((promiseType & ShortcutInfo.FLAG_RESTORED_APP_TYPE) != 0) {
- // This is a common app. Try to replace this.
- int appType = CommonAppTypeParser.decodeItemTypeFromFlag(promiseType);
- CommonAppTypeParser parser = new CommonAppTypeParser(id, appType, context);
- if (parser.findDefaultApp()) {
- // Default app found. Replace it.
- intent = parser.parsedIntent;
- cn = intent.getComponent();
- ContentValues values = parser.parsedValues;
- values.put(LauncherSettings.Favorites.RESTORED, 0);
- updateItem(id, values);
- restored = false;
- itemReplaced = true;
-
- } else {
- FileLog.d(TAG, "Unrestored package removed: " + cn);
- itemsToRemove.add(id);
- continue;
- }
- } else {
- FileLog.d(TAG, "Unrestored package removed: " + cn);
- itemsToRemove.add(id);
+ c.markDeleted("Unable to find a launch target");
continue;
}
- } else if (PackageManagerHelper.isAppOnSdcard(
- manager, cn.getPackageName())) {
- // Package is present but not available.
- allowMissingTarget = true;
- disabledState = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
- } else if (!isSdCardReady) {
- // SdCard is not ready yet. Package might get available,
- // once it is ready.
- Log.d(TAG, "Invalid package: " + cn + " (check again later)");
- HashSet<String> pkgs = sPendingPackages.get(user);
- if (pkgs == null) {
- pkgs = new HashSet<String>();
- sPendingPackages.put(user, pkgs);
- }
- pkgs.add(cn.getPackageName());
- allowMissingTarget = true;
- // Add the icon on the workspace anyway.
-
} else {
- // Do not wait for external media load anymore.
- // Log the invalid package, and remove it
- FileLog.d(TAG, "Invalid package removed: " + cn);
- itemsToRemove.add(id);
+ // The app is installed but the component is no
+ // longer available.
+ c.markDeleted("Invalid component removed: " + cn);
continue;
}
- } else if (cn == null) {
- // For shortcuts with no component, keep them as they are
- restoredRows.add(id);
- restored = false;
}
- } catch (URISyntaxException e) {
- FileLog.d(TAG, "Invalid uri: " + intentDescription);
- itemsToRemove.add(id);
- continue;
+ }
+ // else if cn == null => can't infer much, leave it
+ // else if !validPkg => could be restored icon or missing sd-card
+
+ if (!TextUtils.isEmpty(targetPkg) && !validTarget) {
+ // Points to a valid app (superset of cn != null) but the apk
+ // is not available.
+
+ if (c.restoreFlag != 0) {
+ // Package is not yet available but might be
+ // installed later.
+ FileLog.d(TAG, "package not yet restored: " + targetPkg);
+
+ if (c.hasRestoreFlag(ShortcutInfo.FLAG_RESTORE_STARTED)) {
+ // Restore has started once.
+ } else if (installingPkgs.containsKey(targetPkg)) {
+ // App restore has started. Update the flag
+ c.restoreFlag |= ShortcutInfo.FLAG_RESTORE_STARTED;
+ c.updater().commit();
+ } else {
+ c.markDeleted("Unrestored app removed: " + targetPkg);
+ continue;
+ }
+ } else if (pmHelper.isAppOnSdcard(targetPkg, c.user)) {
+ // Package is present but not available.
+ disabledState |= ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
+ // Add the icon on the workspace anyway.
+ allowMissingTarget = true;
+ } else if (!isSdCardReady) {
+ // SdCard is not ready yet. Package might get available,
+ // once it is ready.
+ Log.d(TAG, "Missing pkg, will check later: " + targetPkg);
+ pendingPackages.addToList(c.user, targetPkg);
+ // Add the icon on the workspace anyway.
+ allowMissingTarget = true;
+ } else {
+ // Do not wait for external media load anymore.
+ c.markDeleted("Invalid package removed: " + targetPkg);
+ continue;
+ }
}
- boolean useLowResIcon = container >= 0 &&
+ if (validTarget) {
+ // The shortcut points to a valid target (either no target
+ // or something which is ready to be used)
+ c.markRestored();
+ }
+
+ boolean useLowResIcon = !c.isOnWorkspaceOrHotseat() &&
c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
- if (itemReplaced) {
- if (user.equals(UserHandleCompat.myUserHandle())) {
- info = getAppShortcutInfo(intent, user, null,
- cursorIconInfo, false, useLowResIcon);
- } else {
- // Don't replace items for other profiles.
- itemsToRemove.add(id);
- continue;
- }
- } else if (restored) {
- if (user.equals(UserHandleCompat.myUserHandle())) {
- info = getRestoredItemInfo(c, intent,
- promiseType, itemType, cursorIconInfo);
- intent = getRestoredItemIntent(c, context, intent);
- } else {
- // Don't restore items for other profiles.
- itemsToRemove.add(id);
- continue;
- }
- } else if (itemType ==
+ if (c.restoreFlag != 0) {
+ // Already verified above that user is same as default user
+ info = c.getRestoredItemInfo(intent);
+ } else if (c.itemType ==
LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
- info = getAppShortcutInfo(intent, user, c,
- cursorIconInfo, allowMissingTarget, useLowResIcon);
- } else if (itemType ==
+ info = c.getAppShortcutInfo(
+ intent, allowMissingTarget, useLowResIcon);
+ } else if (c.itemType ==
LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
- ShortcutKey key = ShortcutKey.fromIntent(intent, user);
- if (unlockedUsers.get(serialNumber)) {
+ ShortcutKey key = ShortcutKey.fromIntent(intent, c.user);
+ if (unlockedUsers.get(c.serialNumber)) {
ShortcutInfoCompat pinnedShortcut =
shortcutKeyToPinnedShortcuts.get(key);
if (pinnedShortcut == null) {
// The shortcut is no longer valid.
- itemsToRemove.add(id);
+ c.markDeleted("Pinned shortcut not found");
continue;
}
info = new ShortcutInfo(pinnedShortcut, context);
+ info.iconBitmap = LauncherIcons
+ .createShortcutIcon(pinnedShortcut, context);
+ if (pmHelper.isAppSuspended(
+ pinnedShortcut.getPackage(), info.user)) {
+ info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
+ }
intent = info.intent;
} else {
// Create a shortcut info in disabled mode for now.
- info = new ShortcutInfo();
- info.user = user;
- info.itemType = itemType;
- loadInfoFromCursor(info, c, cursorIconInfo);
-
+ info = c.loadSimpleShortcut();
info.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
}
- incrementPinnedShortcutCount(key, false /* shouldPin */);
} else { // item type == ITEM_TYPE_SHORTCUT
- info = getShortcutInfo(c, cursorIconInfo);
+ info = c.loadSimpleShortcut();
// Shortcuts are only available on the primary profile
- if (PackageManagerHelper.isAppSuspended(manager, targetPackage)) {
+ if (!TextUtils.isEmpty(targetPkg)
+ && pmHelper.isAppSuspended(targetPkg, c.user)) {
disabledState |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
}
@@ -2004,121 +1082,64 @@
}
if (info != null) {
- info.id = id;
+ c.applyCommonProperties(info);
+
info.intent = intent;
- info.container = container;
- info.screenId = c.getInt(screenIndex);
- info.cellX = c.getInt(cellXIndex);
- info.cellY = c.getInt(cellYIndex);
info.rank = c.getInt(rankIndex);
info.spanX = 1;
info.spanY = 1;
- info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
- if (info.promisedIntent != null) {
- info.promisedIntent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
- }
info.isDisabled |= disabledState;
if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
}
- // check & update map of what's occupied
- if (!checkItemPlacement(occupied, info, sBgWorkspaceScreens)) {
- itemsToRemove.add(id);
- break;
- }
-
- if (restored) {
- ComponentName cn = info.getTargetComponent();
- if (cn != null) {
- Integer progress = installingPkgs.get(cn.getPackageName());
- if (progress != null) {
- info.setInstallProgress(progress);
- } else {
- info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
- }
+ if (c.restoreFlag != 0 && !TextUtils.isEmpty(targetPkg)) {
+ Integer progress = installingPkgs.get(targetPkg);
+ if (progress != null) {
+ info.setInstallProgress(progress);
+ } else {
+ info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
}
}
- switch (container) {
- case LauncherSettings.Favorites.CONTAINER_DESKTOP:
- case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
- sBgWorkspaceItems.add(info);
- break;
- default:
- // Item is in a user folder
- FolderInfo folderInfo =
- findOrMakeFolder(sBgFolders, container);
- folderInfo.add(info, false);
- break;
- }
- sBgItemsIdMap.put(info.id, info);
+ c.checkAndAddItem(info, sBgDataModel);
} else {
throw new RuntimeException("Unexpected null ShortcutInfo");
}
break;
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
- id = c.getLong(idIndex);
- FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
+ FolderInfo folderInfo = sBgDataModel.findOrMakeFolder(c.id);
+ c.applyCommonProperties(folderInfo);
// Do not trim the folder label, as is was set by the user.
- folderInfo.title = c.getString(cursorIconInfo.titleIndex);
- folderInfo.id = id;
- folderInfo.container = container;
- folderInfo.screenId = c.getInt(screenIndex);
- folderInfo.cellX = c.getInt(cellXIndex);
- folderInfo.cellY = c.getInt(cellYIndex);
+ folderInfo.title = c.getString(c.titleIndex);
folderInfo.spanX = 1;
folderInfo.spanY = 1;
folderInfo.options = c.getInt(optionsIndex);
- // check & update map of what's occupied
- if (!checkItemPlacement(occupied, folderInfo, sBgWorkspaceScreens)) {
- itemsToRemove.add(id);
- break;
- }
+ // no special handling required for restored folders
+ c.markRestored();
- switch (container) {
- case LauncherSettings.Favorites.CONTAINER_DESKTOP:
- case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
- sBgWorkspaceItems.add(folderInfo);
- break;
- }
-
- if (restored) {
- // no special handling required for restored folders
- restoredRows.add(id);
- }
-
- sBgItemsIdMap.put(folderInfo.id, folderInfo);
- sBgFolders.put(folderInfo.id, folderInfo);
+ c.checkAndAddItem(folderInfo, sBgDataModel);
break;
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
// Read all Launcher-specific widget details
- boolean customWidget = itemType ==
+ boolean customWidget = c.itemType ==
LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
int appWidgetId = c.getInt(appWidgetIdIndex);
- serialNumber = c.getLong(profileIdIndex);
String savedProvider = c.getString(appWidgetProviderIndex);
- id = c.getLong(idIndex);
- user = allUsers.get(serialNumber);
- if (user == null) {
- itemsToRemove.add(id);
- continue;
- }
final ComponentName component =
ComponentName.unflattenFromString(savedProvider);
- final int restoreStatus = c.getInt(restoredIndex);
- final boolean isIdValid = (restoreStatus &
- LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) == 0;
- final boolean wasProviderReady = (restoreStatus &
- LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0;
+ final boolean isIdValid = !c.hasRestoreFlag(
+ LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
+ final boolean wasProviderReady = !c.hasRestoreFlag(
+ LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY);
if (widgetProvidersMap == null) {
widgetProvidersMap = AppWidgetManagerCompat
@@ -2127,14 +1148,14 @@
final AppWidgetProviderInfo provider = widgetProvidersMap.get(
new ComponentKey(
ComponentName.unflattenFromString(savedProvider),
- user));
+ c.user));
final boolean isProviderReady = isValidProvider(provider);
if (!isSafeMode && !customWidget &&
wasProviderReady && !isProviderReady) {
- FileLog.d(TAG, "Deleting widget that isn't installed anymore: "
+ c.markDeleted(
+ "Deleting widget that isn't installed anymore: "
+ provider);
- itemsToRemove.add(id);
} else {
if (isProviderReady) {
appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
@@ -2143,7 +1164,7 @@
// The provider is available. So the widget is either
// available or not available. We do not need to track
// any future restore updates.
- int status = restoreStatus &
+ int status = c.restoreFlag &
~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
if (!wasProviderReady) {
// If provider was not previously ready, update the
@@ -2159,23 +1180,22 @@
}
appWidgetInfo.restoreStatus = status;
} else {
- Log.v(TAG, "Widget restore pending id=" + id
+ Log.v(TAG, "Widget restore pending id=" + c.id
+ " appWidgetId=" + appWidgetId
- + " status =" + restoreStatus);
+ + " status =" + c.restoreFlag);
appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
component);
- appWidgetInfo.restoreStatus = restoreStatus;
+ appWidgetInfo.restoreStatus = c.restoreFlag;
Integer installProgress = installingPkgs.get(component.getPackageName());
- if ((restoreStatus & LauncherAppWidgetInfo.FLAG_RESTORE_STARTED) != 0) {
+ if (c.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_RESTORE_STARTED)) {
// Restore has started once.
} else if (installProgress != null) {
// App restore has started. Update the flag
appWidgetInfo.restoreStatus |=
LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
} else if (!isSafeMode) {
- FileLog.d(TAG, "Unrestored widget removed: " + component);
- itemsToRemove.add(id);
+ c.markDeleted("Unrestored widget removed: " + component);
continue;
}
@@ -2184,52 +1204,34 @@
}
if (appWidgetInfo.hasRestoreFlag(
LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG)) {
- intentDescription = c.getString(intentIndex);
- if (!TextUtils.isEmpty(intentDescription)) {
- appWidgetInfo.bindOptions =
- Intent.parseUri(intentDescription, 0);
- }
+ appWidgetInfo.bindOptions = c.parseIntent();
}
- appWidgetInfo.id = id;
- appWidgetInfo.screenId = c.getInt(screenIndex);
- appWidgetInfo.cellX = c.getInt(cellXIndex);
- appWidgetInfo.cellY = c.getInt(cellYIndex);
+ c.applyCommonProperties(appWidgetInfo);
appWidgetInfo.spanX = c.getInt(spanXIndex);
appWidgetInfo.spanY = c.getInt(spanYIndex);
- appWidgetInfo.user = user;
+ appWidgetInfo.user = c.user;
- if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
- container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- Log.e(TAG, "Widget found where container != " +
+ if (!c.isOnWorkspaceOrHotseat()) {
+ c.markDeleted("Widget found where container != " +
"CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
- itemsToRemove.add(id);
continue;
}
- appWidgetInfo.container = container;
- // check & update map of what's occupied
- if (!checkItemPlacement(occupied, appWidgetInfo, sBgWorkspaceScreens)) {
- itemsToRemove.add(id);
- break;
- }
-
if (!customWidget) {
String providerName =
appWidgetInfo.providerName.flattenToString();
if (!providerName.equals(savedProvider) ||
- (appWidgetInfo.restoreStatus != restoreStatus)) {
- ContentValues values = new ContentValues();
- values.put(
- LauncherSettings.Favorites.APPWIDGET_PROVIDER,
- providerName);
- values.put(LauncherSettings.Favorites.RESTORED,
- appWidgetInfo.restoreStatus);
- updateItem(id, values);
+ (appWidgetInfo.restoreStatus != c.restoreFlag)) {
+ c.updater()
+ .put(LauncherSettings.Favorites.APPWIDGET_PROVIDER,
+ providerName)
+ .put(LauncherSettings.Favorites.RESTORED,
+ appWidgetInfo.restoreStatus)
+ .commit();
}
}
- sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
- sBgAppWidgets.add(appWidgetInfo);
+ c.checkAndAddItem(appWidgetInfo, sBgDataModel);
}
break;
}
@@ -2243,48 +1245,48 @@
// Break early if we've stopped loading
if (mStopped) {
- clearSBgDataStructures();
+ sBgDataModel.clear();
return;
}
- if (itemsToRemove.size() > 0) {
- // Remove dead items
- contentResolver.delete(LauncherSettings.Favorites.CONTENT_URI,
- Utilities.createDbSelectionQuery(
- LauncherSettings.Favorites._ID, itemsToRemove), null);
- if (DEBUG_LOADERS) {
- Log.d(TAG, "Removed = " + Utilities.createDbSelectionQuery(
- LauncherSettings.Favorites._ID, itemsToRemove));
- }
-
+ // Remove dead items
+ if (c.commitDeleted()) {
// Remove any empty folder
ArrayList<Long> deletedFolderIds = (ArrayList<Long>) LauncherSettings.Settings
.call(contentResolver,
LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS)
.getSerializable(LauncherSettings.Settings.EXTRA_VALUE);
for (long folderId : deletedFolderIds) {
- sBgWorkspaceItems.remove(sBgFolders.get(folderId));
- sBgFolders.remove(folderId);
- sBgItemsIdMap.remove(folderId);
+ sBgDataModel.workspaceItems.remove(sBgDataModel.folders.get(folderId));
+ sBgDataModel.folders.remove(folderId);
+ sBgDataModel.itemsIdMap.remove(folderId);
}
+
+ // Remove any ghost widgets
+ LauncherSettings.Settings.call(contentResolver,
+ LauncherSettings.Settings.METHOD_REMOVE_GHOST_WIDGETS);
}
// Unpin shortcuts that don't exist on the workspace.
+ HashSet<ShortcutKey> pendingShortcuts =
+ InstallShortcutReceiver.getPendingShortcuts(context);
for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) {
- MutableInt numTimesPinned = sBgPinnedShortcutCounts.get(key);
- if (numTimesPinned == null || numTimesPinned.value == 0) {
+ MutableInt numTimesPinned = sBgDataModel.pinnedShortcutCounts.get(key);
+ if ((numTimesPinned == null || numTimesPinned.value == 0)
+ && !pendingShortcuts.contains(key)) {
// Shortcut is pinned but doesn't exist on the workspace; unpin it.
- mDeepShortcutManager.unpinShortcut(key);
+ shortcutManager.unpinShortcut(key);
}
}
// Sort all the folder items and make sure the first 3 items are high resolution.
- for (FolderInfo folder : sBgFolders) {
+ for (FolderInfo folder : sBgDataModel.folders) {
Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
int pos = 0;
for (ShortcutInfo info : folder.contents) {
- if (info.usingLowResIcon) {
- info.updateIcon(mIconCache, false);
+ if (info.usingLowResIcon &&
+ info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+ mIconCache.getTitleAndIcon(info, false);
}
pos ++;
if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
@@ -2293,24 +1295,19 @@
}
}
- if (restoredRows.size() > 0) {
- // Update restored items that no longer require special handling
- ContentValues values = new ContentValues();
- values.put(LauncherSettings.Favorites.RESTORED, 0);
- contentResolver.update(LauncherSettings.Favorites.CONTENT_URI, values,
- Utilities.createDbSelectionQuery(
- LauncherSettings.Favorites._ID, restoredRows), null);
- }
-
- if (!isSdCardReady && !sPendingPackages.isEmpty()) {
- context.registerReceiver(new AppsAvailabilityCheck(),
+ c.commitRestoredItems();
+ if (!isSdCardReady && !pendingPackages.isEmpty()) {
+ context.registerReceiver(
+ new SdCardAvailableReceiver(
+ LauncherModel.this, mContext, pendingPackages),
new IntentFilter(Intent.ACTION_BOOT_COMPLETED),
- null, sWorker);
+ null,
+ sWorker);
}
// Remove any empty screens
- ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
- for (ItemInfo item: sBgItemsIdMap) {
+ ArrayList<Long> unusedScreens = new ArrayList<>(sBgDataModel.workspaceScreens);
+ for (ItemInfo item: sBgDataModel.itemsIdMap) {
long screenId = item.screenId;
if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
unusedScreens.contains(screenId)) {
@@ -2320,25 +1317,8 @@
// If there are any empty screens remove them, and update.
if (unusedScreens.size() != 0) {
- sBgWorkspaceScreens.removeAll(unusedScreens);
- updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
- }
-
- if (DEBUG_LOADERS) {
- Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
- Log.d(TAG, "workspace layout: ");
- int nScreens = occupied.size();
- for (int y = 0; y < countY; y++) {
- String line = "";
-
- for (int i = 0; i < nScreens; i++) {
- long screenId = occupied.keyAt(i);
- if (screenId > 0) {
- line += " | ";
- }
- }
- Log.d(TAG, "[ " + line + " ]");
- }
+ sBgDataModel.workspaceScreens.removeAll(unusedScreens);
+ updateWorkspaceScreenOrder(context, sBgDataModel.workspaceScreens);
}
}
if (LauncherAppState.PROFILE_STARTUP) {
@@ -2346,17 +1326,6 @@
}
}
- /**
- * Partially updates the item without any notification. Must be called on the worker thread.
- */
- private void updateItem(long itemId, ContentValues update) {
- mContext.getContentResolver().update(
- LauncherSettings.Favorites.CONTENT_URI,
- update,
- BaseColumns._ID + "= ?",
- new String[]{Long.toString(itemId)});
- }
-
/** Filters the set of items who are directly or indirectly (via another container) on the
* specified screen. */
private void filterCurrentWorkspaceItems(long currentScreenId,
@@ -2424,8 +1393,7 @@
/** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
* right) */
private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
- final LauncherAppState app = LauncherAppState.getInstance();
- final InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
+ final InvariantDeviceProfile profile = mApp.getInvariantDeviceProfile();
final int screenCols = profile.numColumns;
final int screenCellCount = profile.numColumns * profile.numRows;
Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
@@ -2534,10 +1502,10 @@
ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
ArrayList<Long> orderedScreenIds = new ArrayList<>();
- synchronized (sBgLock) {
- workspaceItems.addAll(sBgWorkspaceItems);
- appWidgets.addAll(sBgAppWidgets);
- orderedScreenIds.addAll(sBgWorkspaceScreens);
+ synchronized (sBgDataModel) {
+ workspaceItems.addAll(sBgDataModel.workspaceItems);
+ appWidgets.addAll(sBgDataModel.appWidgets);
+ orderedScreenIds.addAll(sBgDataModel.workspaceScreens);
}
final int currentScreen;
@@ -2655,34 +1623,11 @@
}
}
- private void loadAndBindAllApps() {
- if (DEBUG_LOADERS) {
- Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
- }
- if (!mAllAppsLoaded) {
- loadAllApps();
- synchronized (LoaderTask.this) {
- if (mStopped) {
- return;
- }
- }
- updateIconCache();
- synchronized (LoaderTask.this) {
- if (mStopped) {
- return;
- }
- mAllAppsLoaded = true;
- }
- } else {
- onlyBindAllApps();
- }
- }
-
private void updateIconCache() {
// Ignore packages which have a promise icon.
HashSet<String> packagesToIgnore = new HashSet<>();
- synchronized (sBgLock) {
- for (ItemInfo info : sBgItemsIdMap) {
+ synchronized (sBgDataModel) {
+ for (ItemInfo info : sBgDataModel.itemsIdMap) {
if (info instanceof ShortcutInfo) {
ShortcutInfo si = (ShortcutInfo) info;
if (si.isPromise() && si.getTargetComponent() != null) {
@@ -2728,7 +1673,7 @@
}
private void scheduleManagedHeuristicRunnable(final ManagedProfileHeuristic heuristic,
- final UserHandleCompat user, final List<LauncherActivityInfoCompat> apps) {
+ final UserHandle user, final List<LauncherActivityInfo> apps) {
if (heuristic != null) {
// Assume the app lists now is updated.
mIsManagedHeuristicAppsUpdated = false;
@@ -2740,7 +1685,7 @@
// list will override everything in processUserApps().
sWorker.post(new Runnable() {
public void run() {
- final List<LauncherActivityInfoCompat> updatedApps =
+ final List<LauncherActivityInfo> updatedApps =
mLauncherApps.getActivityList(null, user);
scheduleManagedHeuristicRunnable(heuristic, user,
updatedApps);
@@ -2778,14 +1723,14 @@
return;
}
- final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
+ final List<UserHandle> profiles = mUserManager.getUserProfiles();
// Clear the list of apps
mBgAllAppsList.clear();
- for (UserHandleCompat user : profiles) {
+ for (UserHandle user : profiles) {
// Query for the set of apps
final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
- final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
+ final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user);
if (DEBUG_LOADERS) {
Log.d(TAG, "getActivityList took "
+ (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
@@ -2799,9 +1744,9 @@
boolean quietMode = mUserManager.isQuietModeEnabled(user);
// Create the ApplicationInfos
for (int i = 0; i < apps.size(); i++) {
- LauncherActivityInfoCompat app = apps.get(i);
+ LauncherActivityInfo app = apps.get(i);
// This builds the icon bitmaps.
- mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache, quietMode));
+ mBgAllAppsList.add(new AppInfo(app, user, quietMode), app);
}
final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
if (heuristic != null) {
@@ -2837,72 +1782,27 @@
}
}
- private void loadAndBindDeepShortcuts() {
- if (DEBUG_LOADERS) {
- Log.d(TAG, "loadAndBindDeepShortcuts mDeepShortcutsLoaded=" + mDeepShortcutsLoaded);
- }
- if (!mDeepShortcutsLoaded) {
- mBgDeepShortcutMap.clear();
- mHasShortcutHostPermission = mDeepShortcutManager.hasHostPermission();
+ private void loadDeepShortcuts() {
+ if (!mModelLoaded) {
+ sBgDataModel.deepShortcutMap.clear();
+ DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(mContext);
+ mHasShortcutHostPermission = shortcutManager.hasHostPermission();
if (mHasShortcutHostPermission) {
- for (UserHandleCompat user : mUserManager.getUserProfiles()) {
+ for (UserHandle user : mUserManager.getUserProfiles()) {
if (mUserManager.isUserUnlocked(user)) {
- List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager
- .queryForAllShortcuts(user);
- updateDeepShortcutMap(null, user, shortcuts);
+ List<ShortcutInfoCompat> shortcuts =
+ shortcutManager.queryForAllShortcuts(user);
+ sBgDataModel.updateDeepShortcutMap(null, user, shortcuts);
}
}
}
- synchronized (LoaderTask.this) {
- if (mStopped) {
- return;
- }
- mDeepShortcutsLoaded = true;
- }
- }
- bindDeepShortcuts();
- }
-
- public void dumpState() {
- synchronized (sBgLock) {
- Log.d(TAG, "mLoaderTask.mContext=" + mContext);
- Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
- Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
- Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
- }
- }
- }
-
- /**
- * Clear all the shortcuts for the given package, and re-add the new shortcuts.
- */
- private void updateDeepShortcutMap(
- String packageName, UserHandleCompat user, List<ShortcutInfoCompat> shortcuts) {
- if (packageName != null) {
- Iterator<ComponentKey> keysIter = mBgDeepShortcutMap.keySet().iterator();
- while (keysIter.hasNext()) {
- ComponentKey next = keysIter.next();
- if (next.componentName.getPackageName().equals(packageName)
- && next.user.equals(user)) {
- keysIter.remove();
- }
- }
- }
-
- // Now add the new shortcuts to the map.
- for (ShortcutInfoCompat shortcut : shortcuts) {
- boolean shouldShowInContainer = shortcut.isEnabled()
- && (shortcut.isDeclaredInManifest() || shortcut.isDynamic());
- if (shouldShowInContainer) {
- ComponentKey targetComponent
- = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
- mBgDeepShortcutMap.addToList(targetComponent, shortcut.getId());
}
}
}
public void bindDeepShortcuts() {
- final MultiHashMap<ComponentKey, String> shortcutMapCopy = mBgDeepShortcutMap.clone();
+ final MultiHashMap<ComponentKey, String> shortcutMapCopy =
+ sBgDataModel.deepShortcutMap.clone();
Runnable r = new Runnable() {
@Override
public void run() {
@@ -2921,7 +1821,7 @@
* use partial updates similar to {@link UserManagerCompat}
*/
public void refreshShortcutsIfRequired() {
- if (Utilities.isNycMR1OrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT_MR1) {
sWorker.removeCallbacks(mShortcutPermissionCheckRunnable);
sWorker.post(mShortcutPermissionCheckRunnable);
}
@@ -2930,934 +1830,155 @@
/**
* Called when the icons for packages have been updated in the icon cache.
*/
- public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandleCompat user) {
- final Callbacks callbacks = getCallback();
- final ArrayList<AppInfo> updatedApps = new ArrayList<>();
- final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
-
+ public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandle user) {
// If any package icon has changed (app was updated while launcher was dead),
// update the corresponding shortcuts.
- synchronized (sBgLock) {
- for (ItemInfo info : sBgItemsIdMap) {
- if (info instanceof ShortcutInfo && user.equals(info.user)
- && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
- ShortcutInfo si = (ShortcutInfo) info;
- ComponentName cn = si.getTargetComponent();
- if (cn != null && updatedPackages.contains(cn.getPackageName())) {
- si.updateIcon(mIconCache);
- updatedShortcuts.add(si);
- }
- }
- }
- mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps);
- }
-
- bindUpdatedShortcuts(updatedShortcuts, user);
-
- if (!updatedApps.isEmpty()) {
- mHandler.post(new Runnable() {
-
- public void run() {
- Callbacks cb = getCallback();
- if (cb != null && callbacks == cb) {
- cb.bindAppsUpdated(updatedApps);
- }
- }
- });
- }
+ enqueueModelUpdateTask(new CacheDataUpdatedTask(
+ CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages));
}
- private void bindUpdatedShortcuts(
- ArrayList<ShortcutInfo> updatedShortcuts, UserHandleCompat user) {
- bindUpdatedShortcuts(updatedShortcuts, new ArrayList<ShortcutInfo>(), user);
- }
-
- private void bindUpdatedShortcuts(
- final ArrayList<ShortcutInfo> updatedShortcuts,
- final ArrayList<ShortcutInfo> removedShortcuts,
- final UserHandleCompat user) {
- if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) {
- final Callbacks callbacks = getCallback();
- mHandler.post(new Runnable() {
-
- public void run() {
- Callbacks cb = getCallback();
- if (cb != null && callbacks == cb) {
- cb.bindShortcutsChanged(updatedShortcuts, removedShortcuts, user);
- }
- }
- });
+ void enqueueModelUpdateTask(BaseModelUpdateTask task) {
+ if (!mModelLoaded && mLoaderTask == null) {
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "enqueueModelUpdateTask Ignoring task since loader is pending=" + task);
+ }
+ return;
}
- }
-
- void enqueueItemUpdatedTask(Runnable task) {
- sWorker.post(task);
- }
-
- @Thunk class AppsAvailabilityCheck extends BroadcastReceiver {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- synchronized (sBgLock) {
- final LauncherAppsCompat launcherApps = LauncherAppsCompat
- .getInstance(mApp.getContext());
- final PackageManager manager = context.getPackageManager();
- final ArrayList<String> packagesRemoved = new ArrayList<String>();
- final ArrayList<String> packagesUnavailable = new ArrayList<String>();
- for (Entry<UserHandleCompat, HashSet<String>> entry : sPendingPackages.entrySet()) {
- UserHandleCompat user = entry.getKey();
- packagesRemoved.clear();
- packagesUnavailable.clear();
- for (String pkg : entry.getValue()) {
- if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
- if (PackageManagerHelper.isAppOnSdcard(manager, pkg)) {
- packagesUnavailable.add(pkg);
- } else {
- packagesRemoved.add(pkg);
- }
- }
- }
- if (!packagesRemoved.isEmpty()) {
- enqueueItemUpdatedTask(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE,
- packagesRemoved.toArray(new String[packagesRemoved.size()]), user));
- }
- if (!packagesUnavailable.isEmpty()) {
- enqueueItemUpdatedTask(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE,
- packagesUnavailable.toArray(new String[packagesUnavailable.size()]), user));
- }
- }
- sPendingPackages.clear();
- }
- }
- }
-
- private class PackageUpdatedTask implements Runnable {
- int mOp;
- String[] mPackages;
- UserHandleCompat mUser;
-
- public static final int OP_NONE = 0;
- public static final int OP_ADD = 1;
- public static final int OP_UPDATE = 2;
- public static final int OP_REMOVE = 3; // uninstlled
- public static final int OP_UNAVAILABLE = 4; // external media unmounted
- public static final int OP_SUSPEND = 5; // package suspended
- public static final int OP_UNSUSPEND = 6; // package unsuspended
- public static final int OP_USER_AVAILABILITY_CHANGE = 7; // user available/unavailable
-
- public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) {
- mOp = op;
- mPackages = packages;
- mUser = user;
- }
-
- public void run() {
- if (!mHasLoaderCompletedOnce) {
- // Loader has not yet run.
- return;
- }
- mIsManagedHeuristicAppsUpdated = true;
- final Context context = mApp.getContext();
-
- final String[] packages = mPackages;
- final int N = packages.length;
- FlagOp flagOp = FlagOp.NO_OP;
- StringFilter pkgFilter = StringFilter.of(new HashSet<>(Arrays.asList(packages)));
- switch (mOp) {
- case OP_ADD: {
- for (int i=0; i<N; i++) {
- if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
- mIconCache.updateIconsForPkg(packages[i], mUser);
- mBgAllAppsList.addPackage(context, packages[i], mUser);
- }
-
- ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
- if (heuristic != null) {
- heuristic.processPackageAdd(mPackages);
- }
- break;
- }
- case OP_UPDATE:
- for (int i=0; i<N; i++) {
- if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
- mIconCache.updateIconsForPkg(packages[i], mUser);
- mBgAllAppsList.updatePackage(context, packages[i], mUser);
- mApp.getWidgetCache().removePackage(packages[i], mUser);
- }
- // Since package was just updated, the target must be available now.
- flagOp = FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
- break;
- case OP_REMOVE: {
- ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
- if (heuristic != null) {
- heuristic.processPackageRemoved(mPackages);
- }
- for (int i=0; i<N; i++) {
- if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
- mIconCache.removeIconsForPkg(packages[i], mUser);
- }
- // Fall through
- }
- case OP_UNAVAILABLE:
- for (int i=0; i<N; i++) {
- if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
- mBgAllAppsList.removePackage(packages[i], mUser);
- mApp.getWidgetCache().removePackage(packages[i], mUser);
- }
- flagOp = FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
- break;
- case OP_SUSPEND:
- case OP_UNSUSPEND:
- flagOp = mOp == OP_SUSPEND ?
- FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED) :
- FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED);
- if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.(un)suspend " + N);
- mBgAllAppsList.updatePackageFlags(pkgFilter, mUser, flagOp);
- break;
- case OP_USER_AVAILABILITY_CHANGE:
- flagOp = UserManagerCompat.getInstance(context).isQuietModeEnabled(mUser)
- ? FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER)
- : FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER);
- // We want to update all packages for this user.
- pkgFilter = StringFilter.matchesAll();
- mBgAllAppsList.updatePackageFlags(pkgFilter, mUser, flagOp);
- break;
- }
-
- ArrayList<AppInfo> added = null;
- ArrayList<AppInfo> modified = null;
- final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
-
- if (mBgAllAppsList.added.size() > 0) {
- added = new ArrayList<>(mBgAllAppsList.added);
- mBgAllAppsList.added.clear();
- }
- if (mBgAllAppsList.modified.size() > 0) {
- modified = new ArrayList<>(mBgAllAppsList.modified);
- mBgAllAppsList.modified.clear();
- }
- if (mBgAllAppsList.removed.size() > 0) {
- removedApps.addAll(mBgAllAppsList.removed);
- mBgAllAppsList.removed.clear();
- }
-
- final HashMap<ComponentName, AppInfo> addedOrUpdatedApps = new HashMap<>();
-
- if (added != null) {
- addAppsToAllApps(context, added);
- for (AppInfo ai : added) {
- addedOrUpdatedApps.put(ai.componentName, ai);
- }
- }
-
- if (modified != null) {
- final Callbacks callbacks = getCallback();
- final ArrayList<AppInfo> modifiedFinal = modified;
- for (AppInfo ai : modified) {
- addedOrUpdatedApps.put(ai.componentName, ai);
- }
-
- mHandler.post(new Runnable() {
- public void run() {
- Callbacks cb = getCallback();
- if (callbacks == cb && cb != null) {
- callbacks.bindAppsUpdated(modifiedFinal);
- }
- }
- });
- }
-
- // Update shortcut infos
- if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
- final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<ShortcutInfo>();
- final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<ShortcutInfo>();
- final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<LauncherAppWidgetInfo>();
-
- synchronized (sBgLock) {
- for (ItemInfo info : sBgItemsIdMap) {
- if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
- ShortcutInfo si = (ShortcutInfo) info;
- boolean infoUpdated = false;
- boolean shortcutUpdated = false;
-
- // Update shortcuts which use iconResource.
- if ((si.iconResource != null)
- && pkgFilter.matches(si.iconResource.packageName)) {
- Bitmap icon = Utilities.createIconBitmap(
- si.iconResource.packageName,
- si.iconResource.resourceName, context);
- if (icon != null) {
- si.setIcon(icon);
- si.usingFallbackIcon = false;
- infoUpdated = true;
- }
- }
-
- ComponentName cn = si.getTargetComponent();
- if (cn != null && pkgFilter.matches(cn.getPackageName())) {
- AppInfo appInfo = addedOrUpdatedApps.get(cn);
-
- if (si.isPromise()) {
- if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
- // Auto install icon
- PackageManager pm = context.getPackageManager();
- ResolveInfo matched = pm.resolveActivity(
- new Intent(Intent.ACTION_MAIN)
- .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
- PackageManager.MATCH_DEFAULT_ONLY);
- if (matched == null) {
- // Try to find the best match activity.
- Intent intent = pm.getLaunchIntentForPackage(
- cn.getPackageName());
- if (intent != null) {
- cn = intent.getComponent();
- appInfo = addedOrUpdatedApps.get(cn);
- }
-
- if ((intent == null) || (appInfo == null)) {
- removedShortcuts.add(si);
- continue;
- }
- si.promisedIntent = intent;
- }
- }
-
- // Restore the shortcut.
- if (appInfo != null) {
- si.flags = appInfo.flags;
- }
-
- si.intent = si.promisedIntent;
- si.promisedIntent = null;
- si.status = ShortcutInfo.DEFAULT;
- infoUpdated = true;
- si.updateIcon(mIconCache);
- }
-
- if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
- && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
- si.updateIcon(mIconCache);
- si.title = Utilities.trim(appInfo.title);
- si.contentDescription = appInfo.contentDescription;
- infoUpdated = true;
- }
-
- int oldDisabledFlags = si.isDisabled;
- si.isDisabled = flagOp.apply(si.isDisabled);
- if (si.isDisabled != oldDisabledFlags) {
- shortcutUpdated = true;
- }
- }
-
- if (infoUpdated || shortcutUpdated) {
- updatedShortcuts.add(si);
- }
- if (infoUpdated) {
- updateItemInDatabase(context, si);
- }
- } else if (info instanceof LauncherAppWidgetInfo && mOp == OP_ADD) {
- LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
- if (mUser.equals(widgetInfo.user)
- && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
- && pkgFilter.matches(widgetInfo.providerName.getPackageName())) {
- widgetInfo.restoreStatus &=
- ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY &
- ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
-
- // adding this flag ensures that launcher shows 'click to setup'
- // if the widget has a config activity. In case there is no config
- // activity, it will be marked as 'restored' during bind.
- widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
-
- widgets.add(widgetInfo);
- updateItemInDatabase(context, widgetInfo);
- }
- }
- }
- }
-
- bindUpdatedShortcuts(updatedShortcuts, removedShortcuts, mUser);
- if (!removedShortcuts.isEmpty()) {
- deleteItemsFromDatabase(context, removedShortcuts);
- }
-
- if (!widgets.isEmpty()) {
- final Callbacks callbacks = getCallback();
- mHandler.post(new Runnable() {
- public void run() {
- Callbacks cb = getCallback();
- if (callbacks == cb && cb != null) {
- callbacks.bindWidgetsRestored(widgets);
- }
- }
- });
- }
- }
-
- final HashSet<String> removedPackages = new HashSet<>();
- final HashSet<ComponentName> removedComponents = new HashSet<>();
- if (mOp == OP_REMOVE) {
- // Mark all packages in the broadcast to be removed
- Collections.addAll(removedPackages, packages);
-
- // No need to update the removedComponents as
- // removedPackages is a super-set of removedComponents
- } else if (mOp == OP_UPDATE) {
- // Mark disabled packages in the broadcast to be removed
- for (int i=0; i<N; i++) {
- if (isPackageDisabled(context, packages[i], mUser)) {
- removedPackages.add(packages[i]);
- }
- }
-
- // Update removedComponents as some components can get removed during package update
- for (AppInfo info : removedApps) {
- removedComponents.add(info.componentName);
- }
- }
-
- if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
- for (String pn : removedPackages) {
- deletePackageFromDatabase(context, pn, mUser);
- }
- for (ComponentName cn : removedComponents) {
- deleteItemsFromDatabase(context, getItemInfoForComponentName(cn, mUser));
- }
-
- // Remove any queued items from the install queue
- InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser);
-
- // Call the components-removed callback
- final Callbacks callbacks = getCallback();
- mHandler.post(new Runnable() {
- public void run() {
- Callbacks cb = getCallback();
- if (callbacks == cb && cb != null) {
- callbacks.bindWorkspaceComponentsRemoved(
- removedPackages, removedComponents, mUser);
- }
- }
- });
- }
-
- if (!removedApps.isEmpty()) {
- // Remove corresponding apps from All-Apps
- final Callbacks callbacks = getCallback();
- mHandler.post(new Runnable() {
- public void run() {
- Callbacks cb = getCallback();
- if (callbacks == cb && cb != null) {
- callbacks.bindAppInfosRemoved(removedApps);
- }
- }
- });
- }
-
- // 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)) {
- final Callbacks callbacks = getCallback();
- mHandler.post(new Runnable() {
- public void run() {
- Callbacks cb = getCallback();
- if (callbacks == cb && cb != null) {
- callbacks.notifyWidgetProvidersChanged();
- }
- }
- });
- }
- }
+ task.init(this);
+ runOnWorkerThread(task);
}
/**
- * Repopulates the shortcut info, possibly updating any icon already on the workspace.
+ * A task to be executed on the current callbacks on the UI thread.
+ * If there is no current callbacks, the task is ignored.
*/
- public void updateShortcutInfo(final ShortcutInfoCompat fullDetail, final ShortcutInfo info) {
- enqueueItemUpdatedTask(new Runnable() {
+ public interface CallbackTask {
+
+ void execute(Callbacks callbacks);
+ }
+
+ /**
+ * A runnable which changes/updates the data model of the launcher based on certain events.
+ */
+ public static abstract class BaseModelUpdateTask implements Runnable {
+
+ private LauncherModel mModel;
+ private DeferredHandler mUiHandler;
+
+ /* package private */
+ void init(LauncherModel model) {
+ mModel = model;
+ mUiHandler = mModel.mHandler;
+ }
+
+ @Override
+ public void run() {
+ if (!mModel.mHasLoaderCompletedOnce) {
+ // Loader has not yet run.
+ return;
+ }
+ execute(mModel.mApp, sBgDataModel, mModel.mBgAllAppsList);
+ }
+
+ /**
+ * Execute the actual task. Called on the worker thread.
+ */
+ public abstract void execute(
+ LauncherAppState app, BgDataModel dataModel, AllAppsList apps);
+
+ /**
+ * Schedules a {@param task} to be executed on the current callbacks.
+ */
+ public final void scheduleCallbackTask(final CallbackTask task) {
+ final Callbacks callbacks = mModel.getCallback();
+ mUiHandler.post(new Runnable() {
+ public void run() {
+ Callbacks cb = mModel.getCallback();
+ if (callbacks == cb && cb != null) {
+ task.execute(callbacks);
+ }
+ }
+ });
+ }
+
+ public ModelWriter getModelWriter() {
+ // Updates from model task, do not deal with icon position in hotseat.
+ return mModel.getWriter(false /* hasVerticalHotseat */);
+ }
+ }
+
+ public void updateAndBindShortcutInfo(final ShortcutInfo si, final ShortcutInfoCompat info) {
+ updateAndBindShortcutInfo(new Provider<ShortcutInfo>() {
@Override
- public void run() {
- info.updateFromDeepShortcutInfo(
- fullDetail, LauncherAppState.getInstance().getContext());
- ArrayList<ShortcutInfo> update = new ArrayList<ShortcutInfo>();
- update.add(info);
- bindUpdatedShortcuts(update, fullDetail.getUserHandle());
+ public ShortcutInfo get() {
+ si.updateFromDeepShortcutInfo(info, mApp.getContext());
+ si.iconBitmap = LauncherIcons.createShortcutIcon(info, mApp.getContext());
+ return si;
}
});
}
- private class ShortcutsChangedTask implements Runnable {
- private final String mPackageName;
- private final List<ShortcutInfoCompat> mShortcuts;
- private final UserHandleCompat mUser;
- private final boolean mUpdateIdMap;
-
- public ShortcutsChangedTask(String packageName, List<ShortcutInfoCompat> shortcuts,
- UserHandleCompat user, boolean updateIdMap) {
- mPackageName = packageName;
- mShortcuts = shortcuts;
- mUser = user;
- mUpdateIdMap = updateIdMap;
- }
-
- @Override
- public void run() {
- mDeepShortcutManager.onShortcutsChanged(mShortcuts);
-
- // Find ShortcutInfo's that have changed on the workspace.
- final ArrayList<ShortcutInfo> removedShortcutInfos = new ArrayList<>();
- MultiHashMap<String, ShortcutInfo> idsToWorkspaceShortcutInfos = new MultiHashMap<>();
- for (ItemInfo itemInfo : sBgItemsIdMap) {
- if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
- ShortcutInfo si = (ShortcutInfo) itemInfo;
- if (si.getPromisedIntent().getPackage().equals(mPackageName)
- && si.user.equals(mUser)) {
- idsToWorkspaceShortcutInfos.addToList(si.getDeepShortcutId(), si);
- }
- }
- }
-
- final Context context = LauncherAppState.getInstance().getContext();
- final ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
- if (!idsToWorkspaceShortcutInfos.isEmpty()) {
- // Update the workspace to reflect the changes to updated shortcuts residing on it.
- List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager.queryForFullDetails(
- mPackageName, new ArrayList<>(idsToWorkspaceShortcutInfos.keySet()), mUser);
- for (ShortcutInfoCompat fullDetails : shortcuts) {
- List<ShortcutInfo> shortcutInfos = idsToWorkspaceShortcutInfos
- .remove(fullDetails.getId());
- if (!fullDetails.isPinned()) {
- // The shortcut was previously pinned but is no longer, so remove it from
- // the workspace and our pinned shortcut counts.
- // Note that we put this check here, after querying for full details,
- // because there's a possible race condition between pinning and
- // receiving this callback.
- removedShortcutInfos.addAll(shortcutInfos);
- continue;
- }
- for (ShortcutInfo shortcutInfo : shortcutInfos) {
- shortcutInfo.updateFromDeepShortcutInfo(fullDetails, context);
- updatedShortcutInfos.add(shortcutInfo);
- }
- }
- }
-
- // If there are still entries in idsToWorkspaceShortcutInfos, that means that
- // the corresponding shortcuts weren't passed in onShortcutsChanged(). This
- // means they were cleared, so we remove and unpin them now.
- for (String id : idsToWorkspaceShortcutInfos.keySet()) {
- removedShortcutInfos.addAll(idsToWorkspaceShortcutInfos.get(id));
- }
-
- bindUpdatedShortcuts(updatedShortcutInfos, removedShortcutInfos, mUser);
- if (!removedShortcutInfos.isEmpty()) {
- deleteItemsFromDatabase(context, removedShortcutInfos);
- }
-
- if (mUpdateIdMap) {
- // Update the deep shortcut map if the list of ids has changed for an activity.
- updateDeepShortcutMap(mPackageName, mUser, mShortcuts);
- bindDeepShortcuts();
- }
- }
- }
-
/**
- * Task to handle changing of lock state of the user
+ * Utility method to update a shortcut on the background thread.
*/
- private class UserLockStateChangedTask implements Runnable {
-
- private final UserHandleCompat mUser;
-
- public UserLockStateChangedTask(UserHandleCompat user) {
- mUser = user;
- }
-
- @Override
- public void run() {
- boolean isUserUnlocked = mUserManager.isUserUnlocked(mUser);
- Context context = mApp.getContext();
-
- HashMap<ShortcutKey, ShortcutInfoCompat> pinnedShortcuts = new HashMap<>();
- if (isUserUnlocked) {
- List<ShortcutInfoCompat> shortcuts =
- mDeepShortcutManager.queryForPinnedShortcuts(null, mUser);
- if (mDeepShortcutManager.wasLastCallSuccess()) {
- for (ShortcutInfoCompat shortcut : shortcuts) {
- pinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut);
- }
- } else {
- // Shortcut manager can fail due to some race condition when the lock state
- // changes too frequently. For the purpose of the update,
- // consider it as still locked.
- isUserUnlocked = false;
- }
+ public void updateAndBindShortcutInfo(final Provider<ShortcutInfo> shortcutProvider) {
+ enqueueModelUpdateTask(new ExtendedModelTask() {
+ @Override
+ public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+ ShortcutInfo info = shortcutProvider.get();
+ ArrayList<ShortcutInfo> update = new ArrayList<>();
+ update.add(info);
+ bindUpdatedShortcuts(update, info.user);
}
-
- // Update the workspace to reflect the changes to updated shortcuts residing on it.
- ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
- ArrayList<ShortcutInfo> deletedShortcutInfos = new ArrayList<>();
- for (ItemInfo itemInfo : sBgItemsIdMap) {
- if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
- && mUser.equals(itemInfo.user)) {
- ShortcutInfo si = (ShortcutInfo) itemInfo;
- if (isUserUnlocked) {
- ShortcutInfoCompat shortcut =
- pinnedShortcuts.get(ShortcutKey.fromShortcutInfo(si));
- // We couldn't verify the shortcut during loader. If its no longer available
- // (probably due to clear data), delete the workspace item as well
- if (shortcut == null) {
- deletedShortcutInfos.add(si);
- continue;
- }
- si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
- si.updateFromDeepShortcutInfo(shortcut, context);
- } else {
- si.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
- }
- updatedShortcutInfos.add(si);
- }
- }
- bindUpdatedShortcuts(updatedShortcutInfos, deletedShortcutInfos, mUser);
- if (!deletedShortcutInfos.isEmpty()) {
- deleteItemsFromDatabase(context, deletedShortcutInfos);
- }
-
- // Remove shortcut id map for that user
- Iterator<ComponentKey> keysIter = mBgDeepShortcutMap.keySet().iterator();
- while (keysIter.hasNext()) {
- if (keysIter.next().user.equals(mUser)) {
- keysIter.remove();
- }
- }
-
- if (isUserUnlocked) {
- updateDeepShortcutMap(null, mUser, mDeepShortcutManager.queryForAllShortcuts(mUser));
- }
- bindDeepShortcuts();
- }
+ });
}
- private void bindWidgetsModel(final Callbacks callbacks, final WidgetsModel model) {
+ private void bindWidgetsModel(final Callbacks callbacks) {
+ final MultiHashMap<PackageItemInfo, WidgetItem> widgets
+ = mBgWidgetsModel.getWidgetsMap().clone();
mHandler.post(new Runnable() {
@Override
public void run() {
Callbacks cb = getCallback();
if (callbacks == cb && cb != null) {
- callbacks.bindWidgetsModel(model);
+ callbacks.bindAllWidgets(widgets);
}
}
});
}
- public void refreshAndBindWidgetsAndShortcuts(
- final Callbacks callbacks, final boolean bindFirst) {
+ public void refreshAndBindWidgetsAndShortcuts(final Callbacks callbacks,
+ final boolean bindFirst, @Nullable final PackageUserKey packageUser) {
runOnWorkerThread(new Runnable() {
@Override
public void run() {
if (bindFirst && !mBgWidgetsModel.isEmpty()) {
- bindWidgetsModel(callbacks, mBgWidgetsModel.clone());
+ bindWidgetsModel(callbacks);
}
- final WidgetsModel model = mBgWidgetsModel.updateAndClone(mApp.getContext());
- bindWidgetsModel(callbacks, model);
+ ArrayList<WidgetItem> widgets = mBgWidgetsModel.update(
+ mApp.getContext(), packageUser);
+ bindWidgetsModel(callbacks);
+
// update the Widget entries inside DB on the worker thread.
- LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(
- model.getRawList());
+ mApp.getWidgetCache().removeObsoletePreviews(widgets, packageUser);
}
});
}
- @Thunk static boolean isPackageDisabled(Context context, String packageName,
- UserHandleCompat user) {
- final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
- return !launcherApps.isPackageEnabledForProfile(packageName, user);
- }
-
- public static boolean isValidPackageActivity(Context context, ComponentName cn,
- UserHandleCompat user) {
- if (cn == null) {
- return false;
- }
- final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
- if (!launcherApps.isPackageEnabledForProfile(cn.getPackageName(), user)) {
- return false;
- }
- return launcherApps.isActivityEnabledForProfile(cn, user);
- }
-
- public static boolean isValidPackage(Context context, String packageName,
- UserHandleCompat user) {
- if (packageName == null) {
- return false;
- }
- final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
- return launcherApps.isPackageEnabledForProfile(packageName, user);
- }
-
- /**
- * Make an ShortcutInfo object for a restored application or shortcut item that points
- * to a package that is not yet installed on the system.
- */
- public ShortcutInfo getRestoredItemInfo(Cursor c, Intent intent,
- int promiseType, int itemType, CursorIconInfo iconInfo) {
- final ShortcutInfo info = new ShortcutInfo();
- info.user = UserHandleCompat.myUserHandle();
-
- Bitmap icon = iconInfo.loadIcon(c, info);
- // the fallback icon
- if (icon == null) {
- mIconCache.getTitleAndIcon(info, intent, info.user, false /* useLowResIcon */);
- } else {
- info.setIcon(icon);
- }
-
- if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
- String title = iconInfo.getTitle(c);
- if (!TextUtils.isEmpty(title)) {
- info.title = Utilities.trim(title);
- }
- } else if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
- if (TextUtils.isEmpty(info.title)) {
- info.title = iconInfo.getTitle(c);
- }
- } else {
- throw new InvalidParameterException("Invalid restoreType " + promiseType);
- }
-
- info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
- info.itemType = itemType;
- info.promisedIntent = intent;
- info.status = promiseType;
- return info;
- }
-
- /**
- * Make an Intent object for a restored application or shortcut item that points
- * to the market page for the item.
- */
- @Thunk Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) {
- ComponentName componentName = intent.getComponent();
- return getMarketIntent(componentName.getPackageName());
- }
-
- static Intent getMarketIntent(String packageName) {
- return new Intent(Intent.ACTION_VIEW)
- .setData(new Uri.Builder()
- .scheme("market")
- .authority("details")
- .appendQueryParameter("id", packageName)
- .build());
- }
-
- /**
- * Make an ShortcutInfo object for a shortcut that is an application.
- *
- * If c is not null, then it will be used to fill in missing data like the title and icon.
- */
- public ShortcutInfo getAppShortcutInfo(Intent intent,
- UserHandleCompat user, Cursor c, CursorIconInfo iconInfo,
- boolean allowMissingTarget, boolean useLowResIcon) {
- if (user == null) {
- Log.d(TAG, "Null user found in getShortcutInfo");
- return null;
- }
-
- ComponentName componentName = intent.getComponent();
- if (componentName == null) {
- Log.d(TAG, "Missing component found in getShortcutInfo");
- return null;
- }
-
- Intent newIntent = new Intent(intent.getAction(), null);
- newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
- newIntent.setComponent(componentName);
- LauncherActivityInfoCompat lai = mLauncherApps.resolveActivity(newIntent, user);
- if ((lai == null) && !allowMissingTarget) {
- Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName);
- return null;
- }
-
- final ShortcutInfo info = new ShortcutInfo();
- mIconCache.getTitleAndIcon(info, componentName, lai, user, false, useLowResIcon);
- if (mIconCache.isDefaultIcon(info.getIcon(mIconCache), user) && c != null) {
- Bitmap icon = iconInfo.loadIcon(c);
- info.setIcon(icon == null ? mIconCache.getDefaultIcon(user) : icon);
- }
-
- if (lai != null && PackageManagerHelper.isAppSuspended(lai.getApplicationInfo())) {
- info.isDisabled = ShortcutInfo.FLAG_DISABLED_SUSPENDED;
- }
-
- // from the db
- if (TextUtils.isEmpty(info.title) && c != null) {
- info.title = iconInfo.getTitle(c);
- }
-
- // fall back to the class name of the activity
- if (info.title == null) {
- info.title = componentName.getClassName();
- }
-
- info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
- info.user = user;
- info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
- if (lai != null) {
- info.flags = AppInfo.initFlags(lai);
- }
- return info;
- }
-
- static ArrayList<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos,
- ItemInfoFilter f) {
- HashSet<ItemInfo> filtered = new HashSet<ItemInfo>();
- for (ItemInfo i : infos) {
- if (i instanceof ShortcutInfo) {
- ShortcutInfo info = (ShortcutInfo) i;
- ComponentName cn = info.getTargetComponent();
- if (cn != null && f.filterItem(null, info, cn)) {
- filtered.add(info);
- }
- } else if (i instanceof FolderInfo) {
- FolderInfo info = (FolderInfo) i;
- for (ShortcutInfo s : info.contents) {
- ComponentName cn = s.getTargetComponent();
- if (cn != null && f.filterItem(info, s, cn)) {
- filtered.add(s);
- }
- }
- } else if (i instanceof LauncherAppWidgetInfo) {
- LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
- ComponentName cn = info.providerName;
- if (cn != null && f.filterItem(null, info, cn)) {
- filtered.add(info);
- }
- }
- }
- return new ArrayList<ItemInfo>(filtered);
- }
-
- @Thunk ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname,
- final UserHandleCompat user) {
- ItemInfoFilter filter = new ItemInfoFilter() {
- @Override
- public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
- if (info.user == null) {
- return cn.equals(cname);
- } else {
- return cn.equals(cname) && info.user.equals(user);
- }
- }
- };
- return filterItemInfos(sBgItemsIdMap, filter);
- }
-
- /**
- * Make an ShortcutInfo object for a shortcut that isn't an application.
- */
- @Thunk ShortcutInfo getShortcutInfo(Cursor c, CursorIconInfo iconInfo) {
- final ShortcutInfo info = new ShortcutInfo();
- // Non-app shortcuts are only supported for current user.
- info.user = UserHandleCompat.myUserHandle();
- info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
-
- // TODO: If there's an explicit component and we can't install that, delete it.
-
- loadInfoFromCursor(info, c, iconInfo);
- return info;
- }
-
- /**
- * Make an ShortcutInfo object for a shortcut that isn't an application.
- */
- public void loadInfoFromCursor(ShortcutInfo info, Cursor c, CursorIconInfo iconInfo) {
- info.title = iconInfo.getTitle(c);
- Bitmap icon = iconInfo.loadIcon(c, info);
- // the fallback icon
- if (icon == null) {
- icon = mIconCache.getDefaultIcon(info.user);
- info.usingFallbackIcon = true;
- }
- info.setIcon(icon);
- }
-
- ShortcutInfo infoFromShortcutIntent(Context context, Intent data) {
- Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
- String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
- Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
-
- if (intent == null) {
- // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
- Log.e(TAG, "Can't construct ShorcutInfo with null intent");
- return null;
- }
-
- Bitmap icon = null;
- boolean customIcon = false;
- ShortcutIconResource iconResource = null;
-
- if (bitmap instanceof Bitmap) {
- icon = Utilities.createIconBitmap((Bitmap) bitmap, context);
- customIcon = true;
- } else {
- Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
- if (extra instanceof ShortcutIconResource) {
- iconResource = (ShortcutIconResource) extra;
- icon = Utilities.createIconBitmap(iconResource.packageName,
- iconResource.resourceName, context);
- }
- }
-
- final ShortcutInfo info = new ShortcutInfo();
-
- // Only support intents for current user for now. Intents sent from other
- // users wouldn't get here without intent forwarding anyway.
- info.user = UserHandleCompat.myUserHandle();
- if (icon == null) {
- icon = mIconCache.getDefaultIcon(info.user);
- info.usingFallbackIcon = true;
- }
- info.setIcon(icon);
-
- info.title = Utilities.trim(name);
- info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
- info.intent = intent;
- info.iconResource = iconResource;
-
- return info;
- }
-
- /**
- * Return an existing FolderInfo object if we have encountered this ID previously,
- * or make a new one.
- */
- @Thunk static FolderInfo findOrMakeFolder(LongArrayMap<FolderInfo> folders, long id) {
- // See if a placeholder was created for us already
- FolderInfo folderInfo = folders.get(id);
- if (folderInfo == null) {
- // No placeholder -- create a new instance
- folderInfo = new FolderInfo();
- folders.put(id, folderInfo);
- }
- return folderInfo;
- }
-
-
static boolean isValidProvider(AppWidgetProviderInfo provider) {
return (provider != null) && (provider.provider != null)
&& (provider.provider.getPackageName() != null);
}
- public void dumpState() {
- Log.d(TAG, "mCallbacks=" + mCallbacks);
- AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
- AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
- AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
- AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
- if (mLoaderTask != null) {
- mLoaderTask.dumpState();
- } else {
- Log.d(TAG, "mLoaderTask=null");
+ public void dumpState(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
+ writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size());
+ for (AppInfo info : mBgAllAppsList.data) {
+ writer.println(prefix + " title=\"" + info.title + "\" iconBitmap=" + info.iconBitmap
+ + " componentName=" + info.componentName.getPackageName());
+ }
}
+ sBgDataModel.dump(prefix, fd, writer, args);
}
public Callbacks getCallback() {
@@ -3868,8 +1989,8 @@
* @return {@link FolderInfo} if its already loaded.
*/
public FolderInfo findFolderById(Long folderId) {
- synchronized (sBgLock) {
- return sBgFolders.get(folderId);
+ synchronized (sBgDataModel) {
+ return sBgDataModel.folders.get(folderId);
}
}
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index f3d9493..3150d5b 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -16,7 +16,6 @@
package com.android.launcher3;
-import android.annotation.TargetApi;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
@@ -39,12 +38,12 @@
import android.database.sqlite.SQLiteStatement;
import android.net.Uri;
import android.os.Binder;
-import android.os.Build;
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;
import android.util.Log;
@@ -52,11 +51,12 @@
import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherSettings.WorkspaceScreens;
-import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.dynamicui.ExtractionUtils;
+import com.android.launcher3.graphics.IconShapeOverride;
+import com.android.launcher3.logging.FileLog;
import com.android.launcher3.provider.LauncherDbUtils;
import com.android.launcher3.provider.RestoreDbTask;
import com.android.launcher3.util.ManagedProfileHeuristic;
@@ -64,16 +64,30 @@
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.Thunk;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Locale;
+import java.util.HashSet;
public class LauncherProvider extends ContentProvider {
private static final String TAG = "LauncherProvider";
private static final boolean LOGD = false;
- private static final int DATABASE_VERSION = 27;
+ /**
+ * Represents the schema of the database. Changes in scheme need not be backwards compatible.
+ */
+ private static final int SCHEMA_VERSION = 27;
+ /**
+ * Represents the actual data. It could include additional validations and normalizations added
+ * overtime. These must be backwards compatible, else we risk breaking old devices during
+ * restore or binary version downgrade.
+ */
+ private static final int DATA_VERSION = 3;
+
+ private static final String PREF_KEY_DATA_VERISON = "provider_data_version";
public static final String AUTHORITY = ProviderConfig.AUTHORITY;
@@ -86,6 +100,18 @@
protected DatabaseHelper mOpenHelper;
+ /**
+ * $ adb shell dumpsys activity provider com.android.launcher3
+ */
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
+ if (appState == null || !appState.getModel().isModelLoaded()) {
+ return;
+ }
+ appState.getModel().dumpState("", fd, writer, args);
+ }
+
@Override
public boolean onCreate() {
if (ProviderConfig.IS_DOGFOOD_BUILD) {
@@ -93,7 +119,12 @@
}
mListenerHandler = new Handler(mListenerWrapper);
- LauncherAppState.setLauncherProvider(this);
+ // The content provider exists for the entire duration of the launcher main process and
+ // is the first component to get created. Initializing FileLog here ensures that it's
+ // always available in the main process.
+ FileLog.setDir(getContext().getApplicationContext().getFilesDir());
+ IconShapeOverride.apply(getContext());
+ SessionCommitReceiver.applyDefaultUserPrefs(getContext());
return true;
}
@@ -172,7 +203,7 @@
if (Utilities.ATLEAST_MARSHMALLOW && Binder.getCallingPid() != Process.myPid()) {
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
if (app != null) {
- app.reloadWorkspace();
+ app.getModel().forceReload();
}
}
}
@@ -203,7 +234,7 @@
// Deprecated behavior to support legacy devices which rely on provider callbacks.
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
if (app != null && "true".equals(uri.getQueryParameter("isExternalAdd"))) {
- app.reloadWorkspace();
+ app.getModel().forceReload();
}
String notify = uri.getQueryParameter("notify");
@@ -232,10 +263,11 @@
if (cn != null) {
try {
- int appWidgetId = new AppWidgetHost(getContext(), Launcher.APPWIDGET_HOST_ID)
- .allocateAppWidgetId();
+ AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost();
+ int appWidgetId = widgetHost.allocateAppWidgetId();
values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId);
if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,cn)) {
+ widgetHost.deleteAppWidgetId(appWidgetId);
return false;
}
} catch (RuntimeException e) {
@@ -317,23 +349,7 @@
if (Binder.getCallingPid() != Process.myPid()
&& Favorites.TABLE_NAME.equalsIgnoreCase(args.table)) {
- String widgetSelection = TextUtils.isEmpty(args.where) ? "1=1" : args.where;
- widgetSelection = String.format(Locale.ENGLISH, "%1$s = %2$d AND ( %3$s )",
- Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET, widgetSelection);
- try (Cursor c = db.query(Favorites.TABLE_NAME, new String[] { Favorites.APPWIDGET_ID },
- widgetSelection, args.args, null, null, null)) {
- AppWidgetHost host = new AppWidgetHost(getContext(), Launcher.APPWIDGET_HOST_ID);
- while (c.moveToNext()) {
- int widgetId = c.getInt(0);
- if (widgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
- try {
- host.deleteAppWidgetId(widgetId);
- } catch (RuntimeException e) {
- Log.e(TAG, "Error deleting widget id " + widgetId, e);
- }
- }
- }
- }
+ mOpenHelper.removeGhostWidgets(mOpenHelper.getWritableDatabase());
}
int count = db.delete(args.table, args.where, args.args);
if (count > 0) {
@@ -404,16 +420,15 @@
return result;
}
case LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB: {
- createEmptyDB();
+ mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
return null;
}
case LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES: {
loadDefaultFavoritesIfNecessary();
return null;
}
- case LauncherSettings.Settings.METHOD_DELETE_DB: {
- // Are you sure? (y/n)
- mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
+ case LauncherSettings.Settings.METHOD_REMOVE_GHOST_WIDGETS: {
+ mOpenHelper.removeGhostWidgets(mOpenHelper.getWritableDatabase());
return null;
}
}
@@ -467,13 +482,6 @@
values.put(LauncherSettings.ChangeLogColumns.MODIFIED, System.currentTimeMillis());
}
- /**
- * Clears all the data for a fresh start.
- */
- synchronized private void createEmptyDB() {
- mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
- }
-
private void clearFlagEmptyDbCreated() {
Utilities.getPrefs(getContext()).edit().remove(EMPTY_DATABASE_CREATED).commit();
}
@@ -491,7 +499,7 @@
if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
Log.d(TAG, "loading default workspace");
- AppWidgetHost widgetHost = new AppWidgetHost(getContext(), Launcher.APPWIDGET_HOST_ID);
+ AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost();
AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHost);
if (loader == null) {
loader = AutoInstallsLayout.get(getContext(),widgetHost, mOpenHelper);
@@ -516,12 +524,12 @@
// There might be some partially restored DB items, due to buggy restore logic in
// previous versions of launcher.
- createEmptyDB();
+ mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
// Populate favorites table with initial favorites
if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
&& usingExternallyProvidedLayout) {
// Unable to load external layout. Cleanup and load the internal layout.
- createEmptyDB();
+ mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
getDefaultLayoutParser(widgetHost));
}
@@ -534,13 +542,7 @@
*
* @return the loader if the restrictions are set and the resource exists; null otherwise.
*/
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(AppWidgetHost widgetHost) {
- // UserManager.getApplicationRestrictions() requires minSdkVersion >= 18
- if (!Utilities.ATLEAST_JB_MR2) {
- return null;
- }
-
Context ctx = getContext();
UserManager um = (UserManager) ctx.getSystemService(Context.USER_SERVICE);
Bundle bundle = um.getApplicationRestrictions(ctx.getPackageName());
@@ -564,8 +566,7 @@
}
private DefaultLayoutParser getDefaultLayoutParser(AppWidgetHost widgetHost) {
- int defaultLayout = LauncherAppState.getInstance()
- .getInvariantDeviceProfile().defaultLayoutId;
+ int defaultLayout = LauncherAppState.getIDP(getContext()).defaultLayoutId;
return new DefaultLayoutParser(getContext(), widgetHost,
mOpenHelper, getContext().getResources(), defaultLayout);
}
@@ -599,7 +600,7 @@
*/
public DatabaseHelper(
Context context, Handler widgetHostResetHandler, String tableName) {
- super(new NoLocaleSqliteContext(context), tableName, null, DATABASE_VERSION);
+ super(new NoLocaleSqliteContext(context), tableName, null, SCHEMA_VERSION);
mContext = context;
mWidgetHostResetHandler = widgetHostResetHandler;
}
@@ -648,25 +649,22 @@
protected void onEmptyDbCreated() {
// Database was just created, so wipe any previous widgets
if (mWidgetHostResetHandler != null) {
- new AppWidgetHost(mContext, Launcher.APPWIDGET_HOST_ID).deleteHost();
- mWidgetHostResetHandler.sendMessage(Message.obtain(
- mWidgetHostResetHandler,
- ChangeListenerWrapper.MSG_APP_WIDGET_HOST_RESET,
- mContext
- ));
+ newLauncherWidgetHost().deleteHost();
+ mWidgetHostResetHandler.sendEmptyMessage(
+ ChangeListenerWrapper.MSG_APP_WIDGET_HOST_RESET);
}
// Set the flag for empty DB
Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit();
// When a new DB is created, remove all previously stored managed profile information.
- ManagedProfileHeuristic.processAllUsers(Collections.<UserHandleCompat>emptyList(),
+ ManagedProfileHeuristic.processAllUsers(Collections.<UserHandle>emptyList(),
mContext);
}
public long getDefaultUserSerial() {
return UserManagerCompat.getInstance(mContext).getSerialNumberForUser(
- UserHandleCompat.myUserHandle());
+ Process.myUserHandle());
}
private void addFavoritesTable(SQLiteDatabase db, boolean optional) {
@@ -714,6 +712,56 @@
}
@Override
+ public void onOpen(SQLiteDatabase db) {
+ super.onOpen(db);
+ SharedPreferences prefs = mContext
+ .getSharedPreferences(LauncherFiles.DEVICE_PREFERENCES_KEY, 0);
+ int oldVersion = prefs.getInt(PREF_KEY_DATA_VERISON, 0);
+ if (oldVersion != DATA_VERSION) {
+ // Only run the data upgrade path for an existing db.
+ if (!Utilities.getPrefs(mContext).getBoolean(EMPTY_DATABASE_CREATED, false)) {
+ db.beginTransaction();
+ try {
+ onDataUpgrade(db, oldVersion);
+ db.setTransactionSuccessful();
+ } catch (Exception e) {
+ Log.d(TAG, "Error updating data version, ignoring", e);
+ return;
+ } finally {
+ db.endTransaction();
+ }
+ }
+ prefs.edit().putInt(PREF_KEY_DATA_VERISON, DATA_VERSION).apply();
+ }
+ }
+
+ /**
+ * Called when the data is updated as part of app update. It can be called multiple times
+ * with old version, even though it had been run before. The changes made here must be
+ * backwards compatible, else we risk breaking old devices during restore or binary
+ * version downgrade.
+ */
+ protected void onDataUpgrade(SQLiteDatabase db, int oldVersion) {
+ switch (oldVersion) {
+ case 0:
+ case 1: {
+ // Remove "profile extra"
+ UserManagerCompat um = UserManagerCompat.getInstance(mContext);
+ for (UserHandle user : um.getUserProfiles()) {
+ long serial = um.getSerialNumberForUser(user);
+ String sql = "update favorites set intent = replace(intent, "
+ + "';l.profile=" + serial + ";', ';') where itemType = 0;";
+ db.execSQL(sql);
+ }
+ }
+ case 2:
+ case 3:
+ // data updated
+ return;
+ }
+ }
+
+ @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (LOGD) Log.d(TAG, "onUpgrade triggered: " + oldVersion);
switch (oldVersion) {
@@ -764,12 +812,7 @@
}
}
case 16: {
- // We use the db version upgrade here to identify users who may not have seen
- // clings yet (because they weren't available), but for whom the clings are now
- // available (tablet users). Because one of the possible cling flows (migration)
- // is very destructive (wipes out workspaces), we want to prevent this from showing
- // until clear data. We do so by marking that the clings have been shown.
- LauncherClings.markFirstRunClingDismissed(mContext);
+ // No-op
}
case 17: {
// No-op
@@ -812,13 +855,12 @@
case 26:
// QSB was moved to the grid. Clear the first row on screen 0.
if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
- !LauncherDbUtils.prepareScreenZeroToHostQsb(db)) {
+ !LauncherDbUtils.prepareScreenZeroToHostQsb(mContext, db)) {
break;
}
- case 27: {
+ case 27:
// DB Upgraded successfully
return;
- }
}
// DB was not upgraded
@@ -828,6 +870,11 @@
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion == 28 && newVersion == 27) {
+ // TODO: remove this check. This is only applicable for internal development/testing
+ // and for any released version of Launcher.
+ return;
+ }
// This shouldn't happen -- throw our hands up in the air and start over.
Log.w(TAG, "Database version downgrade from: " + oldVersion + " to " + newVersion +
". Wiping databse.");
@@ -838,9 +885,56 @@
* Clears all the data for a fresh start.
*/
public void createEmptyDB(SQLiteDatabase db) {
- db.execSQL("DROP TABLE IF EXISTS " + Favorites.TABLE_NAME);
- db.execSQL("DROP TABLE IF EXISTS " + WorkspaceScreens.TABLE_NAME);
- onCreate(db);
+ db.beginTransaction();
+ try {
+ db.execSQL("DROP TABLE IF EXISTS " + Favorites.TABLE_NAME);
+ db.execSQL("DROP TABLE IF EXISTS " + WorkspaceScreens.TABLE_NAME);
+ onCreate(db);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ /**
+ * Removes widgets which are registered to the Launcher's host, but are not present
+ * in our model.
+ */
+ public void removeGhostWidgets(SQLiteDatabase db) {
+ // Get all existing widget ids.
+ final AppWidgetHost host = newLauncherWidgetHost();
+ final int[] allWidgets;
+ try {
+ Method getter = AppWidgetHost.class.getDeclaredMethod("getAppWidgetIds");
+ getter.setAccessible(true);
+ allWidgets = (int[]) getter.invoke(host);
+ } catch (Exception e) {
+ Log.e(TAG, "getAppWidgetIds not supported", e);
+ return;
+ }
+ try {
+ Cursor c = db.query(Favorites.TABLE_NAME,
+ new String[] {Favorites.APPWIDGET_ID },
+ "itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null, null, null);
+ HashSet<Integer> validWidgets = new HashSet<>();
+ while (c.moveToNext()) {
+ validWidgets.add(c.getInt(0));
+ }
+ c.close();
+
+ for (int widgetId : allWidgets) {
+ if (!validWidgets.contains(widgetId)) {
+ try {
+ FileLog.d(TAG, "Deleting invalid widget " + widgetId);
+ host.deleteAppWidgetId(widgetId);
+ } catch (RuntimeException e) {
+ // Ignore
+ }
+ }
+ }
+ } catch (SQLException ex) {
+ Log.w(TAG, "Error getting widgets list", ex);
+ }
}
/**
@@ -1012,6 +1106,10 @@
return mMaxItemId;
}
+ public AppWidgetHost newLauncherWidgetHost() {
+ return new AppWidgetHost(mContext, Launcher.APPWIDGET_HOST_ID);
+ }
+
@Override
public long insertAndCheck(SQLiteDatabase db, ContentValues values) {
return dbInsertAndCheck(this, db, Favorites.TABLE_NAME, null, values);
@@ -1019,7 +1117,7 @@
public void checkId(String table, ContentValues values) {
long id = values.getAsLong(LauncherSettings.BaseLauncherColumns._ID);
- if (table == WorkspaceScreens.TABLE_NAME) {
+ if (WorkspaceScreens.TABLE_NAME.equals(table)) {
mMaxScreenId = Math.max(id, mMaxScreenId);
} else {
mMaxItemId = Math.max(id, mMaxItemId);
@@ -1141,17 +1239,13 @@
if (mListener != null) {
switch (msg.what) {
case MSG_LAUNCHER_PROVIDER_CHANGED:
- mListener.onLauncherProviderChange();
+ mListener.onLauncherProviderChanged();
break;
case MSG_EXTRACTED_COLORS_CHANGED:
mListener.onExtractedColorsChanged();
break;
case MSG_APP_WIDGET_HOST_RESET:
- Context context = (Context) msg.obj;
- if (context != null) {
- context.sendBroadcast(new Intent(Launcher.ACTION_APPWIDGET_HOST_RESET)
- .setPackage(context.getPackageName()));
- }
+ mListener.onAppWidgetHostReset();
break;
}
}
diff --git a/src/com/android/launcher3/LauncherProviderChangeListener.java b/src/com/android/launcher3/LauncherProviderChangeListener.java
index 5998dad..7044812 100644
--- a/src/com/android/launcher3/LauncherProviderChangeListener.java
+++ b/src/com/android/launcher3/LauncherProviderChangeListener.java
@@ -7,7 +7,9 @@
*/
public interface LauncherProviderChangeListener {
- public void onLauncherProviderChange();
+ void onLauncherProviderChanged();
- public void onExtractedColorsChanged();
+ void onExtractedColorsChanged();
+
+ void onAppWidgetHostReset();
}
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index e884393..b25b256 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -37,7 +37,7 @@
public static final String MODIFIED = "modified";
}
- static interface BaseLauncherColumns extends ChangeLogColumns {
+ static public interface BaseLauncherColumns extends ChangeLogColumns {
/**
* Descriptive name of the gesture that can be displayed to the user.
* <P>Type: TEXT</P>
@@ -291,7 +291,6 @@
public static final String METHOD_NEW_SCREEN_ID = "generate_new_screen_id";
public static final String METHOD_CREATE_EMPTY_DB = "create_empty_db";
- public static final String METHOD_DELETE_DB = "delete_db";
public static final String METHOD_LOAD_DEFAULT_FAVORITES = "load_default_favorites";
@@ -300,6 +299,8 @@
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";
public static Bundle call(ContentResolver cr, String method) {
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index eb70650..39c466d 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -23,24 +23,19 @@
import android.animation.PropertyValuesHolder;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
-import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
import android.content.res.Resources;
-import android.os.Build;
import android.util.Log;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
-import android.view.animation.DecelerateInterpolator;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.anim.AnimationLayerSet;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.CircleRevealOutlineProvider;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.widget.WidgetsContainerView;
-import java.util.HashMap;
-
/**
* TODO: figure out what kind of tests we can write for this
*
@@ -119,9 +114,6 @@
public static final String TAG = "LSTAnimation";
- // Flags to determine how to set the layers on views before the transition animation
- public static final int BUILD_LAYER = 0;
- public static final int BUILD_AND_SET_LAYER = 1;
public static final int SINGLE_FRAME_DELAY = 16;
@Thunk Launcher mLauncher;
@@ -139,7 +131,7 @@
* @param startSearchAfterTransition Immediately starts app search after the transition to
* All Apps is completed.
*/
- public void startAnimationToAllApps(final Workspace.State fromWorkspaceState,
+ public void startAnimationToAllApps(
final boolean animated, final boolean startSearchAfterTransition) {
final AllAppsContainerView toView = mLauncher.getAppsView();
final View buttonView = mLauncher.getStartViewForAllAppsRevealAnimation();
@@ -174,18 +166,17 @@
animType = PULLUP;
}
// Only animate the search bar if animating from spring loaded mode back to all apps
- startAnimationToOverlay(fromWorkspaceState,
+ startAnimationToOverlay(
Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, animType, cb);
}
/**
* Starts an animation to the widgets view.
*/
- public void startAnimationToWidgets(final Workspace.State fromWorkspaceState,
- final boolean animated) {
+ public void startAnimationToWidgets(final boolean animated) {
final WidgetsContainerView toView = mLauncher.getWidgetsView();
final View buttonView = mLauncher.getWidgetsButton();
- startAnimationToOverlay(fromWorkspaceState,
+ startAnimationToOverlay(
Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated, CIRCULAR_REVEAL,
new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS){
@Override
@@ -228,22 +219,18 @@
/**
* Creates and starts a new animation to a particular overlay view.
*/
- @SuppressLint("NewApi")
private void startAnimationToOverlay(
- final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
+ 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 boolean material = Utilities.ATLEAST_LOLLIPOP;
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 fromView = mLauncher.getWorkspace();
-
- final HashMap<View, Integer> layerViews = new HashMap<>();
+ final AnimationLayerSet layerViews = new AnimationLayerSet();
// If for some reason our views aren't initialized, don't animate
boolean initialized = buttonView != null;
@@ -252,7 +239,7 @@
cancelAnimation();
final View contentView = toView.getContentView();
- playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
+ playCommonTransitionAnimations(toWorkspaceState,
animated, initialized, animation, layerViews);
if (!animated || !initialized) {
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
@@ -268,13 +255,6 @@
// Show the content view
contentView.setVisibility(View.VISIBLE);
-
- dispatchOnLauncherTransitionPrepare(fromView, animated, false);
- dispatchOnLauncherTransitionStart(fromView, animated, false);
- dispatchOnLauncherTransitionEnd(fromView, animated, false);
- dispatchOnLauncherTransitionPrepare(toView, animated, false);
- dispatchOnLauncherTransitionStart(toView, animated, false);
- dispatchOnLauncherTransitionEnd(toView, animated, false);
pCb.onTransitionComplete();
return;
}
@@ -291,20 +271,11 @@
revealView.setTranslationX(0f);
// Calculate the final animation values
- final float revealViewToAlpha;
- final float revealViewToXDrift;
- final float revealViewToYDrift;
- if (material) {
- int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(
- revealView, buttonView, null);
- revealViewToAlpha = pCb.materialRevealViewFinalAlpha;
- revealViewToYDrift = buttonViewToPanelDelta[1];
- revealViewToXDrift = buttonViewToPanelDelta[0];
- } else {
- revealViewToAlpha = 0f;
- revealViewToYDrift = 2 * height / 3;
- revealViewToXDrift = 0;
- }
+ 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 =
@@ -319,14 +290,14 @@
panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
// Play the animation
- layerViews.put(revealView, BUILD_AND_SET_LAYER);
+ layerViews.addView(revealView);
animation.play(panelAlphaAndDrift);
// Setup the animation for the content view
contentView.setVisibility(View.VISIBLE);
contentView.setAlpha(0f);
contentView.setTranslationY(revealViewToYDrift);
- layerViews.put(contentView, BUILD_AND_SET_LAYER);
+ layerViews.addView(contentView);
// Create the individual animators
ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
@@ -342,36 +313,24 @@
itemsAlpha.setStartDelay(itemsAlphaStagger);
animation.play(itemsAlpha);
- if (material) {
- 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);
+ 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) {
- dispatchOnLauncherTransitionEnd(fromView, animated, false);
- dispatchOnLauncherTransitionEnd(toView, animated, false);
-
// Hide the reveal view
revealView.setVisibility(View.INVISIBLE);
- // Disable all necessary layers
- for (View v : layerViews.keySet()) {
- if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
- v.setLayerType(View.LAYER_TYPE_NONE, null);
- }
- }
-
// This can hold unnecessary references to views.
cleanupAnimation();
pCb.onTransitionComplete();
@@ -379,92 +338,28 @@
});
- // Dispatch the prepare transition signal
- dispatchOnLauncherTransitionPrepare(fromView, animated, false);
- dispatchOnLauncherTransitionPrepare(toView, animated, false);
-
- final AnimatorSet stateAnimation = animation;
- final Runnable startAnimRunnable = new Runnable() {
- public void run() {
- // Check that mCurrentAnimation hasn't changed while
- // we waited for a layout/draw pass
- if (mCurrentAnimation != stateAnimation)
- return;
- dispatchOnLauncherTransitionStart(fromView, animated, false);
- dispatchOnLauncherTransitionStart(toView, animated, false);
-
- // Enable all necessary layers
- for (View v : layerViews.keySet()) {
- if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
- v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- }
- if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
- v.buildLayer();
- }
- }
-
- // Focus the new view
- toView.requestFocus();
-
- stateAnimation.start();
- }
- };
toView.bringToFront();
toView.setVisibility(View.VISIBLE);
- toView.post(startAnimRunnable);
+
+ animation.addListener(layerViews);
+ toView.post(new StartAnimRunnable(animation, toView));
mCurrentAnimation = animation;
} else if (animType == PULLUP) {
// We are animating the content view alpha, so ensure we have a layer for it
- layerViews.put(contentView, BUILD_AND_SET_LAYER);
+ layerViews.addView(contentView);
animation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- dispatchOnLauncherTransitionEnd(fromView, animated, false);
- dispatchOnLauncherTransitionEnd(toView, animated, false);
-
- // Disable all necessary layers
- for (View v : layerViews.keySet()) {
- if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
- v.setLayerType(View.LAYER_TYPE_NONE, null);
- }
- }
-
cleanupAnimation();
pCb.onTransitionComplete();
}
});
boolean shouldPost = mAllAppsController.animateToAllApps(animation, revealDurationSlide);
- dispatchOnLauncherTransitionPrepare(fromView, animated, false);
- dispatchOnLauncherTransitionPrepare(toView, animated, false);
-
- final AnimatorSet stateAnimation = animation;
- final Runnable startAnimRunnable = new Runnable() {
- public void run() {
- // Check that mCurrentAnimation hasn't changed while
- // we waited for a layout/draw pass
- if (mCurrentAnimation != stateAnimation)
- return;
-
- dispatchOnLauncherTransitionStart(fromView, animated, false);
- dispatchOnLauncherTransitionStart(toView, animated, false);
-
- // Enable all necessary layers
- for (View v : layerViews.keySet()) {
- if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
- v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- }
- if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
- v.buildLayer();
- }
- }
-
- toView.requestFocus();
- stateAnimation.start();
- }
- };
+ Runnable startAnimRunnable = new StartAnimRunnable(animation, toView);
mCurrentAnimation = animation;
+ mCurrentAnimation.addListener(layerViews);
if (shouldPost) {
toView.post(startAnimRunnable);
} else {
@@ -477,9 +372,9 @@
* Plays animations used by various transitions.
*/
private void playCommonTransitionAnimations(
- Workspace.State toWorkspaceState, View fromView, View toView,
+ Workspace.State toWorkspaceState,
boolean animated, boolean initialized, AnimatorSet animation,
- HashMap<View, Integer> layerViews) {
+ AnimationLayerSet layerViews) {
// Create the workspace animation.
// NOTE: this call apparently also sets the state for the workspace if !animated
Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
@@ -490,36 +385,16 @@
if (workspaceAnim != null) {
animation.play(workspaceAnim);
}
- // Dispatch onLauncherTransitionStep() as the animation interpolates.
- animation.play(dispatchOnLauncherTransitionStepAnim(fromView, toView));
}
}
/**
- * Returns an Animator that calls {@link #dispatchOnLauncherTransitionStep(View, float)} on
- * {@param fromView} and {@param toView} as the animation interpolates.
- *
- * This is a bit hacky: we create a dummy ValueAnimator just for the AnimatorUpdateListener.
- */
- private Animator dispatchOnLauncherTransitionStepAnim(final View fromView, final View toView) {
- ValueAnimator updateAnimator = ValueAnimator.ofFloat(0, 1);
- updateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- dispatchOnLauncherTransitionStep(fromView, animation.getAnimatedFraction());
- dispatchOnLauncherTransitionStep(toView, animation.getAnimatedFraction());
- }
- });
- return updateAnimator;
- }
-
- /**
* 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) {
- AllAppsContainerView appsView = mLauncher.getAppsView();
+ final AllAppsContainerView appsView = mLauncher.getAppsView();
// No alpha anim from all apps
PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
@Override
@@ -549,6 +424,7 @@
@Override
void onTransitionComplete() {
mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
+ appsView.reset();
}
};
// Only animate the search bar if animating to spring loaded mode from all apps
@@ -594,73 +470,32 @@
final Workspace.State toWorkspaceState, final boolean animated,
final Runnable onCompleteRunnable) {
final View fromWorkspace = mLauncher.getWorkspace();
- final HashMap<View, Integer> layerViews = new HashMap<>();
+ final AnimationLayerSet layerViews = new AnimationLayerSet();
final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
- final int revealDuration = mLauncher.getResources()
- .getInteger(R.integer.config_overlayRevealTime);
// Cancel the current animation
cancelAnimation();
- boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages;
-
- playCommonTransitionAnimations(toWorkspaceState, fromWorkspace, null,
- animated, animated, animation, layerViews);
+ playCommonTransitionAnimations(toWorkspaceState, animated, animated, animation, layerViews);
+ mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
if (animated) {
- dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible);
-
- final AnimatorSet stateAnimation = animation;
- final Runnable startAnimRunnable = new Runnable() {
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- public void run() {
- // Check that mCurrentAnimation hasn't changed while
- // we waited for a layout/draw pass
- if (mCurrentAnimation != stateAnimation)
- return;
-
- dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
-
- // Enable all necessary layers
- for (View v : layerViews.keySet()) {
- if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
- v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- }
- if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
- v.buildLayer();
- }
- }
- stateAnimation.start();
- }
- };
animation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
-
// Run any queued runnables
if (onCompleteRunnable != null) {
onCompleteRunnable.run();
}
- // Disable all necessary layers
- for (View v : layerViews.keySet()) {
- if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
- v.setLayerType(View.LAYER_TYPE_NONE, null);
- }
- }
-
// This can hold unnecessary references to views.
cleanupAnimation();
}
});
- fromWorkspace.post(startAnimRunnable);
+ animation.addListener(layerViews);
+ fromWorkspace.post(new StartAnimRunnable(animation, null));
mCurrentAnimation = animation;
} else /* if (!animated) */ {
- dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible);
- dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
- dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
-
// Run any queued runnables
if (onCompleteRunnable != null) {
onCompleteRunnable.run();
@@ -680,17 +515,15 @@
final PrivateTransitionCallbacks pCb) {
final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
final Resources res = mLauncher.getResources();
- final boolean material = Utilities.ATLEAST_LOLLIPOP;
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 int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger);
final View toView = mLauncher.getWorkspace();
final View revealView = fromView.getRevealView();
final View contentView = fromView.getContentView();
- final HashMap<View, Integer> layerViews = new HashMap<>();
+ final AnimationLayerSet layerViews = new AnimationLayerSet();
// If for some reason our views aren't initialized, don't animate
boolean initialized = buttonView != null;
@@ -698,9 +531,7 @@
// Cancel the current animation
cancelAnimation();
- boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages;
-
- playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
+ playCommonTransitionAnimations(toWorkspaceState,
animated, initialized, animation, layerViews);
if (!animated || !initialized) {
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
@@ -708,12 +539,6 @@
mAllAppsController.finishPullDown();
}
fromView.setVisibility(View.GONE);
- dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
- dispatchOnLauncherTransitionStart(fromView, animated, true);
- dispatchOnLauncherTransitionEnd(fromView, animated, true);
- dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
- dispatchOnLauncherTransitionStart(toView, animated, true);
- dispatchOnLauncherTransitionEnd(toView, animated, true);
pCb.onTransitionComplete();
// Run any queued runnables
@@ -733,27 +558,17 @@
revealView.setVisibility(View.VISIBLE);
revealView.setAlpha(1f);
revealView.setTranslationY(0);
- layerViews.put(revealView, BUILD_AND_SET_LAYER);
+ layerViews.addView(revealView);
// Calculate the final animation values
- final float revealViewToXDrift;
- final float revealViewToYDrift;
- if (material) {
- int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
- buttonView, null);
- revealViewToYDrift = buttonViewToPanelDelta[1];
- revealViewToXDrift = buttonViewToPanelDelta[0];
- } else {
- revealViewToYDrift = 2 * height / 3;
- revealViewToXDrift = 0;
- }
+ 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 = material ?
- new LogDecelerateInterpolator(100, 0) :
- new DecelerateInterpolator(1f);
+ TimeInterpolator decelerateInterpolator = new LogDecelerateInterpolator(100, 0);
ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY",
0, revealViewToYDrift);
panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
@@ -769,19 +584,16 @@
animation.play(panelDriftX);
// Setup animation for the reveal panel alpha
- final float revealViewToAlpha = !material ? 0f :
- pCb.materialRevealViewFinalAlpha;
- if (revealViewToAlpha != 1f) {
+ if (pCb.materialRevealViewFinalAlpha != 1f) {
ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha",
- 1f, revealViewToAlpha);
- panelAlpha.setDuration(material ? revealDuration : 150);
- panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
+ 1f, pCb.materialRevealViewFinalAlpha);
+ panelAlpha.setDuration(revealDuration);
panelAlpha.setInterpolator(decelerateInterpolator);
animation.play(panelAlpha);
}
// Setup the animation for the content view
- layerViews.put(contentView, BUILD_AND_SET_LAYER);
+ layerViews.addView(contentView);
// Create the individual animators
ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
@@ -809,45 +621,30 @@
});
animation.play(invalidateScrim);
- if (material) {
- // 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);
+ // 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);
}
- dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
- dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
-
animation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
fromView.setVisibility(View.GONE);
- dispatchOnLauncherTransitionEnd(fromView, animated, true);
- dispatchOnLauncherTransitionEnd(toView, animated, true);
-
// Run any queued runnables
if (onCompleteRunnable != null) {
onCompleteRunnable.run();
}
- // Disable all necessary layers
- for (View v : layerViews.keySet()) {
- if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
- v.setLayerType(View.LAYER_TYPE_NONE, null);
- }
- }
-
// Reset page transforms
if (contentView != null) {
contentView.setTranslationX(0);
@@ -861,35 +658,12 @@
}
});
- final AnimatorSet stateAnimation = animation;
- final Runnable startAnimRunnable = new Runnable() {
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- public void run() {
- // Check that mCurrentAnimation hasn't changed while
- // we waited for a layout/draw pass
- if (mCurrentAnimation != stateAnimation)
- return;
-
- dispatchOnLauncherTransitionStart(fromView, animated, false);
- dispatchOnLauncherTransitionStart(toView, animated, false);
-
- // Enable all necessary layers
- for (View v : layerViews.keySet()) {
- if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
- v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- }
- if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
- v.buildLayer();
- }
- }
- stateAnimation.start();
- }
- };
mCurrentAnimation = animation;
- fromView.post(startAnimRunnable);
+ 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.put(contentView, BUILD_AND_SET_LAYER);
+ layerViews.addView(contentView);
animation.addListener(new AnimatorListenerAdapter() {
boolean canceled = false;
@@ -901,20 +675,11 @@
@Override
public void onAnimationEnd(Animator animation) {
if (canceled) return;
- dispatchOnLauncherTransitionEnd(fromView, animated, true);
- dispatchOnLauncherTransitionEnd(toView, animated, true);
// Run any queued runnables
if (onCompleteRunnable != null) {
onCompleteRunnable.run();
}
- // Disable all necessary layers
- for (View v : layerViews.keySet()) {
- if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
- v.setLayerType(View.LAYER_TYPE_NONE, null);
- }
- }
-
cleanupAnimation();
pCb.onTransitionComplete();
}
@@ -922,37 +687,9 @@
});
boolean shouldPost = mAllAppsController.animateToWorkspace(animation, revealDurationSlide);
- // Dispatch the prepare transition signal
- dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
- dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
-
- final AnimatorSet stateAnimation = animation;
- final Runnable startAnimRunnable = new Runnable() {
- public void run() {
- // Check that mCurrentAnimation hasn't changed while
- // we waited for a layout/draw pass
- if (mCurrentAnimation != stateAnimation)
- return;
-
- dispatchOnLauncherTransitionStart(fromView, animated, false);
- dispatchOnLauncherTransitionStart(toView, animated, false);
-
- // Enable all necessary layers
- for (View v : layerViews.keySet()) {
- if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
- v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- }
- if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
- v.buildLayer();
- }
- }
-
- // Focus the new view
- toView.requestFocus();
- stateAnimation.start();
- }
- };
+ Runnable startAnimRunnable = new StartAnimRunnable(animation, toView);
mCurrentAnimation = animation;
+ mCurrentAnimation.addListener(layerViews);
if (shouldPost) {
fromView.post(startAnimRunnable);
} else {
@@ -963,52 +700,6 @@
}
/**
- * Dispatches the prepare-transition event to suitable views.
- */
- void dispatchOnLauncherTransitionPrepare(View v, boolean animated,
- boolean multiplePagesVisible) {
- if (v instanceof LauncherTransitionable) {
- ((LauncherTransitionable) v).onLauncherTransitionPrepare(mLauncher, animated,
- multiplePagesVisible);
- }
- }
-
- /**
- * Dispatches the start-transition event to suitable views.
- */
- void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) {
- if (v instanceof LauncherTransitionable) {
- ((LauncherTransitionable) v).onLauncherTransitionStart(mLauncher, animated,
- toWorkspace);
- }
-
- // Update the workspace transition step as well
- dispatchOnLauncherTransitionStep(v, 0f);
- }
-
- /**
- * Dispatches the step-transition event to suitable views.
- */
- void dispatchOnLauncherTransitionStep(View v, float t) {
- if (v instanceof LauncherTransitionable) {
- ((LauncherTransitionable) v).onLauncherTransitionStep(mLauncher, t);
- }
- }
-
- /**
- * Dispatches the end-transition event to suitable views.
- */
- void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) {
- if (v instanceof LauncherTransitionable) {
- ((LauncherTransitionable) v).onLauncherTransitionEnd(mLauncher, animated,
- toWorkspace);
- }
-
- // Update the workspace transition step as well
- dispatchOnLauncherTransitionStep(v, 1f);
- }
-
- /**
* Cancels the current animation.
*/
private void cancelAnimation() {
@@ -1022,4 +713,26 @@
@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/LauncherTransitionable.java b/src/com/android/launcher3/LauncherTransitionable.java
deleted file mode 100644
index b97aaec..0000000
--- a/src/com/android/launcher3/LauncherTransitionable.java
+++ /dev/null
@@ -1,27 +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;
-
-/**
- * An interface to get callbacks during a launcher transition.
- */
-public interface LauncherTransitionable {
- void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean multiplePagesVisible);
- void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace);
- void onLauncherTransitionStep(Launcher l, float t);
- void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace);
-}
diff --git a/src/com/android/launcher3/LauncherViewPropertyAnimator.java b/src/com/android/launcher3/LauncherViewPropertyAnimator.java
deleted file mode 100644
index 4406a2c..0000000
--- a/src/com/android/launcher3/LauncherViewPropertyAnimator.java
+++ /dev/null
@@ -1,275 +0,0 @@
-/*
- * Copyright (C) 2012 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.Animator.AnimatorListener;
-import android.animation.TimeInterpolator;
-import android.view.View;
-import android.view.ViewPropertyAnimator;
-
-import java.util.ArrayList;
-import java.util.EnumSet;
-
-public class LauncherViewPropertyAnimator extends Animator implements AnimatorListener {
-
- enum Properties {
- TRANSLATION_X,
- TRANSLATION_Y,
- SCALE_X,
- SCALE_Y,
- ROTATION_Y,
- ALPHA,
- START_DELAY,
- DURATION,
- INTERPOLATOR,
- WITH_LAYER
- }
- EnumSet<Properties> mPropertiesToSet = EnumSet.noneOf(Properties.class);
- ViewPropertyAnimator mViewPropertyAnimator;
- View mTarget;
-
- float mTranslationX;
- float mTranslationY;
- float mScaleX;
- float mScaleY;
- float mRotationY;
- float mAlpha;
- long mStartDelay;
- long mDuration;
- TimeInterpolator mInterpolator;
- ArrayList<Animator.AnimatorListener> mListeners = new ArrayList<>();
- boolean mRunning = false;
- FirstFrameAnimatorHelper mFirstFrameHelper;
-
- public LauncherViewPropertyAnimator(View target) {
- mTarget = target;
- }
-
- @Override
- public void addListener(Animator.AnimatorListener listener) {
- mListeners.add(listener);
- }
-
- @Override
- public void cancel() {
- if (mViewPropertyAnimator != null) {
- mViewPropertyAnimator.cancel();
- }
- }
-
- @Override
- public Animator clone() {
- throw new RuntimeException("Not implemented");
- }
-
- @Override
- public void end() {
- throw new RuntimeException("Not implemented");
- }
-
- @Override
- public long getDuration() {
- return mDuration;
- }
-
- @Override
- public ArrayList<Animator.AnimatorListener> getListeners() {
- return mListeners;
- }
-
- @Override
- public long getStartDelay() {
- return mStartDelay;
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- for (int i = 0; i < mListeners.size(); i++) {
- Animator.AnimatorListener listener = mListeners.get(i);
- listener.onAnimationCancel(this);
- }
- mRunning = false;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- for (int i = 0; i < mListeners.size(); i++) {
- Animator.AnimatorListener listener = mListeners.get(i);
- listener.onAnimationEnd(this);
- }
- mRunning = false;
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
- for (int i = 0; i < mListeners.size(); i++) {
- Animator.AnimatorListener listener = mListeners.get(i);
- listener.onAnimationRepeat(this);
- }
- }
-
- @Override
- public void onAnimationStart(Animator animation) {
- // This is the first time we get a handle to the internal ValueAnimator
- // used by the ViewPropertyAnimator.
- mFirstFrameHelper.onAnimationStart(animation);
-
- for (int i = 0; i < mListeners.size(); i++) {
- Animator.AnimatorListener listener = mListeners.get(i);
- listener.onAnimationStart(this);
- }
- mRunning = true;
- }
-
- @Override
- public boolean isRunning() {
- return mRunning;
- }
-
- @Override
- public boolean isStarted() {
- return mViewPropertyAnimator != null;
- }
-
- @Override
- public void removeAllListeners() {
- mListeners.clear();
- }
-
- @Override
- public void removeListener(Animator.AnimatorListener listener) {
- mListeners.remove(listener);
- }
-
- @Override
- public Animator setDuration(long duration) {
- mPropertiesToSet.add(Properties.DURATION);
- mDuration = duration;
- return this;
- }
-
- @Override
- public void setInterpolator(TimeInterpolator value) {
- mPropertiesToSet.add(Properties.INTERPOLATOR);
- mInterpolator = value;
- }
-
- @Override
- public void setStartDelay(long startDelay) {
- mPropertiesToSet.add(Properties.START_DELAY);
- mStartDelay = startDelay;
- }
-
- @Override
- public void setTarget(Object target) {
- throw new RuntimeException("Not implemented");
- }
-
- @Override
- public void setupEndValues() {
-
- }
-
- @Override
- public void setupStartValues() {
- }
-
- @Override
- public void start() {
- mViewPropertyAnimator = mTarget.animate();
-
- // FirstFrameAnimatorHelper hooks itself up to the updates on the animator,
- // and then adjusts the play time to keep the first two frames jank-free
- mFirstFrameHelper = new FirstFrameAnimatorHelper(mViewPropertyAnimator, mTarget);
-
- if (mPropertiesToSet.contains(Properties.TRANSLATION_X)) {
- mViewPropertyAnimator.translationX(mTranslationX);
- }
- if (mPropertiesToSet.contains(Properties.TRANSLATION_Y)) {
- mViewPropertyAnimator.translationY(mTranslationY);
- }
- if (mPropertiesToSet.contains(Properties.SCALE_X)) {
- mViewPropertyAnimator.scaleX(mScaleX);
- }
- if (mPropertiesToSet.contains(Properties.ROTATION_Y)) {
- mViewPropertyAnimator.rotationY(mRotationY);
- }
- if (mPropertiesToSet.contains(Properties.SCALE_Y)) {
- mViewPropertyAnimator.scaleY(mScaleY);
- }
- if (mPropertiesToSet.contains(Properties.ALPHA)) {
- mViewPropertyAnimator.alpha(mAlpha);
- }
- if (mPropertiesToSet.contains(Properties.START_DELAY)) {
- mViewPropertyAnimator.setStartDelay(mStartDelay);
- }
- if (mPropertiesToSet.contains(Properties.DURATION)) {
- mViewPropertyAnimator.setDuration(mDuration);
- }
- if (mPropertiesToSet.contains(Properties.INTERPOLATOR)) {
- mViewPropertyAnimator.setInterpolator(mInterpolator);
- }
- if (mPropertiesToSet.contains(Properties.WITH_LAYER)) {
- mViewPropertyAnimator.withLayer();
- }
- mViewPropertyAnimator.setListener(this);
- mViewPropertyAnimator.start();
- LauncherAnimUtils.cancelOnDestroyActivity(this);
- }
-
- public LauncherViewPropertyAnimator translationX(float value) {
- mPropertiesToSet.add(Properties.TRANSLATION_X);
- mTranslationX = value;
- return this;
- }
-
- public LauncherViewPropertyAnimator translationY(float value) {
- mPropertiesToSet.add(Properties.TRANSLATION_Y);
- mTranslationY = value;
- return this;
- }
-
- public LauncherViewPropertyAnimator scaleX(float value) {
- mPropertiesToSet.add(Properties.SCALE_X);
- mScaleX = value;
- return this;
- }
-
- public LauncherViewPropertyAnimator scaleY(float value) {
- mPropertiesToSet.add(Properties.SCALE_Y);
- mScaleY = value;
- return this;
- }
-
- public LauncherViewPropertyAnimator rotationY(float value) {
- mPropertiesToSet.add(Properties.ROTATION_Y);
- mRotationY = value;
- return this;
- }
-
- public LauncherViewPropertyAnimator alpha(float value) {
- mPropertiesToSet.add(Properties.ALPHA);
- mAlpha = value;
- return this;
- }
-
- public LauncherViewPropertyAnimator withLayer() {
- mPropertiesToSet.add(Properties.WITH_LAYER);
- return this;
- }
-}
diff --git a/src/com/android/launcher3/MainThreadExecutor.java b/src/com/android/launcher3/MainThreadExecutor.java
index 866b17c..4ca0a59 100644
--- a/src/com/android/launcher3/MainThreadExecutor.java
+++ b/src/com/android/launcher3/MainThreadExecutor.java
@@ -16,65 +16,18 @@
package com.android.launcher3;
-import android.os.Handler;
import android.os.Looper;
-import java.util.List;
-import java.util.concurrent.AbstractExecutorService;
-import java.util.concurrent.TimeUnit;
+import com.android.launcher3.util.LooperExecuter;
/**
* An executor service that executes its tasks on the main thread.
*
* Shutting down this executor is not supported.
*/
-public class MainThreadExecutor extends AbstractExecutorService {
+public class MainThreadExecutor extends LooperExecuter {
- private Handler mHandler = new Handler(Looper.getMainLooper());
-
- @Override
- public void execute(Runnable runnable) {
- if (Looper.getMainLooper() == Looper.myLooper()) {
- runnable.run();
- } else {
- mHandler.post(runnable);
- }
- }
-
- /**
- * Not supported and throws an exception when used.
- */
- @Override
- @Deprecated
- public void shutdown() {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Not supported and throws an exception when used.
- */
- @Override
- @Deprecated
- public List<Runnable> shutdownNow() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isShutdown() {
- return false;
- }
-
- @Override
- public boolean isTerminated() {
- return false;
- }
-
- /**
- * Not supported and throws an exception when used.
- */
- @Override
- @Deprecated
- public boolean awaitTermination(long l, TimeUnit timeUnit) throws InterruptedException {
- throw new UnsupportedOperationException();
+ public MainThreadExecutor() {
+ super(Looper.getMainLooper());
}
}
diff --git a/src/com/android/launcher3/OverviewButtonClickListener.java b/src/com/android/launcher3/OverviewButtonClickListener.java
new file mode 100644
index 0000000..dd670d2
--- /dev/null
+++ b/src/com/android/launcher3/OverviewButtonClickListener.java
@@ -0,0 +1,51 @@
+package com.android.launcher3;
+
+import android.view.View;
+
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+
+/**
+ * A specialized listener for Overview buttons where both clicks and long clicks are logged
+ * handled the same via {@link #handleViewClick(View)}.
+ */
+public abstract class OverviewButtonClickListener implements View.OnClickListener,
+ View.OnLongClickListener {
+
+ private int mControlType; /** ControlType enum as defined in {@link Action.Touch} */
+
+ public OverviewButtonClickListener(int controlType) {
+ mControlType = controlType;
+ }
+
+ public void attachTo(View v) {
+ v.setOnClickListener(this);
+ v.setOnLongClickListener(this);
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (shouldPerformClick(view)) {
+ handleViewClick(view, Action.Touch.TAP);
+ }
+ }
+
+ @Override
+ public boolean onLongClick(View view) {
+ if (shouldPerformClick(view)) {
+ handleViewClick(view, Action.Touch.LONGPRESS);
+ }
+ return true;
+ }
+
+ private boolean shouldPerformClick(View view) {
+ return !Launcher.getLauncher(view.getContext()).getWorkspace().isSwitchingState();
+ }
+
+ private void handleViewClick(View view, int action) {
+ handleViewClick(view);
+ Launcher.getLauncher(view.getContext()).getUserEventDispatcher()
+ .logActionOnControl(action, mControlType);
+ }
+
+ public abstract void handleViewClick(View view);
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index e380e26..fb6a611 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -22,14 +22,11 @@
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
-import android.graphics.RectF;
-import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -50,6 +47,7 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.Interpolator;
+import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.pageindicators.PageIndicator;
import com.android.launcher3.util.LauncherEdgeEffect;
import com.android.launcher3.util.Thunk;
@@ -98,7 +96,6 @@
@ViewDebug.ExportedProperty(category = "launcher")
protected int mCurrentPage;
- protected int mRestorePage = INVALID_RESTORE_PAGE;
private int mChildCountOnLastLayout;
@ViewDebug.ExportedProperty(category = "launcher")
@@ -119,7 +116,6 @@
private float mLastMotionXRemainder;
private float mLastMotionY;
private float mTotalMotionX;
- private int mLastScreenCenter = -1;
private boolean mCancelTap;
@@ -132,7 +128,6 @@
protected final static int TOUCH_STATE_REORDERING = 4;
protected int mTouchState = TOUCH_STATE_REST;
- private boolean mForceScreenScrolled = false;
protected OnLongClickListener mLongClickListener;
@@ -145,7 +140,7 @@
protected int mActivePointerId = INVALID_POINTER;
- protected boolean mIsPageMoving = false;
+ protected boolean mIsPageInTransition = false;
protected boolean mWasInOverscroll = false;
@@ -184,7 +179,6 @@
private static final float[] sTmpPoint = new float[2];
private static final int[] sTmpIntPoint = new int[2];
private static final Rect sTmpRect = new Rect();
- private static final RectF sTmpRectF = new RectF();
protected final Rect mInsets = new Rect();
protected final boolean mIsRtl;
@@ -410,7 +404,6 @@
if (getChildCount() == 0) {
return;
}
- mForceScreenScrolled = true;
mCurrentPage = validateNewPage(currentPage);
updateCurrentPageScroll();
notifyPageSwitchListener();
@@ -418,17 +411,6 @@
}
/**
- * The restore page will be set in place of the current page at the next (likely first)
- * layout.
- */
- void setRestorePage(int restorePage) {
- mRestorePage = restorePage;
- }
- int getRestorePage() {
- return mRestorePage;
- }
-
- /**
* Should be called whenever the page changes. In the case of a scroll, we wait until the page
* has settled.
*/
@@ -445,30 +427,36 @@
}
}
}
- protected void pageBeginMoving() {
- if (!mIsPageMoving) {
- mIsPageMoving = true;
- onPageBeginMoving();
+ protected void pageBeginTransition() {
+ if (!mIsPageInTransition) {
+ mIsPageInTransition = true;
+ onPageBeginTransition();
}
}
- protected void pageEndMoving() {
- if (mIsPageMoving) {
- mIsPageMoving = false;
- onPageEndMoving();
+ protected void pageEndTransition() {
+ if (mIsPageInTransition) {
+ mIsPageInTransition = false;
+ onPageEndTransition();
}
}
- protected boolean isPageMoving() {
- return mIsPageMoving;
+ protected boolean isPageInTransition() {
+ return mIsPageInTransition;
}
- // a method that subclasses can override to add behavior
- protected void onPageBeginMoving() {
+ /**
+ * Called when the page starts moving as part of the scroll. Subclasses can override this
+ * to provide custom behavior during animation.
+ */
+ protected void onPageBeginTransition() {
}
- // a method that subclasses can override to add behavior
- protected void onPageEndMoving() {
+ /**
+ * Called when the page ends moving as part of the scroll. Subclasses can override this
+ * to provide custom behavior during animation.
+ */
+ protected void onPageEndTransition() {
mWasInOverscroll = false;
}
@@ -576,7 +564,7 @@
protected boolean computeScrollHelper(boolean shouldInvalidate) {
if (mScroller.computeScrollOffset()) {
// Don't bother scrolling if the page does not need to be moved
- if (getScrollX() != mScroller.getCurrX()
+ if (getUnboundedScrollX() != mScroller.getCurrX()
|| getScrollY() != mScroller.getCurrY()) {
float scaleX = mFreeScroll ? getScaleX() : 1f;
int scrollX = (int) (mScroller.getCurrX() * (1 / scaleX));
@@ -596,7 +584,7 @@
// We don't want to trigger a page end moving unless the page has settled
// and the user has stopped scrolling
if (mTouchState == TOUCH_STATE_REST) {
- pageEndMoving();
+ pageEndTransition();
}
onPostReorderingAnimationCompleted();
@@ -879,12 +867,7 @@
}
if (mScroller.isFinished() && mChildCountOnLastLayout != childCount) {
- if (mRestorePage != INVALID_RESTORE_PAGE) {
- setCurrentPage(mRestorePage);
- mRestorePage = INVALID_RESTORE_PAGE;
- } else {
- setCurrentPage(getNextPage());
- }
+ setCurrentPage(getNextPage());
}
mChildCountOnLastLayout = childCount;
@@ -916,11 +899,6 @@
requestLayout();
}
- /**
- * Called when the center screen changes during scrolling.
- */
- protected void screenScrolled(int screenCenter) { }
-
@Override
public void onChildViewAdded(View parent, View child) {
// Update the page indicator, we don't update the page indicator as we
@@ -931,14 +909,12 @@
// This ensures that when children are added, they get the correct transforms / alphas
// in accordance with any scroll effects.
- mForceScreenScrolled = true;
updateFreescrollBounds();
invalidate();
}
@Override
public void onChildViewRemoved(View parent, View child) {
- mForceScreenScrolled = true;
updateFreescrollBounds();
invalidate();
}
@@ -996,99 +972,6 @@
range[1] = Math.max(0, getChildCount() - 1);
}
- protected void getVisiblePages(int[] range) {
- final int count = getChildCount();
- range[0] = -1;
- range[1] = -1;
-
- if (count > 0) {
- final int visibleLeft = -getLeft();
- final int visibleRight = visibleLeft + getViewportWidth();
- final Matrix pageShiftMatrix = getPageShiftMatrix();
- int curScreen = 0;
-
- for (int i = 0; i < count; i++) {
- View currPage = getPageAt(i);
-
- // Verify if the page bounds are within the visible range.
- sTmpRectF.left = 0;
- sTmpRectF.right = currPage.getMeasuredWidth();
- currPage.getMatrix().mapRect(sTmpRectF);
- sTmpRectF.offset(currPage.getLeft() - getScrollX(), 0);
- pageShiftMatrix.mapRect(sTmpRectF);
-
- if (sTmpRectF.left > visibleRight || sTmpRectF.right < visibleLeft) {
- if (range[0] == -1) {
- continue;
- } else {
- break;
- }
- }
- curScreen = i;
- if (range[0] < 0) {
- range[0] = curScreen;
- }
- }
-
- range[1] = curScreen;
- } else {
- range[0] = -1;
- range[1] = -1;
- }
- }
-
- protected Matrix getPageShiftMatrix() {
- return getMatrix();
- }
-
- protected boolean shouldDrawChild(View child) {
- return child.getVisibility() == VISIBLE;
- }
-
- @Override
- protected void dispatchDraw(Canvas canvas) {
- // Find out which screens are visible; as an optimization we only call draw on them
- final int pageCount = getChildCount();
- if (pageCount > 0) {
- int halfScreenSize = getViewportWidth() / 2;
- int screenCenter = getScrollX() + halfScreenSize;
-
- if (screenCenter != mLastScreenCenter || mForceScreenScrolled) {
- // set mForceScreenScrolled before calling screenScrolled so that screenScrolled can
- // set it for the next frame
- mForceScreenScrolled = false;
- screenScrolled(screenCenter);
- mLastScreenCenter = screenCenter;
- }
-
- getVisiblePages(mTempVisiblePagesRange);
- final int leftScreen = mTempVisiblePagesRange[0];
- final int rightScreen = mTempVisiblePagesRange[1];
- if (leftScreen != -1 && rightScreen != -1) {
- final long drawingTime = getDrawingTime();
- // Clip to the bounds
- canvas.save();
- canvas.clipRect(getScrollX(), getScrollY(), getScrollX() + getRight() - getLeft(),
- getScrollY() + getBottom() - getTop());
-
- // Draw all the children, leaving the drag view for last
- for (int i = pageCount - 1; i >= 0; i--) {
- final View v = getPageAt(i);
- if (v == mDragView) continue;
- if (leftScreen <= i && i <= rightScreen && shouldDrawChild(v)) {
- drawChild(canvas, v, drawingTime);
- }
- }
- // Draw the drag view on top (if there is one)
- if (mDragView != null) {
- drawChild(canvas, mDragView, drawingTime);
- }
-
- canvas.restore();
- }
- }
- }
-
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
@@ -1099,7 +982,7 @@
canvas.translate(display.left, display.top);
canvas.rotate(270);
- getEdgeVerticalPostion(sTmpIntPoint);
+ getEdgeVerticalPosition(sTmpIntPoint);
canvas.translate(display.top - sTmpIntPoint[1], 0);
mEdgeGlowLeft.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], display.width());
if (mEdgeGlowLeft.draw(canvas)) {
@@ -1113,7 +996,7 @@
canvas.translate(display.left + mPageScrolls[mIsRtl ? 0 : (getPageCount() - 1)], display.top);
canvas.rotate(90);
- getEdgeVerticalPostion(sTmpIntPoint);
+ getEdgeVerticalPosition(sTmpIntPoint);
canvas.translate(sTmpIntPoint[0] - display.top, -display.width());
mEdgeGlowRight.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], display.width());
@@ -1128,7 +1011,7 @@
/**
* Returns the top and bottom position for the edge effect.
*/
- protected abstract void getEdgeVerticalPostion(int[] pos);
+ protected abstract void getEdgeVerticalPosition(int[] pos);
@Override
public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
@@ -1184,6 +1067,10 @@
@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+ if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
+ return;
+ }
+
// XXX-RTL: This will be fixed in a future CL
if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) {
getPageAt(mCurrentPage).addFocusables(views, direction, focusableMode);
@@ -1338,7 +1225,7 @@
mTouchState = TOUCH_STATE_REST;
if (!mScroller.isFinished() && !mFreeScroll) {
setCurrentPage(getNextPage());
- pageEndMoving();
+ pageEndTransition();
}
} else {
if (isTouchPointInViewportWithBuffer((int) mDownMotionX, (int) mDownMotionY)) {
@@ -1399,7 +1286,7 @@
mLastMotionX = x;
mLastMotionXRemainder = 0;
onScrollInteractionBegin();
- pageBeginMoving();
+ pageBeginTransition();
// Stop listening for things like pinches.
requestDisallowInterceptTouchEvent(true);
}
@@ -1484,8 +1371,12 @@
dampedOverScroll(amount);
}
- public void enableFreeScroll() {
+ /**
+ * return true if freescroll has been enabled, false otherwise
+ */
+ public boolean enableFreeScroll() {
setEnableFreeScroll(true);
+ return true;
}
public void disableFreeScroll() {
@@ -1581,7 +1472,7 @@
if (mTouchState == TOUCH_STATE_SCROLLING) {
onScrollInteractionBegin();
- pageBeginMoving();
+ pageBeginTransition();
}
break;
@@ -2027,7 +1918,6 @@
mNextPage = whichPage;
- pageBeginMoving();
awakenScrollBars(duration);
if (immediate) {
duration = 0;
@@ -2035,6 +1925,10 @@
duration = Math.abs(delta);
}
+ if (duration != 0) {
+ pageBeginTransition();
+ }
+
if (!mScroller.isFinished()) {
abortScrollerAnimation(false);
}
@@ -2052,9 +1946,9 @@
// Trigger a compute() to finish switching pages if necessary
if (immediate) {
computeScroll();
+ pageEndTransition();
}
- mForceScreenScrolled = true;
invalidate();
}
@@ -2105,11 +1999,12 @@
// Animate the drag view back to the original position
private void animateDragViewToOriginalPosition() {
if (mDragView != null) {
- Animator anim = new LauncherViewPropertyAnimator(mDragView)
- .translationX(0)
- .translationY(0)
- .scaleX(1)
- .scaleY(1)
+ Animator anim = LauncherAnimUtils.ofPropertyValuesHolder(mDragView,
+ new PropertyListBuilder()
+ .scale(1)
+ .translationX(0)
+ .translationY(0)
+ .build())
.setDuration(REORDERING_DROP_REPOSITION_DURATION);
anim.addListener(new AnimatorListenerAdapter() {
@Override
@@ -2184,20 +2079,14 @@
if (!mReorderingStarted) return;
mReorderingStarted = false;
- // If we haven't flung-to-delete the current child, then we just animate the drag view
- // back into position
- final Runnable onCompleteRunnable = new Runnable() {
- @Override
- public void run() {
- onEndReordering();
- }
- };
-
mPostReorderingPreZoomInRunnable = new Runnable() {
public void run() {
- onCompleteRunnable.run();
+ // If we haven't flung-to-delete the current child,
+ // then we just animate the drag view back into position
+ onEndReordering();
+
enableFreeScroll();
- };
+ }
};
mPostReorderingPreZoomInRemainingAnimationCount =
@@ -2210,7 +2099,6 @@
/* Accessibility */
@SuppressWarnings("deprecation")
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
@@ -2227,9 +2115,7 @@
// Besides disabling the accessibility long-click, this also prevents this view from getting
// accessibility focus.
info.setLongClickable(false);
- if (Utilities.ATLEAST_LOLLIPOP) {
- info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
- }
+ info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
}
@Override
diff --git a/src/com/android/launcher3/PendingAddItemInfo.java b/src/com/android/launcher3/PendingAddItemInfo.java
index 31820d7..76de3e7 100644
--- a/src/com/android/launcher3/PendingAddItemInfo.java
+++ b/src/com/android/launcher3/PendingAddItemInfo.java
@@ -20,7 +20,7 @@
/**
* Meta data that is used for deferred binding.
- * e.g., this object is used to pass information on dragable targets when they are dropped onto
+ * e.g., this object is used to pass information on draggable targets when they are dropped onto
* the workspace from another container.
*/
public class PendingAddItemInfo extends ItemInfo {
diff --git a/src/com/android/launcher3/PendingAppWidgetHostView.java b/src/com/android/launcher3/PendingAppWidgetHostView.java
index f01c7f2..b163464 100644
--- a/src/com/android/launcher3/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/PendingAppWidgetHostView.java
@@ -16,17 +16,13 @@
package com.android.launcher3;
-import android.annotation.TargetApi;
import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources.Theme;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.os.Bundle;
import android.text.Layout;
import android.text.StaticLayout;
@@ -36,18 +32,21 @@
import android.view.View;
import android.view.View.OnClickListener;
-public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implements OnClickListener {
+import com.android.launcher3.IconCache.ItemInfoUpdateReceiver;
+import com.android.launcher3.graphics.DrawableFactory;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.util.Themes;
+
+public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
+ implements OnClickListener, ItemInfoUpdateReceiver {
private static final float SETUP_ICON_SIZE_FACTOR = 2f / 5;
private static final float MIN_SATUNATION = 0.7f;
- private static Theme sPreloaderTheme;
-
private final Rect mRect = new Rect();
private View mDefaultView;
private OnClickListener mClickListener;
private final LauncherAppWidgetInfo mInfo;
private final int mStartState;
- private final Intent mIconLookupIntent;
private final boolean mDisabledForSafeMode;
private Launcher mLauncher;
@@ -61,27 +60,30 @@
private final TextPaint mPaint;
private Layout mSetupTextLayout;
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
public PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info,
- boolean disabledForSafeMode) {
+ IconCache cache, boolean disabledForSafeMode) {
super(new ContextThemeWrapper(context, R.style.WidgetContainerTheme));
mLauncher = Launcher.getLauncher(context);
mInfo = info;
mStartState = info.restoreStatus;
- mIconLookupIntent = new Intent().setComponent(info.providerName);
mDisabledForSafeMode = disabledForSafeMode;
mPaint = new TextPaint();
- mPaint.setColor(0xFFFFFFFF);
+ mPaint.setColor(Themes.getAttrColor(getContext(), android.R.attr.textColorPrimary));
mPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,
mLauncher.getDeviceProfile().iconTextSizePx, getResources().getDisplayMetrics()));
- setBackgroundResource(R.drawable.quantum_panel_dark);
+ setBackgroundResource(R.drawable.pending_widget_bg);
setWillNotDraw(false);
- if (Utilities.ATLEAST_LOLLIPOP) {
- setElevation(getResources().getDimension(R.dimen.pending_widget_elevation));
- }
+ setElevation(getResources().getDimension(R.dimen.pending_widget_elevation));
+ updateAppWidget(null);
+ setOnClickListener(mLauncher);
+
+ // Load icon
+ PackageItemInfo item = new PackageItemInfo(info.providerName.getPackageName());
+ item.user = info.user;
+ cache.updateIconInBackground(this, item);
}
@Override
@@ -117,8 +119,9 @@
mDrawableSizeChanged = true;
}
- public void updateIcon(IconCache cache) {
- Bitmap icon = cache.getIcon(mIconLookupIntent, mInfo.user);
+ @Override
+ public void reapplyItemInfo(ItemInfoWithIcon info) {
+ Bitmap icon = info.iconBitmap;
if (mIcon == icon) {
return;
}
@@ -132,30 +135,27 @@
// 1) App icon in the center
// 2) Preload icon in the center
// 3) Setup icon in the center and app icon in the top right corner.
+ DrawableFactory drawableFactory = DrawableFactory.get(getContext());
if (mDisabledForSafeMode) {
- FastBitmapDrawable disabledIcon = mLauncher.createIconDrawable(mIcon);
- disabledIcon.setState(FastBitmapDrawable.State.DISABLED);
+ FastBitmapDrawable disabledIcon = drawableFactory.newIcon(mIcon, mInfo);
+ disabledIcon.setIsDisabled(true);
mCenterDrawable = disabledIcon;
mSettingIconDrawable = null;
} else if (isReadyForClickSetup()) {
- mCenterDrawable = new FastBitmapDrawable(mIcon);
+ mCenterDrawable = drawableFactory.newIcon(mIcon, mInfo);
mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
updateSettingColor();
} else {
- if (sPreloaderTheme == null) {
- sPreloaderTheme = getResources().newTheme();
- sPreloaderTheme.applyStyle(R.style.PreloadIcon, true);
- }
-
- FastBitmapDrawable drawable = mLauncher.createIconDrawable(mIcon);
- mCenterDrawable = new PreloadIconDrawable(drawable, sPreloaderTheme);
+ mCenterDrawable = DrawableFactory.get(getContext())
+ .newPendingIcon(mIcon, getContext());
mCenterDrawable.setCallback(this);
mSettingIconDrawable = null;
applyState();
}
mDrawableSizeChanged = true;
}
+ invalidate();
}
private void updateSettingColor() {
@@ -219,13 +219,10 @@
int availableHeight = getHeight() - paddingTop - paddingBottom - 2 * minPadding;
if (mSettingIconDrawable == null) {
- int outset = (mCenterDrawable instanceof PreloadIconDrawable) ?
- ((PreloadIconDrawable) mCenterDrawable).getOutset() : 0;
- int maxSize = grid.iconSizePx + 2 * outset;
+ int maxSize = grid.iconSizePx;
int size = Math.min(maxSize, Math.min(availableWidth, availableHeight));
mRect.set(0, 0, size, size);
- mRect.inset(outset, outset);
mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
mCenterDrawable.setBounds(mRect);
} else {
diff --git a/src/com/android/launcher3/PinchAnimationManager.java b/src/com/android/launcher3/PinchAnimationManager.java
index baeb77c..f8196e5 100644
--- a/src/com/android/launcher3/PinchAnimationManager.java
+++ b/src/com/android/launcher3/PinchAnimationManager.java
@@ -24,6 +24,10 @@
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;
@@ -58,7 +62,6 @@
private final Animator[] mAnimators = new Animator[4];
- private final int[] mVisiblePageRange = new int[2];
private Launcher mLauncher;
private Workspace mWorkspace;
@@ -107,7 +110,7 @@
public void onAnimationEnd(Animator animation) {
mIsAnimating = false;
thresholdManager.reset();
- mWorkspace.onLauncherTransitionEnd(mLauncher, false, true);
+ mWorkspace.onEndStateTransition();
}
});
animator.setDuration(duration).start();
@@ -162,9 +165,15 @@
} 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 {
@@ -173,17 +182,13 @@
}
private void setOverviewPanelsAlpha(float alpha, int duration) {
- mWorkspace.getVisiblePages(mVisiblePageRange);
- for (int i = mVisiblePageRange[0]; i <= mVisiblePageRange[1]; i++) {
- View page = mWorkspace.getPageAt(i);
- if (!mWorkspace.shouldDrawChild(page)) {
- continue;
- }
+ int childCount = mWorkspace.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final CellLayout cl = (CellLayout) mWorkspace.getChildAt(i);
if (duration == 0) {
- ((CellLayout) page).setBackgroundAlpha(alpha);
+ cl.setBackgroundAlpha(alpha);
} else {
- ObjectAnimator.ofFloat(page, "backgroundAlpha", alpha)
- .setDuration(duration).start();
+ ObjectAnimator.ofFloat(cl, "backgroundAlpha", alpha).setDuration(duration).start();
}
}
}
@@ -207,14 +212,24 @@
}
private void animateShowHideView(int index, final View view, boolean show) {
- Animator animator = new LauncherViewPropertyAnimator(view).alpha(show ? 1 : 0).withLayer();
+ 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) {
- view.setVisibility(View.INVISIBLE);
+ if (!mCancelled) {
+ view.setVisibility(View.INVISIBLE);
+ }
}
});
}
@@ -223,7 +238,6 @@
private void startAnimator(int index, Animator animator, long duration) {
if (mAnimators[index] != null) {
- mAnimators[index].removeAllListeners();
mAnimators[index].cancel();
}
mAnimators[index] = animator;
diff --git a/src/com/android/launcher3/PinchToOverviewListener.java b/src/com/android/launcher3/PinchToOverviewListener.java
index 48a75d1..42515d1 100644
--- a/src/com/android/launcher3/PinchToOverviewListener.java
+++ b/src/com/android/launcher3/PinchToOverviewListener.java
@@ -61,12 +61,12 @@
mPinchDetector = new ScaleGestureDetector((Context) mLauncher, this);
}
- public boolean onInterceptTouchEvent(MotionEvent ev) {
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
mPinchDetector.onTouchEvent(ev);
return mPinchStarted;
}
- public boolean onTouchEvent(MotionEvent ev) {
+ public boolean onControllerTouchEvent(MotionEvent ev) {
if (mPinchStarted) {
if (ev.getPointerCount() > 2) {
// Using more than two fingers causes weird behavior, so just cancel the pinch.
@@ -102,7 +102,7 @@
// once the state switching animation is complete.
return false;
}
- if (mLauncher.getTopFloatingView() != null) {
+ if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
// Don't listen for the pinch gesture if a floating view is open.
return false;
}
@@ -112,7 +112,7 @@
mInterpolator = mWorkspace.isInOverviewMode() ? new LogDecelerateInterpolator(100, 0)
: new LogAccelerateInterpolator(100, 0);
mPinchStarted = true;
- mWorkspace.onLauncherTransitionPrepare(mLauncher, false, true);
+ mWorkspace.onPrepareStateTransition(true);
return true;
}
@@ -142,7 +142,7 @@
mThresholdManager);
} else {
mThresholdManager.reset();
- mWorkspace.onLauncherTransitionEnd(mLauncher, false, true);
+ mWorkspace.onEndStateTransition();
}
mPinchStarted = false;
mPinchCanceled = false;
diff --git a/src/com/android/launcher3/PreloadIconDrawable.java b/src/com/android/launcher3/PreloadIconDrawable.java
deleted file mode 100644
index b064c47..0000000
--- a/src/com/android/launcher3/PreloadIconDrawable.java
+++ /dev/null
@@ -1,253 +0,0 @@
-package com.android.launcher3;
-
-import android.animation.ObjectAnimator;
-import android.content.res.Resources.Theme;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-
-public class PreloadIconDrawable extends Drawable {
-
- private static final float ANIMATION_PROGRESS_STOPPED = -1.0f;
- private static final float ANIMATION_PROGRESS_STARTED = 0f;
- private static final float ANIMATION_PROGRESS_COMPLETED = 1.0f;
-
- private static final float MIN_SATUNATION = 0.2f;
- private static final float MIN_LIGHTNESS = 0.6f;
-
- private static final float ICON_SCALE_FACTOR = 0.5f;
- private static final int DEFAULT_COLOR = 0xFF009688;
-
- private static final Rect sTempRect = new Rect();
-
- private final RectF mIndicatorRect = new RectF();
- private boolean mIndicatorRectDirty;
-
- private final Paint mPaint;
- public final Drawable mIcon;
-
- private Drawable mBgDrawable;
- private int mRingOutset;
-
- private int mIndicatorColor = 0;
-
- /**
- * Indicates the progress of the preloader [0-100]. If it goes above 100, only the icon
- * is shown with no progress bar.
- */
- private int mProgress = 0;
-
- private float mAnimationProgress = ANIMATION_PROGRESS_STOPPED;
- private ObjectAnimator mAnimator;
-
- public PreloadIconDrawable(Drawable icon, Theme theme) {
- mIcon = icon;
-
- mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mPaint.setStyle(Paint.Style.STROKE);
- mPaint.setStrokeCap(Paint.Cap.ROUND);
-
- setBounds(icon.getBounds());
- applyPreloaderTheme(theme);
- onLevelChange(0);
- }
-
- public void applyPreloaderTheme(Theme t) {
- TypedArray ta = t.obtainStyledAttributes(R.styleable.PreloadIconDrawable);
- mBgDrawable = ta.getDrawable(R.styleable.PreloadIconDrawable_background);
- mBgDrawable.setFilterBitmap(true);
- mPaint.setStrokeWidth(ta.getDimension(R.styleable.PreloadIconDrawable_indicatorSize, 0));
- mRingOutset = ta.getDimensionPixelSize(R.styleable.PreloadIconDrawable_ringOutset, 0);
- ta.recycle();
- onBoundsChange(getBounds());
- invalidateSelf();
- }
-
- @Override
- protected void onBoundsChange(Rect bounds) {
- mIcon.setBounds(bounds);
- if (mBgDrawable != null) {
- sTempRect.set(bounds);
- sTempRect.inset(-mRingOutset, -mRingOutset);
- mBgDrawable.setBounds(sTempRect);
- }
- mIndicatorRectDirty = true;
- }
-
- public int getOutset() {
- return mRingOutset;
- }
-
- /**
- * The size of the indicator is same as the content region of the {@link #mBgDrawable} minus
- * half the stroke size to accommodate the indicator.
- */
- private void initIndicatorRect() {
- Drawable d = mBgDrawable;
- Rect bounds = d.getBounds();
-
- d.getPadding(sTempRect);
- // Amount by which padding has to be scaled
- float paddingScaleX = ((float) bounds.width()) / d.getIntrinsicWidth();
- float paddingScaleY = ((float) bounds.height()) / d.getIntrinsicHeight();
- mIndicatorRect.set(
- bounds.left + sTempRect.left * paddingScaleX,
- bounds.top + sTempRect.top * paddingScaleY,
- bounds.right - sTempRect.right * paddingScaleX,
- bounds.bottom - sTempRect.bottom * paddingScaleY);
-
- float inset = mPaint.getStrokeWidth() / 2;
- mIndicatorRect.inset(inset, inset);
- mIndicatorRectDirty = false;
- }
-
- @Override
- public void draw(Canvas canvas) {
- final Rect r = new Rect(getBounds());
- if (canvas.getClipBounds(sTempRect) && !Rect.intersects(sTempRect, r)) {
- // The draw region has been clipped.
- return;
- }
- if (mIndicatorRectDirty) {
- initIndicatorRect();
- }
- final float iconScale;
-
- if ((mAnimationProgress >= ANIMATION_PROGRESS_STARTED)
- && (mAnimationProgress < ANIMATION_PROGRESS_COMPLETED)) {
- mPaint.setAlpha((int) ((1 - mAnimationProgress) * 255));
- mBgDrawable.setAlpha(mPaint.getAlpha());
- mBgDrawable.draw(canvas);
- canvas.drawOval(mIndicatorRect, mPaint);
-
- iconScale = ICON_SCALE_FACTOR + (1 - ICON_SCALE_FACTOR) * mAnimationProgress;
- } else if (mAnimationProgress == ANIMATION_PROGRESS_STOPPED) {
- mPaint.setAlpha(255);
- iconScale = ICON_SCALE_FACTOR;
- mBgDrawable.setAlpha(255);
- mBgDrawable.draw(canvas);
-
- if (mProgress >= 100) {
- canvas.drawOval(mIndicatorRect, mPaint);
- } else if (mProgress > 0) {
- canvas.drawArc(mIndicatorRect, -90, mProgress * 3.6f, false, mPaint);
- }
- } else {
- iconScale = 1;
- }
-
- canvas.save();
- canvas.scale(iconScale, iconScale, r.exactCenterX(), r.exactCenterY());
- mIcon.draw(canvas);
- canvas.restore();
- }
-
- @Override
- public int getOpacity() {
- return PixelFormat.TRANSLUCENT;
- }
-
- @Override
- public void setAlpha(int alpha) {
- mIcon.setAlpha(alpha);
- }
-
- @Override
- public void setColorFilter(ColorFilter cf) {
- mIcon.setColorFilter(cf);
- }
-
- @Override
- protected boolean onLevelChange(int level) {
- mProgress = level;
-
- // Stop Animation
- if (mAnimator != null) {
- mAnimator.cancel();
- mAnimator = null;
- }
- mAnimationProgress = ANIMATION_PROGRESS_STOPPED;
- if (level > 0) {
- // Set the paint color only when the level changes, so that the dominant color
- // is only calculated when needed.
- mPaint.setColor(getIndicatorColor());
- }
- if (mIcon instanceof FastBitmapDrawable) {
- ((FastBitmapDrawable) mIcon).setState(level <= 0 ?
- FastBitmapDrawable.State.DISABLED : FastBitmapDrawable.State.NORMAL);
- }
-
- invalidateSelf();
- return true;
- }
-
- /**
- * Runs the finish animation if it is has not been run after last level change.
- */
- public void maybePerformFinishedAnimation() {
- if (mAnimationProgress > ANIMATION_PROGRESS_STOPPED) {
- return;
- }
- if (mAnimator != null) {
- mAnimator.cancel();
- }
- setAnimationProgress(ANIMATION_PROGRESS_STARTED);
- mAnimator = ObjectAnimator.ofFloat(this, "animationProgress",
- ANIMATION_PROGRESS_STARTED, ANIMATION_PROGRESS_COMPLETED);
- mAnimator.start();
- }
-
- public void setAnimationProgress(float progress) {
- if (progress != mAnimationProgress) {
- mAnimationProgress = progress;
- invalidateSelf();
- }
- }
-
- public float getAnimationProgress() {
- return mAnimationProgress;
- }
-
- public boolean hasNotCompleted() {
- return mAnimationProgress < ANIMATION_PROGRESS_COMPLETED;
- }
-
- @Override
- public int getIntrinsicHeight() {
- return mIcon.getIntrinsicHeight();
- }
-
- @Override
- public int getIntrinsicWidth() {
- return mIcon.getIntrinsicWidth();
- }
-
- private int getIndicatorColor() {
- if (mIndicatorColor != 0) {
- return mIndicatorColor;
- }
- if (!(mIcon instanceof FastBitmapDrawable)) {
- mIndicatorColor = DEFAULT_COLOR;
- return mIndicatorColor;
- }
- mIndicatorColor = Utilities.findDominantColorByHue(
- ((FastBitmapDrawable) mIcon).getBitmap(), 20);
-
- // Make sure that the dominant color has enough saturation to be visible properly.
- float[] hsv = new float[3];
- Color.colorToHSV(mIndicatorColor, hsv);
- if (hsv[1] < MIN_SATUNATION) {
- mIndicatorColor = DEFAULT_COLOR;
- return mIndicatorColor;
- }
- hsv[2] = Math.max(MIN_LIGHTNESS, hsv[2]);
- mIndicatorColor = Color.HSVToColor(hsv);
- return mIndicatorColor;
- }
-}
diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
new file mode 100644
index 0000000..61bcc17
--- /dev/null
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.annotation.TargetApi;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.launcher3.compat.LauncherAppsCompat;
+
+import java.util.List;
+
+/**
+ * BroadcastReceiver to handle session commit intent.
+ */
+@TargetApi(Build.VERSION_CODES.O)
+public class SessionCommitReceiver extends BroadcastReceiver {
+
+ private static final String TAG = "SessionCommitReceiver";
+
+ // The content provider for the add to home screen setting. It should be of the format:
+ // <package name>.addtohomescreen
+ private static final String MARKER_PROVIDER_PREFIX = ".addtohomescreen";
+
+ // Preference key for automatically adding icon to homescreen.
+ public static final String ADD_ICON_PREFERENCE_KEY = "pref_add_icon_to_home";
+ public static final String ADD_ICON_PREFERENCE_INITIALIZED_KEY =
+ "pref_add_icon_to_home_initialized";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!isEnabled(context) || !Utilities.isAtLeastO()) {
+ // User has decided to not add icons on homescreen.
+ return;
+ }
+
+ SessionInfo info = intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION);
+ UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
+
+ if (TextUtils.isEmpty(info.getAppPackageName()) ||
+ info.getInstallReason() != PackageManager.INSTALL_REASON_USER) {
+ return;
+ }
+
+ if (!Process.myUserHandle().equals(user)) {
+ // Managed profile is handled using ManagedProfileHeuristic
+ return;
+ }
+
+ List<LauncherActivityInfo> activities = LauncherAppsCompat.getInstance(context)
+ .getActivityList(info.getAppPackageName(), user);
+ if (activities == null || activities.isEmpty()) {
+ // no activity found
+ return;
+ }
+ InstallShortcutReceiver.queueActivityInfo(activities.get(0), context);
+ }
+
+ public static boolean isEnabled(Context context) {
+ return Utilities.getPrefs(context).getBoolean(ADD_ICON_PREFERENCE_KEY, true);
+ }
+
+ public static void applyDefaultUserPrefs(final Context context) {
+ if (!Utilities.isAtLeastO()) {
+ return;
+ }
+ SharedPreferences prefs = Utilities.getPrefs(context);
+ if (prefs.getAll().isEmpty()) {
+ // This logic assumes that the code is the first thing that is executed (before any
+ // shared preference is written).
+ // TODO: Move this logic to DB upgrade once we have proper support for db downgrade
+ // If it is a fresh start, just apply the default value. We use prefs.isEmpty() to infer
+ // a fresh start as put preferences always contain some values corresponding to current
+ // grid.
+ prefs.edit().putBoolean(ADD_ICON_PREFERENCE_KEY, true).apply();
+ } else if (!prefs.contains(ADD_ICON_PREFERENCE_INITIALIZED_KEY)) {
+ new PrefInitTask(context).executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR);
+ }
+ }
+
+ private static class PrefInitTask extends AsyncTask<Void, Void, Void> {
+ private final Context mContext;
+
+ PrefInitTask(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ protected Void doInBackground(Void... voids) {
+ boolean addIconToHomeScreenEnabled = readValueFromMarketApp();
+ Utilities.getPrefs(mContext).edit()
+ .putBoolean(ADD_ICON_PREFERENCE_KEY, addIconToHomeScreenEnabled)
+ .putBoolean(ADD_ICON_PREFERENCE_INITIALIZED_KEY, true)
+ .apply();
+ return null;
+ }
+
+ public boolean readValueFromMarketApp() {
+ // Get the marget package
+ ResolveInfo ri = mContext.getPackageManager().resolveActivity(
+ new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_APP_MARKET),
+ PackageManager.MATCH_DEFAULT_ONLY | PackageManager.MATCH_SYSTEM_ONLY);
+ if (ri == null) {
+ return true;
+ }
+
+ Cursor c = null;
+ try {
+ c = mContext.getContentResolver().query(
+ Uri.parse("content://" + ri.activityInfo.packageName
+ + MARKER_PROVIDER_PREFIX),
+ null, null, null, null);
+ if (c.moveToNext()) {
+ return c.getInt(c.getColumnIndexOrThrow(Settings.NameValueTable.VALUE)) != 0;
+ }
+ } catch (Exception e) {
+ Log.d(TAG, "Error reading add to homescreen preference", e);
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ return true;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/SettingsActivity.java b/src/com/android/launcher3/SettingsActivity.java
index cedeb39..7ae6b26 100644
--- a/src/com/android/launcher3/SettingsActivity.java
+++ b/src/com/android/launcher3/SettingsActivity.java
@@ -21,10 +21,14 @@
import android.database.ContentObserver;
import android.os.Bundle;
import android.os.Handler;
+import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.provider.Settings;
import android.provider.Settings.System;
+import android.support.v4.os.BuildCompat;
+
+import com.android.launcher3.graphics.IconShapeOverride;
/**
* Settings activity for Launcher. Currently implements the following setting: Allow rotation
@@ -72,6 +76,20 @@
mRotationLockObserver.onChange(true);
rotationPref.setDefaultValue(Utilities.getAllowRotationDefaultValue(getActivity()));
}
+
+ if (!BuildCompat.isAtLeastO()) {
+ getPreferenceScreen().removePreference(
+ findPreference(SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY));
+ }
+
+ Preference iconShapeOverride = findPreference(IconShapeOverride.KEY_PREFERENCE);
+ if (iconShapeOverride != null) {
+ if (IconShapeOverride.isSupported(getActivity())) {
+ IconShapeOverride.handlePreferenceUi((ListPreference) iconShapeOverride);
+ } else {
+ getPreferenceScreen().removePreference(iconShapeOverride);
+ }
+ }
}
@Override
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 37cbf98..f8742f8 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -18,46 +18,40 @@
import android.app.WallpaperManager;
import android.content.Context;
-import android.graphics.Paint;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;
+import com.android.launcher3.CellLayout.ContainerType;
+
public class ShortcutAndWidgetContainer extends ViewGroup {
- static final String TAG = "CellLayoutChildren";
+ static final String TAG = "ShortcutAndWidgetContainer";
// 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 final int[] mTmpCellXY = new int[2];
+ @ContainerType private final int mContainerType;
private final WallpaperManager mWallpaperManager;
- private boolean mIsHotseatLayout;
-
private int mCellWidth;
private int mCellHeight;
- private int mWidthGap;
- private int mHeightGap;
-
private int mCountX;
private Launcher mLauncher;
-
private boolean mInvertIfRtl = false;
- public ShortcutAndWidgetContainer(Context context) {
+ public ShortcutAndWidgetContainer(Context context, @ContainerType int containerType) {
super(context);
mLauncher = Launcher.getLauncher(context);
mWallpaperManager = WallpaperManager.getInstance(context);
+ mContainerType = containerType;
}
- public void setCellDimensions(int cellWidth, int cellHeight, int widthGap, int heightGap,
- int countX, int countY) {
+ public void setCellDimensions(int cellWidth, int cellHeight, int countX, int countY) {
mCellWidth = cellWidth;
mCellHeight = cellHeight;
- mWidthGap = widthGap;
- mHeightGap = heightGap;
mCountX = countX;
}
@@ -91,9 +85,15 @@
}
}
- public void setupLp(CellLayout.LayoutParams lp) {
- lp.setup(mCellWidth, mCellHeight, mWidthGap, mHeightGap, invertLayoutHorizontally(),
- mCountX);
+ public void setupLp(View child) {
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+ if (child instanceof LauncherAppWidgetHostView) {
+ DeviceProfile profile = mLauncher.getDeviceProfile();
+ lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX,
+ profile.appWidgetScale.x, profile.appWidgetScale.y);
+ } else {
+ lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX);
+ }
}
// Set whether or not to invert the layout horizontally if the layout is in RTL mode.
@@ -101,38 +101,26 @@
mInvertIfRtl = invert;
}
- public void setIsHotseat(boolean isHotseat) {
- mIsHotseatLayout = isHotseat;
- }
-
- int getCellContentWidth() {
- final DeviceProfile grid = mLauncher.getDeviceProfile();
- return Math.min(getMeasuredHeight(), mIsHotseatLayout ?
- grid.hotseatCellWidthPx: grid.cellWidthPx);
- }
-
int getCellContentHeight() {
- final DeviceProfile grid = mLauncher.getDeviceProfile();
- return Math.min(getMeasuredHeight(), mIsHotseatLayout ?
- grid.hotseatCellHeightPx : grid.cellHeightPx);
+ return Math.min(getMeasuredHeight(),
+ mLauncher.getDeviceProfile().getCellHeight(mContainerType));
}
public void measureChild(View child) {
- final DeviceProfile grid = mLauncher.getDeviceProfile();
- final int cellWidth = mCellWidth;
- final int cellHeight = mCellHeight;
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
if (!lp.isFullscreen) {
- lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, invertLayoutHorizontally(),
- mCountX);
+ final DeviceProfile profile = mLauncher.getDeviceProfile();
if (child instanceof LauncherAppWidgetHostView) {
- // Widgets have their own padding, so skip
+ lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX,
+ profile.appWidgetScale.x, profile.appWidgetScale.y);
+ // Widgets have their own padding
} else {
- // Otherwise, center the icon/folder
+ 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 = (int) (grid.edgeMarginPx / 2f);
+ int cellPaddingX = (int) (profile.edgeMarginPx / 2f);
child.setPadding(cellPaddingX, cellPaddingY, cellPaddingX, 0);
}
} else {
@@ -157,6 +145,20 @@
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+
+ if (child instanceof LauncherAppWidgetHostView) {
+ LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child;
+
+ // Scale and center the widget to fit within its cells.
+ DeviceProfile profile = mLauncher.getDeviceProfile();
+ float scaleX = profile.appWidgetScale.x;
+ float scaleY = profile.appWidgetScale.y;
+
+ lahv.setScaleToFit(Math.min(scaleX, scaleY));
+ lahv.setTranslationForCentering(-(lp.width - (lp.width * scaleX)) / 2.0f,
+ -(lp.height - (lp.height * scaleY)) / 2.0f);
+ }
+
int childLeft = lp.x;
int childTop = lp.y;
child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
@@ -201,28 +203,4 @@
child.cancelLongPress();
}
}
-
- @Override
- protected void setChildrenDrawingCacheEnabled(boolean enabled) {
- final int count = getChildCount();
- for (int i = 0; i < count; i++) {
- final View view = getChildAt(i);
- view.setDrawingCacheEnabled(enabled);
- // Update the drawing caches
- if (!view.isHardwareAccelerated() && enabled) {
- view.buildDrawingCache(true);
- }
- }
- }
-
- protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
- super.setChildrenDrawnWithCacheEnabled(enabled);
- }
-
- @Override
- public void setLayerType(int layerType, Paint paint) {
- // When clip children is disabled do not use hardware layer,
- // as hardware layer forces clip children.
- super.setLayerType(getClipChildren() ? layerType : LAYER_TYPE_NONE, paint);
- }
}
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index fb93743..6f0417c 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -17,26 +17,20 @@
package com.android.launcher3;
import android.annotation.TargetApi;
-import android.content.ComponentName;
-import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.TextUtils;
import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.compat.LauncherActivityInfoCompat;
-import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.util.ContentWriter;
/**
* Represents a launchable icon on the workspaces and in folders.
*/
-public class ShortcutInfo extends ItemInfo {
+public class ShortcutInfo extends ItemInfoWithIcon {
public static final int DEFAULT = 0;
@@ -68,6 +62,7 @@
* 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;
/**
@@ -76,28 +71,12 @@
public Intent intent;
/**
- * Indicates whether we're using the default fallback icon instead of something from the
- * app.
- */
- boolean usingFallbackIcon;
-
- /**
- * Indicates whether we're using a low res icon
- */
- boolean usingLowResIcon;
-
- /**
* If isShortcut=true and customIcon=false, this contains a reference to the
* shortcut icon as an application's resource.
*/
public Intent.ShortcutIconResource iconResource;
/**
- * The application icon.
- */
- private Bitmap mIcon;
-
- /**
* Indicates that the icon is disabled due to safe mode restrictions.
*/
public static final int FLAG_DISABLED_SAFEMODE = 1 << 0;
@@ -131,7 +110,7 @@
* Could be disabled, if the the app is installed but unavailable (eg. in safe mode or when
* sd-card is not available).
*/
- int isDisabled = DEFAULT;
+ public int isDisabled = DEFAULT;
/**
* A message to display when the user tries to start a disabled shortcut.
@@ -139,60 +118,25 @@
*/
CharSequence disabledMessage;
- int status;
+ public int status;
/**
* The installation progress [0-100] of the package that this shortcut represents.
*/
private int mInstallProgress;
- /**
- * TODO move this to {@link #status}
- */
- int flags = 0;
-
- /**
- * If this shortcut is a placeholder, then intent will be a market intent for the package, and
- * this will hold the original intent from the database. Otherwise, null.
- * Refer {@link #FLAG_RESTORED_ICON}, {@link #FLAG_AUTOINTALL_ICON}
- */
- Intent promisedIntent;
-
public ShortcutInfo() {
itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
}
- @Override
- public Intent getIntent() {
- return intent;
- }
-
- /** Returns {@link #promisedIntent}, or {@link #intent} if promisedIntent is null. */
- public Intent getPromisedIntent() {
- return promisedIntent != null ? promisedIntent : intent;
- }
-
- ShortcutInfo(Intent intent, CharSequence title, CharSequence contentDescription,
- Bitmap icon, UserHandleCompat user) {
- this();
- this.intent = intent;
- this.title = Utilities.trim(title);
- this.contentDescription = contentDescription;
- mIcon = icon;
- this.user = user;
- }
-
public ShortcutInfo(ShortcutInfo info) {
super(info);
title = info.title;
intent = new Intent(info.intent);
iconResource = info.iconResource;
- mIcon = info.mIcon; // TODO: should make a copy here. maybe we don't need this ctor at all
- flags = info.flags;
status = info.status;
mInstallProgress = info.mInstallProgress;
isDisabled = info.isDisabled;
- usingFallbackIcon = info.usingFallbackIcon;
}
/** TODO: Remove this. It's only called by ApplicationInfo.makeShortcut. */
@@ -200,20 +144,9 @@
super(info);
title = Utilities.trim(info.title);
intent = new Intent(info.intent);
- flags = info.flags;
isDisabled = info.isDisabled;
}
- public ShortcutInfo(LauncherActivityInfoCompat info, Context context) {
- user = info.getUser();
- title = Utilities.trim(info.getLabel());
- contentDescription = UserManagerCompat.getInstance(context)
- .getBadgedLabelForUser(info.getLabel(), info.getUser());
- intent = AppInfo.makeLaunchIntent(context, info, info.getUser());
- itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
- flags = AppInfo.initFlags(info);
- }
-
/**
* Creates a {@link ShortcutInfo} from a {@link ShortcutInfoCompat}.
*/
@@ -221,57 +154,29 @@
public ShortcutInfo(ShortcutInfoCompat shortcutInfo, Context context) {
user = shortcutInfo.getUserHandle();
itemType = LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
- flags = 0;
updateFromDeepShortcutInfo(shortcutInfo, context);
}
- public void setIcon(Bitmap b) {
- mIcon = b;
- }
+ @Override
+ public void onAddToDatabase(ContentWriter writer) {
+ super.onAddToDatabase(writer);
+ writer.put(LauncherSettings.BaseLauncherColumns.TITLE, title)
+ .put(LauncherSettings.BaseLauncherColumns.INTENT, getIntent())
+ .put(LauncherSettings.Favorites.RESTORED, status);
- public Bitmap getIcon(IconCache iconCache) {
- if (mIcon == null) {
- updateIcon(iconCache);
+ if (!usingLowResIcon) {
+ writer.putIcon(iconBitmap, user);
}
- return mIcon;
- }
-
- public void updateIcon(IconCache iconCache, boolean useLowRes) {
- if (itemType == Favorites.ITEM_TYPE_APPLICATION) {
- iconCache.getTitleAndIcon(this, promisedIntent != null ? promisedIntent : intent, user,
- useLowRes);
+ if (iconResource != null) {
+ writer.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE, iconResource.packageName)
+ .put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE,
+ iconResource.resourceName);
}
}
- public void updateIcon(IconCache iconCache) {
- updateIcon(iconCache, shouldUseLowResIcon());
- }
-
@Override
- void onAddToDatabase(Context context, ContentValues values) {
- super.onAddToDatabase(context, values);
-
- String titleStr = title != null ? title.toString() : null;
- values.put(LauncherSettings.BaseLauncherColumns.TITLE, titleStr);
-
- String uri = promisedIntent != null ? promisedIntent.toUri(0)
- : (intent != null ? intent.toUri(0) : null);
- values.put(LauncherSettings.BaseLauncherColumns.INTENT, uri);
- values.put(LauncherSettings.Favorites.RESTORED, status);
-
- if (!usingFallbackIcon && !usingLowResIcon) {
- writeBitmap(values, mIcon);
- }
- if (iconResource != null) {
- values.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE,
- iconResource.packageName);
- values.put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE,
- iconResource.resourceName);
- }
- }
-
- public ComponentName getTargetComponent() {
- return promisedIntent != null ? promisedIntent.getComponent() : intent.getComponent();
+ public Intent getIntent() {
+ return intent;
}
public boolean hasStatusFlag(int flag) {
@@ -292,13 +197,9 @@
status |= FLAG_INSTALL_SESSION_ACTIVE;
}
- public boolean shouldUseLowResIcon() {
- return usingLowResIcon && container >= 0 && rank >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
- }
-
public void updateFromDeepShortcutInfo(ShortcutInfoCompat shortcutInfo, Context context) {
// {@link ShortcutInfoCompat#getActivity} can change during an update. Recreate the intent
- intent = shortcutInfo.makeIntent(context);
+ intent = shortcutInfo.makeIntent();
title = shortcutInfo.getShortLabel();
CharSequence label = shortcutInfo.getLongLabel();
@@ -313,40 +214,12 @@
isDisabled |= FLAG_DISABLED_BY_PUBLISHER;
}
disabledMessage = shortcutInfo.getDisabledMessage();
-
- // TODO: Use cache for this
- LauncherAppState launcherAppState = LauncherAppState.getInstance();
- Drawable unbadgedDrawable = launcherAppState.getShortcutManager()
- .getShortcutIconDrawable(shortcutInfo,
- launcherAppState.getInvariantDeviceProfile().fillResIconDpi);
-
- IconCache cache = launcherAppState.getIconCache();
- Bitmap unbadgedBitmap = unbadgedDrawable == null
- ? cache.getDefaultIcon(UserHandleCompat.myUserHandle())
- : Utilities.createScaledBitmapWithoutShadow(unbadgedDrawable, context);
- setIcon(getBadgedIcon(unbadgedBitmap, shortcutInfo, cache, context));
- }
-
- protected Bitmap getBadgedIcon(Bitmap unbadgedBitmap, ShortcutInfoCompat shortcutInfo,
- IconCache cache, Context context) {
- unbadgedBitmap = Utilities.addShadowToIcon(unbadgedBitmap);
- // Get the app info for the source activity.
- AppInfo appInfo = new AppInfo();
- appInfo.user = user;
- appInfo.componentName = shortcutInfo.getActivity();
- try {
- cache.getTitleAndIcon(appInfo, shortcutInfo.getActivityInfo(context), false);
- } catch (NullPointerException e) {
- // This may happen when we fail to load the activity info. Worst case ignore badging.
- return Utilities.badgeIconForUser(unbadgedBitmap, user, context);
- }
- return Utilities.badgeWithBitmap(unbadgedBitmap, appInfo.iconBitmap, context);
}
/** Returns the ShortcutInfo id associated with the deep shortcut. */
public String getDeepShortcutId() {
return itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT ?
- getPromisedIntent().getStringExtra(ShortcutInfoCompat.EXTRA_SHORTCUT_ID) : null;
+ getIntent().getStringExtra(ShortcutInfoCompat.EXTRA_SHORTCUT_ID) : null;
}
@Override
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index 9153943..0fac29f 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -1,18 +1,19 @@
package com.android.launcher3;
-import android.annotation.TargetApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.PackageManager;
import android.net.Uri;
-import android.os.Build;
import android.os.Bundle;
+import android.os.UserHandle;
import android.os.UserManager;
import android.util.AttributeSet;
-import android.util.Pair;
import android.widget.Toast;
-import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.LauncherAppsCompat;
public class UninstallDropTarget extends ButtonDropTarget {
@@ -38,34 +39,42 @@
return supportsDrop(getContext(), info);
}
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public static boolean supportsDrop(Context context, Object info) {
- if (Utilities.ATLEAST_JB_MR2) {
- UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
- Bundle restrictions = userManager.getUserRestrictions();
- if (restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false)
- || restrictions.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS, false)) {
- return false;
- }
+ UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ Bundle restrictions = userManager.getUserRestrictions();
+ if (restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false)
+ || restrictions.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS, false)) {
+ return false;
}
- Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(info);
- return componentInfo != null && (componentInfo.second & AppInfo.DOWNLOADED_FLAG) != 0;
+ return getUninstallTarget(context, info) != null;
}
/**
- * @return the component name and flags if {@param info} is an AppInfo or an app shortcut.
+ * @return the component name that should be uninstalled or null.
*/
- private static Pair<ComponentName, Integer> getAppInfoFlags(Object item) {
+ private static ComponentName getUninstallTarget(Context context, Object item) {
+ Intent intent = null;
+ UserHandle user = null;
if (item instanceof AppInfo) {
AppInfo info = (AppInfo) item;
- return Pair.create(info.componentName, info.flags);
+ intent = info.intent;
+ user = info.user;
} else if (item instanceof ShortcutInfo) {
ShortcutInfo info = (ShortcutInfo) item;
- ComponentName component = info.getTargetComponent();
- if (info.itemType == LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION
- && component != null) {
- return Pair.create(component, info.flags);
+ if (info.itemType == LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION) {
+ // Do not use restore/target intent here as we cannot uninstall an app which is
+ // being installed/restored.
+ intent = info.intent;
+ user = info.user;
+ }
+ }
+ if (intent != null) {
+ LauncherActivityInfo info = LauncherAppsCompat.getInstance(context)
+ .resolveActivity(intent, user);
+ if (info != null
+ && (info.getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ return info.getComponentName();
}
}
return null;
@@ -81,7 +90,7 @@
}
@Override
- void completeDrop(final DragObject d) {
+ public void completeDrop(final DragObject d) {
DropTargetResultCallback callback = d.dragSource instanceof DropTargetResultCallback
? (DropTargetResultCallback) d.dragSource : null;
startUninstallActivity(mLauncher, d.dragInfo, callback);
@@ -93,11 +102,10 @@
public static boolean startUninstallActivity(
final Launcher launcher, ItemInfo info, DropTargetResultCallback callback) {
- Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(info);
- ComponentName cn = componentInfo.first;
+ final ComponentName cn = getUninstallTarget(launcher, info);
final boolean isUninstallable;
- if ((componentInfo.second & AppInfo.DOWNLOADED_FLAG) == 0) {
+ 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();
@@ -107,13 +115,12 @@
Uri.fromParts("package", cn.getPackageName(), cn.getClassName()))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- info.user.addToIntent(intent, Intent.EXTRA_USER);
+ intent.putExtra(Intent.EXTRA_USER, info.user);
launcher.startActivity(intent);
isUninstallable = true;
}
if (callback != null) {
- sendUninstallResult(
- launcher, isUninstallable, componentInfo.first, info.user, callback);
+ sendUninstallResult(launcher, isUninstallable, cn, info.user, callback);
}
return isUninstallable;
}
@@ -127,15 +134,16 @@
*/
protected static void sendUninstallResult(
final Launcher launcher, boolean activityStarted,
- final ComponentName cn, final UserHandleCompat user,
+ final ComponentName cn, final UserHandle user,
final DropTargetResultCallback callback) {
if (activityStarted) {
final Runnable checkIfUninstallWasSuccess = new Runnable() {
@Override
public void run() {
- String packageName = cn.getPackageName();
- boolean uninstallSuccessful = !AllAppsList.packageHasActivities(
- launcher, packageName, user);
+ // 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);
}
};
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index b0e096a..207a7d4 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -16,10 +16,7 @@
package com.android.launcher3;
-import android.annotation.TargetApi;
-import android.app.Activity;
import android.app.WallpaperManager;
-import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -30,23 +27,17 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.database.Cursor;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
-import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.PaintDrawable;
import android.os.Build;
import android.os.Bundle;
+import android.os.DeadObjectException;
import android.os.PowerManager;
+import android.os.TransactionTooLargeException;
+import android.support.v4.os.BuildCompat;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
@@ -56,24 +47,19 @@
import android.util.Pair;
import android.util.SparseArray;
import android.util.TypedValue;
-import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
-import android.widget.Toast;
-import com.android.launcher3.compat.UserHandleCompat;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.config.ProviderConfig;
-import com.android.launcher3.graphics.ShadowGenerator;
-import com.android.launcher3.util.IconNormalizer;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
-import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.Executor;
@@ -90,29 +76,24 @@
private static final String TAG = "Launcher.Utilities";
- private static final Rect sOldBounds = new Rect();
- private static final Canvas sCanvas = new Canvas();
-
private static final Pattern sTrimPattern =
Pattern.compile("^[\\s|\\p{javaSpaceChar}]*(.*)[\\s|\\p{javaSpaceChar}]*$");
- static {
- sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
- Paint.FILTER_BITMAP_FLAG));
- }
- static int sColors[] = { 0xffff0000, 0xff00ff00, 0xff0000ff };
- static int sColorIndex = 0;
-
private static final int[] sLoc0 = new int[2];
private static final int[] sLoc1 = new int[2];
+ private static final float[] sPoint = new float[2];
+ private static final Matrix sMatrix = new Matrix();
+ private static final Matrix sInverseMatrix = new Matrix();
- public static boolean isNycMR1OrAbove() {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1;
+ public static boolean isAtLeastO() {
+ return BuildCompat.isAtLeastO();
}
- public static boolean isNycOrAbove() {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
- }
+ public static final boolean ATLEAST_NOUGAT_MR1 =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1;
+
+ public static final boolean ATLEAST_NOUGAT =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
public static final boolean ATLEAST_MARSHMALLOW =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
@@ -120,17 +101,11 @@
public static final boolean ATLEAST_LOLLIPOP_MR1 =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1;
- public static final boolean ATLEAST_LOLLIPOP =
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
-
- public static final boolean ATLEAST_KITKAT =
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
-
- public static final boolean ATLEAST_JB_MR1 =
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;
-
- public static final boolean ATLEAST_JB_MR2 =
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2;
+ /**
+ * Indicates if the device has a debug build. Should only be used to store additional info or
+ * add extra logging and not for changing the app behavior.
+ */
+ public static final boolean IS_DEBUG_DEVICE = Build.TYPE.toLowerCase().contains("debug");
// An intent extra to indicate the horizontal scroll of the wallpaper.
public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET";
@@ -159,7 +134,7 @@
}
public static boolean getAllowRotationDefaultValue(Context context) {
- if (isNycOrAbove()) {
+ if (ATLEAST_NOUGAT) {
// If the device was scaled, used the original dimensions to determine if rotation
// is allowed of not.
Resources res = context.getResources();
@@ -170,198 +145,6 @@
return false;
}
- public static Bitmap createIconBitmap(Cursor c, int iconIndex, Context context) {
- byte[] data = c.getBlob(iconIndex);
- try {
- return createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length), context);
- } catch (Exception e) {
- return null;
- }
- }
-
- /**
- * Returns a bitmap suitable for the all apps view. If the package or the resource do not
- * exist, it returns null.
- */
- public static Bitmap createIconBitmap(String packageName, String resourceName,
- Context context) {
- PackageManager packageManager = context.getPackageManager();
- // the resource
- try {
- Resources resources = packageManager.getResourcesForApplication(packageName);
- if (resources != null) {
- final int id = resources.getIdentifier(resourceName, null, null);
- return createIconBitmap(
- resources.getDrawableForDensity(id, LauncherAppState.getInstance()
- .getInvariantDeviceProfile().fillResIconDpi), context);
- }
- } catch (Exception e) {
- // Icon not found.
- }
- return null;
- }
-
- private static int getIconBitmapSize() {
- return LauncherAppState.getInstance().getInvariantDeviceProfile().iconBitmapSize;
- }
-
- /**
- * Returns a bitmap which is of the appropriate size to be displayed as an icon
- */
- public static Bitmap createIconBitmap(Bitmap icon, Context context) {
- final int iconBitmapSize = getIconBitmapSize();
- if (iconBitmapSize == icon.getWidth() && iconBitmapSize == icon.getHeight()) {
- return icon;
- }
- return createIconBitmap(new BitmapDrawable(context.getResources(), icon), context);
- }
-
- /**
- * Returns a bitmap suitable for the all apps view. The icon is badged for {@param user}.
- * The bitmap is also visually normalized with other icons.
- */
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- public static Bitmap createBadgedIconBitmap(
- Drawable icon, UserHandleCompat user, Context context) {
- float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ?
- 1 : IconNormalizer.getInstance().getScale(icon, null);
- Bitmap bitmap = createIconBitmap(icon, context, scale);
- return badgeIconForUser(bitmap, user, context);
- }
-
- /**
- * Badges the provided icon with the user badge if required.
- */
- public static Bitmap badgeIconForUser(Bitmap icon, UserHandleCompat user, Context context) {
- if (Utilities.ATLEAST_LOLLIPOP && user != null
- && !UserHandleCompat.myUserHandle().equals(user)) {
- BitmapDrawable drawable = new FixedSizeBitmapDrawable(icon);
- Drawable badged = context.getPackageManager().getUserBadgedIcon(
- drawable, user.getUser());
- if (badged instanceof BitmapDrawable) {
- return ((BitmapDrawable) badged).getBitmap();
- } else {
- return createIconBitmap(badged, context);
- }
- } else {
- return icon;
- }
- }
-
- /**
- * 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) {
- RectF iconBounds = new RectF();
- float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ?
- 1 : IconNormalizer.getInstance().getScale(icon, iconBounds);
- scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds));
- 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)}
- */
- public static Bitmap addShadowToIcon(Bitmap icon) {
- return ShadowGenerator.getInstance().recreateIcon(icon);
- }
-
- /**
- * Adds the {@param badge} on top of {@param srcTgt} using the badge dimensions.
- */
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- public static Bitmap badgeWithBitmap(Bitmap srcTgt, Bitmap badge, Context context) {
- int badgeSize = context.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
- synchronized (sCanvas) {
- sCanvas.setBitmap(srcTgt);
- sCanvas.drawBitmap(badge, new Rect(0, 0, badge.getWidth(), badge.getHeight()),
- new Rect(srcTgt.getWidth() - badgeSize,
- srcTgt.getHeight() - badgeSize, srcTgt.getWidth(), srcTgt.getHeight()),
- new Paint(Paint.FILTER_BITMAP_FLAG));
- sCanvas.setBitmap(null);
- }
- return srcTgt;
- }
-
- /**
- * Returns a bitmap suitable for the all apps view.
- */
- public static Bitmap createIconBitmap(Drawable icon, Context context) {
- return createIconBitmap(icon, context, 1.0f /* scale */);
- }
-
- /**
- * @param scale the scale to apply before drawing {@param icon} on the canvas
- */
- public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) {
- synchronized (sCanvas) {
- final int iconBitmapSize = getIconBitmapSize();
-
- int width = iconBitmapSize;
- int height = iconBitmapSize;
-
- if (icon instanceof PaintDrawable) {
- PaintDrawable painter = (PaintDrawable) icon;
- painter.setIntrinsicWidth(width);
- painter.setIntrinsicHeight(height);
- } else if (icon instanceof BitmapDrawable) {
- // Ensure the bitmap has a density.
- BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
- Bitmap bitmap = bitmapDrawable.getBitmap();
- if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) {
- bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
- }
- }
- int sourceWidth = icon.getIntrinsicWidth();
- int sourceHeight = icon.getIntrinsicHeight();
- if (sourceWidth > 0 && sourceHeight > 0) {
- // Scale the icon proportionally to the icon dimensions
- final float ratio = (float) sourceWidth / sourceHeight;
- if (sourceWidth > sourceHeight) {
- height = (int) (width / ratio);
- } else if (sourceHeight > sourceWidth) {
- width = (int) (height * ratio);
- }
- }
-
- // no intrinsic size --> use default size
- int textureWidth = iconBitmapSize;
- int textureHeight = iconBitmapSize;
-
- final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
- Bitmap.Config.ARGB_8888);
- final Canvas canvas = sCanvas;
- canvas.setBitmap(bitmap);
-
- final int left = (textureWidth-width) / 2;
- final int top = (textureHeight-height) / 2;
-
- @SuppressWarnings("all") // suppress dead code warning
- final boolean debug = false;
- if (debug) {
- // draw a big box for the icon for debugging
- canvas.drawColor(sColors[sColorIndex]);
- if (++sColorIndex >= sColors.length) sColorIndex = 0;
- Paint debugPaint = new Paint();
- debugPaint.setColor(0xffcccc00);
- canvas.drawRect(left, top, left+width, top+height, debugPaint);
- }
-
- sOldBounds.set(icon.getBounds());
- icon.setBounds(left, top, left+width, top+height);
- canvas.save(Canvas.MATRIX_SAVE_FLAG);
- canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
- icon.draw(canvas);
- canvas.restore();
- icon.setBounds(sOldBounds);
- canvas.setBitmap(null);
-
- return bitmap;
- }
- }
-
/**
* Given a coordinate relative to the descendant, find the coordinate in a parent view's
* coordinates.
@@ -377,68 +160,52 @@
*/
public static float getDescendantCoordRelativeToAncestor(
View descendant, View ancestor, int[] coord, boolean includeRootScroll) {
- float[] pt = {coord[0], coord[1]};
+ sPoint[0] = coord[0];
+ sPoint[1] = coord[1];
+
float scale = 1.0f;
View v = descendant;
while(v != ancestor && v != null) {
// For TextViews, scroll has a meaning which relates to the text position
// which is very strange... ignore the scroll.
if (v != descendant || includeRootScroll) {
- pt[0] -= v.getScrollX();
- pt[1] -= v.getScrollY();
+ sPoint[0] -= v.getScrollX();
+ sPoint[1] -= v.getScrollY();
}
- v.getMatrix().mapPoints(pt);
- pt[0] += v.getLeft();
- pt[1] += v.getTop();
+ v.getMatrix().mapPoints(sPoint);
+ sPoint[0] += v.getLeft();
+ sPoint[1] += v.getTop();
scale *= v.getScaleX();
v = (View) v.getParent();
}
- coord[0] = Math.round(pt[0]);
- coord[1] = Math.round(pt[1]);
+ coord[0] = Math.round(sPoint[0]);
+ coord[1] = Math.round(sPoint[1]);
return scale;
}
/**
* Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, int[], boolean)}.
*/
- public static float mapCoordInSelfToDescendent(View descendant, View root,
- int[] coord) {
- ArrayList<View> ancestorChain = new ArrayList<View>();
-
- float[] pt = {coord[0], coord[1]};
-
+ public static void mapCoordInSelfToDescendant(View descendant, View root, int[] coord) {
+ sMatrix.reset();
View v = descendant;
while(v != root) {
- ancestorChain.add(v);
+ sMatrix.postTranslate(-v.getScrollX(), -v.getScrollY());
+ sMatrix.postConcat(v.getMatrix());
+ sMatrix.postTranslate(v.getLeft(), v.getTop());
v = (View) v.getParent();
}
- ancestorChain.add(root);
+ sMatrix.postTranslate(-v.getScrollX(), -v.getScrollY());
+ sMatrix.invert(sInverseMatrix);
- float scale = 1.0f;
- Matrix inverse = new Matrix();
- int count = ancestorChain.size();
- for (int i = count - 1; i >= 0; i--) {
- View ancestor = ancestorChain.get(i);
- View next = i > 0 ? ancestorChain.get(i-1) : null;
-
- pt[0] += ancestor.getScrollX();
- pt[1] += ancestor.getScrollY();
-
- if (next != null) {
- pt[0] -= next.getLeft();
- pt[1] -= next.getTop();
- next.getMatrix().invert(inverse);
- inverse.mapPoints(pt);
- scale *= next.getScaleX();
- }
- }
-
- coord[0] = (int) Math.round(pt[0]);
- coord[1] = (int) Math.round(pt[1]);
- return scale;
+ sPoint[0] = coord[0];
+ sPoint[1] = coord[1];
+ sInverseMatrix.mapPoints(sPoint);
+ coord[0] = Math.round(sPoint[0]);
+ coord[1] = Math.round(sPoint[1]);
}
/**
@@ -452,31 +219,7 @@
localY < (v.getHeight() + slop);
}
- /** Translates MotionEvents from src's coordinate system to dst's. */
- public static void translateEventCoordinates(View src, View dst, MotionEvent dstEvent) {
- toGlobalMotionEvent(src, dstEvent);
- toLocalMotionEvent(dst, dstEvent);
- }
-
- /**
- * Emulates View.toGlobalMotionEvent(). This implementation does not handle transformations
- * (scaleX, scaleY, etc).
- */
- private static void toGlobalMotionEvent(View view, MotionEvent event) {
- view.getLocationOnScreen(sLoc0);
- event.offsetLocation(sLoc0[0], sLoc0[1]);
- }
-
- /**
- * Emulates View.toLocalMotionEvent(). This implementation does not handle transformations
- * (scaleX, scaleY, etc).
- */
- private static void toLocalMotionEvent(View view, MotionEvent event) {
- view.getLocationOnScreen(sLoc0);
- event.offsetLocation(-sLoc0[0], -sLoc0[1]);
- }
-
- public static int[] getCenterDeltaInScreenSpace(View v0, View v1, int[] delta) {
+ public static int[] getCenterDeltaInScreenSpace(View v0, View v1) {
v0.getLocationInWindow(sLoc0);
v1.getLocationInWindow(sLoc1);
@@ -484,15 +227,7 @@
sLoc0[1] += (v0.getMeasuredHeight() * v0.getScaleY()) / 2;
sLoc1[0] += (v1.getMeasuredWidth() * v1.getScaleX()) / 2;
sLoc1[1] += (v1.getMeasuredHeight() * v1.getScaleY()) / 2;
-
- if (delta == null) {
- delta = new int[2];
- }
-
- delta[0] = sLoc1[0] - sLoc0[0];
- delta[1] = sLoc1[1] - sLoc0[1];
-
- return delta;
+ return new int[] {sLoc1[0] - sLoc0[0], sLoc1[1] - sLoc0[1]};
}
public static void scaleRectAboutCenter(Rect r, float scale) {
@@ -510,18 +245,18 @@
}
}
- public static void startActivityForResultSafely(
- Activity activity, Intent intent, int requestCode) {
- try {
- activity.startActivityForResult(intent, requestCode);
- } catch (ActivityNotFoundException e) {
- Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
- } catch (SecurityException e) {
- Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
- Log.e(TAG, "Launcher does not have the permission to launch " + intent +
- ". Make sure to create a MAIN intent-filter for the corresponding activity " +
- "or use the exported attribute for this activity.", e);
+ public static float shrinkRect(Rect r, float scaleX, float scaleY) {
+ float scale = Math.min(Math.min(scaleX, scaleY), 1.0f);
+ if (scale < 1.0f) {
+ int deltaX = (int) (r.width() * (scaleX - scale) * 0.5f);
+ r.left += deltaX;
+ r.right -= deltaX;
+
+ int deltaY = (int) (r.height() * (scaleY - scale) * 0.5f);
+ r.top += deltaY;
+ r.bottom -= deltaY;
}
+ return scale;
}
static boolean isSystemApp(Context context, Intent intent) {
@@ -554,7 +289,7 @@
* @param bitmap The bitmap to scan
* @param samples The approximate max number of samples to use.
*/
- static int findDominantColorByHue(Bitmap bitmap, int samples) {
+ public static int findDominantColorByHue(Bitmap bitmap, int samples) {
final int height = bitmap.getHeight();
final int width = bitmap.getWidth();
int sampleStride = (int) Math.sqrt((height * width) / samples);
@@ -712,10 +447,8 @@
System.out.println(b.toString());
}
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static boolean isRtl(Resources res) {
- return ATLEAST_JB_MR1 &&
- (res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
+ return res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
}
/**
@@ -735,13 +468,8 @@
&& TextUtils.isEmpty(launchIntent.getDataString())) {
// An app target can either have no extra or have ItemInfo.EXTRA_PROFILE.
Bundle extras = launchIntent.getExtras();
- if (extras == null) {
- return true;
- } else {
- Set<String> keys = extras.keySet();
- return keys.size() == 1 && keys.contains(ItemInfo.EXTRA_PROFILE);
- }
- };
+ return extras == null || extras.keySet().isEmpty();
+ }
return false;
}
@@ -802,16 +530,11 @@
* @param msg original message
* @param ttsMsg message to be spoken
*/
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static CharSequence wrapForTts(CharSequence msg, String ttsMsg) {
- if (Utilities.ATLEAST_LOLLIPOP) {
- SpannableString spanned = new SpannableString(msg);
- spanned.setSpan(new TtsSpan.TextBuilder(ttsMsg).build(),
- 0, spanned.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
- return spanned;
- } else {
- return msg;
- }
+ SpannableString spanned = new SpannableString(msg);
+ spanned.setSpan(new TtsSpan.TextBuilder(ttsMsg).build(),
+ 0, spanned.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ return spanned;
}
/**
@@ -826,14 +549,13 @@
LauncherFiles.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE);
}
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static boolean isPowerSaverOn(Context context) {
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- return ATLEAST_LOLLIPOP && powerManager.isPowerSaveMode();
+ return powerManager.isPowerSaveMode();
}
- public static boolean isWallapaperAllowed(Context context) {
- if (isNycOrAbove()) {
+ public static boolean isWallpaperAllowed(Context context) {
+ if (ATLEAST_NOUGAT) {
try {
WallpaperManager wm = context.getSystemService(WallpaperManager.class);
return (Boolean) wm.getClass().getDeclaredMethod("isSetWallpaperAllowed")
@@ -880,35 +602,6 @@
return c == null || c.isEmpty();
}
- /**
- * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
- * This allows the badging to be done based on the action bitmap size rather than
- * the scaled bitmap size.
- */
- private static class FixedSizeBitmapDrawable extends BitmapDrawable {
-
- public FixedSizeBitmapDrawable(Bitmap bitmap) {
- super(null, bitmap);
- }
-
- @Override
- public int getIntrinsicHeight() {
- return getBitmap().getWidth();
- }
-
- @Override
- public int getIntrinsicWidth() {
- return getBitmap().getWidth();
- }
- }
-
- public static int getColorAccent(Context context) {
- TypedArray ta = context.obtainStyledAttributes(new int[]{android.R.attr.colorAccent});
- int colorAccent = ta.getColor(0, 0);
- ta.recycle();
- return colorAccent;
- }
-
public static void sendCustomAccessibilityEvent(View target, int type, String text) {
AccessibilityManager accessibilityManager = (AccessibilityManager)
target.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
@@ -919,4 +612,38 @@
accessibilityManager.sendAccessibilityEvent(event);
}
}
+
+ public static boolean isBinderSizeError(Exception e) {
+ return e.getCause() instanceof TransactionTooLargeException
+ || e.getCause() instanceof DeadObjectException;
+ }
+
+ public static <T> T getOverrideObject(Class<T> clazz, Context context, int resId) {
+ String className = context.getString(resId);
+ if (!TextUtils.isEmpty(className)) {
+ try {
+ Class<?> cls = Class.forName(className);
+ return (T) cls.getDeclaredConstructor(Context.class).newInstance(context);
+ } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
+ | ClassCastException | NoSuchMethodException | InvocationTargetException e) {
+ Log.e(TAG, "Bad overriden class", e);
+ }
+ }
+
+ try {
+ return clazz.newInstance();
+ } catch (InstantiationException|IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Returns a HashSet with a single element. We use this instead of Collections.singleton()
+ * because HashSet ensures all operations, such as remove, are supported.
+ */
+ public static <T> HashSet<T> singletonHashSet(T elem) {
+ HashSet<T> hashSet = new HashSet<>(1);
+ hashSet.add(elem);
+ return hashSet;
+ }
}
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 45e65b5..c525cd4 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -3,7 +3,6 @@
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
-import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -15,24 +14,31 @@
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.graphics.drawable.BitmapDrawable;
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;
+import android.support.annotation.Nullable;
+import android.support.v4.graphics.ColorUtils;
import android.util.Log;
import android.util.LongSparseArray;
import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.ShortcutConfigActivityInfo;
import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.graphics.ShadowGenerator;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.SQLiteCacheHelper;
import com.android.launcher3.util.Thunk;
@@ -52,8 +58,6 @@
private static final String TAG = "WidgetPreviewLoader";
private static final boolean DEBUG = false;
- private static final float WIDGET_PREVIEW_ICON_PADDING_PERCENTAGE = 0.25f;
-
private final HashMap<String, long[]> mPackageVersions = new HashMap<>();
/**
@@ -70,7 +74,6 @@
private final UserManagerCompat mUserManager;
private final AppWidgetManagerCompat mWidgetManager;
private final CacheDb mDb;
- private final int mProfileBadgeMargin;
private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
@Thunk final Handler mWorkerHandler;
@@ -82,8 +85,6 @@
mUserManager = UserManagerCompat.getInstance(context);
mDb = new CacheDb(context);
mWorkerHandler = new Handler(LauncherModel.getWorkerLooper());
- mProfileBadgeMargin = context.getResources()
- .getDimensionPixelSize(R.dimen.profile_badge_margin);
}
/**
@@ -92,14 +93,18 @@
*
* @return a request id which can be used to cancel the request.
*/
- public PreviewLoadRequest getPreview(WidgetItem item, int previewWidth,
- int previewHeight, WidgetCell caller) {
+ public CancellationSignal getPreview(WidgetItem item, int previewWidth,
+ int previewHeight, WidgetCell caller, boolean animate) {
String size = previewWidth + "x" + previewHeight;
WidgetCacheKey key = new WidgetCacheKey(item.componentName, item.user, size);
- PreviewLoadTask task = new PreviewLoadTask(key, item, previewWidth, previewHeight, caller);
+ PreviewLoadTask task = new PreviewLoadTask(key, item, previewWidth, previewHeight, caller,
+ animate);
task.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR);
- return new PreviewLoadRequest(task);
+
+ CancellationSignal signal = new CancellationSignal();
+ signal.setOnCancelListener(task);
+ return signal;
}
/**
@@ -107,7 +112,7 @@
* sizes (landscape vs portrait).
*/
private static class CacheDb extends SQLiteCacheHelper {
- private static final int DB_VERSION = 4;
+ private static final int DB_VERSION = 6;
private static final String TABLE_NAME = "shortcut_and_widget_previews";
private static final String COLUMN_COMPONENT = "componentName";
@@ -149,11 +154,11 @@
mDb.insertOrReplace(values);
}
- public void removePackage(String packageName, UserHandleCompat user) {
+ public void removePackage(String packageName, UserHandle user) {
removePackage(packageName, user, mUserManager.getSerialNumberForUser(user));
}
- private void removePackage(String packageName, UserHandleCompat user, long userSerial) {
+ private void removePackage(String packageName, UserHandle user, long userSerial) {
synchronized(mPackageVersions) {
mPackageVersions.remove(packageName);
}
@@ -168,8 +173,12 @@
* 1. Any preview generated for an old package version is removed
* 2. Any preview for an absent package is removed
* This ensures that we remove entries for packages which changed while the launcher was dead.
+ *
+ * @param packageUser if provided, specifies that list only contains previews for the
+ * given package/user, otherwise the list contains all previews
*/
- public void removeObsoletePreviews(ArrayList<? extends ComponentKey> list) {
+ public void removeObsoletePreviews(ArrayList<? extends ComponentKey> list,
+ @Nullable PackageUserKey packageUser) {
Preconditions.assertWorkerThread();
LongSparseArray<HashSet<String>> validPackages = new LongSparseArray<>();
@@ -185,6 +194,8 @@
}
LongSparseArray<HashSet<String>> packagesToDelete = new LongSparseArray<>();
+ long passedUserId = packageUser == null ? 0
+ : mUserManager.getSerialNumberForUser(packageUser.mUser);
Cursor c = null;
try {
c = mDb.query(
@@ -197,6 +208,12 @@
long lastUpdated = c.getLong(2);
long version = c.getLong(3);
+ if (packageUser != null && (!pkg.equals(packageUser.mPackageName)
+ || userId != passedUserId)) {
+ // This preview is associated with a different package/user, no need to remove.
+ continue;
+ }
+
HashSet<String> packages = validPackages.get(userId);
if (packages != null && packages.contains(pkg)) {
long[] versions = getPackageVersion(pkg);
@@ -217,7 +234,7 @@
for (int i = 0; i < packagesToDelete.size(); i++) {
long userId = packagesToDelete.keyAt(i);
- UserHandleCompat user = mUserManager.getUserForSerialNumber(userId);
+ UserHandle user = mUserManager.getUserForSerialNumber(userId);
for (String pkg : packagesToDelete.valueAt(i)) {
removePackage(pkg, user, userId);
}
@@ -242,7 +259,7 @@
CacheDb.COLUMN_COMPONENT + " = ? AND " + CacheDb.COLUMN_USER + " = ? AND "
+ CacheDb.COLUMN_SIZE + " = ?",
new String[]{
- key.componentName.flattenToString(),
+ key.componentName.flattenToShortString(),
Long.toString(mUserManager.getSerialNumberForUser(key.user)),
key.size
});
@@ -272,7 +289,7 @@
return null;
}
- private Bitmap generatePreview(Launcher launcher, WidgetItem item, Bitmap recycle,
+ private Bitmap generatePreview(BaseActivity launcher, WidgetItem item, Bitmap recycle,
int previewWidth, int previewHeight) {
if (item.widgetInfo != null) {
return generateWidgetPreview(launcher, item.widgetInfo,
@@ -294,14 +311,21 @@
* @param preScaledWidthOut return the width of the returned bitmap
* @return
*/
- public Bitmap generateWidgetPreview(Launcher launcher, LauncherAppWidgetProviderInfo info,
+ public Bitmap generateWidgetPreview(BaseActivity launcher, LauncherAppWidgetProviderInfo info,
int maxPreviewWidth, Bitmap preview, int[] preScaledWidthOut) {
// Load the preview image if possible
if (maxPreviewWidth < 0) maxPreviewWidth = Integer.MAX_VALUE;
Drawable drawable = null;
if (info.previewImage != 0) {
- drawable = mWidgetManager.loadPreview(info);
+ try {
+ drawable = info.loadPreviewImage(mContext, 0);
+ } catch (OutOfMemoryError e) {
+ Log.w(TAG, "Error loading widget preview for: " + info.provider, e);
+ // During OutOfMemoryError, the previous heap stack is not affected. Catching
+ // an OOM error here should be safe & not affect other parts of launcher.
+ drawable = null;
+ }
if (drawable != null) {
drawable = mutateOnMainThread(drawable);
} else {
@@ -317,17 +341,14 @@
int previewWidth;
int previewHeight;
- Bitmap tileBitmap = null;
-
if (widgetPreviewExists) {
previewWidth = drawable.getIntrinsicWidth();
previewHeight = drawable.getIntrinsicHeight();
} else {
- // Generate a preview image if we couldn't load one
- tileBitmap = ((BitmapDrawable) mContext.getResources().getDrawable(
- R.drawable.widget_tile)).getBitmap();
- previewWidth = tileBitmap.getWidth() * spanX;
- previewHeight = tileBitmap.getHeight() * spanY;
+ DeviceProfile dp = launcher.getDeviceProfile();
+ int tileSize = Math.min(dp.cellWidthPx, dp.cellHeightPx);
+ previewWidth = tileSize * spanX;
+ previewHeight = tileSize * spanY;
}
// Scale to fit width only - let the widget preview be clipped in the
@@ -337,7 +358,7 @@
preScaledWidthOut[0] = previewWidth;
}
if (previewWidth > maxPreviewWidth) {
- scale = (maxPreviewWidth - 2 * mProfileBadgeMargin) / (float) (previewWidth);
+ scale = maxPreviewWidth / (float) (previewWidth);
}
if (scale != 1f) {
previewWidth = (int) (scale * previewWidth);
@@ -350,6 +371,12 @@
preview = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888);
c.setBitmap(preview);
} else {
+ // We use the preview bitmap height to determine where the badge will be drawn in the
+ // UI. If its larger than what we need, resize the preview bitmap so that there are
+ // no transparent pixels between the preview and the badge.
+ if (preview.getHeight() > previewHeight) {
+ preview.reconfigure(preview.getWidth(), previewHeight, preview.getConfig());
+ }
// Reusing bitmap. Clear it.
c.setBitmap(preview);
c.drawColor(0, PorterDuff.Mode.CLEAR);
@@ -361,93 +388,106 @@
drawable.setBounds(x, 0, x + previewWidth, previewHeight);
drawable.draw(c);
} else {
- final Paint p = new Paint();
- p.setFilterBitmap(true);
- int appIconSize = launcher.getDeviceProfile().iconSizePx;
+ final Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
+ RectF boxRect = drawBoxWithShadow(c, p, previewWidth, previewHeight);
- // draw the spanX x spanY tiles
- final Rect src = new Rect(0, 0, tileBitmap.getWidth(), tileBitmap.getHeight());
+ // Draw horizontal and vertical lines to represent individual columns.
+ p.setStyle(Paint.Style.STROKE);
+ p.setStrokeWidth(mContext.getResources()
+ .getDimension(R.dimen.widget_preview_cell_divider_width));
+ p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
- float tileW = scale * tileBitmap.getWidth();
- float tileH = scale * tileBitmap.getHeight();
- final RectF dst = new RectF(0, 0, tileW, tileH);
-
- float tx = x;
- for (int i = 0; i < spanX; i++, tx += tileW) {
- float ty = 0;
- for (int j = 0; j < spanY; j++, ty += tileH) {
- dst.offsetTo(tx, ty);
- c.drawBitmap(tileBitmap, src, dst, p);
- }
+ float t = boxRect.left;
+ float tileSize = boxRect.width() / spanX;
+ for (int i = 1; i < spanX; i++) {
+ t += tileSize;
+ c.drawLine(t, 0, t, previewHeight, p);
}
- // Draw the icon in the top left corner
- // TODO: use top right for RTL
- int minOffset = (int) (appIconSize * WIDGET_PREVIEW_ICON_PADDING_PERCENTAGE);
- int smallestSide = Math.min(previewWidth, previewHeight);
- float iconScale = Math.min((float) smallestSide / (appIconSize + 2 * minOffset), scale);
+ t = boxRect.top;
+ tileSize = boxRect.height() / spanY;
+ for (int i = 1; i < spanY; i++) {
+ t += tileSize;
+ c.drawLine(0, t, previewWidth, t, p);
+ }
+ // Draw icon in the center.
try {
- Drawable icon = mWidgetManager.loadIcon(info, mIconCache);
+ Drawable icon = info.getIcon(launcher, mIconCache);
if (icon != null) {
+ int appIconSize = launcher.getDeviceProfile().iconSizePx;
+ int iconSize = (int) Math.min(appIconSize * scale,
+ Math.min(boxRect.width(), boxRect.height()));
+
icon = mutateOnMainThread(icon);
- int hoffset = (int) ((tileW - appIconSize * iconScale) / 2) + x;
- int yoffset = (int) ((tileH - appIconSize * iconScale) / 2);
- icon.setBounds(hoffset, yoffset,
- hoffset + (int) (appIconSize * iconScale),
- yoffset + (int) (appIconSize * iconScale));
+ int hoffset = (previewWidth - iconSize) / 2;
+ int yoffset = (previewHeight - iconSize) / 2;
+ icon.setBounds(hoffset, yoffset, hoffset + iconSize, yoffset + iconSize);
icon.draw(c);
}
- } catch (Resources.NotFoundException e) {
- }
+ } catch (Resources.NotFoundException e) { }
c.setBitmap(null);
}
- int imageWidth = Math.min(preview.getWidth(), previewWidth + mProfileBadgeMargin);
- int imageHeight = Math.min(preview.getHeight(), previewHeight + mProfileBadgeMargin);
- return mWidgetManager.getBadgeBitmap(info, preview, imageWidth, imageHeight);
+ return preview;
}
- private Bitmap generateShortcutPreview(
- Launcher launcher, ActivityInfo info, int maxWidth, int maxHeight, Bitmap preview) {
+ private RectF drawBoxWithShadow(Canvas c, Paint p, int width, int height) {
+ Resources res = mContext.getResources();
+ float shadowBlur = res.getDimension(R.dimen.widget_preview_shadow_blur);
+ float keyShadowDistance = res.getDimension(R.dimen.widget_preview_key_shadow_distance);
+ float corner = res.getDimension(R.dimen.widget_preview_corner_radius);
+
+ RectF bounds = new RectF(shadowBlur, shadowBlur,
+ width - shadowBlur, height - shadowBlur - keyShadowDistance);
+ p.setColor(Color.WHITE);
+
+ // Key shadow
+ p.setShadowLayer(shadowBlur, 0, keyShadowDistance,
+ ShadowGenerator.KEY_SHADOW_ALPHA << 24);
+ c.drawRoundRect(bounds, corner, corner, p);
+
+ // Ambient shadow
+ p.setShadowLayer(shadowBlur, 0, 0,
+ ColorUtils.setAlphaComponent(Color.BLACK, ShadowGenerator.AMBIENT_SHADOW_ALPHA));
+ c.drawRoundRect(bounds, corner, corner, p);
+
+ p.clearShadowLayer();
+ return bounds;
+ }
+
+ private Bitmap generateShortcutPreview(BaseActivity launcher, ShortcutConfigActivityInfo info,
+ int maxWidth, int maxHeight, Bitmap preview) {
+ int iconSize = launcher.getDeviceProfile().iconSizePx;
+ int padding = launcher.getResources()
+ .getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
+
+ int size = iconSize + 2 * padding;
+ if (maxHeight < size || maxWidth < size) {
+ throw new RuntimeException("Max size is too small for preview");
+ }
final Canvas c = new Canvas();
- if (preview == null) {
- preview = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888);
+ if (preview == null || preview.getWidth() < size || preview.getHeight() < size) {
+ preview = Bitmap.createBitmap(size, size, Config.ARGB_8888);
c.setBitmap(preview);
- } else if (preview.getWidth() != maxWidth || preview.getHeight() != maxHeight) {
- throw new RuntimeException("Improperly sized bitmap passed as argument");
} else {
+ if (preview.getWidth() > size || preview.getHeight() > size) {
+ preview.reconfigure(size, size, preview.getConfig());
+ }
+
// Reusing bitmap. Clear it.
c.setBitmap(preview);
c.drawColor(0, PorterDuff.Mode.CLEAR);
}
+ Paint p = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+ RectF boxRect = drawBoxWithShadow(c, p, size, size);
- Drawable icon = mutateOnMainThread(mIconCache.getFullResIcon(info));
- icon.setFilterBitmap(true);
+ Bitmap icon = LauncherIcons.createScaledBitmapWithoutShadow(
+ mutateOnMainThread(info.getFullResIcon(mIconCache)), mContext, Build.VERSION_CODES.O);
+ Rect src = new Rect(0, 0, icon.getWidth(), icon.getHeight());
- // Draw a desaturated/scaled version of the icon in the background as a watermark
- ColorMatrix colorMatrix = new ColorMatrix();
- colorMatrix.setSaturation(0);
- icon.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
- icon.setAlpha((int) (255 * 0.06f));
-
- Resources res = mContext.getResources();
- int paddingTop = res.getDimensionPixelOffset(R.dimen.shortcut_preview_padding_top);
- int paddingLeft = res.getDimensionPixelOffset(R.dimen.shortcut_preview_padding_left);
- int paddingRight = res.getDimensionPixelOffset(R.dimen.shortcut_preview_padding_right);
- int scaledIconWidth = (maxWidth - paddingLeft - paddingRight);
- icon.setBounds(paddingLeft, paddingTop,
- paddingLeft + scaledIconWidth, paddingTop + scaledIconWidth);
- icon.draw(c);
-
- // Draw the final icon at top left corner.
- // TODO: use top right for RTL
- int appIconSize = launcher.getDeviceProfile().iconSizePx;
-
- icon.setAlpha(255);
- icon.setColorFilter(null);
- icon.setBounds(0, 0, appIconSize, appIconSize);
- icon.draw(c);
-
+ boxRect.set(0, 0, iconSize, iconSize);
+ boxRect.offset(padding, padding);
+ c.drawBitmap(icon, src, boxRect, p);
c.setBitmap(null);
return preview;
}
@@ -490,57 +530,27 @@
}
}
- /**
- * A request Id which can be used by the client to cancel any request.
- */
- public class PreviewLoadRequest {
-
- @Thunk final PreviewLoadTask mTask;
-
- public PreviewLoadRequest(PreviewLoadTask task) {
- mTask = task;
- }
-
- public void cleanup() {
- if (mTask != null) {
- mTask.cancel(true);
- }
-
- // This only handles the case where the PreviewLoadTask is cancelled after the task has
- // successfully completed (including having written to disk when necessary). In the
- // other cases where it is cancelled while the task is running, it will be cleaned up
- // in the tasks's onCancelled() call, and if cancelled while the task is writing to
- // disk, it will be cancelled in the task's onPostExecute() call.
- if (mTask.mBitmapToRecycle != null) {
- mWorkerHandler.post(new Runnable() {
- @Override
- public void run() {
- synchronized (mUnusedBitmaps) {
- mUnusedBitmaps.add(mTask.mBitmapToRecycle);
- }
- mTask.mBitmapToRecycle = null;
- }
- });
- }
- }
- }
-
- public class PreviewLoadTask extends AsyncTask<Void, Void, Bitmap> {
+ public class PreviewLoadTask extends AsyncTask<Void, Void, Bitmap>
+ implements CancellationSignal.OnCancelListener {
@Thunk final WidgetCacheKey mKey;
private final WidgetItem mInfo;
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) {
+ int previewHeight, WidgetCell caller, boolean animate) {
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",
mKey, mInfo, mPreviewHeight, mPreviewWidth));
@@ -582,19 +592,20 @@
// Fetch the version info before we generate the preview, so that, in-case the
// app was updated while we are generating the preview, we use the old version info,
// which would gets re-written next time.
- mVersions = getPackageVersion(mKey.componentName.getPackageName());
-
- Launcher launcher = Launcher.getLauncher(mCaller.getContext());
+ boolean persistable = mInfo.activityInfo == null
+ || mInfo.activityInfo.isPersistable();
+ mVersions = persistable ? getPackageVersion(mKey.componentName.getPackageName())
+ : null;
// it's not in the db... we need to generate it
- preview = generatePreview(launcher, mInfo, unusedBitmap, mPreviewWidth, mPreviewHeight);
+ preview = generatePreview(mActivity, mInfo, unusedBitmap, mPreviewWidth, mPreviewHeight);
}
return preview;
}
@Override
protected void onPostExecute(final Bitmap preview) {
- mCaller.applyPreview(preview);
+ mCaller.applyPreview(preview, mAnimatePreviewIn);
// Write the generated preview to the DB in the worker thread
if (mVersions != null) {
@@ -638,14 +649,35 @@
});
}
}
+
+ @Override
+ public void onCancel() {
+ cancel(true);
+
+ // This only handles the case where the PreviewLoadTask is cancelled after the task has
+ // successfully completed (including having written to disk when necessary). In the
+ // other cases where it is cancelled while the task is running, it will be cleaned up
+ // in the tasks's onCancelled() call, and if cancelled while the task is writing to
+ // disk, it will be cancelled in the task's onPostExecute() call.
+ if (mBitmapToRecycle != null) {
+ mWorkerHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mUnusedBitmaps) {
+ mUnusedBitmaps.add(mBitmapToRecycle);
+ }
+ mBitmapToRecycle = null;
+ }
+ });
+ }
+ }
}
private static final class WidgetCacheKey extends ComponentKey {
- // TODO: remove dependency on size
@Thunk final String size;
- public WidgetCacheKey(ComponentName componentName, UserHandleCompat user, String size) {
+ public WidgetCacheKey(ComponentName componentName, UserHandle user, String size) {
super(componentName, user);
this.size = size;
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 340177d..369d80d 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -28,19 +28,17 @@
import android.app.WallpaperManager;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
-import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Matrix;
import android.graphics.Point;
-import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.IBinder;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Property;
@@ -53,32 +51,37 @@
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.TextView;
+import android.widget.Toast;
import com.android.launcher3.Launcher.CustomContentCallbacks;
import com.android.launcher3.Launcher.LauncherOverlay;
import com.android.launcher3.UninstallDropTarget.DropTargetSource;
-import com.android.launcher3.accessibility.AccessibileDragListenerAdapter;
+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.badge.FolderBadgeInfo;
import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.dragndrop.DragScroller;
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.dragndrop.SpringLoadedDragController;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.DragPreviewProvider;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.graphics.PreloadIconDrawable;
+import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LongArrayMap;
import com.android.launcher3.util.MultiStateAlphaController;
+import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.VerticalFlingDetector;
import com.android.launcher3.util.WallpaperOffsetInterpolator;
@@ -86,8 +89,8 @@
import com.android.launcher3.widget.PendingAddWidgetInfo;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
+import java.util.Set;
/**
* The workspace is a wide area with a wallpaper and a finite number of pages.
@@ -95,11 +98,19 @@
* interact with. A workspace is meant to be used with a fixed width only.
*/
public class Workspace extends PagedView
- implements DropTarget, DragSource, DragScroller, View.OnTouchListener,
- DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener,
+ implements DropTarget, DragSource, View.OnTouchListener,
+ DragController.DragListener, ViewGroup.OnHierarchyChangeListener,
Insettable, DropTargetSource {
private static final String TAG = "Launcher.Workspace";
+ /** The value that {@link #mTransitionProgress} must be greater than for
+ * {@link #transitionStateShouldAllowDrop()} to return true. */
+ private static final float ALLOW_DROP_TRANSITION_PROGRESS = 0.25f;
+
+ /** The value that {@link #mTransitionProgress} must be greater than for
+ * {@link #isFinishedSwitchingState()} ()} to return true. */
+ private static final float FINISHED_SWITCHING_STATE_TRANSITION_PROGRESS = 0.5f;
+
private static boolean ENFORCE_DRAG_EVENT_ORDER = false;
private static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400;
@@ -164,17 +175,15 @@
private CellLayout mDropToLayout = null;
@Thunk Launcher mLauncher;
- @Thunk IconCache mIconCache;
@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 float[] mTempCellLayoutCenterCoordinates = new float[2];
- private int[] mTempVisiblePagesRange = new int[2];
- private Matrix mTempMatrix = new Matrix();
+ private float[] mTempTouchCoordinates = new float[2];
private SpringLoadedDragController mSpringLoadedDragController;
private float mOverviewModeShrinkFactor;
@@ -183,18 +192,20 @@
// in all apps or customize mode)
public enum State {
- NORMAL (false, false),
- NORMAL_HIDDEN (false, false),
- SPRING_LOADED (false, true),
- OVERVIEW (true, true),
- OVERVIEW_HIDDEN (true, false);
+ 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) {
+ State(boolean shouldUpdateWidget, boolean hasMultipleVisiblePages, int containerType) {
this.shouldUpdateWidget = shouldUpdateWidget;
this.hasMultipleVisiblePages = hasMultipleVisiblePages;
+ this.containerType = containerType;
}
}
@@ -240,11 +251,7 @@
private boolean mStripScreensOnPageStopMoving = false;
- /** Is the user is dragging an item near the edge of a page? */
- private boolean mInScrollArea = false;
-
private DragPreviewProvider mOutlineProvider = null;
- public static final int DRAG_BITMAP_PADDING = DragPreviewProvider.DRAG_BITMAP_PADDING;
private boolean mWorkspaceFadeInAdjacentScreens;
final WallpaperOffsetInterpolator mWallpaperOffset;
@@ -303,7 +310,7 @@
LauncherOverlay mLauncherOverlay;
boolean mScrollInteractionBegan;
boolean mStartedSendingScrollEvents;
- float mLastOverlaySroll = 0;
+ float mLastOverlayScroll = 0;
// Total over scrollX in the overlay direction.
private int mUnboundedScrollX;
private boolean mForceDrawAdjacentPages = false;
@@ -375,17 +382,36 @@
mOnStateChangeListener = listener;
}
- // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each
- // dimension if unsuccessful
- public int[] estimateItemSize(ItemInfo itemInfo, boolean springLoaded) {
+ /**
+ * 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;
int[] size = new int[2];
if (getChildCount() > 0) {
// Use the first non-custom page to estimate the child position
CellLayout cl = (CellLayout) getChildAt(numCustomPages());
+ boolean isWidget = itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+
Rect r = estimateItemPosition(cl, 0, 0, itemInfo.spanX, itemInfo.spanY);
+
+ float scale = 1;
+ if (isWidget) {
+ DeviceProfile profile = mLauncher.getDeviceProfile();
+ scale = Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
+ }
size[0] = r.width();
size[1] = r.height();
+
+ if (isWidget && unscaledSize) {
+ size[0] /= scale;
+ size[1] /= scale;
+ }
+
if (springLoaded) {
size[0] *= shrinkFactor;
size[1] *= shrinkFactor;
@@ -407,7 +433,12 @@
@Override
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
if (ENFORCE_DRAG_EVENT_ORDER) {
- enfoceDragParity("onDragStart", 0, 0);
+ enforceDragParity("onDragStart", 0, 0);
+ }
+
+ if (mDragInfo != null && mDragInfo.cell != null) {
+ CellLayout layout = (CellLayout) mDragInfo.cell.getParent().getParent();
+ layout.markCellsAsUnoccupiedForView(mDragInfo.cell);
}
if (mOutlineProvider != null) {
@@ -451,10 +482,8 @@
}
}
- if (!FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) {
- // Always enter the spring loaded mode
- mLauncher.enterSpringLoadedDragMode();
- }
+ // Always enter the spring loaded mode
+ mLauncher.enterSpringLoadedDragMode();
}
public void deferRemoveExtraEmptyScreen() {
@@ -464,7 +493,7 @@
@Override
public void onDragEnd() {
if (ENFORCE_DRAG_EVENT_ORDER) {
- enfoceDragParity("onDragEnd", 0, 0);
+ enforceDragParity("onDragEnd", 0, 0);
}
if (!mDeferRemoveExtraEmptyScreen) {
@@ -477,6 +506,8 @@
// Re-enable any Un/InstallShortcutReceiver and now process any queued items
InstallShortcutReceiver.disableAndFlushInstallQueue(getContext());
+ mOutlineProvider = null;
+ mDragInfo = null;
mDragSourceInternal = null;
mLauncher.onInteractionEnd();
}
@@ -486,13 +517,10 @@
*/
protected void initWorkspace() {
mCurrentPage = getDefaultPage();
- LauncherAppState app = LauncherAppState.getInstance();
DeviceProfile grid = mLauncher.getDeviceProfile();
- mIconCache = app.getIconCache();
setWillNotDraw(false);
setClipChildren(false);
setClipToPadding(false);
- setChildrenDrawnWithCacheEnabled(true);
setMinScale(mOverviewModeShrinkFactor);
setupLayoutTransition();
@@ -545,32 +573,6 @@
super.onChildViewAdded(parent, child);
}
- protected boolean shouldDrawChild(View child) {
- final CellLayout cl = (CellLayout) child;
- return super.shouldDrawChild(child) &&
- (mIsSwitchingState ||
- cl.getShortcutsAndWidgets().getAlpha() > 0 ||
- cl.getBackgroundAlpha() > 0);
- }
-
- /**
- * @return The open folder on the current screen, or null if there is none
- */
- public Folder getOpenFolder() {
- DragLayer dragLayer = mLauncher.getDragLayer();
- // Iterate in reverse order. Folder 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 Folder) {
- Folder folder = (Folder) child;
- if (folder.getInfo().opened)
- return folder;
- }
- }
- return null;
- }
-
boolean isTouchActive() {
return mTouchState != TOUCH_STATE_REST;
}
@@ -582,7 +584,7 @@
/**
* Initializes and binds the first page
- * @param qsb an exisitng qsb to recycle or null.
+ * @param qsb an existing qsb to recycle or null.
*/
public void bindAndInitFirstWorkspaceScreen(View qsb) {
if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
@@ -642,7 +644,8 @@
// of workspace despite that it's not a true child.
// Note that it relies on the strict ordering of measuring the workspace before the QSB
// at the dragLayer level.
- if (getChildCount() > 0) {
+ // Only measure the QSB when the view is enabled
+ if (FeatureFlags.QSB_ON_FIRST_SCREEN && getChildCount() > 0) {
CellLayout firstPage = (CellLayout) getChildAt(0);
int cellHeight = firstPage.getCellHeight();
@@ -737,11 +740,7 @@
addFullScreenPage(customScreen);
// Update the custom content hint
- if (mRestorePage != INVALID_RESTORE_PAGE) {
- mRestorePage = mRestorePage + 1;
- } else {
- setCurrentPage(getCurrentPage() + 1);
- }
+ setCurrentPage(getCurrentPage() + 1);
}
public void removeCustomContentPage() {
@@ -762,11 +761,7 @@
mCustomContentCallbacks = null;
// Update the custom content hint
- if (mRestorePage != INVALID_RESTORE_PAGE) {
- mRestorePage = mRestorePage - 1;
- } else {
- setCurrentPage(getCurrentPage() - 1);
- }
+ setCurrentPage(getCurrentPage() - 1);
}
public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks,
@@ -1012,7 +1007,7 @@
return;
}
- if (isPageMoving()) {
+ if (isPageInTransition()) {
mStripScreensOnPageStopMoving = true;
return;
}
@@ -1070,23 +1065,28 @@
}
}
- // See implementation for parameter definition.
- void addInScreen(View child, long container, long screenId,
- int x, int y, int spanX, int spanY) {
- addInScreen(child, container, screenId, x, y, spanX, spanY, false, false);
+ /**
+ * At bind time, we use the rank (screenId) to compute x and y for hotseat items.
+ * See {@link #addInScreen}.
+ */
+ public void addInScreenFromBind(View child, ItemInfo info) {
+ int x = info.cellX;
+ int y = info.cellY;
+ if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ int screenId = (int) info.screenId;
+ x = mLauncher.getHotseat().getCellXFromOrder(screenId);
+ y = mLauncher.getHotseat().getCellYFromOrder(screenId);
+ }
+ addInScreen(child, info.container, info.screenId, x, y, info.spanX, info.spanY);
}
- // At bind time, we use the rank (screenId) to compute x and y for hotseat items.
- // See implementation for parameter definition.
- public void addInScreenFromBind(View child, long container, long screenId, int x, int y,
- int spanX, int spanY) {
- addInScreen(child, container, screenId, x, y, spanX, spanY, false, true);
- }
-
- // See implementation for parameter definition.
- void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
- boolean insert) {
- addInScreen(child, container, screenId, x, y, spanX, spanY, insert, false);
+ /**
+ * Adds the specified child in the specified screen based on the {@param info}
+ * See {@link #addInScreen}.
+ */
+ public void addInScreen(View child, ItemInfo info) {
+ addInScreen(child, info.container, info.screenId, info.cellX, info.cellY,
+ info.spanX, info.spanY);
}
/**
@@ -1099,13 +1099,9 @@
* @param y The Y position of the child in the screen's grid.
* @param spanX The number of cells spanned horizontally by the child.
* @param spanY The number of cells spanned vertically by the child.
- * @param insert When true, the child is inserted at the beginning of the children list.
- * @param computeXYFromRank When true, we use the rank (stored in screenId) to compute
- * the x and y position in which to place hotseat items. Otherwise
- * we use the x and y position to compute the rank.
*/
- void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
- boolean insert, boolean computeXYFromRank) {
+ private void addInScreen(View child, long container, long screenId, int x, int y,
+ int spanX, int spanY) {
if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
if (getScreenWithId(screenId) == null) {
Log.e(TAG, "Skipping child, screenId " + screenId + " not found");
@@ -1128,13 +1124,6 @@
if (child instanceof FolderIcon) {
((FolderIcon) child).setTextVisible(false);
}
-
- if (computeXYFromRank) {
- x = mLauncher.getHotseat().getCellXFromOrder((int) screenId);
- y = mLauncher.getHotseat().getCellYFromOrder((int) screenId);
- } else {
- screenId = mLauncher.getHotseat().getOrderInHotseat(x, y);
- }
} else {
// Show folder title if not in the hotseat
if (child instanceof FolderIcon) {
@@ -1165,7 +1154,7 @@
int childId = mLauncher.getViewIdForItem(info);
boolean markCellsAsOccupied = !(child instanceof Folder);
- if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
+ if (!layout.addViewToCellLayout(child, -1, childId, lp, markCellsAsOccupied)) {
// TODO: This branch occurs when the workspace is adding views
// outside of the defined grid
// maybe we should be deleting these items from the LauncherModel?
@@ -1193,7 +1182,7 @@
}
private boolean shouldConsumeTouch(View v) {
- return (workspaceInModalState() || !isFinishedSwitchingState())
+ return !workspaceIconsCanBeDragged()
|| (!workspaceInModalState() && indexOfChild(v) != mCurrentPage);
}
@@ -1204,7 +1193,8 @@
/** This differs from isSwitchingState in that we take into account how far the transition
* has completed. */
public boolean isFinishedSwitchingState() {
- return !mIsSwitchingState || (mTransitionProgress > 0.5f);
+ return !mIsSwitchingState
+ || (mTransitionProgress > FINISHED_SWITCHING_STATE_TRANSITION_PROGRESS);
}
protected void onWindowVisibilityChanged (int visibility) {
@@ -1328,31 +1318,14 @@
}
}
- protected void onPageBeginMoving() {
- super.onPageBeginMoving();
-
- if (isHardwareAccelerated()) {
- updateChildrenLayersEnabled(false);
- } else {
- if (mNextPage != INVALID_PAGE) {
- // we're snapping to a particular screen
- enableChildrenCache(mCurrentPage, mNextPage);
- } else {
- // this is when user is actively dragging a particular screen, they might
- // swipe it either left or right (but we won't advance by more than one screen)
- enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1);
- }
- }
+ protected void onPageBeginTransition() {
+ super.onPageBeginTransition();
+ updateChildrenLayersEnabled(false);
}
- protected void onPageEndMoving() {
- super.onPageEndMoving();
-
- if (isHardwareAccelerated()) {
- updateChildrenLayersEnabled(false);
- } else {
- clearChildrenCache();
- }
+ protected void onPageEndTransition() {
+ super.onPageEndTransition();
+ updateChildrenLayersEnabled(false);
if (mDragController.isDragging()) {
if (workspaceInModalState()) {
@@ -1419,12 +1392,10 @@
// it's own settling, and every gesture to the overlay should be self-contained and start
// from 0, so we zero it out here.
if (isScrollingOverlay()) {
- int finalScroll = mIsRtl ? mMaxScrollX : 0;
-
// We reset mWasInOverscroll so that PagedView doesn't zero out the overscroll
- // interaction when we call scrollTo.
+ // interaction when we call snapToPageImmediately.
mWasInOverscroll = false;
- scrollTo(finalScroll, getScrollY());
+ snapToPageImmediately(0);
} else {
super.snapToDestination();
}
@@ -1454,6 +1425,10 @@
if (!isTransitioning) {
showPageIndicatorAtCurrentScroll();
}
+
+ updatePageAlphaValues();
+ updateStateForCustomContent();
+ enableHwLayersOnVisiblePages();
}
private void showPageIndicatorAtCurrentScroll() {
@@ -1470,7 +1445,7 @@
boolean shouldScrollOverlay = mLauncherOverlay != null &&
((amount <= 0 && !mIsRtl) || (amount >= 0 && mIsRtl));
- boolean shouldZeroOverlay = mLauncherOverlay != null && mLastOverlaySroll != 0 &&
+ boolean shouldZeroOverlay = mLauncherOverlay != null && mLastOverlayScroll != 0 &&
((amount >= 0 && !mIsRtl) || (amount <= 0 && mIsRtl));
if (shouldScrollOverlay) {
@@ -1479,8 +1454,8 @@
mLauncherOverlay.onScrollInteractionBegin();
}
- mLastOverlaySroll = Math.abs(amount / getViewportWidth());
- mLauncherOverlay.onScrollChange(mLastOverlaySroll, mIsRtl);
+ mLastOverlayScroll = Math.abs(amount / getViewportWidth());
+ mLauncherOverlay.onScrollChange(mLastOverlayScroll, mIsRtl);
} else if (shouldOverScroll) {
dampedOverScroll(amount);
}
@@ -1612,19 +1587,7 @@
}
@Override
- protected Matrix getPageShiftMatrix() {
- if (Float.compare(mOverlayTranslation, 0) != 0) {
- // The pages are translated by mOverlayTranslation. incorporate that in the
- // visible page calculation by shifting everything back by that same amount.
- mTempMatrix.set(getMatrix());
- mTempMatrix.postTranslate(-mOverlayTranslation, 0);
- return mTempMatrix;
- }
- return super.getPageShiftMatrix();
- }
-
- @Override
- protected void getEdgeVerticalPostion(int[] pos) {
+ protected void getEdgeVerticalPosition(int[] pos) {
View child = getChildAt(getPageCount() - 1);
pos[0] = child.getTop();
pos[1] = child.getBottom();
@@ -1656,8 +1619,7 @@
Utilities.THREAD_POOL_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
- final Point size = LauncherAppState.getInstance()
- .getInvariantDeviceProfile().defaultWallpaperSize;
+ final Point size = LauncherAppState.getIDP(getContext()).defaultWallpaperSize;
if (size.x != mWallpaperManager.getDesiredMinimumWidth()
|| size.y != mWallpaperManager.getDesiredMinimumHeight()) {
mWallpaperManager.suggestDesiredDimensions(size.x, size.y);
@@ -1723,15 +1685,16 @@
}
public void showOutlinesTemporarily() {
- if (!mIsPageMoving && !isTouchActive()) {
+ if (!mIsPageInTransition && !isTouchActive()) {
snapToPage(mCurrentPage);
}
}
- private void updatePageAlphaValues(int screenCenter) {
+ private void updatePageAlphaValues() {
if (mWorkspaceFadeInAdjacentScreens &&
!workspaceInModalState() &&
!mIsSwitchingState) {
+ int screenCenter = getScrollX() + getViewportWidth() / 2;
for (int i = numCustomPages(); i < getChildCount(); i++) {
CellLayout child = (CellLayout) getChildAt(i);
if (child != null) {
@@ -1756,10 +1719,10 @@
}
public boolean isOnOrMovingToCustomContent() {
- return hasCustomContent() && getNextPage() == 0 && mRestorePage == INVALID_RESTORE_PAGE;
+ return hasCustomContent() && getNextPage() == 0;
}
- private void updateStateForCustomContent(int screenCenter) {
+ private void updateStateForCustomContent() {
float translationX = 0;
float progress = 0;
if (hasCustomContent()) {
@@ -1807,13 +1770,6 @@
}
}
- @Override
- protected void screenScrolled(int screenCenter) {
- updatePageAlphaValues(screenCenter);
- updateStateForCustomContent(screenCenter);
- enableHwLayersOnVisiblePages();
- }
-
protected void onAttachedToWindow() {
super.onAttachedToWindow();
IBinder windowToken = getWindowToken();
@@ -1828,11 +1784,6 @@
}
protected void onResume() {
- // Update wallpaper dimensions if they were changed since last onResume
- // (we also always set the wallpaper dimensions in the constructor)
- if (LauncherAppState.getInstance().hasWallpaperChangedSinceLastCheck()) {
- setWallpaperDimension();
- }
mWallpaperOffset.onResume();
}
@@ -1875,7 +1826,7 @@
}
});
}
-
+ updatePageAlphaValues();
}
@Override
@@ -1890,40 +1841,14 @@
return mState != State.NORMAL;
}
- void enableChildrenCache(int fromPage, int toPage) {
- if (fromPage > toPage) {
- final int temp = fromPage;
- fromPage = toPage;
- toPage = temp;
- }
-
- final int screenCount = getChildCount();
-
- fromPage = Math.max(fromPage, 0);
- toPage = Math.min(toPage, screenCount - 1);
-
- for (int i = fromPage; i <= toPage; i++) {
- final CellLayout layout = (CellLayout) getChildAt(i);
- layout.setChildrenDrawnWithCacheEnabled(true);
- layout.setChildrenDrawingCacheEnabled(true);
- }
- }
-
- void clearChildrenCache() {
- final int screenCount = getChildCount();
- for (int i = 0; i < screenCount; i++) {
- final CellLayout layout = (CellLayout) getChildAt(i);
- layout.setChildrenDrawnWithCacheEnabled(false);
- // In software mode, we don't want the items to continue to be drawn into bitmaps
- if (!isHardwareAccelerated()) {
- layout.setChildrenDrawingCacheEnabled(false);
- }
- }
+ /** 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;
}
@Thunk void updateChildrenLayersEnabled(boolean force) {
boolean small = mState == State.OVERVIEW || mIsSwitchingState;
- boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageMoving();
+ boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageInTransition();
if (enableChildrenLayers != mChildrenLayersEnabled) {
mChildrenLayersEnabled = enableChildrenLayers;
@@ -1941,9 +1866,37 @@
private void enableHwLayersOnVisiblePages() {
if (mChildrenLayersEnabled) {
final int screenCount = getChildCount();
- getVisiblePages(mTempVisiblePagesRange);
- int leftScreen = mTempVisiblePagesRange[0];
- int rightScreen = mTempVisiblePagesRange[1];
+
+ float visibleLeft = getViewportOffsetX();
+ float visibleRight = visibleLeft + getViewportWidth();
+ float scaleX = getScaleX();
+ if (scaleX < 1 && scaleX > 0) {
+ float mid = getMeasuredWidth() / 2;
+ visibleLeft = mid - ((mid - visibleLeft) / scaleX);
+ visibleRight = mid + ((visibleRight - mid) / scaleX);
+ }
+
+ int leftScreen = -1;
+ int rightScreen = -1;
+ for (int i = numCustomPages(); i < screenCount; i++) {
+ final View child = getPageAt(i);
+
+ float left = child.getLeft() + child.getTranslationX() - getScrollX();
+ if (left <= visibleRight && (left + child.getMeasuredWidth()) >= visibleLeft) {
+ if (leftScreen == -1) {
+ leftScreen = i;
+ }
+ rightScreen = i;
+ }
+ }
+ if (mForceDrawAdjacentPages) {
+ // In overview mode, make sure that the two side pages are visible.
+ leftScreen = Utilities.boundToRange(getCurrentPage() - 1,
+ numCustomPages(), rightScreen);
+ rightScreen = Utilities.boundToRange(getCurrentPage() + 1,
+ leftScreen, getPageCount() - 1);
+ }
+
if (leftScreen == rightScreen) {
// make sure we're caching at least two pages always
if (rightScreen < screenCount - 1) {
@@ -1953,14 +1906,10 @@
}
}
- final CellLayout customScreen = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
- for (int i = 0; i < screenCount; i++) {
+ for (int i = numCustomPages(); i < screenCount; i++) {
final CellLayout layout = (CellLayout) getPageAt(i);
-
- // enable layers between left and right screen inclusive, except for the
- // customScreen, which may animate its content during transitions.
- boolean enableLayer = layout != customScreen &&
- leftScreen <= i && i <= rightScreen && shouldDrawChild(layout);
+ // enable layers between left and right screen inclusive.
+ boolean enableLayer = leftScreen <= i && i <= rightScreen;
layout.enableHardwareLayer(enableLayer);
}
}
@@ -1979,16 +1928,6 @@
updateChildrenLayersEnabled(false);
}
- @Override
- protected void getVisiblePages(int[] range) {
- super.getVisiblePages(range);
- if (mForceDrawAdjacentPages) {
- // In overview mode, make sure that the two side pages are visible.
- range[0] = Utilities.boundToRange(getCurrentPage() - 1, numCustomPages(), range[1]);
- range[1] = Utilities.boundToRange(getCurrentPage() + 1, range[0], getPageCount() - 1);
- }
- }
-
protected void onWallpaperTap(MotionEvent ev) {
final int[] position = mTempXY;
getLocationOnScreen(position);
@@ -2009,7 +1948,7 @@
public void exitWidgetResizeMode() {
DragLayer dragLayer = mLauncher.getDragLayer();
- dragLayer.clearAllResizeFrames();
+ dragLayer.clearResizeFrame();
}
@Override
@@ -2045,7 +1984,7 @@
CellLayout cl = ((CellLayout) getChildAt(i));
mScreenOrder.add(getIdForScreen(cl));
}
-
+ mLauncher.getUserEventDispatcher().logOverviewReorder();
mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
// Re-enable auto layout transitions for page deletion.
@@ -2106,15 +2045,19 @@
* to that new state.
*/
public Animator setStateWithAnimation(State toState, boolean animated,
- HashMap<View, Integer> layerViews) {
- // Create the animation to the new state
- AnimatorSet workspaceAnim = mStateTransitionAnimation.getAnimationToState(mState,
- toState, animated, layerViews);
+ AnimationLayerSet layerViews) {
+ final State fromState = mState;
- boolean shouldNotifyWidgetChange = !mState.shouldUpdateWidget
- && toState.shouldUpdateWidget;
// 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) {
@@ -2125,6 +2068,20 @@
mOnStateChangeListener.prepareStateChange(toState, animated ? workspaceAnim : null);
}
+ 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;
}
@@ -2135,20 +2092,13 @@
public void updateAccessibilityFlags() {
// TODO: Update the accessibility flags appropriately when dragging.
if (!mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
- if (Utilities.ATLEAST_LOLLIPOP) {
- int total = getPageCount();
- for (int i = numCustomPages(); i < total; i++) {
- updateAccessibilityFlags((CellLayout) getPageAt(i), i);
- }
- setImportantForAccessibility((mState == State.NORMAL || mState == State.OVERVIEW)
- ? IMPORTANT_FOR_ACCESSIBILITY_AUTO
- : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
- } else {
- int accessible = mState == State.NORMAL ?
- IMPORTANT_FOR_ACCESSIBILITY_AUTO :
- IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
- setImportantForAccessibility(accessible);
+ int total = getPageCount();
+ for (int i = numCustomPages(); i < total; i++) {
+ updateAccessibilityFlags((CellLayout) getPageAt(i), i);
}
+ setImportantForAccessibility((mState == State.NORMAL || mState == State.OVERVIEW)
+ ? IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
}
}
@@ -2177,9 +2127,7 @@
}
}
- @Override
- public void onLauncherTransitionPrepare(Launcher l, boolean animated,
- boolean multiplePagesVisible) {
+ public void onPrepareStateTransition(boolean multiplePagesVisible) {
mIsSwitchingState = true;
mTransitionProgress = 0;
@@ -2192,32 +2140,12 @@
hideCustomContentIfNecessary();
}
- @Override
- public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
- if (mPageIndicator != null) {
- boolean isNewStateSpringLoaded = mState == State.SPRING_LOADED;
- mPageIndicator.setShouldAutoHide(!isNewStateSpringLoaded);
- if (isNewStateSpringLoaded) {
- // Show the page indicator at the same time as the rest of the transition.
- showPageIndicatorAtCurrentScroll();
- }
- }
- }
-
- @Override
- public void onLauncherTransitionStep(Launcher l, float t) {
- mTransitionProgress = t;
- }
-
- @Override
- public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
+ public void onEndStateTransition() {
mIsSwitchingState = false;
updateChildrenLayersEnabled(false);
showCustomContentIfNecessary();
mForceDrawAdjacentPages = false;
- if (mState == State.SPRING_LOADED) {
- showPageIndicatorAtCurrentScroll();
- }
+ mTransitionProgress = 1;
}
void updateCustomContentVisibility() {
@@ -2270,11 +2198,9 @@
mDragInfo = cellInfo;
child.setVisibility(INVISIBLE);
- CellLayout layout = (CellLayout) child.getParent().getParent();
- layout.prepareChildForDrag(child);
if (options.isAccessibleDrag) {
- mDragController.addDragListener(new AccessibileDragListenerAdapter(
+ mDragController.addDragListener(new AccessibleDragListenerAdapter(
this, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG) {
@Override
protected void enableAccessibleDrag(boolean enable) {
@@ -2322,20 +2248,18 @@
Point dragVisualizeOffset = null;
Rect dragRect = null;
if (child instanceof BubbleTextView) {
- int iconSize = grid.iconSizePx;
- int top = child.getPaddingTop();
- int left = (b.getWidth() - iconSize) / 2;
- int right = left + iconSize;
- int bottom = top + iconSize;
- dragLayerY += top;
- // Note: The drag region is used to calculate drag layer offsets, but the
+ dragRect = new Rect();
+ ((BubbleTextView) child).getIconBounds(dragRect);
+ dragLayerY += dragRect.top;
+ // Note: The dragRect is used to calculate drag layer offsets, but the
// dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
dragVisualizeOffset = new Point(- halfPadding, halfPadding);
- dragRect = new Rect(left, top, right, bottom);
} else if (child instanceof FolderIcon) {
int previewSize = grid.folderIconSizePx;
dragVisualizeOffset = new Point(- halfPadding, halfPadding - child.getPaddingTop());
dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
+ } else if (previewProvider instanceof ShortcutDragPreviewProvider) {
+ dragVisualizeOffset = new Point(- halfPadding, halfPadding);
}
// Clear the pressed state if necessary
@@ -2348,6 +2272,16 @@
mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
}
+ if (child instanceof BubbleTextView) {
+ PopupContainerWithArrow popupContainer = PopupContainerWithArrow
+ .showForIcon((BubbleTextView) child);
+ if (popupContainer != null) {
+ dragOptions.preDragCondition = popupContainer.createPreDragCondition();
+
+ mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
+ }
+ }
+
DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source,
dragObject, dragVisualizeOffset, dragRect, scale, dragOptions);
dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
@@ -2355,8 +2289,8 @@
return dv;
}
- public boolean transitionStateShouldAllowDrop() {
- return ((!isSwitchingState() || mTransitionProgress > 0.5f) &&
+ private boolean transitionStateShouldAllowDrop() {
+ return ((!isSwitchingState() || mTransitionProgress > ALLOW_DROP_TRANSITION_PROGRESS) &&
(mState == State.NORMAL || mState == State.SPRING_LOADED));
}
@@ -2423,18 +2357,7 @@
// Don't accept the drop if there's no room for the item
if (!foundCell) {
- // Don't show the message if we are dropping on the AllApps button and the hotseat
- // is full
- boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
- if (mTargetCell != null && isHotseat && !FeatureFlags.NO_ALL_APPS_ICON) {
- Hotseat hotseat = mLauncher.getHotseat();
- if (mLauncher.getDeviceProfile().inv.isAllAppsButtonRank(
- hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) {
- return false;
- }
- }
-
- mLauncher.showOutOfSpaceMessage(isHotseat);
+ onNoCellFound(dropTargetLayout);
return false;
}
}
@@ -2606,9 +2529,10 @@
if (d.dragSource != this) {
final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
(int) mDragViewVisualCenter[1] };
- onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
+ onDropExternal(touchXY, dropTargetLayout, d);
} else if (mDragInfo != null) {
final View cell = mDragInfo.cell;
+ boolean droppedOnOriginalCellDuringTransition = false;
if (dropTargetLayout != null && !d.cancelled) {
// Move internally
@@ -2631,7 +2555,7 @@
// 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 (!mInScrollArea && createUserFolderIfNecessary(cell, container,
+ if (createUserFolderIfNecessary(cell, container,
dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
return;
}
@@ -2651,10 +2575,23 @@
minSpanY = item.minSpanY;
}
+ droppedOnOriginalCellDuringTransition = mIsSwitchingState
+ && item.screenId == screenId && item.container == container
+ && item.cellX == mTargetCell[0] && item.cellY == mTargetCell[1];
+
+ // When quickly moving an item, a user may accidentally rearrange their
+ // workspace. So instead we move the icon back safely to its original position.
+ boolean returnToOriginalCellToPreventShuffling = !isFinishedSwitchingState()
+ && !droppedOnOriginalCellDuringTransition && !dropTargetLayout
+ .isRegionVacant(mTargetCell[0], mTargetCell[1], spanX, spanY);
int[] resultSpan = new int[2];
- mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
- (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
- mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
+ if (returnToOriginalCellToPreventShuffling) {
+ mTargetCell[0] = mTargetCell[1] = -1;
+ } else {
+ mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
+ mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
+ }
boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
@@ -2669,12 +2606,12 @@
resultSpan[1]);
}
- if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
- snapScreen = getPageIndexForScreenId(screenId);
- snapToPage(snapScreen);
- }
-
if (foundCell) {
+ if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
+ snapScreen = getPageIndexForScreenId(screenId);
+ snapToPage(snapScreen);
+ }
+
final ItemInfo info = (ItemInfo) cell.getTag();
if (hasMovedLayouts) {
// Reparent the view
@@ -2708,18 +2645,22 @@
&& !d.accessibleDrag) {
mDelayedResizeRunnable = new Runnable() {
public void run() {
- if (!isPageMoving() && !mIsSwitchingState) {
+ if (!isPageInTransition()) {
DragLayer dragLayer = mLauncher.getDragLayer();
- dragLayer.addResizeFrame(info, hostView, cellLayout);
+ dragLayer.addResizeFrame(hostView, cellLayout);
}
}
};
}
}
- LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, lp.cellX,
- lp.cellY, item.spanX, item.spanY);
+ mLauncher.getModelWriter().modifyItemInDatabase(info, container, screenId,
+ lp.cellX, lp.cellY, item.spanX, item.spanY);
} else {
+ if (!returnToOriginalCellToPreventShuffling) {
+ onNoCellFound(dropTargetLayout);
+ }
+
// If we can't find a drop location, we return the item to its original position
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
mTargetCell[0] = lp.cellX;
@@ -2741,6 +2682,17 @@
};
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();
+ mLauncher.getDropTargetBar().onDragEnd();
+ parent.onDropChild(cell);
+ return;
+ }
final ItemInfo info = (ItemInfo) cell.getTag();
boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
|| info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
@@ -2765,6 +2717,26 @@
}
}
+ public void onNoCellFound(View dropTargetLayout) {
+ if (mLauncher.isHotseatLayout(dropTargetLayout)) {
+ Hotseat hotseat = mLauncher.getHotseat();
+ boolean droppedOnAllAppsIcon = !FeatureFlags.NO_ALL_APPS_ICON
+ && mTargetCell != null && !mLauncher.getDeviceProfile().inv.isAllAppsButtonRank(
+ hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]));
+ if (!droppedOnAllAppsIcon) {
+ // Only show message when hotseat is full and drop target was not AllApps button
+ showOutOfSpaceMessage(true);
+ }
+ } else {
+ showOutOfSpaceMessage(false);
+ }
+ }
+
+ private void showOutOfSpaceMessage(boolean isHotseatLayout) {
+ int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
+ Toast.makeText(mLauncher, mLauncher.getString(strId), Toast.LENGTH_SHORT).show();
+ }
+
/**
* Computes the area relative to dragLayer which is used to display a page.
*/
@@ -2790,64 +2762,47 @@
@Override
public void onDragEnter(DragObject d) {
if (ENFORCE_DRAG_EVENT_ORDER) {
- enfoceDragParity("onDragEnter", 1, 1);
+ enforceDragParity("onDragEnter", 1, 1);
}
mCreateUserFolderOnDrop = false;
mAddToExistingFolderOnDrop = false;
mDropToLayout = null;
- setDropLayoutForDragObject(d);
-
- if (!workspaceInModalState() && FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) {
- mLauncher.getDragLayer().showPageHints();
- }
+ mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
+ setDropLayoutForDragObject(d, mDragViewVisualCenter[0], mDragViewVisualCenter[1]);
}
@Override
public void onDragExit(DragObject d) {
if (ENFORCE_DRAG_EVENT_ORDER) {
- enfoceDragParity("onDragExit", -1, 0);
+ enforceDragParity("onDragExit", -1, 0);
}
// Here we store the final page that will be dropped to, if the workspace in fact
// receives the drop
- if (mInScrollArea) {
- if (isPageMoving()) {
- // If the user drops while the page is scrolling, we should use that page as the
- // destination instead of the page that is being hovered over.
- mDropToLayout = (CellLayout) getPageAt(getNextPage());
- } else {
- mDropToLayout = mDragOverlappingLayout;
- }
- } else {
- mDropToLayout = mDragTargetLayout;
- }
-
+ mDropToLayout = mDragTargetLayout;
if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
mCreateUserFolderOnDrop = true;
} else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
mAddToExistingFolderOnDrop = true;
}
- // Reset the scroll area and previous drag target
- onResetScrollArea();
+ // Reset the previous drag target
setCurrentDropLayout(null);
setCurrentDragOverlappingLayout(null);
mSpringLoadedDragController.cancel();
-
- mLauncher.getDragLayer().hidePageHints();
}
- private void enfoceDragParity(String event, int update, int expectedValue) {
- enfoceDragParity(this, event, update, expectedValue);
+ private void enforceDragParity(String event, int update, int expectedValue) {
+ enforceDragParity(this, event, update, expectedValue);
for (int i = 0; i < getChildCount(); i++) {
- enfoceDragParity(getChildAt(i), event, update, expectedValue);
+ enforceDragParity(getChildAt(i), event, update, expectedValue);
}
}
- private void enfoceDragParity(View v, String event, int update, int expectedValue) {
+ private void enforceDragParity(View v, String event, int update, int expectedValue) {
Object tag = v.getTag(R.id.drag_event_parity);
int value = tag == null ? 0 : (Integer) tag;
value += update;
@@ -2968,7 +2923,7 @@
mTempXY[0] = (int) xy[0];
mTempXY[1] = (int) xy[1];
mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true);
- mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempXY);
+ mLauncher.getDragLayer().mapCoordInSelfToDescendant(hotseat.getLayout(), mTempXY);
xy[0] = mTempXY[0];
xy[1] = mTempXY[1];
@@ -2985,68 +2940,6 @@
xy[1] += v.getTop();
}
- static private float squaredDistance(float[] point1, float[] point2) {
- float distanceX = point1[0] - point2[0];
- float distanceY = point2[1] - point2[1];
- return distanceX * distanceX + distanceY * distanceY;
- }
-
- /*
- *
- * This method returns the CellLayout that is currently being dragged to. In order to drag
- * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
- * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
- *
- * Return null if no CellLayout is currently being dragged over
- *
- */
- private CellLayout findMatchingPageForDragOver(
- DragView dragView, float originX, float originY, boolean exact) {
- // We loop through all the screens (ie CellLayouts) and see which ones overlap
- // with the item being dragged and then choose the one that's closest to the touch point
- final int screenCount = getChildCount();
- CellLayout bestMatchingScreen = null;
- float smallestDistSoFar = Float.MAX_VALUE;
-
- for (int i = 0; i < screenCount; i++) {
- // The custom content screen is not a valid drag over option
- if (mScreenOrder.get(i) == CUSTOM_CONTENT_SCREEN_ID) {
- continue;
- }
-
- CellLayout cl = (CellLayout) getChildAt(i);
-
- final float[] touchXy = {originX, originY};
- mapPointFromSelfToChild(cl, touchXy);
-
- if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
- touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
- return cl;
- }
-
- if (!exact) {
- // Get the center of the cell layout in screen coordinates
- final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
- cellLayoutCenter[0] = cl.getWidth()/2;
- cellLayoutCenter[1] = cl.getHeight()/2;
- mapPointFromChildToSelf(cl, cellLayoutCenter);
-
- touchXy[0] = originX;
- touchXy[1] = originY;
-
- // Calculate the distance between the center of the CellLayout
- // and the touch point
- float dist = squaredDistance(touchXy, cellLayoutCenter);
-
- if (dist < smallestDistSoFar) {
- smallestDistSoFar = dist;
- bestMatchingScreen = cl;
- }
- }
- }
- return bestMatchingScreen;
- }
-
private boolean isDragWidget(DragObject d) {
return (d.dragInfo instanceof LauncherAppWidgetInfo ||
d.dragInfo instanceof PendingAddWidgetInfo);
@@ -3054,7 +2947,7 @@
public void onDragOver(DragObject d) {
// Skip drag over events while we are dragging over side pages
- if (mInScrollArea || !transitionStateShouldAllowDrop()) return;
+ if (!transitionStateShouldAllowDrop()) return;
ItemInfo item = d.dragInfo;
if (item == null) {
@@ -3069,14 +2962,11 @@
mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
final View child = (mDragInfo == null) ? null : mDragInfo.cell;
- if (setDropLayoutForDragObject(d)) {
- boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED);
- if (isInSpringLoadedMode) {
- if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
- mSpringLoadedDragController.cancel();
- } else {
- mSpringLoadedDragController.setAlarm(mDragTargetLayout);
- }
+ if (setDropLayoutForDragObject(d, mDragViewVisualCenter[0], mDragViewVisualCenter[1])) {
+ if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
+ mSpringLoadedDragController.cancel();
+ } else {
+ mSpringLoadedDragController.setAlarm(mDragTargetLayout);
}
}
@@ -3153,7 +3043,7 @@
*
* @return whether the layout is different from the current {@link #mDragTargetLayout}.
*/
- private boolean setDropLayoutForDragObject(DragObject d) {
+ private boolean setDropLayoutForDragObject(DragObject d, float centerX, float centerY) {
CellLayout layout = null;
// Test to see if we are over the hotseat first
if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
@@ -3161,12 +3051,25 @@
layout = mLauncher.getHotseat().getLayout();
}
}
- if (layout == null) {
- // Identify whether we have dragged over a side page,
- // otherwise just use the current page
- layout = workspaceInModalState() ?
- findMatchingPageForDragOver(d.dragView, d.x, d.y, false)
- : getCurrentDropLayout();
+
+ int nextPage = getNextPage();
+ if (layout == null && !isPageInTransition()) {
+ // Check if the item is dragged over left page
+ mTempTouchCoordinates[0] = Math.min(centerX, d.x);
+ mTempTouchCoordinates[1] = d.y;
+ layout = verifyInsidePage(nextPage + (mIsRtl ? 1 : -1), mTempTouchCoordinates);
+ }
+
+ if (layout == null && !isPageInTransition()) {
+ // Check if the item is dragged over right page
+ mTempTouchCoordinates[0] = Math.max(centerX, d.x);
+ mTempTouchCoordinates[1] = d.y;
+ layout = verifyInsidePage(nextPage + (mIsRtl ? -1 : 1), mTempTouchCoordinates);
+ }
+
+ // Always pick the current page.
+ if (layout == null && nextPage >= numCustomPages() && nextPage < getPageCount()) {
+ layout = (CellLayout) getChildAt(nextPage);
}
if (layout != mDragTargetLayout) {
setCurrentDropLayout(layout);
@@ -3176,6 +3079,22 @@
return false;
}
+ /**
+ * 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()) {
+ CellLayout cl = (CellLayout) getChildAt(pageNo);
+ mapPointFromSelfToChild(cl, touchXy);
+ if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
+ touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
+ // This point is inside the cell layout
+ return cl;
+ }
+ }
+ return null;
+ }
+
private void manageFolderFeedback(CellLayout targetLayout,
int[] targetCell, float distance, DragObject dragObject) {
if (distance > mMaxDistanceForFolderCreation) return;
@@ -3313,8 +3232,7 @@
* NOTE: This can also be called when we are outside of a drag event, when we want
* to add an item to one of the workspace screens.
*/
- private void onDropExternal(final int[] touchXY, final ItemInfo dragInfo,
- final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
+ private void onDropExternal(final int[] touchXY, final CellLayout cellLayout, DragObject d) {
final Runnable exitSpringLoadedRunnable = new Runnable() {
@Override
public void run() {
@@ -3323,7 +3241,15 @@
}
};
- ItemInfo info = dragInfo;
+ if (d.dragInfo instanceof PendingAddShortcutInfo) {
+ ShortcutInfo si = ((PendingAddShortcutInfo) d.dragInfo)
+ .activityInfo.createShortcutInfo();
+ if (si != null) {
+ d.dragInfo = si;
+ }
+ }
+
+ ItemInfo info = d.dragInfo;
int spanX = info.spanX;
int spanY = info.spanY;
if (mDragInfo != null) {
@@ -3342,7 +3268,7 @@
}
if (info instanceof PendingAddItemInfo) {
- final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo;
+ final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) info;
boolean findNearestVacantCell = true;
if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
@@ -3405,7 +3331,7 @@
int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
if (isWidget && ((PendingAddWidgetInfo) pendingInfo).info != null &&
- ((PendingAddWidgetInfo) pendingInfo).info.configure != null) {
+ ((PendingAddWidgetInfo) pendingInfo).getHandler().needsConfigure()) {
animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
}
animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
@@ -3427,7 +3353,7 @@
break;
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
- (FolderInfo) info, mIconCache);
+ (FolderInfo) info);
break;
default:
throw new IllegalStateException("Unknown item type: " + info.itemType);
@@ -3461,11 +3387,11 @@
}
// Add the item to DB before adding to screen ensures that the container and other
// values of the info is properly updated.
- LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId,
+ mLauncher.getModelWriter().addOrMoveItemInDatabase(info, container, screenId,
mTargetCell[0], mTargetCell[1]);
- addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX,
- info.spanY, insertAtFirst);
+ addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1],
+ info.spanX, info.spanY);
cellLayout.onDropChild(view);
cellLayout.getShortcutsAndWidgets().measureChild(view);
@@ -3482,7 +3408,7 @@
}
public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
- int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo, false);
+ int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo, false, true);
int visibility = layout.getVisibility();
layout.setVisibility(VISIBLE);
@@ -3508,6 +3434,10 @@
int spanY = info.spanY;
Rect r = estimateItemPosition(layout, targetCell[0], targetCell[1], spanX, spanY);
+ if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
+ DeviceProfile profile = mLauncher.getDeviceProfile();
+ Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
+ }
loc[0] = r.left;
loc[1] = r.top;
@@ -3516,24 +3446,32 @@
mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
resetTransitionTransform(layout);
- float dragViewScaleX;
- float dragViewScaleY;
if (scale) {
- dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
- dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
+ float dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
+ float dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
+
+ // The animation will scale the dragView about its center, so we need to center about
+ // the final location.
+ loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2
+ - Math.ceil(layout.getUnusedHorizontalSpace() / 2f);
+ loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
+ scaleXY[0] = dragViewScaleX * cellLayoutScale;
+ scaleXY[1] = dragViewScaleY * cellLayoutScale;
} else {
- dragViewScaleX = 1f;
- dragViewScaleY = 1f;
+ // Since we are not cross-fading the dragView, align the drag view to the
+ // final cell position.
+ float dragScale = dragView.getInitialScale() * cellLayoutScale;
+ loc[0] += (dragScale - 1) * dragView.getWidth() / 2;
+ loc[1] += (dragScale - 1) * dragView.getHeight() / 2;
+ scaleXY[0] = scaleXY[1] = dragScale;
+
+ // If a dragRegion was provided, offset the final position accordingly.
+ Rect dragRegion = dragView.getDragRegion();
+ if (dragRegion != null) {
+ loc[0] += cellLayoutScale * dragRegion.left;
+ loc[1] += cellLayoutScale * dragRegion.top;
+ }
}
-
- // The animation will scale the dragView about its center, so we need to center about
- // the final location.
- loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2
- - Math.ceil(layout.getUnusedHorizontalSpace() / 2f);
- loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
-
- scaleXY[0] = dragViewScaleX * cellLayoutScale;
- scaleXY[1] = dragViewScaleY * cellLayoutScale;
}
public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, final DragView dragView,
@@ -3609,14 +3547,6 @@
}
/**
- * Return the current {@link CellLayout}, correctly picking the destination
- * screen while a scroll is in progress.
- */
- public CellLayout getCurrentDropLayout() {
- return (CellLayout) getChildAt(getNextPage());
- }
-
- /**
* 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/
* destroyed
@@ -3656,8 +3586,10 @@
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;
}
@@ -3685,7 +3617,6 @@
&& mDragInfo.cell != null) {
mDragInfo.cell.setVisibility(VISIBLE);
}
- mOutlineProvider = null;
mDragInfo = null;
if (!isFlingToDelete) {
@@ -3751,13 +3682,8 @@
}
@Override
- public boolean supportsFlingToDelete() {
- return true;
- }
-
- @Override
public boolean supportsAppInfoDropTarget() {
- return !FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND;
+ return true;
}
@Override
@@ -3765,16 +3691,6 @@
return true;
}
- @Override
- public void onFlingToDelete(DragObject d, PointF vec) {
- // Do nothing
- }
-
- @Override
- public void onFlingToDeleteCompleted() {
- // Do nothing
- }
-
public boolean isDropEnabled() {
return true;
}
@@ -3813,7 +3729,7 @@
if (!workspaceInModalState() && !mIsSwitchingState) {
super.scrollLeft();
}
- Folder openFolder = getOpenFolder();
+ Folder openFolder = Folder.getOpen(mLauncher);
if (openFolder != null) {
openFolder.completeDragExit();
}
@@ -3824,71 +3740,12 @@
if (!workspaceInModalState() && !mIsSwitchingState) {
super.scrollRight();
}
- Folder openFolder = getOpenFolder();
+ Folder openFolder = Folder.getOpen(mLauncher);
if (openFolder != null) {
openFolder.completeDragExit();
}
}
- @Override
- public boolean onEnterScrollArea(int x, int y, int direction) {
- // Ignore the scroll area if we are dragging over the hot seat
- boolean isPortrait = !mLauncher.getDeviceProfile().isLandscape;
- if (mLauncher.getHotseat() != null && isPortrait) {
- Rect r = new Rect();
- mLauncher.getHotseat().getHitRect(r);
- if (r.contains(x, y)) {
- return false;
- }
- }
-
- boolean result = false;
- if (!workspaceInModalState() && !mIsSwitchingState && getOpenFolder() == null) {
- mInScrollArea = true;
-
- final int page = getNextPage() +
- (direction == DragController.SCROLL_LEFT ? -1 : 1);
- // We always want to exit the current layout to ensure parity of enter / exit
- setCurrentDropLayout(null);
-
- if (0 <= page && page < getChildCount()) {
- // Ensure that we are not dragging over to the custom content screen
- if (getScreenIdForPageIndex(page) == CUSTOM_CONTENT_SCREEN_ID) {
- return false;
- }
-
- CellLayout layout = (CellLayout) getChildAt(page);
- setCurrentDragOverlappingLayout(layout);
-
- // Workspace is responsible for drawing the edge glow on adjacent pages,
- // so we need to redraw the workspace when this may have changed.
- invalidate();
- result = true;
- }
- }
- return result;
- }
-
- @Override
- public boolean onExitScrollArea() {
- boolean result = false;
- if (mInScrollArea) {
- invalidate();
- CellLayout layout = getCurrentDropLayout();
- setCurrentDropLayout(layout);
- setCurrentDragOverlappingLayout(layout);
-
- result = true;
- mInScrollArea = false;
- }
- return result;
- }
-
- private void onResetScrollArea() {
- setCurrentDragOverlappingLayout(null);
- mInScrollArea = false;
- }
-
/**
* Returns a specific CellLayout
*/
@@ -4003,63 +3860,36 @@
for (final CellLayout layoutParent: cellLayouts) {
final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
- final HashMap<ItemInfo, View> children = new HashMap<>();
+ LongArrayMap<View> idToViewMap = new LongArrayMap<>();
+ ArrayList<ItemInfo> items = new ArrayList<>();
for (int j = 0; j < layout.getChildCount(); j++) {
final View view = layout.getChildAt(j);
- children.put((ItemInfo) view.getTag(), view);
+ if (view.getTag() instanceof ItemInfo) {
+ ItemInfo item = (ItemInfo) view.getTag();
+ items.add(item);
+ idToViewMap.put(item.id, view);
+ }
}
- final ArrayList<View> childrenToRemove = new ArrayList<>();
- final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove = new HashMap<>();
- LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
- @Override
- public boolean filterItem(ItemInfo parent, ItemInfo info,
- ComponentName cn) {
- if (parent instanceof FolderInfo) {
- if (matcher.matches(info, cn)) {
- FolderInfo folder = (FolderInfo) parent;
- ArrayList<ShortcutInfo> appsToRemove;
- if (folderAppsToRemove.containsKey(folder)) {
- appsToRemove = folderAppsToRemove.get(folder);
- } else {
- appsToRemove = new ArrayList<ShortcutInfo>();
- folderAppsToRemove.put(folder, appsToRemove);
- }
- appsToRemove.add((ShortcutInfo) info);
- return true;
- }
- } else {
- if (matcher.matches(info, cn)) {
- childrenToRemove.add(children.get(info));
- return true;
- }
+ for (ItemInfo itemToRemove : matcher.filterItemInfos(items)) {
+ View child = idToViewMap.get(itemToRemove.id);
+
+ if (child != null) {
+ // Note: We can not remove the view directly from CellLayoutChildren as this
+ // does not re-mark the spaces as unoccupied.
+ layoutParent.removeViewInLayout(child);
+ if (child instanceof DropTarget) {
+ mDragController.removeDropTarget((DropTarget) child);
}
- return false;
+ } else if (itemToRemove.container >= 0) {
+ // The item may belong to a folder.
+ View parent = idToViewMap.get(itemToRemove.container);
+ if (parent != null) {
+ FolderInfo folderInfo = (FolderInfo) parent.getTag();
+ folderInfo.prepareAutoUpdate();
+ folderInfo.remove((ShortcutInfo) itemToRemove, false);
+ }
}
- };
- LauncherModel.filterItemInfos(children.keySet(), filter);
-
- // Remove all the apps from their folders
- for (FolderInfo folder : folderAppsToRemove.keySet()) {
- ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder);
- for (ShortcutInfo info : appsToRemove) {
- folder.remove(info, false);
- }
- }
-
- // Remove all the other children
- for (View child : childrenToRemove) {
- // Note: We can not remove the view directly from CellLayoutChildren as this
- // does not re-mark the spaces as unoccupied.
- layoutParent.removeViewInLayout(child);
- if (child instanceof DropTarget) {
- mDragController.removeDropTarget((DropTarget) child);
- }
- }
-
- if (childrenToRemove.size() > 0) {
- layout.requestLayout();
- layout.invalidate();
}
}
@@ -4136,8 +3966,7 @@
Drawable oldIcon = getTextViewIcon(shortcut);
boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
&& ((PreloadIconDrawable) oldIcon).hasNotCompleted();
- shortcut.applyFromShortcutInfo(si, mIconCache,
- si.isPromise() != oldPromiseState);
+ shortcut.applyFromShortcutInfo(si, si.isPromise() != oldPromiseState);
}
// process all the shortcuts
return false;
@@ -4157,11 +3986,49 @@
});
}
- public void removeAbandonedPromise(String packageName, UserHandleCompat user) {
+ public void updateIconBadges(final Set<PackageUserKey> updatedBadges) {
+ final PackageUserKey packageUserKey = new PackageUserKey(null, null);
+ final HashSet<Long> folderIds = new HashSet<>();
+ mapOverItems(MAP_RECURSE, new ItemOperator() {
+ @Override
+ public boolean evaluate(ItemInfo info, View v) {
+ if (info instanceof ShortcutInfo && v instanceof BubbleTextView
+ && packageUserKey.updateFromItemInfo(info)) {
+ if (updatedBadges.contains(packageUserKey)) {
+ ((BubbleTextView) v).applyBadgeState(info, true /* animate */);
+ folderIds.add(info.container);
+ }
+ }
+ // process all the shortcuts
+ return false;
+ }
+ });
+
+ // Update folder icons
+ mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
+ @Override
+ public boolean evaluate(ItemInfo info, View v) {
+ if (info instanceof FolderInfo && folderIds.contains(info.id)
+ && v instanceof FolderIcon) {
+ FolderBadgeInfo folderBadgeInfo = new FolderBadgeInfo();
+ for (ShortcutInfo si : ((FolderInfo) info).contents) {
+ folderBadgeInfo.addBadgeInfo(mLauncher.getPopupDataProvider()
+ .getBadgeInfoForItem(si));
+ }
+ ((FolderIcon) v).setBadgeInfo(folderBadgeInfo);
+ }
+ // process all the shortcuts
+ return false;
+ }
+ });
+ }
+
+ public void removeAbandonedPromise(String packageName, UserHandle user) {
HashSet<String> packages = new HashSet<>(1);
packages.add(packageName);
- LauncherModel.deletePackageFromDatabase(mLauncher, packageName, user);
- removeItemsByMatcher(ItemInfoMatcher.ofPackages(packages, user));
+ ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packages, user);
+ mLauncher.getModelWriter().deleteItemsFromDatabase(matcher);
+ removeItemsByMatcher(matcher);
}
public void updateRestoreItems(final HashSet<ItemInfo> updates) {
@@ -4170,7 +4037,7 @@
public boolean evaluate(ItemInfo info, View v) {
if (info instanceof ShortcutInfo && v instanceof BubbleTextView
&& updates.contains(info)) {
- ((BubbleTextView) v).applyState(false);
+ ((BubbleTextView) v).applyPromiseState(false /* promiseStateChanged */);
} else if (v instanceof PendingAppWidgetHostView
&& info instanceof LauncherAppWidgetInfo
&& updates.contains(info)) {
@@ -4286,16 +4153,26 @@
}
@Override
- public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
+ public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
target.gridX = info.cellX;
target.gridY = info.cellY;
target.pageIndex = getCurrentPage();
- targetParent.containerType = LauncherLogProto.WORKSPACE;
+ targetParent.containerType = ContainerType.WORKSPACE;
if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
target.rank = info.rank;
- targetParent.containerType = LauncherLogProto.HOTSEAT;
+ targetParent.containerType = ContainerType.HOTSEAT;
} else if (info.container >= 0) {
- targetParent.containerType = LauncherLogProto.FOLDER;
+ targetParent.containerType = ContainerType.FOLDER;
+ }
+ }
+
+ @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;
}
}
@@ -4338,7 +4215,6 @@
@Override
public boolean evaluate(ItemInfo info, View view) {
if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) {
- PendingAppWidgetHostView hostView = (PendingAppWidgetHostView) view;
mLauncher.removeItem(view, info, false /* deleteFromDb */);
mLauncher.bindAppWidget((LauncherAppWidgetInfo) info);
}
@@ -4362,4 +4238,26 @@
public static final boolean isQsbContainerPage(int pageNo) {
return pageNo == 0;
}
+
+ private class StateTransitionListener extends AnimatorListenerAdapter
+ implements AnimatorUpdateListener {
+ @Override
+ public void onAnimationUpdate(ValueAnimator anim) {
+ mTransitionProgress = anim.getAnimatedFraction();
+ }
+
+ @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;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ onEndStateTransition();
+ }
+ }
}
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 598ba74..482a2c9 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -30,12 +30,12 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.DecelerateInterpolator;
+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 java.util.HashMap;
-
/**
* A convenience class to update a view's visibility state after an alpha animation.
*/
@@ -226,7 +226,7 @@
}
public AnimatorSet getAnimationToState(Workspace.State fromState, Workspace.State toState,
- boolean animated, HashMap<View, Integer> layerViews) {
+ boolean animated, AnimationLayerSet layerViews) {
AccessibilityManager am = (AccessibilityManager)
mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
final boolean accessibilityEnabled = am.isEnabled();
@@ -262,8 +262,7 @@
* Starts a transition animation for the workspace.
*/
private void animateWorkspace(final TransitionStates states, final boolean animated,
- final int duration, final HashMap<View, Integer> layerViews,
- final boolean accessibilityEnabled) {
+ final int duration, AnimationLayerSet layerViews, final boolean accessibilityEnabled) {
// Cancel existing workspace animations and create a new animator set if requested
cancelAnimation();
if (animated) {
@@ -339,10 +338,9 @@
if (animated) {
float oldBackgroundAlpha = cl.getBackgroundAlpha();
if (initialAlpha != finalAlpha) {
- LauncherViewPropertyAnimator alphaAnim =
- new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
- alphaAnim.alpha(finalAlpha)
- .setDuration(duration)
+ Animator alphaAnim = ObjectAnimator.ofFloat(
+ cl.getShortcutsAndWidgets(), View.ALPHA, finalAlpha);
+ alphaAnim.setDuration(duration)
.setInterpolator(mZoomInInterpolator);
mStateAnimator.play(alphaAnim);
}
@@ -358,7 +356,8 @@
cl.setShortcutAndWidgetAlpha(finalAlpha);
}
- if (Workspace.isQsbContainerPage(i)) {
+ if (Workspace.isQsbContainerPage(i) &&
+ states.stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
if (animated) {
Animator anim = mWorkspace.mQsbAlphaController
.animateAlphaAtIndex(finalAlpha, Workspace.QSB_ALPHA_INDEX_PAGE_SCROLL);
@@ -374,34 +373,29 @@
final ViewGroup overviewPanel = mLauncher.getOverviewPanel();
- final View qsbContainer = mLauncher.getQsbContainer();
-
Animator qsbAlphaAnimation = mWorkspace.mQsbAlphaController
.animateAlphaAtIndex(finalQsbAlpha, Workspace.QSB_ALPHA_INDEX_STATE_CHANGE);
if (animated) {
- LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(mWorkspace);
- scale.scaleX(mNewScale)
- .scaleY(mNewScale)
- .translationY(finalWorkspaceTranslationY)
- .setDuration(duration)
- .setInterpolator(mZoomInInterpolator);
+ Animator scale = LauncherAnimUtils.ofPropertyValuesHolder(mWorkspace,
+ new PropertyListBuilder().scale(mNewScale)
+ .translationY(finalWorkspaceTranslationY).build())
+ .setDuration(duration);
+ scale.setInterpolator(mZoomInInterpolator);
mStateAnimator.play(scale);
Animator hotseatAlpha = mWorkspace.createHotseatAlphaAnimator(finalHotseatAlpha);
- LauncherViewPropertyAnimator overviewPanelAlpha =
- new LauncherViewPropertyAnimator(overviewPanel).alpha(finalOverviewPanelAlpha);
+ Animator overviewPanelAlpha = ObjectAnimator.ofFloat(
+ overviewPanel, View.ALPHA, finalOverviewPanelAlpha);
overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel,
accessibilityEnabled));
// 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.put(overviewPanel, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
- layerViews.put(qsbContainer, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
- layerViews.put(mLauncher.getHotseat(),
- LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
- layerViews.put(mWorkspace.getPageIndicator(),
- LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
+ layerViews.addView(overviewPanel);
+ layerViews.addView(mLauncher.getQsbContainer());
+ layerViews.addView(mLauncher.getHotseat());
+ layerViews.addView(mWorkspace.getPageIndicator());
if (states.workspaceToOverview) {
hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
@@ -426,6 +420,11 @@
}
@Override
+ public void onAnimationStart(Animator animation) {
+ mWorkspace.getPageIndicator().setShouldAutoHide(!states.stateIsSpringLoaded);
+ }
+
+ @Override
public void onAnimationEnd(Animator animation) {
mStateAnimator = null;
if (canceled) return;
@@ -438,6 +437,7 @@
} else {
overviewPanel.setAlpha(finalOverviewPanelAlpha);
AlphaUpdateListener.updateVisibility(overviewPanel, accessibilityEnabled);
+ mWorkspace.getPageIndicator().setShouldAutoHide(!states.stateIsSpringLoaded);
qsbAlphaAnimation.end();
mWorkspace.createHotseatAlphaAnimator(finalHotseatAlpha).end();
diff --git a/src/com/android/launcher3/accessibility/AccessibileDragListenerAdapter.java b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java
similarity index 93%
rename from src/com/android/launcher3/accessibility/AccessibileDragListenerAdapter.java
rename to src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java
index 62a9a6d..f8df5d7 100644
--- a/src/com/android/launcher3/accessibility/AccessibileDragListenerAdapter.java
+++ b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java
@@ -28,7 +28,7 @@
* Utility listener to enable/disable accessibility drag flags for a ViewGroup
* containing CellLayouts
*/
-public class AccessibileDragListenerAdapter implements DragListener {
+public class AccessibleDragListenerAdapter implements DragListener {
private final ViewGroup mViewGroup;
private final int mDragType;
@@ -38,7 +38,7 @@
* @param dragType either {@link CellLayout#WORKSPACE_ACCESSIBILITY_DRAG} or
* {@link CellLayout#FOLDER_ACCESSIBILITY_DRAG}
*/
- public AccessibileDragListenerAdapter(ViewGroup parent, int dragType) {
+ public AccessibleDragListenerAdapter(ViewGroup parent, int dragType) {
mViewGroup = parent;
mDragType = dragType;
}
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 173aad0..a476650 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -1,11 +1,9 @@
package com.android.launcher3.accessibility;
-import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.appwidget.AppWidgetProviderInfo;
import android.content.DialogInterface;
import android.graphics.Rect;
-import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
@@ -20,11 +18,9 @@
import com.android.launcher3.AppWidgetResizeFrame;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
+import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.DeleteDropTarget;
-import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.folder.Folder;
import com.android.launcher3.FolderInfo;
import com.android.launcher3.InfoDropTarget;
import com.android.launcher3.ItemInfo;
@@ -39,13 +35,12 @@
import com.android.launcher3.UninstallDropTarget;
import com.android.launcher3.Workspace;
import com.android.launcher3.dragndrop.DragController.DragListener;
-import com.android.launcher3.shortcuts.DeepShortcutTextView;
-import com.android.launcher3.shortcuts.DeepShortcutsContainer;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.folder.Folder;
import com.android.launcher3.util.Thunk;
import java.util.ArrayList;
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class LauncherAccessibilityDelegate extends AccessibilityDelegate implements DragListener {
private static final String TAG = "LauncherAccessibilityDelegate";
@@ -57,7 +52,7 @@
protected static final int MOVE = R.id.action_move;
protected static final int MOVE_TO_WORKSPACE = R.id.action_move_to_workspace;
protected static final int RESIZE = R.id.action_resize;
- protected static final int DEEP_SHORTCUTS = R.id.action_deep_shortcuts;
+ public static final int DEEP_SHORTCUTS = R.id.action_deep_shortcuts;
public enum DragType {
ICON,
@@ -100,14 +95,17 @@
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
- addActions(host, info);
+ addSupportedActions(host, info, false);
}
- protected void addActions(View host, AccessibilityNodeInfo info) {
+ public void addSupportedActions(View host, AccessibilityNodeInfo info, boolean fromKeyboard) {
if (!(host.getTag() instanceof ItemInfo)) return;
ItemInfo item = (ItemInfo) host.getTag();
- if (host instanceof BubbleTextView && ((BubbleTextView) host).hasDeepShortcuts()) {
+ // If the request came from keyboard, do not add custom shortcuts as that is already
+ // exposed as a direct shortcut
+ if (!fromKeyboard && host instanceof BubbleTextView
+ && ((BubbleTextView) host).hasDeepShortcuts()) {
info.addAction(mActions.get(DEEP_SHORTCUTS));
}
@@ -117,13 +115,14 @@
if (UninstallDropTarget.supportsDrop(host.getContext(), item)) {
info.addAction(mActions.get(UNINSTALL));
}
- if (InfoDropTarget.supportsDrop(item)) {
+ if (InfoDropTarget.supportsDrop(host.getContext(), item)) {
info.addAction(mActions.get(INFO));
}
- if ((item instanceof ShortcutInfo)
+ // Do not add move actions for keyboard request as this uses virtual nodes.
+ if (!fromKeyboard && ((item instanceof ShortcutInfo)
|| (item instanceof LauncherAppWidgetInfo)
- || (item instanceof FolderInfo)) {
+ || (item instanceof FolderInfo))) {
info.addAction(mActions.get(MOVE));
if (item.container >= 0) {
@@ -169,7 +168,7 @@
public void run() {
if (item instanceof AppInfo) {
ShortcutInfo info = ((AppInfo) item).makeShortcut();
- LauncherModel.addItemToDatabase(mLauncher, info,
+ mLauncher.getModelWriter().addItemToDatabase(info,
LauncherSettings.Favorites.CONTAINER_DESKTOP,
screenId, coordinates[0], coordinates[1]);
@@ -188,14 +187,14 @@
});
return true;
} else if (action == MOVE_TO_WORKSPACE) {
- Folder folder = mLauncher.getWorkspace().getOpenFolder();
- mLauncher.closeFolder(folder, true);
+ Folder folder = Folder.getOpen(mLauncher);
+ folder.close(true);
ShortcutInfo info = (ShortcutInfo) item;
folder.getInfo().remove(info, false);
final int[] coordinates = new int[2];
final long screenId = findSpaceOnWorkspace(item, coordinates);
- LauncherModel.moveItemInDatabase(mLauncher, info,
+ mLauncher.getModelWriter().moveItemInDatabase(info,
LauncherSettings.Favorites.CONTAINER_DESKTOP,
screenId, coordinates[0], coordinates[1]);
@@ -232,7 +231,7 @@
.show();
return true;
} else if (action == DEEP_SHORTCUTS) {
- return DeepShortcutsContainer.showForIcon((BubbleTextView) host) != null;
+ return PopupContainerWithArrow.showForIcon((BubbleTextView) host) != null;
}
return false;
}
@@ -305,7 +304,7 @@
((LauncherAppWidgetHostView) host).updateAppWidgetSize(null,
sizeRange.left, sizeRange.top, sizeRange.right, sizeRange.bottom);
host.requestLayout();
- LauncherModel.updateItemInDatabase(mLauncher, info);
+ mLauncher.getModelWriter().updateItemInDatabase(info);
announceConfirmation(mLauncher.getString(R.string.widget_resized, info.spanX, info.spanY));
}
@@ -369,12 +368,10 @@
mLauncher.getDragLayer().getDescendantRectRelativeToSelf(item, pos);
mLauncher.getDragController().prepareAccessibleDrag(pos.centerX(), pos.centerY());
- Workspace workspace = mLauncher.getWorkspace();
-
- Folder folder = workspace.getOpenFolder();
+ Folder folder = Folder.getOpen(mLauncher);
if (folder != null) {
if (!folder.getItemsInReadingOrder().contains(item)) {
- mLauncher.closeFolder();
+ folder.close(true);
folder = null;
}
}
@@ -386,7 +383,7 @@
if (folder != null) {
folder.startDrag(cellInfo.cell, options);
} else {
- workspace.startDrag(cellInfo, options);
+ mLauncher.getWorkspace().startDrag(cellInfo, options);
}
}
diff --git a/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java
index 385a766..29dd95c 100644
--- a/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java
@@ -44,7 +44,7 @@
Context context = host.getContext();
info.addAction(new AccessibilityAction(OVERVIEW, context.getText(OVERVIEW)));
- if (Utilities.isWallapaperAllowed(context)) {
+ if (Utilities.isWallpaperAllowed(context)) {
info.addAction(new AccessibilityAction(WALLPAPERS, context.getText(WALLPAPERS)));
}
info.addAction(new AccessibilityAction(WIDGETS, context.getText(WIDGETS)));
diff --git a/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
index 5f68f90..edb0b16 100644
--- a/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
@@ -16,21 +16,19 @@
package com.android.launcher3.accessibility;
-import android.annotation.TargetApi;
import android.content.Context;
-import android.os.Build;
import android.os.Bundle;
import android.util.SparseArray;
import android.view.View;
import android.view.View.AccessibilityDelegate;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class OverviewScreenAccessibilityDelegate extends AccessibilityDelegate {
private static final int MOVE_BACKWARD = R.id.action_move_screen_backwards;
diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
index 0baa8f3..b784fe7 100644
--- a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
@@ -19,6 +19,7 @@
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherModel;
@@ -40,8 +41,10 @@
}
@Override
- protected void addActions(View host, AccessibilityNodeInfo info) {
- info.addAction(mActions.get(ADD_TO_WORKSPACE));
+ public void addSupportedActions(View host, AccessibilityNodeInfo info, boolean fromKeyboard) {
+ if ((host.getParent() instanceof DeepShortcutView)) {
+ info.addAction(mActions.get(ADD_TO_WORKSPACE));
+ }
}
@Override
@@ -56,13 +59,13 @@
Runnable onComplete = new Runnable() {
@Override
public void run() {
- LauncherModel.addItemToDatabase(mLauncher, info,
+ mLauncher.getModelWriter().addItemToDatabase(info,
LauncherSettings.Favorites.CONTAINER_DESKTOP,
screenId, coordinates[0], coordinates[1]);
ArrayList<ItemInfo> itemList = new ArrayList<>();
itemList.add(info);
mLauncher.bindItems(itemList, 0, itemList.size(), true);
- mLauncher.closeShortcutsContainer();
+ AbstractFloatingView.closeAllOpenViews(mLauncher);
announceConfirmation(R.string.item_added_to_workspace);
}
};
diff --git a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
index c71307d..9a23aa8 100644
--- a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
+++ b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
@@ -57,7 +57,7 @@
int y = id / mCountX;
LauncherAccessibilityDelegate.DragInfo dragInfo = mDelegate.getDragInfo();
- if (dragInfo.dragType == DragType.WIDGET && mView.isHotseat()) {
+ if (dragInfo.dragType == DragType.WIDGET && !mView.acceptsWidget()) {
return INVALID_POSITION;
}
@@ -161,11 +161,7 @@
View child = mView.getChildAt(x, y);
if (child == null || child == dragInfo.item) {
- if (mView.isHotseat()) {
- return mContext.getString(R.string.move_to_hotseat_position, id + 1);
- } else {
- return mContext.getString(R.string.move_to_empty_cell, y + 1, x + 1);
- }
+ return mView.getItemMoveDescription(x, y);
} else {
return getDescriptionForDropOver(child, mContext);
}
diff --git a/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java b/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java
index cfd07e6..c71bc31 100644
--- a/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java
+++ b/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java
@@ -23,71 +23,73 @@
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-
import android.view.Gravity;
+
import com.android.launcher3.R;
/**
- * A helper class to positon and orient a drawable to be drawn.
- */
-class TransformedImageDrawable {
- private Drawable mImage;
- private float mXPercent;
- private float mYPercent;
- private int mGravity;
- private int mAlpha;
-
- /**
- * @param gravity If one of the Gravity center values, the x and y offset will take the width
- * and height of the image into account to center the image to the offset.
- */
- public TransformedImageDrawable(Resources res, int resourceId, float xPct, float yPct,
- int gravity) {
- mImage = res.getDrawable(resourceId);
- mXPercent = xPct;
- mYPercent = yPct;
- mGravity = gravity;
- }
-
- public void setAlpha(int alpha) {
- mImage.setAlpha(alpha);
- mAlpha = alpha;
- }
-
- public int getAlpha() {
- return mAlpha;
- }
-
- public void updateBounds(Rect bounds) {
- int width = mImage.getIntrinsicWidth();
- int height = mImage.getIntrinsicHeight();
- int left = bounds.left + (int) (mXPercent * bounds.width());
- int top = bounds.top + (int) (mYPercent * bounds.height());
- if ((mGravity & Gravity.CENTER_HORIZONTAL) == Gravity.CENTER_HORIZONTAL) {
- left -= (width / 2);
- }
- if ((mGravity & Gravity.CENTER_VERTICAL) == Gravity.CENTER_VERTICAL) {
- top -= (height / 2);
- }
- mImage.setBounds(left, top, left + width, top + height);
- }
-
- public void draw(Canvas canvas) {
- int c = canvas.save(Canvas.MATRIX_SAVE_FLAG);
- mImage.draw(canvas);
- canvas.restoreToCount(c);
- }
-}
-
-/**
* This is a custom composite drawable that has a fixed virtual size and dynamically lays out its
* children images relatively within its bounds. This way, we can reduce the memory usage of a
* single, large sparsely populated image.
*/
public class AllAppsBackgroundDrawable extends Drawable {
- private final TransformedImageDrawable mHand;
- private final TransformedImageDrawable[] mIcons;
+ /**
+ * A helper class to positon and orient a drawable to be drawn.
+ */
+ protected static class TransformedImageDrawable {
+ private Drawable mImage;
+ private float mXPercent;
+ private float mYPercent;
+ private int mGravity;
+ private int mAlpha;
+
+ /**
+ * @param gravity If one of the Gravity center values, the x and y offset will take the width
+ * and height of the image into account to center the image to the offset.
+ */
+ public TransformedImageDrawable(Resources res, int resourceId, float xPct, float yPct,
+ int gravity) {
+ mImage = res.getDrawable(resourceId);
+ mXPercent = xPct;
+ mYPercent = yPct;
+ mGravity = gravity;
+ }
+
+ public void setAlpha(int alpha) {
+ mImage.setAlpha(alpha);
+ mAlpha = alpha;
+ }
+
+ public int getAlpha() {
+ return mAlpha;
+ }
+
+ public void updateBounds(Rect bounds) {
+ int width = mImage.getIntrinsicWidth();
+ int height = mImage.getIntrinsicHeight();
+ int left = bounds.left + (int) (mXPercent * bounds.width());
+ int top = bounds.top + (int) (mYPercent * bounds.height());
+ if ((mGravity & Gravity.CENTER_HORIZONTAL) == Gravity.CENTER_HORIZONTAL) {
+ left -= (width / 2);
+ }
+ if ((mGravity & Gravity.CENTER_VERTICAL) == Gravity.CENTER_VERTICAL) {
+ top -= (height / 2);
+ }
+ mImage.setBounds(left, top, left + width, top + height);
+ }
+
+ public void draw(Canvas canvas) {
+ mImage.draw(canvas);
+ }
+
+ public Rect getBounds() {
+ return mImage.getBounds();
+ }
+ }
+
+ protected final TransformedImageDrawable mHand;
+ protected final TransformedImageDrawable[] mIcons;
private final int mWidth;
private final int mHeight;
@@ -188,7 +190,6 @@
private ObjectAnimator cancelAnimator(ObjectAnimator animator) {
if (animator != null) {
- animator.removeAllListeners();
animator.cancel();
}
return null;
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 77ef642..2c7d156 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -15,11 +15,13 @@
*/
package com.android.launcher3.allapps;
-import android.annotation.SuppressLint;
import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Point;
+import android.graphics.Color;
import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.InsetDrawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.text.Selection;
import android.text.Spannable;
@@ -31,139 +33,60 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewConfiguration;
import android.view.ViewGroup;
import com.android.launcher3.AppInfo;
import com.android.launcher3.BaseContainerView;
import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.CellLayout;
import com.android.launcher3.DeleteDropTarget;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
import com.android.launcher3.ExtendedEditText;
+import com.android.launcher3.Insettable;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherTransitionable;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.discovery.AppDiscoveryItem;
+import com.android.launcher3.discovery.AppDiscoveryUpdateState;
+import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.graphics.TintedDrawableSpan;
import com.android.launcher3.keyboard.FocusedItemDecorator;
-import com.android.launcher3.shortcuts.DeepShortcutsContainer;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.PackageUserKey;
-import java.nio.charset.Charset;
-import java.nio.charset.CharsetEncoder;
import java.util.ArrayList;
import java.util.List;
-
-
-/**
- * A merge algorithm that merges every section indiscriminately.
- */
-final class FullMergeAlgorithm implements AlphabeticalAppsList.MergeAlgorithm {
-
- @Override
- public boolean continueMerging(AlphabeticalAppsList.SectionInfo section,
- AlphabeticalAppsList.SectionInfo withSection,
- int sectionAppCount, int numAppsPerRow, int mergeCount) {
- // Don't merge the predicted apps
- if (section.firstAppItem.viewType != AllAppsGridAdapter.VIEW_TYPE_ICON) {
- return false;
- }
- // Otherwise, merge every other section
- return true;
- }
-}
-
-/**
- * The logic we use to merge multiple sections. We only merge sections when their final row
- * contains less than a certain number of icons, and stop at a specified max number of merges.
- * In addition, we will try and not merge sections that identify apps from different scripts.
- */
-final class SimpleSectionMergeAlgorithm implements AlphabeticalAppsList.MergeAlgorithm {
-
- private int mMinAppsPerRow;
- private int mMinRowsInMergedSection;
- private int mMaxAllowableMerges;
- private CharsetEncoder mAsciiEncoder;
-
- public SimpleSectionMergeAlgorithm(int minAppsPerRow, int minRowsInMergedSection, int maxNumMerges) {
- mMinAppsPerRow = minAppsPerRow;
- mMinRowsInMergedSection = minRowsInMergedSection;
- mMaxAllowableMerges = maxNumMerges;
- mAsciiEncoder = Charset.forName("US-ASCII").newEncoder();
- }
-
- @Override
- public boolean continueMerging(AlphabeticalAppsList.SectionInfo section,
- AlphabeticalAppsList.SectionInfo withSection,
- int sectionAppCount, int numAppsPerRow, int mergeCount) {
- // Don't merge the predicted apps
- if (section.firstAppItem.viewType != AllAppsGridAdapter.VIEW_TYPE_ICON) {
- return false;
- }
-
- // Continue merging if the number of hanging apps on the final row is less than some
- // fixed number (ragged), the merged rows has yet to exceed some minimum row count,
- // and while the number of merged sections is less than some fixed number of merges
- int rows = sectionAppCount / numAppsPerRow;
- int cols = sectionAppCount % numAppsPerRow;
-
- // Ensure that we do not merge across scripts, currently we only allow for english and
- // native scripts so we can test if both can just be ascii encoded
- boolean isCrossScript = false;
- if (section.firstAppItem != null && withSection.firstAppItem != null) {
- isCrossScript = mAsciiEncoder.canEncode(section.firstAppItem.sectionName) !=
- mAsciiEncoder.canEncode(withSection.firstAppItem.sectionName);
- }
- return (0 < cols && cols < mMinAppsPerRow) &&
- rows < mMinRowsInMergedSection &&
- mergeCount < mMaxAllowableMerges &&
- !isCrossScript;
- }
-}
+import java.util.Set;
/**
* The all apps view container.
*/
public class AllAppsContainerView extends BaseContainerView implements DragSource,
- LauncherTransitionable, View.OnLongClickListener, AllAppsSearchBarController.Callbacks {
-
- private static final int MIN_ROWS_IN_MERGED_SECTION_PHONE = 3;
- private static final int MAX_NUM_MERGES_PHONE = 2;
+ View.OnLongClickListener, AllAppsSearchBarController.Callbacks, Insettable {
private final Launcher mLauncher;
private final AlphabeticalAppsList mApps;
private final AllAppsGridAdapter mAdapter;
private final RecyclerView.LayoutManager mLayoutManager;
- private final RecyclerView.ItemDecoration mItemDecoration;
-
- // The computed bounds of the container
- private final Rect mContentBounds = new Rect();
private AllAppsRecyclerView mAppsRecyclerView;
private AllAppsSearchBarController mSearchBarController;
private View mSearchContainer;
+ private int mSearchContainerMinHeight;
private ExtendedEditText mSearchInput;
private HeaderElevationController mElevationController;
- private int mSearchContainerOffsetTop;
private SpannableStringBuilder mSearchQueryBuilder = null;
- private int mSectionNamesMargin;
private int mNumAppsPerRow;
private int mNumPredictedAppsPerRow;
- private int mRecyclerViewBottomPadding;
- // This coordinate is relative to this container view
- private final Point mBoundsCheckLastTouchDownPos = new Point(-1, -1);
public AllAppsContainerView(Context context) {
this(context, null);
@@ -175,27 +98,37 @@
public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- Resources res = context.getResources();
mLauncher = Launcher.getLauncher(context);
- mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
mApps = new AlphabeticalAppsList(context);
mAdapter = new AllAppsGridAdapter(mLauncher, mApps, mLauncher, this);
mApps.setAdapter(mAdapter);
mLayoutManager = mAdapter.getLayoutManager();
- mItemDecoration = mAdapter.getItemDecoration();
- DeviceProfile grid = mLauncher.getDeviceProfile();
- if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && !grid.isVerticalBarLayout()) {
- mRecyclerViewBottomPadding = 0;
- setPadding(0, 0, 0, 0);
- } else {
- mRecyclerViewBottomPadding =
- res.getDimensionPixelSize(R.dimen.all_apps_list_bottom_padding);
- }
mSearchQueryBuilder = new SpannableStringBuilder();
+ mSearchContainerMinHeight
+ = getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_height);
+
Selection.setSelection(mSearchQueryBuilder, 0);
}
+ @Override
+ protected void updateBackground(
+ int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) {
+ if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
+ 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);
+ }
+ } else {
+ super.updateBackground(paddingLeft, paddingTop, paddingRight, paddingBottom);
+ }
+ }
+
/**
* Sets the current set of predicted apps.
*/
@@ -268,7 +201,7 @@
int[] point = new int[2];
point[0] = (int) ev.getX();
point[1] = (int) ev.getY();
- Utilities.mapCoordInSelfToDescendent(mAppsRecyclerView, this, point);
+ Utilities.mapCoordInSelfToDescendant(mAppsRecyclerView, this, point);
// IF the MotionEvent is inside the search box, and the container keeps on receiving
// touch input, container should move down.
@@ -281,14 +214,9 @@
return false;
}
- // IF a shortcuts container is open, container should not be pulled down.
- if (mLauncher.getOpenShortcutsContainer() != null) {
- 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.getScrollBar().getThumbOffset().y <= 0) {
+ if (mAppsRecyclerView.getCurrentScrollY() == 0) {
return true;
}
return false;
@@ -340,12 +268,7 @@
0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
mSearchInput.setHint(spanned);
- mSearchContainerOffsetTop = getResources().getDimensionPixelSize(
- R.dimen.all_apps_search_bar_margin_top);
-
- mElevationController = Utilities.ATLEAST_LOLLIPOP
- ? new HeaderElevationController.ControllerVL(mSearchContainer)
- : new HeaderElevationController.ControllerV16(mSearchContainer);
+ mElevationController = new HeaderElevationController(mSearchContainer);
// Load the all apps recycler view
mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view);
@@ -356,10 +279,6 @@
mAppsRecyclerView.addOnScrollListener(mElevationController);
mAppsRecyclerView.setElevationController(mElevationController);
- if (mItemDecoration != null) {
- mAppsRecyclerView.addItemDecoration(mItemDecoration);
- }
-
FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(mAppsRecyclerView);
mAppsRecyclerView.addItemDecoration(focusedItemDecorator);
mAppsRecyclerView.preMeasureViews(mAdapter);
@@ -373,15 +292,15 @@
}
@Override
+ public View getTouchDelegateTargetView() {
+ return mAppsRecyclerView;
+ }
+
+ @Override
public void onBoundsChanged(Rect newBounds) { }
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int widthPx = MeasureSpec.getSize(widthMeasureSpec);
- int heightPx = MeasureSpec.getSize(heightMeasureSpec);
- updatePaddingsAndMargins(widthPx, heightPx);
- mContentBounds.set(mContainerPaddingLeft, 0, widthPx - mContainerPaddingRight, heightPx);
-
DeviceProfile grid = mLauncher.getDeviceProfile();
grid.updateAppsViewNumCols();
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
@@ -392,18 +311,15 @@
mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
mAdapter.setNumAppsPerRow(mNumAppsPerRow);
- mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, new FullMergeAlgorithm());
- if (mNumAppsPerRow > 0) {
- int rvPadding = mAppsRecyclerView.getPaddingStart(); // Assumes symmetry
- final int thumbMaxWidth =
- getResources().getDimensionPixelSize(
- R.dimen.container_fastscroll_thumb_max_width);
- mSearchContainer.setPadding(
- rvPadding - mContainerPaddingLeft + thumbMaxWidth,
- mSearchContainer.getPaddingTop(),
- rvPadding - mContainerPaddingRight + thumbMaxWidth,
- mSearchContainer.getPaddingBottom());
- }
+ mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
+ }
+ if (!grid.isVerticalBarLayout()) {
+ MarginLayoutParams searchContainerLp =
+ (MarginLayoutParams) mSearchContainer.getLayoutParams();
+
+ searchContainerLp.height = mLauncher.getDragLayer().getInsets().top
+ + mSearchContainerMinHeight;
+ mSearchContainer.setLayoutParams(searchContainerLp);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
@@ -412,98 +328,21 @@
// --- remove START when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
// Update the number of items in the grid before we measure the view
- // TODO: mSectionNamesMargin is currently 0, but also account for it,
- // if it's enabled in the future.
grid.updateAppsViewNumCols();
if (mNumAppsPerRow != grid.allAppsNumCols ||
mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) {
mNumAppsPerRow = grid.allAppsNumCols;
mNumPredictedAppsPerRow = grid.allAppsNumPredictiveCols;
- // If there is a start margin to draw section names, determine how we are going to merge
- // app sections
- boolean mergeSectionsFully = mSectionNamesMargin == 0 || !grid.isPhone;
- AlphabeticalAppsList.MergeAlgorithm mergeAlgorithm = mergeSectionsFully ?
- new FullMergeAlgorithm() :
- new SimpleSectionMergeAlgorithm((int) Math.ceil(mNumAppsPerRow / 2f),
- MIN_ROWS_IN_MERGED_SECTION_PHONE, MAX_NUM_MERGES_PHONE);
-
mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
mAdapter.setNumAppsPerRow(mNumAppsPerRow);
- mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, mergeAlgorithm);
+ mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
}
// --- remove END when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
- /**
- * Update the background and padding of the Apps view and children. Instead of insetting the
- * container view, we inset the background and padding of the recycler view to allow for the
- * recycler view to handle touch events (for fast scrolling) all the way to the edge.
- */
- private void updatePaddingsAndMargins(int widthPx, int heightPx) {
- Rect bgPadding = new Rect();
- getRevealView().getBackground().getPadding(bgPadding);
-
- mAppsRecyclerView.updateBackgroundPadding(bgPadding);
- mAdapter.updateBackgroundPadding(bgPadding);
- mElevationController.updateBackgroundPadding(bgPadding);
-
- // Pad the recycler view by the background padding plus the start margin (for the section
- // names)
- int maxScrollBarWidth = mAppsRecyclerView.getMaxScrollbarWidth();
- int startInset = Math.max(mSectionNamesMargin, maxScrollBarWidth);
- if (Utilities.isRtl(getResources())) {
- mAppsRecyclerView.setPadding(bgPadding.left + maxScrollBarWidth, 0, bgPadding.right
- + startInset, mRecyclerViewBottomPadding);
- } else {
- mAppsRecyclerView.setPadding(bgPadding.left + startInset, 0, bgPadding.right +
- maxScrollBarWidth, mRecyclerViewBottomPadding);
- }
-
- MarginLayoutParams lp = (MarginLayoutParams) mSearchContainer.getLayoutParams();
- lp.leftMargin = bgPadding.left;
- lp.rightMargin = bgPadding.right;
-
- // Clip the view to the left and right edge of the background to
- // to prevent shadows from rendering beyond the edges
- final Rect newClipBounds = new Rect(
- bgPadding.left, 0, widthPx - bgPadding.right, heightPx);
- setClipBounds(newClipBounds);
-
- // Allow the overscroll effect to reach the edges of the view
- mAppsRecyclerView.setClipToPadding(false);
-
- DeviceProfile grid = mLauncher.getDeviceProfile();
- if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
- if (!grid.isVerticalBarLayout()) {
- MarginLayoutParams mlp = (MarginLayoutParams) mAppsRecyclerView.getLayoutParams();
-
- Rect insets = mLauncher.getDragLayer().getInsets();
- getContentView().setPadding(0, 0, 0, 0);
- int height = insets.top + grid.hotseatCellHeightPx;
-
- mlp.topMargin = height;
- mAppsRecyclerView.setLayoutParams(mlp);
-
- mSearchContainer.setPadding(
- mSearchContainer.getPaddingLeft(),
- insets.top + mSearchContainerOffsetTop,
- mSearchContainer.getPaddingRight(),
- mSearchContainer.getPaddingBottom());
- lp.height = height;
-
- View navBarBg = findViewById(R.id.nav_bar_bg);
- ViewGroup.LayoutParams params = navBarBg.getLayoutParams();
- params.height = insets.bottom;
- navBarBg.setLayoutParams(params);
- navBarBg.setVisibility(View.VISIBLE);
- }
- }
- mSearchContainer.setLayoutParams(lp);
- }
-
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
// Determine if the key event was actual text, if so, focus the search bar and then dispatch
@@ -526,18 +365,7 @@
}
@Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- return handleTouchEvent(ev);
- }
-
- @SuppressLint("ClickableViewAccessibility")
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- return handleTouchEvent(ev);
- }
-
- @Override
- public boolean onLongClick(View v) {
+ public boolean onLongClick(final View v) {
// Return early if this is not initiated from a touch
if (!v.isInTouchMode()) return false;
// When we have exited all apps or are in transition, disregard long clicks
@@ -549,37 +377,24 @@
if (mLauncher.getDragController().isDragging()) return false;
// Start the drag
- DragOptions dragOptions = new DragOptions();
- if (v instanceof BubbleTextView) {
- final BubbleTextView icon = (BubbleTextView) v;
- if (icon.hasDeepShortcuts()) {
- DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon(icon);
- if (dsc != null) {
- dragOptions.deferDragCondition = dsc.createDeferDragCondition(new Runnable() {
- @Override
- public void run() {
- icon.setVisibility(VISIBLE);
- }
- });
- }
+ final DragController dragController = mLauncher.getDragController();
+ dragController.addDragListener(new DragController.DragListener() {
+ @Override
+ public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+ v.setVisibility(INVISIBLE);
}
- }
- mLauncher.getWorkspace().beginDragShared(v, this, dragOptions);
- if (FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) {
- // Enter spring loaded mode (the new workspace does this in
- // onDragStart(), so we don't want to do it here)
- mLauncher.enterSpringLoadedDragMode();
- }
+ @Override
+ public void onDragEnd() {
+ v.setVisibility(VISIBLE);
+ dragController.removeDragListener(this);
+ }
+ });
+ mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions());
return false;
}
@Override
- public boolean supportsFlingToDelete() {
- return true;
- }
-
- @Override
public boolean supportsAppInfoDropTarget() {
return true;
}
@@ -596,14 +411,6 @@
}
@Override
- public void onFlingToDeleteCompleted() {
- // We just dismiss the drag when we fling, so cleanup here
- mLauncher.exitSpringLoadedDragModeDelayed(true,
- Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
- mLauncher.unlockScreenOrientation(false);
- }
-
- @Override
public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
boolean success) {
if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
@@ -615,111 +422,30 @@
}
mLauncher.unlockScreenOrientation(false);
- // Display an error message if the drag failed due to there not being enough space on the
- // target layout we were dropping on.
if (!success) {
- boolean showOutOfSpaceMessage = false;
- if (target instanceof Workspace && !mLauncher.getDragController().isDeferringDrag()) {
- int currentScreen = mLauncher.getCurrentWorkspaceScreen();
- Workspace workspace = (Workspace) target;
- CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
- ItemInfo itemInfo = d.dragInfo;
- if (layout != null) {
- showOutOfSpaceMessage =
- !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
- }
- }
- if (showOutOfSpaceMessage) {
- mLauncher.showOutOfSpaceMessage(false);
- }
-
d.deferDragViewCleanupPostAnimation = false;
}
}
@Override
- public void onLauncherTransitionPrepare(Launcher l, boolean animated,
- boolean multiplePagesVisible) {
- // Do nothing
- }
-
- @Override
- public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
- // Do nothing
- }
-
- @Override
- public void onLauncherTransitionStep(Launcher l, float t) {
- // Do nothing
- }
-
- @Override
- public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
- if (toWorkspace) {
- reset();
- }
- }
-
- /**
- * Handles the touch events to dismiss all apps when clicking outside the bounds of the
- * recycler view.
- */
- private boolean handleTouchEvent(MotionEvent ev) {
- DeviceProfile grid = mLauncher.getDeviceProfile();
- int x = (int) ev.getX();
- int y = (int) ev.getY();
-
- switch (ev.getAction()) {
- case MotionEvent.ACTION_DOWN:
- if (!mContentBounds.isEmpty()) {
- // Outset the fixed bounds and check if the touch is outside all apps
- Rect tmpRect = new Rect(mContentBounds);
- tmpRect.inset(-grid.allAppsIconSizePx / 2, 0);
- if (ev.getX() < tmpRect.left || ev.getX() > tmpRect.right) {
- mBoundsCheckLastTouchDownPos.set(x, y);
- return true;
- }
- } else {
- // Check if the touch is outside all apps
- if (ev.getX() < getPaddingLeft() ||
- ev.getX() > (getWidth() - getPaddingRight())) {
- mBoundsCheckLastTouchDownPos.set(x, y);
- return true;
- }
- }
- break;
- case MotionEvent.ACTION_UP:
- if (mBoundsCheckLastTouchDownPos.x > -1) {
- ViewConfiguration viewConfig = ViewConfiguration.get(getContext());
- float dx = ev.getX() - mBoundsCheckLastTouchDownPos.x;
- float dy = ev.getY() - mBoundsCheckLastTouchDownPos.y;
- float distance = (float) Math.hypot(dx, dy);
- if (distance < viewConfig.getScaledTouchSlop()) {
- // The background was clicked, so just go home
- Launcher launcher = Launcher.getLauncher(getContext());
- launcher.showWorkspace(true);
- return true;
- }
- }
- // Fall through
- case MotionEvent.ACTION_CANCEL:
- mBoundsCheckLastTouchDownPos.set(-1, -1);
- break;
- }
- return false;
- }
-
- @Override
public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
if (apps != null) {
- if (mApps.setOrderedFilter(apps)) {
- mAppsRecyclerView.onSearchResultsChanged();
- }
+ mApps.setOrderedFilter(apps);
+ mAppsRecyclerView.onSearchResultsChanged();
mAdapter.setLastSearchQuery(query);
}
}
@Override
+ public void onAppDiscoverySearchUpdate(@Nullable AppDiscoveryItem app,
+ @NonNull AppDiscoveryUpdateState state) {
+ if (!mLauncher.isDestroyed()) {
+ mApps.onAppDiscoverySearchUpdate(app, state);
+ mAppsRecyclerView.onSearchResultsChanged();
+ }
+ }
+
+ @Override
public void clearSearchResult() {
if (mApps.setOrderedFilter(null)) {
mAppsRecyclerView.onSearchResultsChanged();
@@ -732,11 +458,44 @@
}
@Override
- public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
+ public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
targetParent.containerType = mAppsRecyclerView.getContainerType(v);
}
public boolean shouldRestoreImeState() {
return !TextUtils.isEmpty(mSearchInput.getText());
}
+
+ @Override
+ public void setInsets(Rect insets) {
+ DeviceProfile grid = mLauncher.getDeviceProfile();
+ if (grid.isVerticalBarLayout()) {
+ ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
+ mlp.leftMargin = insets.left;
+ mlp.topMargin = insets.top;
+ mlp.rightMargin = insets.right;
+ setLayoutParams(mlp);
+ } else {
+ View navBarBg = findViewById(R.id.nav_bar_bg);
+ ViewGroup.LayoutParams navBarBgLp = navBarBg.getLayoutParams();
+ navBarBgLp.height = insets.bottom;
+ navBarBg.setLayoutParams(navBarBgLp);
+ navBarBg.setVisibility(View.VISIBLE);
+ }
+ }
+
+ public void updateIconBadges(Set<PackageUserKey> updatedBadges) {
+ final PackageUserKey packageUserKey = new PackageUserKey(null, null);
+ final int n = mAppsRecyclerView.getChildCount();
+ for (int i = 0; i < n; i++) {
+ View child = mAppsRecyclerView.getChildAt(i);
+ if (!(child instanceof BubbleTextView) || !(child.getTag() instanceof ItemInfo)) {
+ continue;
+ }
+ ItemInfo info = (ItemInfo) child.getTag();
+ if (packageUserKey.updateFromItemInfo(info) && updatedBadges.contains(packageUserKey)) {
+ ((BubbleTextView) child).applyBadgeState(info, true /* animate */);
+ }
+ }
+ }
}
diff --git a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
index 76934af..a1ff822 100644
--- a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
+++ b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
@@ -19,6 +19,7 @@
import android.view.View;
import com.android.launcher3.BaseRecyclerViewFastScrollBar;
+import com.android.launcher3.BubbleTextView;
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.util.Thunk;
@@ -29,12 +30,11 @@
private static final int INITIAL_TOUCH_SETTLING_DURATION = 100;
private static final int REPEAT_TOUCH_SETTLING_DURATION = 200;
- private static final float FAST_SCROLL_TOUCH_VELOCITY_BARRIER = 1900f;
private AllAppsRecyclerView mRv;
private AlphabeticalAppsList mApps;
- // Keeps track of the current and targetted fast scroll section (the section to scroll to after
+ // Keeps track of the current and targeted fast scroll section (the section to scroll to after
// the initial delay)
int mTargetFastScrollPosition = -1;
@Thunk String mCurrentFastScrollSection;
@@ -46,8 +46,7 @@
// Set of all views animated during fast scroll. We keep track of these ourselves since there
// is no way to reset a view once it gets scrapped or recycled without other hacks
- private HashSet<BaseRecyclerViewFastScrollBar.FastScrollFocusableView> mTrackedFastScrollViews =
- new HashSet<>();
+ private HashSet<RecyclerView.ViewHolder> mTrackedFastScrollViews = new HashSet<>();
// Smooth fast-scroll animation frames
@Thunk int mFastScrollFrameIndex;
@@ -187,12 +186,7 @@
public void onBindView(AllAppsGridAdapter.ViewHolder holder) {
// Update newly bound views to the current fast scroll state if we are fast scrolling
if (mCurrentFastScrollSection != null || mTargetFastScrollSection != null) {
- if (holder.mContent instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
- BaseRecyclerViewFastScrollBar.FastScrollFocusableView v =
- (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) holder.mContent;
- updateViewFastScrollFocusState(v, holder.getPosition(), false /* animated */);
- mTrackedFastScrollViews.add(v);
- }
+ mTrackedFastScrollViews.add(holder);
}
}
@@ -202,9 +196,9 @@
private void trackAllChildViews() {
int childCount = mRv.getChildCount();
for (int i = 0; i < childCount; i++) {
- View v = mRv.getChildAt(i);
- if (v instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
- mTrackedFastScrollViews.add((BaseRecyclerViewFastScrollBar.FastScrollFocusableView) v);
+ RecyclerView.ViewHolder viewHolder = mRv.getChildViewHolder(mRv.getChildAt(i));
+ if (viewHolder != null) {
+ mTrackedFastScrollViews.add(viewHolder);
}
}
}
@@ -213,27 +207,16 @@
* Updates the fast scroll focus on all the children.
*/
private void updateTrackedViewsFastScrollFocusState() {
- for (BaseRecyclerViewFastScrollBar.FastScrollFocusableView v : mTrackedFastScrollViews) {
- RecyclerView.ViewHolder viewHolder = mRv.getChildViewHolder((View) v);
- int pos = (viewHolder != null) ? viewHolder.getPosition() : -1;
- updateViewFastScrollFocusState(v, pos, true);
+ for (RecyclerView.ViewHolder viewHolder : mTrackedFastScrollViews) {
+ int pos = viewHolder.getAdapterPosition();
+ boolean isActive = false;
+ if (mCurrentFastScrollSection != null && pos > -1) {
+ AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(pos);
+ isActive = item != null &&
+ mCurrentFastScrollSection.equals(item.sectionName) &&
+ item.position == mTargetFastScrollPosition;
+ }
+ viewHolder.itemView.setActivated(isActive);
}
}
-
- /**
- * Updates the fast scroll focus on all a given view.
- */
- private void updateViewFastScrollFocusState(BaseRecyclerViewFastScrollBar.FastScrollFocusableView v,
- int pos, boolean animated) {
- FastBitmapDrawable.State newState = FastBitmapDrawable.State.NORMAL;
- if (mCurrentFastScrollSection != null && pos > -1) {
- AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(pos);
- boolean highlight = item.sectionName.equals(mCurrentFastScrollSection) &&
- item.position == mTargetFastScrollPosition;
- newState = highlight ?
- FastBitmapDrawable.State.FAST_SCROLL_HIGHLIGHTED :
- FastBitmapDrawable.State.FAST_SCROLL_UNHIGHLIGHTED;
- }
- v.setFastScrollFocusState(newState, animated);
- }
}
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 7b6aef1..59cac8d 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -18,12 +18,9 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
@@ -36,14 +33,14 @@
import android.view.accessibility.AccessibilityEvent;
import android.widget.TextView;
+import com.android.launcher3.discovery.AppDiscoveryAppInfo;
import com.android.launcher3.AppInfo;
import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
+import com.android.launcher3.discovery.AppDiscoveryItemView;
-import java.util.HashMap;
import java.util.List;
/**
@@ -52,10 +49,7 @@
public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHolder> {
public static final String TAG = "AppsGridAdapter";
- private static final boolean DEBUG = false;
- // A section break in the grid
- public static final int VIEW_TYPE_SECTION_BREAK = 1 << 0;
// A normal icon
public static final int VIEW_TYPE_ICON = 1 << 1;
// A prediction icon
@@ -74,29 +68,29 @@
public static final int VIEW_TYPE_SEARCH_DIVIDER = 1 << 6;
// The divider that separates prediction icons from the app list
public static final int VIEW_TYPE_PREDICTION_DIVIDER = 1 << 7;
+ public static final int VIEW_TYPE_APPS_LOADING_DIVIDER = 1 << 8;
+ public static final int VIEW_TYPE_DISCOVERY_ITEM = 1 << 9;
// Common view type masks
public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_SEARCH_DIVIDER
| VIEW_TYPE_SEARCH_MARKET_DIVIDER
- | VIEW_TYPE_PREDICTION_DIVIDER
- | VIEW_TYPE_SECTION_BREAK;
+ | VIEW_TYPE_PREDICTION_DIVIDER;
public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON
| VIEW_TYPE_PREDICTION_ICON;
+ public static final int VIEW_TYPE_MASK_CONTENT = VIEW_TYPE_MASK_ICON
+ | VIEW_TYPE_DISCOVERY_ITEM;
public interface BindViewCallback {
- public void onBindView(ViewHolder holder);
+ void onBindView(ViewHolder holder);
}
/**
* ViewHolder for each icon.
*/
public static class ViewHolder extends RecyclerView.ViewHolder {
- public View mContent;
-
public ViewHolder(View v) {
super(v);
- mContent = v;
}
}
@@ -118,17 +112,53 @@
final AccessibilityRecordCompat record = AccessibilityEventCompat
.asRecord(event);
record.setItemCount(mApps.getNumFilteredApps());
+ record.setFromIndex(Math.max(0,
+ record.getFromIndex() - getRowsNotForAccessibility(record.getFromIndex())));
+ record.setToIndex(Math.max(0,
+ record.getToIndex() - getRowsNotForAccessibility(record.getToIndex())));
}
@Override
public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
RecyclerView.State state) {
- if (mApps.hasNoFilteredResults()) {
- // Disregard the no-search-results text as a list item for accessibility
- return 0;
- } else {
- return super.getRowCountForAccessibility(recycler, state);
+ return super.getRowCountForAccessibility(recycler, state) -
+ getRowsNotForAccessibility(mApps.getAdapterItems().size() - 1);
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
+ RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
+ super.onInitializeAccessibilityNodeInfoForItem(recycler, state, host, info);
+
+ ViewGroup.LayoutParams lp = host.getLayoutParams();
+ AccessibilityNodeInfoCompat.CollectionItemInfoCompat cic = info.getCollectionItemInfo();
+ if (!(lp instanceof LayoutParams) || (cic == null)) {
+ return;
}
+ LayoutParams glp = (LayoutParams) lp;
+ info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
+ cic.getRowIndex() - getRowsNotForAccessibility(glp.getViewAdapterPosition()),
+ cic.getRowSpan(),
+ cic.getColumnIndex(),
+ cic.getColumnSpan(),
+ cic.isHeading(),
+ cic.isSelected()));
+ }
+
+ /**
+ * Returns the number of rows before {@param adapterPosition}, including this position
+ * which should not be counted towards the collection info.
+ */
+ private int getRowsNotForAccessibility(int adapterPosition) {
+ List<AdapterItem> items = mApps.getAdapterItems();
+ adapterPosition = Math.max(adapterPosition, mApps.getAdapterItems().size() - 1);
+ int extraRows = 0;
+ for (int i = 0; i <= adapterPosition; i++) {
+ if (!isViewType(items.get(i).viewType, VIEW_TYPE_MASK_CONTENT)) {
+ extraRows++;
+ }
+ }
+ return extraRows;
}
@Override
@@ -158,189 +188,14 @@
}
}
- /**
- * Helper class to draw the section headers
- */
- public class GridItemDecoration extends RecyclerView.ItemDecoration {
-
- private static final boolean DEBUG_SECTION_MARGIN = false;
- private static final boolean FADE_OUT_SECTIONS = false;
-
- private HashMap<String, PointF> mCachedSectionBounds = new HashMap<>();
- private Rect mTmpBounds = new Rect();
-
- @Override
- public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
- if (mApps.hasFilter() || mAppsPerRow == 0) {
- return;
- }
-
- if (DEBUG_SECTION_MARGIN) {
- Paint p = new Paint();
- p.setColor(0x33ff0000);
- c.drawRect(mBackgroundPadding.left, 0, mBackgroundPadding.left + mSectionNamesMargin,
- parent.getMeasuredHeight(), p);
- }
-
- List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
- boolean showSectionNames = mSectionNamesMargin > 0;
- int childCount = parent.getChildCount();
- int lastSectionTop = 0;
- int lastSectionHeight = 0;
- for (int i = 0; i < childCount; i++) {
- View child = parent.getChildAt(i);
- ViewHolder holder = (ViewHolder) parent.getChildViewHolder(child);
- if (!isValidHolderAndChild(holder, child, items)) {
- continue;
- }
-
- if (showSectionNames && shouldDrawItemSection(holder, i, items)) {
- // At this point, we only draw sections for each section break;
- int viewTopOffset = (2 * child.getPaddingTop());
- int pos = holder.getPosition();
- AlphabeticalAppsList.AdapterItem item = items.get(pos);
- AlphabeticalAppsList.SectionInfo sectionInfo = item.sectionInfo;
-
- // Draw all the sections for this index
- String lastSectionName = item.sectionName;
- for (int j = item.sectionAppIndex; j < sectionInfo.numApps; j++, pos++) {
- AlphabeticalAppsList.AdapterItem nextItem = items.get(pos);
- String sectionName = nextItem.sectionName;
- if (nextItem.sectionInfo != sectionInfo) {
- break;
- }
- if (j > item.sectionAppIndex && sectionName.equals(lastSectionName)) {
- continue;
- }
-
- // Find the section name bounds
- PointF sectionBounds = getAndCacheSectionBounds(sectionName);
-
- // Calculate where to draw the section
- int sectionBaseline = (int) (viewTopOffset + sectionBounds.y);
- int x = mIsRtl ?
- parent.getWidth() - mBackgroundPadding.left - mSectionNamesMargin :
- mBackgroundPadding.left;
- x += (int) ((mSectionNamesMargin - sectionBounds.x) / 2f);
- int y = child.getTop() + sectionBaseline;
-
- // Determine whether this is the last row with apps in that section, if
- // so, then fix the section to the row allowing it to scroll past the
- // baseline, otherwise, bound it to the baseline so it's in the viewport
- int appIndexInSection = items.get(pos).sectionAppIndex;
- int nextRowPos = Math.min(items.size() - 1,
- pos + mAppsPerRow - (appIndexInSection % mAppsPerRow));
- AlphabeticalAppsList.AdapterItem nextRowItem = items.get(nextRowPos);
- boolean fixedToRow = !sectionName.equals(nextRowItem.sectionName);
- if (!fixedToRow) {
- y = Math.max(sectionBaseline, y);
- }
-
- // In addition, if it overlaps with the last section that was drawn, then
- // offset it so that it does not overlap
- if (lastSectionHeight > 0 && y <= (lastSectionTop + lastSectionHeight)) {
- y += lastSectionTop - y + lastSectionHeight;
- }
-
- // Draw the section header
- if (FADE_OUT_SECTIONS) {
- int alpha = 255;
- if (fixedToRow) {
- alpha = Math.min(255,
- (int) (255 * (Math.max(0, y) / (float) sectionBaseline)));
- }
- mSectionTextPaint.setAlpha(alpha);
- }
- c.drawText(sectionName, x, y, mSectionTextPaint);
-
- lastSectionTop = y;
- lastSectionHeight = (int) (sectionBounds.y + mSectionHeaderOffset);
- lastSectionName = sectionName;
- }
- i += (sectionInfo.numApps - item.sectionAppIndex);
- }
- }
- }
-
- @Override
- public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
- RecyclerView.State state) {
- // Do nothing
- }
-
- /**
- * Given a section name, return the bounds of the given section name.
- */
- private PointF getAndCacheSectionBounds(String sectionName) {
- PointF bounds = mCachedSectionBounds.get(sectionName);
- if (bounds == null) {
- mSectionTextPaint.getTextBounds(sectionName, 0, sectionName.length(), mTmpBounds);
- bounds = new PointF(mSectionTextPaint.measureText(sectionName), mTmpBounds.height());
- mCachedSectionBounds.put(sectionName, bounds);
- }
- return bounds;
- }
-
- /**
- * Returns whether we consider this a valid view holder for us to draw a divider or section for.
- */
- private boolean isValidHolderAndChild(ViewHolder holder, View child,
- List<AlphabeticalAppsList.AdapterItem> items) {
- // Ensure item is not already removed
- GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
- child.getLayoutParams();
- if (lp.isItemRemoved()) {
- return false;
- }
- // Ensure we have a valid holder
- if (holder == null) {
- return false;
- }
- // Ensure we have a holder position
- int pos = holder.getPosition();
- if (pos < 0 || pos >= items.size()) {
- return false;
- }
- return true;
- }
-
- /**
- * Returns whether to draw the section for the given child.
- */
- private boolean shouldDrawItemSection(ViewHolder holder, int childIndex,
- List<AlphabeticalAppsList.AdapterItem> items) {
- int pos = holder.getPosition();
- AlphabeticalAppsList.AdapterItem item = items.get(pos);
-
- // Ensure it's an icon
- if (item.viewType != AllAppsGridAdapter.VIEW_TYPE_ICON) {
- return false;
- }
- // Draw the section header for the first item in each section
- return (childIndex == 0) ||
- (items.get(pos - 1).viewType == AllAppsGridAdapter.VIEW_TYPE_SECTION_BREAK);
- }
- }
-
private final Launcher mLauncher;
private final LayoutInflater mLayoutInflater;
private final AlphabeticalAppsList mApps;
private final GridLayoutManager mGridLayoutMgr;
private final GridSpanSizer mGridSizer;
- private final GridItemDecoration mItemDecoration;
private final View.OnClickListener mIconClickListener;
private final View.OnLongClickListener mIconLongClickListener;
- private final Rect mBackgroundPadding = new Rect();
- private final boolean mIsRtl;
-
- // Section drawing
- @Deprecated
- private final int mSectionNamesMargin;
- @Deprecated
- private final int mSectionHeaderOffset;
- private final Paint mSectionTextPaint;
-
private int mAppsPerRow;
private BindViewCallback mBindViewCallback;
@@ -361,18 +216,9 @@
mGridSizer = new GridSpanSizer();
mGridLayoutMgr = new AppsGridLayoutManager(launcher);
mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
- mItemDecoration = new GridItemDecoration();
mLayoutInflater = LayoutInflater.from(launcher);
mIconClickListener = iconClickListener;
mIconLongClickListener = iconLongClickListener;
- mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
- mSectionHeaderOffset = res.getDimensionPixelSize(R.dimen.all_apps_grid_section_y_offset);
- mIsRtl = Utilities.isRtl(res);
-
- mSectionTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mSectionTextPaint.setTextSize(res.getDimensionPixelSize(
- R.dimen.all_apps_grid_section_text_size));
- mSectionTextPaint.setColor(Utilities.getColorAccent(launcher));
}
public static boolean isDividerViewType(int viewType) {
@@ -421,36 +267,17 @@
}
/**
- * Notifies the adapter of the background padding so that it can draw things correctly in the
- * item decorator.
- */
- public void updateBackgroundPadding(Rect padding) {
- mBackgroundPadding.set(padding);
- }
-
- /**
* Returns the grid layout manager.
*/
public GridLayoutManager getLayoutManager() {
return mGridLayoutMgr;
}
- /**
- * Returns the item decoration for the recycler view.
- */
- public RecyclerView.ItemDecoration getItemDecoration() {
- // We don't draw any headers when we are uncomfortably dense
- return mItemDecoration;
- }
-
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
- case VIEW_TYPE_SECTION_BREAK:
- return new ViewHolder(new View(parent.getContext()));
case VIEW_TYPE_ICON:
- /* falls through */
- case VIEW_TYPE_PREDICTION_ICON: {
+ case VIEW_TYPE_PREDICTION_ICON:
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
R.layout.all_apps_icon, parent, false);
icon.setOnClickListener(mIconClickListener);
@@ -460,14 +287,14 @@
icon.setOnFocusChangeListener(mIconFocusListener);
// Ensure the all apps icon height matches the workspace icons
- DeviceProfile profile = mLauncher.getDeviceProfile();
- Point cellSize = profile.getCellSize();
- GridLayoutManager.LayoutParams lp =
- (GridLayoutManager.LayoutParams) icon.getLayoutParams();
- lp.height = cellSize.y;
- icon.setLayoutParams(lp);
+ icon.getLayoutParams().height = getCellSize().y;
return new ViewHolder(icon);
- }
+ case VIEW_TYPE_DISCOVERY_ITEM:
+ AppDiscoveryItemView appDiscoveryItemView = (AppDiscoveryItemView) mLayoutInflater
+ .inflate(R.layout.all_apps_discovery_item, parent, false);
+ appDiscoveryItemView.init(mIconClickListener, mLauncher.getAccessibilityDelegate(),
+ mIconLongClickListener);
+ return new ViewHolder(appDiscoveryItemView);
case VIEW_TYPE_EMPTY_SEARCH:
return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search,
parent, false));
@@ -484,8 +311,11 @@
case VIEW_TYPE_SEARCH_DIVIDER:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.all_apps_search_divider, parent, false));
+ case VIEW_TYPE_APPS_LOADING_DIVIDER:
+ View loadingDividerView = mLayoutInflater.inflate(
+ R.layout.all_apps_discovery_loading_divider, parent, false);
+ return new ViewHolder(loadingDividerView);
case VIEW_TYPE_PREDICTION_DIVIDER:
- /* falls through */
case VIEW_TYPE_SEARCH_MARKET_DIVIDER:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.all_apps_divider, parent, false));
@@ -494,37 +324,49 @@
}
}
+ private Point getCellSize() {
+ return mLauncher.getDeviceProfile().getCellSize();
+ }
+
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
switch (holder.getItemViewType()) {
- case VIEW_TYPE_ICON: {
+ case VIEW_TYPE_ICON:
+ case VIEW_TYPE_PREDICTION_ICON:
AppInfo info = mApps.getAdapterItems().get(position).appInfo;
- BubbleTextView icon = (BubbleTextView) holder.mContent;
+ BubbleTextView icon = (BubbleTextView) holder.itemView;
icon.applyFromApplicationInfo(info);
icon.setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
break;
- }
- case VIEW_TYPE_PREDICTION_ICON: {
- AppInfo info = mApps.getAdapterItems().get(position).appInfo;
- BubbleTextView icon = (BubbleTextView) holder.mContent;
- icon.applyFromApplicationInfo(info);
- icon.setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
+ case VIEW_TYPE_DISCOVERY_ITEM:
+ AppDiscoveryAppInfo appDiscoveryAppInfo = (AppDiscoveryAppInfo)
+ mApps.getAdapterItems().get(position).appInfo;
+ AppDiscoveryItemView view = (AppDiscoveryItemView) holder.itemView;
+ view.apply(appDiscoveryAppInfo);
break;
- }
case VIEW_TYPE_EMPTY_SEARCH:
- TextView emptyViewText = (TextView) holder.mContent;
+ TextView emptyViewText = (TextView) holder.itemView;
emptyViewText.setText(mEmptySearchMessage);
emptyViewText.setGravity(mApps.hasNoFilteredResults() ? Gravity.CENTER :
Gravity.START | Gravity.CENTER_VERTICAL);
break;
case VIEW_TYPE_SEARCH_MARKET:
- TextView searchView = (TextView) holder.mContent;
+ TextView searchView = (TextView) holder.itemView;
if (mMarketSearchIntent != null) {
searchView.setVisibility(View.VISIBLE);
} else {
searchView.setVisibility(View.GONE);
}
break;
+ case VIEW_TYPE_APPS_LOADING_DIVIDER:
+ int visLoading = mApps.isAppDiscoveryRunning() ? View.VISIBLE : View.GONE;
+ int visLoaded = !mApps.isAppDiscoveryRunning() ? View.VISIBLE : View.GONE;
+ holder.itemView.findViewById(R.id.loadingProgressBar).setVisibility(visLoading);
+ holder.itemView.findViewById(R.id.loadedDivider).setVisibility(visLoaded);
+ break;
+ case VIEW_TYPE_SEARCH_MARKET_DIVIDER:
+ // nothing to do
+ break;
}
if (mBindViewCallback != null) {
mBindViewCallback.onBindView(holder);
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 0173847..64e2fcb 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -22,6 +22,7 @@
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.SparseIntArray;
+import android.view.MotionEvent;
import android.view.View;
import com.android.launcher3.BaseRecyclerView;
@@ -29,7 +30,9 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.DrawableFactory;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import java.util.List;
@@ -101,7 +104,6 @@
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ICON, approxRows * mNumAppsPerRow);
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON, mNumAppsPerRow);
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER, 1);
- pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_SECTION_BREAK, approxRows);
}
/**
@@ -109,47 +111,40 @@
* all the different view types.
*/
public void preMeasureViews(AllAppsGridAdapter adapter) {
+ View icon = adapter.onCreateViewHolder(this, AllAppsGridAdapter.VIEW_TYPE_ICON).itemView;
+ final int iconHeight = icon.getLayoutParams().height;
+ mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, iconHeight);
+ mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON, iconHeight);
+
final int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(
getResources().getDisplayMetrics().widthPixels, View.MeasureSpec.AT_MOST);
final int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(
getResources().getDisplayMetrics().heightPixels, View.MeasureSpec.AT_MOST);
- // Icons
- BubbleTextView icon = (BubbleTextView) adapter.onCreateViewHolder(this,
- AllAppsGridAdapter.VIEW_TYPE_ICON).mContent;
- int iconHeight = icon.getLayoutParams().height;
- mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, iconHeight);
- mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON, iconHeight);
+ putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
+ AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER,
+ AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET_DIVIDER);
+ putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
+ AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER);
+ putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
+ AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET);
+ putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
+ AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH);
- // Search divider
- View searchDivider = adapter.onCreateViewHolder(this,
- AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER).mContent;
- searchDivider.measure(widthMeasureSpec, heightMeasureSpec);
- int searchDividerHeight = searchDivider.getMeasuredHeight();
- mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER, searchDividerHeight);
+ if (FeatureFlags.DISCOVERY_ENABLED) {
+ putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
+ AllAppsGridAdapter.VIEW_TYPE_APPS_LOADING_DIVIDER);
+ putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
+ AllAppsGridAdapter.VIEW_TYPE_DISCOVERY_ITEM);
+ }
+ }
- // Generic dividers
- View divider = adapter.onCreateViewHolder(this,
- AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER).mContent;
- divider.measure(widthMeasureSpec, heightMeasureSpec);
- int dividerHeight = divider.getMeasuredHeight();
- mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER, dividerHeight);
- mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET_DIVIDER, dividerHeight);
-
- // Search views
- View emptySearch = adapter.onCreateViewHolder(this,
- AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH).mContent;
- emptySearch.measure(widthMeasureSpec, heightMeasureSpec);
- mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH,
- emptySearch.getMeasuredHeight());
- View searchMarket = adapter.onCreateViewHolder(this,
- AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET).mContent;
- searchMarket.measure(widthMeasureSpec, heightMeasureSpec);
- mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET,
- searchMarket.getMeasuredHeight());
-
- // Section breaks
- mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SECTION_BREAK, 0);
+ private void putSameHeightFor(AllAppsGridAdapter adapter, int w, int h, int... viewTypes) {
+ View view = adapter.onCreateViewHolder(this, viewTypes[0]).itemView;
+ view.measure(w, h);
+ for (int viewType : viewTypes) {
+ mViewHeights.put(viewType, view.getMeasuredHeight());
+ }
}
/**
@@ -166,27 +161,10 @@
}
}
- /**
- * We need to override the draw to ensure that we don't draw the overscroll effect beyond the
- * background bounds.
- */
- @Override
- protected void dispatchDraw(Canvas canvas) {
- // Clip to ensure that we don't draw the overscroll effect beyond the background bounds
- canvas.clipRect(mBackgroundPadding.left, mBackgroundPadding.top,
- getWidth() - mBackgroundPadding.right,
- getHeight() - mBackgroundPadding.bottom);
- super.dispatchDraw(canvas);
- }
-
@Override
public void onDraw(Canvas c) {
// Draw the background
if (mEmptySearchBackground != null && mEmptySearchBackground.getAlpha() > 0) {
- c.clipRect(mBackgroundPadding.left, mBackgroundPadding.top,
- getWidth() - mBackgroundPadding.right,
- getHeight() - mBackgroundPadding.bottom);
-
mEmptySearchBackground.draw(c);
}
@@ -205,7 +183,7 @@
public int getContainerType(View v) {
if (mApps.hasFilter()) {
- return LauncherLogProto.SEARCHRESULT;
+ return ContainerType.SEARCHRESULT;
} else {
if (v instanceof BubbleTextView) {
BubbleTextView icon = (BubbleTextView) v;
@@ -214,11 +192,11 @@
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
AlphabeticalAppsList.AdapterItem item = items.get(position);
if (item.viewType == AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON) {
- return LauncherLogProto.PREDICTION;
+ return ContainerType.PREDICTION;
}
}
}
- return LauncherLogProto.ALLAPPS;
+ return ContainerType.ALLAPPS;
}
}
@@ -226,9 +204,10 @@
// Always scroll the view to the top so the user can see the changed results
scrollToTop();
- if (mApps.hasNoFilteredResults()) {
+ if (mApps.shouldShowEmptySearch()) {
if (mEmptySearchBackground == null) {
- mEmptySearchBackground = new AllAppsBackgroundDrawable(getContext());
+ mEmptySearchBackground = DrawableFactory.get(getContext())
+ .getAllAppsBackground(getContext());
mEmptySearchBackground.setAlpha(0);
mEmptySearchBackground.setCallback(this);
updateEmptySearchBackgroundBounds();
@@ -241,6 +220,16 @@
}
}
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent e) {
+ boolean result = super.onInterceptTouchEvent(e);
+ if (!result && e.getAction() == MotionEvent.ACTION_DOWN
+ && mEmptySearchBackground != null && mEmptySearchBackground.getAlpha() > 0) {
+ mEmptySearchBackground.setHotspot(e.getX(), e.getY());
+ }
+ return result;
+ }
+
/**
* Maps the touch (from 0..1) to the adapter position that should be visible.
*/
@@ -299,14 +288,14 @@
// Skip early if there are no items or we haven't been measured
if (items.isEmpty() || mNumAppsPerRow == 0) {
- mScrollbar.setThumbOffset(-1, -1);
+ mScrollbar.setThumbOffsetY(-1);
return;
}
// Skip early if, there no child laid out in the container.
int scrollY = getCurrentScrollY();
if (scrollY < 0) {
- mScrollbar.setThumbOffset(-1, -1);
+ mScrollbar.setThumbOffsetY(-1);
return;
}
@@ -314,7 +303,7 @@
int availableScrollBarHeight = getAvailableScrollBarHeight();
int availableScrollHeight = getAvailableScrollHeight();
if (availableScrollHeight <= 0) {
- mScrollbar.setThumbOffset(-1, -1);
+ mScrollbar.setThumbOffsetY(-1);
return;
}
@@ -323,11 +312,10 @@
// Calculate the current scroll position, the scrollY of the recycler view accounts
// for the view padding, while the scrollBarY is drawn right up to the background
// padding (ignoring padding)
- int scrollBarX = getScrollBarX();
- int scrollBarY = mBackgroundPadding.top +
- (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
+ int scrollBarY = (int)
+ (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
- int thumbScrollY = mScrollbar.getThumbOffset().y;
+ int thumbScrollY = mScrollbar.getThumbOffsetY();
int diffScrollY = scrollBarY - thumbScrollY;
if (diffScrollY * dy > 0f) {
// User is scrolling in the same direction the thumb needs to catch up to the
@@ -344,7 +332,7 @@
thumbScrollY += Math.min(offset, diffScrollY);
}
thumbScrollY = Math.max(0, Math.min(availableScrollBarHeight, thumbScrollY));
- mScrollbar.setThumbOffset(scrollBarX, thumbScrollY);
+ mScrollbar.setThumbOffsetY(thumbScrollY);
if (scrollBarY == thumbScrollY) {
mScrollbar.reattachThumbToScroll();
}
@@ -352,7 +340,7 @@
// User is scrolling in an opposite direction to the direction that the thumb
// needs to catch up to the scroll position. Do nothing except for updating
// the scroll bar x to match the thumb width.
- mScrollbar.setThumbOffset(scrollBarX, thumbScrollY);
+ mScrollbar.setThumbOffsetY(thumbScrollY);
}
}
} else {
@@ -416,8 +404,8 @@
}
@Override
- protected int getVisibleHeight() {
- return super.getVisibleHeight()
+ protected int getScrollbarTrackHeight() {
+ return super.getScrollbarTrackHeight()
- Launcher.getLauncher(getContext()).getDragLayer().getInsets().bottom;
}
@@ -429,7 +417,7 @@
protected int getAvailableScrollHeight() {
int paddedHeight = getCurrentScrollY(mApps.getAdapterItems().size(), 0);
int totalHeight = paddedHeight + getPaddingBottom();
- return totalHeight - getVisibleHeight();
+ return totalHeight - getScrollbarTrackHeight();
}
/**
@@ -447,4 +435,5 @@
x + mEmptySearchBackground.getIntrinsicWidth(),
y + mEmptySearchBackground.getIntrinsicHeight());
}
+
}
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
index b5afb2b..6587ad7 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
@@ -21,6 +21,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
+import android.widget.RelativeLayout;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
@@ -33,7 +34,7 @@
* A container for RecyclerView to allow for the click shadow view to be shown behind an icon that
* is launching.
*/
-public class AllAppsRecyclerViewContainerView extends FrameLayout
+public class AllAppsRecyclerViewContainerView extends RelativeLayout
implements BubbleTextShadowHandler {
private final ClickShadowView mTouchFeedbackView;
diff --git a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
index 365ab31..c7ba3ab 100644
--- a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
@@ -19,6 +19,8 @@
import android.content.Intent;
import android.graphics.Rect;
import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
@@ -32,6 +34,8 @@
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
+import com.android.launcher3.discovery.AppDiscoveryItem;
+import com.android.launcher3.discovery.AppDiscoveryUpdateState;
import com.android.launcher3.util.ComponentKey;
import java.util.ArrayList;
@@ -46,7 +50,7 @@
protected AlphabeticalAppsList mApps;
protected Callbacks mCb;
protected ExtendedEditText mInput;
- private String mQuery;
+ protected String mQuery;
protected DefaultAppSearchAlgorithm mSearchAlgorithm;
protected InputMethodManager mInputMethodManager;
@@ -73,6 +77,14 @@
mInput.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
mSearchAlgorithm = onInitializeSearch();
+
+ onInitialized();
+ }
+
+ /**
+ * You can override this method to perform custom initialization.
+ */
+ protected void onInitialized() {
}
/**
@@ -117,6 +129,7 @@
if (actionId != EditorInfo.IME_ACTION_SEARCH) {
return false;
}
+
// Skip if the query is empty
String query = v.getText().toString();
if (query.isEmpty()) {
@@ -206,5 +219,19 @@
* Called when the search results should be cleared.
*/
void clearSearchResult();
+
+
+ /**
+ * Called when the app discovery is providing an update of search, which can either be
+ * START for starting a new discovery,
+ * UPDATE for providing a new search result, can be called multiple times,
+ * END for indicating the end of results.
+ *
+ * @param app result item if UPDATE, else null
+ * @param app the update state, START, UPDATE or END
+ */
+ void onAppDiscoverySearchUpdate(@Nullable AppDiscoveryItem app,
+ @NonNull AppDiscoveryUpdateState state);
}
+
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index b129bb0..30ed180 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -7,24 +7,24 @@
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.graphics.Color;
-import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.ColorUtils;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
-import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
-import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Hotseat;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
-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.util.Themes;
import com.android.launcher3.util.TouchController;
/**
@@ -46,11 +46,10 @@
private final Interpolator mAccelInterpolator = new AccelerateInterpolator(2f);
private final Interpolator mDecelInterpolator = new DecelerateInterpolator(3f);
private final Interpolator mFastOutSlowInInterpolator = new FastOutSlowInInterpolator();
- private final ScrollInterpolator mScrollInterpolator = new ScrollInterpolator();
+ private final VerticalPullDetector.ScrollInterpolator mScrollInterpolator
+ = new VerticalPullDetector.ScrollInterpolator();
- private static final float ANIMATION_DURATION = 1200;
private static final float PARALLAX_COEFFICIENT = .125f;
- private static final float FAST_FLING_PX_MS = 10;
private static final int SINGLE_FRAME_MS = 16;
private AllAppsContainerView mAppsView;
@@ -84,7 +83,6 @@
private static final float RECATCH_REJECTION_FRACTION = .0875f;
- private int mBezelSwipeUpHeight;
private long mAnimationDuration;
private AnimatorSet mCurrentAnimation;
@@ -100,15 +98,13 @@
mDetector.setListener(this);
mShiftRange = DEFAULT_SHIFT_RANGE;
mProgress = 1f;
- mBezelSwipeUpHeight = l.getResources().getDimensionPixelSize(
- R.dimen.all_apps_bezel_swipe_height);
mEvaluator = new ArgbEvaluator();
- mAllAppsBackgroundColor = ContextCompat.getColor(l, R.color.all_apps_container_color);
+ mAllAppsBackgroundColor = Themes.getAttrColor(l, android.R.attr.colorPrimary);
}
@Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mNoIntercept = false;
if (!mLauncher.isAllAppsVisible() && mLauncher.getWorkspace().workspaceInModalState()) {
@@ -116,7 +112,7 @@
} else if (mLauncher.isAllAppsVisible() &&
!mAppsView.shouldContainerScroll(ev)) {
mNoIntercept = true;
- } else if (!mLauncher.isAllAppsVisible() && !shouldPossiblyIntercept(ev)) {
+ } else if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
mNoIntercept = true;
} else {
// Now figure out which direction scroll events the controller will start
@@ -144,6 +140,7 @@
ignoreSlopWhenSettling);
}
}
+
if (mNoIntercept) {
return false;
}
@@ -154,27 +151,8 @@
return mDetector.isDraggingOrSettling();
}
- private boolean shouldPossiblyIntercept(MotionEvent ev) {
- DeviceProfile grid = mLauncher.getDeviceProfile();
- if (mDetector.isIdleState()) {
- if (grid.isVerticalBarLayout()) {
- if (ev.getY() > mLauncher.getDeviceProfile().heightPx - mBezelSwipeUpHeight) {
- return true;
- }
- } else {
- if (mLauncher.getDragLayer().isEventOverHotseat(ev) ||
- mLauncher.getDragLayer().isEventOverPageIndicator(ev)) {
- return true;
- }
- }
- return false;
- } else {
- return true;
- }
- }
-
@Override
- public boolean onTouchEvent(MotionEvent ev) {
+ public boolean onControllerTouchEvent(MotionEvent ev) {
return mDetector.onTouchEvent(ev);
}
@@ -221,9 +199,9 @@
if (!mLauncher.isAllAppsVisible()) {
mLauncher.getUserEventDispatcher().logActionOnContainer(
- LauncherLogProto.Action.FLING,
- LauncherLogProto.Action.UP,
- LauncherLogProto.HOTSEAT);
+ Action.Touch.FLING,
+ Action.Direction.UP,
+ ContainerType.HOTSEAT);
}
mLauncher.showAppsView(true /* animated */,
false /* updatePredictedApps */,
@@ -241,9 +219,9 @@
calculateDuration(velocity, Math.abs(mAppsView.getTranslationY()));
if (!mLauncher.isAllAppsVisible()) {
mLauncher.getUserEventDispatcher().logActionOnContainer(
- LauncherLogProto.Action.SWIPE,
- LauncherLogProto.Action.UP,
- LauncherLogProto.HOTSEAT);
+ Action.Touch.SWIPE,
+ Action.Direction.UP,
+ ContainerType.HOTSEAT);
}
mLauncher.showAppsView(true, /* animated */
false /* updatePredictedApps */,
@@ -282,7 +260,7 @@
// Use a light status bar (dark icons) if all apps is behind at least half of the status
// bar. If the status bar is already light due to wallpaper extraction, keep it that way.
boolean forceLight = shift <= mStatusBarHeight / 2;
- mLauncher.activateLightStatusBar(forceLight);
+ mLauncher.activateLightSystemBars(forceLight, true /* statusBar */, true /* navBar */);
}
/**
@@ -335,13 +313,7 @@
}
private void calculateDuration(float velocity, float disp) {
- // TODO: make these values constants after tuning.
- float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity));
- float travelDistance = Math.max(0.2f, disp / mShiftRange);
- mAnimationDuration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
- if (DBG) {
- Log.d(TAG, String.format("calculateDuration=%d, v=%f, d=%f", mAnimationDuration, velocity, disp));
- }
+ mAnimationDuration = mDetector.calculateDuration(velocity, disp / mShiftRange);
}
public boolean animateToAllApps(AnimatorSet animationOut, long duration) {
@@ -531,21 +503,4 @@
setProgress(mProgress);
}
- static class ScrollInterpolator implements Interpolator {
-
- boolean mSteeper;
-
- public void setVelocityAtZero(float velocity) {
- mSteeper = velocity > FAST_FLING_PX_MS;
- }
-
- public float getInterpolation(float t) {
- t -= 1.0f;
- float output = t * t * t;
- if (mSteeper) {
- output *= t * t; // Make interpolation initial slope steeper
- }
- return output + 1;
- }
- }
}
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 173065b..f5cf7ef 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -16,15 +16,20 @@
package com.android.launcher3.allapps;
import android.content.Context;
+import android.os.Process;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.util.Log;
import com.android.launcher3.AppInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.compat.AlphabeticIndexCompat;
-import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.config.ProviderConfig;
-import com.android.launcher3.model.AppNameComparator;
+import com.android.launcher3.discovery.AppDiscoveryAppInfo;
+import com.android.launcher3.discovery.AppDiscoveryItem;
+import com.android.launcher3.discovery.AppDiscoveryUpdateState;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.LabelComparator;
import java.util.ArrayList;
import java.util.Collections;
@@ -48,17 +53,7 @@
private final int mFastScrollDistributionMode = FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS;
- /**
- * Info about a section in the alphabetic list
- */
- public static class SectionInfo {
- // The number of applications in this section
- public int numApps;
- // The section break AdapterItem for this section
- public AdapterItem sectionBreakItem;
- // The first app AdapterItem for this section
- public AdapterItem firstAppItem;
- }
+ private AppDiscoveryUpdateState mAppDiscoveryUpdateState;
/**
* Info about a fast scroller section, depending if sections are merged, the fast scroller
@@ -87,16 +82,10 @@
// The type of this item
public int viewType;
- /** Section & App properties */
- // The section for this item
- public SectionInfo sectionInfo;
-
/** App-only properties */
// The section name of this app. Note that there can be multiple items with different
// sectionNames in the same section
public String sectionName = null;
- // The index of this app in the section
- public int sectionAppIndex = -1;
// The row that this item shows up on
public int rowIndex;
// The index of this app in the row
@@ -106,30 +95,30 @@
// The index of this app not including sections
public int appIndex = -1;
- public static AdapterItem asSectionBreak(int pos, SectionInfo section) {
- AdapterItem item = new AdapterItem();
- item.viewType = AllAppsGridAdapter.VIEW_TYPE_SECTION_BREAK;
- item.position = pos;
- item.sectionInfo = section;
- section.sectionBreakItem = item;
- return item;
- }
-
- public static AdapterItem asPredictedApp(int pos, SectionInfo section, String sectionName,
- int sectionAppIndex, AppInfo appInfo, int appIndex) {
- AdapterItem item = asApp(pos, section, sectionName, sectionAppIndex, appInfo, appIndex);
+ public static AdapterItem asPredictedApp(int pos, String sectionName, AppInfo appInfo,
+ int appIndex) {
+ AdapterItem item = asApp(pos, sectionName, appInfo, appIndex);
item.viewType = AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON;
return item;
}
- public static AdapterItem asApp(int pos, SectionInfo section, String sectionName,
- int sectionAppIndex, AppInfo appInfo, int appIndex) {
+ public static AdapterItem asApp(int pos, String sectionName, AppInfo appInfo,
+ int appIndex) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.VIEW_TYPE_ICON;
item.position = pos;
- item.sectionInfo = section;
item.sectionName = sectionName;
- item.sectionAppIndex = sectionAppIndex;
+ item.appInfo = appInfo;
+ item.appIndex = appIndex;
+ return item;
+ }
+
+ public static AdapterItem asDiscoveryItem(int pos, String sectionName, AppInfo appInfo,
+ int appIndex) {
+ AdapterItem item = new AdapterItem();
+ item.viewType = AllAppsGridAdapter.VIEW_TYPE_DISCOVERY_ITEM;
+ item.position = pos;
+ item.sectionName = sectionName;
item.appInfo = appInfo;
item.appIndex = appIndex;
return item;
@@ -149,7 +138,7 @@
return item;
}
- public static AdapterItem asSearchDivder(int pos) {
+ public static AdapterItem asSearchDivider(int pos) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER;
item.position = pos;
@@ -163,6 +152,13 @@
return item;
}
+ public static AdapterItem asLoadingDivider(int pos) {
+ AdapterItem item = new AdapterItem();
+ item.viewType = AllAppsGridAdapter.VIEW_TYPE_APPS_LOADING_DIVIDER;
+ item.position = pos;
+ return item;
+ }
+
public static AdapterItem asMarketSearch(int pos) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET;
@@ -171,39 +167,30 @@
}
}
- /**
- * Common interface for different merging strategies.
- */
- public interface MergeAlgorithm {
- boolean continueMerging(SectionInfo section, SectionInfo withSection,
- int sectionAppCount, int numAppsPerRow, int mergeCount);
- }
-
- private Launcher mLauncher;
+ private final Launcher mLauncher;
// The set of apps from the system not including predictions
private final List<AppInfo> mApps = new ArrayList<>();
private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>();
// The set of filtered apps with the current filter
- private List<AppInfo> mFilteredApps = new ArrayList<>();
+ private final List<AppInfo> mFilteredApps = new ArrayList<>();
// The current set of adapter items
- private List<AdapterItem> mAdapterItems = new ArrayList<>();
- // The set of sections for the apps with the current filter
- private List<SectionInfo> mSections = new ArrayList<>();
+ private final ArrayList<AdapterItem> mAdapterItems = new ArrayList<>();
// The set of sections that we allow fast-scrolling to (includes non-merged sections)
- private List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
+ private final List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
// The set of predicted app component names
- private List<ComponentKey> mPredictedAppComponents = new ArrayList<>();
+ private final List<ComponentKey> mPredictedAppComponents = new ArrayList<>();
// The set of predicted apps resolved from the component names and the current set of apps
- private List<AppInfo> mPredictedApps = new ArrayList<>();
+ private final List<AppInfo> mPredictedApps = new ArrayList<>();
+ private final List<AppDiscoveryAppInfo> mDiscoveredApps = new ArrayList<>();
+
// The of ordered component names as a result of a search query
private ArrayList<ComponentKey> mSearchResults;
private HashMap<CharSequence, String> mCachedSectionNames = new HashMap<>();
private AllAppsGridAdapter mAdapter;
private AlphabeticIndexCompat mIndexer;
- private AppNameComparator mAppNameComparator;
- private MergeAlgorithm mMergeAlgorithm;
+ private AppInfoComparator mAppNameComparator;
private int mNumAppsPerRow;
private int mNumPredictedAppsPerRow;
private int mNumAppRowsInAdapter;
@@ -211,17 +198,15 @@
public AlphabeticalAppsList(Context context) {
mLauncher = Launcher.getLauncher(context);
mIndexer = new AlphabeticIndexCompat(context);
- mAppNameComparator = new AppNameComparator(context);
+ mAppNameComparator = new AppInfoComparator(context);
}
/**
* Sets the number of apps per row.
*/
- public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow,
- MergeAlgorithm mergeAlgorithm) {
+ public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow) {
mNumAppsPerRow = numAppsPerRow;
mNumPredictedAppsPerRow = numPredictedAppsPerRow;
- mMergeAlgorithm = mergeAlgorithm;
updateAdapterItems();
}
@@ -241,13 +226,6 @@
}
/**
- * Returns sections of all the current filtered applications.
- */
- public List<SectionInfo> getSections() {
- return mSections;
- }
-
- /**
* Returns fast scroller sections of all the current filtered applications.
*/
public List<FastScrollSectionInfo> getFastScrollerSections() {
@@ -289,6 +267,10 @@
return (mSearchResults != null) && mFilteredApps.isEmpty();
}
+ boolean shouldShowEmptySearch() {
+ return hasNoFilteredResults() && !isAppDiscoveryRunning() && mDiscoveredApps.isEmpty();
+ }
+
/**
* Sets the sorted list of filtered components.
*/
@@ -302,6 +284,20 @@
return false;
}
+ public void onAppDiscoverySearchUpdate(@Nullable AppDiscoveryItem app,
+ @NonNull AppDiscoveryUpdateState state) {
+ mAppDiscoveryUpdateState = state;
+ switch (state) {
+ case START:
+ mDiscoveredApps.clear();
+ break;
+ case UPDATE:
+ mDiscoveredApps.add(new AppDiscoveryAppInfo(app));
+ break;
+ }
+ updateAdapterItems();
+ }
+
/**
* Sets the current set of predicted apps. Since this can be called before we get the full set
* of applications, we should merge the results only in onAppsUpdated() which is idempotent.
@@ -354,17 +350,16 @@
// Sort the list of apps
mApps.clear();
mApps.addAll(mComponentToAppMap.values());
- Collections.sort(mApps, mAppNameComparator.getAppInfoComparator());
+ Collections.sort(mApps, mAppNameComparator);
// As a special case for some languages (currently only Simplified Chinese), we may need to
// coalesce sections
Locale curLocale = mLauncher.getResources().getConfiguration().locale;
- TreeMap<String, ArrayList<AppInfo>> sectionMap = null;
boolean localeRequiresSectionSorting = curLocale.equals(Locale.SIMPLIFIED_CHINESE);
if (localeRequiresSectionSorting) {
- // Compute the section headers. We use a TreeMap with the section name comparator to
+ // Compute the section headers. We use a TreeMap with the section name comparator to
// ensure that the sections are ordered when we iterate over it later
- sectionMap = new TreeMap<>(mAppNameComparator.getSectionNameComparator());
+ TreeMap<String, ArrayList<AppInfo>> sectionMap = new TreeMap<>(new LabelComparator());
for (AppInfo info : mApps) {
// Add the section to the cache
String sectionName = getAndUpdateCachedSectionName(info.title);
@@ -379,13 +374,10 @@
}
// Add each of the section apps to the list in order
- List<AppInfo> allApps = new ArrayList<>(mApps.size());
- for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) {
- allApps.addAll(entry.getValue());
- }
-
mApps.clear();
- mApps.addAll(allApps);
+ for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) {
+ mApps.addAll(entry.getValue());
+ }
} else {
// Just compute the section headers for use below
for (AppInfo info : mApps) {
@@ -403,7 +395,17 @@
* mCachedSectionNames to have been calculated for the set of all apps in mApps.
*/
private void updateAdapterItems() {
- SectionInfo lastSectionInfo = null;
+ refillAdapterItems();
+ refreshRecyclerView();
+ }
+
+ private void refreshRecyclerView() {
+ if (mAdapter != null) {
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+
+ private void refillAdapterItems() {
String lastSectionName = null;
FastScrollSectionInfo lastFastScrollerSectionInfo = null;
int position = 0;
@@ -413,23 +415,22 @@
mFilteredApps.clear();
mFastScrollerSections.clear();
mAdapterItems.clear();
- mSections.clear();
if (DEBUG_PREDICTIONS) {
if (mPredictedAppComponents.isEmpty() && !mApps.isEmpty()) {
mPredictedAppComponents.add(new ComponentKey(mApps.get(0).componentName,
- UserHandleCompat.myUserHandle()));
+ Process.myUserHandle()));
mPredictedAppComponents.add(new ComponentKey(mApps.get(0).componentName,
- UserHandleCompat.myUserHandle()));
+ Process.myUserHandle()));
mPredictedAppComponents.add(new ComponentKey(mApps.get(0).componentName,
- UserHandleCompat.myUserHandle()));
+ Process.myUserHandle()));
mPredictedAppComponents.add(new ComponentKey(mApps.get(0).componentName,
- UserHandleCompat.myUserHandle()));
+ Process.myUserHandle()));
}
}
// Add the search divider
- mAdapterItems.add(AdapterItem.asSearchDivder(position++));
+ mAdapterItems.add(AdapterItem.asSearchDivider(position++));
// Process the predicted app components
mPredictedApps.clear();
@@ -451,19 +452,14 @@
if (!mPredictedApps.isEmpty()) {
// Add a section for the predictions
- lastSectionInfo = new SectionInfo();
lastFastScrollerSectionInfo = new FastScrollSectionInfo("");
- AdapterItem sectionItem = AdapterItem.asSectionBreak(position++, lastSectionInfo);
- mSections.add(lastSectionInfo);
mFastScrollerSections.add(lastFastScrollerSectionInfo);
- mAdapterItems.add(sectionItem);
// Add the predicted app items
for (AppInfo info : mPredictedApps) {
- AdapterItem appItem = AdapterItem.asPredictedApp(position++, lastSectionInfo,
- "", lastSectionInfo.numApps++, info, appIndex++);
- if (lastSectionInfo.firstAppItem == null) {
- lastSectionInfo.firstAppItem = appItem;
+ AdapterItem appItem = AdapterItem.asPredictedApp(position++, "", info,
+ appIndex++);
+ if (lastFastScrollerSectionInfo.fastScrollToItem == null) {
lastFastScrollerSectionInfo.fastScrollToItem = appItem;
}
mAdapterItems.add(appItem);
@@ -480,43 +476,49 @@
String sectionName = getAndUpdateCachedSectionName(info.title);
// Create a new section if the section names do not match
- if (lastSectionInfo == null || !sectionName.equals(lastSectionName)) {
+ if (!sectionName.equals(lastSectionName)) {
lastSectionName = sectionName;
- lastSectionInfo = new SectionInfo();
lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName);
- mSections.add(lastSectionInfo);
mFastScrollerSections.add(lastFastScrollerSectionInfo);
-
- // Create a new section item to break the flow of items in the list
- if (!hasFilter()) {
- AdapterItem sectionItem = AdapterItem.asSectionBreak(position++, lastSectionInfo);
- mAdapterItems.add(sectionItem);
- }
}
// Create an app item
- AdapterItem appItem = AdapterItem.asApp(position++, lastSectionInfo, sectionName,
- lastSectionInfo.numApps++, info, appIndex++);
- if (lastSectionInfo.firstAppItem == null) {
- lastSectionInfo.firstAppItem = appItem;
+ AdapterItem appItem = AdapterItem.asApp(position++, sectionName, info, appIndex++);
+ if (lastFastScrollerSectionInfo.fastScrollToItem == null) {
lastFastScrollerSectionInfo.fastScrollToItem = appItem;
}
mAdapterItems.add(appItem);
mFilteredApps.add(info);
}
- // Append the search market item if we are currently searching
if (hasFilter()) {
- if (hasNoFilteredResults()) {
- mAdapterItems.add(AdapterItem.asEmptySearch(position++));
- } else {
- mAdapterItems.add(AdapterItem.asMarketDivider(position++));
- }
- mAdapterItems.add(AdapterItem.asMarketSearch(position++));
- }
+ if (isAppDiscoveryRunning() || mDiscoveredApps.size() > 0) {
+ mAdapterItems.add(AdapterItem.asLoadingDivider(position++));
+ // Append all app discovery results
+ for (int i = 0; i < mDiscoveredApps.size(); i++) {
+ AppDiscoveryAppInfo appDiscoveryAppInfo = mDiscoveredApps.get(i);
+ if (appDiscoveryAppInfo.isRecent) {
+ // already handled in getFilteredAppInfos()
+ continue;
+ }
+ AdapterItem item = AdapterItem.asDiscoveryItem(position++,
+ "", appDiscoveryAppInfo, appIndex++);
+ mAdapterItems.add(item);
+ }
- // Merge multiple sections together as requested by the merge strategy for this device
- mergeSections();
+ if (!isAppDiscoveryRunning()) {
+ mAdapterItems.add(AdapterItem.asMarketSearch(position++));
+ }
+ } else {
+ // Append the search market item
+ if (hasNoFilteredResults()) {
+ mAdapterItems.add(AdapterItem.asEmptySearch(position++));
+ } else {
+ mAdapterItems.add(AdapterItem.asMarketDivider(position++));
+ }
+ mAdapterItems.add(AdapterItem.asMarketSearch(position++));
+ }
+ }
if (mNumAppsPerRow != 0) {
// Update the number of rows in the adapter after we do all the merging (otherwise, we
@@ -571,11 +573,11 @@
break;
}
}
+ }
- // Refresh the recycler view
- if (mAdapter != null) {
- mAdapter.notifyDataSetChanged();
- }
+ public boolean isAppDiscoveryRunning() {
+ return mAppDiscoveryUpdateState == AppDiscoveryUpdateState.START
+ || mAppDiscoveryUpdateState == AppDiscoveryUpdateState.UPDATE;
}
private List<AppInfo> getFiltersAppInfos() {
@@ -590,62 +592,18 @@
result.add(match);
}
}
- return result;
- }
- /**
- * Merges multiple sections to reduce visual raggedness.
- */
- private void mergeSections() {
- // Ignore merging until we have an algorithm and a valid row size
- if (mMergeAlgorithm == null || mNumAppsPerRow == 0) {
- return;
- }
-
- // Go through each section and try and merge some of the sections
- if (!hasFilter()) {
- int sectionAppCount = 0;
- for (int i = 0; i < mSections.size() - 1; i++) {
- SectionInfo section = mSections.get(i);
- sectionAppCount = section.numApps;
- int mergeCount = 1;
-
- // Merge rows based on the current strategy
- while (i < (mSections.size() - 1) &&
- mMergeAlgorithm.continueMerging(section, mSections.get(i + 1),
- sectionAppCount, mNumAppsPerRow, mergeCount)) {
- SectionInfo nextSection = mSections.remove(i + 1);
-
- // Remove the next section break
- mAdapterItems.remove(nextSection.sectionBreakItem);
- int pos = mAdapterItems.indexOf(section.firstAppItem);
-
- // Point the section for these new apps to the merged section
- int nextPos = pos + section.numApps;
- for (int j = nextPos; j < (nextPos + nextSection.numApps); j++) {
- AdapterItem item = mAdapterItems.get(j);
- item.sectionInfo = section;
- item.sectionAppIndex += section.numApps;
- }
-
- // Update the following adapter items of the removed section item
- pos = mAdapterItems.indexOf(nextSection.firstAppItem);
- for (int j = pos; j < mAdapterItems.size(); j++) {
- AdapterItem item = mAdapterItems.get(j);
- item.position--;
- }
- section.numApps += nextSection.numApps;
- sectionAppCount += nextSection.numApps;
-
- if (DEBUG) {
- Log.d(TAG, "Merging: " + nextSection.firstAppItem.sectionName +
- " to " + section.firstAppItem.sectionName +
- " mergedNumRows: " + (sectionAppCount / mNumAppsPerRow));
- }
- mergeCount++;
+ // adding recently used instant apps
+ if (mDiscoveredApps.size() > 0) {
+ for (int i = 0; i < mDiscoveredApps.size(); i++) {
+ AppDiscoveryAppInfo discoveryAppInfo = mDiscoveredApps.get(i);
+ if (discoveryAppInfo.isRecent) {
+ result.add(discoveryAppInfo);
}
}
+ Collections.sort(result, mAppNameComparator);
}
+ return result;
}
/**
@@ -660,4 +618,5 @@
}
return sectionName;
}
+
}
diff --git a/src/com/android/launcher3/allapps/AppInfoComparator.java b/src/com/android/launcher3/allapps/AppInfoComparator.java
new file mode 100644
index 0000000..80577a7
--- /dev/null
+++ b/src/com/android/launcher3/allapps/AppInfoComparator.java
@@ -0,0 +1,65 @@
+/*
+ * 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.allapps;
+
+import android.content.Context;
+import android.os.Process;
+import android.os.UserHandle;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.util.LabelComparator;
+
+import java.util.Comparator;
+
+/**
+ * A comparator to arrange items based on user profiles.
+ */
+public class AppInfoComparator implements Comparator<AppInfo> {
+
+ private final UserManagerCompat mUserManager;
+ private final UserHandle mMyUser;
+ private final LabelComparator mLabelComparator;
+
+ public AppInfoComparator(Context context) {
+ mUserManager = UserManagerCompat.getInstance(context);
+ mMyUser = Process.myUserHandle();
+ mLabelComparator = new LabelComparator();
+ }
+
+ @Override
+ public int compare(AppInfo a, AppInfo b) {
+ // Order by the title in the current locale
+ int result = mLabelComparator.compare(a.title.toString(), b.title.toString());
+ if (result != 0) {
+ return result;
+ }
+
+ // If labels are same, compare component names
+ result = a.componentName.compareTo(b.componentName);
+ if (result != 0) {
+ return result;
+ }
+
+ if (mMyUser.equals(a.user)) {
+ return -1;
+ } else {
+ Long aUserSerial = mUserManager.getSerialNumberForUser(a.user);
+ Long bUserSerial = mUserManager.getSerialNumberForUser(b.user);
+ return aUserSerial.compareTo(bUserSerial);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/allapps/HeaderElevationController.java b/src/com/android/launcher3/allapps/HeaderElevationController.java
index ce9837c..b167fed 100644
--- a/src/com/android/launcher3/allapps/HeaderElevationController.java
+++ b/src/com/android/launcher3/allapps/HeaderElevationController.java
@@ -1,27 +1,52 @@
package com.android.launcher3.allapps;
-import android.annotation.TargetApi;
import android.content.res.Resources;
import android.graphics.Outline;
-import android.graphics.Rect;
-import android.graphics.drawable.GradientDrawable;
-import android.os.Build;
import android.support.v7.widget.RecyclerView;
import android.view.View;
-import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
-import android.widget.FrameLayout;
import com.android.launcher3.BaseRecyclerView;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
/**
* Helper class for controlling the header elevation in response to RecyclerView scroll.
*/
-public abstract class HeaderElevationController extends RecyclerView.OnScrollListener {
+public class HeaderElevationController extends RecyclerView.OnScrollListener {
+
+ private final View mHeader;
+ private final float mMaxElevation;
+ private final float mScrollToElevation;
private int mCurrentY = 0;
+ public HeaderElevationController(View header) {
+ mHeader = header;
+ final Resources res = mHeader.getContext().getResources();
+ mMaxElevation = res.getDimension(R.dimen.all_apps_header_max_elevation);
+ mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation);
+
+ // We need to provide a custom outline so the shadow only appears on the bottom edge.
+ // The top, left and right edges are all extended out, and the shadow is clipped
+ // by the parent.
+ final ViewOutlineProvider vop = new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ final View parent = (View) mHeader.getParent();
+
+ final int left = parent.getLeft(); // Use the parent to account for offsets
+ final int top = view.getTop();
+ final int right = left + view.getWidth();
+ final int bottom = view.getBottom();
+
+ final int offset = Utilities.pxFromDp(mMaxElevation, res.getDisplayMetrics());
+ outline.setRect(left - offset, top - offset, right + offset, bottom);
+ }
+ };
+ mHeader.setOutlineProvider(vop);
+ }
+
public void reset() {
mCurrentY = 0;
onScroll(mCurrentY);
@@ -33,91 +58,12 @@
onScroll(mCurrentY);
}
- public void updateBackgroundPadding(Rect bgPadding) { }
-
- abstract void onScroll(int scrollY);
-
- public static class ControllerV16 extends HeaderElevationController {
-
- private final View mShadow;
- private final float mScrollToElevation;
-
- public ControllerV16(View header) {
- Resources res = header.getContext().getResources();
- mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation);
-
- mShadow = new View(header.getContext());
- mShadow.setBackground(new GradientDrawable(
- GradientDrawable.Orientation.TOP_BOTTOM, new int[] {0x1E000000, 0x00000000}));
- mShadow.setAlpha(0);
-
- FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.MATCH_PARENT,
- res.getDimensionPixelSize(R.dimen.all_apps_header_shadow_height));
- lp.topMargin = ((FrameLayout.LayoutParams) header.getLayoutParams()).height;
-
- ((ViewGroup) header.getParent()).addView(mShadow, lp);
- }
-
- @Override
- public void onScroll(int scrollY) {
- float elevationPct = (float) Math.min(scrollY, mScrollToElevation) /
- mScrollToElevation;
- mShadow.setAlpha(elevationPct);
- }
-
- @Override
- public void updateBackgroundPadding(Rect bgPadding) {
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mShadow.getLayoutParams();
- lp.leftMargin = bgPadding.left;
- lp.rightMargin = bgPadding.right;
- mShadow.requestLayout();
+ private void onScroll(int scrollY) {
+ float elevationPct = Math.min(scrollY, mScrollToElevation) / mScrollToElevation;
+ float newElevation = mMaxElevation * elevationPct;
+ if (Float.compare(mHeader.getElevation(), newElevation) != 0) {
+ mHeader.setElevation(newElevation);
}
}
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- public static class ControllerVL extends HeaderElevationController {
-
- private final View mHeader;
- private final float mMaxElevation;
- private final float mScrollToElevation;
-
- public ControllerVL(View header) {
- mHeader = header;
- Resources res = mHeader.getContext().getResources();
- mMaxElevation = res.getDimension(R.dimen.all_apps_header_max_elevation);
- mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation);
-
- // We need to provide a custom outline so the shadow only appears on the bottom edge.
- // The top, left and right edges are all extended out, and the shadow is clipped
- // by the parent.
- final ViewOutlineProvider vop = new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- final View parent = (View) mHeader.getParent();
-
- final int left = parent.getLeft(); // Use the parent to account for offsets
- final int top = view.getTop();
- final int right = left + view.getWidth();
- final int bottom = view.getBottom();
-
- outline.setRect(
- left - (int) mMaxElevation,
- top - (int) mMaxElevation,
- right + (int) mMaxElevation,
- bottom);
- }
- };
- mHeader.setOutlineProvider(vop);
- }
-
- @Override
- public void onScroll(int scrollY) {
- float elevationPct = Math.min(scrollY, mScrollToElevation) / mScrollToElevation;
- float newElevation = mMaxElevation * elevationPct;
- if (Float.compare(mHeader.getElevation(), newElevation) != 0) {
- mHeader.setElevation(newElevation);
- }
- }
- }
}
diff --git a/src/com/android/launcher3/allapps/VerticalPullDetector.java b/src/com/android/launcher3/allapps/VerticalPullDetector.java
index ab2b6ed..7800c01 100644
--- a/src/com/android/launcher3/allapps/VerticalPullDetector.java
+++ b/src/com/android/launcher3/allapps/VerticalPullDetector.java
@@ -4,6 +4,7 @@
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
+import android.view.animation.Interpolator;
/**
* One dimensional scroll gesture detector for all apps container pull up interaction.
@@ -23,6 +24,9 @@
public static final int DIRECTION_DOWN = 1 << 1;
public static final int DIRECTION_BOTH = DIRECTION_DOWN | DIRECTION_UP;
+ private static final float ANIMATION_DURATION = 1200;
+ private static final float FAST_FLING_PX_MS = 10;
+
/**
* The minimum release velocity in pixels per millisecond that triggers fling..
*/
@@ -112,7 +116,7 @@
mListener = l;
}
- interface Listener {
+ public interface Listener {
void onDragStart(boolean start);
boolean onDrag(float displacement, float velocity);
@@ -230,7 +234,7 @@
private void reportDragEnd() {
if (DBG) {
- Log.d(TAG, String.format("onScrolEnd disp=%.1f, velocity=%.1f",
+ Log.d(TAG, String.format("onScrollEnd disp=%.1f, velocity=%.1f",
mDisplacementY, mVelocity));
}
mListener.onDragEnd(mVelocity, Math.abs(mVelocity) > RELEASE_VELOCITY_PX_MS);
@@ -272,4 +276,33 @@
private static float interpolate(float from, float to, float alpha) {
return (1.0f - alpha) * from + alpha * to;
}
+
+ public long calculateDuration(float velocity, float progressNeeded) {
+ // TODO: make these values constants after tuning.
+ float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity));
+ float travelDistance = Math.max(0.2f, progressNeeded);
+ long duration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
+ if (DBG) {
+ Log.d(TAG, String.format("calculateDuration=%d, v=%f, d=%f", duration, velocity, progressNeeded));
+ }
+ return duration;
+ }
+
+ public static class ScrollInterpolator implements Interpolator {
+
+ boolean mSteeper;
+
+ public void setVelocityAtZero(float velocity) {
+ mSteeper = velocity > FAST_FLING_PX_MS;
+ }
+
+ public float getInterpolation(float t) {
+ t -= 1.0f;
+ float output = t * t * t;
+ if (mSteeper) {
+ output *= t * t; // Make interpolation initial slope steeper
+ }
+ return output + 1;
+ }
+ }
}
diff --git a/src/com/android/launcher3/anim/AnimationLayerSet.java b/src/com/android/launcher3/anim/AnimationLayerSet.java
new file mode 100644
index 0000000..14bcd17
--- /dev/null
+++ b/src/com/android/launcher3/anim/AnimationLayerSet.java
@@ -0,0 +1,70 @@
+/*
+ * 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;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.view.View;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Helper class to automatically build view hardware layers for the duration of an animation.
+ */
+public class AnimationLayerSet extends AnimatorListenerAdapter {
+
+ private final HashMap<View, Integer> mViewsToLayerTypeMap;
+
+ public AnimationLayerSet() {
+ mViewsToLayerTypeMap = new HashMap<>();
+ }
+
+ public AnimationLayerSet(View v) {
+ mViewsToLayerTypeMap = new HashMap<>(1);
+ addView(v);
+ }
+
+ public void addView(View v) {
+ mViewsToLayerTypeMap.put(v, v.getLayerType());
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ // Enable all necessary layers
+ Iterator<Map.Entry<View, Integer>> itr = mViewsToLayerTypeMap.entrySet().iterator();
+ while (itr.hasNext()) {
+ Map.Entry<View, Integer> entry = itr.next();
+ View v = entry.getKey();
+ entry.setValue(v.getLayerType());
+ v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ if (v.isAttachedToWindow() && v.getVisibility() == View.VISIBLE) {
+ v.buildLayer();
+ }
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ Iterator<Map.Entry<View, Integer>> itr = mViewsToLayerTypeMap.entrySet().iterator();
+ while (itr.hasNext()) {
+ Map.Entry<View, Integer> entry = itr.next();
+ entry.getKey().setLayerType(entry.getValue(), null);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java b/src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java
new file mode 100644
index 0000000..be1e2d6
--- /dev/null
+++ b/src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java
@@ -0,0 +1,43 @@
+/*
+ * 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.graphics.Rect;
+
+import com.android.launcher3.util.PillRevealOutlineProvider;
+
+/**
+ * Extension of {@link PillRevealOutlineProvider} which only changes the height of the pill.
+ * For now, we assume the height is added/removed from the bottom.
+ */
+public class PillHeightRevealOutlineProvider extends PillRevealOutlineProvider {
+
+ private final int mNewHeight;
+
+ public PillHeightRevealOutlineProvider(Rect pillRect, float radius, int newHeight) {
+ super(0, 0, pillRect, radius);
+ mOutline.set(pillRect);
+ mNewHeight = newHeight;
+ }
+
+ @Override
+ public void setProgress(float progress) {
+ mOutline.top = 0;
+ int heightDifference = mPillRect.height() - mNewHeight;
+ mOutline.bottom = (int) (mPillRect.bottom - heightDifference * (1 - progress));
+ }
+}
diff --git a/src/com/android/launcher3/anim/PropertyListBuilder.java b/src/com/android/launcher3/anim/PropertyListBuilder.java
new file mode 100644
index 0000000..33e7f66
--- /dev/null
+++ b/src/com/android/launcher3/anim/PropertyListBuilder.java
@@ -0,0 +1,50 @@
+package com.android.launcher3.anim;
+
+import android.animation.PropertyValuesHolder;
+import android.view.View;
+
+import java.util.ArrayList;
+
+/**
+ * Helper class to build a list of {@link PropertyValuesHolder} for view properties
+ */
+public class PropertyListBuilder {
+
+ private final ArrayList<PropertyValuesHolder> mProperties = new ArrayList<>();
+
+ public PropertyListBuilder translationX(float value) {
+ mProperties.add(PropertyValuesHolder.ofFloat(View.TRANSLATION_X, value));
+ return this;
+ }
+
+ public PropertyListBuilder translationY(float value) {
+ mProperties.add(PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, value));
+ return this;
+ }
+
+ public PropertyListBuilder scaleX(float value) {
+ mProperties.add(PropertyValuesHolder.ofFloat(View.SCALE_X, value));
+ return this;
+ }
+
+ public PropertyListBuilder scaleY(float value) {
+ mProperties.add(PropertyValuesHolder.ofFloat(View.SCALE_Y, value));
+ return this;
+ }
+
+ /**
+ * Helper method to set both scaleX and scaleY
+ */
+ public PropertyListBuilder scale(float value) {
+ return scaleX(value).scaleY(value);
+ }
+
+ public PropertyListBuilder alpha(float value) {
+ mProperties.add(PropertyValuesHolder.ofFloat(View.ALPHA, value));
+ return this;
+ }
+
+ public PropertyValuesHolder[] build() {
+ return mProperties.toArray(new PropertyValuesHolder[mProperties.size()]);
+ }
+}
diff --git a/src/com/android/launcher3/anim/PropertyResetListener.java b/src/com/android/launcher3/anim/PropertyResetListener.java
new file mode 100644
index 0000000..eefb014
--- /dev/null
+++ b/src/com/android/launcher3/anim/PropertyResetListener.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.launcher3.anim;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.util.Property;
+
+/**
+ * An AnimatorListener that sets the given property to the given value at the end of the animation.
+ */
+public class PropertyResetListener<T, V> extends AnimatorListenerAdapter {
+
+ private Property<T, V> mPropertyToReset;
+ private V mResetToValue;
+
+ public PropertyResetListener(Property<T, V> propertyToReset, V resetToValue) {
+ mPropertyToReset = propertyToReset;
+ mResetToValue = resetToValue;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mPropertyToReset.set((T) ((ObjectAnimator) animation).getTarget(), mResetToValue);
+ }
+}
diff --git a/src/com/android/launcher3/badge/BadgeInfo.java b/src/com/android/launcher3/badge/BadgeInfo.java
new file mode 100644
index 0000000..08d8ad4
--- /dev/null
+++ b/src/com/android/launcher3/badge/BadgeInfo.java
@@ -0,0 +1,164 @@
+/*
+ * 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.badge;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
+
+import com.android.launcher3.notification.NotificationInfo;
+import com.android.launcher3.notification.NotificationKeyData;
+import com.android.launcher3.util.PackageUserKey;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Contains data to be used in an icon badge.
+ */
+public class BadgeInfo {
+
+ public static final int MAX_COUNT = 999;
+
+ /** Used to link this BadgeInfo to icons on the workspace and all apps */
+ private PackageUserKey mPackageUserKey;
+
+ /**
+ * The keys of the notifications that this badge represents. These keys can later be
+ * used to retrieve {@link NotificationInfo}'s.
+ */
+ private List<NotificationKeyData> mNotificationKeys;
+
+ /**
+ * The current sum of the counts in {@link #mNotificationKeys},
+ * updated whenever a key is added or removed.
+ */
+ private int mTotalCount;
+
+ /** This will only be initialized if the badge should display the notification icon. */
+ private NotificationInfo mNotificationInfo;
+
+ /**
+ * When retrieving the notification icon, we draw it into this shader, which can be clipped
+ * as necessary when drawn in a badge.
+ */
+ private Shader mNotificationIcon;
+
+ public BadgeInfo(PackageUserKey packageUserKey) {
+ mPackageUserKey = packageUserKey;
+ mNotificationKeys = new ArrayList<>();
+ }
+
+ /**
+ * Returns whether the notification was added or its count changed.
+ */
+ public boolean addOrUpdateNotificationKey(NotificationKeyData notificationKey) {
+ int indexOfPrevKey = mNotificationKeys.indexOf(notificationKey);
+ NotificationKeyData prevKey = indexOfPrevKey == -1 ? null
+ : mNotificationKeys.get(indexOfPrevKey);
+ if (prevKey != null) {
+ if (prevKey.count == notificationKey.count) {
+ return false;
+ }
+ // Notification was updated with a new count.
+ mTotalCount -= prevKey.count;
+ mTotalCount += notificationKey.count;
+ prevKey.count = notificationKey.count;
+ return true;
+ }
+ boolean added = mNotificationKeys.add(notificationKey);
+ if (added) {
+ mTotalCount += notificationKey.count;
+ }
+ return added;
+ }
+
+ /**
+ * Returns whether the notification was removed (false if it didn't exist).
+ */
+ public boolean removeNotificationKey(NotificationKeyData notificationKey) {
+ boolean removed = mNotificationKeys.remove(notificationKey);
+ if (removed) {
+ mTotalCount -= notificationKey.count;
+ }
+ return removed;
+ }
+
+ public List<NotificationKeyData> getNotificationKeys() {
+ return mNotificationKeys;
+ }
+
+ public int getNotificationCount() {
+ return Math.min(mTotalCount, MAX_COUNT);
+ }
+
+ public void setNotificationToShow(@Nullable NotificationInfo notificationInfo) {
+ mNotificationInfo = notificationInfo;
+ mNotificationIcon = null;
+ }
+
+ public boolean hasNotificationToShow() {
+ return mNotificationInfo != null;
+ }
+
+ /**
+ * Returns a shader to set on a Paint that will draw the notification icon in a badge.
+ *
+ * The shader is cached until {@link #setNotificationToShow(NotificationInfo)} is called.
+ */
+ public @Nullable Shader getNotificationIconForBadge(Context context, int badgeColor,
+ int badgeSize, int badgePadding) {
+ if (mNotificationInfo == null) {
+ return null;
+ }
+ if (mNotificationIcon == null) {
+ Drawable icon = mNotificationInfo.getIconForBackground(context, badgeColor)
+ .getConstantState().newDrawable();
+ int iconSize = badgeSize - badgePadding * 2;
+ icon.setBounds(0, 0, iconSize, iconSize);
+ Bitmap iconBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(iconBitmap);
+ canvas.translate(badgePadding, badgePadding);
+ icon.draw(canvas);
+ mNotificationIcon = new BitmapShader(iconBitmap, Shader.TileMode.CLAMP,
+ Shader.TileMode.CLAMP);
+ }
+ return mNotificationIcon;
+ }
+
+ public boolean isIconLarge() {
+ return mNotificationInfo != null && mNotificationInfo.isIconLarge();
+ }
+
+ /**
+ * Whether newBadge represents the same PackageUserKey as this badge, and icons with
+ * this badge should be invalidated. So, for instance, if a badge has 3 notifications
+ * and one of those notifications is updated, this method should return false because
+ * the badge still says "3" and the contents of those notifications are only retrieved
+ * upon long-click. This method always returns true when adding or removing notifications,
+ * or if the badge has a notification icon to show.
+ */
+ public boolean shouldBeInvalidated(BadgeInfo newBadge) {
+ return mPackageUserKey.equals(newBadge.mPackageUserKey)
+ && (getNotificationCount() != newBadge.getNotificationCount()
+ || hasNotificationToShow());
+ }
+}
diff --git a/src/com/android/launcher3/badge/BadgeRenderer.java b/src/com/android/launcher3/badge/BadgeRenderer.java
new file mode 100644
index 0000000..ba1977a
--- /dev/null
+++ b/src/com/android/launcher3/badge/BadgeRenderer.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.badge;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.support.annotation.Nullable;
+import android.util.SparseArray;
+
+import com.android.launcher3.R;
+import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.graphics.ShadowGenerator;
+
+/**
+ * Contains parameters necessary to draw a badge for an icon (e.g. the size of the badge).
+ * @see BadgeInfo for the data to draw
+ */
+public class BadgeRenderer {
+
+ private static final boolean DOTS_ONLY = true;
+
+ // The badge sizes are defined as percentages of the app icon size.
+ private static final float SIZE_PERCENTAGE = 0.38f;
+ // Used to expand the width of the badge for each additional digit.
+ private static final float CHAR_SIZE_PERCENTAGE = 0.12f;
+ private static final float TEXT_SIZE_PERCENTAGE = 0.26f;
+ private static final float OFFSET_PERCENTAGE = 0.02f;
+ private static final float STACK_OFFSET_PERCENTAGE_X = 0.05f;
+ private static final float STACK_OFFSET_PERCENTAGE_Y = 0.06f;
+ private static final float DOT_SCALE = 0.6f;
+
+ private final Context mContext;
+ private final int mSize;
+ private final int mCharSize;
+ private final int mTextHeight;
+ private final int mOffset;
+ private final int mStackOffsetX;
+ private final int mStackOffsetY;
+ private final IconDrawer mLargeIconDrawer;
+ private final IconDrawer mSmallIconDrawer;
+ private final Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG
+ | Paint.FILTER_BITMAP_FLAG);
+ private final SparseArray<Bitmap> mBackgroundsWithShadow;
+
+ public BadgeRenderer(Context context, int iconSizePx) {
+ mContext = context;
+ Resources res = context.getResources();
+ mSize = (int) (SIZE_PERCENTAGE * iconSizePx);
+ mCharSize = (int) (CHAR_SIZE_PERCENTAGE * iconSizePx);
+ mOffset = (int) (OFFSET_PERCENTAGE * iconSizePx);
+ mStackOffsetX = (int) (STACK_OFFSET_PERCENTAGE_X * iconSizePx);
+ mStackOffsetY = (int) (STACK_OFFSET_PERCENTAGE_Y * iconSizePx);
+ mTextPaint.setTextSize(iconSizePx * TEXT_SIZE_PERCENTAGE);
+ mTextPaint.setTextAlign(Paint.Align.CENTER);
+ mLargeIconDrawer = new IconDrawer(res.getDimensionPixelSize(R.dimen.badge_small_padding));
+ mSmallIconDrawer = new IconDrawer(res.getDimensionPixelSize(R.dimen.badge_large_padding));
+ // Measure the text height.
+ Rect tempTextHeight = new Rect();
+ mTextPaint.getTextBounds("0", 0, 1, tempTextHeight);
+ mTextHeight = tempTextHeight.height();
+
+ mBackgroundsWithShadow = new SparseArray<>(3);
+ }
+
+ /**
+ * Draw a circle in the top right corner of the given bounds, and draw
+ * {@link BadgeInfo#getNotificationCount()} on top of the circle.
+ * @param palette The colors (based on the icon) to use for the badge.
+ * @param badgeInfo Contains data to draw on the badge. Could be null if we are animating out.
+ * @param iconBounds The bounds of the icon being badged.
+ * @param badgeScale The progress of the animation, from 0 to 1.
+ * @param spaceForOffset How much space is available to offset the badge up and to the right.
+ */
+ public void draw(Canvas canvas, IconPalette palette, @Nullable BadgeInfo badgeInfo,
+ Rect iconBounds, float badgeScale, Point spaceForOffset) {
+ mTextPaint.setColor(palette.textColor);
+ IconDrawer iconDrawer = badgeInfo != null && badgeInfo.isIconLarge()
+ ? mLargeIconDrawer : mSmallIconDrawer;
+ Shader icon = badgeInfo == null ? null : badgeInfo.getNotificationIconForBadge(
+ mContext, palette.backgroundColor, mSize, iconDrawer.mPadding);
+ String notificationCount = badgeInfo == null ? "0"
+ : String.valueOf(badgeInfo.getNotificationCount());
+ int numChars = notificationCount.length();
+ int width = DOTS_ONLY ? mSize : mSize + mCharSize * (numChars - 1);
+ // Lazily load the background with shadow.
+ Bitmap backgroundWithShadow = mBackgroundsWithShadow.get(numChars);
+ if (backgroundWithShadow == null) {
+ backgroundWithShadow = ShadowGenerator.createPillWithShadow(Color.WHITE, width, mSize);
+ mBackgroundsWithShadow.put(numChars, backgroundWithShadow);
+ }
+ canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ // We draw the badge relative to its center.
+ int badgeCenterX = iconBounds.right - width / 2;
+ int badgeCenterY = iconBounds.top + mSize / 2;
+ boolean isText = !DOTS_ONLY && badgeInfo != null && badgeInfo.getNotificationCount() != 0;
+ boolean isIcon = !DOTS_ONLY && icon != null;
+ boolean isDot = !(isText || isIcon);
+ if (isDot) {
+ badgeScale *= DOT_SCALE;
+ }
+ int offsetX = Math.min(mOffset, spaceForOffset.x);
+ int offsetY = Math.min(mOffset, spaceForOffset.y);
+ canvas.translate(badgeCenterX + offsetX, badgeCenterY - offsetY);
+ canvas.scale(badgeScale, badgeScale);
+ // Prepare the background and shadow and possible stacking effect.
+ mBackgroundPaint.setColorFilter(palette.backgroundColorMatrixFilter);
+ int backgroundWithShadowSize = backgroundWithShadow.getHeight(); // Same as width.
+ boolean shouldStack = !isDot && badgeInfo != null
+ && badgeInfo.getNotificationKeys().size() > 1;
+ if (shouldStack) {
+ int offsetDiffX = mStackOffsetX - mOffset;
+ int offsetDiffY = mStackOffsetY - mOffset;
+ canvas.translate(offsetDiffX, offsetDiffY);
+ canvas.drawBitmap(backgroundWithShadow, -backgroundWithShadowSize / 2,
+ -backgroundWithShadowSize / 2, mBackgroundPaint);
+ canvas.translate(-offsetDiffX, -offsetDiffY);
+ }
+
+ if (isText) {
+ canvas.drawBitmap(backgroundWithShadow, -backgroundWithShadowSize / 2,
+ -backgroundWithShadowSize / 2, mBackgroundPaint);
+ canvas.drawText(notificationCount, 0, mTextHeight / 2, mTextPaint);
+ } else if (isIcon) {
+ canvas.drawBitmap(backgroundWithShadow, -backgroundWithShadowSize / 2,
+ -backgroundWithShadowSize / 2, mBackgroundPaint);
+ iconDrawer.drawIcon(icon, canvas);
+ } else if (isDot) {
+ mBackgroundPaint.setColorFilter(palette.saturatedBackgroundColorMatrixFilter);
+ canvas.drawBitmap(backgroundWithShadow, -backgroundWithShadowSize / 2,
+ -backgroundWithShadowSize / 2, mBackgroundPaint);
+ }
+ canvas.restore();
+ }
+
+ /** Draws the notification icon with padding of a given size. */
+ private class IconDrawer {
+
+ private final int mPadding;
+ private final Bitmap mCircleClipBitmap;
+ private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG |
+ Paint.FILTER_BITMAP_FLAG);
+
+ public IconDrawer(int padding) {
+ mPadding = padding;
+ mCircleClipBitmap = Bitmap.createBitmap(mSize, mSize, Bitmap.Config.ALPHA_8);
+ Canvas canvas = new Canvas();
+ canvas.setBitmap(mCircleClipBitmap);
+ canvas.drawCircle(mSize / 2, mSize / 2, mSize / 2 - padding, mPaint);
+ }
+
+ public void drawIcon(Shader icon, Canvas canvas) {
+ mPaint.setShader(icon);
+ canvas.drawBitmap(mCircleClipBitmap, -mSize / 2, -mSize / 2, mPaint);
+ mPaint.setShader(null);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/badge/FolderBadgeInfo.java b/src/com/android/launcher3/badge/FolderBadgeInfo.java
new file mode 100644
index 0000000..3a1bf60
--- /dev/null
+++ b/src/com/android/launcher3/badge/FolderBadgeInfo.java
@@ -0,0 +1,62 @@
+/*
+ * 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.badge;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * Subclass of BadgeInfo that only contains the badge count, which is
+ * the sum of all the Folder's items' notifications (each counts as 1).
+ */
+public class FolderBadgeInfo extends BadgeInfo {
+
+ private static final int MIN_COUNT = 0;
+
+ private int mNumNotifications;
+
+ public FolderBadgeInfo() {
+ super(null);
+ }
+
+ public void addBadgeInfo(BadgeInfo badgeToAdd) {
+ if (badgeToAdd == null) {
+ return;
+ }
+ mNumNotifications += badgeToAdd.getNotificationKeys().size();
+ mNumNotifications = Utilities.boundToRange(
+ mNumNotifications, MIN_COUNT, BadgeInfo.MAX_COUNT);
+ }
+
+ public void subtractBadgeInfo(BadgeInfo badgeToSubtract) {
+ if (badgeToSubtract == null) {
+ return;
+ }
+ mNumNotifications -= badgeToSubtract.getNotificationKeys().size();
+ mNumNotifications = Utilities.boundToRange(
+ mNumNotifications, MIN_COUNT, BadgeInfo.MAX_COUNT);
+ }
+
+ @Override
+ public int getNotificationCount() {
+ // This forces the folder badge to always show up as a dot.
+ return 0;
+ }
+
+ public boolean hasBadge() {
+ return mNumNotifications > 0;
+ }
+}
diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
index c7a529d..84e82e3 100644
--- a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
+++ b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
@@ -23,7 +23,7 @@
BaseIndex index = null;
try {
- if (Utilities.isNycOrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT) {
index = new AlphabeticIndexVN(context);
}
} catch (Exception e) {
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
index 811cacf..3efbbfb 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
@@ -22,14 +22,15 @@
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.os.UserHandle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
-import com.android.launcher3.IconCache;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.PackageUserKey;
import java.util.HashMap;
import java.util.List;
@@ -39,14 +40,13 @@
private static final Object sInstanceLock = new Object();
private static AppWidgetManagerCompat sInstance;
-
public static AppWidgetManagerCompat getInstance(Context context) {
synchronized (sInstanceLock) {
if (sInstance == null) {
- if (Utilities.ATLEAST_LOLLIPOP) {
- sInstance = new AppWidgetManagerCompatVL(context.getApplicationContext());
+ if (Utilities.isAtLeastO()) {
+ sInstance = new AppWidgetManagerCompatVO(context.getApplicationContext());
} else {
- sInstance = new AppWidgetManagerCompatV16(context.getApplicationContext());
+ sInstance = new AppWidgetManagerCompatVL(context.getApplicationContext());
}
}
return sInstance;
@@ -70,27 +70,17 @@
return info == null ? null : LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
}
- public abstract List<AppWidgetProviderInfo> getAllProviders();
-
- public abstract String loadLabel(LauncherAppWidgetProviderInfo info);
+ public abstract List<AppWidgetProviderInfo> getAllProviders(
+ @Nullable PackageUserKey packageUser);
public abstract boolean bindAppWidgetIdIfAllowed(
int appWidgetId, AppWidgetProviderInfo info, Bundle options);
- public abstract UserHandleCompat getUser(LauncherAppWidgetProviderInfo info);
-
public abstract void startConfigActivity(AppWidgetProviderInfo info, int widgetId,
Activity activity, AppWidgetHost host, int requestCode);
- public abstract Drawable loadPreview(AppWidgetProviderInfo info);
-
- public abstract Drawable loadIcon(LauncherAppWidgetProviderInfo info, IconCache cache);
-
- public abstract Bitmap getBadgeBitmap(LauncherAppWidgetProviderInfo info, Bitmap bitmap,
- int imageWidth, int imageHeight);
-
public abstract LauncherAppWidgetProviderInfo findProvider(
- ComponentName provider, UserHandleCompat user);
+ ComponentName provider, UserHandle user);
public abstract HashMap<ComponentKey, AppWidgetProviderInfo> getAllProvidersMap();
}
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
deleted file mode 100644
index de9414e..0000000
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import android.annotation.TargetApi;
-import android.app.Activity;
-import android.appwidget.AppWidgetHost;
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.os.Bundle;
-
-import com.android.launcher3.IconCache;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.util.ComponentKey;
-
-import java.util.HashMap;
-import java.util.List;
-
-class AppWidgetManagerCompatV16 extends AppWidgetManagerCompat {
-
- AppWidgetManagerCompatV16(Context context) {
- super(context);
- }
-
- @Override
- public List<AppWidgetProviderInfo> getAllProviders() {
- return mAppWidgetManager.getInstalledProviders();
- }
-
- @Override
- public String loadLabel(LauncherAppWidgetProviderInfo info) {
- return Utilities.trim(info.label);
- }
-
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
- @Override
- public boolean bindAppWidgetIdIfAllowed(int appWidgetId, AppWidgetProviderInfo info,
- Bundle options) {
- if (Utilities.ATLEAST_JB_MR1) {
- return mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.provider, options);
- } else {
- return mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.provider);
- }
- }
-
- @Override
- public UserHandleCompat getUser(LauncherAppWidgetProviderInfo info) {
- return UserHandleCompat.myUserHandle();
- }
-
- @Override
- public void startConfigActivity(AppWidgetProviderInfo info, int widgetId, Activity activity,
- AppWidgetHost host, int requestCode) {
- Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
- intent.setComponent(info.configure);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
- Utilities.startActivityForResultSafely(activity, intent, requestCode);
- }
-
- @Override
- public Drawable loadPreview(AppWidgetProviderInfo info) {
- return mContext.getPackageManager().getDrawable(
- info.provider.getPackageName(), info.previewImage, null);
- }
-
- @Override
- public Drawable loadIcon(LauncherAppWidgetProviderInfo info, IconCache cache) {
- return cache.getFullResIcon(info.provider.getPackageName(), info.icon);
- }
-
- @Override
- public Bitmap getBadgeBitmap(LauncherAppWidgetProviderInfo info, Bitmap bitmap,
- int imageWidth, int imageHeight) {
- return bitmap;
- }
-
- @Override
- public LauncherAppWidgetProviderInfo findProvider(
- ComponentName provider, UserHandleCompat user) {
- for (AppWidgetProviderInfo info : mAppWidgetManager.getInstalledProviders()) {
- if (info.provider.equals(provider)) {
- return LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
- }
- }
- return null;
- }
-
- @Override
- public HashMap<ComponentKey, AppWidgetProviderInfo> getAllProvidersMap() {
- HashMap<ComponentKey, AppWidgetProviderInfo> result = new HashMap<>();
- UserHandleCompat user = UserHandleCompat.myUserHandle();
- for (AppWidgetProviderInfo info : mAppWidgetManager.getInstalledProviders()) {
- result.put(new ComponentKey(info.provider, user), info);
- }
- return result;
- }
-}
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
index a1570e6..f239f5c 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
@@ -16,64 +16,59 @@
package com.android.launcher3.compat;
-import android.annotation.TargetApi;
import android.app.Activity;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
-import android.view.View;
+import android.support.annotation.Nullable;
import android.widget.Toast;
-import com.android.launcher3.IconCache;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.R;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.PackageUserKey;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
class AppWidgetManagerCompatVL extends AppWidgetManagerCompat {
private final UserManager mUserManager;
- private final PackageManager mPm;
AppWidgetManagerCompatVL(Context context) {
super(context);
- mPm = context.getPackageManager();
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
}
@Override
- public List<AppWidgetProviderInfo> getAllProviders() {
- ArrayList<AppWidgetProviderInfo> providers = new ArrayList<AppWidgetProviderInfo>();
- for (UserHandle user : mUserManager.getUserProfiles()) {
- providers.addAll(mAppWidgetManager.getInstalledProvidersForProfile(user));
+ public List<AppWidgetProviderInfo> getAllProviders(@Nullable PackageUserKey packageUser) {
+ if (packageUser == null) {
+ ArrayList<AppWidgetProviderInfo> providers = new ArrayList<AppWidgetProviderInfo>();
+ for (UserHandle user : mUserManager.getUserProfiles()) {
+ providers.addAll(mAppWidgetManager.getInstalledProvidersForProfile(user));
+ }
+ return providers;
+ }
+ // Only get providers for the given package/user.
+ List<AppWidgetProviderInfo> providers = new ArrayList<>(mAppWidgetManager
+ .getInstalledProvidersForProfile(packageUser.mUser));
+ Iterator<AppWidgetProviderInfo> iterator = providers.iterator();
+ while (iterator.hasNext()) {
+ if (!iterator.next().provider.getPackageName().equals(packageUser.mPackageName)) {
+ iterator.remove();
+ }
}
return providers;
}
@Override
- public String loadLabel(LauncherAppWidgetProviderInfo info) {
- return info.getLabel(mPm);
- }
-
- @Override
public boolean bindAppWidgetIdIfAllowed(int appWidgetId, AppWidgetProviderInfo info,
Bundle options) {
return mAppWidgetManager.bindAppWidgetIdIfAllowed(
@@ -81,14 +76,6 @@
}
@Override
- public UserHandleCompat getUser(LauncherAppWidgetProviderInfo info) {
- if (info.isCustomWidget) {
- return UserHandleCompat.myUserHandle();
- }
- return UserHandleCompat.fromUser(info.getProfile());
- }
-
- @Override
public void startConfigActivity(AppWidgetProviderInfo info, int widgetId, Activity activity,
AppWidgetHost host, int requestCode) {
try {
@@ -99,60 +86,9 @@
}
@Override
- public Drawable loadPreview(AppWidgetProviderInfo info) {
- return info.loadPreviewImage(mContext, 0);
- }
-
- @Override
- public Drawable loadIcon(LauncherAppWidgetProviderInfo info, IconCache cache) {
- return info.getIcon(mContext, cache);
- }
-
- @Override
- public Bitmap getBadgeBitmap(LauncherAppWidgetProviderInfo info, Bitmap bitmap,
- int imageWidth, int imageHeight) {
- if (info.isCustomWidget || info.getProfile().equals(android.os.Process.myUserHandle())) {
- return bitmap;
- }
-
- // Add a user badge in the bottom right of the image.
- final Resources res = mContext.getResources();
- final int badgeMinTop = res.getDimensionPixelSize(R.dimen.profile_badge_minimum_top);
-
- // choose min between badge size defined for widget tray versus width, height of the image.
- // Width, height of the image can be smaller than widget tray badge size when being dropped
- // to the workspace.
- final int badgeSize = Math.min(res.getDimensionPixelSize(R.dimen.profile_badge_size),
- Math.min(imageWidth, imageHeight - badgeMinTop));
- final Rect badgeLocation = new Rect(0, 0, badgeSize, badgeSize);
-
- final int top = Math.max(imageHeight - badgeSize, badgeMinTop);
-
- if (res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
- badgeLocation.offset(0, top);
- } else {
- badgeLocation.offset(bitmap.getWidth() - badgeSize, top);
- }
-
- Drawable drawable = mPm.getUserBadgedDrawableForDensity(
- new BitmapDrawable(res, bitmap), info.getProfile(), badgeLocation, 0);
-
- if (drawable instanceof BitmapDrawable) {
- return ((BitmapDrawable) drawable).getBitmap();
- }
-
- bitmap.eraseColor(Color.TRANSPARENT);
- Canvas c = new Canvas(bitmap);
- drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
- drawable.draw(c);
- c.setBitmap(null);
- return bitmap;
- }
-
- @Override
- public LauncherAppWidgetProviderInfo findProvider(ComponentName provider, UserHandleCompat user) {
+ public LauncherAppWidgetProviderInfo findProvider(ComponentName provider, UserHandle user) {
for (AppWidgetProviderInfo info : mAppWidgetManager
- .getInstalledProvidersForProfile(user.getUser())) {
+ .getInstalledProvidersForProfile(user)) {
if (info.provider.equals(provider)) {
return LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
}
@@ -164,10 +100,9 @@
public HashMap<ComponentKey, AppWidgetProviderInfo> getAllProvidersMap() {
HashMap<ComponentKey, AppWidgetProviderInfo> result = new HashMap<>();
for (UserHandle user : mUserManager.getUserProfiles()) {
- UserHandleCompat userHandle = UserHandleCompat.fromUser(user);
for (AppWidgetProviderInfo info :
mAppWidgetManager.getInstalledProvidersForProfile(user)) {
- result.put(new ComponentKey(info.provider, userHandle), info);
+ result.put(new ComponentKey(info.provider, user), info);
}
}
return result;
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java
new file mode 100644
index 0000000..1c48a13
--- /dev/null
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.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.launcher3.compat;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.support.annotation.Nullable;
+
+import com.android.launcher3.util.PackageUserKey;
+
+import java.util.List;
+
+class AppWidgetManagerCompatVO extends AppWidgetManagerCompatVL {
+
+ AppWidgetManagerCompatVO(Context context) {
+ super(context);
+ }
+
+ @Override
+ public List<AppWidgetProviderInfo> getAllProviders(@Nullable PackageUserKey packageUser) {
+ if (packageUser == null) {
+ return super.getAllProviders(null);
+ }
+ return mAppWidgetManager.getInstalledProvidersForPackage(packageUser.mPackageName,
+ packageUser.mUser);
+ }
+}
diff --git a/src/com/android/launcher3/compat/DeferredLauncherActivityInfo.java b/src/com/android/launcher3/compat/DeferredLauncherActivityInfo.java
deleted file mode 100644
index 46d36d1..0000000
--- a/src/com/android/launcher3/compat/DeferredLauncherActivityInfo.java
+++ /dev/null
@@ -1,83 +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.compat;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.graphics.drawable.Drawable;
-import android.util.Log;
-
-/**
- * {@link LauncherActivityInfoCompat} which loads its data only when needed.
- */
-public class DeferredLauncherActivityInfo extends LauncherActivityInfoCompat {
-
- private final ComponentName mComponent;
- private final UserHandleCompat mUser;
- private final Context mContext;
-
- private LauncherActivityInfoCompat mActualInfo;
-
- public DeferredLauncherActivityInfo(
- ComponentName component, UserHandleCompat user, Context context) {
- mComponent = component;
- mUser = user;
- mContext = context;
- }
-
- @Override
- public ComponentName getComponentName() {
- return mComponent;
- }
-
- @Override
- public UserHandleCompat getUser() {
- return mUser;
- }
-
- private synchronized LauncherActivityInfoCompat getActualInfo() {
- if (mActualInfo == null) {
- Intent intent = new Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_LAUNCHER)
- .setComponent(mComponent);
- mActualInfo = LauncherAppsCompat.getInstance(mContext).resolveActivity(intent, mUser);
- }
- return mActualInfo;
- }
-
- @Override
- public CharSequence getLabel() {
- return getActualInfo().getLabel();
- }
-
- @Override
- public Drawable getIcon(int density) {
- return getActualInfo().getIcon(density);
- }
-
- @Override
- public ApplicationInfo getApplicationInfo() {
- return getActualInfo().getApplicationInfo();
- }
-
- @Override
- public long getFirstInstallTime() {
- return getActualInfo().getFirstInstallTime();
- }
-}
diff --git a/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java b/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java
deleted file mode 100644
index 0bc9588..0000000
--- a/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.ResolveInfo;
-import android.graphics.drawable.Drawable;
-
-public abstract class LauncherActivityInfoCompat {
-
- LauncherActivityInfoCompat() {
- }
-
- public abstract ComponentName getComponentName();
- public abstract UserHandleCompat getUser();
- public abstract CharSequence getLabel();
- public abstract Drawable getIcon(int density);
- public abstract ApplicationInfo getApplicationInfo();
- public abstract long getFirstInstallTime();
-
- /**
- * Creates a LauncherActivityInfoCompat for the primary user.
- */
- public static LauncherActivityInfoCompat fromResolveInfo(ResolveInfo info, Context context) {
- return new LauncherActivityInfoCompatV16(context, info);
- }
-}
diff --git a/src/com/android/launcher3/compat/LauncherActivityInfoCompatV16.java b/src/com/android/launcher3/compat/LauncherActivityInfoCompatV16.java
deleted file mode 100644
index fee0376..0000000
--- a/src/com/android/launcher3/compat/LauncherActivityInfoCompatV16.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageInfo;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-
-
-public class LauncherActivityInfoCompatV16 extends LauncherActivityInfoCompat {
- private final ResolveInfo mResolveInfo;
- private final ActivityInfo mActivityInfo;
- private final ComponentName mComponentName;
- private final PackageManager mPm;
-
- LauncherActivityInfoCompatV16(Context context, ResolveInfo info) {
- super();
- mResolveInfo = info;
- mActivityInfo = info.activityInfo;
- mComponentName = new ComponentName(mActivityInfo.packageName, mActivityInfo.name);
- mPm = context.getPackageManager();
- }
-
- public ComponentName getComponentName() {
- return mComponentName;
- }
-
- public UserHandleCompat getUser() {
- return UserHandleCompat.myUserHandle();
- }
-
- public CharSequence getLabel() {
- return mResolveInfo.loadLabel(mPm);
- }
-
- public Drawable getIcon(int density) {
- int iconRes = mResolveInfo.getIconResource();
- Resources resources = null;
- Drawable icon = null;
- // Get the preferred density icon from the app's resources
- if (density != 0 && iconRes != 0) {
- try {
- resources = mPm.getResourcesForApplication(mActivityInfo.applicationInfo);
- icon = resources.getDrawableForDensity(iconRes, density);
- } catch (NameNotFoundException | Resources.NotFoundException exc) {
- }
- }
- // Get the default density icon
- if (icon == null) {
- icon = mResolveInfo.loadIcon(mPm);
- }
- if (icon == null) {
- resources = Resources.getSystem();
- icon = resources.getDrawableForDensity(android.R.mipmap.sym_def_app_icon, density);
- }
- return icon;
- }
-
- public ApplicationInfo getApplicationInfo() {
- return mActivityInfo.applicationInfo;
- }
-
- public long getFirstInstallTime() {
- try {
- PackageInfo info = mPm.getPackageInfo(mActivityInfo.packageName, 0);
- return info != null ? info.firstInstallTime : 0;
- } catch (NameNotFoundException e) {
- return 0;
- }
- }
-
- public String getName() {
- return mActivityInfo.name;
- }
-}
diff --git a/src/com/android/launcher3/compat/LauncherActivityInfoCompatVL.java b/src/com/android/launcher3/compat/LauncherActivityInfoCompatVL.java
deleted file mode 100644
index 67c5c27..0000000
--- a/src/com/android/launcher3/compat/LauncherActivityInfoCompatVL.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import android.annotation.TargetApi;
-import android.content.ComponentName;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.LauncherActivityInfo;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
-public class LauncherActivityInfoCompatVL extends LauncherActivityInfoCompat {
- private LauncherActivityInfo mLauncherActivityInfo;
-
- LauncherActivityInfoCompatVL(LauncherActivityInfo launcherActivityInfo) {
- super();
- mLauncherActivityInfo = launcherActivityInfo;
- }
-
- public ComponentName getComponentName() {
- return mLauncherActivityInfo.getComponentName();
- }
-
- public UserHandleCompat getUser() {
- return UserHandleCompat.fromUser(mLauncherActivityInfo.getUser());
- }
-
- public CharSequence getLabel() {
- return mLauncherActivityInfo.getLabel();
- }
-
- public Drawable getIcon(int density) {
- return mLauncherActivityInfo.getIcon(density);
- }
-
- public ApplicationInfo getApplicationInfo() {
- return mLauncherActivityInfo.getApplicationInfo();
- }
-
- public long getFirstInstallTime() {
- return mLauncherActivityInfo.getFirstInstallTime();
- }
-}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java
index 645e68a..e997a99 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompat.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java
@@ -19,28 +19,36 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.LauncherApps;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.os.UserHandle;
+import android.support.annotation.Nullable;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
+import com.android.launcher3.graphics.LauncherIcons;
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.util.LooperExecuter;
+import com.android.launcher3.util.PackageUserKey;
import java.util.List;
public abstract class LauncherAppsCompat {
public interface OnAppsChangedCallbackCompat {
- void onPackageRemoved(String packageName, UserHandleCompat user);
- void onPackageAdded(String packageName, UserHandleCompat user);
- void onPackageChanged(String packageName, UserHandleCompat user);
- void onPackagesAvailable(String[] packageNames, UserHandleCompat user, boolean replacing);
- void onPackagesUnavailable(String[] packageNames, UserHandleCompat user, boolean replacing);
- void onPackagesSuspended(String[] packageNames, UserHandleCompat user);
- void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user);
+ void onPackageRemoved(String packageName, UserHandle user);
+ void onPackageAdded(String packageName, UserHandle user);
+ void onPackageChanged(String packageName, UserHandle user);
+ void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing);
+ void onPackagesUnavailable(String[] packageNames, UserHandle user, boolean replacing);
+ void onPackagesSuspended(String[] packageNames, UserHandle user);
+ void onPackagesUnsuspended(String[] packageNames, UserHandle user);
void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
- UserHandleCompat user);
+ UserHandle user);
}
protected LauncherAppsCompat() {
@@ -52,27 +60,91 @@
public static LauncherAppsCompat getInstance(Context context) {
synchronized (sInstanceLock) {
if (sInstance == null) {
- if (Utilities.ATLEAST_LOLLIPOP) {
- sInstance = new LauncherAppsCompatVL(context.getApplicationContext());
+ if (Utilities.isAtLeastO()) {
+ sInstance = new LauncherAppsCompatVO(context.getApplicationContext());
} else {
- sInstance = new LauncherAppsCompatV16(context.getApplicationContext());
+ sInstance = new LauncherAppsCompatVL(context.getApplicationContext());
}
}
return sInstance;
}
}
- public abstract List<LauncherActivityInfoCompat> getActivityList(String packageName,
- UserHandleCompat user);
- public abstract LauncherActivityInfoCompat resolveActivity(Intent intent,
- UserHandleCompat user);
- public abstract void startActivityForProfile(ComponentName component, UserHandleCompat user,
+ public abstract List<LauncherActivityInfo> getActivityList(String packageName,
+ UserHandle user);
+ public abstract LauncherActivityInfo resolveActivity(Intent intent,
+ UserHandle user);
+ public abstract void startActivityForProfile(ComponentName component, UserHandle user,
Rect sourceBounds, Bundle opts);
- public abstract void showAppDetailsForProfile(ComponentName component, UserHandleCompat user);
+ public abstract ApplicationInfo getApplicationInfo(
+ String packageName, int flags, UserHandle user);
+ public abstract void showAppDetailsForProfile(ComponentName component, UserHandle user,
+ Rect sourceBounds, Bundle opts);
public abstract void addOnAppsChangedCallback(OnAppsChangedCallbackCompat listener);
public abstract void removeOnAppsChangedCallback(OnAppsChangedCallbackCompat listener);
- public abstract boolean isPackageEnabledForProfile(String packageName, UserHandleCompat user);
+ public abstract boolean isPackageEnabledForProfile(String packageName, UserHandle user);
public abstract boolean isActivityEnabledForProfile(ComponentName component,
- UserHandleCompat user);
- public abstract boolean isPackageSuspendedForProfile(String packageName, UserHandleCompat user);
+ UserHandle user);
+ public abstract List<ShortcutConfigActivityInfo> getCustomShortcutActivityList(
+ @Nullable PackageUserKey packageUser);
+
+ /**
+ * request.accept() will initiate the following flow:
+ * -> go-to-system-process for actual processing (a)
+ * -> callback-to-launcher on UI thread (b)
+ * -> post callback on the worker thread (c)
+ * -> Update model and unpin (in system) any shortcut not in out model. (d)
+ *
+ * Note that (b) will take at-least one frame as it involves posting callback from binder
+ * thread to UI thread.
+ * If (d) happens before we add this shortcut to our model, we will end up unpinning
+ * the shortcut in the system.
+ * Here its the caller's responsibility to add the newly created ShortcutInfo immediately
+ * to the model (which may involves a single post-to-worker-thread). That will guarantee
+ * that (d) happens after model is updated.
+ */
+ @Nullable
+ public static ShortcutInfo createShortcutInfoFromPinItemRequest(
+ Context context, final PinItemRequestCompat request, final long acceptDelay) {
+ if (request != null &&
+ request.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_SHORTCUT &&
+ request.isValid()) {
+
+ if (acceptDelay <= 0) {
+ if (!request.accept()) {
+ return null;
+ }
+ } else {
+ // Block the worker thread until the accept() is called.
+ new LooperExecuter(LauncherModel.getWorkerLooper()).execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(acceptDelay);
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ if (request.isValid()) {
+ request.accept();
+ }
+ }
+ });
+ }
+
+ ShortcutInfoCompat compat = new ShortcutInfoCompat(request.getShortcutInfo());
+ ShortcutInfo info = new ShortcutInfo(compat, context);
+ // Apply the unbadged icon and fetch the actual icon asynchronously.
+ info.iconBitmap = LauncherIcons
+ .createShortcutIcon(compat, context, false /* badged */);
+ LauncherAppState.getInstance(context).getModel()
+ .updateAndBindShortcutInfo(info, compat);
+ return info;
+ } else {
+ return null;
+ }
+ }
+
+ public void showAppDetailsForProfile(ComponentName component, UserHandle user) {
+ showAppDetailsForProfile(component, user, null, null);
+ }
}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
deleted file mode 100644
index 49a0df6..0000000
--- a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ActivityInfo;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.Settings;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.shortcuts.ShortcutInfoCompat;
-import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.util.Thunk;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Version of {@link LauncherAppsCompat} for devices with API level 16.
- * Devices Pre-L don't support multiple profiles in one launcher so
- * user parameters are ignored and all methods operate on the current user.
- */
-public class LauncherAppsCompatV16 extends LauncherAppsCompat {
-
- private PackageManager mPm;
- private Context mContext;
- private List<OnAppsChangedCallbackCompat> mCallbacks
- = new ArrayList<OnAppsChangedCallbackCompat>();
- private PackageMonitor mPackageMonitor;
-
- LauncherAppsCompatV16(Context context) {
- mPm = context.getPackageManager();
- mContext = context;
- mPackageMonitor = new PackageMonitor();
- }
-
- public List<LauncherActivityInfoCompat> getActivityList(String packageName,
- UserHandleCompat user) {
- final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
- mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
- mainIntent.setPackage(packageName);
- List<ResolveInfo> infos = mPm.queryIntentActivities(mainIntent, 0);
- List<LauncherActivityInfoCompat> list =
- new ArrayList<LauncherActivityInfoCompat>(infos.size());
- for (ResolveInfo info : infos) {
- list.add(new LauncherActivityInfoCompatV16(mContext, info));
- }
- return list;
- }
-
- public LauncherActivityInfoCompat resolveActivity(Intent intent, UserHandleCompat user) {
- ResolveInfo info = mPm.resolveActivity(intent, 0);
- if (info != null) {
- return new LauncherActivityInfoCompatV16(mContext, info);
- }
- return null;
- }
-
- public void startActivityForProfile(ComponentName component, UserHandleCompat user,
- Rect sourceBounds, Bundle opts) {
- Intent launchIntent = new Intent(Intent.ACTION_MAIN);
- launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
- launchIntent.setComponent(component);
- launchIntent.setSourceBounds(sourceBounds);
- launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(launchIntent, opts);
- }
-
- public void showAppDetailsForProfile(ComponentName component, UserHandleCompat user) {
- String packageName = component.getPackageName();
- Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
- Uri.fromParts("package", packageName, null));
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK |
- Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- mContext.startActivity(intent, null);
- }
-
- public synchronized void addOnAppsChangedCallback(OnAppsChangedCallbackCompat callback) {
- if (callback != null && !mCallbacks.contains(callback)) {
- mCallbacks.add(callback);
- if (mCallbacks.size() == 1) {
- registerForPackageIntents();
- }
- }
- }
-
- public synchronized void removeOnAppsChangedCallback(OnAppsChangedCallbackCompat callback) {
- mCallbacks.remove(callback);
- if (mCallbacks.size() == 0) {
- unregisterForPackageIntents();
- }
- }
-
- public boolean isPackageEnabledForProfile(String packageName, UserHandleCompat user) {
- return PackageManagerHelper.isAppEnabled(mPm, packageName);
- }
-
- public boolean isActivityEnabledForProfile(ComponentName component, UserHandleCompat user) {
- try {
- ActivityInfo info = mPm.getActivityInfo(component, 0);
- return info != null && info.isEnabled();
- } catch (NameNotFoundException e) {
- return false;
- }
- }
-
- public boolean isPackageSuspendedForProfile(String packageName, UserHandleCompat user) {
- return false;
- }
-
- private void unregisterForPackageIntents() {
- mContext.unregisterReceiver(mPackageMonitor);
- }
-
- private void registerForPackageIntents() {
- IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
- filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- filter.addDataScheme("package");
- mContext.registerReceiver(mPackageMonitor, filter);
- filter = new IntentFilter();
- filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
- filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
- mContext.registerReceiver(mPackageMonitor, filter);
- }
-
- @Thunk synchronized List<OnAppsChangedCallbackCompat> getCallbacks() {
- return new ArrayList<OnAppsChangedCallbackCompat>(mCallbacks);
- }
-
- @Thunk class PackageMonitor extends BroadcastReceiver {
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- final UserHandleCompat user = UserHandleCompat.myUserHandle();
-
- if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
- || Intent.ACTION_PACKAGE_REMOVED.equals(action)
- || Intent.ACTION_PACKAGE_ADDED.equals(action)) {
- final String packageName = intent.getData().getSchemeSpecificPart();
- final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
-
- if (packageName == null || packageName.length() == 0) {
- // they sent us a bad intent
- return;
- }
- if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
- for (OnAppsChangedCallbackCompat callback : getCallbacks()) {
- callback.onPackageChanged(packageName, user);
- }
- } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
- if (!replacing) {
- for (OnAppsChangedCallbackCompat callback : getCallbacks()) {
- callback.onPackageRemoved(packageName, user);
- }
- }
- // else, we are replacing the package, so a PACKAGE_ADDED will be sent
- // later, we will update the package at this time
- } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
- if (!replacing) {
- for (OnAppsChangedCallbackCompat callback : getCallbacks()) {
- callback.onPackageAdded(packageName, user);
- }
- } else {
- for (OnAppsChangedCallbackCompat callback : getCallbacks()) {
- callback.onPackageChanged(packageName, user);
- }
- }
- }
- } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
- // EXTRA_REPLACING is available Kitkat onwards. For lower devices, it is broadcasted
- // when moving a package or mounting/un-mounting external storage. Assume that
- // it is a replacing operation.
- final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING,
- !Utilities.ATLEAST_KITKAT);
- String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
- for (OnAppsChangedCallbackCompat callback : getCallbacks()) {
- callback.onPackagesAvailable(packages, user, replacing);
- }
- } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
- // This intent is broadcasted when moving a package or mounting/un-mounting
- // external storage.
- // However on Kitkat this is also sent when a package is being updated, and
- // contains an extra Intent.EXTRA_REPLACING=true for that case.
- // Using false as default for Intent.EXTRA_REPLACING gives correct value on
- // lower devices as the intent is not sent when the app is updating/replacing.
- final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
- String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
- for (OnAppsChangedCallbackCompat callback : getCallbacks()) {
- callback.onPackagesUnavailable(packages, user, replacing);
- }
- }
- }
- }
-}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
index d97bf2f..647c315 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
@@ -16,72 +16,91 @@
package com.android.launcher3.compat;
-import android.annotation.TargetApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.graphics.Rect;
-import android.os.Build;
import android.os.Bundle;
+import android.os.Process;
import android.os.UserHandle;
+import android.support.annotation.Nullable;
+import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVL;
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.util.PackageUserKey;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
-public class LauncherAppsCompatVL extends LauncherAppsCompatV16 {
+public class LauncherAppsCompatVL extends LauncherAppsCompat {
- protected LauncherApps mLauncherApps;
+ protected final LauncherApps mLauncherApps;
+ protected final Context mContext;
- private Map<OnAppsChangedCallbackCompat, WrappedCallback> mCallbacks
- = new HashMap<OnAppsChangedCallbackCompat, WrappedCallback>();
+ private Map<OnAppsChangedCallbackCompat, WrappedCallback> mCallbacks = new HashMap<>();
LauncherAppsCompatVL(Context context) {
- super(context);
- mLauncherApps = (LauncherApps) context.getSystemService("launcherapps");
+ mContext = context;
+ mLauncherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
}
- public List<LauncherActivityInfoCompat> getActivityList(String packageName,
- UserHandleCompat user) {
- List<LauncherActivityInfo> list = mLauncherApps.getActivityList(packageName,
- user.getUser());
- if (list.size() == 0) {
- return Collections.emptyList();
- }
- ArrayList<LauncherActivityInfoCompat> compatList =
- new ArrayList<LauncherActivityInfoCompat>(list.size());
- for (LauncherActivityInfo info : list) {
- compatList.add(new LauncherActivityInfoCompatVL(info));
- }
- return compatList;
+ @Override
+ public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
+ return mLauncherApps.getActivityList(packageName, user);
}
- public LauncherActivityInfoCompat resolveActivity(Intent intent, UserHandleCompat user) {
- LauncherActivityInfo activity = mLauncherApps.resolveActivity(intent, user.getUser());
- if (activity != null) {
- return new LauncherActivityInfoCompatVL(activity);
- } else {
+ @Override
+ public LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
+ return mLauncherApps.resolveActivity(intent, user);
+ }
+
+ @Override
+ public void startActivityForProfile(ComponentName component, UserHandle user,
+ Rect sourceBounds, Bundle opts) {
+ mLauncherApps.startMainActivity(component, user, sourceBounds, opts);
+ }
+
+ @Override
+ public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user) {
+ final boolean isPrimaryUser = Process.myUserHandle().equals(user);
+ if (!isPrimaryUser && (flags == 0)) {
+ // We are looking for an installed app on a secondary profile. Prior to O, the only
+ // entry point for work profiles is through the LauncherActivity.
+ List<LauncherActivityInfo> activityList =
+ mLauncherApps.getActivityList(packageName, user);
+ return activityList.size() > 0 ? activityList.get(0).getApplicationInfo() : null;
+ }
+ try {
+ ApplicationInfo info =
+ mContext.getPackageManager().getApplicationInfo(packageName, flags);
+ // There is no way to check if the app is installed for managed profile. But for
+ // primary profile, we can still have this check.
+ if (isPrimaryUser && ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0)
+ || !info.enabled) {
+ return null;
+ }
+ return info;
+ } catch (PackageManager.NameNotFoundException e) {
+ // Package not found
return null;
}
}
- public void startActivityForProfile(ComponentName component, UserHandleCompat user,
+ @Override
+ public void showAppDetailsForProfile(ComponentName component, UserHandle user,
Rect sourceBounds, Bundle opts) {
- mLauncherApps.startMainActivity(component, user.getUser(), sourceBounds, opts);
+ mLauncherApps.startAppDetailsActivity(component, user, sourceBounds, opts);
}
- public void showAppDetailsForProfile(ComponentName component, UserHandleCompat user) {
- mLauncherApps.startAppDetailsActivity(component, user.getUser(), null, null);
- }
-
+ @Override
public void addOnAppsChangedCallback(LauncherAppsCompat.OnAppsChangedCallbackCompat callback) {
WrappedCallback wrappedCallback = new WrappedCallback(callback);
synchronized (mCallbacks) {
@@ -90,9 +109,9 @@
mLauncherApps.registerCallback(wrappedCallback);
}
- public void removeOnAppsChangedCallback(
- LauncherAppsCompat.OnAppsChangedCallbackCompat callback) {
- WrappedCallback wrappedCallback = null;
+ @Override
+ public void removeOnAppsChangedCallback(OnAppsChangedCallbackCompat callback) {
+ final WrappedCallback wrappedCallback;
synchronized (mCallbacks) {
wrappedCallback = mCallbacks.remove(callback);
}
@@ -101,16 +120,14 @@
}
}
- public boolean isPackageEnabledForProfile(String packageName, UserHandleCompat user) {
- return mLauncherApps.isPackageEnabled(packageName, user.getUser());
+ @Override
+ public boolean isPackageEnabledForProfile(String packageName, UserHandle user) {
+ return mLauncherApps.isPackageEnabled(packageName, user);
}
- public boolean isActivityEnabledForProfile(ComponentName component, UserHandleCompat user) {
- return mLauncherApps.isActivityEnabled(component, user.getUser());
- }
-
- public boolean isPackageSuspendedForProfile(String packageName, UserHandleCompat user) {
- return false;
+ @Override
+ public boolean isActivityEnabledForProfile(ComponentName component, UserHandle user) {
+ return mLauncherApps.isActivityEnabled(component, user);
}
private static class WrappedCallback extends LauncherApps.Callback {
@@ -121,36 +138,34 @@
}
public void onPackageRemoved(String packageName, UserHandle user) {
- mCallback.onPackageRemoved(packageName, UserHandleCompat.fromUser(user));
+ mCallback.onPackageRemoved(packageName, user);
}
public void onPackageAdded(String packageName, UserHandle user) {
- mCallback.onPackageAdded(packageName, UserHandleCompat.fromUser(user));
+ mCallback.onPackageAdded(packageName, user);
}
public void onPackageChanged(String packageName, UserHandle user) {
- mCallback.onPackageChanged(packageName, UserHandleCompat.fromUser(user));
+ mCallback.onPackageChanged(packageName, user);
}
public void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing) {
- mCallback.onPackagesAvailable(packageNames, UserHandleCompat.fromUser(user), replacing);
+ mCallback.onPackagesAvailable(packageNames, user, replacing);
}
public void onPackagesUnavailable(String[] packageNames, UserHandle user,
boolean replacing) {
- mCallback.onPackagesUnavailable(packageNames, UserHandleCompat.fromUser(user),
- replacing);
+ mCallback.onPackagesUnavailable(packageNames, user, replacing);
}
public void onPackagesSuspended(String[] packageNames, UserHandle user) {
- mCallback.onPackagesSuspended(packageNames, UserHandleCompat.fromUser(user));
+ mCallback.onPackagesSuspended(packageNames, user);
}
public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
- mCallback.onPackagesUnsuspended(packageNames, UserHandleCompat.fromUser(user));
+ mCallback.onPackagesUnsuspended(packageNames, user);
}
- @Override
public void onShortcutsChanged(String packageName, List<ShortcutInfo> shortcuts,
UserHandle user) {
List<ShortcutInfoCompat> shortcutInfoCompats = new ArrayList<>(shortcuts.size());
@@ -158,9 +173,26 @@
shortcutInfoCompats.add(new ShortcutInfoCompat(shortcutInfo));
}
- mCallback.onShortcutsChanged(packageName, shortcutInfoCompats,
- UserHandleCompat.fromUser(user));
+ mCallback.onShortcutsChanged(packageName, shortcutInfoCompats, user);
}
}
+
+ @Override
+ public List<ShortcutConfigActivityInfo> getCustomShortcutActivityList(
+ @Nullable PackageUserKey packageUser) {
+ List<ShortcutConfigActivityInfo> result = new ArrayList<>();
+ if (packageUser != null && !packageUser.mUser.equals(Process.myUserHandle())) {
+ return result;
+ }
+ PackageManager pm = mContext.getPackageManager();
+ for (ResolveInfo info :
+ pm.queryIntentActivities(new Intent(Intent.ACTION_CREATE_SHORTCUT), 0)) {
+ if (packageUser == null || packageUser.mPackageName
+ .equals(info.activityInfo.packageName)) {
+ result.add(new ShortcutConfigActivityInfoVL(info.activityInfo, pm));
+ }
+ }
+ return result;
+ }
}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
new file mode 100644
index 0000000..d145539
--- /dev/null
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
@@ -0,0 +1,97 @@
+/*
+ * 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.compat;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVO;
+import com.android.launcher3.util.PackageUserKey;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+@TargetApi(26)
+public class LauncherAppsCompatVO extends LauncherAppsCompatVL {
+
+ LauncherAppsCompatVO(Context context) {
+ super(context);
+ }
+
+ @Override
+ public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user) {
+ try {
+ // TODO: Temporary workaround until the API signature is updated
+ if (false) {
+ throw new PackageManager.NameNotFoundException();
+ }
+
+ ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, flags, user);
+ return (info.flags & ApplicationInfo.FLAG_INSTALLED) == 0 || !info.enabled
+ ? null : info;
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ @Override
+ public List<ShortcutConfigActivityInfo> getCustomShortcutActivityList(
+ @Nullable PackageUserKey packageUser) {
+ List<ShortcutConfigActivityInfo> result = new ArrayList<>();
+ UserHandle myUser = Process.myUserHandle();
+
+ try {
+ Method m = LauncherApps.class.getDeclaredMethod("getShortcutConfigActivityList",
+ String.class, UserHandle.class);
+ final List<UserHandle> users;
+ final String packageName;
+ if (packageUser == null) {
+ users = UserManagerCompat.getInstance(mContext).getUserProfiles();
+ packageName = null;
+ } else {
+ users = new ArrayList<>(1);
+ users.add(packageUser.mUser);
+ packageName = packageUser.mPackageName;
+ }
+ for (UserHandle user : users) {
+ boolean ignoreTargetSdk = myUser.equals(user);
+ List<LauncherActivityInfo> activities =
+ (List<LauncherActivityInfo>) m.invoke(mLauncherApps, packageName, user);
+ for (LauncherActivityInfo activityInfo : activities) {
+ if (ignoreTargetSdk || activityInfo.getApplicationInfo().targetSdkVersion >=
+ Build.VERSION_CODES.O) {
+ result.add(new ShortcutConfigActivityInfoVO(activityInfo));
+ }
+ }
+ }
+ } catch (Exception e) {
+ Log.e("LauncherAppsCompatVO", "Error calling new API", e);
+ }
+
+ return result;
+ }
+}
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompat.java b/src/com/android/launcher3/compat/PackageInstallerCompat.java
index ec5014d..c7fe0ce 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompat.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompat.java
@@ -18,8 +18,6 @@
import android.content.Context;
-import com.android.launcher3.Utilities;
-
import java.util.HashMap;
public abstract class PackageInstallerCompat {
@@ -34,11 +32,7 @@
public static PackageInstallerCompat getInstance(Context context) {
synchronized (sInstanceLock) {
if (sInstance == null) {
- if (Utilities.ATLEAST_LOLLIPOP) {
- sInstance = new PackageInstallerCompatVL(context);
- } else {
- sInstance = new PackageInstallerCompatV16();
- }
+ sInstance = new PackageInstallerCompatVL(context);
}
return sInstance;
}
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatV16.java b/src/com/android/launcher3/compat/PackageInstallerCompatV16.java
deleted file mode 100644
index 654e349..0000000
--- a/src/com/android/launcher3/compat/PackageInstallerCompatV16.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import java.util.HashMap;
-
-public class PackageInstallerCompatV16 extends PackageInstallerCompat {
-
- PackageInstallerCompatV16() { }
-
- @Override
- public void onStop() { }
-
- @Override
- public HashMap<String, Integer> updateAndGetActiveSessionCache() {
- return new HashMap<>();
- }
-}
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
index 4aa667e..b87582f 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
@@ -16,13 +16,13 @@
package com.android.launcher3.compat;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionCallback;
import android.content.pm.PackageInstaller.SessionInfo;
-import android.os.Build;
import android.os.Handler;
+import android.os.Process;
+import android.os.UserHandle;
import android.util.SparseArray;
import com.android.launcher3.IconCache;
@@ -32,7 +32,6 @@
import java.util.HashMap;
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class PackageInstallerCompatVL extends PackageInstallerCompat {
@Thunk final SparseArray<String> mActiveSessions = new SparseArray<>();
@@ -43,7 +42,7 @@
PackageInstallerCompatVL(Context context) {
mInstaller = context.getPackageManager().getPackageInstaller();
- mCache = LauncherAppState.getInstance().getIconCache();
+ mCache = LauncherAppState.getInstance(context).getIconCache();
mWorker = new Handler(LauncherModel.getWorkerLooper());
mInstaller.registerSessionCallback(mCallback, mWorker);
@@ -52,9 +51,9 @@
@Override
public HashMap<String, Integer> updateAndGetActiveSessionCache() {
HashMap<String, Integer> activePackages = new HashMap<>();
- UserHandleCompat user = UserHandleCompat.myUserHandle();
+ UserHandle user = Process.myUserHandle();
for (SessionInfo info : mInstaller.getAllSessions()) {
- addSessionInfoToCahce(info, user);
+ addSessionInfoToCache(info, user);
if (info.getAppPackageName() != null) {
activePackages.put(info.getAppPackageName(), (int) (info.getProgress() * 100));
mActiveSessions.put(info.getSessionId(), info.getAppPackageName());
@@ -63,7 +62,7 @@
return activePackages;
}
- @Thunk void addSessionInfoToCahce(SessionInfo info, UserHandleCompat user) {
+ @Thunk void addSessionInfoToCache(SessionInfo info, UserHandle user) {
String packageName = info.getAppPackageName();
if (packageName != null) {
mCache.cachePackageInstallInfo(packageName, user, info.getAppIcon(),
@@ -124,7 +123,7 @@
private void pushSessionDisplayToLauncher(int sessionId) {
SessionInfo session = mInstaller.getSessionInfo(sessionId);
if (session != null && session.getAppPackageName() != null) {
- addSessionInfoToCahce(session, UserHandleCompat.myUserHandle());
+ addSessionInfoToCache(session, Process.myUserHandle());
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
if (app != null) {
diff --git a/src/com/android/launcher3/compat/PinItemRequestCompat.java b/src/com/android/launcher3/compat/PinItemRequestCompat.java
new file mode 100644
index 0000000..1308cba
--- /dev/null
+++ b/src/com/android/launcher3/compat/PinItemRequestCompat.java
@@ -0,0 +1,126 @@
+/*
+ * 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.compat;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * A wrapper around platform implementation of PinItemRequestCompat until the
+ * updated SDK is available.
+ */
+public class PinItemRequestCompat implements Parcelable {
+
+ public static final String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
+
+ public static final int REQUEST_TYPE_SHORTCUT = 1;
+ public static final int REQUEST_TYPE_APPWIDGET = 2;
+
+ private final Parcelable mObject;
+
+ private PinItemRequestCompat(Parcelable object) {
+ mObject = object;
+ }
+
+ public int getRequestType() {
+ return (Integer) invokeMethod("getRequestType");
+ }
+
+ public ShortcutInfo getShortcutInfo() {
+ return (ShortcutInfo) invokeMethod("getShortcutInfo");
+ }
+
+ public AppWidgetProviderInfo getAppWidgetProviderInfo(Context context) {
+ try {
+ return (AppWidgetProviderInfo) mObject.getClass()
+ .getDeclaredMethod("getAppWidgetProviderInfo", Context.class)
+ .invoke(mObject, context);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public boolean isValid() {
+ return (Boolean) invokeMethod("isValid");
+ }
+
+ public boolean accept() {
+ return (Boolean) invokeMethod("accept");
+ }
+
+ public boolean accept(Bundle options) {
+ try {
+ return (Boolean) mObject.getClass().getDeclaredMethod("accept", Bundle.class)
+ .invoke(mObject, options);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public Bundle getExtras() {
+ try {
+ return (Bundle) mObject.getClass().getDeclaredMethod("getExtras").invoke(mObject);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ private Object invokeMethod(String methodName) {
+ try {
+ return mObject.getClass().getDeclaredMethod(methodName).invoke(mObject);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int i) {
+ parcel.writeParcelable(mObject, i);
+ }
+
+ public static final Parcelable.Creator<PinItemRequestCompat> CREATOR =
+ new Parcelable.Creator<PinItemRequestCompat>() {
+ public PinItemRequestCompat createFromParcel(Parcel source) {
+ Parcelable object = source.readParcelable(null);
+ return new PinItemRequestCompat(object);
+ }
+
+ public PinItemRequestCompat[] newArray(int size) {
+ return new PinItemRequestCompat[size];
+ }
+ };
+
+ public static PinItemRequestCompat getPinItemRequest(Intent intent) {
+ if (!Utilities.isAtLeastO()) {
+ return null;
+ }
+ Parcelable extra = intent.getParcelableExtra(EXTRA_PIN_ITEM_REQUEST);
+ return extra == null ? null : new PinItemRequestCompat(extra);
+ }
+}
diff --git a/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java b/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java
new file mode 100644
index 0000000..4a55e8c
--- /dev/null
+++ b/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java
@@ -0,0 +1,167 @@
+/*
+ * 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.compat;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.launcher3.IconCache;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.R;
+import com.android.launcher3.ShortcutInfo;
+
+import java.lang.reflect.Method;
+
+/**
+ * Wrapper class for representing a shortcut configure activity.
+ */
+public abstract class ShortcutConfigActivityInfo {
+
+ private static final String TAG = "SCActivityInfo";
+
+ private final ComponentName mCn;
+ private final UserHandle mUser;
+
+ protected ShortcutConfigActivityInfo(ComponentName cn, UserHandle user) {
+ mCn = cn;
+ mUser = user;
+ }
+
+ public ComponentName getComponent() {
+ return mCn;
+ }
+
+ public UserHandle getUser() {
+ return mUser;
+ }
+
+ public int getItemType() {
+ return LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+ }
+
+ public abstract CharSequence getLabel();
+
+ public abstract Drawable getFullResIcon(IconCache cache);
+
+ /**
+ * Return a shortcut info, if it can be created directly on drop, without requiring any
+ * {@link #startConfigActivity(Activity, int)}.
+ */
+ public ShortcutInfo createShortcutInfo() {
+ return null;
+ }
+
+ public boolean startConfigActivity(Activity activity, int requestCode) {
+ Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT)
+ .setComponent(getComponent());
+ try {
+ activity.startActivityForResult(intent, requestCode);
+ return true;
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+ } catch (SecurityException e) {
+ Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+ Log.e(TAG, "Launcher does not have the permission to launch " + intent +
+ ". Make sure to create a MAIN intent-filter for the corresponding activity " +
+ "or use the exported attribute for this activity.", e);
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if various properties ({@link #getLabel()}, {@link #getFullResIcon}) can
+ * be safely persisted.
+ */
+ public boolean isPersistable() {
+ return true;
+ }
+
+ static class ShortcutConfigActivityInfoVL extends ShortcutConfigActivityInfo {
+
+ private final ActivityInfo mInfo;
+ private final PackageManager mPm;
+
+
+ public ShortcutConfigActivityInfoVL(ActivityInfo info, PackageManager pm) {
+ super(new ComponentName(info.packageName, info.name), Process.myUserHandle());
+ mInfo = info;
+ mPm = pm;
+ }
+
+ @Override
+ public CharSequence getLabel() {
+ return mInfo.loadLabel(mPm);
+ }
+
+ @Override
+ public Drawable getFullResIcon(IconCache cache) {
+ return cache.getFullResIcon(mInfo);
+ }
+ }
+
+ @TargetApi(26)
+ static class ShortcutConfigActivityInfoVO extends ShortcutConfigActivityInfo {
+
+ private final LauncherActivityInfo mInfo;
+
+ public ShortcutConfigActivityInfoVO(LauncherActivityInfo info) {
+ super(info.getComponentName(), info.getUser());
+ mInfo = info;
+ }
+
+ @Override
+ public CharSequence getLabel() {
+ return mInfo.getLabel();
+ }
+
+ @Override
+ public Drawable getFullResIcon(IconCache cache) {
+ return cache.getFullResIcon(mInfo);
+ }
+
+ @Override
+ public boolean startConfigActivity(Activity activity, int requestCode) {
+ if (getUser().equals(Process.myUserHandle())) {
+ return super.startConfigActivity(activity, requestCode);
+ }
+ try {
+ Method m = LauncherApps.class.getDeclaredMethod(
+ "getShortcutConfigActivityIntent", LauncherActivityInfo.class);
+ IntentSender is = (IntentSender) m.invoke(
+ activity.getSystemService(LauncherApps.class), mInfo);
+ activity.startIntentSenderForResult(is, requestCode, null, 0, 0, 0);
+ return true;
+ } catch (Exception e) {
+ Log.e(TAG, "Error calling new API", e);
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/compat/UserHandleCompat.java b/src/com/android/launcher3/compat/UserHandleCompat.java
deleted file mode 100644
index 50af21b..0000000
--- a/src/com/android/launcher3/compat/UserHandleCompat.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import android.annotation.TargetApi;
-import android.content.Intent;
-import android.os.Build;
-import android.os.UserHandle;
-import com.android.launcher3.Utilities;
-
-public class UserHandleCompat {
- private UserHandle mUser;
-
- private UserHandleCompat(UserHandle user) {
- mUser = user;
- }
-
- private UserHandleCompat() {
- }
-
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
- public static UserHandleCompat myUserHandle() {
- if (Utilities.ATLEAST_JB_MR1) {
- return new UserHandleCompat(android.os.Process.myUserHandle());
- } else {
- return new UserHandleCompat();
- }
- }
-
- public static UserHandleCompat fromUser(UserHandle user) {
- if (user == null) {
- return null;
- } else {
- return new UserHandleCompat(user);
- }
- }
-
- public UserHandle getUser() {
- return mUser;
- }
-
- @Override
- public String toString() {
- if (Utilities.ATLEAST_JB_MR1) {
- return mUser.toString();
- } else {
- return "";
- }
- }
-
- @Override
- public boolean equals(Object other) {
- if (!(other instanceof UserHandleCompat)) {
- return false;
- }
- if (Utilities.ATLEAST_JB_MR1) {
- return mUser.equals(((UserHandleCompat) other).mUser);
- } else {
- return true;
- }
- }
-
- @Override
- public int hashCode() {
- if (Utilities.ATLEAST_JB_MR1) {
- return mUser.hashCode();
- } else {
- return 0;
- }
- }
-
- /**
- * Adds {@link UserHandle} to the intent in for L or above.
- * Pre-L the launcher doesn't support showing apps for multiple
- * profiles so this is a no-op.
- */
- public void addToIntent(Intent intent, String name) {
- if (Utilities.ATLEAST_LOLLIPOP && mUser != null) {
- intent.putExtra(name, mUser);
- }
- }
-
- public static UserHandleCompat fromIntent(Intent intent) {
- if (Utilities.ATLEAST_LOLLIPOP) {
- UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
- if (user != null) {
- return UserHandleCompat.fromUser(user);
- }
- }
- return null;
- }
-}
diff --git a/src/com/android/launcher3/compat/UserManagerCompat.java b/src/com/android/launcher3/compat/UserManagerCompat.java
index a5f8dd2..25808d2 100644
--- a/src/com/android/launcher3/compat/UserManagerCompat.java
+++ b/src/com/android/launcher3/compat/UserManagerCompat.java
@@ -17,6 +17,7 @@
package com.android.launcher3.compat;
import android.content.Context;
+import android.os.UserHandle;
import com.android.launcher3.Utilities;
@@ -32,18 +33,14 @@
public static UserManagerCompat getInstance(Context context) {
synchronized (sInstanceLock) {
if (sInstance == null) {
- if (Utilities.isNycMR1OrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT_MR1) {
sInstance = new UserManagerCompatVNMr1(context.getApplicationContext());
- } else if (Utilities.isNycOrAbove()) {
+ } else if (Utilities.ATLEAST_NOUGAT) {
sInstance = new UserManagerCompatVN(context.getApplicationContext());
} else if (Utilities.ATLEAST_MARSHMALLOW) {
sInstance = new UserManagerCompatVM(context.getApplicationContext());
- } else if (Utilities.ATLEAST_LOLLIPOP) {
- sInstance = new UserManagerCompatVL(context.getApplicationContext());
- } else if (Utilities.ATLEAST_JB_MR1) {
- sInstance = new UserManagerCompatV17(context.getApplicationContext());
} else {
- sInstance = new UserManagerCompatV16();
+ sInstance = new UserManagerCompatVL(context.getApplicationContext());
}
}
return sInstance;
@@ -55,13 +52,13 @@
*/
public abstract void enableAndResetCache();
- public abstract List<UserHandleCompat> getUserProfiles();
- public abstract long getSerialNumberForUser(UserHandleCompat user);
- public abstract UserHandleCompat getUserForSerialNumber(long serialNumber);
- public abstract CharSequence getBadgedLabelForUser(CharSequence label, UserHandleCompat user);
- public abstract long getUserCreationTime(UserHandleCompat user);
- public abstract boolean isQuietModeEnabled(UserHandleCompat user);
- public abstract boolean isUserUnlocked(UserHandleCompat user);
+ public abstract List<UserHandle> getUserProfiles();
+ public abstract long getSerialNumberForUser(UserHandle user);
+ public abstract UserHandle getUserForSerialNumber(long serialNumber);
+ public abstract CharSequence getBadgedLabelForUser(CharSequence label, UserHandle user);
+ public abstract long getUserCreationTime(UserHandle user);
+ public abstract boolean isQuietModeEnabled(UserHandle user);
+ public abstract boolean isUserUnlocked(UserHandle user);
public abstract boolean isDemoUser();
}
diff --git a/src/com/android/launcher3/compat/UserManagerCompatV16.java b/src/com/android/launcher3/compat/UserManagerCompatV16.java
deleted file mode 100644
index 9bd4567..0000000
--- a/src/com/android/launcher3/compat/UserManagerCompatV16.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class UserManagerCompatV16 extends UserManagerCompat {
-
- UserManagerCompatV16() {
- }
-
- public List<UserHandleCompat> getUserProfiles() {
- List<UserHandleCompat> profiles = new ArrayList<UserHandleCompat>(1);
- profiles.add(UserHandleCompat.myUserHandle());
- return profiles;
- }
-
- public UserHandleCompat getUserForSerialNumber(long serialNumber) {
- return UserHandleCompat.myUserHandle();
- }
-
- public long getSerialNumberForUser(UserHandleCompat user) {
- return 0;
- }
-
- public CharSequence getBadgedLabelForUser(CharSequence label, UserHandleCompat user) {
- return label;
- }
-
- @Override
- public long getUserCreationTime(UserHandleCompat user) {
- return 0;
- }
-
- @Override
- public void enableAndResetCache() {
- }
-
- @Override
- public boolean isQuietModeEnabled(UserHandleCompat user) {
- return false;
- }
-
- @Override
- public boolean isUserUnlocked(UserHandleCompat user) {
- return true;
- }
-
- @Override
- public boolean isDemoUser() {
- return false;
- }
-}
diff --git a/src/com/android/launcher3/compat/UserManagerCompatV17.java b/src/com/android/launcher3/compat/UserManagerCompatV17.java
deleted file mode 100644
index 75203b7..0000000
--- a/src/com/android/launcher3/compat/UserManagerCompatV17.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.os.Build;
-import android.os.UserManager;
-
-import com.android.launcher3.util.LongArrayMap;
-
-import java.util.HashMap;
-
-@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
-public class UserManagerCompatV17 extends UserManagerCompatV16 {
-
- protected LongArrayMap<UserHandleCompat> mUsers;
- // Create a separate reverse map as LongArrayMap.indexOfValue checks if objects are same
- // and not {@link Object#equals}
- protected HashMap<UserHandleCompat, Long> mUserToSerialMap;
-
- protected UserManager mUserManager;
-
- UserManagerCompatV17(Context context) {
- mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
- }
-
- public long getSerialNumberForUser(UserHandleCompat user) {
- synchronized (this) {
- if (mUserToSerialMap != null) {
- Long serial = mUserToSerialMap.get(user);
- return serial == null ? 0 : serial;
- }
- }
- return mUserManager.getSerialNumberForUser(user.getUser());
- }
-
- public UserHandleCompat getUserForSerialNumber(long serialNumber) {
- synchronized (this) {
- if (mUsers != null) {
- return mUsers.get(serialNumber);
- }
- }
- return UserHandleCompat.fromUser(mUserManager.getUserForSerialNumber(serialNumber));
- }
-
- @Override
- public void enableAndResetCache() {
- synchronized (this) {
- mUsers = new LongArrayMap<>();
- mUserToSerialMap = new HashMap<>();
- UserHandleCompat myUser = UserHandleCompat.myUserHandle();
- long serial = mUserManager.getSerialNumberForUser(myUser.getUser());
- mUsers.put(serial, myUser);
- mUserToSerialMap.put(myUser, serial);
- }
- }
-}
-
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVL.java b/src/com/android/launcher3/compat/UserManagerCompatVL.java
index 2552b0c..45525f5 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVL.java
@@ -16,12 +16,11 @@
package com.android.launcher3.compat;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
-import android.os.Build;
import android.os.UserHandle;
+import android.os.UserManager;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.LongArrayMap;
@@ -31,20 +30,61 @@
import java.util.HashMap;
import java.util.List;
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
-public class UserManagerCompatVL extends UserManagerCompatV17 {
+public class UserManagerCompatVL extends UserManagerCompat {
private static final String USER_CREATION_TIME_KEY = "user_creation_time_";
+ protected final UserManager mUserManager;
private final PackageManager mPm;
private final Context mContext;
+ protected LongArrayMap<UserHandle> mUsers;
+ // Create a separate reverse map as LongArrayMap.indexOfValue checks if objects are same
+ // and not {@link Object#equals}
+ protected HashMap<UserHandle, Long> mUserToSerialMap;
+
UserManagerCompatVL(Context context) {
- super(context);
+ mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
mPm = context.getPackageManager();
mContext = context;
}
@Override
+ public long getSerialNumberForUser(UserHandle user) {
+ synchronized (this) {
+ if (mUserToSerialMap != null) {
+ Long serial = mUserToSerialMap.get(user);
+ return serial == null ? 0 : serial;
+ }
+ }
+ return mUserManager.getSerialNumberForUser(user);
+ }
+
+ @Override
+ public UserHandle getUserForSerialNumber(long serialNumber) {
+ synchronized (this) {
+ if (mUsers != null) {
+ return mUsers.get(serialNumber);
+ }
+ }
+ return mUserManager.getUserForSerialNumber(serialNumber);
+ }
+
+ @Override
+ public boolean isQuietModeEnabled(UserHandle user) {
+ return false;
+ }
+
+ @Override
+ public boolean isUserUnlocked(UserHandle user) {
+ return true;
+ }
+
+ @Override
+ public boolean isDemoUser() {
+ return false;
+ }
+
+ @Override
public void enableAndResetCache() {
synchronized (this) {
mUsers = new LongArrayMap<>();
@@ -53,46 +93,35 @@
if (users != null) {
for (UserHandle user : users) {
long serial = mUserManager.getSerialNumberForUser(user);
- UserHandleCompat userCompat = UserHandleCompat.fromUser(user);
- mUsers.put(serial, userCompat);
- mUserToSerialMap.put(userCompat, serial);
+ mUsers.put(serial, user);
+ mUserToSerialMap.put(user, serial);
}
}
}
}
@Override
- public List<UserHandleCompat> getUserProfiles() {
+ public List<UserHandle> getUserProfiles() {
synchronized (this) {
if (mUsers != null) {
- List<UserHandleCompat> users = new ArrayList<>();
- users.addAll(mUserToSerialMap.keySet());
- return users;
+ return new ArrayList<>(mUserToSerialMap.keySet());
}
}
List<UserHandle> users = mUserManager.getUserProfiles();
- if (users == null) {
- return Collections.emptyList();
- }
- ArrayList<UserHandleCompat> compatUsers = new ArrayList<UserHandleCompat>(
- users.size());
- for (UserHandle user : users) {
- compatUsers.add(UserHandleCompat.fromUser(user));
- }
- return compatUsers;
+ return users == null ? Collections.<UserHandle>emptyList() : users;
}
@Override
- public CharSequence getBadgedLabelForUser(CharSequence label, UserHandleCompat user) {
+ public CharSequence getBadgedLabelForUser(CharSequence label, UserHandle user) {
if (user == null) {
return label;
}
- return mPm.getUserBadgedLabel(label, user.getUser());
+ return mPm.getUserBadgedLabel(label, user);
}
@Override
- public long getUserCreationTime(UserHandleCompat user) {
+ public long getUserCreationTime(UserHandle user) {
SharedPreferences prefs = Utilities.getPrefs(mContext);
String key = USER_CREATION_TIME_KEY + getSerialNumberForUser(user);
if (!prefs.contains(key)) {
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVM.java b/src/com/android/launcher3/compat/UserManagerCompatVM.java
index 81d67ea..75c1877 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVM.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVM.java
@@ -19,6 +19,7 @@
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
+import android.os.UserHandle;
@TargetApi(Build.VERSION_CODES.M)
public class UserManagerCompatVM extends UserManagerCompatVL {
@@ -28,7 +29,7 @@
}
@Override
- public long getUserCreationTime(UserHandleCompat user) {
- return mUserManager.getUserCreationTime(user.getUser());
+ public long getUserCreationTime(UserHandle user) {
+ return mUserManager.getUserCreationTime(user);
}
}
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVN.java b/src/com/android/launcher3/compat/UserManagerCompatVN.java
index 4edac05..50a0217 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVN.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVN.java
@@ -19,8 +19,7 @@
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
-
-import com.android.launcher3.Utilities;
+import android.os.UserHandle;
@TargetApi(Build.VERSION_CODES.N)
public class UserManagerCompatVN extends UserManagerCompatVM {
@@ -30,13 +29,13 @@
}
@Override
- public boolean isQuietModeEnabled(UserHandleCompat user) {
- return mUserManager.isQuietModeEnabled(user.getUser());
+ public boolean isQuietModeEnabled(UserHandle user) {
+ return mUserManager.isQuietModeEnabled(user);
}
@Override
- public boolean isUserUnlocked(UserHandleCompat user) {
- return mUserManager.isUserUnlocked(user.getUser());
+ public boolean isUserUnlocked(UserHandle user) {
+ return mUserManager.isUserUnlocked(user);
}
}
diff --git a/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java
new file mode 100644
index 0000000..06493b2
--- /dev/null
+++ b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.discovery;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+
+public class AppDiscoveryAppInfo extends AppInfo {
+
+ public final boolean showAsDiscoveryItem;
+ public final boolean isInstantApp;
+ public final boolean isRecent;
+ public final float rating;
+ public final long reviewCount;
+ public final @NonNull String publisher;
+ public final @NonNull Intent installIntent;
+ public final @NonNull Intent launchIntent;
+ public final @Nullable String priceFormatted;
+
+ public AppDiscoveryAppInfo(AppDiscoveryItem item) {
+ 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;
+ this.rating = item.starRating;
+ this.showAsDiscoveryItem = true;
+ this.publisher = item.publisher != null ? item.publisher : "";
+ this.priceFormatted = item.price;
+ this.componentName = new ComponentName(item.packageName, "");
+ this.installIntent = item.installIntent;
+ this.launchIntent = item.launchIntent;
+ this.reviewCount = item.reviewCount;
+ this.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+ }
+
+ @Override
+ public ShortcutInfo makeShortcut() {
+ if (!isDragAndDropSupported()) {
+ throw new RuntimeException("DnD is currently not supported for discovered store apps");
+ }
+ return super.makeShortcut();
+ }
+
+ public boolean isDragAndDropSupported() {
+ return isInstantApp;
+ }
+
+}
diff --git a/src/com/android/launcher3/discovery/AppDiscoveryItem.java b/src/com/android/launcher3/discovery/AppDiscoveryItem.java
new file mode 100644
index 0000000..09c91ac
--- /dev/null
+++ b/src/com/android/launcher3/discovery/AppDiscoveryItem.java
@@ -0,0 +1,65 @@
+/*
+ * 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.discovery;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+
+/**
+ * This class represents the model for a discovered app via app discovery.
+ * It holds all information for one result retrieved from an app discovery service.
+ */
+public class AppDiscoveryItem {
+
+ public final String packageName;
+ public final boolean isInstantApp;
+ public final boolean isRecent;
+ public final float starRating;
+ public final long reviewCount;
+ public final Intent launchIntent;
+ public final Intent installIntent;
+ public final CharSequence title;
+ public final String publisher;
+ public final String price;
+ public final Bitmap bitmap;
+
+ public AppDiscoveryItem(String packageName,
+ boolean isInstantApp,
+ boolean isRecent,
+ float starRating,
+ long reviewCount,
+ CharSequence title,
+ String publisher,
+ Bitmap bitmap,
+ String price,
+ Intent launchIntent,
+ Intent installIntent) {
+ this.packageName = packageName;
+ this.isInstantApp = isInstantApp;
+ this.isRecent = isRecent;
+ this.starRating = starRating;
+ this.reviewCount = reviewCount;
+ this.launchIntent = launchIntent;
+ this.installIntent = installIntent;
+ this.title = title;
+ this.publisher = publisher;
+ this.price = price;
+ this.bitmap = bitmap;
+ }
+
+}
diff --git a/src/com/android/launcher3/discovery/AppDiscoveryItemView.java b/src/com/android/launcher3/discovery/AppDiscoveryItemView.java
new file mode 100644
index 0000000..9bb3b10
--- /dev/null
+++ b/src/com/android/launcher3/discovery/AppDiscoveryItemView.java
@@ -0,0 +1,97 @@
+/*
+ * 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.discovery;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.R;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+
+public class AppDiscoveryItemView extends RelativeLayout {
+
+ private static boolean SHOW_REVIEW_COUNT = false;
+
+ private ImageView mImage;
+ private TextView mTitle;
+ private TextView mRatingText;
+ private RatingView mRatingView;
+ private TextView mReviewCount;
+ private TextView mPrice;
+ private OnLongClickListener mOnLongClickListener;
+
+ public AppDiscoveryItemView(Context context) {
+ this(context, null);
+ }
+
+ public AppDiscoveryItemView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AppDiscoveryItemView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ this.mImage = (ImageView) findViewById(R.id.image);
+ this.mTitle = (TextView) findViewById(R.id.title);
+ this.mRatingText = (TextView) findViewById(R.id.rating);
+ this.mRatingView = (RatingView) findViewById(R.id.rating_view);
+ this.mPrice = (TextView) findViewById(R.id.price);
+ this.mReviewCount = (TextView) findViewById(R.id.review_count);
+ }
+
+ public void init(OnClickListener clickListener,
+ AccessibilityDelegate accessibilityDelegate,
+ OnLongClickListener onLongClickListener) {
+ setOnClickListener(clickListener);
+ mImage.setOnClickListener(clickListener);
+ setAccessibilityDelegate(accessibilityDelegate);
+ mOnLongClickListener = onLongClickListener;
+ }
+
+ public void apply(@NonNull AppDiscoveryAppInfo info) {
+ setTag(info);
+ mImage.setTag(info);
+ mImage.setImageBitmap(info.iconBitmap);
+ mImage.setOnLongClickListener(info.isDragAndDropSupported() ? mOnLongClickListener : null);
+ mTitle.setText(info.title);
+ mPrice.setText(info.priceFormatted != null ? info.priceFormatted : "");
+ mReviewCount.setVisibility(SHOW_REVIEW_COUNT ? View.VISIBLE : View.GONE);
+ if (info.rating >= 0) {
+ mRatingText.setText(new DecimalFormat("#.#").format(info.rating));
+ mRatingView.setRating(info.rating);
+ mRatingView.setVisibility(View.VISIBLE);
+ String reviewCountFormatted = NumberFormat.getInstance().format(info.reviewCount);
+ mReviewCount.setText("(" + reviewCountFormatted + ")");
+ } else {
+ // if we don't have a rating
+ mRatingView.setVisibility(View.GONE);
+ mRatingText.setText("");
+ mReviewCount.setText("");
+ }
+ }
+}
diff --git a/src/com/android/launcher3/discovery/AppDiscoveryUpdateState.java b/src/com/android/launcher3/discovery/AppDiscoveryUpdateState.java
new file mode 100644
index 0000000..0700a10
--- /dev/null
+++ b/src/com/android/launcher3/discovery/AppDiscoveryUpdateState.java
@@ -0,0 +1,21 @@
+/*
+ * 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.discovery;
+
+public enum AppDiscoveryUpdateState {
+ START, UPDATE, END
+}
diff --git a/src/com/android/launcher3/discovery/RatingView.java b/src/com/android/launcher3/discovery/RatingView.java
new file mode 100644
index 0000000..8fe63d6
--- /dev/null
+++ b/src/com/android/launcher3/discovery/RatingView.java
@@ -0,0 +1,94 @@
+/*
+ * 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.discovery;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.ClipDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+
+import com.android.launcher3.R;
+
+/**
+ * A simple rating view that shows stars with a rating from 0-5.
+ */
+public class RatingView extends View {
+
+ private static final float WIDTH_FACTOR = 0.9f;
+ private static final int MAX_LEVEL = 10000;
+ private static final int MAX_STARS = 5;
+
+ private final Drawable mStarDrawable;
+ private final int mColorGray;
+ private final int mColorHighlight;
+
+ private float rating;
+
+ public RatingView(Context context) {
+ this(context, null);
+ }
+
+ public RatingView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public RatingView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mStarDrawable = getResources().getDrawable(R.drawable.ic_star_rating, null);
+ mColorGray = 0x1E000000;
+ mColorHighlight = 0x8A000000;
+ }
+
+ public void setRating(float rating) {
+ this.rating = Math.min(Math.max(rating, 0), MAX_STARS);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ drawStars(canvas, MAX_STARS, mColorGray);
+ drawStars(canvas, rating, mColorHighlight);
+ }
+
+ private void drawStars(Canvas canvas, float stars, int color) {
+ int fullWidth = getLayoutParams().width;
+ int cellWidth = fullWidth / MAX_STARS;
+ int starWidth = (int) (cellWidth * WIDTH_FACTOR);
+ int padding = cellWidth - starWidth;
+ int fullStars = (int) stars;
+ float partialStarFactor = stars - fullStars;
+
+ for (int i = 0; i < fullStars; i++) {
+ int x = i * cellWidth + padding;
+ Drawable star = mStarDrawable.getConstantState().newDrawable().mutate();
+ star.setTint(color);
+ star.setBounds(x, padding, x + starWidth, padding + starWidth);
+ star.draw(canvas);
+ }
+ if (partialStarFactor > 0f) {
+ int x = fullStars * cellWidth + padding;
+ ClipDrawable star = new ClipDrawable(mStarDrawable,
+ Gravity.LEFT, ClipDrawable.HORIZONTAL);
+ star.setTint(color);
+ star.setLevel((int) (MAX_LEVEL * partialStarFactor));
+ star.setBounds(x, padding, x + starWidth, padding + starWidth);
+ star.draw(canvas);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
new file mode 100644
index 0000000..09592a8
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -0,0 +1,307 @@
+/*
+ * 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 static com.android.launcher3.logging.LoggerUtils.newCommandAction;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.logging.LoggerUtils.newItemTarget;
+import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
+
+import android.annotation.TargetApi;
+import android.app.ActivityOptions;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetManager;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.DragShadowBuilder;
+import android.view.View.OnLongClickListener;
+import android.view.View.OnTouchListener;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.InstallShortcutReceiver;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.AppWidgetManagerCompat;
+import com.android.launcher3.compat.PinItemRequestCompat;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.widget.PendingAddShortcutInfo;
+import com.android.launcher3.widget.PendingAddWidgetInfo;
+import com.android.launcher3.widget.WidgetHostViewLoader;
+import com.android.launcher3.widget.WidgetImageView;
+
+@TargetApi(Build.VERSION_CODES.N_MR1)
+public class AddItemActivity extends BaseActivity implements OnLongClickListener, OnTouchListener {
+
+ private static final int SHADOW_SIZE = 10;
+
+ private static final int REQUEST_BIND_APPWIDGET = 1;
+ private static final String STATE_EXTRA_WIDGET_ID = "state.widget.id";
+
+ private final PointF mLastTouchPos = new PointF();
+
+ private PinItemRequestCompat mRequest;
+ private LauncherAppState mApp;
+ private InvariantDeviceProfile mIdp;
+
+ private LivePreviewWidgetCell mWidgetCell;
+
+ // Widget request specific options.
+ private AppWidgetHost mAppWidgetHost;
+ private AppWidgetManagerCompat mAppWidgetManager;
+ private PendingAddWidgetInfo mPendingWidgetInfo;
+ private int mPendingBindWidgetId;
+ private Bundle mWidgetOptions;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mRequest = PinItemRequestCompat.getPinItemRequest(getIntent());
+ if (mRequest == null) {
+ finish();
+ return;
+ }
+
+ mApp = LauncherAppState.getInstance(this);
+ mIdp = mApp.getInvariantDeviceProfile();
+
+ // Use the application context to get the device profile, as in multiwindow-mode, the
+ // confirmation activity might be rotated.
+ mDeviceProfile = mIdp.getDeviceProfile(getApplicationContext());
+
+ setContentView(R.layout.add_item_confirmation_activity);
+ mWidgetCell = (LivePreviewWidgetCell) findViewById(R.id.widget_cell);
+
+ if (mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_SHORTCUT) {
+ setupShortcut();
+ } else {
+ if (!setupWidget()) {
+ // TODO: show error toast?
+ finish();
+ }
+ }
+
+ mWidgetCell.setOnTouchListener(this);
+ mWidgetCell.setOnLongClickListener(this);
+
+ // savedInstanceState is null when the activity is created the first time (i.e., avoids
+ // duplicate logging during rotation)
+ if (savedInstanceState == null) {
+ logCommand(Action.Command.ENTRY);
+ }
+ }
+
+ @Override
+ public boolean onTouch(View view, MotionEvent motionEvent) {
+ mLastTouchPos.set(motionEvent.getX(), motionEvent.getY());
+ return false;
+ }
+
+ @Override
+ public boolean onLongClick(View view) {
+ // Find the position of the preview relative to the touch location.
+ WidgetImageView img = mWidgetCell.getWidgetView();
+
+ // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
+ // we abort the drag.
+ if (img.getBitmap() == null) {
+ return false;
+ }
+
+ Rect bounds = img.getBitmapBounds();
+ bounds.offset(img.getLeft() - (int) mLastTouchPos.x, img.getTop() - (int) mLastTouchPos.y);
+
+ // Start home and pass the draw request params
+ PinItemDragListener listener = new PinItemDragListener(mRequest, bounds,
+ img.getBitmap().getWidth(), img.getWidth());
+ Intent homeIntent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .setPackage(getPackageName())
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .putExtra(PinItemDragListener.EXTRA_PIN_ITEM_DRAG_LISTENER, listener);
+
+ if (!getResources().getBoolean(R.bool.allow_rotation) &&
+ !Utilities.isAllowRotationPrefEnabled(this) &&
+ (getResources().getConfiguration().orientation ==
+ Configuration.ORIENTATION_LANDSCAPE && !isInMultiWindowMode())) {
+ // If we are starting the drag in landscape even though home is locked in portrait,
+ // restart the home activity to temporarily allow rotation.
+ homeIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ }
+
+ startActivity(homeIntent,
+ ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out).toBundle());
+
+ // Start a system drag and drop. We use a transparent bitmap as preview for system drag
+ // as the preview is handled internally by launcher.
+ ClipDescription description = new ClipDescription("", new String[]{listener.getMimeType()});
+ ClipData data = new ClipData(description, new ClipData.Item(""));
+ view.startDragAndDrop(data, new DragShadowBuilder(view) {
+
+ @Override
+ public void onDrawShadow(Canvas canvas) { }
+
+ @Override
+ public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) {
+ outShadowSize.set(SHADOW_SIZE, SHADOW_SIZE);
+ outShadowTouchPoint.set(SHADOW_SIZE / 2, SHADOW_SIZE / 2);
+ }
+ }, null, View.DRAG_FLAG_GLOBAL);
+ return false;
+ }
+
+ private void setupShortcut() {
+ PinShortcutRequestActivityInfo shortcutInfo =
+ new PinShortcutRequestActivityInfo(mRequest, this);
+ WidgetItem item = new WidgetItem(shortcutInfo);
+ mWidgetCell.getWidgetView().setTag(new PendingAddShortcutInfo(shortcutInfo));
+ mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache());
+ mWidgetCell.ensurePreview();
+ }
+
+ private boolean setupWidget() {
+ LauncherAppWidgetProviderInfo widgetInfo = LauncherAppWidgetProviderInfo
+ .fromProviderInfo(this, mRequest.getAppWidgetProviderInfo(this));
+ if (widgetInfo.minSpanX > mIdp.numColumns || widgetInfo.minSpanY > mIdp.numRows) {
+ // Cannot add widget
+ return false;
+ }
+ mWidgetCell.setPreview(PinItemDragListener.getPreview(mRequest));
+
+ mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
+ mAppWidgetHost = new AppWidgetHost(this, Launcher.APPWIDGET_HOST_ID);
+
+ mPendingWidgetInfo = new PendingAddWidgetInfo(widgetInfo);
+ mPendingWidgetInfo.spanX = Math.min(mIdp.numColumns, widgetInfo.spanX);
+ mPendingWidgetInfo.spanY = Math.min(mIdp.numRows, widgetInfo.spanY);
+ mWidgetOptions = WidgetHostViewLoader.getDefaultOptionsForWidget(this, mPendingWidgetInfo);
+
+ WidgetItem item = new WidgetItem(widgetInfo, getPackageManager(), mIdp);
+ mWidgetCell.getWidgetView().setTag(mPendingWidgetInfo);
+ mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache());
+ mWidgetCell.ensurePreview();
+ return true;
+ }
+
+ /**
+ * Called when the cancel button is clicked.
+ */
+ public void onCancelClick(View v) {
+ logCommand(Action.Command.CANCEL);
+ finish();
+ }
+
+ /**
+ * Called when place-automatically button is clicked.
+ */
+ public void onPlaceAutomaticallyClick(View v) {
+ if (mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_SHORTCUT) {
+ InstallShortcutReceiver.queueShortcut(
+ new ShortcutInfoCompat(mRequest.getShortcutInfo()), this);
+ logCommand(Action.Command.CONFIRM);
+ mRequest.accept();
+ finish();
+ return;
+ }
+
+ mPendingBindWidgetId = mAppWidgetHost.allocateAppWidgetId();
+ boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
+ mPendingBindWidgetId, mRequest.getAppWidgetProviderInfo(this), mWidgetOptions);
+ if (success) {
+ acceptWidget(mPendingBindWidgetId);
+ return;
+ }
+
+ // request bind widget
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mPendingBindWidgetId);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER,
+ mPendingWidgetInfo.componentName);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE,
+ mRequest.getAppWidgetProviderInfo(this).getProfile());
+ startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
+ }
+
+ private void acceptWidget(int widgetId) {
+ InstallShortcutReceiver.queueWidget(mRequest.getAppWidgetProviderInfo(this), widgetId, this);
+ mWidgetOptions.putInt(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
+ mRequest.accept(mWidgetOptions);
+ logCommand(Action.Command.CONFIRM);
+ finish();
+ }
+
+ @Override
+ public void onBackPressed() {
+ logCommand(Action.Command.BACK);
+ super.onBackPressed();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUEST_BIND_APPWIDGET) {
+ int widgetId = data != null
+ ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mPendingBindWidgetId)
+ : mPendingBindWidgetId;
+ if (resultCode == RESULT_OK) {
+ acceptWidget(widgetId);
+ } else {
+ // Simply wait it out.
+ mAppWidgetHost.deleteAppWidgetId(widgetId);
+ mPendingBindWidgetId = -1;
+ }
+ return;
+ }
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(STATE_EXTRA_WIDGET_ID, mPendingBindWidgetId);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ mPendingBindWidgetId = savedInstanceState
+ .getInt(STATE_EXTRA_WIDGET_ID, mPendingBindWidgetId);
+ }
+
+ private void logCommand(int command) {
+ getUserEventDispatcher().dispatchUserEvent(newLauncherEvent(
+ newCommandAction(command),
+ newItemTarget(mWidgetCell.getWidgetView()),
+ newContainerTarget(ContainerType.PINITEM)), null);
+ }
+}
diff --git a/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java b/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java
deleted file mode 100644
index 156941a..0000000
--- a/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java
+++ /dev/null
@@ -1,76 +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.dragndrop;
-
-import android.content.Context;
-import android.view.View;
-
-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.userevent.nano.LauncherLogProto.Target;
-
-/**
- * DragSource used when the drag started at another window.
- */
-public class AnotherWindowDragSource implements DragSource {
-
- private final Context mContext;
-
- AnotherWindowDragSource(Context context) {
- mContext = context;
- }
-
- @Override
- public boolean supportsFlingToDelete() {
- return false;
- }
-
- @Override
- public boolean supportsAppInfoDropTarget() {
- return false;
- }
-
- @Override
- public boolean supportsDeleteDropTarget() {
- return false;
- }
-
- @Override
- public float getIntrinsicIconScaleFactor() {
- return 1;
- }
-
- @Override
- public void onFlingToDeleteCompleted() {
- }
-
- @Override
- public void onDropCompleted(View target, DragObject d,
- boolean isFlingToDelete, boolean success) {
- if (!success) {
- Launcher.getLauncher(mContext).exitSpringLoadedDragModeDelayed(false, 0, null);
- }
-
- }
-
- @Override
- public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
- // TODO: Probably log something
- }
-}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 6eb7dcc..7410ae6 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -17,33 +17,25 @@
package com.android.launcher3.dragndrop;
import android.content.ComponentName;
-import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Point;
-import android.graphics.PointF;
import android.graphics.Rect;
-import android.os.Handler;
import android.os.IBinder;
import android.view.DragEvent;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.MotionEvent;
-import android.view.VelocityTracker;
import android.view.View;
-import android.view.ViewConfiguration;
import android.view.inputmethod.InputMethodManager;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
-import com.android.launcher3.PagedView;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.Utilities;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.TouchController;
@@ -54,29 +46,14 @@
* Class for initiating a drag within a view or across multiple views.
*/
public class DragController implements DragDriver.EventListener, TouchController {
- private static final String TAG = "Launcher.DragController";
-
- public static final int SCROLL_DELAY = 500;
- public static final int RESCROLL_DELAY = PagedView.PAGE_SNAP_ANIMATION_DURATION + 150;
-
private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
- private static final int SCROLL_OUTSIDE_ZONE = 0;
- private static final int SCROLL_WAITING_IN_ZONE = 1;
-
- public static final int SCROLL_NONE = -1;
- public static final int SCROLL_LEFT = 0;
- public static final int SCROLL_RIGHT = 1;
-
- private static final float MAX_FLING_DEGREES = 35f;
-
@Thunk Launcher mLauncher;
- private Handler mHandler;
+ private FlingToDeleteHelper mFlingToDeleteHelper;
// temporaries to avoid gc thrash
private Rect mRectTemp = new Rect();
private final int[] mCoordinatesTemp = new int[2];
- private final boolean mIsRtl;
/**
* Drag driver for the current drag/drop operation, or null if there is no active DND operation.
@@ -93,34 +70,19 @@
/** Y coordinate of the down event. */
private int mMotionDownY;
- /** the area at the edge of the screen that makes the workspace go left
- * or right while you're dragging.
- */
- private final int mScrollZone;
-
private DropTarget.DragObject mDragObject;
/** Who can receive drop events */
- private ArrayList<DropTarget> mDropTargets = new ArrayList<DropTarget>();
- private ArrayList<DragListener> mListeners = new ArrayList<DragListener>();
- private DropTarget mFlingToDeleteDropTarget;
+ private ArrayList<DropTarget> mDropTargets = new ArrayList<>();
+ private ArrayList<DragListener> mListeners = new ArrayList<>();
/** The window token used as the parent for the DragView. */
private IBinder mWindowToken;
- /** The view that will be scrolled when dragging to the left and right edges of the screen. */
- private View mScrollView;
-
private View mMoveTarget;
- @Thunk DragScroller mDragScroller;
- @Thunk int mScrollState = SCROLL_OUTSIDE_ZONE;
- private ScrollRunnable mScrollRunnable = new ScrollRunnable();
-
private DropTarget mLastDropTarget;
- private InputMethodManager mInputMethodManager;
-
@Thunk int mLastTouch[] = new int[2];
@Thunk long mLastTouchUpTime = -1;
@Thunk int mDistanceSinceScroll = 0;
@@ -128,10 +90,7 @@
private int mTmpPoint[] = new int[2];
private Rect mDragLayerRect = new Rect();
- protected final int mFlingToDeleteThresholdVelocity;
- private VelocityTracker mVelocityTracker;
-
- private boolean mIsDragDeferred;
+ private boolean mIsInPreDrag;
/**
* Interface to receive notifications when a drag starts or stops
@@ -155,37 +114,8 @@
* Used to create a new DragLayer from XML.
*/
public DragController(Launcher launcher) {
- Resources r = launcher.getResources();
mLauncher = launcher;
- mHandler = new Handler();
- mScrollZone = r.getDimensionPixelSize(R.dimen.scroll_zone);
- mVelocityTracker = VelocityTracker.obtain();
-
- mFlingToDeleteThresholdVelocity =
- r.getDimensionPixelSize(R.dimen.drag_flingToDeleteMinVelocity);
- mIsRtl = Utilities.isRtl(r);
- }
-
- /**
- * Starts a drag.
- *
- * @param v The view that is being dragged
- * @param bmp The bitmap that represents the view being dragged
- * @param source An object representing where the drag originated
- * @param dragInfo The data associated with the object that is being dragged
- * @param viewImageBounds the position of the image inside the view
- */
- public void startDrag(View v, Bitmap bmp, DragSource source, ItemInfo dragInfo,
- Rect viewImageBounds, float initialDragViewScale, DragOptions options) {
- int[] loc = mCoordinatesTemp;
- mLauncher.getDragLayer().getLocationInDragLayer(v, loc);
- int dragLayerX = loc[0] + viewImageBounds.left
- + (int) ((initialDragViewScale * bmp.getWidth() - bmp.getWidth()) / 2);
- int dragLayerY = loc[1] + viewImageBounds.top
- + (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2);
-
- startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, null,
- null, initialDragViewScale, options);
+ mFlingToDeleteHelper = new FlingToDeleteHelper(launcher);
}
/**
@@ -208,11 +138,8 @@
}
// Hide soft keyboard, if visible
- if (mInputMethodManager == null) {
- mInputMethodManager = (InputMethodManager)
- mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE);
- }
- mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);
+ mLauncher.getSystemService(InputMethodManager.class)
+ .hideSoftInputFromWindow(mWindowToken, 0);
mOptions = options;
if (mOptions.systemDndStartPoint != null) {
@@ -230,14 +157,12 @@
mDragObject = new DropTarget.DragObject();
- mIsDragDeferred = !mOptions.deferDragCondition.shouldStartDeferredDrag(0);
+ mIsInPreDrag = mOptions.preDragCondition != null
+ && !mOptions.preDragCondition.shouldStartDrag(0);
final Resources res = mLauncher.getResources();
- final float scaleDps = FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND
- ? res.getDimensionPixelSize(R.dimen.dragViewScale)
- : mIsDragDeferred
- ? res.getDimensionPixelSize(R.dimen.deferred_drag_view_scale)
- : 0f;
+ final float scaleDps = mIsInPreDrag
+ ? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale) : 0f;
final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
registrationY, initialDragViewScale, scaleDps);
@@ -271,10 +196,10 @@
dragView.show(mMotionDownX, mMotionDownY);
mDistanceSinceScroll = 0;
- if (!mIsDragDeferred) {
- startDeferredDrag();
- } else {
- mOptions.deferDragCondition.onDeferredDragStart();
+ if (!mIsInPreDrag) {
+ callOnDragStart();
+ } else if (mOptions.preDragCondition != null) {
+ mOptions.preDragCondition.onPreDragStart(mDragObject);
}
mLastTouch[0] = mMotionDownX;
@@ -284,16 +209,14 @@
return dragView;
}
- public boolean isDeferringDrag() {
- return mIsDragDeferred;
- }
-
- public void startDeferredDrag() {
+ private void callOnDragStart() {
for (DragListener listener : new ArrayList<>(mListeners)) {
listener.onDragStart(mDragObject, mOptions);
}
- mOptions.deferDragCondition.onDragStart();
- mIsDragDeferred = false;
+ if (mOptions.preDragCondition != null) {
+ mOptions.preDragCondition.onPreDragEnd(mDragObject, true /* dragStarted*/);
+ }
+ mIsInPreDrag = false;
}
/**
@@ -314,10 +237,6 @@
return mDragDriver != null || (mOptions != null && mOptions.isAccessibleDrag);
}
- public boolean isExternalDrag() {
- return (mOptions != null && mOptions.systemDndStartPoint != null);
- }
-
/**
* Stop dragging without dropping.
*/
@@ -329,7 +248,9 @@
mDragObject.deferDragViewCleanupPostAnimation = false;
mDragObject.cancelled = true;
mDragObject.dragComplete = true;
- mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false);
+ if (!mIsInPreDrag) {
+ mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false);
+ }
}
endDrag();
}
@@ -350,26 +271,51 @@
private void endDrag() {
if (isDragging()) {
mDragDriver = null;
- mOptions = null;
- clearScrollRunnable();
boolean isDeferred = false;
if (mDragObject.dragView != null) {
isDeferred = mDragObject.deferDragViewCleanupPostAnimation;
if (!isDeferred) {
mDragObject.dragView.remove();
+ } else if (mIsInPreDrag) {
+ animateDragViewToOriginalPosition(null, null, -1);
}
mDragObject.dragView = null;
}
// Only end the drag if we are not deferred
if (!isDeferred) {
- for (DragListener listener : new ArrayList<>(mListeners)) {
- listener.onDragEnd();
- }
+ callOnDragEnd();
}
}
- releaseVelocityTracker();
+ mFlingToDeleteHelper.releaseVelocityTracker();
+ }
+
+ public void animateDragViewToOriginalPosition(final Runnable onComplete,
+ final View originalIcon, int duration) {
+ Runnable onCompleteRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (originalIcon != null) {
+ originalIcon.setVisibility(View.VISIBLE);
+ }
+ if (onComplete != null) {
+ onComplete.run();
+ }
+ }
+ };
+ mDragObject.dragView.animateTo(mMotionDownX, mMotionDownY, onCompleteRunnable, duration);
+ }
+
+ private void callOnDragEnd() {
+ if (mIsInPreDrag && mOptions.preDragCondition != null) {
+ mOptions.preDragCondition.onPreDragEnd(mDragObject, false /* dragStarted*/);
+ }
+ mIsInPreDrag = false;
+ mOptions = null;
+ for (DragListener listener : new ArrayList<>(mListeners)) {
+ listener.onDragEnd();
+ }
}
/**
@@ -380,16 +326,10 @@
if (mDragObject.deferDragViewCleanupPostAnimation) {
// If we skipped calling onDragEnd() before, do it now
- for (DragListener listener : new ArrayList<>(mListeners)) {
- listener.onDragEnd();
- }
+ callOnDragEnd();
}
}
- public void onDeferredEndFling(DropTarget.DragObject d) {
- d.dragSource.onFlingToDeleteCompleted();
- }
-
/**
* Clamps the position to the drag layer bounds.
*/
@@ -428,22 +368,16 @@
}
@Override
- public void onDriverDragEnd(float x, float y, DropTarget dropTargetOverride) {
+ public void onDriverDragEnd(float x, float y) {
DropTarget dropTarget;
- PointF vec = null;
-
- if (dropTargetOverride != null) {
- dropTarget = dropTargetOverride;
+ Runnable flingAnimation = mFlingToDeleteHelper.getFlingAnimation(mDragObject);
+ if (flingAnimation != null) {
+ dropTarget = mFlingToDeleteHelper.getDropTarget();
} else {
- vec = isFlingingToDelete(mDragObject.dragSource);
- if (vec != null) {
- dropTarget = mFlingToDeleteDropTarget;
- } else {
- dropTarget = findDropTarget((int) x, (int) y, mCoordinatesTemp);
- }
+ dropTarget = findDropTarget((int) x, (int) y, mCoordinatesTemp);
}
- drop(dropTarget, x, y, vec);
+ drop(dropTarget, flingAnimation);
endDrag();
}
@@ -456,13 +390,13 @@
/**
* Call this from a drag source view.
*/
- public boolean onInterceptTouchEvent(MotionEvent ev) {
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (mOptions != null && mOptions.isAccessibleDrag) {
return false;
}
// Update the velocity tracker
- acquireVelocityTrackerAndAddMovement(ev);
+ mFlingToDeleteHelper.recordMotionEvent(ev);
final int action = ev.getAction();
final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
@@ -486,7 +420,8 @@
/**
* Call this from a drag source view.
*/
- public boolean onDragEvent(DragEvent event) {
+ public boolean onDragEvent(long dragStartTime, DragEvent event) {
+ mFlingToDeleteHelper.recordDragEvent(dragStartTime, event);
return mDragDriver != null && mDragDriver.onDragEvent(event);
}
@@ -510,16 +445,6 @@
return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction);
}
- private void clearScrollRunnable() {
- mHandler.removeCallbacks(mScrollRunnable);
- if (mScrollState == SCROLL_WAITING_IN_ZONE) {
- mScrollState = SCROLL_OUTSIDE_ZONE;
- mScrollRunnable.setDirection(SCROLL_RIGHT);
- mDragScroller.onExitScrollArea();
- mLauncher.getDragLayer().onExitScrollArea();
- }
- }
-
private void handleMoveEvent(int x, int y) {
mDragObject.dragView.move(x, y);
@@ -534,11 +459,10 @@
mDistanceSinceScroll += Math.hypot(mLastTouch[0] - x, mLastTouch[1] - y);
mLastTouch[0] = x;
mLastTouch[1] = y;
- checkScrollState(x, y);
- if (mIsDragDeferred && mOptions.deferDragCondition.shouldStartDeferredDrag(
- Math.hypot(x - mMotionDownX, y - mMotionDownY))) {
- startDeferredDrag();
+ if (mIsInPreDrag && mOptions.preDragCondition != null
+ && mOptions.preDragCondition.shouldStartDrag(mDistanceSinceScroll)) {
+ callOnDragStart();
}
}
@@ -571,46 +495,16 @@
mLastDropTarget = dropTarget;
}
- @Thunk void checkScrollState(int x, int y) {
- final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop();
- final int delay = mDistanceSinceScroll < slop ? RESCROLL_DELAY : SCROLL_DELAY;
- final DragLayer dragLayer = mLauncher.getDragLayer();
- final int forwardDirection = mIsRtl ? SCROLL_RIGHT : SCROLL_LEFT;
- final int backwardsDirection = mIsRtl ? SCROLL_LEFT : SCROLL_RIGHT;
-
- if (x < mScrollZone) {
- if (mScrollState == SCROLL_OUTSIDE_ZONE) {
- mScrollState = SCROLL_WAITING_IN_ZONE;
- if (mDragScroller.onEnterScrollArea(x, y, forwardDirection)) {
- dragLayer.onEnterScrollArea();
- mScrollRunnable.setDirection(forwardDirection);
- mHandler.postDelayed(mScrollRunnable, delay);
- }
- }
- } else if (x > mScrollView.getWidth() - mScrollZone) {
- if (mScrollState == SCROLL_OUTSIDE_ZONE) {
- mScrollState = SCROLL_WAITING_IN_ZONE;
- if (mDragScroller.onEnterScrollArea(x, y, backwardsDirection)) {
- dragLayer.onEnterScrollArea();
- mScrollRunnable.setDirection(backwardsDirection);
- mHandler.postDelayed(mScrollRunnable, delay);
- }
- }
- } else {
- clearScrollRunnable();
- }
- }
-
/**
* Call this from a drag source view.
*/
- public boolean onTouchEvent(MotionEvent ev) {
+ public boolean onControllerTouchEvent(MotionEvent ev) {
if (mDragDriver == null || mOptions == null || mOptions.isAccessibleDrag) {
return false;
}
// Update the velocity tracker
- acquireVelocityTrackerAndAddMovement(ev);
+ mFlingToDeleteHelper.recordMotionEvent(ev);
final int action = ev.getAction();
final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
@@ -622,17 +516,6 @@
// Remember where the motion event started
mMotionDownX = dragLayerX;
mMotionDownY = dragLayerY;
-
- if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) {
- mScrollState = SCROLL_WAITING_IN_ZONE;
- mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
- } else {
- mScrollState = SCROLL_OUTSIDE_ZONE;
- }
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- mHandler.removeCallbacks(mScrollRunnable);
break;
}
@@ -663,47 +546,12 @@
dropTarget.prepareAccessibilityDrop();
// Perform the drop
- drop(dropTarget, location[0], location[1], null);
+ drop(dropTarget, null);
endDrag();
}
- /**
- * Determines whether the user flung the current item to delete it.
- *
- * @return the vector at which the item was flung, or null if no fling was detected.
- */
- private PointF isFlingingToDelete(DragSource source) {
- if (mFlingToDeleteDropTarget == null) return null;
- if (!source.supportsFlingToDelete()) return null;
-
- ViewConfiguration config = ViewConfiguration.get(mLauncher);
- mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
- PointF vel = new PointF(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
- float theta = MAX_FLING_DEGREES + 1;
- if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
- // Do a quick dot product test to ensure that we are flinging upwards
- PointF upVec = new PointF(0f, -1f);
- theta = getAngleBetweenVectors(vel, upVec);
- } else if (mLauncher.getDeviceProfile().isVerticalBarLayout() &&
- mVelocityTracker.getXVelocity() < mFlingToDeleteThresholdVelocity) {
- // Remove icon is on left side instead of top, so check if we are flinging to the left.
- PointF leftVec = new PointF(-1f, 0f);
- theta = getAngleBetweenVectors(vel, leftVec);
- }
- if (theta <= Math.toRadians(MAX_FLING_DEGREES)) {
- return vel;
- }
- return null;
- }
-
- private float getAngleBetweenVectors(PointF vec1, PointF vec2) {
- return (float) Math.acos(((vec1.x * vec2.x) + (vec1.y * vec2.y)) /
- (vec1.length() * vec2.length()));
- }
-
- void drop(DropTarget dropTarget, float x, float y, PointF flingVel) {
+ private void drop(DropTarget dropTarget, Runnable flingAnimation) {
final int[] coordinates = mCoordinatesTemp;
-
mDragObject.x = coordinates[0];
mDragObject.y = coordinates[1];
@@ -725,20 +573,19 @@
if (dropTarget != null) {
dropTarget.onDragExit(mDragObject);
if (dropTarget.acceptDrop(mDragObject)) {
- if (flingVel != null) {
- dropTarget.onFlingToDelete(mDragObject, flingVel);
- } else {
+ if (flingAnimation != null) {
+ flingAnimation.run();
+ } else if (!mIsInPreDrag) {
dropTarget.onDrop(mDragObject);
}
accepted = true;
}
}
final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null;
- mDragObject.dragSource.onDropCompleted(
- dropTargetAsView, mDragObject, flingVel != null, accepted);
- mLauncher.getUserEventDispatcher().logDragNDrop(mDragObject, dropTargetAsView);
- if (mIsDragDeferred) {
- mOptions.deferDragCondition.onDropBeforeDeferredDrag();
+ if (!mIsInPreDrag) {
+ mLauncher.getUserEventDispatcher().logDragNDrop(mDragObject, dropTargetAsView);
+ mDragObject.dragSource.onDropCompleted(
+ dropTargetAsView, mDragObject, flingAnimation != null, accepted);
}
}
@@ -760,7 +607,7 @@
dropCoordinates[0] = x;
dropCoordinates[1] = y;
- mLauncher.getDragLayer().mapCoordInSelfToDescendent((View) target, dropCoordinates);
+ mLauncher.getDragLayer().mapCoordInSelfToDescendant((View) target, dropCoordinates);
return target;
}
@@ -768,16 +615,12 @@
return null;
}
- public void setDragScoller(DragScroller scroller) {
- mDragScroller = scroller;
- }
-
public void setWindowToken(IBinder token) {
mWindowToken = token;
}
/**
- * Sets the drag listner which will be notified when a drag starts or ends.
+ * Sets the drag listener which will be notified when a drag starts or ends.
*/
public void addDragListener(DragListener l) {
mListeners.add(l);
@@ -804,61 +647,4 @@
mDropTargets.remove(target);
}
- /**
- * Sets the current fling-to-delete drop target.
- */
- public void setFlingToDeleteDropTarget(DropTarget target) {
- mFlingToDeleteDropTarget = target;
- }
-
- private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.addMovement(ev);
- }
-
- private void releaseVelocityTracker() {
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- }
-
- /**
- * Set which view scrolls for touch events near the edge of the screen.
- */
- public void setScrollView(View v) {
- mScrollView = v;
- }
-
- private class ScrollRunnable implements Runnable {
- private int mDirection;
-
- ScrollRunnable() {
- }
-
- public void run() {
- if (mDragScroller != null) {
- if (mDirection == SCROLL_LEFT) {
- mDragScroller.scrollLeft();
- } else {
- mDragScroller.scrollRight();
- }
- mScrollState = SCROLL_OUTSIDE_ZONE;
- mDistanceSinceScroll = 0;
- mDragScroller.onExitScrollArea();
- mLauncher.getDragLayer().onExitScrollArea();
-
- if (isDragging()) {
- // Check the scroll again so that we can requeue the scroller if necessary
- checkScrollState(mLastTouch[0], mLastTouch[1]);
- }
- }
- }
-
- void setDirection(int direction) {
- mDirection = direction;
- }
- }
}
diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
index 4db8c07..65c0f29 100644
--- a/src/com/android/launcher3/dragndrop/DragDriver.java
+++ b/src/com/android/launcher3/dragndrop/DragDriver.java
@@ -16,21 +16,13 @@
package com.android.launcher3.dragndrop;
-import android.content.ClipData;
-import android.content.ClipDescription;
import android.content.Context;
-import android.content.Intent;
import android.view.DragEvent;
import android.view.MotionEvent;
-import com.android.launcher3.DropTarget;
import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.InstallShortcutReceiver;
-import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
-import java.util.ArrayList;
-
/**
* Base class for driving a drag/drop operation.
*/
@@ -40,7 +32,7 @@
public interface EventListener {
void onDriverDragMove(float x, float y);
void onDriverDragExitWindow();
- void onDriverDragEnd(float x, float y, DropTarget dropTargetOverride);
+ void onDriverDragEnd(float x, float y);
void onDriverDragCancel();
}
@@ -62,7 +54,7 @@
break;
case MotionEvent.ACTION_UP:
mEventListener.onDriverDragMove(ev.getX(), ev.getY());
- mEventListener.onDriverDragEnd(ev.getX(), ev.getY(), null);
+ mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
break;
case MotionEvent.ACTION_CANCEL:
mEventListener.onDriverDragCancel();
@@ -80,7 +72,7 @@
switch (action) {
case MotionEvent.ACTION_UP:
- mEventListener.onDriverDragEnd(ev.getX(), ev.getY(), null);
+ mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
break;
case MotionEvent.ACTION_CANCEL:
mEventListener.onDriverDragCancel();
@@ -92,7 +84,7 @@
public static DragDriver create(Context context, DragController dragController,
DragObject dragObject, DragOptions options) {
- if (Utilities.isNycOrAbove() && options.systemDndStartPoint != null) {
+ if (Utilities.ATLEAST_NOUGAT && options.systemDndStartPoint != null) {
return new SystemDragDriver(dragController, context, dragObject);
} else {
return new InternalDragDriver(dragController);
@@ -108,7 +100,6 @@
private final DragObject mDragObject;
private final Context mContext;
- boolean mReceivedDropEvent = false;
float mLastX = 0;
float mLastY = 0;
@@ -150,65 +141,21 @@
case DragEvent.ACTION_DROP:
mLastX = event.getX();
mLastY = event.getY();
- mReceivedDropEvent =
- updateInfoFromClipData(event.getClipData(), event.getClipDescription());
- return mReceivedDropEvent;
-
+ mEventListener.onDriverDragMove(event.getX(), event.getY());
+ mEventListener.onDriverDragEnd(mLastX, mLastY);
+ return true;
case DragEvent.ACTION_DRAG_EXITED:
mEventListener.onDriverDragExitWindow();
return true;
case DragEvent.ACTION_DRAG_ENDED:
- if (mReceivedDropEvent) {
- mEventListener.onDriverDragEnd(mLastX, mLastY, null);
- } else {
- mEventListener.onDriverDragCancel();
- }
+ mEventListener.onDriverDragCancel();
return true;
default:
return false;
}
}
-
- private boolean updateInfoFromClipData(ClipData data, ClipDescription desc) {
- if (data == null) {
- return false;
- }
- ArrayList<Intent> intents = new ArrayList<>();
- int itemCount = data.getItemCount();
- for (int i = 0; i < itemCount; i++) {
- Intent intent = data.getItemAt(i).getIntent();
- if (intent == null) {
- continue;
- }
-
- // Give preference to shortcut intents.
- if (!Intent.ACTION_CREATE_SHORTCUT.equals(intent.getAction())) {
- intents.add(intent);
- continue;
- }
- ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(mContext, intent);
- if (info != null) {
- mDragObject.dragInfo = info;
- return true;
- }
- return true;
- }
-
- // Try creating shortcuts just using the intent and label
- Intent fullIntent = new Intent().putExtra(Intent.EXTRA_SHORTCUT_NAME, desc.getLabel());
- for (Intent intent : intents) {
- fullIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent);
- ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(mContext, fullIntent);
- if (info != null) {
- mDragObject.dragInfo = info;
- return true;
- }
- }
-
- return false;
- }
}
/**
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 016347b..7178c5e 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -21,24 +21,15 @@
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.TargetApi;
-import android.content.ClipDescription;
import android.content.Context;
-import android.content.Intent;
import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.util.AttributeSet;
-import android.util.Log;
-import android.view.DragEvent;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -49,30 +40,28 @@
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.InstallShortcutReceiver;
-import com.android.launcher3.ItemInfo;
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.ShortcutInfo;
import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
-import com.android.launcher3.shortcuts.DeepShortcutsContainer;
+import com.android.launcher3.logging.LoggerUtils;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.TouchController;
+import com.android.launcher3.widget.WidgetsBottomSheet;
-import java.net.URISyntaxException;
import java.util.ArrayList;
/**
@@ -90,11 +79,9 @@
@Thunk DragController mDragController;
- private int mXDown, mYDown;
private Launcher mLauncher;
// Variables relating to resizing widgets
- private final ArrayList<AppWidgetResizeFrame> mResizeFrames = new ArrayList<>();
private final boolean mIsRtl;
private AppWidgetResizeFrame mCurrentResizeFrame;
@@ -121,13 +108,6 @@
private final Rect mScrollChildPosition = new Rect();
private final ViewGroupFocusHelper mFocusIndicatorHelper;
- private boolean mInScrollArea;
- private boolean mShowPageHints;
- private Drawable mLeftHoverDrawable;
- private Drawable mRightHoverDrawable;
- private Drawable mLeftHoverDrawableActive;
- private Drawable mRightHoverDrawableActive;
-
// Related to pinch-to-go-to-overview gesture.
private PinchToOverviewListener mPinchListener = null;
@@ -148,12 +128,7 @@
setMotionEventSplittingEnabled(false);
setChildrenDrawingOrderEnabled(true);
- final Resources res = getResources();
- mLeftHoverDrawable = res.getDrawable(R.drawable.page_hover_left);
- mRightHoverDrawable = res.getDrawable(R.drawable.page_hover_right);
- mLeftHoverDrawableActive = res.getDrawable(R.drawable.page_hover_left_active);
- mRightHoverDrawableActive = res.getDrawable(R.drawable.page_hover_right_active);
- mIsRtl = Utilities.isRtl(res);
+ mIsRtl = Utilities.isRtl(getResources());
mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
}
@@ -183,18 +158,13 @@
}
public boolean isEventOverPageIndicator(MotionEvent ev) {
- getDescendantRectRelativeToSelf(mLauncher.getWorkspace().getPageIndicator(), mHitRect);
- return mHitRect.contains((int) ev.getX(), (int) ev.getY());
+ return isEventOverView(mLauncher.getWorkspace().getPageIndicator(), ev);
}
public boolean isEventOverHotseat(MotionEvent ev) {
return isEventOverView(mLauncher.getHotseat(), ev);
}
- private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) {
- return isEventOverView(folder.getEditTextRegion(), ev);
- }
-
private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
return isEventOverView(folder, ev);
}
@@ -209,62 +179,29 @@
}
private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
- Rect hitRect = new Rect();
- int x = (int) ev.getX();
- int y = (int) ev.getY();
-
- for (AppWidgetResizeFrame child: mResizeFrames) {
- child.getHitRect(hitRect);
- if (hitRect.contains(x, y)) {
- if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) {
- mCurrentResizeFrame = child;
- mXDown = x;
- mYDown = y;
- requestDisallowInterceptTouchEvent(true);
+ AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher);
+ if (topView != null && intercept) {
+ ExtendedEditText textView = topView.getActiveTextView();
+ if (textView != null) {
+ if (!isEventOverView(textView, ev)) {
+ textView.dispatchBackKey();
return true;
}
- }
- }
-
- // Remove the shortcuts container when touching outside of it.
- DeepShortcutsContainer deepShortcutsContainer = mLauncher.getOpenShortcutsContainer();
- if (deepShortcutsContainer != null) {
- if (isEventOverView(deepShortcutsContainer, ev)) {
- // Let the container handle the event.
- return false;
- } else {
+ } else if (!isEventOverView(topView, ev)) {
if (isInAccessibleDrag()) {
// Do not close the container if in drag and drop.
if (!isEventOverDropTargetBar(ev)) {
return true;
}
} else {
- mLauncher.closeShortcutsContainer();
+ 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.
- return !isEventOverView(deepShortcutsContainer.getDeferredDragIcon(), ev);
- }
- }
- }
-
- Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
- if (currentFolder != null && intercept) {
- if (currentFolder.isEditingName()) {
- if (!isEventOverFolderTextRegion(currentFolder, ev)) {
- currentFolder.dismissEditingName();
- return true;
- }
- }
-
- if (!isEventOverFolder(currentFolder, ev)) {
- if (isInAccessibleDrag()) {
- // Do not close the folder if in drag and drop.
- if (!isEventOverDropTargetBar(ev)) {
- return true;
- }
- } else {
- mLauncher.closeFolder();
- return true;
+ View extendedTouch = topView.getExtendedTouchView();
+ return extendedTouch == null || !isEventOverView(extendedTouch, ev);
}
}
}
@@ -289,21 +226,33 @@
}
mTouchCompleteListener = null;
}
- clearAllResizeFrames();
-
mActiveController = null;
- if (mDragController.onInterceptTouchEvent(ev)) {
+ if (mCurrentResizeFrame != null
+ && mCurrentResizeFrame.onControllerInterceptTouchEvent(ev)) {
+ mActiveController = mCurrentResizeFrame;
+ return true;
+ } else {
+ clearResizeFrame();
+ }
+
+ if (mDragController.onControllerInterceptTouchEvent(ev)) {
mActiveController = mDragController;
return true;
}
- if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && mAllAppsController.onInterceptTouchEvent(ev)) {
+ if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && mAllAppsController.onControllerInterceptTouchEvent(ev)) {
mActiveController = mAllAppsController;
return true;
}
- if (mPinchListener != null && mPinchListener.onInterceptTouchEvent(ev)) {
+ 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;
@@ -316,7 +265,7 @@
if (mLauncher == null || mLauncher.getWorkspace() == null) {
return false;
}
- Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
+ Folder currentFolder = Folder.getOpen(mLauncher);
if (currentFolder == null) {
return false;
} else {
@@ -366,7 +315,7 @@
@Override
public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
// Shortcuts can appear above folder
- View topView = mLauncher.getTopFloatingView();
+ View topView = AbstractFloatingView.getTopOpenView(mLauncher);
if (topView != null) {
if (child == topView) {
return super.onRequestSendAccessibilityEvent(child, event);
@@ -383,7 +332,7 @@
@Override
public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
- View topView = mLauncher.getTopFloatingView();
+ View topView = AbstractFloatingView.getTopOpenView(mLauncher);
if (topView != null) {
// Only add the top view as a child for accessibility when it is open
childrenForAccessibility.add(topView);
@@ -405,12 +354,8 @@
@Override
public boolean onTouchEvent(MotionEvent ev) {
- boolean handled = false;
int action = ev.getAction();
- int x = (int) ev.getX();
- int y = (int) ev.getY();
-
if (action == MotionEvent.ACTION_DOWN) {
if (handleTouchDown(ev, false)) {
return true;
@@ -422,69 +367,12 @@
mTouchCompleteListener = null;
}
- if (mCurrentResizeFrame != null) {
- handled = true;
- switch (action) {
- case MotionEvent.ACTION_MOVE:
- mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
- break;
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
- mCurrentResizeFrame.onTouchUp();
- mCurrentResizeFrame = null;
- }
- }
- if (handled) return true;
if (mActiveController != null) {
- return mActiveController.onTouchEvent(ev);
+ return mActiveController.onControllerTouchEvent(ev);
}
return false;
}
- @TargetApi(Build.VERSION_CODES.N)
- private void handleSystemDragStart(DragEvent event) {
- if (!FeatureFlags.LAUNCHER3_USE_SYSTEM_DRAG_DRIVER || !Utilities.isNycOrAbove()) {
- return;
- }
- if (mLauncher.isWorkspaceLocked()) {
- return;
- }
-
- ClipDescription description = event.getClipDescription();
- if (!description.hasMimeType(ClipDescription.MIMETYPE_TEXT_INTENT)) {
- return;
- }
- ShortcutInfo info = new ShortcutInfo();
- // Set a dummy intent until we get the final value
- info.intent = new Intent();
-
- // Since we are not going through the workspace for starting the drag, set drag related
- // information on the workspace before starting the drag.
- ExternalDragPreviewProvider previewProvider =
- new ExternalDragPreviewProvider(mLauncher, info);
- mLauncher.getWorkspace().prepareDragWithProvider(previewProvider);
-
- DragOptions options = new DragOptions();
- options.systemDndStartPoint = new Point((int) event.getX(), (int) event.getY());
-
- int halfPadding = previewProvider.previewPadding / 2;
- mDragController.startDrag(
- Bitmap.createBitmap(1, 1, Config.ARGB_8888),
- 0, 0,
- new AnotherWindowDragSource(mLauncher), info,
- new Point(- halfPadding, halfPadding),
- previewProvider.getPreviewBounds(), 1f, options);
- }
-
- @Override
- public boolean onDragEvent (DragEvent event) {
- if (event.getAction() == DragEvent.ACTION_DRAG_STARTED) {
- handleSystemDragStart(event);
- }
- return mDragController.onDragEvent(event);
- }
-
/**
* Determine the rect of the descendant in this DragLayer's coordinates
*
@@ -534,8 +422,8 @@
/**
* Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
*/
- public float mapCoordInSelfToDescendent(View descendant, int[] coord) {
- return Utilities.mapCoordInSelfToDescendent(descendant, this, coord);
+ public void mapCoordInSelfToDescendant(View descendant, int[] coord) {
+ Utilities.mapCoordInSelfToDescendant(descendant, this, coord);
}
public void getViewRectRelativeToSelf(View v, Rect r) {
@@ -556,11 +444,17 @@
@Override
public boolean dispatchUnhandledMove(View focused, int direction) {
// Consume the unhandled move if a container is open, to avoid switching pages underneath.
- boolean isContainerOpen = mLauncher.getTopFloatingView() != null;
+ boolean isContainerOpen = AbstractFloatingView.getTopOpenView(mLauncher) != null;
return isContainerOpen || mDragController.dispatchUnhandledMove(focused, direction);
}
@Override
+ public void setInsets(Rect insets) {
+ super.setInsets(insets);
+ setBackgroundResource(insets.top == 0 ? 0 : R.drawable.workspace_bg);
+ }
+
+ @Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
@@ -645,36 +539,24 @@
}
}
- public void clearAllResizeFrames() {
- if (mResizeFrames.size() > 0) {
- for (AppWidgetResizeFrame frame: mResizeFrames) {
- frame.commitResize();
- removeView(frame);
- }
- mResizeFrames.clear();
+ public void clearResizeFrame() {
+ if (mCurrentResizeFrame != null) {
+ mCurrentResizeFrame.commitResize();
+ removeView(mCurrentResizeFrame);
+ mCurrentResizeFrame = null;
}
}
- public boolean hasResizeFrames() {
- return mResizeFrames.size() > 0;
- }
+ public void addResizeFrame(LauncherAppWidgetHostView widget, CellLayout cellLayout) {
+ clearResizeFrame();
- public boolean isWidgetBeingResized() {
- return mCurrentResizeFrame != null;
- }
+ mCurrentResizeFrame = (AppWidgetResizeFrame) LayoutInflater.from(mLauncher)
+ .inflate(R.layout.app_widget_resize_frame, this, false);
+ mCurrentResizeFrame.setupForWidget(widget, cellLayout, this);
+ ((LayoutParams) mCurrentResizeFrame.getLayoutParams()).customPosition = true;
- public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget,
- CellLayout cellLayout) {
- AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(),
- widget, cellLayout, this);
-
- LayoutParams lp = new LayoutParams(-1, -1);
- lp.customPosition = true;
-
- addView(resizeFrame, lp);
- mResizeFrames.add(resizeFrame);
-
- resizeFrame.snapToWidget(false);
+ addView(mCurrentResizeFrame);
+ mCurrentResizeFrame.snapToWidget(false);
}
public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
@@ -736,7 +618,7 @@
} else if (child instanceof FolderIcon) {
// Account for holographic blur padding on the drag view
toY += Math.round(scale * (child.getPaddingTop() - dragView.getDragRegionTop()));
- toY -= scale * Workspace.DRAG_BITMAP_PADDING / 2;
+ toY -= scale * dragView.getBlurSizeOutline() / 2;
toY -= (1 - scale) * dragView.getMeasuredHeight() / 2;
// Center in the x coordinate about the target's drawable
toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
@@ -807,15 +689,11 @@
// If duration < 0, this is a cue to compute the duration based on the distance
if (duration < 0) {
- if (mDragController != null && mDragController.isExternalDrag()) {
- duration = 1;
- } else {
- duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
- if (dist < maxDist) {
- duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
- }
- duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
+ duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
+ if (dist < maxDist) {
+ duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
}
+ duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
}
// Fall back to cubic ease out interpolator for the animation if none is specified
@@ -977,29 +855,6 @@
}
}
- void onEnterScrollArea() {
- mInScrollArea = true;
- invalidate();
- }
-
- void onExitScrollArea() {
- mInScrollArea = false;
- invalidate();
- }
-
- public void showPageHints() {
- mShowPageHints = true;
- Workspace workspace = mLauncher.getWorkspace();
- getDescendantRectRelativeToSelf(workspace.getChildAt(workspace.numCustomPages()),
- mScrollChildPosition);
- invalidate();
- }
-
- public void hidePageHints() {
- mShowPageHints = false;
- invalidate();
- }
-
public void invalidateScrim() {
if (mBackgroundAlpha > 0.0f) {
invalidate();
@@ -1029,41 +884,6 @@
super.dispatchDraw(canvas);
}
- private void drawPageHints(Canvas canvas) {
- if (mShowPageHints) {
- Workspace workspace = mLauncher.getWorkspace();
- int width = getMeasuredWidth();
- int page = workspace.getNextPage();
- CellLayout leftPage = (CellLayout) workspace.getChildAt(mIsRtl ? page + 1 : page - 1);
- CellLayout rightPage = (CellLayout) workspace.getChildAt(mIsRtl ? page - 1 : page + 1);
-
- if (leftPage != null && leftPage.isDragTarget()) {
- Drawable left = mInScrollArea && leftPage.getIsDragOverlapping() ?
- mLeftHoverDrawableActive : mLeftHoverDrawable;
- left.setBounds(0, mScrollChildPosition.top,
- left.getIntrinsicWidth(), mScrollChildPosition.bottom);
- left.draw(canvas);
- }
- if (rightPage != null && rightPage.isDragTarget()) {
- Drawable right = mInScrollArea && rightPage.getIsDragOverlapping() ?
- mRightHoverDrawableActive : mRightHoverDrawable;
- right.setBounds(width - right.getIntrinsicWidth(),
- mScrollChildPosition.top, width, mScrollChildPosition.bottom);
- right.draw(canvas);
- }
- }
- }
-
- protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
- boolean ret = super.drawChild(canvas, child, drawingTime);
-
- // We want to draw the page hints above the workspace, but below the drag view.
- if (child instanceof Workspace) {
- drawPageHints(canvas);
- }
- return ret;
- }
-
public void setBackgroundAlpha(float alpha) {
if (alpha != mBackgroundAlpha) {
mBackgroundAlpha = alpha;
@@ -1077,7 +897,7 @@
@Override
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
- View topView = mLauncher.getTopFloatingView();
+ View topView = AbstractFloatingView.getTopOpenView(mLauncher);
if (topView != null) {
return topView.requestFocus(direction, previouslyFocusedRect);
} else {
@@ -1087,7 +907,7 @@
@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
- View topView = mLauncher.getTopFloatingView();
+ View topView = AbstractFloatingView.getTopOpenView(mLauncher);
if (topView != null) {
topView.addFocusables(views, direction);
} else {
diff --git a/src/com/android/launcher3/dragndrop/DragOptions.java b/src/com/android/launcher3/dragndrop/DragOptions.java
index dbf46f3..230fa2d 100644
--- a/src/com/android/launcher3/dragndrop/DragOptions.java
+++ b/src/com/android/launcher3/dragndrop/DragOptions.java
@@ -17,6 +17,10 @@
package com.android.launcher3.dragndrop;
import android.graphics.Point;
+import android.support.annotation.CallSuper;
+import android.view.View;
+
+import com.android.launcher3.DropTarget;
/**
* Set of options to control the drag and drop behavior.
@@ -29,8 +33,8 @@
/** Specifies the start location for the system DnD, null when using internal DnD */
public Point systemDndStartPoint = null;
- /** Determines when a deferred drag should start. By default, drags aren't deferred at all. */
- public DeferDragCondition deferDragCondition = new DeferDragCondition();
+ /** Determines when a pre-drag should transition to a drag. By default, this is immediate. */
+ public PreDragCondition preDragCondition = null;
/**
* Specifies a condition that must be met before DragListener#onDragStart() is called.
@@ -38,34 +42,26 @@
* DragController#startDrag().
*
* This condition can be overridden, and callbacks are provided for the following cases:
- * - The drag starts, but onDragStart() is deferred (onDeferredDragStart()).
- * - The drag ends before the condition is met (onDropBeforeDeferredDrag()).
- * - The condition is met (onDragStart()).
+ * - The pre-drag starts, but onDragStart() is deferred (onPreDragStart()).
+ * - The pre-drag ends before the condition is met (onPreDragEnd(false)).
+ * - The actual drag starts when the condition is met (onPreDragEnd(true)).
*/
- public static class DeferDragCondition {
- public boolean shouldStartDeferredDrag(double distanceDragged) {
- return true;
- }
+ public interface PreDragCondition {
+
+ public boolean shouldStartDrag(double distanceDragged);
/**
- * The drag has started, but onDragStart() is deferred.
- * This happens when shouldStartDeferredDrag() returns true.
+ * The pre-drag has started, but onDragStart() is
+ * deferred until shouldStartDrag() returns true.
*/
- public void onDeferredDragStart() {
- // Do nothing.
- }
+ void onPreDragStart(DropTarget.DragObject dragObject);
/**
- * User dropped before the deferred condition was met,
- * i.e. before shouldStartDeferredDrag() returned true.
+ * The pre-drag has ended. This gets called at the same time as onDragStart()
+ * if the condition is met, otherwise at the same time as onDragEnd().
+ * @param dragStarted Whether the pre-drag ended because the actual drag started.
+ * This will be true if the condition was met, otherwise false.
*/
- public void onDropBeforeDeferredDrag() {
- // Do nothing
- }
-
- /** onDragStart() has been called, now we are in a normal drag. */
- public void onDragStart() {
- // Do nothing
- }
+ void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted);
}
}
diff --git a/src/com/android/launcher3/dragndrop/DragScroller.java b/src/com/android/launcher3/dragndrop/DragScroller.java
deleted file mode 100644
index 165d0b1..0000000
--- a/src/com/android/launcher3/dragndrop/DragScroller.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.dragndrop;
-
-/**
- * Handles scrolling while dragging
- *
- */
-public interface DragScroller {
- void scrollLeft();
- void scrollRight();
-
- /**
- * The touch point has entered the scroll area; a scroll is imminent.
- * This event will only occur while a drag is active.
- *
- * @param direction The scroll direction
- */
- boolean onEnterScrollArea(int x, int y, int direction);
-
- /**
- * The touch point has left the scroll area.
- * NOTE: This may not be called, if a drop occurs inside the scroll area.
- */
- boolean onExitScrollArea();
-}
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 8a2ae94..7806c98 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -22,27 +22,21 @@
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
-import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
-import android.os.Build;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.Thunk;
-
import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
+import com.android.launcher3.util.Thunk;
import java.util.Arrays;
@@ -55,8 +49,11 @@
private Bitmap mBitmap;
private Bitmap mCrossFadeBitmap;
@Thunk Paint mPaint;
+ private final int mBlurSizeOutline;
private final int mRegistrationX;
private final int mRegistrationY;
+ private final float mInitialScale;
+ private final int[] mTempLoc = new int[2];
private Point mDragVisualizeOffset = null;
private Rect mDragRegion = null;
@@ -89,7 +86,6 @@
* @param registrationX The x coordinate of the registration point.
* @param registrationY The y coordinate of the registration point.
*/
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
public DragView(Launcher launcher, Bitmap bitmap, int registrationX, int registrationY,
final float initialScale, final float finalScaleDps) {
super(launcher);
@@ -103,7 +99,7 @@
setScaleY(initialScale);
// Animate the view into the correct position
- mAnim = LauncherAnimUtils.ofFloat(this, 0f, 1f);
+ mAnim = LauncherAnimUtils.ofFloat(0f, 1f);
mAnim.setDuration(VIEW_ZOOM_DURATION);
mAnim.addUpdateListener(new AnimatorUpdateListener() {
@Override
@@ -138,14 +134,16 @@
mRegistrationX = registrationX;
mRegistrationY = registrationY;
+ mInitialScale = initialScale;
+
// Force a measure, because Workspace uses getMeasuredHeight() before the layout pass
int ms = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
measure(ms, ms);
mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
- if (Utilities.ATLEAST_LOLLIPOP) {
- setElevation(getResources().getDimension(R.dimen.drag_elevation));
- }
+ mBlurSizeOutline = getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline);
+
+ setElevation(getResources().getDimension(R.dimen.drag_elevation));
}
/** Sets the scale of the view over the normal workspace icon size. */
@@ -239,7 +237,7 @@
}
public void crossFade(int duration) {
- ValueAnimator va = LauncherAnimUtils.ofFloat(this, 0f, 1f);
+ ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
va.setDuration(duration);
va.setInterpolator(new DecelerateInterpolator(1.5f));
va.addUpdateListener(new AnimatorUpdateListener() {
@@ -261,17 +259,12 @@
m1.setSaturation(0);
ColorMatrix m2 = new ColorMatrix();
- setColorScale(color, m2);
+ Themes.setColorScaleOnMatrix(color, m2);
m1.postConcat(m2);
- if (Utilities.ATLEAST_LOLLIPOP) {
- animateFilterTo(m1.getArray());
- } else {
- mPaint.setColorFilter(new ColorMatrixColorFilter(m1));
- invalidate();
- }
+ animateFilterTo(m1.getArray());
} else {
- if (!Utilities.ATLEAST_LOLLIPOP || mCurrentFilter == null) {
+ if (mCurrentFilter == null) {
mPaint.setColorFilter(null);
invalidate();
} else {
@@ -280,7 +273,6 @@
}
}
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void animateFilterTo(float[] targetFilter) {
float[] oldFilter = mCurrentFilter == null ? new ColorMatrix().getArray() : mCurrentFilter;
mCurrentFilter = Arrays.copyOf(oldFilter, oldFilter.length);
@@ -356,6 +348,13 @@
applyTranslation();
}
+ public void animateTo(int toTouchX, int toTouchY, Runnable onCompleteRunnable, int duration) {
+ mTempLoc[0] = toTouchX - mRegistrationX;
+ mTempLoc[1] = toTouchY - mRegistrationY;
+ mDragLayer.animateViewIntoPosition(this, mTempLoc, 1f, mInitialScale, mInitialScale,
+ DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
+ }
+
public void animateShift(final int shiftX, final int shiftY) {
if (mAnim.isStarted()) {
return;
@@ -385,8 +384,11 @@
}
}
- public static void setColorScale(int color, ColorMatrix target) {
- target.setScale(Color.red(color) / 255f, Color.green(color) / 255f,
- Color.blue(color) / 255f, Color.alpha(color) / 255f);
+ public int getBlurSizeOutline() {
+ return mBlurSizeOutline;
+ }
+
+ public float getInitialScale() {
+ return mInitialScale;
}
}
diff --git a/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java b/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java
deleted file mode 100644
index 6b14be7..0000000
--- a/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java
+++ /dev/null
@@ -1,79 +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.dragndrop;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Rect;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.HolographicOutlineHelper;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.graphics.DragPreviewProvider;
-
-/**
- * Extension of {@link DragPreviewProvider} which provides a dummy outline when drag starts from
- * a different window.
- * It just draws an empty circle to a placeholder outline.
- */
-public class ExternalDragPreviewProvider extends DragPreviewProvider {
-
- private final Launcher mLauncher;
- private final ItemInfo mAddInfo;
-
- private final int[] mOutlineSize;
-
- public ExternalDragPreviewProvider(Launcher launcher, ItemInfo addInfo) {
- super(null);
- mLauncher = launcher;
- mAddInfo = addInfo;
-
- mOutlineSize = mLauncher.getWorkspace().estimateItemSize(mAddInfo, false);
- }
-
- public Rect getPreviewBounds() {
- Rect rect = new Rect();
- DeviceProfile dp = mLauncher.getDeviceProfile();
- rect.left = DRAG_BITMAP_PADDING / 2;
- rect.top = (mOutlineSize[1] - dp.cellHeightPx) / 2;
- rect.right = rect.left + dp.iconSizePx;
- rect.bottom = rect.top + dp.iconSizePx;
- return rect;
- }
-
- @Override
- public Bitmap createDragOutline(Canvas canvas) {
- final Bitmap b = Bitmap.createBitmap(mOutlineSize[0], mOutlineSize[1], Bitmap.Config.ALPHA_8);
- canvas.setBitmap(b);
-
- Paint paint = new Paint();
- paint.setColor(Color.WHITE);
- paint.setStyle(Paint.Style.FILL);
-
- // Use 0.9f times the radius for the actual circle to account for icon normalization.
- float radius = getPreviewBounds().width() * 0.5f;
- canvas.drawCircle(DRAG_BITMAP_PADDING / 2 + radius,
- DRAG_BITMAP_PADDING / 2 + radius, radius * 0.9f, paint);
-
- HolographicOutlineHelper.obtain(mLauncher).applyExpensiveOutlineWithBlur(b, canvas);
- canvas.setBitmap(null);
- return b;
- }
-}
diff --git a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
new file mode 100644
index 0000000..e794744
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
@@ -0,0 +1,136 @@
+/*
+ * 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.dragndrop;
+
+import android.graphics.PointF;
+import android.os.SystemClock;
+import android.view.DragEvent;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+
+import com.android.launcher3.ButtonDropTarget;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.util.FlingAnimation;
+
+/**
+ * Utility class to manage fling to delete action during drag and drop.
+ */
+public class FlingToDeleteHelper {
+
+ private static final float MAX_FLING_DEGREES = 35f;
+
+ private final Launcher mLauncher;
+ private final int mFlingToDeleteThresholdVelocity;
+
+ private ButtonDropTarget mDropTarget;
+ private VelocityTracker mVelocityTracker;
+
+ public FlingToDeleteHelper(Launcher launcher) {
+ mLauncher = launcher;
+ mFlingToDeleteThresholdVelocity = launcher.getResources()
+ .getDimensionPixelSize(R.dimen.drag_flingToDeleteMinVelocity);
+ }
+
+ public void recordMotionEvent(MotionEvent ev) {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+ }
+
+ /**
+ * Same as {@link #recordMotionEvent}. It creates a temporary {@link MotionEvent} object
+ * using {@param event} for tracking velocity.
+ */
+ public void recordDragEvent(long dragStartTime, DragEvent event) {
+ final int motionAction;
+ switch (event.getAction()) {
+ case DragEvent.ACTION_DRAG_STARTED:
+ motionAction = MotionEvent.ACTION_DOWN;
+ break;
+ case DragEvent.ACTION_DRAG_LOCATION:
+ motionAction = MotionEvent.ACTION_MOVE;
+ break;
+ case DragEvent.ACTION_DRAG_ENDED:
+ motionAction = MotionEvent.ACTION_UP;
+ break;
+ default:
+ return;
+ }
+ MotionEvent emulatedEvent = MotionEvent.obtain(dragStartTime, SystemClock.uptimeMillis(),
+ motionAction, event.getX(), event.getY(), 0);
+ recordMotionEvent(emulatedEvent);
+ emulatedEvent.recycle();
+ }
+
+ public void releaseVelocityTracker() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ public DropTarget getDropTarget() {
+ return mDropTarget;
+ }
+
+ public Runnable getFlingAnimation(DropTarget.DragObject dragObject) {
+ PointF vel = isFlingingToDelete();
+ if (vel == null) {
+ return null;
+ }
+ return new FlingAnimation(dragObject, vel, mDropTarget, mLauncher);
+ }
+
+ /**
+ * Determines whether the user flung the current item to delete it.
+ *
+ * @return the vector at which the item was flung, or null if no fling was detected.
+ */
+ private PointF isFlingingToDelete() {
+ if (mDropTarget == null) {
+ mDropTarget = (ButtonDropTarget) mLauncher.findViewById(R.id.delete_target_text);
+ }
+ if (mDropTarget == null || !mDropTarget.isDropEnabled()) return null;
+ ViewConfiguration config = ViewConfiguration.get(mLauncher);
+ mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
+ PointF vel = new PointF(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+ float theta = MAX_FLING_DEGREES + 1;
+ if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
+ // Do a quick dot product test to ensure that we are flinging upwards
+ PointF upVec = new PointF(0f, -1f);
+ theta = getAngleBetweenVectors(vel, upVec);
+ } else if (mLauncher.getDeviceProfile().isVerticalBarLayout() &&
+ mVelocityTracker.getXVelocity() < mFlingToDeleteThresholdVelocity) {
+ // Remove icon is on left side instead of top, so check if we are flinging to the left.
+ PointF leftVec = new PointF(-1f, 0f);
+ theta = getAngleBetweenVectors(vel, leftVec);
+ }
+ if (theta <= Math.toRadians(MAX_FLING_DEGREES)) {
+ return vel;
+ }
+ return null;
+ }
+
+ private float getAngleBetweenVectors(PointF vec1, PointF vec2) {
+ return (float) Math.acos(((vec1.x * vec2.x) + (vec1.y * vec2.y)) /
+ (vec1.length() * vec2.length()));
+ }
+}
diff --git a/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java b/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
new file mode 100644
index 0000000..36a0292
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
@@ -0,0 +1,99 @@
+package com.android.launcher3.dragndrop;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.RemoteViews;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.WidgetCell;
+
+/**
+ * Extension of {@link WidgetCell} which supports generating previews from {@link RemoteViews}
+ */
+public class LivePreviewWidgetCell extends WidgetCell {
+
+ private RemoteViews mPreview;
+
+ public LivePreviewWidgetCell(Context context) {
+ this(context, null);
+ }
+
+ public LivePreviewWidgetCell(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public LivePreviewWidgetCell(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public void setPreview(RemoteViews view) {
+ mPreview = view;
+ }
+
+ @Override
+ public void ensurePreview() {
+ if (mPreview != null && mActiveRequest == null) {
+ Bitmap preview = generateFromRemoteViews(
+ mActivity, mPreview, mItem.widgetInfo, mPresetPreviewSize, new int[1]);
+ if (preview != null) {
+ applyPreview(preview);
+ return;
+ }
+ }
+ super.ensurePreview();
+ }
+
+ /**
+ * Generates a bitmap by inflating {@param views}.
+ * @see com.android.launcher3.WidgetPreviewLoader#generateWidgetPreview
+ *
+ * TODO: Consider moving this to the background thread.
+ */
+ public static Bitmap generateFromRemoteViews(BaseActivity activity, RemoteViews views,
+ LauncherAppWidgetProviderInfo info, int previewSize, int[] preScaledWidthOut) {
+
+ DeviceProfile dp = activity.getDeviceProfile();
+ int viewWidth = dp.cellWidthPx * info.spanX;
+ int viewHeight = dp.cellHeightPx * info.spanY;
+
+ final View v;
+ try {
+ v = views.apply(activity, new FrameLayout(activity));
+ v.measure(MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.EXACTLY));
+
+ viewWidth = v.getMeasuredWidth();
+ viewHeight = v.getMeasuredHeight();
+ v.layout(0, 0, viewWidth, viewHeight);
+ } catch (Exception e) {
+ return null;
+ }
+
+ preScaledWidthOut[0] = viewWidth;
+ final int bitmapWidth, bitmapHeight;
+ final float scale;
+ if (viewWidth > previewSize) {
+ scale = ((float) previewSize) / viewWidth;
+ bitmapWidth = previewSize;
+ bitmapHeight = (int) (viewHeight * scale);
+ } else {
+ scale = 1;
+ bitmapWidth = viewWidth;
+ bitmapHeight = viewHeight;
+ }
+
+ Bitmap preview = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(preview);
+ c.scale(scale, scale);
+ v.draw(c);
+ c.setBitmap(null);
+ return preview;
+ }
+}
diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
new file mode 100644
index 0000000..df0c47c
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
@@ -0,0 +1,308 @@
+/*
+ * 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.appwidget.AppWidgetManager;
+import android.content.ClipDescription;
+import android.content.Intent;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.DragEvent;
+import android.view.View;
+import android.widget.RemoteViews;
+
+import com.android.launcher3.DeleteDropTarget;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.PendingAddItemInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.compat.PinItemRequestCompat;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.widget.PendingAddShortcutInfo;
+import com.android.launcher3.widget.PendingAddWidgetInfo;
+import com.android.launcher3.widget.PendingItemDragHelper;
+import com.android.launcher3.widget.WidgetAddFlowHandler;
+
+import java.util.UUID;
+
+/**
+ * {@link DragSource} for handling drop from a different window. This object is initialized
+ * in the source window and is passed on to the Launcher activity as an Intent extra.
+ */
+public class PinItemDragListener
+ implements Parcelable, View.OnDragListener, DragSource, DragOptions.PreDragCondition {
+
+ private static final String TAG = "PinItemDragListener";
+
+ private static final String MIME_TYPE_PREFIX = "com.android.launcher3.drag_and_drop/";
+ public static final String EXTRA_PIN_ITEM_DRAG_LISTENER = "pin_item_drag_listener";
+
+ private final PinItemRequestCompat mRequest;
+
+ // Position of preview relative to the touch location
+ private final Rect mPreviewRect;
+
+ private final int mPreviewBitmapWidth;
+ private final int mPreviewViewWidth;
+
+ // Randomly generated id used to verify the drag event.
+ private final String mId;
+
+ private Launcher mLauncher;
+ private DragController mDragController;
+ private long mDragStartTime;
+
+ public PinItemDragListener(PinItemRequestCompat request, Rect previewRect,
+ int previewBitmapWidth, int previewViewWidth) {
+ mRequest = request;
+ mPreviewRect = previewRect;
+ mPreviewBitmapWidth = previewBitmapWidth;
+ mPreviewViewWidth = previewViewWidth;
+ mId = UUID.randomUUID().toString();
+ }
+
+ private PinItemDragListener(Parcel parcel) {
+ mRequest = PinItemRequestCompat.CREATOR.createFromParcel(parcel);
+ mPreviewRect = Rect.CREATOR.createFromParcel(parcel);
+ mPreviewBitmapWidth = parcel.readInt();
+ mPreviewViewWidth = parcel.readInt();
+ mId = parcel.readString();
+ }
+
+ public String getMimeType() {
+ return MIME_TYPE_PREFIX + mId;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int i) {
+ mRequest.writeToParcel(parcel, i);
+ mPreviewRect.writeToParcel(parcel, i);
+ parcel.writeInt(mPreviewBitmapWidth);
+ parcel.writeInt(mPreviewViewWidth);
+ parcel.writeString(mId);
+ }
+
+ public void setLauncher(Launcher launcher) {
+ mLauncher = launcher;
+ mDragController = launcher.getDragController();
+ }
+
+ @Override
+ public boolean onDrag(View view, DragEvent event) {
+ if (mLauncher == null || mDragController == null) {
+ postCleanup();
+ return false;
+ }
+ if (event.getAction() == DragEvent.ACTION_DRAG_STARTED) {
+ if (onDragStart(event)) {
+ return true;
+ } else {
+ postCleanup();
+ return false;
+ }
+ }
+ return mDragController.onDragEvent(mDragStartTime, event);
+ }
+
+ private boolean onDragStart(DragEvent event) {
+ if (!mRequest.isValid()) {
+ return false;
+ }
+ ClipDescription desc = event.getClipDescription();
+ if (desc == null || !desc.hasMimeType(getMimeType())) {
+ Log.e(TAG, "Someone started a dragAndDrop before us.");
+ return false;
+ }
+
+ final PendingAddItemInfo item;
+ if (mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_SHORTCUT) {
+ item = new PendingAddShortcutInfo(
+ new PinShortcutRequestActivityInfo(mRequest, mLauncher));
+ } else {
+ // mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_APPWIDGET
+ LauncherAppWidgetProviderInfo providerInfo =
+ LauncherAppWidgetProviderInfo.fromProviderInfo(
+ mLauncher, mRequest.getAppWidgetProviderInfo(mLauncher));
+ final PinWidgetFlowHandler flowHandler =
+ new PinWidgetFlowHandler(providerInfo, mRequest);
+ item = new PendingAddWidgetInfo(providerInfo) {
+ @Override
+ public WidgetAddFlowHandler getHandler() {
+ return flowHandler;
+ }
+ };
+ }
+ View view = new View(mLauncher);
+ view.setTag(item);
+
+ Point downPos = new Point((int) event.getX(), (int) event.getY());
+ DragOptions options = new DragOptions();
+ options.systemDndStartPoint = downPos;
+ options.preDragCondition = this;
+
+ // We use drag event position as the screenPos for the preview image. Since mPreviewRect
+ // already includes the view position relative to the drag event on the source window,
+ // and the absolute position (position relative to the screen) of drag event is same
+ // across windows, using drag position here give a good estimate for relative position
+ // to source window.
+ PendingItemDragHelper dragHelper = new PendingItemDragHelper(view);
+ if (mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_APPWIDGET) {
+ dragHelper.setPreview(getPreview(mRequest));
+ }
+
+ dragHelper.startDrag(new Rect(mPreviewRect),
+ mPreviewBitmapWidth, mPreviewViewWidth, downPos, this, options);
+ mDragStartTime = SystemClock.uptimeMillis();
+ return true;
+ }
+
+ @Override
+ public boolean shouldStartDrag(double distanceDragged) {
+ // Stay in pre-drag mode, if workspace is locked.
+ return !mLauncher.isWorkspaceLocked();
+ }
+
+ @Override
+ public void onPreDragStart(DropTarget.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.
+ mLauncher.getDragLayer().setAlpha(1);
+
+ dragObject.dragView.setColor(
+ mLauncher.getResources().getColor(R.color.delete_target_hover_tint));
+ }
+
+ @Override
+ public void onPreDragEnd(DropTarget.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;
+ }
+ postCleanup();
+ }
+
+ @Override
+ public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
+ LauncherLogProto.Target targetParent) {
+ targetParent.containerType = ContainerType.PINITEM;
+ }
+
+ private void postCleanup() {
+ if (mLauncher != null) {
+ // Remove any drag params from the launcher intent since the drag operation is complete.
+ Intent newIntent = new Intent(mLauncher.getIntent());
+ newIntent.removeExtra(EXTRA_PIN_ITEM_DRAG_LISTENER);
+ mLauncher.setIntent(newIntent);
+ }
+
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ removeListener();
+ }
+ });
+ }
+
+ public void removeListener() {
+ if (mLauncher != null) {
+ mLauncher.getDragLayer().setOnDragListener(null);
+ }
+ }
+
+ public static RemoteViews getPreview(PinItemRequestCompat request) {
+ Bundle extras = request.getExtras();
+ if (extras != null &&
+ extras.get(AppWidgetManager.EXTRA_APPWIDGET_PREVIEW) instanceof RemoteViews) {
+ return (RemoteViews) extras.get(AppWidgetManager.EXTRA_APPWIDGET_PREVIEW);
+ }
+ return null;
+ }
+
+ public static boolean handleDragRequest(Launcher launcher, Intent intent) {
+ if (intent == null || !Intent.ACTION_MAIN.equals(intent.getAction())) {
+ return false;
+ }
+ Parcelable dragExtra = intent.getParcelableExtra(EXTRA_PIN_ITEM_DRAG_LISTENER);
+ if (dragExtra instanceof PinItemDragListener) {
+ PinItemDragListener dragListener = (PinItemDragListener) dragExtra;
+ dragListener.setLauncher(launcher);
+
+ launcher.getDragLayer().setOnDragListener(dragListener);
+ return true;
+ }
+ return false;
+ }
+
+ public static final Parcelable.Creator<PinItemDragListener> CREATOR =
+ new Parcelable.Creator<PinItemDragListener>() {
+ public PinItemDragListener createFromParcel(Parcel source) {
+ return new PinItemDragListener(source);
+ }
+
+ public PinItemDragListener[] newArray(int size) {
+ return new PinItemDragListener[size];
+ }
+ };
+}
diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
new file mode 100644
index 0000000..bb5ac5b
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -0,0 +1,96 @@
+/*
+ * 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.annotation.TargetApi;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+
+import com.android.launcher3.IconCache;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.R;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.PinItemRequestCompat;
+import com.android.launcher3.compat.ShortcutConfigActivityInfo;
+
+/**
+ * Extension of ShortcutConfigActivityInfo to be used in the confirmation prompt for pin item
+ * request.
+ */
+@TargetApi(Build.VERSION_CODES.N_MR1)
+class PinShortcutRequestActivityInfo extends ShortcutConfigActivityInfo {
+
+ // Class name used in the target component, such that it will never represent an
+ // actual existing class.
+ private static final String DUMMY_COMPONENT_CLASS = "pinned-shortcut";
+
+ private final PinItemRequestCompat mRequest;
+ private final ShortcutInfo mInfo;
+ private final Context mContext;
+
+ public PinShortcutRequestActivityInfo(PinItemRequestCompat request, Context context) {
+ super(new ComponentName(request.getShortcutInfo().getPackage(), DUMMY_COMPONENT_CLASS),
+ request.getShortcutInfo().getUserHandle());
+ mRequest = request;
+ mInfo = request.getShortcutInfo();
+ mContext = context;
+ }
+
+ @Override
+ public int getItemType() {
+ return LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+ }
+
+ @Override
+ public CharSequence getLabel() {
+ return mInfo.getShortLabel();
+ }
+
+ @Override
+ public Drawable getFullResIcon(IconCache cache) {
+ return mContext.getSystemService(LauncherApps.class)
+ .getShortcutIconDrawable(mInfo, LauncherAppState.getIDP(mContext).fillResIconDpi);
+ }
+
+ @Override
+ 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;
+ // Delay the actual accept() call until the drop animation is complete.
+ return LauncherAppsCompat.createShortcutInfoFromPinItemRequest(
+ mContext, mRequest, duration);
+ }
+
+ @Override
+ public boolean startConfigActivity(Activity activity, int requestCode) {
+ return false;
+ }
+
+ @Override
+ public boolean isPersistable() {
+ return false;
+ }
+}
diff --git a/src/com/android/launcher3/dragndrop/PinWidgetFlowHandler.java b/src/com/android/launcher3/dragndrop/PinWidgetFlowHandler.java
new file mode 100644
index 0000000..b6da6ad
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/PinWidgetFlowHandler.java
@@ -0,0 +1,80 @@
+/*
+ * 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.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.compat.PinItemRequestCompat;
+import com.android.launcher3.widget.WidgetAddFlowHandler;
+
+/**
+ * Extension of WidgetAddFlowHandler to handle pin item request behavior.
+ *
+ * No config activity is shown even if it is defined in widget config. And a callback is sent when
+ * the widget is bound.
+ */
+public class PinWidgetFlowHandler extends WidgetAddFlowHandler implements Parcelable {
+
+ private final PinItemRequestCompat mRequest;
+
+ public PinWidgetFlowHandler(AppWidgetProviderInfo providerInfo, PinItemRequestCompat request) {
+ super(providerInfo);
+ mRequest = request;
+ }
+
+ protected PinWidgetFlowHandler(Parcel parcel) {
+ super(parcel);
+ mRequest = PinItemRequestCompat.CREATOR.createFromParcel(parcel);
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int i) {
+ super.writeToParcel(parcel, i);
+ mRequest.writeToParcel(parcel, i);
+ }
+
+ @Override
+ public boolean startConfigActivity(Launcher launcher, int appWidgetId, ItemInfo info,
+ int requestCode) {
+ Bundle extras = new Bundle();
+ extras.putInt(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+ mRequest.accept(extras);
+ return false;
+ }
+
+ @Override
+ public boolean needsConfigure() {
+ return false;
+ }
+
+ public static final Parcelable.Creator<PinWidgetFlowHandler> CREATOR =
+ new Parcelable.Creator<PinWidgetFlowHandler>() {
+ public PinWidgetFlowHandler createFromParcel(Parcel source) {
+ return new PinWidgetFlowHandler(source);
+ }
+
+ public PinWidgetFlowHandler[] newArray(int size) {
+ return new PinWidgetFlowHandler[size];
+ }
+ };
+}
diff --git a/src/com/android/launcher3/dynamicui/ColorExtractionService.java b/src/com/android/launcher3/dynamicui/ColorExtractionService.java
index 1369f60..f94d442 100644
--- a/src/com/android/launcher3/dynamicui/ColorExtractionService.java
+++ b/src/com/android/launcher3/dynamicui/ColorExtractionService.java
@@ -16,24 +16,35 @@
package com.android.launcher3.dynamicui;
+import android.annotation.TargetApi;
import android.app.IntentService;
import android.app.WallpaperManager;
import android.content.Intent;
import android.graphics.Bitmap;
+import android.graphics.BitmapRegionDecoder;
+import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
+import android.os.Build;
import android.os.Bundle;
+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 IntentService {
+ private static final String TAG = "ColorExtractionService";
+
/** The fraction of the wallpaper to extract colors for use on the hotseat. */
private static final float HOTSEAT_FRACTION = 1f / 4;
@@ -45,32 +56,18 @@
protected void onHandleIntent(Intent intent) {
WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
int wallpaperId = ExtractionUtils.getWallpaperId(wallpaperManager);
+
ExtractedColors extractedColors = new ExtractedColors();
if (wallpaperManager.getWallpaperInfo() != null) {
// We can't extract colors from live wallpapers, so just use the default color always.
- extractedColors.updatePalette(null);
extractedColors.updateHotseatPalette(null);
} else {
- Bitmap wallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap();
- Palette palette = Palette.from(wallpaper).generate();
- extractedColors.updatePalette(palette);
// We extract colors for the hotseat and status bar separately,
// since they only consider part of the wallpaper.
- Palette hotseatPalette = Palette.from(wallpaper)
- .setRegion(0, (int) (wallpaper.getHeight() * (1f - HOTSEAT_FRACTION)),
- wallpaper.getWidth(), wallpaper.getHeight())
- .clearFilters()
- .generate();
- extractedColors.updateHotseatPalette(hotseatPalette);
+ extractedColors.updateHotseatPalette(getHotseatPalette());
if (FeatureFlags.LIGHT_STATUS_BAR) {
- int statusBarHeight = getResources()
- .getDimensionPixelSize(R.dimen.status_bar_height);
- Palette statusBarPalette = Palette.from(wallpaper)
- .setRegion(0, 0, wallpaper.getWidth(), statusBarHeight)
- .clearFilters()
- .generate();
- extractedColors.updateStatusBarPalette(statusBarPalette);
+ extractedColors.updateStatusBarPalette(getStatusBarPalette());
}
}
@@ -84,4 +81,63 @@
LauncherSettings.Settings.METHOD_SET_EXTRACTED_COLORS_AND_WALLPAPER_ID,
null, extras);
}
+
+ @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();
+ }
}
diff --git a/src/com/android/launcher3/dynamicui/ExtractedColors.java b/src/com/android/launcher3/dynamicui/ExtractedColors.java
index 6a3011d..711508e 100644
--- a/src/com/android/launcher3/dynamicui/ExtractedColors.java
+++ b/src/com/android/launcher3/dynamicui/ExtractedColors.java
@@ -113,34 +113,6 @@
}
/**
- * Updates colors based on the palette.
- * If the palette is null, the default color is used in all cases.
- */
- public void updatePalette(Palette palette) {
- if (palette == null) {
- for (int i = 0; i < NUM_COLOR_PROFILES; i++) {
- setColorAtIndex(i, ExtractedColors.DEFAULT_COLOR);
- }
- } else {
- // We currently don't use any of the colors defined by the Palette API,
- // but this is how we would add them if we ever need them.
-
- // setColorAtIndex(ExtractedColors.VIBRANT_INDEX,
- // palette.getVibrantColor(ExtractedColors.DEFAULT_COLOR));
- // setColorAtIndex(ExtractedColors.VIBRANT_DARK_INDEX,
- // palette.getDarkVibrantColor(ExtractedColors.DEFAULT_DARK));
- // setColorAtIndex(ExtractedColors.VIBRANT_LIGHT_INDEX,
- // palette.getLightVibrantColor(ExtractedColors.DEFAULT_LIGHT));
- // setColorAtIndex(ExtractedColors.MUTED_INDEX,
- // palette.getMutedColor(DEFAULT_COLOR));
- // setColorAtIndex(ExtractedColors.MUTED_DARK_INDEX,
- // palette.getDarkMutedColor(ExtractedColors.DEFAULT_DARK));
- // setColorAtIndex(ExtractedColors.MUTED_LIGHT_INDEX,
- // palette.getLightVibrantColor(ExtractedColors.DEFAULT_LIGHT));
- }
- }
-
- /**
* The hotseat's color is defined as follows:
* - 12% black for super light wallpaper
* - 18% white for super dark
diff --git a/src/com/android/launcher3/dynamicui/ExtractionUtils.java b/src/com/android/launcher3/dynamicui/ExtractionUtils.java
index 6dc0035..1cf5d55 100644
--- a/src/com/android/launcher3/dynamicui/ExtractionUtils.java
+++ b/src/com/android/launcher3/dynamicui/ExtractionUtils.java
@@ -16,18 +16,18 @@
package com.android.launcher3.dynamicui;
+import android.annotation.TargetApi;
import android.app.WallpaperManager;
import android.content.Context;
import android.content.Intent;
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 java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
import java.util.List;
/**
@@ -37,7 +37,6 @@
public static final String EXTRACTED_COLORS_PREFERENCE_KEY = "pref_extractedColors";
public static final String WALLPAPER_ID_PREFERENCE_KEY = "pref_wallpaperId";
- private static final int FLAG_SET_SYSTEM = 1 << 0; // TODO: use WallpaperManager.FLAG_SET_SYSTEM
private static final float MIN_CONTRAST_RATIO = 2f;
/**
@@ -63,7 +62,7 @@
}
private static boolean hasWallpaperIdChanged(Context context) {
- if (!Utilities.isNycOrAbove()) {
+ if (!Utilities.ATLEAST_NOUGAT) {
// TODO: update an id in sharedprefs in onWallpaperChanged broadcast, and read it here.
return false;
}
@@ -73,14 +72,10 @@
return wallpaperId != savedWallpaperId;
}
+ @TargetApi(Build.VERSION_CODES.N)
public static int getWallpaperId(WallpaperManager wallpaperManager) {
- // TODO: use WallpaperManager#getWallpaperId(WallpaperManager.FLAG_SET_SYSTEM) directly.
- try {
- Method getWallpaperId = WallpaperManager.class.getMethod("getWallpaperId", int.class);
- return (int) getWallpaperId.invoke(wallpaperManager, FLAG_SET_SYSTEM);
- } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
- return -1;
- }
+ return Utilities.ATLEAST_NOUGAT ?
+ wallpaperManager.getWallpaperId(WallpaperManager.FLAG_SYSTEM) : -1;
}
public static boolean isSuperLight(Palette p) {
diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
index 6ee02f9..840fcf5 100644
--- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
@@ -1,16 +1,17 @@
package com.android.launcher3.folder;
-import android.graphics.Path;
-import android.graphics.Point;
+import android.view.View;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+
+import java.util.ArrayList;
+import java.util.List;
public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule {
static final int MAX_NUM_ITEMS_IN_PREVIEW = 4;
private static final int MIN_NUM_ITEMS_IN_PREVIEW = 2;
+ private static final int MAX_NUM_ITEMS_PER_ROW = 2;
final float MIN_SCALE = 0.48f;
final float MAX_SCALE = 0.58f;
@@ -38,7 +39,7 @@
public FolderIcon.PreviewItemDrawingParams computePreviewItemDrawingParams(int index,
int curNumItems, FolderIcon.PreviewItemDrawingParams params) {
- float totalScale = scaleForNumItems(curNumItems);
+ float totalScale = scaleForItem(index, curNumItems);
float transX;
float transY;
float overlayAlpha = 0;
@@ -94,7 +95,7 @@
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 * scaleForNumItems(curNumItems)) / 2;
+ float halfIconSize = (mIconSize * scaleForItem(index, 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
@@ -104,7 +105,9 @@
}
- private float scaleForNumItems(int numItems) {
+ @Override
+ public float scaleForItem(int index, int numItems) {
+ // Scale is determined by the number of items in the preview.
float scale = 1f;
if (numItems <= 2) {
scale = MAX_SCALE;
@@ -118,7 +121,7 @@
}
@Override
- public int numItems() {
+ public int maxNumItems() {
return MAX_NUM_ITEMS_IN_PREVIEW;
}
@@ -127,4 +130,23 @@
return true;
}
+ @Override
+ public List<View> getItemsToDisplay(Folder folder) {
+ List<View> items = new ArrayList<>(folder.getItemsInReadingOrder());
+ int numItems = items.size();
+ if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION && numItems > MAX_NUM_ITEMS_IN_PREVIEW) {
+ // We match the icons in the preview with the layout of the opened folder (b/27944225),
+ // but we still need to figure out how we want to handle updating the preview when the
+ // upper left quadrant changes.
+ int appsPerRow = folder.mContent.getPageAt(0).getCountX();
+ int appsToDelete = appsPerRow - MAX_NUM_ITEMS_PER_ROW;
+
+ // We only display the upper left quadrant.
+ while (appsToDelete > 0) {
+ items.remove(MAX_NUM_ITEMS_PER_ROW);
+ appsToDelete--;
+ }
+ }
+ return items.subList(0, Math.min(numItems, MAX_NUM_ITEMS_IN_PREVIEW));
+ }
}
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 698e5aa..93c9ea8 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -22,15 +22,11 @@
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
-import android.graphics.PointF;
import android.graphics.Rect;
-import android.os.Build;
import android.text.InputType;
import android.text.Selection;
-import android.text.Spannable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ActionMode;
@@ -45,13 +41,11 @@
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.LinearLayout;
import android.widget.TextView;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Alarm;
import com.android.launcher3.AppInfo;
-import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DragSource;
@@ -62,24 +56,24 @@
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.LauncherModel;
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.AccessibileDragListenerAdapter;
+import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.config.ProviderConfig;
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.pageindicators.PageIndicatorDots;
-import com.android.launcher3.shortcuts.DeepShortcutsContainer;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.CircleRevealOutlineProvider;
import com.android.launcher3.util.Thunk;
@@ -91,9 +85,10 @@
/**
* Represents a set of icons chosen by the user or generated by the system.
*/
-public class Folder extends LinearLayout implements DragSource, View.OnClickListener,
+public class Folder extends AbstractFloatingView implements DragSource, View.OnClickListener,
View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
- View.OnFocusChangeListener, DragListener, DropTargetSource {
+ View.OnFocusChangeListener, DragListener, DropTargetSource,
+ ExtendedEditText.OnBackKeyListener {
private static final String TAG = "Launcher.Folder";
/**
@@ -110,7 +105,12 @@
/**
* Time for which the scroll hint is shown before automatically changing page.
*/
- public static final int SCROLL_HINT_DURATION = DragController.SCROLL_DELAY;
+ public static final int SCROLL_HINT_DURATION = 500;
+ public static final int RESCROLL_DELAY = PagedView.PAGE_SNAP_ANIMATION_DURATION + 150;
+
+ public static final int SCROLL_NONE = -1;
+ public static final int SCROLL_LEFT = 0;
+ public static final int SCROLL_RIGHT = 1;
/**
* Fraction of icon width which behave as scroll region.
@@ -137,8 +137,6 @@
private final int mMaterialExpandDuration;
private final int mMaterialExpandStagger;
- private final InputMethodManager mInputMethodManager;
-
protected final Launcher mLauncher;
protected DragController mDragController;
public FolderInfo mInfo;
@@ -186,8 +184,8 @@
// Folder scrolling
private int mScrollAreaOffset;
- @Thunk int mScrollHintDir = DragController.SCROLL_NONE;
- @Thunk int mCurrentScrollDir = DragController.SCROLL_NONE;
+ @Thunk int mScrollHintDir = SCROLL_NONE;
+ @Thunk int mCurrentScrollDir = SCROLL_NONE;
/**
* Used to inflate the Workspace from XML.
@@ -198,9 +196,6 @@
public Folder(Context context, AttributeSet attrs) {
super(context, attrs);
setAlwaysDrawnWithCacheEnabled(false);
- mInputMethodManager = (InputMethodManager)
- getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
-
Resources res = getResources();
mExpandDuration = res.getInteger(R.integer.config_folderExpandDuration);
mMaterialExpandDuration = res.getInteger(R.integer.config_materialFolderExpandDuration);
@@ -227,14 +222,7 @@
mPageIndicator = (PageIndicatorDots) findViewById(R.id.folder_page_indicator);
mFolderName = (ExtendedEditText) findViewById(R.id.folder_name);
- mFolderName.setOnBackKeyListener(new ExtendedEditText.OnBackKeyListener() {
- @Override
- public boolean onBackKey() {
- // Close the activity on back key press
- doneEditingFolderName(true);
- return false;
- }
- });
+ mFolderName.setOnBackKeyListener(this);
mFolderName.setOnFocusChangeListener(this);
if (!Utilities.ATLEAST_MARSHMALLOW) {
@@ -259,8 +247,11 @@
}
mFolderName.setOnEditorActionListener(this);
mFolderName.setSelectAllOnFocus(true);
- mFolderName.setInputType(mFolderName.getInputType() |
- InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+ mFolderName.setInputType(mFolderName.getInputType()
+ & ~InputType.TYPE_TEXT_FLAG_AUTO_CORRECT
+ & ~InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
+ | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+ mFolderName.forceDisableSuggestions(true);
mFooter = findViewById(R.id.folder_footer);
@@ -281,17 +272,7 @@
public boolean onLongClick(View v) {
// Return if global dragging is not enabled
if (!mLauncher.isDraggingEnabled()) return true;
- DragOptions dragOptions = new DragOptions();
- if (v instanceof BubbleTextView) {
- BubbleTextView icon = (BubbleTextView) v;
- if (icon.hasDeepShortcuts()) {
- DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon(icon);
- if (dsc != null) {
- dragOptions.deferDragCondition = dsc.createDeferDragCondition(null);
- }
- }
- }
- return startDrag(v, dragOptions);
+ return startDrag(v, new DragOptions());
}
public boolean startDrag(View v, DragOptions options) {
@@ -307,7 +288,7 @@
mDragController.addDragListener(this);
if (options.isAccessibleDrag) {
- mDragController.addDragListener(new AccessibileDragListenerAdapter(
+ mDragController.addDragListener(new AccessibleDragListenerAdapter(
mContent, CellLayout.FOLDER_ACCESSIBILITY_DRAG) {
@Override
@@ -367,52 +348,44 @@
});
}
- public void dismissEditingName() {
- mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
- doneEditingFolderName(true);
- }
- public void doneEditingFolderName(boolean commit) {
+ @Override
+ public boolean onBackKey() {
mFolderName.setHint(sHintText);
// Convert to a string here to ensure that no other state associated with the text field
// gets saved.
String newTitle = mFolderName.getText().toString();
mInfo.setTitle(newTitle);
- LauncherModel.updateItemInDatabase(mLauncher, mInfo);
+ mLauncher.getModelWriter().updateItemInDatabase(mInfo);
- if (commit) {
- Utilities.sendCustomAccessibilityEvent(
- this, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
- getContext().getString(R.string.folder_renamed, newTitle));
- }
+ Utilities.sendCustomAccessibilityEvent(
+ this, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
+ getContext().getString(R.string.folder_renamed, newTitle));
// This ensures that focus is gained every time the field is clicked, which selects all
// the text and brings up the soft keyboard if necessary.
mFolderName.clearFocus();
- Selection.setSelection((Spannable) mFolderName.getText(), 0, 0);
+ Selection.setSelection(mFolderName.getText(), 0, 0);
mIsEditingName = false;
+ return true;
}
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
- dismissEditingName();
+ mFolderName.dispatchBackKey();
return true;
}
return false;
}
- public View getEditTextRegion() {
- return mFolderName;
+ @Override
+ public ExtendedEditText getActiveTextView() {
+ return isEditingName() ? mFolderName : null;
}
- /**
- * We need to handle touch events to prevent them from falling through to the workspace below.
- */
- @SuppressLint("ClickableViewAccessibility")
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- return true;
+ public FolderIcon getFolderIcon() {
+ return mFolderIcon;
}
public void setDragController(DragController dragController) {
@@ -464,7 +437,7 @@
// TODO: Remove this, as with multi-page folders, there will never be any overflow
for (ShortcutInfo item: overflow) {
mInfo.remove(item, false);
- LauncherModel.deleteItemFromDatabase(mLauncher, item);
+ mLauncher.getModelWriter().deleteItemFromDatabase(item);
}
DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
@@ -528,8 +501,32 @@
mState = STATE_SMALL;
}
+ /**
+ * 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
+ * is played.
+ */
public void animateOpen() {
- if (!(getParent() instanceof DragLayer)) return;
+ Folder openFolder = getOpen(mLauncher);
+ if (openFolder != null && openFolder != this) {
+ // Close any open folder before opening a folder.
+ openFolder.close(true);
+ }
+
+ DragLayer dragLayer = mLauncher.getDragLayer();
+ // Just verify that the folder hasn't already been added to the DragLayer.
+ // There was a one-off crash where the folder had a parent already.
+ if (getParent() == null) {
+ dragLayer.addView(this);
+ mDragController.addDropTarget(this);
+ } else {
+ if (ProviderConfig.IS_DOGFOOD_BUILD) {
+ Log.e(TAG, "Opening folder (" + this + ") which already has a parent:"
+ + getParent());
+ }
+ }
+
+ mIsOpen = true;
mContent.completePendingPageChanges();
if (!mDragInProgress) {
@@ -542,83 +539,65 @@
// dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice.
mDeleteFolderOnDropCompleted = false;
- Animator openFolderAnim = null;
final Runnable onCompleteRunnable;
- if (!Utilities.ATLEAST_LOLLIPOP) {
- positionAndSizeAsIcon();
- centerAboutIcon();
+ prepareReveal();
+ centerAboutIcon();
- final ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(this, 1, 1, 1);
- oa.setDuration(mExpandDuration);
- openFolderAnim = oa;
+ mFolderIcon.growAndFadeOut();
- setLayerType(LAYER_TYPE_HARDWARE, null);
- onCompleteRunnable = new Runnable() {
- @Override
- public void run() {
- setLayerType(LAYER_TYPE_NONE, null);
- }
- };
- } else {
- prepareReveal();
- centerAboutIcon();
+ AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
+ int width = getFolderWidth();
+ int height = getFolderHeight();
- AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
- int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
- 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);
- 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));
- 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);
- 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));
- 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));
- 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));
- 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);
- anim.play(drift);
- anim.play(iconsAlpha);
- anim.play(textAlpha);
- anim.play(reveal);
-
- openFolderAnim = anim;
-
- mContent.setLayerType(LAYER_TYPE_HARDWARE, null);
- mFooter.setLayerType(LAYER_TYPE_HARDWARE, null);
- onCompleteRunnable = new Runnable() {
- @Override
- public void run() {
- mContent.setLayerType(LAYER_TYPE_NONE, null);
- mFooter.setLayerType(LAYER_TYPE_NONE, null);
- mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
- }
- };
- }
- openFolderAnim.addListener(new AnimatorListenerAdapter() {
+ mContent.setLayerType(LAYER_TYPE_HARDWARE, null);
+ mFooter.setLayerType(LAYER_TYPE_HARDWARE, null);
+ onCompleteRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mContent.setLayerType(LAYER_TYPE_NONE, null);
+ mFooter.setLayerType(LAYER_TYPE_NONE, null);
+ mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
+ }
+ };
+ anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
Utilities.sendCustomAccessibilityEvent(
@@ -649,21 +628,20 @@
// Do not update the flag if we are in drag mode. The flag will be updated, when we
// actually drop the icon.
final boolean updateAnimationFlag = !mDragInProgress;
- openFolderAnim.addListener(new AnimatorListenerAdapter() {
+ anim.addListener(new AnimatorListenerAdapter() {
@SuppressLint("InlinedApi")
@Override
public void onAnimationEnd(Animator animation) {
mFolderName.animate().setDuration(FOLDER_NAME_ANIMATION_DURATION)
.translationX(0)
- .setInterpolator(Utilities.ATLEAST_LOLLIPOP ?
- AnimationUtils.loadInterpolator(mLauncher,
- android.R.interpolator.fast_out_slow_in)
- : new LogDecelerateInterpolator(100, 0));
+ .setInterpolator(AnimationUtils.loadInterpolator(
+ mLauncher, android.R.interpolator.fast_out_slow_in));
mPageIndicator.playEntryAnimation();
if (updateAnimationFlag) {
- mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, mLauncher);
+ mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true,
+ mLauncher.getModelWriter());
}
}
});
@@ -672,7 +650,7 @@
}
mPageIndicator.stopAllAnimations();
- openFolderAnim.start();
+ anim.start();
// Make sure the folder picks up the last drag move even if the finger doesn't move.
if (mDragController.isDragging()) {
@@ -680,6 +658,11 @@
}
mContent.verifyVisibleHighResIcons(mContent.getNextPage());
+
+ // Notify the accessibility manager that this folder "window" has appeared and occluded
+ // the workspace items
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ dragLayer.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
}
public void beginExternalDrag() {
@@ -692,14 +675,44 @@
mDragController.addDragListener(this);
}
- public void animateClosed() {
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_FOLDER) != 0;
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ mIsOpen = false;
+
+ if (isEditingName()) {
+ mFolderName.dispatchBackKey();
+ }
+
+ if (mFolderIcon != null) {
+ mFolderIcon.shrinkAndFadeIn(animate);
+ }
+
if (!(getParent() instanceof DragLayer)) return;
+ DragLayer parent = (DragLayer) getParent();
+
+ if (animate) {
+ animateClosed();
+ } else {
+ closeComplete(false);
+ }
+
+ // Notify the accessibility manager that this folder "window" has disappeared and no
+ // longer occludes the workspace items
+ parent.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ }
+
+ private void animateClosed() {
final ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(this, 0, 0.9f, 0.9f);
oa.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
setLayerType(LAYER_TYPE_NONE, null);
- close(true);
+ closeComplete(true);
}
@Override
public void onAnimationStart(Animator animation) {
@@ -715,7 +728,7 @@
oa.start();
}
- public void close(boolean wasAnimated) {
+ private void closeComplete(boolean wasAnimated) {
// TODO: Clear all active animations.
DragLayer parent = (DragLayer) getParent();
if (parent != null) {
@@ -767,7 +780,6 @@
}
};
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public boolean isLayoutRtl() {
return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
}
@@ -811,15 +823,15 @@
boolean isOutsideRightEdge = x > (getWidth() - cellOverlap);
if (currentPage > 0 && (mContent.mIsRtl ? isOutsideRightEdge : isOutsideLeftEdge)) {
- showScrollHint(DragController.SCROLL_LEFT, d);
+ showScrollHint(SCROLL_LEFT, d);
} else if (currentPage < (mContent.getPageCount() - 1)
&& (mContent.mIsRtl ? isOutsideLeftEdge : isOutsideRightEdge)) {
- showScrollHint(DragController.SCROLL_RIGHT, d);
+ showScrollHint(SCROLL_RIGHT, d);
} else {
mOnScrollHintAlarm.cancelAlarm();
- if (mScrollHintDir != DragController.SCROLL_NONE) {
+ if (mScrollHintDir != SCROLL_NONE) {
mContent.clearScrollHint();
- mScrollHintDir = DragController.SCROLL_NONE;
+ mScrollHintDir = SCROLL_NONE;
}
}
}
@@ -850,8 +862,8 @@
};
public void completeDragExit() {
- if (mInfo.opened) {
- mLauncher.closeFolder();
+ if (mIsOpen) {
+ close(true);
mRearrangeOnClose = true;
} else if (mState == STATE_ANIMATING) {
mRearrangeOnClose = true;
@@ -877,9 +889,9 @@
mOnScrollHintAlarm.cancelAlarm();
mScrollPauseAlarm.cancelAlarm();
- if (mScrollHintDir != DragController.SCROLL_NONE) {
+ if (mScrollHintDir != SCROLL_NONE) {
mContent.clearScrollHint();
- mScrollHintDir = DragController.SCROLL_NONE;
+ mScrollHintDir = SCROLL_NONE;
}
}
@@ -916,7 +928,7 @@
if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon && target != this) {
replaceFolderWithFinalItem();
}
- } else if (!mDragController.isDeferringDrag()) {
+ } else {
// The drag failed, we need to return the item to the folder
ShortcutInfo info = (ShortcutInfo) d.dragInfo;
View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info)
@@ -955,7 +967,8 @@
// been refreshed yet.
if (getItemCount() <= mContent.itemsPerPage()) {
// Show the animation, next time something is added to the folder.
- mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, false, mLauncher);
+ mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, false,
+ mLauncher.getModelWriter());
}
if (!isFlingToDelete) {
@@ -985,13 +998,8 @@
}
@Override
- public boolean supportsFlingToDelete() {
- return true;
- }
-
- @Override
public boolean supportsAppInfoDropTarget() {
- return !FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND;
+ return true;
}
@Override
@@ -999,16 +1007,6 @@
return true;
}
- @Override
- public void onFlingToDelete(DragObject d, PointF vec) {
- // Do nothing
- }
-
- @Override
- public void onFlingToDeleteCompleted() {
- // Do nothing
- }
-
private void updateItemLocationsInDatabaseBatch() {
ArrayList<View> list = getItemsInReadingOrder();
ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
@@ -1019,7 +1017,7 @@
items.add(info);
}
- LauncherModel.moveItemsInDatabase(mLauncher, items, mInfo.id, 0);
+ mLauncher.getModelWriter().moveItemsInDatabase(items, mInfo.id, 0);
}
public void notifyDrop() {
@@ -1041,7 +1039,7 @@
DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
DragLayer parent = (DragLayer) mLauncher.findViewById(R.id.drag_layer);
- int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
+ int width = getFolderWidth();
int height = getFolderHeight();
float scale = parent.getDescendantRectRelativeToSelf(mFolderIcon, sTempRect);
@@ -1083,6 +1081,7 @@
int folderPivotY = height / 2 + (centeredTop - top);
setPivotX(folderPivotX);
setPivotY(folderPivotY);
+
mFolderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() *
(1.0f * folderPivotX / width));
mFolderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() *
@@ -1114,6 +1113,10 @@
return Math.max(mContent.getDesiredWidth(), MIN_CONTENT_DIMEN);
}
+ private int getFolderWidth() {
+ return getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
+ }
+
private int getFolderHeight() {
return getFolderHeight(getContentAreaHeight());
}
@@ -1186,8 +1189,8 @@
mInfo.screenId);
ShortcutInfo finalItem = mInfo.contents.remove(0);
newIcon = mLauncher.createShortcut(cellLayout, finalItem);
- LauncherModel.addOrMoveItemInDatabase(mLauncher, finalItem, mInfo.container,
- mInfo.screenId, mInfo.cellX, mInfo.cellY);
+ mLauncher.getModelWriter().addOrMoveItemInDatabase(finalItem,
+ mInfo.container, mInfo.screenId, mInfo.cellX, mInfo.cellY);
}
// Remove the folder
@@ -1200,8 +1203,7 @@
// We add the child after removing the folder to prevent both from existing
// at the same time in the CellLayout. We need to add the new item with
// addInScreenFromBind() to ensure that hotseat items are placed correctly.
- mLauncher.getWorkspace().addInScreenFromBind(newIcon, mInfo.container,
- mInfo.screenId, mInfo.cellX, mInfo.cellY, mInfo.spanX, mInfo.spanY);
+ mLauncher.getWorkspace().addInScreenFromBind(newIcon, mInfo);
// Focus the newly created child
newIcon.requestFocus();
@@ -1299,8 +1301,8 @@
currentDragView = mContent.createAndAddViewForRank(si, mEmptyCellRank);
// Actually move the item in the database if it was an external drag. Call this
// before creating the view, so that ShortcutInfo is updated appropriately.
- LauncherModel.addOrMoveItemInDatabase(
- mLauncher, si, mInfo.id, 0, si.cellX, si.cellY);
+ mLauncher.getModelWriter().addOrMoveItemInDatabase(
+ si, mInfo.id, 0, si.cellX, si.cellY);
// We only need to update the locations if it doesn't get handled in #onDropCompleted.
if (d.dragSource != this) {
@@ -1309,9 +1311,7 @@
mIsExternalDrag = false;
} else {
currentDragView = mCurrentDragView;
- if (!mDragController.isDeferringDrag()) {
- mContent.addViewForRank(currentDragView, si, mEmptyCellRank);
- }
+ mContent.addViewForRank(currentDragView, si, mEmptyCellRank);
}
if (d.dragView.hasDrawn()) {
@@ -1332,11 +1332,9 @@
mItemsInvalidated = true;
rearrangeChildren();
- if (!mDragController.isDeferringDrag()) {
- // Temporarily suppress the listener, as we did all the work already here.
- try (SuppressInfoChanges s = new SuppressInfoChanges()) {
- mInfo.add(si, false);
- }
+ // Temporarily suppress the listener, as we did all the work already here.
+ try (SuppressInfoChanges s = new SuppressInfoChanges()) {
+ mInfo.add(si, false);
}
// Clear the drag info, as it is no longer being dragged.
@@ -1344,7 +1342,7 @@
if (mContent.getPageCount() > 1) {
// The animation has already been shown while opening the folder.
- mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, mLauncher);
+ mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, mLauncher.getModelWriter());
}
if (d.stateAnnouncer != null) {
@@ -1368,8 +1366,8 @@
public void onAdd(ShortcutInfo item) {
mContent.createAndAddViewForRank(item, mContent.allocateRankForNewItem());
mItemsInvalidated = true;
- LauncherModel.addOrMoveItemInDatabase(
- mLauncher, item, mInfo.id, 0, item.cellX, item.cellY);
+ mLauncher.getModelWriter().addOrMoveItemInDatabase(
+ item, mInfo.id, 0, item.cellX, item.cellY);
}
public void onRemove(ShortcutInfo item) {
@@ -1382,8 +1380,8 @@
rearrangeChildren();
}
if (getItemCount() <= 1) {
- if (mInfo.opened) {
- mLauncher.closeFolder(this, true);
+ if (mIsOpen) {
+ close(true);
} else {
replaceFolderWithFinalItem();
}
@@ -1405,6 +1403,11 @@
updateTextViewFocus();
}
+ @Override
+ public void prepareAutoUpdate() {
+ close(false);
+ }
+
public void onTitleChanged(CharSequence title) {
}
@@ -1429,7 +1432,7 @@
if (hasFocus) {
startEditingFolderName();
} else {
- dismissEditingName();
+ mFolderName.dispatchBackKey();
}
}
}
@@ -1442,11 +1445,11 @@
}
@Override
- public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
+ public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
target.gridX = info.cellX;
target.gridY = info.cellY;
target.pageIndex = mContent.getCurrentPage();
- targetParent.containerType = LauncherLogProto.FOLDER;
+ targetParent.containerType = ContainerType.FOLDER;
}
private class OnScrollHintListener implements OnAlarmListener {
@@ -1462,21 +1465,21 @@
*/
@Override
public void onAlarm(Alarm alarm) {
- if (mCurrentScrollDir == DragController.SCROLL_LEFT) {
+ if (mCurrentScrollDir == SCROLL_LEFT) {
mContent.scrollLeft();
- mScrollHintDir = DragController.SCROLL_NONE;
- } else if (mCurrentScrollDir == DragController.SCROLL_RIGHT) {
+ mScrollHintDir = SCROLL_NONE;
+ } else if (mCurrentScrollDir == SCROLL_RIGHT) {
mContent.scrollRight();
- mScrollHintDir = DragController.SCROLL_NONE;
+ mScrollHintDir = SCROLL_NONE;
} else {
// This should not happen
return;
}
- mCurrentScrollDir = DragController.SCROLL_NONE;
+ mCurrentScrollDir = SCROLL_NONE;
// Pause drag event until the scrolling is finished
mScrollPauseAlarm.setOnAlarmListener(new OnScrollFinishedListener(mDragObject));
- mScrollPauseAlarm.setAlarm(DragController.RESCROLL_DELAY);
+ mScrollPauseAlarm.setAlarm(RESCROLL_DELAY);
}
}
@@ -1528,4 +1531,16 @@
updateTextViewFocus();
}
}
+
+ /**
+ * Returns a folder which is already open or null
+ */
+ public static Folder getOpen(Launcher launcher) {
+ return getOpenView(launcher, TYPE_FOLDER);
+ }
+
+ @Override
+ public int getLogContainerType() {
+ return ContainerType.FOLDER;
+ }
}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 69c2b0f..25123fb 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -18,20 +18,27 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
+import android.graphics.Point;
import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.Region;
+import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
+import android.util.Property;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -52,26 +59,27 @@
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.FolderInfo;
import com.android.launcher3.FolderInfo.FolderListener;
-import com.android.launcher3.IconCache;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.OnAlarmListener;
-import com.android.launcher3.PreloadIconDrawable;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.SimpleOnStylusPressListener;
import com.android.launcher3.StylusEventHelper;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
+import com.android.launcher3.badge.BadgeRenderer;
+import com.android.launcher3.badge.FolderBadgeInfo;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.util.Thunk;
import java.util.ArrayList;
+import java.util.List;
/**
* An icon that can appear on in the workspace representing an {@link Folder}.
@@ -114,7 +122,7 @@
private PreviewLayoutRule mPreviewLayoutRule;
boolean mAnimating = false;
- private Rect mOldBounds = new Rect();
+ private Rect mTempBounds = new Rect();
private float mSlop;
@@ -122,10 +130,27 @@
private ArrayList<PreviewItemDrawingParams> mDrawingParams = new ArrayList<PreviewItemDrawingParams>();
private Drawable mReferenceDrawable = null;
- Paint mBgPaint = new Paint();
-
private Alarm mOpenAlarm = new Alarm();
+ private FolderBadgeInfo mBadgeInfo = new FolderBadgeInfo();
+ private BadgeRenderer mBadgeRenderer;
+ private float mBadgeScale;
+ private Point mTempSpaceForBadgeOffset = new Point();
+
+ private static final Property<FolderIcon, Float> BADGE_SCALE_PROPERTY
+ = new Property<FolderIcon, Float>(Float.TYPE, "badgeScale") {
+ @Override
+ public Float get(FolderIcon folderIcon) {
+ return folderIcon.mBadgeScale;
+ }
+
+ @Override
+ public void set(FolderIcon folderIcon, Float value) {
+ folderIcon.mBadgeScale = value;
+ folderIcon.invalidate();
+ }
+ };
+
public FolderIcon(Context context, AttributeSet attrs) {
super(context, attrs);
init();
@@ -142,10 +167,11 @@
mPreviewLayoutRule = FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON ?
new StackFolderIconLayoutRule() :
new ClippedFolderIconLayoutRule();
+ mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
public static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group,
- FolderInfo folderInfo, IconCache iconCache) {
+ FolderInfo folderInfo) {
@SuppressWarnings("all") // suppress dead code warning
final boolean error = INITIAL_ITEM_ANIMATION_DURATION >= DROP_IN_ANIMATION_DURATION;
if (error) {
@@ -157,11 +183,6 @@
DeviceProfile grid = launcher.getDeviceProfile();
FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false);
- // For performance and compatibility reasons we render the preview using a software layer.
- // In particular, hardware path clipping has spotty ecosystem support and bad performance.
- // Software rendering also allows us to use shadow layers.
- icon.setLayerType(LAYER_TYPE_SOFTWARE, new Paint(Paint.FILTER_BITMAP_FLAG));
-
icon.setClipToPadding(false);
icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name);
icon.mFolderName.setText(folderInfo.title);
@@ -173,6 +194,7 @@
icon.setOnClickListener(launcher);
icon.mInfo = folderInfo;
icon.mLauncher = launcher;
+ icon.mBadgeRenderer = launcher.getDeviceProfile().mBadgeRenderer;
icon.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title));
Folder folder = Folder.fromXml(launcher);
folder.setDragController(launcher.getDragController());
@@ -202,16 +224,12 @@
updateItemDrawingParams(false);
}
- public FolderInfo getFolderInfo() {
- return mInfo;
- }
-
private boolean willAcceptItem(ItemInfo item) {
final int itemType = item.itemType;
return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) &&
- !mFolder.isFull() && item != mInfo && !mInfo.opened);
+ !mFolder.isFull() && item != mInfo && !mFolder.isOpen());
}
public boolean acceptDrop(ItemInfo dragInfo) {
@@ -243,12 +261,12 @@
OnAlarmListener mOnOpenListener = new OnAlarmListener() {
public void onAlarm(Alarm alarm) {
mFolder.beginExternalDrag();
- mLauncher.openFolder(FolderIcon.this);
+ mFolder.animateOpen();
}
};
public Drawable prepareCreate(final View destView) {
- Drawable animateDrawable = getTopDrawable((TextView) destView);
+ Drawable animateDrawable = ((TextView) destView).getCompoundDrawables()[1];
computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
destView.getMeasuredWidth());
return animateDrawable;
@@ -273,7 +291,7 @@
}
public void performDestroyAnimation(final View finalView, Runnable onCompleteRunnable) {
- Drawable animateDrawable = getTopDrawable((TextView) finalView);
+ Drawable animateDrawable = ((TextView) finalView).getCompoundDrawables()[1];
computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
finalView.getMeasuredWidth());
@@ -325,7 +343,7 @@
to.offset(center[0] - animateView.getMeasuredWidth() / 2,
center[1] - animateView.getMeasuredHeight() / 2);
- float finalAlpha = index < mPreviewLayoutRule.numItems() ? 0.5f : 0f;
+ float finalAlpha = index < mPreviewLayoutRule.maxNumItems() ? 0.5f : 0f;
float finalScale = scale * scaleRelativeToDragLayer;
dragLayer.animateView(animateView, from, to, finalAlpha,
@@ -384,6 +402,26 @@
computePreviewDrawingParams(d.getIntrinsicWidth(), getMeasuredWidth());
}
+ public void setBadgeInfo(FolderBadgeInfo badgeInfo) {
+ updateBadgeScale(mBadgeInfo.hasBadge(), badgeInfo.hasBadge());
+ mBadgeInfo = badgeInfo;
+ }
+
+ /**
+ * Sets mBadgeScale to 1 or 0, animating if wasBadged or isBadged is false
+ * (the badge is being added or removed).
+ */
+ private void updateBadgeScale(boolean wasBadged, boolean isBadged) {
+ float newBadgeScale = isBadged ? 1f : 0f;
+ // Animate when a badge is first added or when it is removed.
+ if ((wasBadged ^ isBadged) && isShown()) {
+ ObjectAnimator.ofFloat(this, BADGE_SCALE_PROPERTY, newBadgeScale).start();
+ } else {
+ mBadgeScale = newBadgeScale;
+ invalidate();
+ }
+ }
+
static class PreviewItemDrawingParams {
PreviewItemDrawingParams(float transX, float transY, float scale, float overlayAlpha) {
this.transX = transX;
@@ -418,8 +456,8 @@
}
private float getLocalCenterForIndex(int index, int curNumItems, int[] center) {
- mTmpParams = computePreviewItemDrawingParams(Math.min(mPreviewLayoutRule.numItems(), index),
- curNumItems, mTmpParams);
+ mTmpParams = computePreviewItemDrawingParams(
+ Math.min(mPreviewLayoutRule.maxNumItems(), index), curNumItems, mTmpParams);
mTmpParams.transX += mBackground.basePreviewOffsetX;
mTmpParams.transY += mBackground.basePreviewOffsetY;
@@ -452,27 +490,24 @@
}
private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) {
- canvas.save();
+ canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(params.transX, params.transY);
canvas.scale(params.scale, params.scale);
Drawable d = params.drawable;
if (d != null) {
- mOldBounds.set(d.getBounds());
+ mTempBounds.set(d.getBounds());
d.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize);
if (d instanceof FastBitmapDrawable) {
FastBitmapDrawable fd = (FastBitmapDrawable) d;
- float oldBrightness = fd.getBrightness();
- fd.setBrightness(params.overlayAlpha);
- d.draw(canvas);
- fd.setBrightness(oldBrightness);
+ fd.drawWithBrightness(canvas, params.overlayAlpha);
} else {
d.setColorFilter(Color.argb((int) (params.overlayAlpha * 255), 255, 255, 255),
PorterDuff.Mode.SRC_ATOP);
d.draw(canvas);
d.clearColorFilter();
}
- d.setBounds(mOldBounds);
+ d.setBounds(mTempBounds);
}
canvas.restore();
}
@@ -482,10 +517,29 @@
* information, handles drawing, and animation (accept state <--> rest state).
*/
public static class PreviewBackground {
+
+ private final PorterDuffXfermode mClipPorterDuffXfermode
+ = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
+ // Create a RadialGradient such that it draws a black circle and then extends with
+ // transparent. To achieve this, we keep the gradient to black for the range [0, 1) and
+ // just at the edge quickly change it to transparent.
+ private final RadialGradient mClipShader = new RadialGradient(0, 0, 1,
+ new int[] {Color.BLACK, Color.BLACK, Color.TRANSPARENT },
+ new float[] {0, 0.999f, 1},
+ Shader.TileMode.CLAMP);
+
+ private final PorterDuffXfermode mShadowPorterDuffXfermode
+ = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
+ private RadialGradient mShadowShader = null;
+
+ private final Matrix mShaderMatrix = new Matrix();
+ private final Path mPath = new Path();
+
+ private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
private float mScale = 1f;
private float mColorMultiplier = 1f;
- private Path mClipPath = new Path();
- private int mStrokeWidth;
+ private float mStrokeWidth;
private View mInvalidateDelegate;
public int previewSize;
@@ -508,7 +562,7 @@
private static final int BG_OPACITY = 160;
private static final int MAX_BG_OPACITY = 225;
private static final int BG_INTENSITY = 245;
- private static final int SHADOW_OPACITY = 80;
+ private static final int SHADOW_OPACITY = 40;
ValueAnimator mScaleAnimator;
@@ -524,7 +578,16 @@
basePreviewOffsetX = (availableSpace - this.previewSize) / 2;
basePreviewOffsetY = previewPadding + grid.folderBackgroundOffset + topPadding;
- mStrokeWidth = Utilities.pxFromDp(1, dm);
+ // Stroke width is 1dp
+ mStrokeWidth = dm.density;
+
+ float radius = getScaledRadius();
+ float shadowRadius = radius + mStrokeWidth;
+ int shadowColor = Color.argb(SHADOW_OPACITY, 0, 0, 0);
+ mShadowShader = new RadialGradient(0, 0, 1,
+ new int[] {shadowColor, Color.TRANSPARENT},
+ new float[] {radius / shadowRadius, 1},
+ Shader.TileMode.CLAMP);
invalidate();
}
@@ -545,11 +608,15 @@
return basePreviewOffsetY - (getScaledRadius() - getRadius());
}
- void invalidate() {
- int radius = getScaledRadius();
- mClipPath.reset();
- mClipPath.addCircle(radius, radius, radius, Path.Direction.CW);
+ /**
+ * Returns the progress of the scale animation, where 0 means the scale is at 1f
+ * and 1 means the scale is at ACCEPT_SCALE_FACTOR.
+ */
+ float getScaleProgress() {
+ return (mScale - 1f) / (ACCEPT_SCALE_FACTOR - 1f);
+ }
+ void invalidate() {
if (mInvalidateDelegate != null) {
mInvalidateDelegate.invalidate();
}
@@ -564,70 +631,94 @@
invalidate();
}
- public void drawBackground(Canvas canvas, Paint paint) {
- canvas.save();
- canvas.translate(getOffsetX(), getOffsetY());
-
- paint.reset();
- paint.setStyle(Paint.Style.FILL);
- paint.setXfermode(null);
- paint.setAntiAlias(true);
-
+ public void drawBackground(Canvas canvas) {
+ mPaint.setStyle(Paint.Style.FILL);
int alpha = (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier);
- paint.setColor(Color.argb(alpha, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY));
+ mPaint.setColor(Color.argb(alpha, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY));
+ drawCircle(canvas, 0 /* deltaRadius */);
+
+ // Draw shadow.
+ if (mShadowShader == null) {
+ return;
+ }
float radius = getScaledRadius();
+ float shadowRadius = radius + mStrokeWidth;
+ mPaint.setColor(Color.BLACK);
+ int offsetX = getOffsetX();
+ int offsetY = getOffsetY();
+ final int saveCount;
+ if (canvas.isHardwareAccelerated()) {
+ saveCount = canvas.saveLayer(offsetX - mStrokeWidth, offsetY,
+ offsetX + radius + shadowRadius, offsetY + shadowRadius + shadowRadius,
+ null, Canvas.CLIP_TO_LAYER_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
- canvas.drawCircle(radius, radius, radius, paint);
- canvas.clipPath(mClipPath, Region.Op.DIFFERENCE);
+ } else {
+ saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+ clipCanvasSoftware(canvas, Region.Op.DIFFERENCE);
+ }
- paint.setStyle(Paint.Style.STROKE);
- paint.setColor(Color.TRANSPARENT);
- paint.setShadowLayer(mStrokeWidth, 0, mStrokeWidth, Color.argb(SHADOW_OPACITY, 0, 0, 0));
- canvas.drawCircle(radius, radius, radius, paint);
+ mShaderMatrix.setScale(shadowRadius, shadowRadius);
+ mShaderMatrix.postTranslate(radius + offsetX, shadowRadius + offsetY);
+ mShadowShader.setLocalMatrix(mShaderMatrix);
+ mPaint.setShader(mShadowShader);
+ canvas.drawPaint(mPaint);
+ mPaint.setShader(null);
- canvas.restore();
+ if (canvas.isHardwareAccelerated()) {
+ mPaint.setXfermode(mShadowPorterDuffXfermode);
+ canvas.drawCircle(radius + offsetX, radius + offsetY, radius, mPaint);
+ mPaint.setXfermode(null);
+ }
+
+ canvas.restoreToCount(saveCount);
}
- public void drawBackgroundStroke(Canvas canvas, Paint paint) {
- canvas.save();
- canvas.translate(getOffsetX(), getOffsetY());
-
- paint.reset();
- paint.setAntiAlias(true);
- paint.setColor(Color.argb(255, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY));
- paint.setStyle(Paint.Style.STROKE);
- paint.setStrokeWidth(mStrokeWidth);
-
- float radius = getScaledRadius();
- canvas.drawCircle(radius, radius, radius - 1, paint);
-
- canvas.restore();
+ public void drawBackgroundStroke(Canvas canvas) {
+ mPaint.setColor(Color.argb(255, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY));
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeWidth(mStrokeWidth);
+ drawCircle(canvas, 1 /* deltaRadius */);
}
- public void drawLeaveBehind(Canvas canvas, Paint paint) {
+ public void drawLeaveBehind(Canvas canvas) {
float originalScale = mScale;
mScale = 0.5f;
- canvas.save();
- canvas.translate(getOffsetX(), getOffsetY());
+ mPaint.setStyle(Paint.Style.FILL);
+ mPaint.setColor(Color.argb(160, 245, 245, 245));
+ drawCircle(canvas, 0 /* deltaRadius */);
- paint.reset();
- paint.setAntiAlias(true);
- paint.setColor(Color.argb(160, 245, 245, 245));
-
- float radius = getScaledRadius();
- canvas.drawCircle(radius, radius, radius, paint);
-
- canvas.restore();
mScale = originalScale;
}
- // It is the callers responsibility to save and restore the canvas.
- private void clipCanvas(Canvas canvas) {
- canvas.translate(getOffsetX(), getOffsetY());
- canvas.clipPath(mClipPath);
- canvas.translate(-getOffsetX(), -getOffsetY());
+ private void drawCircle(Canvas canvas,float deltaRadius) {
+ float radius = getScaledRadius();
+ canvas.drawCircle(radius + getOffsetX(), radius + getOffsetY(),
+ radius - deltaRadius, mPaint);
+ }
+
+ // It is the callers responsibility to save and restore the canvas layers.
+ private void clipCanvasSoftware(Canvas canvas, Region.Op op) {
+ mPath.reset();
+ float r = getScaledRadius();
+ mPath.addCircle(r + getOffsetX(), r + getOffsetY(), r, Path.Direction.CW);
+ canvas.clipPath(mPath, op);
+ }
+
+ // It is the callers responsibility to save and restore the canvas layers.
+ private void clipCanvasHardware(Canvas canvas) {
+ mPaint.setColor(Color.BLACK);
+ mPaint.setXfermode(mClipPorterDuffXfermode);
+
+ float radius = getScaledRadius();
+ mShaderMatrix.setScale(radius, radius);
+ mShaderMatrix.postTranslate(radius + getOffsetX(), radius + getOffsetY());
+ mClipShader.setLocalMatrix(mShaderMatrix);
+ mPaint.setShader(mClipShader);
+ canvas.drawPaint(mPaint);
+ mPaint.setXfermode(null);
+ mPaint.setShader(null);
}
private void delegateDrawing(CellLayout delegate, int cellX, int cellY) {
@@ -667,7 +758,7 @@
mScaleAnimator.cancel();
}
- mScaleAnimator = LauncherAnimUtils.ofFloat(null, 0f, 1.0f);
+ mScaleAnimator = LauncherAnimUtils.ofFloat(0f, 1.0f);
mScaleAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
@@ -747,17 +838,22 @@
}
if (!mBackground.drawingDelegated()) {
- mBackground.drawBackground(canvas, mBgPaint);
+ mBackground.drawBackground(canvas);
}
if (mFolder == null) return;
if (mFolder.getItemCount() == 0 && !mAnimating) return;
- canvas.save();
+ final int saveCount;
-
- if (mPreviewLayoutRule.clipToBackground()) {
- mBackground.clipCanvas(canvas);
+ if (canvas.isHardwareAccelerated()) {
+ saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null,
+ Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
+ } else {
+ saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+ if (mPreviewLayoutRule.clipToBackground()) {
+ mBackground.clipCanvasSoftware(canvas, Region.Op.INTERSECT);
+ }
}
// The items are drawn in coordinates relative to the preview offset
@@ -770,16 +866,29 @@
drawPreviewItem(canvas, p);
}
}
- canvas.restore();
+ canvas.translate(-mBackground.basePreviewOffsetX, -mBackground.basePreviewOffsetY);
+
+ if (mPreviewLayoutRule.clipToBackground() && canvas.isHardwareAccelerated()) {
+ mBackground.clipCanvasHardware(canvas);
+ }
+ canvas.restoreToCount(saveCount);
if (mPreviewLayoutRule.clipToBackground() && !mBackground.drawingDelegated()) {
- mBackground.drawBackgroundStroke(canvas, mBgPaint);
+ mBackground.drawBackgroundStroke(canvas);
}
- }
- private Drawable getTopDrawable(TextView v) {
- Drawable d = v.getCompoundDrawables()[1];
- return (d instanceof PreloadIconDrawable) ? ((PreloadIconDrawable) d).mIcon : d;
+ if ((mBadgeInfo != null && mBadgeInfo.hasBadge()) || mBadgeScale > 0) {
+ int offsetX = mBackground.getOffsetX();
+ int offsetY = mBackground.getOffsetY();
+ int previewSize = (int) (mBackground.previewSize * mBackground.mScale);
+ mTempBounds.set(offsetX, offsetY, offsetX + previewSize, offsetY + previewSize);
+
+ // If we are animating to the accepting state, animate the badge out.
+ float badgeScale = Math.max(0, mBadgeScale - mBackground.getScaleProgress());
+ mTempSpaceForBadgeOffset.set(getWidth() - mTempBounds.right, mTempBounds.top);
+ mBadgeRenderer.draw(canvas, IconPalette.FOLDER_ICON_PALETTE, mBadgeInfo, mTempBounds,
+ badgeScale, mTempSpaceForBadgeOffset);
+ }
}
class FolderPreviewItemAnim {
@@ -813,7 +922,7 @@
final float transX0 = mTmpParams.transX;
final float transY0 = mTmpParams.transY;
- mValueAnimator = LauncherAnimUtils.ofFloat(FolderIcon.this, 0f, 1.0f);
+ mValueAnimator = LauncherAnimUtils.ofFloat(0f, 1.0f);
mValueAnimator.addUpdateListener(new AnimatorUpdateListener(){
public void onAnimationUpdate(ValueAnimator animation) {
float progress = animation.getAnimatedFraction();
@@ -883,8 +992,8 @@
}
private void updateItemDrawingParams(boolean animate) {
- ArrayList<View> items = mFolder.getItemsInReadingOrder();
- int nItemsInPreview = Math.min(items.size(), mPreviewLayoutRule.numItems());
+ List<View> items = mPreviewLayoutRule.getItemsToDisplay(mFolder);
+ int nItemsInPreview = items.size();
int prevNumItems = mDrawingParams.size();
@@ -898,7 +1007,7 @@
for (int i = 0; i < mDrawingParams.size(); i++) {
PreviewItemDrawingParams p = mDrawingParams.get(i);
- p.drawable = getTopDrawable((TextView) items.get(i));
+ p.drawable = ((TextView) items.get(i)).getCompoundDrawables()[1];
if (!animate || FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON) {
computePreviewItemDrawingParams(i, nItemsInPreview, p);
@@ -929,16 +1038,31 @@
requestLayout();
}
+ @Override
+ public void prepareAutoUpdate() {
+ }
+
+ @Override
public void onAdd(ShortcutInfo item) {
+ boolean wasBadged = mBadgeInfo.hasBadge();
+ mBadgeInfo.addBadgeInfo(mLauncher.getPopupDataProvider().getBadgeInfoForItem(item));
+ boolean isBadged = mBadgeInfo.hasBadge();
+ updateBadgeScale(wasBadged, isBadged);
invalidate();
requestLayout();
}
+ @Override
public void onRemove(ShortcutInfo item) {
+ boolean wasBadged = mBadgeInfo.hasBadge();
+ mBadgeInfo.subtractBadgeInfo(mLauncher.getPopupDataProvider().getBadgeInfoForItem(item));
+ boolean isBadged = mBadgeInfo.hasBadge();
+ updateBadgeScale(wasBadged, isBadged);
invalidate();
requestLayout();
}
+ @Override
public void onTitleChanged(CharSequence title) {
mFolderName.setText(title);
setContentDescription(getContext().getString(R.string.folder_name_format, title));
@@ -974,12 +1098,6 @@
}
@Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
- }
-
- @Override
public void cancelLongPress() {
super.cancelLongPress();
mLongPressHelper.cancelLongPress();
@@ -990,13 +1108,76 @@
mInfo.removeListener(mFolder);
}
+ public void shrinkAndFadeIn(boolean animate) {
+ final CellLayout cl = (CellLayout) getParent().getParent();
+ ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true;
+
+ // We remove and re-draw the FolderIcon in-case it has changed
+ final PreviewImageView previewImage = PreviewImageView.get(getContext());
+ previewImage.removeFromParent();
+ copyToPreview(previewImage);
+
+ if (cl != null) {
+ cl.clearFolderLeaveBehind();
+ }
+
+ 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) {
+ if (cl != null) {
+ // 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 growAndFadeOut() {
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
+ // While the folder is open, the position of the icon cannot change.
+ lp.canReorder = false;
+ if (mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ CellLayout cl = (CellLayout) getParent().getParent();
+ cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
+ }
+
+ // 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 interface PreviewLayoutRule {
- public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
+ PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
PreviewItemDrawingParams params);
-
- public void init(int availableSpace, int intrinsicIconSize, boolean rtl);
-
- public int numItems();
- public boolean clipToBackground();
+ void init(int availableSpace, int intrinsicIconSize, boolean rtl);
+ float scaleForItem(int index, int totalNumItems);
+ int maxNumItems();
+ boolean clipToBackground();
+ List<View> getItemsToDisplay(Folder folder);
}
}
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 7e7ee34..eecce18 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -31,7 +31,6 @@
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.FocusHelper.PagedFolderKeyEventListener;
-import com.android.launcher3.IconCache;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
@@ -46,6 +45,7 @@
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
import com.android.launcher3.pageindicators.PageIndicator;
+import com.android.launcher3.util.Themes;
import com.android.launcher3.util.Thunk;
import java.util.ArrayList;
@@ -73,7 +73,6 @@
public final boolean mIsRtl;
private final LayoutInflater mInflater;
- private final IconCache mIconCache;
private final ViewGroupFocusHelper mFocusIndicatorHelper;
@Thunk final HashMap<View, Runnable> mPendingAnimations = new HashMap<>();
@@ -98,21 +97,18 @@
public FolderPagedView(Context context, AttributeSet attrs) {
super(context, attrs);
- LauncherAppState app = LauncherAppState.getInstance();
-
- InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
+ InvariantDeviceProfile profile = LauncherAppState.getIDP(context);
mMaxCountX = profile.numFolderColumns;
mMaxCountY = profile.numFolderRows;
mMaxItemsPerPage = mMaxCountX * mMaxCountY;
mInflater = LayoutInflater.from(context);
- mIconCache = app.getIconCache();
mIsRtl = Utilities.isRtl(getResources());
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
- setEdgeGlowColor(getResources().getColor(R.color.folder_edge_effect_color));
+ setEdgeGlowColor(Themes.getAttrColor(context, android.R.attr.colorEdgeEffect));
mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
}
@@ -231,7 +227,7 @@
public View createNewView(ShortcutInfo item) {
final BubbleTextView textView = (BubbleTextView) mInflater.inflate(
R.layout.folder_application, null, false);
- textView.applyFromShortcutInfo(item, mIconCache);
+ textView.applyFromShortcutInfo(item);
textView.setOnClickListener(mFolder);
textView.setOnLongClickListener(mFolder);
textView.setOnFocusChangeListener(mFocusIndicatorHelper);
@@ -253,10 +249,9 @@
private CellLayout createAndAddNewPage() {
DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
- CellLayout page = new CellLayout(getContext());
+ CellLayout page = (CellLayout) mInflater.inflate(R.layout.folder_page, this, false);
page.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
page.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
- page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
page.setInvertIfRtl(true);
page.setGridSize(mGridCountX, mGridCountY);
@@ -342,7 +337,7 @@
info.cellY = newY;
info.rank = rank;
if (saveChanges) {
- LauncherModel.addOrMoveItemInDatabase(getContext(), info,
+ mFolder.mLauncher.getModelWriter().addOrMoveItemInDatabase(info,
mFolder.mInfo.id, 0, info.cellX, info.cellY);
}
}
@@ -487,7 +482,7 @@
* Scrolls the current view by a fraction
*/
public void showScrollHint(int direction) {
- float fraction = (direction == DragController.SCROLL_LEFT) ^ mIsRtl
+ float fraction = (direction == Folder.SCROLL_LEFT) ^ mIsRtl
? -SCROLL_HINT_FRACTION : SCROLL_HINT_FRACTION;
int hint = (int) (fraction * getWidth());
int scroll = getScrollForPage(getNextPage()) + hint;
@@ -524,12 +519,11 @@
}
@Override
- protected void onPageBeginMoving() {
- super.onPageBeginMoving();
- getVisiblePages(sTempPosArray);
- for (int i = sTempPosArray[0]; i <= sTempPosArray[1]; i++) {
- verifyVisibleHighResIcons(i);
- }
+ protected void onPageBeginTransition() {
+ super.onPageBeginTransition();
+ // Ensure that adjacent pages have high resolution icons
+ verifyVisibleHighResIcons(getCurrentPage() - 1);
+ verifyVisibleHighResIcons(getCurrentPage() + 1);
}
/**
@@ -679,7 +673,7 @@
}
@Override
- protected void getEdgeVerticalPostion(int[] pos) {
+ protected void getEdgeVerticalPosition(int[] pos) {
pos[0] = 0;
pos[1] = getViewportHeight();
}
diff --git a/src/com/android/launcher3/folder/PreviewImageView.java b/src/com/android/launcher3/folder/PreviewImageView.java
new file mode 100644
index 0000000..c4f3ee1
--- /dev/null
+++ b/src/com/android/launcher3/folder/PreviewImageView.java
@@ -0,0 +1,98 @@
+/*
+ * 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.view.ViewGroup;
+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/StackFolderIconLayoutRule.java b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
index 7fb02e3..297203a 100644
--- a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
@@ -16,10 +16,12 @@
package com.android.launcher3.folder;
-import android.graphics.Path;
+import android.view.View;
import com.android.launcher3.folder.FolderIcon.PreviewItemDrawingParams;
+import java.util.List;
+
public class StackFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule {
static final int MAX_NUM_ITEMS_IN_PREVIEW = 3;
@@ -54,10 +56,10 @@
@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 scale = (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r));
float offset = (1 - r) * mMaxPerspectiveShift;
float scaledSize = scale * mBaselineIconSize;
@@ -80,12 +82,26 @@
}
@Override
- public int numItems() {
+ public int maxNumItems() {
return MAX_NUM_ITEMS_IN_PREVIEW;
}
@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 List<View> getItemsToDisplay(Folder folder) {
+ List<View> items = folder.getItemsInReadingOrder();
+ return items.subList(0, Math.min(items.size(), MAX_NUM_ITEMS_IN_PREVIEW));
+ }
}
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index bc91c15..bb136f7 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -16,6 +16,7 @@
package com.android.launcher3.graphics;
+import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
@@ -24,9 +25,9 @@
import android.view.View;
import android.widget.TextView;
-import com.android.launcher3.HolographicOutlineHelper;
import com.android.launcher3.Launcher;
-import com.android.launcher3.PreloadIconDrawable;
+import com.android.launcher3.LauncherAppWidgetHostView;
+import com.android.launcher3.R;
import com.android.launcher3.Workspace;
import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.folder.FolderIcon;
@@ -36,8 +37,6 @@
*/
public class DragPreviewProvider {
- public static final int DRAG_BITMAP_PADDING = 2;
-
private final Rect mTempRect = new Rect();
protected final View mView;
@@ -45,17 +44,25 @@
// The padding added to the drag view during the preview generation.
public final int previewPadding;
- public Bitmap gerenatedDragOutline;
+ protected final int blurSizeOutline;
+
+ public Bitmap generatedDragOutline;
public DragPreviewProvider(View view) {
+ this(view, view.getContext());
+ }
+
+ public DragPreviewProvider(View view, Context context) {
mView = view;
+ blurSizeOutline =
+ context.getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline);
if (mView instanceof TextView) {
Drawable d = Workspace.getTextViewIcon((TextView) mView);
Rect bounds = getDrawableBounds(d);
- previewPadding = DRAG_BITMAP_PADDING - bounds.left - bounds.top;
+ previewPadding = blurSizeOutline - bounds.left - bounds.top;
} else {
- previewPadding = DRAG_BITMAP_PADDING;
+ previewPadding = blurSizeOutline;
}
}
@@ -67,8 +74,8 @@
if (mView instanceof TextView) {
Drawable d = Workspace.getTextViewIcon((TextView) mView);
Rect bounds = getDrawableBounds(d);
- destCanvas.translate(DRAG_BITMAP_PADDING / 2 - bounds.left,
- DRAG_BITMAP_PADDING / 2 - bounds.top);
+ destCanvas.translate(blurSizeOutline / 2 - bounds.left,
+ blurSizeOutline / 2 - bounds.top);
d.draw(destCanvas);
} else {
final Rect clipRect = mTempRect;
@@ -83,8 +90,8 @@
textVisible = true;
}
}
- destCanvas.translate(-mView.getScrollX() + DRAG_BITMAP_PADDING / 2,
- -mView.getScrollY() + DRAG_BITMAP_PADDING / 2);
+ destCanvas.translate(-mView.getScrollX() + blurSizeOutline / 2,
+ -mView.getScrollY() + blurSizeOutline / 2);
destCanvas.clipRect(clipRect, Op.REPLACE);
mView.draw(destCanvas);
@@ -101,31 +108,41 @@
* Responsibility for the bitmap is transferred to the caller.
*/
public Bitmap createDragBitmap(Canvas canvas) {
- Bitmap b;
+ float scale = 1f;
+ int width = mView.getWidth();
+ int height = mView.getHeight();
if (mView instanceof TextView) {
Drawable d = Workspace.getTextViewIcon((TextView) mView);
Rect bounds = getDrawableBounds(d);
- b = Bitmap.createBitmap(bounds.width() + DRAG_BITMAP_PADDING,
- bounds.height() + DRAG_BITMAP_PADDING, Bitmap.Config.ARGB_8888);
- } else {
- b = Bitmap.createBitmap(mView.getWidth() + DRAG_BITMAP_PADDING,
- mView.getHeight() + DRAG_BITMAP_PADDING, Bitmap.Config.ARGB_8888);
+ width = bounds.width();
+ height = bounds.height();
+ } else if (mView instanceof LauncherAppWidgetHostView) {
+ scale = ((LauncherAppWidgetHostView) mView).getScaleToFit();
+ width = (int) (mView.getWidth() * scale);
+ height = (int) (mView.getHeight() * scale);
}
+ Bitmap b = Bitmap.createBitmap(width + blurSizeOutline, height + blurSizeOutline,
+ Bitmap.Config.ARGB_8888);
canvas.setBitmap(b);
+
+ canvas.save();
+ canvas.scale(scale, scale);
drawDragView(canvas);
+ canvas.restore();
+
canvas.setBitmap(null);
return b;
}
public final void generateDragOutline(Canvas canvas) {
- if (ProviderConfig.IS_DOGFOOD_BUILD && gerenatedDragOutline != null) {
+ if (ProviderConfig.IS_DOGFOOD_BUILD && generatedDragOutline != null) {
throw new RuntimeException("Drag outline generated twice");
}
- gerenatedDragOutline = createDragOutline(canvas);
+ generatedDragOutline = createDragOutline(canvas);
}
/**
@@ -133,12 +150,28 @@
* Responsibility for the bitmap is transferred to the caller.
*/
public Bitmap createDragOutline(Canvas canvas) {
- final Bitmap b = Bitmap.createBitmap(mView.getWidth() + DRAG_BITMAP_PADDING,
- mView.getHeight() + DRAG_BITMAP_PADDING, Bitmap.Config.ALPHA_8);
+ 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);
- HolographicOutlineHelper.obtain(mView.getContext())
+ canvas.restore();
+
+ HolographicOutlineHelper.getInstance(mView.getContext())
.applyExpensiveOutlineWithBlur(b, canvas);
+
canvas.setBitmap(null);
return b;
}
@@ -151,18 +184,22 @@
} else {
bounds.offsetTo(0, 0);
}
- if (d instanceof PreloadIconDrawable) {
- int inset = -((PreloadIconDrawable) d).getOutset();
- bounds.inset(inset, inset);
- }
return bounds;
}
public float getScaleAndPosition(Bitmap preview, int[] outPos) {
float scale = Launcher.getLauncher(mView.getContext())
.getDragLayer().getLocationInDragLayer(mView, outPos);
- outPos[0] = Math.round(outPos[0] - (preview.getWidth() - scale * mView.getWidth()) / 2);
- outPos[1] = Math.round(outPos[1] - (1 - scale) * preview.getHeight() / 2 - previewPadding / 2);
+ if (mView instanceof LauncherAppWidgetHostView) {
+ // App widgets are technically scaled, but are drawn at their expected size -- so the
+ // app widget scale should not affect the scale of the preview.
+ scale /= ((LauncherAppWidgetHostView) mView).getScaleToFit();
+ }
+
+ outPos[0] = Math.round(outPos[0] -
+ (preview.getWidth() - scale * mView.getWidth() * mView.getScaleX()) / 2);
+ outPos[1] = Math.round(outPos[1] - (1 - scale) * preview.getHeight() / 2
+ - previewPadding / 2);
return scale;
}
}
diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java
new file mode 100644
index 0000000..60bbce4
--- /dev/null
+++ b/src/com/android/launcher3/graphics/DrawableFactory.java
@@ -0,0 +1,150 @@
+/*
+ * 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.graphics;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Process;
+import android.os.UserHandle;
+import android.support.annotation.UiThread;
+import android.util.Log;
+
+import com.android.launcher3.FastBitmapDrawable;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.AllAppsBackgroundDrawable;
+
+import java.util.HashMap;
+
+/**
+ * Factory for creating new drawables.
+ */
+public class DrawableFactory {
+
+ private static final String TAG = "DrawableFactory";
+
+ private static DrawableFactory sInstance;
+ private static final Object LOCK = new Object();
+
+ private Path mPreloadProgressPath;
+
+ public static DrawableFactory get(Context context) {
+ synchronized (LOCK) {
+ if (sInstance == null) {
+ sInstance = Utilities.getOverrideObject(DrawableFactory.class,
+ context.getApplicationContext(), R.string.drawable_factory_class);
+ }
+ return sInstance;
+ }
+ }
+
+ protected final UserHandle mMyUser = Process.myUserHandle();
+ protected final HashMap<UserHandle, Bitmap> mUserBadges = new HashMap<>();
+
+ /**
+ * Returns a FastBitmapDrawable with the icon.
+ */
+ public FastBitmapDrawable newIcon(Bitmap icon, ItemInfo info) {
+ return new FastBitmapDrawable(icon);
+ }
+
+ /**
+ * Returns a FastBitmapDrawable with the icon.
+ */
+ public PreloadIconDrawable newPendingIcon(Bitmap icon, Context context) {
+ if (mPreloadProgressPath == null) {
+ mPreloadProgressPath = getPreloadProgressPath(context);
+ }
+ return new PreloadIconDrawable(icon, mPreloadProgressPath, context);
+ }
+
+
+ protected Path getPreloadProgressPath(Context context) {
+ if (Utilities.isAtLeastO()) {
+ try {
+ // Try to load the path from Mask Icon
+ Drawable icon = context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper);
+ icon.setBounds(0, 0,
+ PreloadIconDrawable.PATH_SIZE, PreloadIconDrawable.PATH_SIZE);
+ return (Path) icon.getClass().getMethod("getIconMask").invoke(icon);
+ } catch (Exception e) {
+ Log.e(TAG, "Error loading mask icon", e);
+ }
+ }
+
+ // Create a circle static from top center and going clockwise.
+ Path p = new Path();
+ p.moveTo(PreloadIconDrawable.PATH_SIZE / 2, 0);
+ p.addArc(0, 0, PreloadIconDrawable.PATH_SIZE, PreloadIconDrawable.PATH_SIZE, -90, 360);
+ return p;
+ }
+
+ public AllAppsBackgroundDrawable getAllAppsBackground(Context context) {
+ return new AllAppsBackgroundDrawable(context);
+ }
+
+ /**
+ * Returns a drawable that can be used as a badge for the user or null.
+ */
+ @UiThread
+ public Drawable getBadgeForUser(UserHandle user, Context context) {
+ if (mMyUser.equals(user)) {
+ return null;
+ }
+
+ Bitmap badgeBitmap = getUserBadge(user, context);
+ FastBitmapDrawable d = new FastBitmapDrawable(badgeBitmap);
+ d.setFilterBitmap(true);
+ d.setBounds(0, 0, badgeBitmap.getWidth(), badgeBitmap.getHeight());
+ return d;
+ }
+
+ protected synchronized Bitmap getUserBadge(UserHandle user, Context context) {
+ Bitmap badgeBitmap = mUserBadges.get(user);
+ if (badgeBitmap != null) {
+ return badgeBitmap;
+ }
+
+ final Resources res = context.getApplicationContext().getResources();
+ int badgeSize = res.getDimensionPixelSize(R.dimen.profile_badge_size);
+ badgeBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888);
+
+ Drawable drawable = context.getPackageManager().getUserBadgedDrawableForDensity(
+ new BitmapDrawable(res, badgeBitmap), user, new Rect(0, 0, badgeSize, badgeSize),
+ 0);
+ if (drawable instanceof BitmapDrawable) {
+ badgeBitmap = ((BitmapDrawable) drawable).getBitmap();
+ } else {
+ badgeBitmap.eraseColor(Color.TRANSPARENT);
+ Canvas c = new Canvas(badgeBitmap);
+ drawable.setBounds(0, 0, badgeSize, badgeSize);
+ drawable.draw(c);
+ c.setBitmap(null);
+ }
+
+ mUserBadges.put(user, badgeBitmap);
+ return badgeBitmap;
+ }
+}
diff --git a/src/com/android/launcher3/graphics/FixedScaleDrawable.java b/src/com/android/launcher3/graphics/FixedScaleDrawable.java
new file mode 100644
index 0000000..262a95e
--- /dev/null
+++ b/src/com/android/launcher3/graphics/FixedScaleDrawable.java
@@ -0,0 +1,56 @@
+package com.android.launcher3.graphics;
+
+import android.annotation.TargetApi;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.graphics.Canvas;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.DrawableWrapper;
+import android.os.Build;
+import android.util.AttributeSet;
+
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Extension of {@link DrawableWrapper} which scales the child drawables by a fixed amount.
+ */
+@TargetApi(Build.VERSION_CODES.N)
+public class FixedScaleDrawable extends DrawableWrapper {
+
+ // TODO b/33553066 use the constant defined in MaskableIconDrawable
+ private static final float LEGACY_ICON_SCALE = .7f * .6667f;
+ private float mScaleX, mScaleY;
+
+ public FixedScaleDrawable() {
+ super(new ColorDrawable());
+ mScaleX = LEGACY_ICON_SCALE;
+ mScaleY = LEGACY_ICON_SCALE;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ canvas.scale(mScaleX, mScaleY,
+ getBounds().exactCenterX(), getBounds().exactCenterY());
+ super.draw(canvas);
+ canvas.restoreToCount(saveCount);
+ }
+
+ @Override
+ public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) { }
+
+ @Override
+ public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) { }
+
+ public void setScale(float scale) {
+ float h = getIntrinsicHeight();
+ float w = getIntrinsicWidth();
+ mScaleX = scale * LEGACY_ICON_SCALE;
+ mScaleY = scale * LEGACY_ICON_SCALE;
+ if (h > w && w > 0) {
+ mScaleX *= w / h;
+ } else if (w > h && h > 0) {
+ mScaleY *= h / w;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/HolographicOutlineHelper.java b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
similarity index 83%
rename from src/com/android/launcher3/HolographicOutlineHelper.java
rename to src/com/android/launcher3/graphics/HolographicOutlineHelper.java
index 9dec7d9..c9873d9 100644
--- a/src/com/android/launcher3/HolographicOutlineHelper.java
+++ b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.launcher3;
+package com.android.launcher3.graphics;
import android.content.Context;
import android.content.res.Resources;
@@ -29,6 +29,8 @@
import android.graphics.drawable.Drawable;
import android.util.SparseArray;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.R;
import com.android.launcher3.config.ProviderConfig;
import java.nio.ByteBuffer;
@@ -72,9 +74,9 @@
mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
}
- public static HolographicOutlineHelper obtain(Context context) {
+ public static HolographicOutlineHelper getInstance(Context context) {
if (sInstance == null) {
- sInstance = new HolographicOutlineHelper(context);
+ sInstance = new HolographicOutlineHelper(context.getApplicationContext());
}
return sInstance;
}
@@ -84,33 +86,26 @@
* bitmap.
*/
public void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas) {
- applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, true);
- }
-
- public void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas,
- boolean clipAlpha) {
if (ProviderConfig.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
- if (clipAlpha) {
- byte[] pixels = new byte[srcDst.getWidth() * srcDst.getHeight()];
- ByteBuffer buffer = ByteBuffer.wrap(pixels);
- buffer.rewind();
- srcDst.copyPixelsToBuffer(buffer);
+ 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;
- }
+ for (int i = 0; i < pixels.length; i++) {
+ if ((pixels[i] & 0xFF) < 188) {
+ pixels[i] = 0;
}
-
- buffer.rewind();
- srcDst.copyPixelsFromBuffer(buffer);
}
+ buffer.rewind();
+ srcDst.copyPixelsFromBuffer(buffer);
+
// calculate the outer blur first
mBlurPaint.setMaskFilter(mMediumOuterBlurMaskFilter);
int[] outerBlurOffset = new int[2];
@@ -155,19 +150,14 @@
thickInnerBlur.recycle();
}
- Bitmap createMediumDropShadow(BubbleTextView view) {
- return createMediumDropShadow(view.getIcon(), view.getScaleX(), view.getScaleY(), true);
- }
-
- Bitmap createMediumDropShadow(Drawable drawable, boolean shouldCache) {
- return createMediumDropShadow(drawable, 1f, 1f, shouldCache);
- }
-
- Bitmap createMediumDropShadow(Drawable drawable, float scaleX, float scaleY,
- boolean shouldCache) {
+ public Bitmap createMediumDropShadow(BubbleTextView view) {
+ Drawable drawable = view.getIcon();
if (drawable == null) {
return null;
}
+
+ float scaleX = view.getScaleX();
+ float scaleY = view.getScaleY();
Rect rect = drawable.getBounds();
int bitmapWidth = (int) (rect.width() * scaleX);
@@ -177,14 +167,11 @@
}
int key = (bitmapWidth << 16) | bitmapHeight;
- Bitmap cache = shouldCache ? mBitmapCache.get(key) : null;
+ Bitmap cache = mBitmapCache.get(key);
if (cache == null) {
cache = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ALPHA_8);
mCanvas.setBitmap(cache);
-
- if (shouldCache) {
- mBitmapCache.put(key, cache);
- }
+ mBitmapCache.put(key, cache);
} else {
mCanvas.setBitmap(cache);
mCanvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
@@ -204,7 +191,7 @@
int resultWidth = bitmapWidth + extraSize;
int resultHeight = bitmapHeight + extraSize;
key = (resultWidth << 16) | resultHeight;
- Bitmap result = shouldCache ? mBitmapCache.get(key) : null;
+ Bitmap result = mBitmapCache.get(key);
if (result == null) {
result = Bitmap.createBitmap(resultWidth, resultHeight, Bitmap.Config.ALPHA_8);
mCanvas.setBitmap(result);
diff --git a/src/com/android/launcher3/graphics/IconNormalizer.java b/src/com/android/launcher3/graphics/IconNormalizer.java
new file mode 100644
index 0000000..34d0b72
--- /dev/null
+++ b/src/com/android/launcher3/graphics/IconNormalizer.java
@@ -0,0 +1,420 @@
+/*
+ * 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.graphics;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.AdaptiveIconDrawable;
+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 java.io.File;
+import java.io.FileOutputStream;
+import java.nio.ByteBuffer;
+import java.util.Random;
+
+public class IconNormalizer {
+
+ private static final String TAG = "IconNormalizer";
+ private static final boolean DEBUG = false;
+ // Ratio of icon visible area to full icon size for a square shaped icon
+ private static final float MAX_SQUARE_AREA_FACTOR = 375.0f / 576;
+ // Ratio of icon visible area to full icon size for a circular shaped icon
+ private static final float MAX_CIRCLE_AREA_FACTOR = 380.0f / 576;
+
+ private static final float CIRCLE_AREA_BY_RECT = (float) Math.PI / 4;
+
+ // Slope used to calculate icon visible area to full icon size for any generic shaped icon.
+ private static final float LINEAR_SCALE_SLOPE =
+ (MAX_CIRCLE_AREA_FACTOR - MAX_SQUARE_AREA_FACTOR) / (1 - CIRCLE_AREA_BY_RECT);
+
+ private static final int MIN_VISIBLE_ALPHA = 40;
+
+ // Shape detection related constants
+ private static final float BOUND_RATIO_MARGIN = .05f;
+ private static final float PIXEL_DIFF_PERCENTAGE_THRESHOLD = 0.005f;
+ private static final float SCALE_NOT_INITIALIZED = 0;
+
+ private static final Object LOCK = new Object();
+ private static IconNormalizer sIconNormalizer;
+
+ private final int mMaxSize;
+ private final Bitmap mBitmap;
+ private final Bitmap mBitmapARGB;
+ private final Canvas mCanvas;
+ private final Paint mPaintMaskShape;
+ private final Paint mPaintMaskShapeOutline;
+ private final byte[] mPixels;
+ private final int[] mPixelsARGB;
+
+ private final Rect mAdaptiveIconBounds;
+ private float mAdaptiveIconScale;
+
+ // for each y, stores the position of the leftmost x and the rightmost x
+ private final float[] mLeftBorder;
+ private final float[] mRightBorder;
+ private final Rect mBounds;
+ private final Matrix mMatrix;
+
+ private Paint mPaintIcon;
+ private Canvas mCanvasARGB;
+
+ private File mDir;
+ private int mFileId;
+ private Random mRandom;
+
+ private IconNormalizer(Context context) {
+ // Use twice the icon size as maximum size to avoid scaling down twice.
+ mMaxSize = LauncherAppState.getIDP(context).iconBitmapSize * 2;
+ mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
+ mCanvas = new Canvas(mBitmap);
+ mPixels = new byte[mMaxSize * mMaxSize];
+ mPixelsARGB = new int[mMaxSize * mMaxSize];
+ mLeftBorder = new float[mMaxSize];
+ mRightBorder = new float[mMaxSize];
+ mBounds = new Rect();
+ mAdaptiveIconBounds = new Rect();
+
+ // Needed for isShape() method
+ mBitmapARGB = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ARGB_8888);
+ mCanvasARGB = new Canvas(mBitmapARGB);
+
+ mPaintIcon = new Paint();
+ mPaintIcon.setColor(Color.WHITE);
+
+ mPaintMaskShape = new Paint();
+ mPaintMaskShape.setColor(Color.RED);
+ mPaintMaskShape.setStyle(Paint.Style.FILL);
+ mPaintMaskShape.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
+
+ mPaintMaskShapeOutline = new Paint();
+ mPaintMaskShapeOutline.setStrokeWidth(2 * context.getResources().getDisplayMetrics().density);
+ mPaintMaskShapeOutline.setStyle(Paint.Style.STROKE);
+ mPaintMaskShapeOutline.setColor(Color.BLACK);
+ mPaintMaskShapeOutline.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+
+ mMatrix = new Matrix();
+ int[] mPixels = new int[mMaxSize * mMaxSize];
+ mAdaptiveIconScale = SCALE_NOT_INITIALIZED;
+
+ mDir = context.getExternalFilesDir(null);
+ mRandom = new Random();
+ }
+
+ /**
+ * Returns if the shape of the icon is same as the path.
+ * For this method to work, the shape path bounds should be in [0,1]x[0,1] bounds.
+ */
+ private boolean isShape(Path maskPath) {
+ // Condition1:
+ // If width and height of the path not close to a square, then the icon shape is
+ // not same as the mask shape.
+ float iconRatio = ((float) mBounds.width()) / mBounds.height();
+ if (Math.abs(iconRatio - 1) > BOUND_RATIO_MARGIN) {
+ if (DEBUG) {
+ Log.d(TAG, "Not same as mask shape because width != height. " + iconRatio);
+ }
+ return false;
+ }
+
+ // Condition 2:
+ // Actual icon (white) and the fitted shape (e.g., circle)(red) XOR operation
+ // should generate transparent image, if the actual icon is equivalent to the shape.
+ mFileId = mRandom.nextInt();
+ mBitmapARGB.eraseColor(Color.TRANSPARENT);
+ mCanvasARGB.drawBitmap(mBitmap, 0, 0, mPaintIcon);
+
+ if (DEBUG) {
+ final File beforeFile = new File(mDir, "isShape" + mFileId + "_before.png");
+ try {
+ mBitmapARGB.compress(Bitmap.CompressFormat.PNG, 100,
+ new FileOutputStream(beforeFile));
+ } catch (Exception e) {}
+ }
+
+ // Fit the shape within the icon's bounding box
+ mMatrix.reset();
+ mMatrix.setScale(mBounds.width(), mBounds.height());
+ mMatrix.postTranslate(mBounds.left, mBounds.top);
+ maskPath.transform(mMatrix);
+
+ // XOR operation
+ mCanvasARGB.drawPath(maskPath, mPaintMaskShape);
+
+ // DST_OUT operation around the mask path outline
+ mCanvasARGB.drawPath(maskPath, mPaintMaskShapeOutline);
+
+ boolean isTrans = isTransparentBitmap(mBitmapARGB);
+ if (DEBUG) {
+ final File afterFile = new File(mDir, "isShape" + mFileId + "_after_" + isTrans + ".png");
+ try {
+ mBitmapARGB.compress(Bitmap.CompressFormat.PNG, 100,
+ new FileOutputStream(afterFile));
+ } catch (Exception e) {}
+ }
+
+ // Check if the result is almost transparent
+ if (!isTrans) {
+ if (DEBUG) {
+ Log.d(TAG, "Not same as mask shape");
+ }
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Used to determine if certain the bitmap is transparent.
+ */
+ private boolean isTransparentBitmap(Bitmap bitmap) {
+ int w = mBounds.width();
+ int h = mBounds.height();
+ bitmap.getPixels(mPixelsARGB, 0 /* the first index to write into the array */,
+ w /* stride */,
+ mBounds.left, mBounds.top,
+ w, h);
+ int sum = 0;
+ for (int i = 0; i < w * h; i++) {
+ if(Color.alpha(mPixelsARGB[i]) > MIN_VISIBLE_ALPHA) {
+ sum++;
+ }
+ }
+ float percentageDiffPixels = ((float) sum) / (mBounds.width() * mBounds.height());
+ boolean transparentImage = percentageDiffPixels < PIXEL_DIFF_PERCENTAGE_THRESHOLD;
+ if (DEBUG) {
+ Log.d(TAG, "Total # pixel that is different (id="+ mFileId + "):" + percentageDiffPixels + "="+ sum + "/" + mBounds.width() * mBounds.height());
+ }
+ return transparentImage;
+ }
+
+ /**
+ * Returns the amount by which the {@param d} should be scaled (in both dimensions) so that it
+ * matches the design guidelines for a launcher icon.
+ *
+ * We first calculate the convex hull of the visible portion of the icon.
+ * This hull then compared with the bounding rectangle of the hull to find how closely it
+ * resembles a circle and a square, by comparing the ratio of the areas. Note that this is not an
+ * ideal solution but it gives satisfactory result without affecting the performance.
+ *
+ * This closeness is used to determine the ratio of hull area to the full icon size.
+ * Refer {@link #MAX_CIRCLE_AREA_FACTOR} and {@link #MAX_SQUARE_AREA_FACTOR}
+ *
+ * @param outBounds optional rect to receive the fraction distance from each edge.
+ */
+ public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds,
+ @Nullable Path path, @Nullable boolean[] outMaskShape) {
+ if (Utilities.isAtLeastO() && d instanceof AdaptiveIconDrawable &&
+ mAdaptiveIconScale != SCALE_NOT_INITIALIZED) {
+ if (outBounds != null) {
+ outBounds.set(mAdaptiveIconBounds);
+ }
+ return mAdaptiveIconScale;
+ }
+ int width = d.getIntrinsicWidth();
+ int height = d.getIntrinsicHeight();
+ if (width <= 0 || height <= 0) {
+ width = width <= 0 || width > mMaxSize ? mMaxSize : width;
+ height = height <= 0 || height > mMaxSize ? mMaxSize : height;
+ } else if (width > mMaxSize || height > mMaxSize) {
+ int max = Math.max(width, height);
+ width = mMaxSize * width / max;
+ height = mMaxSize * height / max;
+ }
+
+ mBitmap.eraseColor(Color.TRANSPARENT);
+ d.setBounds(0, 0, width, height);
+ d.draw(mCanvas);
+
+ ByteBuffer buffer = ByteBuffer.wrap(mPixels);
+ buffer.rewind();
+ mBitmap.copyPixelsToBuffer(buffer);
+
+ // Overall bounds of the visible icon.
+ int topY = -1;
+ int bottomY = -1;
+ int leftX = mMaxSize + 1;
+ int rightX = -1;
+
+ // Create border by going through all pixels one row at a time and for each row find
+ // the first and the last non-transparent pixel. Set those values to mLeftBorder and
+ // mRightBorder and use -1 if there are no visible pixel in the row.
+
+ // buffer position
+ int index = 0;
+ // buffer shift after every row, width of buffer = mMaxSize
+ int rowSizeDiff = mMaxSize - width;
+ // first and last position for any row.
+ int firstX, lastX;
+
+ for (int y = 0; y < height; y++) {
+ firstX = lastX = -1;
+ for (int x = 0; x < width; x++) {
+ if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
+ if (firstX == -1) {
+ firstX = x;
+ }
+ lastX = x;
+ }
+ index++;
+ }
+ index += rowSizeDiff;
+
+ mLeftBorder[y] = firstX;
+ mRightBorder[y] = lastX;
+
+ // If there is at least one visible pixel, update the overall bounds.
+ if (firstX != -1) {
+ bottomY = y;
+ if (topY == -1) {
+ topY = y;
+ }
+
+ leftX = Math.min(leftX, firstX);
+ rightX = Math.max(rightX, lastX);
+ }
+ }
+
+ if (topY == -1 || rightX == -1) {
+ // No valid pixels found. Do not scale.
+ return 1;
+ }
+
+ convertToConvexArray(mLeftBorder, 1, topY, bottomY);
+ convertToConvexArray(mRightBorder, -1, topY, bottomY);
+
+ // Area of the convex hull
+ float area = 0;
+ for (int y = 0; y < height; y++) {
+ if (mLeftBorder[y] <= -1) {
+ continue;
+ }
+ area += mRightBorder[y] - mLeftBorder[y] + 1;
+ }
+
+ // Area of the rectangle required to fit the convex hull
+ float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX);
+ float hullByRect = area / rectArea;
+
+ float scaleRequired;
+ if (hullByRect < CIRCLE_AREA_BY_RECT) {
+ scaleRequired = MAX_CIRCLE_AREA_FACTOR;
+ } else {
+ scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect);
+ }
+ mBounds.left = leftX;
+ mBounds.right = rightX;
+
+ mBounds.top = topY;
+ mBounds.bottom = bottomY;
+
+ if (outBounds != null) {
+ outBounds.set(((float) mBounds.left) / width, ((float) mBounds.top),
+ 1 - ((float) mBounds.right) / width,
+ 1 - ((float) mBounds.bottom) / height);
+ }
+
+ if (outMaskShape != null && outMaskShape.length > 0) {
+ outMaskShape[0] = isShape(path);
+ }
+ float areaScale = area / (width * height);
+ // Use sqrt of the final ratio as the images is scaled across both width and height.
+ float scale = areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
+ if (Utilities.isAtLeastO() && d instanceof AdaptiveIconDrawable &&
+ mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
+ mAdaptiveIconScale = scale;
+ mAdaptiveIconBounds.set(mBounds);
+ }
+ return scale;
+ }
+
+ /**
+ * Modifies {@param xCoordinates} to represent a convex border. Fills in all missing values
+ * (except on either ends) with appropriate values.
+ * @param xCoordinates map of x coordinate per y.
+ * @param direction 1 for left border and -1 for right border.
+ * @param topY the first Y position (inclusive) with a valid value.
+ * @param bottomY the last Y position (inclusive) with a valid value.
+ */
+ private static void convertToConvexArray(
+ float[] xCoordinates, int direction, int topY, int bottomY) {
+ int total = xCoordinates.length;
+ // The tangent at each pixel.
+ float[] angles = new float[total - 1];
+
+ int first = topY; // First valid y coordinate
+ int last = -1; // Last valid y coordinate which didn't have a missing value
+
+ float lastAngle = Float.MAX_VALUE;
+
+ for (int i = topY + 1; i <= bottomY; i++) {
+ if (xCoordinates[i] <= -1) {
+ continue;
+ }
+ int start;
+
+ if (lastAngle == Float.MAX_VALUE) {
+ start = first;
+ } else {
+ float currentAngle = (xCoordinates[i] - xCoordinates[last]) / (i - last);
+ start = last;
+ // If this position creates a concave angle, keep moving up until we find a
+ // position which creates a convex angle.
+ if ((currentAngle - lastAngle) * direction < 0) {
+ while (start > first) {
+ start --;
+ currentAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
+ if ((currentAngle - angles[start]) * direction >= 0) {
+ break;
+ }
+ }
+ }
+ }
+
+ // Reset from last check
+ lastAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
+ // Update all the points from start.
+ for (int j = start; j < i; j++) {
+ angles[j] = lastAngle;
+ xCoordinates[j] = xCoordinates[start] + lastAngle * (j - start);
+ }
+ last = i;
+ }
+ }
+
+ public static IconNormalizer getInstance(Context context) {
+ synchronized (LOCK) {
+ if (sIconNormalizer == null) {
+ sIconNormalizer = new IconNormalizer(context);
+ }
+ }
+ return sIconNormalizer;
+ }
+}
diff --git a/src/com/android/launcher3/graphics/IconPalette.java b/src/com/android/launcher3/graphics/IconPalette.java
new file mode 100644
index 0000000..0182e53
--- /dev/null
+++ b/src/com/android/launcher3/graphics/IconPalette.java
@@ -0,0 +1,209 @@
+/*
+ * 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.graphics;
+
+import android.app.Notification;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.support.v4.graphics.ColorUtils;
+import android.util.Log;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
+
+/**
+ * Contains colors based on the dominant color of an icon.
+ */
+public class IconPalette {
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = "IconPalette";
+
+ public static final IconPalette FOLDER_ICON_PALETTE = new IconPalette(Color.parseColor("#BDC1C6"));
+
+ private static final float MIN_PRELOAD_COLOR_SATURATION = 0.2f;
+ private static final float MIN_PRELOAD_COLOR_LIGHTNESS = 0.6f;
+
+ public final int dominantColor;
+ public final int backgroundColor;
+ public final ColorMatrixColorFilter backgroundColorMatrixFilter;
+ public final ColorMatrixColorFilter saturatedBackgroundColorMatrixFilter;
+ public final int textColor;
+ public final int secondaryColor;
+
+ private IconPalette(int color) {
+ dominantColor = color;
+ backgroundColor = getMutedColor(dominantColor);
+ ColorMatrix backgroundColorMatrix = new ColorMatrix();
+ Themes.setColorScaleOnMatrix(backgroundColor, backgroundColorMatrix);
+ backgroundColorMatrixFilter = new ColorMatrixColorFilter(backgroundColorMatrix);
+ // Get slightly more saturated background color.
+ Themes.setColorScaleOnMatrix(getMutedColor(dominantColor, 0.54f), backgroundColorMatrix);
+ saturatedBackgroundColorMatrixFilter = new ColorMatrixColorFilter(backgroundColorMatrix);
+ textColor = getTextColorForBackground(backgroundColor);
+ secondaryColor = getLowContrastColor(backgroundColor);
+ }
+
+ /**
+ * Returns a color suitable for the progress bar color of preload icon.
+ */
+ public int getPreloadProgressColor(Context context) {
+ int result = dominantColor;
+
+ // Make sure that the dominant color has enough saturation to be visible properly.
+ float[] hsv = new float[3];
+ Color.colorToHSV(result, hsv);
+ if (hsv[1] < MIN_PRELOAD_COLOR_SATURATION) {
+ result = Themes.getColorAccent(context);
+ } else {
+ hsv[2] = Math.max(MIN_PRELOAD_COLOR_LIGHTNESS, hsv[2]);
+ result = Color.HSVToColor(hsv);
+ }
+ return result;
+ }
+
+ public static IconPalette fromDominantColor(int dominantColor) {
+ return new IconPalette(dominantColor);
+ }
+
+ /**
+ * Resolves a color such that it has enough contrast to be used as the
+ * color of an icon or text on the given background color.
+ *
+ * @return a color of the same hue with enough contrast against the background.
+ *
+ * This was copied from com.android.internal.util.NotificationColorUtil.
+ */
+ public static int resolveContrastColor(Context context, int color, int background) {
+ final int resolvedColor = resolveColor(context, color);
+
+ int contrastingColor = ensureTextContrast(resolvedColor, background);
+
+ if (contrastingColor != resolvedColor) {
+ if (DEBUG){
+ Log.w(TAG, String.format(
+ "Enhanced contrast of notification for %s " +
+ "%s (over background) by changing #%s to %s",
+ context.getPackageName(),
+ contrastChange(resolvedColor, contrastingColor, background),
+ Integer.toHexString(resolvedColor), Integer.toHexString(contrastingColor)));
+ }
+ }
+ return contrastingColor;
+ }
+
+ /**
+ * Resolves {@param color} to an actual color if it is {@link Notification#COLOR_DEFAULT}
+ *
+ * This was copied from com.android.internal.util.NotificationColorUtil.
+ */
+ private static int resolveColor(Context context, int color) {
+ if (color == Notification.COLOR_DEFAULT) {
+ return context.getColor(R.color.notification_icon_default_color);
+ }
+ return color;
+ }
+
+ /** For debugging. This was copied from com.android.internal.util.NotificationColorUtil. */
+ private static String contrastChange(int colorOld, int colorNew, int bg) {
+ return String.format("from %.2f:1 to %.2f:1",
+ ColorUtils.calculateContrast(colorOld, bg),
+ ColorUtils.calculateContrast(colorNew, bg));
+ }
+
+ /**
+ * Finds a text color with sufficient contrast over bg that has the same hue as the original
+ * color.
+ *
+ * This was copied from com.android.internal.util.NotificationColorUtil.
+ */
+ private static int ensureTextContrast(int color, int bg) {
+ return findContrastColor(color, bg, true, 4.5);
+ }
+ /**
+ * Finds a suitable color such that there's enough contrast.
+ *
+ * @param color the color to start searching from.
+ * @param other the color to ensure contrast against. Assumed to be lighter than {@param color}
+ * @param findFg if true, we assume {@param color} is a foreground, otherwise a background.
+ * @param minRatio the minimum contrast ratio required.
+ * @return a color with the same hue as {@param color}, potentially darkened to meet the
+ * contrast ratio.
+ *
+ * This was copied from com.android.internal.util.NotificationColorUtil.
+ */
+ private static int findContrastColor(int color, int other, boolean findFg, double minRatio) {
+ int fg = findFg ? color : other;
+ int bg = findFg ? other : color;
+ if (ColorUtils.calculateContrast(fg, bg) >= minRatio) {
+ return color;
+ }
+
+ double[] lab = new double[3];
+ ColorUtils.colorToLAB(findFg ? fg : bg, lab);
+
+ double low = 0, high = lab[0];
+ final double a = lab[1], b = lab[2];
+ for (int i = 0; i < 15 && high - low > 0.00001; i++) {
+ final double l = (low + high) / 2;
+ if (findFg) {
+ fg = ColorUtils.LABToColor(l, a, b);
+ } else {
+ bg = ColorUtils.LABToColor(l, a, b);
+ }
+ if (ColorUtils.calculateContrast(fg, bg) > minRatio) {
+ low = l;
+ } else {
+ high = l;
+ }
+ }
+ return ColorUtils.LABToColor(low, a, b);
+ }
+
+ private static int getMutedColor(int color) {
+ return getMutedColor(color, 0.87f);
+ }
+
+ private static int getMutedColor(int color, float whiteScrimAlpha) {
+ int whiteScrim = ColorUtils.setAlphaComponent(Color.WHITE, (int) (255 * whiteScrimAlpha));
+ return ColorUtils.compositeColors(whiteScrim, color);
+ }
+
+ private static int getTextColorForBackground(int backgroundColor) {
+ return getLighterOrDarkerVersionOfColor(backgroundColor, 4.5f);
+ }
+
+ private static int getLowContrastColor(int color) {
+ return getLighterOrDarkerVersionOfColor(color, 1.5f);
+ }
+
+ private static int getLighterOrDarkerVersionOfColor(int color, float contrastRatio) {
+ int whiteMinAlpha = ColorUtils.calculateMinimumAlpha(Color.WHITE, color, contrastRatio);
+ int blackMinAlpha = ColorUtils.calculateMinimumAlpha(Color.BLACK, color, contrastRatio);
+ int translucentWhiteOrBlack;
+ if (whiteMinAlpha >= 0) {
+ translucentWhiteOrBlack = ColorUtils.setAlphaComponent(Color.WHITE, whiteMinAlpha);
+ } else if (blackMinAlpha >= 0) {
+ translucentWhiteOrBlack = ColorUtils.setAlphaComponent(Color.BLACK, blackMinAlpha);
+ } else {
+ translucentWhiteOrBlack = Color.WHITE;
+ }
+ return ColorUtils.compositeColors(translucentWhiteOrBlack, color);
+ }
+}
diff --git a/src/com/android/launcher3/graphics/IconShapeOverride.java b/src/com/android/launcher3/graphics/IconShapeOverride.java
new file mode 100644
index 0000000..f78f8a5
--- /dev/null
+++ b/src/com/android/launcher3/graphics/IconShapeOverride.java
@@ -0,0 +1,217 @@
+/*
+ * 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.graphics;
+
+import android.annotation.TargetApi;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.os.Build;
+import android.os.SystemClock;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.LooperExecuter;
+
+import java.lang.reflect.Field;
+
+/**
+ * Utility class to override shape of {@link android.graphics.drawable.AdaptiveIconDrawable}.
+ */
+@TargetApi(Build.VERSION_CODES.O)
+public class IconShapeOverride {
+
+ private static final String TAG = "IconShapeOverride";
+
+ public static final String KEY_PREFERENCE = "pref_override_icon_shape";
+
+ // Time to wait before killing the process this ensures that the progress bar is visible for
+ // sufficient time so that there is no flicker.
+ private static final long PROCESS_KILL_DELAY_MS = 1000;
+
+ private static final int RESTART_REQUEST_CODE = 42; // the answer to everything
+
+ public static boolean isSupported(Context context) {
+ if (!Utilities.isAtLeastO()) {
+ return false;
+ }
+ // Only supported when developer settings is enabled
+ if (Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 1) {
+ return false;
+ }
+
+ try {
+ if (getSystemResField().get(null) != Resources.getSystem()) {
+ // Our assumption that mSystem is the system resource is not true.
+ return false;
+ }
+ } catch (Exception e) {
+ // Ignore, not supported
+ return false;
+ }
+
+ return getConfigResId() != 0;
+ }
+
+ public static void apply(Context context) {
+ if (!Utilities.isAtLeastO()) {
+ return;
+ }
+ String path = getAppliedValue(context);
+ if (TextUtils.isEmpty(path)) {
+ return;
+ }
+ if (!isSupported(context)) {
+ return;
+ }
+
+ // magic
+ try {
+ Resources override =
+ new ResourcesOverride(Resources.getSystem(), getConfigResId(), path);
+ getSystemResField().set(null, override);
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to override icon shape", e);
+ // revert value.
+ prefs(context).edit().remove(KEY_PREFERENCE).apply();
+ }
+ }
+
+ private static Field getSystemResField() throws Exception {
+ Field staticField = Resources.class.getDeclaredField("mSystem");
+ staticField.setAccessible(true);
+ return staticField;
+ }
+
+ private static int getConfigResId() {
+ return Resources.getSystem().getIdentifier("config_icon_mask", "string", "android");
+ }
+
+ private static String getAppliedValue(Context context) {
+ return prefs(context).getString(KEY_PREFERENCE, "");
+ }
+
+ private static SharedPreferences prefs(Context context) {
+ return context.getSharedPreferences(LauncherFiles.DEVICE_PREFERENCES_KEY, 0);
+ }
+
+ public static void handlePreferenceUi(ListPreference preference) {
+ Context context = preference.getContext();
+ preference.setValue(getAppliedValue(context));
+ preference.setOnPreferenceChangeListener(new PreferenceChangeHandler(context));
+ }
+
+ private static class ResourcesOverride extends Resources {
+
+ private final int mOverrideId;
+ private final String mOverrideValue;
+
+ @SuppressWarnings("deprecation")
+ public ResourcesOverride(Resources parent, int overrideId, String overrideValue) {
+ super(parent.getAssets(), parent.getDisplayMetrics(), parent.getConfiguration());
+ mOverrideId = overrideId;
+ mOverrideValue = overrideValue;
+ }
+
+ @NonNull
+ @Override
+ public String getString(int id) throws NotFoundException {
+ if (id == mOverrideId) {
+ return mOverrideValue;
+ }
+ return super.getString(id);
+ }
+ }
+
+ private static class PreferenceChangeHandler implements OnPreferenceChangeListener {
+
+ private final Context mContext;
+
+ private PreferenceChangeHandler(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object o) {
+ String newValue = (String) o;
+ if (!getAppliedValue(mContext).equals(newValue)) {
+ // Value has changed
+ ProgressDialog.show(mContext,
+ null /* title */,
+ mContext.getString(R.string.icon_shape_override_progress),
+ true /* indeterminate */,
+ false /* cancelable */);
+ new LooperExecuter(LauncherModel.getWorkerLooper()).execute(
+ new OverrideApplyHandler(mContext, newValue));
+ }
+ return false;
+ }
+ }
+
+ private static class OverrideApplyHandler implements Runnable {
+
+ private final Context mContext;
+ private final String mValue;
+
+ private OverrideApplyHandler(Context context, String value) {
+ mContext = context;
+ mValue = value;
+ }
+
+ @Override
+ public void run() {
+ // Synchronously write the preference.
+ prefs(mContext).edit().putString(KEY_PREFERENCE, mValue).commit();
+ // Clear the icon cache.
+ LauncherAppState.getInstance(mContext).getIconCache().clear();
+
+ // Wait for it
+ try {
+ Thread.sleep(PROCESS_KILL_DELAY_MS);
+ } catch (Exception e) {
+ Log.e(TAG, "Error waiting", e);
+ }
+
+ // Schedule an alarm before we kill ourself.
+ Intent homeIntent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .setPackage(mContext.getPackageName())
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ PendingIntent pi = PendingIntent.getActivity(mContext, RESTART_REQUEST_CODE,
+ homeIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
+ mContext.getSystemService(AlarmManager.class).setExact(
+ AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 50, pi);
+
+ // Kill process
+ android.os.Process.killProcess(android.os.Process.myPid());
+ }
+ }
+}
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
new file mode 100644
index 0000000..53521f2
--- /dev/null
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -0,0 +1,373 @@
+/*
+ * 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.graphics;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.Intent.ShortcutIconResource;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.PaintDrawable;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.IconCache;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+
+/**
+ * Helper methods for generating various launcher icons
+ */
+public class LauncherIcons {
+
+ private static final Rect sOldBounds = new Rect();
+ private static final Canvas sCanvas = new Canvas();
+
+ static {
+ sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
+ Paint.FILTER_BITMAP_FLAG));
+ }
+
+ /**
+ * Returns a bitmap suitable for the all apps view. If the package or the resource do not
+ * exist, it returns null.
+ */
+ public static Bitmap createIconBitmap(ShortcutIconResource iconRes, Context context) {
+ PackageManager packageManager = context.getPackageManager();
+ // the resource
+ try {
+ 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);
+ }
+ } catch (Exception e) {
+ // Icon not found.
+ }
+ return null;
+ }
+
+ /**
+ * Returns a bitmap which is of the appropriate size to be displayed as an icon
+ */
+ public static Bitmap createIconBitmap(Bitmap icon, Context context) {
+ final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize;
+ if (iconBitmapSize == icon.getWidth() && iconBitmapSize == icon.getHeight()) {
+ return icon;
+ }
+ return createIconBitmap(new BitmapDrawable(context.getResources(), icon), context);
+ }
+
+ /**
+ * Returns a bitmap suitable for the all apps view. The icon is badged for {@param user}.
+ * The bitmap is also visually normalized with other icons.
+ */
+ public static Bitmap createBadgedIconBitmap(
+ Drawable icon, UserHandle user, Context context, int iconAppTargetSdk) {
+
+ IconNormalizer normalizer;
+ float scale = 1f;
+ if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) {
+ normalizer = IconNormalizer.getInstance(context);
+ if (Utilities.isAtLeastO() && iconAppTargetSdk >= Build.VERSION_CODES.O) {
+ boolean[] outShape = new boolean[1];
+ AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
+ context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
+ dr.setBounds(0, 0, 1, 1);
+ scale = normalizer.getScale(icon, null, dr.getIconMask(), outShape);
+ if (FeatureFlags.LEGACY_ICON_TREATMENT &&
+ !outShape[0]){
+ Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale);
+ if (wrappedIcon != icon) {
+ icon = wrappedIcon;
+ scale = normalizer.getScale(icon, null, null, null);
+ }
+ }
+ } else {
+ scale = normalizer.getScale(icon, null, null, null);
+ }
+ }
+ Bitmap bitmap = createIconBitmap(icon, context, scale);
+ if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.isAtLeastO() &&
+ 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);
+ Drawable badged = context.getPackageManager().getUserBadgedIcon(
+ drawable, user);
+ if (badged instanceof BitmapDrawable) {
+ return ((BitmapDrawable) badged).getBitmap();
+ } else {
+ return createIconBitmap(badged, context);
+ }
+ } else {
+ return icon;
+ }
+ }
+
+ /**
+ * 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) {
+ RectF iconBounds = new RectF();
+ IconNormalizer normalizer;
+ float scale = 1f;
+ if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) {
+ normalizer = IconNormalizer.getInstance(context);
+ if (Utilities.isAtLeastO() && iconAppTargetSdk >= Build.VERSION_CODES.O) {
+ boolean[] outShape = new boolean[1];
+ AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
+ context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
+ dr.setBounds(0, 0, 1, 1);
+ scale = normalizer.getScale(icon, iconBounds, dr.getIconMask(), outShape);
+ if (Utilities.isAtLeastO() && FeatureFlags.LEGACY_ICON_TREATMENT &&
+ !outShape[0]) {
+ Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale);
+ if (wrappedIcon != icon) {
+ icon = wrappedIcon;
+ scale = normalizer.getScale(icon, iconBounds, null, null);
+ }
+ }
+ } else {
+ scale = normalizer.getScale(icon, iconBounds, null, null);
+ }
+
+ }
+ scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds));
+ 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) {
+ int badgeSize = context.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
+ synchronized (sCanvas) {
+ sCanvas.setBitmap(srcTgt);
+ sCanvas.drawBitmap(badge, new Rect(0, 0, badge.getWidth(), badge.getHeight()),
+ new Rect(srcTgt.getWidth() - badgeSize,
+ srcTgt.getHeight() - badgeSize, srcTgt.getWidth(), srcTgt.getHeight()),
+ new Paint(Paint.FILTER_BITMAP_FLAG));
+ sCanvas.setBitmap(null);
+ }
+ 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.isAtLeastO() &&
+ icon instanceof AdaptiveIconDrawable) {
+ scale = ShadowGenerator.getScaleForBounds(new RectF(0, 0, 0, 0));
+ }
+ Bitmap bitmap = createIconBitmap(icon, context, scale);
+ if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.isAtLeastO() &&
+ 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) {
+ synchronized (sCanvas) {
+ final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize;
+ int width = iconBitmapSize;
+ int height = iconBitmapSize;
+
+ if (icon instanceof PaintDrawable) {
+ PaintDrawable painter = (PaintDrawable) icon;
+ painter.setIntrinsicWidth(width);
+ painter.setIntrinsicHeight(height);
+ } else if (icon instanceof BitmapDrawable) {
+ // Ensure the bitmap has a density.
+ BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
+ Bitmap bitmap = bitmapDrawable.getBitmap();
+ if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) {
+ bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
+ }
+ }
+
+ int sourceWidth = icon.getIntrinsicWidth();
+ int sourceHeight = icon.getIntrinsicHeight();
+ if (sourceWidth > 0 && sourceHeight > 0) {
+ // Scale the icon proportionally to the icon dimensions
+ final float ratio = (float) sourceWidth / sourceHeight;
+ if (sourceWidth > sourceHeight) {
+ height = (int) (width / ratio);
+ } else if (sourceHeight > sourceWidth) {
+ width = (int) (height * ratio);
+ }
+ }
+ // no intrinsic size --> use default size
+ int textureWidth = iconBitmapSize;
+ int textureHeight = iconBitmapSize;
+
+ Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
+ Bitmap.Config.ARGB_8888);
+ final Canvas canvas = sCanvas;
+ canvas.setBitmap(bitmap);
+
+ final int left = (textureWidth-width) / 2;
+ final int top = (textureHeight-height) / 2;
+
+ sOldBounds.set(icon.getBounds());
+ if (Utilities.isAtLeastO() && icon instanceof AdaptiveIconDrawable) {
+ int offset = Math.min(left, top);
+ int size = Math.max(width, height);
+ icon.setBounds(offset, offset, offset + size, offset + size);
+ } else {
+ icon.setBounds(left, top, left+width, top+height);
+ }
+ canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
+ icon.draw(canvas);
+ canvas.restore();
+ icon.setBounds(sOldBounds);
+ canvas.setBitmap(null);
+
+ return bitmap;
+ }
+ }
+
+ /**
+ * If the platform is running O but the app is not providing AdaptiveIconDrawable, then
+ * 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) {
+ if (!(FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.isAtLeastO())) {
+ return drawable;
+ }
+
+ try {
+ if (!(drawable instanceof AdaptiveIconDrawable)) {
+ AdaptiveIconDrawable iconWrapper = (AdaptiveIconDrawable)
+ context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
+ FixedScaleDrawable fsd = ((FixedScaleDrawable) iconWrapper.getForeground());
+ fsd.setDrawable(drawable);
+ fsd.setScale(scale);
+ return (Drawable) iconWrapper;
+ }
+ } catch (Exception e) {
+ return drawable;
+ }
+ return drawable;
+ }
+
+ public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context) {
+ return createShortcutIcon(shortcutInfo, context, true /* badged */);
+ }
+
+ public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context,
+ boolean badged) {
+ LauncherAppState app = LauncherAppState.getInstance(context);
+ Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context)
+ .getShortcutIconDrawable(shortcutInfo,
+ app.getInvariantDeviceProfile().fillResIconDpi);
+ IconCache cache = app.getIconCache();
+ Bitmap unbadgedBitmap = unbadgedDrawable == null
+ ? cache.getDefaultIcon(Process.myUserHandle())
+ : LauncherIcons.createScaledBitmapWithoutShadow(unbadgedDrawable, context,
+ Build.VERSION_CODES.O);
+
+ if (!badged) {
+ return unbadgedBitmap;
+ }
+ unbadgedBitmap = LauncherIcons.addShadowToIcon(unbadgedBitmap, context);
+
+ final Bitmap badgeBitmap;
+ ComponentName cn = shortcutInfo.getActivity();
+ if (cn != null) {
+ // Get the app info for the source activity.
+ AppInfo appInfo = new AppInfo();
+ appInfo.user = shortcutInfo.getUserHandle();
+ appInfo.componentName = cn;
+ appInfo.intent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_LAUNCHER)
+ .setComponent(cn);
+ cache.getTitleAndIcon(appInfo, false);
+ badgeBitmap = appInfo.iconBitmap;
+ } else {
+ PackageItemInfo pkgInfo = new PackageItemInfo(shortcutInfo.getPackage());
+ cache.getTitleAndIconForApp(pkgInfo, false);
+ badgeBitmap = pkgInfo.iconBitmap;
+ }
+ return badgeWithBitmap(unbadgedBitmap, badgeBitmap, context);
+ }
+
+ /**
+ * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
+ * This allows the badging to be done based on the action bitmap size rather than
+ * the scaled bitmap size.
+ */
+ private static class FixedSizeBitmapDrawable extends BitmapDrawable {
+
+ public FixedSizeBitmapDrawable(Bitmap bitmap) {
+ super(null, bitmap);
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return getBitmap().getWidth();
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return getBitmap().getWidth();
+ }
+ }
+}
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
new file mode 100644
index 0000000..22ce098
--- /dev/null
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -0,0 +1,296 @@
+/*
+ * 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.graphics;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+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 java.lang.ref.WeakReference;
+
+/**
+ * Extension of {@link FastBitmapDrawable} which shows a progress bar around the icon.
+ */
+public class PreloadIconDrawable extends FastBitmapDrawable {
+
+ private static final Property<PreloadIconDrawable, Float> INTERNAL_STATE =
+ new Property<PreloadIconDrawable, Float>(Float.TYPE, "internalStateProgress") {
+ @Override
+ public Float get(PreloadIconDrawable object) {
+ return object.mInternalStateProgress;
+ }
+
+ @Override
+ public void set(PreloadIconDrawable object, Float value) {
+ object.setInternalProgress(value);
+ }
+ };
+
+ public static final int PATH_SIZE = 100;
+
+ private static final float PROGRESS_WIDTH = 7;
+ private static final float PROGRESS_GAP = 2;
+ private static final int MAX_PAINT_ALPHA = 255;
+
+ private static final long DURATION_SCALE = 500;
+
+ // The smaller the number, the faster the animation would be.
+ // Duration = COMPLETE_ANIM_FRACTION * DURATION_SCALE
+ private static final float COMPLETE_ANIM_FRACTION = 0.3f;
+
+ private static final int COLOR_TRACK = 0x77EEEEEE;
+ private static final int COLOR_SHADOW = 0x55000000;
+
+ private static final float SMALL_SCALE = 0.75f;
+
+ private static final SparseArray<WeakReference<Bitmap>> sShadowCache = new SparseArray<>();
+
+ private final Matrix mTmpMatrix = new Matrix();
+ private final PathMeasure mPathMeasure = new PathMeasure();
+
+ private final Context mContext;
+
+ // Path in [0, 100] bounds.
+ private final Path mProgressPath;
+
+ private final Path mScaledTrackPath;
+ private final Path mScaledProgressPath;
+ private final Paint mProgressPaint;
+
+ private Bitmap mShadowBitmap;
+ private int mIndicatorColor = 0;
+
+ private int mTrackAlpha;
+ private float mTrackLength;
+ private float mIconScale;
+
+ private boolean mRanFinishAnimation;
+
+ // Progress of the internal state. [0, 1] indicates the fraction of completed progress,
+ // [1, (1 + COMPLETE_ANIM_FRACTION)] indicates the progress of zoom animation.
+ private float mInternalStateProgress;
+
+ private ObjectAnimator mCurrentAnim;
+
+ /**
+ * @param progressPath fixed path in the bounds [0, 0, 100, 100] representing a progress bar.
+ */
+ public PreloadIconDrawable(Bitmap b, Path progressPath, Context context) {
+ super(b);
+ mContext = context;
+ mProgressPath = progressPath;
+ mScaledTrackPath = new Path();
+ mScaledProgressPath = new Path();
+
+ mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+ mProgressPaint.setStyle(Paint.Style.STROKE);
+ mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
+
+ setInternalProgress(0);
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+ mTmpMatrix.setScale(
+ (bounds.width() - PROGRESS_WIDTH - 2 * PROGRESS_GAP) / PATH_SIZE,
+ (bounds.height() - PROGRESS_WIDTH - 2 * PROGRESS_GAP) / PATH_SIZE);
+ mTmpMatrix.postTranslate(
+ bounds.left + PROGRESS_WIDTH / 2 + PROGRESS_GAP,
+ bounds.top + PROGRESS_WIDTH / 2 + PROGRESS_GAP);
+
+ mProgressPath.transform(mTmpMatrix, mScaledTrackPath);
+ float scale = bounds.width() / PATH_SIZE;
+ mProgressPaint.setStrokeWidth(PROGRESS_WIDTH * scale);
+
+ mShadowBitmap = getShadowBitmap(bounds.width(), bounds.height(),
+ (PROGRESS_GAP ) * scale);
+ mPathMeasure.setPath(mScaledTrackPath, true);
+ mTrackLength = mPathMeasure.getLength();
+
+ setInternalProgress(mInternalStateProgress);
+ }
+
+ private Bitmap getShadowBitmap(int width, int height, float shadowRadius) {
+ int key = (width << 16) | height;
+ WeakReference<Bitmap> shadowRef = sShadowCache.get(key);
+ Bitmap shadow = shadowRef != null ? shadowRef.get() : null;
+ if (shadow != null) {
+ return shadow;
+ }
+ shadow = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(shadow);
+ mProgressPaint.setShadowLayer(shadowRadius, 0, 0, COLOR_SHADOW);
+ mProgressPaint.setColor(COLOR_TRACK);
+ mProgressPaint.setAlpha(MAX_PAINT_ALPHA);
+ c.drawPath(mScaledTrackPath, mProgressPaint);
+ mProgressPaint.clearShadowLayer();
+ c.setBitmap(null);
+
+ sShadowCache.put(key, new WeakReference<>(shadow));
+ return shadow;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (mRanFinishAnimation) {
+ super.draw(canvas);
+ return;
+ }
+
+ // Draw track.
+ mProgressPaint.setColor(mIndicatorColor);
+ mProgressPaint.setAlpha(mTrackAlpha);
+ if (mShadowBitmap != null) {
+ canvas.drawBitmap(mShadowBitmap, getBounds().left, getBounds().top, mProgressPaint);
+ }
+ canvas.drawPath(mScaledProgressPath, mProgressPaint);
+
+ int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ Rect bounds = getBounds();
+
+ canvas.scale(mIconScale, mIconScale, bounds.exactCenterX(), bounds.exactCenterY());
+ drawInternal(canvas);
+ canvas.restoreToCount(saveCount);
+ }
+
+ /**
+ * Updates the install progress based on the level
+ */
+ @Override
+ protected boolean onLevelChange(int level) {
+ // Run the animation if we have already been bound.
+ updateInternalState(level * 0.01f, getBounds().width() > 0, false);
+ return true;
+ }
+
+ /**
+ * Runs the finish animation if it is has not been run after last call to
+ * {@link #onLevelChange}
+ */
+ public void maybePerformFinishedAnimation() {
+ // If the drawable was recently initialized, skip the progress animation.
+ if (mInternalStateProgress == 0) {
+ mInternalStateProgress = 1;
+ }
+ updateInternalState(1 + COMPLETE_ANIM_FRACTION, true, true);
+ }
+
+ public boolean hasNotCompleted() {
+ return !mRanFinishAnimation;
+ }
+
+ private void updateInternalState(float finalProgress, boolean shouldAnimate, boolean isFinish) {
+ if (mCurrentAnim != null) {
+ mCurrentAnim.cancel();
+ mCurrentAnim = null;
+ }
+
+ if (Float.compare(finalProgress, mInternalStateProgress) == 0) {
+ return;
+ }
+ if (finalProgress < mInternalStateProgress) {
+ shouldAnimate = false;
+ }
+ if (!shouldAnimate || mRanFinishAnimation) {
+ setInternalProgress(finalProgress);
+ } else {
+ mCurrentAnim = ObjectAnimator.ofFloat(this, INTERNAL_STATE, finalProgress);
+ mCurrentAnim.setDuration(
+ (long) ((finalProgress - mInternalStateProgress) * DURATION_SCALE));
+ mCurrentAnim.setInterpolator(new LinearInterpolator());
+ if (isFinish) {
+ mCurrentAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mRanFinishAnimation = true;
+ }
+ });
+ }
+ mCurrentAnim.start();
+ }
+
+ }
+
+ /**
+ * Sets the internal progress and updates the UI accordingly
+ * for progress <= 0:
+ * - icon in the small scale and disabled state
+ * - progress track is visible
+ * - progress bar is not visible
+ * for 0 < progress < 1
+ * - icon in the small scale and disabled state
+ * - progress track is visible
+ * - progress bar is visible with dominant color. Progress bar is drawn as a fraction of
+ * {@link #mScaledTrackPath}.
+ * @see PathMeasure#getSegment(float, float, Path, boolean)
+ * for 1 <= progress < (1 + COMPLETE_ANIM_FRACTION)
+ * - we calculate fraction of progress in the above range
+ * - progress track is drawn with alpha based on fraction
+ * - progress bar is drawn at 100% with alpha based on fraction
+ * - icon is scaled up based on fraction and is drawn in enabled state
+ * for progress >= (1 + COMPLETE_ANIM_FRACTION)
+ * - only icon is drawn in normal state
+ */
+ private void setInternalProgress(float progress) {
+ mInternalStateProgress = progress;
+ if (progress <= 0) {
+ mIconScale = SMALL_SCALE;
+ mScaledTrackPath.reset();
+ mTrackAlpha = MAX_PAINT_ALPHA;
+ setIsDisabled(true);
+ } else if (mIndicatorColor == 0) {
+ // Update the indicator color
+ mIndicatorColor = getIconPalette().getPreloadProgressColor(mContext);
+ }
+
+ if (progress < 1 && progress > 0) {
+ mPathMeasure.getSegment(0, progress * mTrackLength, mScaledProgressPath, true);
+ mIconScale = SMALL_SCALE;
+ mTrackAlpha = MAX_PAINT_ALPHA;
+ setIsDisabled(true);
+ } else if (progress >= 1) {
+ setIsDisabled(false);
+ mScaledTrackPath.set(mScaledProgressPath);
+ float fraction = (progress - 1) / COMPLETE_ANIM_FRACTION;
+
+ if (fraction >= 1) {
+ // Animation has completed
+ mIconScale = 1;
+ mTrackAlpha = 0;
+ } else {
+ mTrackAlpha = Math.round((1 - fraction) * MAX_PAINT_ALPHA);
+ mIconScale = SMALL_SCALE + (1 - SMALL_SCALE) * fraction;
+ }
+ }
+ invalidateSelf();
+ }
+}
diff --git a/src/com/android/launcher3/graphics/ShadowGenerator.java b/src/com/android/launcher3/graphics/ShadowGenerator.java
index 2b24ec9..469fe34 100644
--- a/src/com/android/launcher3/graphics/ShadowGenerator.java
+++ b/src/com/android/launcher3/graphics/ShadowGenerator.java
@@ -16,6 +16,7 @@
package com.android.launcher3.graphics;
+import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BlurMaskFilter;
@@ -38,9 +39,9 @@
// Percent of actual icon size
private static final float KEY_SHADOW_DISTANCE = 1f/48;
- private static final int KEY_SHADOW_ALPHA = 61;
+ public static final int KEY_SHADOW_ALPHA = 61;
- private static final int AMBIENT_SHADOW_ALPHA = 30;
+ public static final int AMBIENT_SHADOW_ALPHA = 30;
private static final Object LOCK = new Object();
// Singleton object guarded by {@link #LOCK}
@@ -52,8 +53,8 @@
private final Paint mBlurPaint;
private final Paint mDrawPaint;
- private ShadowGenerator() {
- mIconSize = LauncherAppState.getInstance().getInvariantDeviceProfile().iconBitmapSize;
+ private ShadowGenerator(Context context) {
+ mIconSize = LauncherAppState.getIDP(context).iconBitmapSize;
mCanvas = new Canvas();
mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
mBlurPaint.setMaskFilter(new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL));
@@ -82,11 +83,51 @@
return result;
}
- public static ShadowGenerator getInstance() {
+ public static Bitmap createPillWithShadow(int rectColor, int width, int height) {
+
+ float shadowRadius = height * 1f / 32;
+ float shadowYOffset = height * 1f / 16;
+
+ int radius = height / 2;
+
+ Canvas canvas = new Canvas();
+ Paint blurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+ blurPaint.setMaskFilter(new BlurMaskFilter(shadowRadius, Blur.NORMAL));
+
+ int centerX = Math.round(width / 2 + shadowRadius);
+ int centerY = Math.round(radius + shadowRadius + shadowYOffset);
+ int center = Math.max(centerX, centerY);
+ int size = center * 2;
+ Bitmap result = Bitmap.createBitmap(size, size, Config.ARGB_8888);
+ canvas.setBitmap(result);
+
+ int left = center - width / 2;
+ int top = center - height / 2;
+ int right = center + width / 2;
+ int bottom = center + height / 2;
+
+ // Draw ambient shadow, center aligned within size
+ blurPaint.setAlpha(AMBIENT_SHADOW_ALPHA);
+ canvas.drawRoundRect(left, top, right, bottom, radius, radius, blurPaint);
+
+ // Draw key shadow, bottom aligned within size
+ blurPaint.setAlpha(KEY_SHADOW_ALPHA);
+ canvas.drawRoundRect(left, top + shadowYOffset, right, bottom + shadowYOffset,
+ radius, radius, blurPaint);
+
+ // Draw the circle
+ Paint drawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+ drawPaint.setColor(rectColor);
+ canvas.drawRoundRect(left, top, right, bottom, radius, radius, drawPaint);
+
+ return result;
+ }
+
+ public static ShadowGenerator getInstance(Context context) {
Preconditions.assertNonUiThread();
synchronized (LOCK) {
if (sShadowGenerator == null) {
- sShadowGenerator = new ShadowGenerator();
+ sShadowGenerator = new ShadowGenerator(context);
}
}
return sShadowGenerator;
diff --git a/src/com/android/launcher3/keyboard/CustomActionsPopup.java b/src/com/android/launcher3/keyboard/CustomActionsPopup.java
new file mode 100644
index 0000000..bb0b58a
--- /dev/null
+++ b/src/com/android/launcher3/keyboard/CustomActionsPopup.java
@@ -0,0 +1,93 @@
+/*
+ * 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.keyboard;
+
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import android.widget.PopupMenu;
+import android.widget.PopupMenu.OnMenuItemClickListener;
+
+import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Handles showing a popup menu with available custom actions for a launcher icon.
+ * This allows exposing various custom actions using keyboard shortcuts.
+ */
+public class CustomActionsPopup implements OnMenuItemClickListener {
+
+ private final Launcher mLauncher;
+ private final LauncherAccessibilityDelegate mDelegate;
+ private final View mIcon;
+
+ public CustomActionsPopup(Launcher launcher, View icon) {
+ mLauncher = launcher;
+ mIcon = icon;
+ PopupContainerWithArrow container = PopupContainerWithArrow.getOpen(launcher);
+ if (container != null) {
+ mDelegate = container.getAccessibilityDelegate();
+ } else {
+ mDelegate = launcher.getAccessibilityDelegate();
+ }
+ }
+
+ private List<AccessibilityAction> getActionList() {
+ if (mIcon == null || !(mIcon.getTag() instanceof ItemInfo)) {
+ return Collections.EMPTY_LIST;
+ }
+
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ mDelegate.addSupportedActions(mIcon, info, true);
+ List<AccessibilityAction> result = new ArrayList<>(info.getActionList());
+ info.recycle();
+ return result;
+ }
+
+ public boolean canShow() {
+ return !getActionList().isEmpty();
+ }
+
+ public boolean show() {
+ List<AccessibilityAction> actions = getActionList();
+ if (actions.isEmpty()) {
+ return false;
+ }
+
+ PopupMenu popup = new PopupMenu(mLauncher, mIcon);
+ popup.setOnMenuItemClickListener(this);
+ Menu menu = popup.getMenu();
+ for (AccessibilityAction action : actions) {
+ menu.add(Menu.NONE, action.getId(), Menu.NONE, action.getLabel());
+ }
+ popup.show();
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemClick(MenuItem menuItem) {
+ return mDelegate.performAction(mIcon, (ItemInfo) mIcon.getTag(), menuItem.getItemId());
+ }
+}
diff --git a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
index 7672f5a..c07ab08 100644
--- a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
+++ b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
@@ -23,12 +23,10 @@
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.TargetApi;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
-import android.os.Build.VERSION_CODES;
import android.util.Property;
import android.view.View;
import android.view.View.OnFocusChangeListener;
@@ -38,7 +36,6 @@
/**
* A helper class to draw background of a focused view.
*/
-@TargetApi(VERSION_CODES.LOLLIPOP)
public abstract class FocusIndicatorHelper implements
OnFocusChangeListener, AnimatorUpdateListener {
@@ -143,7 +140,7 @@
}
private Rect getDrawRect() {
- if (mCurrentView != null) {
+ if (mCurrentView != null && mCurrentView.isAttachedToWindow()) {
viewToRect(mCurrentView, sTempRect1);
if (mShift > 0 && mTargetView != null) {
diff --git a/src/com/android/launcher3/logging/DumpTargetWrapper.java b/src/com/android/launcher3/logging/DumpTargetWrapper.java
new file mode 100644
index 0000000..365e8f2
--- /dev/null
+++ b/src/com/android/launcher3/logging/DumpTargetWrapper.java
@@ -0,0 +1,151 @@
+/*
+ * 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.logging;
+
+import android.os.Process;
+import android.text.TextUtils;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.model.nano.LauncherDumpProto;
+import com.android.launcher3.model.nano.LauncherDumpProto.ContainerType;
+import com.android.launcher3.model.nano.LauncherDumpProto.DumpTarget;
+import com.android.launcher3.model.nano.LauncherDumpProto.ItemType;
+import com.android.launcher3.model.nano.LauncherDumpProto.UserType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class can be used when proto definition doesn't support nesting.
+ */
+public class DumpTargetWrapper {
+ DumpTarget node;
+ ArrayList<DumpTargetWrapper> children;
+
+ public DumpTargetWrapper() {
+ children = new ArrayList<>();
+ }
+
+ public DumpTargetWrapper(int containerType, int id) {
+ this();
+ node = newContainerTarget(containerType, id);
+ }
+
+ public DumpTargetWrapper(ItemInfo info) {
+ this();
+ node = newItemTarget(info);
+ }
+
+ public DumpTarget getDumpTarget() {
+ return node;
+ }
+
+ public void add(DumpTargetWrapper child) {
+ children.add(child);
+ }
+
+ public List<DumpTarget> getFlattenedList() {
+ ArrayList<DumpTarget> list = new ArrayList<>();
+ list.add(node);
+ if (!children.isEmpty()) {
+ for(DumpTargetWrapper t: children) {
+ list.addAll(t.getFlattenedList());
+ }
+ list.add(node); // add a delimiter empty object
+ }
+ return list;
+ }
+ public DumpTarget newItemTarget(ItemInfo info) {
+ DumpTarget dt = new DumpTarget();
+ dt.type = DumpTarget.Type.ITEM;
+
+ switch (info.itemType) {
+ case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+ dt.itemType = ItemType.APP_ICON;
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ dt.itemType = ItemType.UNKNOWN_ITEMTYPE;
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+ dt.itemType = ItemType.WIDGET;
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+ dt.itemType = ItemType.SHORTCUT;
+ break;
+ }
+ return dt;
+ }
+
+ public DumpTarget newContainerTarget(int type, int id) {
+ DumpTarget dt = new DumpTarget();
+ dt.type = DumpTarget.Type.CONTAINER;
+ dt.containerType = type;
+ dt.pageId = id;
+ return dt;
+ }
+
+ public static String getDumpTargetStr(DumpTarget t) {
+ if (t == null){
+ return "";
+ }
+ switch (t.type) {
+ case LauncherDumpProto.DumpTarget.Type.ITEM:
+ return getItemStr(t);
+ case LauncherDumpProto.DumpTarget.Type.CONTAINER:
+ String str = LoggerUtils.getFieldName(t.containerType, ContainerType.class);
+ if (t.containerType == ContainerType.WORKSPACE) {
+ str += " id=" + t.pageId;
+ } else if (t.containerType == ContainerType.FOLDER) {
+ str += " grid(" + t.gridX + "," + t.gridY+ ")";
+ }
+ return str;
+ default:
+ return "UNKNOWN TARGET TYPE";
+ }
+ }
+
+ private static String getItemStr(DumpTarget t) {
+ String typeStr = LoggerUtils.getFieldName(t.itemType, ItemType.class);
+ if (!TextUtils.isEmpty(t.packageName)) {
+ typeStr += ", package=" + t.packageName;
+ }
+ if (!TextUtils.isEmpty(t.component)) {
+ typeStr += ", component=" + t.component;
+ }
+ return typeStr + ", grid(" + t.gridX + "," + t.gridY + "), span(" + t.spanX + "," + t.spanY
+ + "), pageIdx=" + t.pageId + " user=" + t.userType;
+ }
+
+ public DumpTarget writeToDumpTarget(ItemInfo info) {
+ node.component = info.getTargetComponent() == null? "":
+ info.getTargetComponent().flattenToString();
+ node.packageName = info.getTargetComponent() == null? "":
+ info.getTargetComponent().getPackageName();
+ if (info instanceof LauncherAppWidgetInfo) {
+ node.component = ((LauncherAppWidgetInfo) info).providerName.flattenToString();
+ node.packageName = ((LauncherAppWidgetInfo) info).providerName.getPackageName();
+ }
+
+ node.gridX = info.cellX;
+ node.gridY = info.cellY;
+ node.spanX = info.spanX;
+ node.spanY = info.spanY;
+ node.userType = (info.user.equals(Process.myUserHandle()))? UserType.DEFAULT : UserType.WORK;
+ return node;
+ }
+}
diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java
index 8629e92..ffb41b7 100644
--- a/src/com/android/launcher3/logging/FileLog.java
+++ b/src/com/android/launcher3/logging/FileLog.java
@@ -40,7 +40,7 @@
private static File sLogsDirectory = null;
public static void setDir(File logsDir) {
- if (ProviderConfig.IS_DOGFOOD_BUILD) {
+ if (ProviderConfig.IS_DOGFOOD_BUILD || Utilities.IS_DEBUG_DEVICE) {
synchronized (DATE_FORMAT) {
// If the target directory changes, stop any active thread.
if (sHandler != null && !logsDir.equals(sLogsDirectory)) {
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index c2b97eb..499fdc7 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -1,5 +1,23 @@
+/*
+ * 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.logging;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.SparseArray;
import android.view.View;
import com.android.launcher3.ButtonDropTarget;
@@ -8,63 +26,78 @@
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.UninstallDropTarget;
-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.ItemType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
/**
- * Debugging helper methods.
- * toString() cannot be overriden inside auto generated {@link LauncherLogProto}.
- * Note: switch statement cannot be replaced with reflection as proguard strips the constants
+ * Helper methods for logging.
*/
public class LoggerUtils {
- private static final String TAG = "LoggerUtils";
+ private static final ArrayMap<Class, SparseArray<String>> sNameCache = new ArrayMap<>();
+ private static final String UNKNOWN = "UNKNOWN";
- public static String getActionStr(LauncherLogProto.Action action) {
- switch(action.touch) {
- case Action.TAP: return "TAP";
- case Action.LONGPRESS: return "LONGPRESS";
- case Action.DRAGDROP: return "DRAGDROP";
- case Action.PINCH: return "PINCH";
- case Action.SWIPE: return "SWIPE";
- case Action.FLING: return "FLING";
- default: return "UNKNOWN";
+ public static String getFieldName(int value, Class c) {
+ SparseArray<String> cache;
+ synchronized (sNameCache) {
+ cache = sNameCache.get(c);
+ if (cache == null) {
+ cache = new SparseArray<>();
+ for (Field f : c.getDeclaredFields()) {
+ if (f.getType() == int.class && Modifier.isStatic(f.getModifiers())) {
+ try {
+ f.setAccessible(true);
+ cache.put(f.getInt(null), f.getName());
+ } catch (IllegalAccessException e) {
+ // Ignore
+ }
+ }
+ }
+ sNameCache.put(c, cache);
+ }
+ }
+ String result = cache.get(value);
+ return result != null ? result : UNKNOWN;
+ }
+
+ public static String getActionStr(Action action) {
+ switch (action.type) {
+ case Action.Type.TOUCH: return getFieldName(action.touch, Action.Touch.class);
+ case Action.Type.COMMAND: return getFieldName(action.command, Action.Command.class);
+ default: return UNKNOWN;
}
}
public static String getTargetStr(Target t) {
- String typeStr = "";
if (t == null){
- return typeStr;
+ return "";
}
switch (t.type) {
- case Target.ITEM:
+ case Target.Type.ITEM:
return getItemStr(t);
- case Target.CONTROL:
- return getControlStr(t);
- case Target.CONTAINER:
- return getContainerStr(t);
+ case Target.Type.CONTROL:
+ return getFieldName(t.controlType, ControlType.class);
+ case Target.Type.CONTAINER:
+ String str = getFieldName(t.containerType, ContainerType.class);
+ if (t.containerType == ContainerType.WORKSPACE) {
+ str += " id=" + t.pageIndex;
+ } else if (t.containerType == ContainerType.FOLDER) {
+ str += " grid(" + t.gridX + "," + t.gridY+ ")";
+ }
+ return str;
default:
return "UNKNOWN TARGET TYPE";
}
}
private static String getItemStr(Target t) {
- String typeStr = "";
- if (t == null){
- return typeStr;
- }
- switch(t.itemType){
- case LauncherLogProto.APP_ICON: typeStr = "APPICON"; break;
- case LauncherLogProto.SHORTCUT: typeStr = "SHORTCUT"; break;
- case LauncherLogProto.WIDGET: typeStr = "WIDGET"; break;
- case LauncherLogProto.DEEPSHORTCUT: typeStr = "DEEPSHORTCUT"; break;
- case LauncherLogProto.FOLDER_ICON: typeStr = "FOLDERICON"; break;
- case LauncherLogProto.SEARCHBOX: typeStr = "SEARCHBOX"; break;
-
- default: typeStr = "UNKNOWN";
- }
-
+ String typeStr = getFieldName(t.itemType, ItemType.class);
if (t.packageNameHash != 0) {
typeStr += ", packageHash=" + t.packageNameHash;
}
@@ -74,175 +107,84 @@
if (t.intentHash != 0) {
typeStr += ", intentHash=" + t.intentHash;
}
- if (t.spanX != 0) {
- typeStr += ", spanX=" + t.spanX;
- }
- return typeStr += ", grid=(" + t.gridX + "," + t.gridY + "), id=" + t.pageIndex;
+ return typeStr + ", grid(" + t.gridX + "," + t.gridY + "), span(" + t.spanX + "," + t.spanY
+ + "), pageIdx=" + t.pageIndex;
}
- private static String getControlStr(Target t) {
- if (t == null){
- return "";
- }
- switch(t.controlType) {
- case LauncherLogProto.ALL_APPS_BUTTON: return "ALL_APPS_BUTTON";
- case LauncherLogProto.WIDGETS_BUTTON: return "WIDGETS_BUTTON";
- case LauncherLogProto.WALLPAPER_BUTTON: return "WALLPAPER_BUTTON";
- case LauncherLogProto.SETTINGS_BUTTON: return "SETTINGS_BUTTON";
- case LauncherLogProto.REMOVE_TARGET: return "REMOVE_TARGET";
- case LauncherLogProto.UNINSTALL_TARGET: return "UNINSTALL_TARGET";
- case LauncherLogProto.APPINFO_TARGET: return "APPINFO_TARGET";
- case LauncherLogProto.RESIZE_HANDLE: return "RESIZE_HANDLE";
- default: return "UNKNOWN";
- }
+ public static Target newItemTarget(View v) {
+ return (v.getTag() instanceof ItemInfo)
+ ? newItemTarget((ItemInfo) v.getTag())
+ : newTarget(Target.Type.ITEM);
}
- private static String getContainerStr(LauncherLogProto.Target t) {
- String str = "";
- if (t == null) {
- return str;
- }
- switch (t.containerType) {
- case LauncherLogProto.WORKSPACE:
- str = "WORKSPACE";
- break;
- case LauncherLogProto.HOTSEAT:
- str = "HOTSEAT";
- break;
- case LauncherLogProto.FOLDER:
- str = "FOLDER";
- break;
- case LauncherLogProto.ALLAPPS:
- str = "ALLAPPS";
- break;
- case LauncherLogProto.WIDGETS:
- str = "WIDGETS";
- break;
- case LauncherLogProto.OVERVIEW:
- str = "OVERVIEW";
- break;
- case LauncherLogProto.PREDICTION:
- str = "PREDICTION";
- break;
- case LauncherLogProto.SEARCHRESULT:
- str = "SEARCHRESULT";
- break;
- case LauncherLogProto.DEEPSHORTCUTS:
- str = "DEEPSHORTCUTS";
- break;
- default:
- str = "UNKNOWN";
- }
- return str + " id=" + t.pageIndex;
- }
-
- /**
- * Used for launching an event by tapping on an icon.
- */
- public static LauncherLogProto.LauncherEvent initLauncherEvent(
- int actionType,
- View v,
- int parentTargetType){
- LauncherLogProto.LauncherEvent event = new LauncherLogProto.LauncherEvent();
-
- event.srcTarget = new LauncherLogProto.Target[2];
- event.srcTarget[0] = initTarget(v);
- event.srcTarget[1] = new LauncherLogProto.Target();
- event.srcTarget[1].type = parentTargetType;
-
- event.action = new LauncherLogProto.Action();
- event.action.type = actionType;
- return event;
- }
-
- /**
- * Used for clicking on controls and buttons.
- */
- public static LauncherLogProto.LauncherEvent initLauncherEvent(
- int actionType,
- int childTargetType){
- LauncherLogProto.LauncherEvent event = new LauncherLogProto.LauncherEvent();
-
- event.srcTarget = new LauncherLogProto.Target[1];
- event.srcTarget[0] = new LauncherLogProto.Target();
- event.srcTarget[0].type = childTargetType;
-
- event.action = new LauncherLogProto.Action();
- event.action.type = actionType;
- return event;
- }
-
- /**
- * Used for drag and drop interaction.
- */
- public static LauncherLogProto.LauncherEvent initLauncherEvent(
- int actionType,
- View v,
- ItemInfo info,
- int parentSrcTargetType,
- View parentDestTargetType){
- LauncherLogProto.LauncherEvent event = new LauncherLogProto.LauncherEvent();
-
- event.srcTarget = new LauncherLogProto.Target[2];
- event.srcTarget[0] = initTarget(v, info);
- event.srcTarget[1] = new LauncherLogProto.Target();
- event.srcTarget[1].type = parentSrcTargetType;
-
- event.destTarget = new LauncherLogProto.Target[2];
- event.destTarget[0] = initTarget(v, info);
- event.destTarget[1] = initDropTarget(parentDestTargetType);
-
- event.action = new LauncherLogProto.Action();
- event.action.type = actionType;
- return event;
- }
-
- private static Target initTarget(View v, ItemInfo info) {
- Target t = new LauncherLogProto.Target();
- t.type = Target.ITEM;
+ public static Target newItemTarget(ItemInfo info) {
+ Target t = newTarget(Target.Type.ITEM);
switch (info.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
- t.itemType = LauncherLogProto.APP_ICON;
+ t.itemType = ItemType.APP_ICON;
break;
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
- t.itemType = LauncherLogProto.SHORTCUT;
+ t.itemType = ItemType.SHORTCUT;
break;
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
- t.itemType = LauncherLogProto.FOLDER_ICON;
+ t.itemType = ItemType.FOLDER_ICON;
break;
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
- t.itemType = LauncherLogProto.WIDGET;
+ t.itemType = ItemType.WIDGET;
break;
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
- t.itemType = LauncherLogProto.DEEPSHORTCUT;
+ t.itemType = ItemType.DEEPSHORTCUT;
break;
}
return t;
}
- private static Target initDropTarget(View v) {
- Target t = new LauncherLogProto.Target();
- t.type = (v instanceof ButtonDropTarget)? Target.CONTROL : Target.CONTAINER;
- if (t.type == Target.CONTAINER) {
- return t;
+ public static Target newDropTarget(View v) {
+ if (!(v instanceof ButtonDropTarget)) {
+ return newTarget(Target.Type.CONTAINER);
}
-
+ Target t = newTarget(Target.Type.CONTROL);
if (v instanceof InfoDropTarget) {
- t.controlType = LauncherLogProto.APPINFO_TARGET;
+ t.controlType = ControlType.APPINFO_TARGET;
} else if (v instanceof UninstallDropTarget) {
- t.controlType = LauncherLogProto.UNINSTALL_TARGET;
+ t.controlType = ControlType.UNINSTALL_TARGET;
} else if (v instanceof DeleteDropTarget) {
- t.controlType = LauncherLogProto.REMOVE_TARGET;
+ t.controlType = ControlType.REMOVE_TARGET;
}
return t;
}
- private static Target initTarget(View v) {
- Target t = new LauncherLogProto.Target();
- t.type = Target.ITEM;
- if (!(v.getTag() instanceof ItemInfo)) {
- return t;
- }
- return initTarget(v, (ItemInfo) v.getTag());
+ public static Target newTarget(int targetType) {
+ Target t = new Target();
+ t.type = targetType;
+ return t;
+ }
+ public static Target newContainerTarget(int containerType) {
+ Target t = newTarget(Target.Type.CONTAINER);
+ t.containerType = containerType;
+ return t;
+ }
+
+ public static Action newAction(int type) {
+ Action a = new Action();
+ a.type = type;
+ return a;
+ }
+ public static Action newCommandAction(int command) {
+ Action a = newAction(Action.Type.COMMAND);
+ a.command = command;
+ return a;
+ }
+ public static Action newTouchAction(int touch) {
+ Action a = newAction(Action.Type.TOUCH);
+ a.touch = touch;
+ return a;
+ }
+
+ public static LauncherEvent newLauncherEvent(Action action, Target... srcTargets) {
+ LauncherEvent event = new LauncherEvent();
+ event.srcTarget = srcTargets;
+ event.action = action;
+ return event;
}
}
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 56fdce8..07e99c6 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -16,7 +16,9 @@
package com.android.launcher3.logging;
+import android.app.PendingIntent;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.os.SystemClock;
import android.util.Log;
@@ -25,19 +27,30 @@
import com.android.launcher3.DropTarget;
import com.android.launcher3.ItemInfo;
+import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.LogConfig;
import java.util.List;
import java.util.Locale;
+import static com.android.launcher3.logging.LoggerUtils.newCommandAction;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.logging.LoggerUtils.newDropTarget;
+import static com.android.launcher3.logging.LoggerUtils.newItemTarget;
+import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
+import static com.android.launcher3.logging.LoggerUtils.newTarget;
+import static com.android.launcher3.logging.LoggerUtils.newTouchAction;
+
/**
* Manages the creation of {@link LauncherEvent}.
- * To debug this class, execute following command before sideloading a new apk.
+ * To debug this class, execute following command before side loading a new apk.
*
* $ adb shell setprop log.tag.UserEvent VERBOSE
*/
@@ -45,16 +58,21 @@
private final static int MAXIMUM_VIEW_HIERARCHY_LEVEL = 5;
- private final boolean mIsVerbose;
+ private static final String TAG = "UserEvent";
+ private static final boolean IS_VERBOSE =
+ ProviderConfig.IS_DOGFOOD_BUILD && Utilities.isPropertyEnabled(LogConfig.USEREVENT);
+
+ public static UserEventDispatcher newInstance(Context context, boolean isInMultiWindowMode) {
+ UserEventDispatcher ued = Utilities.getOverrideObject(UserEventDispatcher.class,
+ context.getApplicationContext(), R.string.user_event_dispatcher_class);
+ ued.mIsInMultiWindowMode = isInMultiWindowMode;
+ return ued;
+ }
/**
- * TODO: change the name of this interface to LogContainerProvider
- * and the method name to fillInLogContainerData. Not changed to minimize CL diff
- * in this branch.
- *
- * Implemented by containers to provide a launch source for a given child.
+ * Implemented by containers to provide a container source for a given child.
*/
- public interface LaunchSourceProvider {
+ public interface LogContainerProvider {
/**
* Copies data from the source to the destination proto.
@@ -64,15 +82,14 @@
* @param target dest of the data
* @param targetParent dest of the data
*/
- void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent);
+ void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent);
}
/**
* Recursively finds the parent of the given child which implements IconLogInfoProvider
*/
- public static LaunchSourceProvider getLaunchProviderRecursive(View v) {
- ViewParent parent = null;
-
+ public static LogContainerProvider getLaunchProviderRecursive(View v) {
+ ViewParent parent;
if (v != null) {
parent = v.getParent();
} else {
@@ -82,8 +99,8 @@
// Optimization to only check up to 5 parents.
int count = MAXIMUM_VIEW_HIERARCHY_LEVEL;
while (parent != null && count-- > 0) {
- if (parent instanceof LaunchSourceProvider) {
- return (LaunchSourceProvider) parent;
+ if (parent instanceof LogContainerProvider) {
+ return (LogContainerProvider) parent;
} else {
parent = parent.getParent();
}
@@ -91,23 +108,14 @@
return null;
}
- private String TAG = "UserEvent";
-
private long mElapsedContainerMillis;
private long mElapsedSessionMillis;
private long mActionDurationMillis;
+ private boolean mIsInMultiWindowMode;
// Used for filling in predictedRank on {@link Target}s.
private List<ComponentKey> mPredictedApps;
- public UserEventDispatcher() {
- if (ProviderConfig.IS_DOGFOOD_BUILD) {
- mIsVerbose = Utilities.isPropertyEnabled(TAG);
- } else {
- mIsVerbose = false;
- }
- }
-
// APP_ICON SHORTCUT WIDGET
// --------------------------------------------------------------
// packageNameHash required optional required
@@ -115,94 +123,155 @@
// intentHash required
// --------------------------------------------------------------
- protected LauncherEvent createLauncherEvent(View v, Intent intent) {
- LauncherEvent event = LoggerUtils.initLauncherEvent(
- Action.TOUCH, v, Target.CONTAINER);
- event.action.touch = Action.TAP;
+ protected LauncherEvent createLauncherEvent(View v, int intentHashCode, ComponentName cn) {
+ LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
+ newItemTarget(v), newTarget(Target.Type.CONTAINER));
- // Fill in grid(x,y), pageIndex of the child and container type of the parent
- // TODO: make this percolate up the view hierarchy if needed.
+ // TODO: make idx percolate up the view hierarchy if needed.
int idx = 0;
- LaunchSourceProvider provider = getLaunchProviderRecursive(v);
- if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
- return null;
- }
- ItemInfo itemInfo = (ItemInfo) v.getTag();
- provider.fillInLaunchSourceData(v, itemInfo, event.srcTarget[idx], event.srcTarget[idx + 1]);
-
- event.srcTarget[idx].intentHash = intent.hashCode();
- ComponentName cn = intent.getComponent();
- if (cn != null) {
- event.srcTarget[idx].packageNameHash = cn.getPackageName().hashCode();
- event.srcTarget[idx].componentHash = cn.hashCode();
- if (mPredictedApps != null) {
- event.srcTarget[idx].predictedRank = mPredictedApps.indexOf(
- new ComponentKey(cn, itemInfo.user));
+ if (fillInLogContainerData(event, v)) {
+ ItemInfo itemInfo = (ItemInfo) v.getTag();
+ event.srcTarget[idx].intentHash = intentHashCode;
+ if (cn != null) {
+ event.srcTarget[idx].packageNameHash = cn.getPackageName().hashCode();
+ event.srcTarget[idx].componentHash = cn.hashCode();
+ if (mPredictedApps != null) {
+ event.srcTarget[idx].predictedRank = mPredictedApps.indexOf(
+ new ComponentKey(cn, itemInfo.user));
+ }
}
}
return event;
}
+ public boolean fillInLogContainerData(LauncherEvent event, View v) {
+ // Fill in grid(x,y), pageIndex of the child and container type of the parent
+ LogContainerProvider provider = getLaunchProviderRecursive(v);
+ if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
+ return false;
+ }
+ ItemInfo itemInfo = (ItemInfo) v.getTag();
+ provider.fillInLogContainerData(v, itemInfo, event.srcTarget[0], event.srcTarget[1]);
+ return true;
+ }
+
public void logAppLaunch(View v, Intent intent) {
- LauncherEvent ev = createLauncherEvent(v, intent);
+ LauncherEvent ev = createLauncherEvent(v, intent.hashCode(), intent.getComponent());
if (ev == null) {
return;
}
dispatchUserEvent(ev, intent);
}
- public void logActionOnItem(int action, int itemType) {
- LauncherEvent event = LoggerUtils.initLauncherEvent(Action.TOUCH, Target.ITEM);
- event.action.touch = action;
- event.srcTarget[0].itemType = itemType;
+ public void logNotificationLaunch(View v, PendingIntent intent) {
+ ComponentName dummyComponent = new ComponentName(intent.getCreatorPackage(), "--dummy--");
+ LauncherEvent ev = createLauncherEvent(v, intent.hashCode(), dummyComponent);
+ if (ev == null) {
+ return;
+ }
+ dispatchUserEvent(ev, null);
+ }
+
+ public void logActionCommand(int command, int containerType) {
+ logActionCommand(command, containerType, 0);
+ }
+
+ public void logActionCommand(int command, int containerType, int pageIndex) {
+ LauncherEvent event = newLauncherEvent(
+ newCommandAction(command), newContainerTarget(containerType));
+ event.srcTarget[0].pageIndex = pageIndex;
+ dispatchUserEvent(event, null);
+ }
+
+ /**
+ * TODO: Make this function work when a container view is passed as the 2nd param.
+ */
+ public void logActionCommand(int command, View itemView, int containerType) {
+ LauncherEvent event = newLauncherEvent(newCommandAction(command),
+ newItemTarget(itemView), newTarget(Target.Type.CONTAINER));
+
+ if (fillInLogContainerData(event, itemView)) {
+ // TODO: Remove the following two lines once fillInLogContainerData can take in a
+ // container view.
+ event.srcTarget[0].type = Target.Type.CONTAINER;
+ event.srcTarget[0].containerType = containerType;
+ }
dispatchUserEvent(event, null);
}
public void logActionOnControl(int action, int controlType) {
- LauncherEvent event = LoggerUtils.initLauncherEvent(Action.TOUCH, Target.CONTROL);
- event.action.touch = action;
+ LauncherEvent event = newLauncherEvent(
+ newTouchAction(action), newTarget(Target.Type.CONTROL));
event.srcTarget[0].controlType = controlType;
dispatchUserEvent(event, null);
}
+ public void logActionTapOutside(Target target) {
+ LauncherEvent event = newLauncherEvent(newTouchAction(Action.Type.TOUCH),
+ target);
+ event.action.isOutside = true;
+ dispatchUserEvent(event, null);
+ }
+
public void logActionOnContainer(int action, int dir, int containerType) {
- LauncherEvent event = LoggerUtils.initLauncherEvent(Action.TOUCH, Target.CONTAINER);
- event.action.touch = action;
+ logActionOnContainer(action, dir, containerType, 0);
+ }
+
+ public void logActionOnContainer(int action, int dir, int containerType, int pageIndex) {
+ LauncherEvent event = newLauncherEvent(newTouchAction(action),
+ newContainerTarget(containerType));
event.action.dir = dir;
- event.srcTarget[0].containerType = containerType;
+ event.srcTarget[0].pageIndex = pageIndex;
+ dispatchUserEvent(event, null);
+ }
+
+ public void logActionOnItem(int action, int dir, int itemType) {
+ Target itemTarget = newTarget(Target.Type.ITEM);
+ itemTarget.itemType = itemType;
+ LauncherEvent event = newLauncherEvent(newTouchAction(action), itemTarget);
+ event.action.dir = dir;
dispatchUserEvent(event, null);
}
public void logDeepShortcutsOpen(View icon) {
- LauncherEvent event = LoggerUtils.initLauncherEvent(
- Action.TOUCH, icon, Target.CONTAINER);
- LaunchSourceProvider provider = getLaunchProviderRecursive(icon);
- if (icon == null && !(icon.getTag() instanceof ItemInfo)) {
+ LogContainerProvider provider = getLaunchProviderRecursive(icon);
+ if (icon == null || !(icon.getTag() instanceof ItemInfo)) {
return;
}
ItemInfo info = (ItemInfo) icon.getTag();
- provider.fillInLaunchSourceData(icon, info, event.srcTarget[0], event.srcTarget[1]);
- event.action.touch = Action.LONGPRESS;
+ LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.LONGPRESS),
+ newItemTarget(info), newTarget(Target.Type.CONTAINER));
+ provider.fillInLogContainerData(icon, info, event.srcTarget[0], event.srcTarget[1]);
dispatchUserEvent(event, null);
+
+ resetElapsedContainerMillis();
}
public void setPredictedApps(List<ComponentKey> predictedApps) {
mPredictedApps = predictedApps;
}
- public void logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView) {
- LauncherEvent event = LoggerUtils.initLauncherEvent(Action.TOUCH,
- dragObj.dragView,
- dragObj.originalDragInfo,
- Target.CONTAINER,
- dropTargetAsView);
- event.action.touch = Action.DRAGDROP;
+ /* Currently we are only interested in whether this event happens or not and don't
+ * care about which screen moves to where. */
+ public void logOverviewReorder() {
+ LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP),
+ newContainerTarget(ContainerType.WORKSPACE),
+ newContainerTarget(ContainerType.OVERVIEW));
+ dispatchUserEvent(event, null);
+ }
- dragObj.dragSource.fillInLaunchSourceData(null, dragObj.originalDragInfo,
+ public void logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView) {
+ LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP),
+ newItemTarget(dragObj.originalDragInfo), newTarget(Target.Type.CONTAINER));
+ event.destTarget = new Target[] {
+ newItemTarget(dragObj.originalDragInfo), newDropTarget(dropTargetAsView)
+ };
+
+ dragObj.dragSource.fillInLogContainerData(null, dragObj.originalDragInfo,
event.srcTarget[0], event.srcTarget[1]);
- if (dropTargetAsView instanceof LaunchSourceProvider) {
- ((LaunchSourceProvider) dropTargetAsView).fillInLaunchSourceData(null,
+ if (dropTargetAsView instanceof LogContainerProvider) {
+ ((LogContainerProvider) dropTargetAsView).fillInLogContainerData(null,
dragObj.dragInfo, event.destTarget[0], event.destTarget[1]);
}
@@ -227,29 +296,31 @@
}
public void dispatchUserEvent(LauncherEvent ev, Intent intent) {
+ ev.isInMultiWindowMode = mIsInMultiWindowMode;
ev.elapsedContainerMillis = SystemClock.uptimeMillis() - mElapsedContainerMillis;
ev.elapsedSessionMillis = SystemClock.uptimeMillis() - mElapsedSessionMillis;
- if (!mIsVerbose) {
+ if (!IS_VERBOSE) {
return;
}
- Log.d(TAG, String.format(Locale.US,
- "\naction:%s\n Source child:%s\tparent:%s",
- LoggerUtils.getActionStr(ev.action),
- LoggerUtils.getTargetStr(ev.srcTarget != null ? ev.srcTarget[0] : null),
- LoggerUtils.getTargetStr(ev.srcTarget != null && ev.srcTarget.length > 1 ?
- ev.srcTarget[1] : null)));
- if (ev.destTarget != null && ev.destTarget.length > 0) {
- Log.d(TAG, String.format(Locale.US,
- " Destination child:%s\tparent:%s",
- LoggerUtils.getTargetStr(ev.destTarget != null ? ev.destTarget[0] : null),
- LoggerUtils.getTargetStr(ev.destTarget != null && ev.destTarget.length > 1 ?
- ev.destTarget[1] : null)));
+ String log = "action:" + LoggerUtils.getActionStr(ev.action);
+ if (ev.srcTarget != null && ev.srcTarget.length > 0) {
+ log += "\n Source " + getTargetsStr(ev.srcTarget);
}
- Log.d(TAG, String.format(Locale.US,
- " Elapsed container %d ms session %d ms action %d ms",
+ if (ev.destTarget != null && ev.destTarget.length > 0) {
+ log += "\n Destination " + getTargetsStr(ev.destTarget);
+ }
+ log += String.format(Locale.US,
+ "\n Elapsed container %d ms session %d ms action %d ms",
ev.elapsedContainerMillis,
ev.elapsedSessionMillis,
- ev.actionDurationMillis));
+ ev.actionDurationMillis);
+ log += "\n isInMultiWindowMode " + ev.isInMultiWindowMode;
+ Log.d(TAG, log);
+ }
+
+ private static String getTargetsStr(Target[] targets) {
+ return "child:" + LoggerUtils.getTargetStr(targets[0]) +
+ (targets.length > 1 ? "\tparent:" + LoggerUtils.getTargetStr(targets[1]) : "");
}
}
diff --git a/src/com/android/launcher3/model/AbstractUserComparator.java b/src/com/android/launcher3/model/AbstractUserComparator.java
deleted file mode 100644
index bd28560..0000000
--- a/src/com/android/launcher3/model/AbstractUserComparator.java
+++ /dev/null
@@ -1,49 +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.model;
-
-import android.content.Context;
-
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.compat.UserHandleCompat;
-import com.android.launcher3.compat.UserManagerCompat;
-
-import java.util.Comparator;
-
-/**
- * A comparator to arrange items based on user profiles.
- */
-public abstract class AbstractUserComparator<T extends ItemInfo> implements Comparator<T> {
-
- private final UserManagerCompat mUserManager;
- private final UserHandleCompat mMyUser;
-
- public AbstractUserComparator(Context context) {
- mUserManager = UserManagerCompat.getInstance(context);
- mMyUser = UserHandleCompat.myUserHandle();
- }
-
- @Override
- public int compare(T lhs, T rhs) {
- if (mMyUser.equals(lhs.user)) {
- return -1;
- } else {
- Long aUserSerial = mUserManager.getSerialNumberForUser(lhs.user);
- Long bUserSerial = mUserManager.getSerialNumberForUser(rhs.user);
- return aUserSerial.compareTo(bUserSerial);
- }
- }
-}
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
new file mode 100644
index 0000000..9696054
--- /dev/null
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -0,0 +1,266 @@
+/*
+ * 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.model;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
+import android.util.LongSparseArray;
+import android.util.Pair;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.Provider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Task to add auto-created workspace items.
+ */
+public class AddWorkspaceItemsTask extends ExtendedModelTask {
+
+ private final Provider<List<ItemInfo>> mAppsProvider;
+
+ /**
+ * @param appsProvider items to add on the workspace
+ */
+ public AddWorkspaceItemsTask(Provider<List<ItemInfo>> appsProvider) {
+ mAppsProvider = appsProvider;
+ }
+
+ @Override
+ public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+ List<ItemInfo> workspaceApps = mAppsProvider.get();
+ if (workspaceApps.isEmpty()) {
+ return;
+ }
+ Context context = app.getContext();
+
+ final ArrayList<ItemInfo> addedItemsFinal = new ArrayList<>();
+ final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<>();
+
+ // Get the list of workspace screens. We need to append to this list and
+ // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
+ // called.
+ ArrayList<Long> workspaceScreens = LauncherModel.loadWorkspaceScreensDb(context);
+ synchronized(dataModel) {
+ for (ItemInfo item : workspaceApps) {
+ if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
+ item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
+ // Short-circuit this logic if the icon exists somewhere on the workspace
+ if (shortcutExists(dataModel, item.getIntent(), item.user)) {
+ continue;
+ }
+ }
+
+ // Find appropriate space for the item.
+ Pair<Long, int[]> coords = findSpaceForItem(app, dataModel, workspaceScreens,
+ addedWorkspaceScreensFinal, item.spanX, item.spanY);
+ long screenId = coords.first;
+ int[] cordinates = coords.second;
+
+ ItemInfo itemInfo;
+ if (item instanceof ShortcutInfo || item instanceof FolderInfo ||
+ item instanceof LauncherAppWidgetInfo) {
+ itemInfo = item;
+ } else if (item instanceof AppInfo) {
+ itemInfo = ((AppInfo) item).makeShortcut();
+ } else {
+ throw new RuntimeException("Unexpected info type");
+ }
+
+ // Add the shortcut to the db
+ getModelWriter().addItemToDatabase(itemInfo,
+ LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId,
+ cordinates[0], cordinates[1]);
+
+ // Save the ShortcutInfo for binding in the workspace
+ addedItemsFinal.add(itemInfo);
+ }
+ }
+
+ // Update the workspace screens
+ updateScreens(context, workspaceScreens);
+
+ if (!addedItemsFinal.isEmpty()) {
+ scheduleCallbackTask(new CallbackTask() {
+ @Override
+ public void execute(Callbacks callbacks) {
+ final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
+ final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
+ if (!addedItemsFinal.isEmpty()) {
+ ItemInfo info = addedItemsFinal.get(addedItemsFinal.size() - 1);
+ long lastScreenId = info.screenId;
+ for (ItemInfo i : addedItemsFinal) {
+ if (i.screenId == lastScreenId) {
+ addAnimated.add(i);
+ } else {
+ addNotAnimated.add(i);
+ }
+ }
+ }
+ callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
+ addNotAnimated, addAnimated, null);
+ }
+ });
+ }
+ }
+
+ protected void updateScreens(Context context, ArrayList<Long> workspaceScreens) {
+ LauncherModel.updateWorkspaceScreenOrder(context, workspaceScreens);
+ }
+
+ /**
+ * Returns true if the shortcuts already exists on the workspace. This must be called after
+ * the workspace has been loaded. We identify a shortcut by its intent.
+ */
+ protected boolean shortcutExists(BgDataModel dataModel, Intent intent, UserHandle user) {
+ final String intentWithPkg, intentWithoutPkg;
+ if (intent == null) {
+ // Skip items with null intents
+ return true;
+ }
+ if (intent.getComponent() != null) {
+ // If component is not null, an intent with null package will produce
+ // the same result and should also be a match.
+ String packageName = intent.getComponent().getPackageName();
+ if (intent.getPackage() != null) {
+ intentWithPkg = intent.toUri(0);
+ intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0);
+ } else {
+ intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0);
+ intentWithoutPkg = intent.toUri(0);
+ }
+ } else {
+ intentWithPkg = intent.toUri(0);
+ intentWithoutPkg = intent.toUri(0);
+ }
+
+ synchronized (dataModel) {
+ for (ItemInfo item : dataModel.itemsIdMap) {
+ if (item instanceof ShortcutInfo) {
+ ShortcutInfo info = (ShortcutInfo) item;
+ if (item.getIntent() != null && info.user.equals(user)) {
+ Intent copyIntent = new Intent(item.getIntent());
+ copyIntent.setSourceBounds(intent.getSourceBounds());
+ String s = copyIntent.toUri(0);
+ if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Find a position on the screen for the given size or adds a new screen.
+ * @return screenId and the coordinates for the item.
+ */
+ protected Pair<Long, int[]> findSpaceForItem(
+ LauncherAppState app, BgDataModel dataModel,
+ ArrayList<Long> workspaceScreens,
+ ArrayList<Long> addedWorkspaceScreensFinal,
+ int spanX, int spanY) {
+ LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
+
+ // Use sBgItemsIdMap as all the items are already loaded.
+ synchronized (dataModel) {
+ for (ItemInfo info : dataModel.itemsIdMap) {
+ if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ ArrayList<ItemInfo> items = screenItems.get(info.screenId);
+ if (items == null) {
+ items = new ArrayList<>();
+ screenItems.put(info.screenId, items);
+ }
+ items.add(info);
+ }
+ }
+ }
+
+ // Find appropriate space for the item.
+ long screenId = 0;
+ int[] cordinates = new int[2];
+ boolean found = false;
+
+ int screenCount = workspaceScreens.size();
+ // First check the preferred screen.
+ int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
+ if (preferredScreenIndex < screenCount) {
+ screenId = workspaceScreens.get(preferredScreenIndex);
+ found = findNextAvailableIconSpaceInScreen(
+ app, screenItems.get(screenId), cordinates, spanX, spanY);
+ }
+
+ if (!found) {
+ // Search on any of the screens starting from the first screen.
+ for (int screen = 1; screen < screenCount; screen++) {
+ screenId = workspaceScreens.get(screen);
+ if (findNextAvailableIconSpaceInScreen(
+ app, screenItems.get(screenId), cordinates, spanX, spanY)) {
+ // We found a space for it
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ // Still no position found. Add a new screen to the end.
+ screenId = LauncherSettings.Settings.call(app.getContext().getContentResolver(),
+ LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
+ .getLong(LauncherSettings.Settings.EXTRA_VALUE);
+
+ // Save the screen id for binding in the workspace
+ workspaceScreens.add(screenId);
+ addedWorkspaceScreensFinal.add(screenId);
+
+ // If we still can't find an empty space, then God help us all!!!
+ if (!findNextAvailableIconSpaceInScreen(
+ app, screenItems.get(screenId), cordinates, spanX, spanY)) {
+ throw new RuntimeException("Can't find space to add the item");
+ }
+ }
+ return Pair.create(screenId, cordinates);
+ }
+
+ private boolean findNextAvailableIconSpaceInScreen(
+ LauncherAppState app, ArrayList<ItemInfo> occupiedPos,
+ int[] xy, int spanX, int spanY) {
+ InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
+
+ GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows);
+ if (occupiedPos != null) {
+ for (ItemInfo r : occupiedPos) {
+ occupied.markCells(r, true);
+ }
+ }
+ return occupied.findVacantCell(xy, spanX, spanY);
+ }
+}
diff --git a/src/com/android/launcher3/model/AppNameComparator.java b/src/com/android/launcher3/model/AppNameComparator.java
deleted file mode 100644
index 5f80037..0000000
--- a/src/com/android/launcher3/model/AppNameComparator.java
+++ /dev/null
@@ -1,99 +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.model;
-
-import android.content.Context;
-
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.util.Thunk;
-
-import java.text.Collator;
-import java.util.Comparator;
-
-/**
- * Class to manage access to an app name comparator.
- * <p>
- * Used to sort application name in all apps view and widget tray view.
- */
-public class AppNameComparator {
- private final Collator mCollator;
- private final AbstractUserComparator<ItemInfo> mAppInfoComparator;
- private final Comparator<String> mSectionNameComparator;
-
- public AppNameComparator(Context context) {
- mCollator = Collator.getInstance();
- mAppInfoComparator = new AbstractUserComparator<ItemInfo>(context) {
-
- @Override
- public final int compare(ItemInfo a, ItemInfo b) {
- // Order by the title in the current locale
- int result = compareTitles(a.title.toString(), b.title.toString());
- if (result == 0 && a instanceof AppInfo && b instanceof AppInfo) {
- AppInfo aAppInfo = (AppInfo) a;
- AppInfo bAppInfo = (AppInfo) b;
- // If two apps have the same title, then order by the component name
- result = aAppInfo.componentName.compareTo(bAppInfo.componentName);
- if (result == 0) {
- // If the two apps are the same component, then prioritize by the order that
- // the app user was created (prioritizing the main user's apps)
- return super.compare(a, b);
- }
- }
- return result;
- }
- };
- mSectionNameComparator = new Comparator<String>() {
- @Override
- public int compare(String o1, String o2) {
- return compareTitles(o1, o2);
- }
- };
- }
-
- /**
- * Returns a locale-aware comparator that will alphabetically order a list of applications.
- */
- public Comparator<ItemInfo> getAppInfoComparator() {
- return mAppInfoComparator;
- }
-
- /**
- * Returns a locale-aware comparator that will alphabetically order a list of section names.
- */
- public Comparator<String> getSectionNameComparator() {
- return mSectionNameComparator;
- }
-
- /**
- * Compares two titles with the same return value semantics as Comparator.
- */
- @Thunk int compareTitles(String titleA, String titleB) {
- // Ensure that we de-prioritize any titles that don't start with a linguistic letter or digit
- boolean aStartsWithLetter = (titleA.length() > 0) &&
- Character.isLetterOrDigit(titleA.codePointAt(0));
- boolean bStartsWithLetter = (titleB.length() > 0) &&
- Character.isLetterOrDigit(titleB.codePointAt(0));
- if (aStartsWithLetter && !bStartsWithLetter) {
- return -1;
- } else if (!aStartsWithLetter && bStartsWithLetter) {
- return 1;
- }
-
- // Order by the title in the current locale
- return mCollator.compare(titleA, titleB);
- }
-}
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
new file mode 100644
index 0000000..0e73ca6
--- /dev/null
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -0,0 +1,374 @@
+/*
+ * 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.model;
+
+import android.content.Context;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.MutableInt;
+
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.InstallShortcutReceiver;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.logging.LoggerUtils;
+import com.android.launcher3.logging.DumpTargetWrapper;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.model.nano.LauncherDumpProto;
+import com.android.launcher3.model.nano.LauncherDumpProto.ContainerType;
+import com.android.launcher3.model.nano.LauncherDumpProto.DumpTarget;
+import com.android.launcher3.model.nano.LauncherDumpProto.ItemType;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.LongArrayMap;
+import com.android.launcher3.util.MultiHashMap;
+import com.google.protobuf.nano.MessageNano;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * All the data stored in-memory and managed by the LauncherModel
+ */
+public class BgDataModel {
+
+ private static final String TAG = "BgDataModel";
+
+ /**
+ * Map of all the ItemInfos (shortcuts, folders, and widgets) created by
+ * LauncherModel to their ids
+ */
+ public final LongArrayMap<ItemInfo> itemsIdMap = new LongArrayMap<>();
+
+ /**
+ * List of all the folders and shortcuts directly on the home screen (no widgets
+ * or shortcuts within folders).
+ */
+ public final ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
+
+ /**
+ * All LauncherAppWidgetInfo created by LauncherModel.
+ */
+ public final ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
+
+ /**
+ * Map of id to FolderInfos of all the folders created by LauncherModel
+ */
+ public final LongArrayMap<FolderInfo> folders = new LongArrayMap<>();
+
+ /**
+ * Ordered list of workspace screens ids.
+ */
+ public final ArrayList<Long> workspaceScreens = new ArrayList<>();
+
+ /**
+ * Map of ShortcutKey to the number of times it is pinned.
+ */
+ public final Map<ShortcutKey, MutableInt> pinnedShortcutCounts = new HashMap<>();
+
+ /**
+ * Maps all launcher activities to the id's of their shortcuts (if they have any).
+ */
+ public final MultiHashMap<ComponentKey, String> deepShortcutMap = new MultiHashMap<>();
+
+ /**
+ * Clears all the data
+ */
+ public synchronized void clear() {
+ workspaceItems.clear();
+ appWidgets.clear();
+ folders.clear();
+ itemsIdMap.clear();
+ workspaceScreens.clear();
+ pinnedShortcutCounts.clear();
+ deepShortcutMap.clear();
+ }
+
+ public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer,
+ String[] args) {
+ if (args.length > 0 && TextUtils.equals(args[0], "--proto")) {
+ dumpProto(prefix, fd, writer, args);
+ return;
+ }
+ writer.println(prefix + "Data Model:");
+ writer.print(prefix + " ---- workspace screens: ");
+ for (int i = 0; i < workspaceScreens.size(); i++) {
+ writer.print(" " + workspaceScreens.get(i).toString());
+ }
+ writer.println();
+ writer.println(prefix + " ---- workspace items ");
+ for (int i = 0; i < workspaceItems.size(); i++) {
+ writer.println(prefix + '\t' + workspaceItems.get(i).toString());
+ }
+ writer.println(prefix + " ---- appwidget items ");
+ for (int i = 0; i < appWidgets.size(); i++) {
+ writer.println(prefix + '\t' + appWidgets.get(i).toString());
+ }
+ writer.println(prefix + " ---- folder items ");
+ for (int i = 0; i< folders.size(); i++) {
+ writer.println(prefix + '\t' + folders.valueAt(i).toString());
+ }
+ writer.println(prefix + " ---- items id map ");
+ for (int i = 0; i< itemsIdMap.size(); i++) {
+ writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString());
+ }
+
+ if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
+ writer.println(prefix + "shortcuts");
+ for (ArrayList<String> map : deepShortcutMap.values()) {
+ writer.print(prefix + " ");
+ for (String str : map) {
+ writer.print(str.toString() + ", ");
+ }
+ writer.println();
+ }
+ }
+ }
+
+ private synchronized void dumpProto(String prefix, FileDescriptor fd, PrintWriter writer,
+ String[] args) {
+
+ // Add top parent nodes. (L1)
+ DumpTargetWrapper hotseat = new DumpTargetWrapper(ContainerType.HOTSEAT, 0);
+ LongArrayMap<DumpTargetWrapper> workspaces = new LongArrayMap<>();
+ for (int i = 0; i < workspaceScreens.size(); i++) {
+ workspaces.put(new Long(workspaceScreens.get(i)),
+ new DumpTargetWrapper(ContainerType.WORKSPACE, i));
+ }
+ DumpTargetWrapper dtw;
+ // Add non leaf / non top nodes (L2)
+ for (int i = 0; i < folders.size(); i++) {
+ FolderInfo fInfo = folders.valueAt(i);
+ dtw = new DumpTargetWrapper(ContainerType.FOLDER, folders.size());
+ dtw.writeToDumpTarget(fInfo);
+ for(ShortcutInfo sInfo: fInfo.contents) {
+ DumpTargetWrapper child = new DumpTargetWrapper(sInfo);
+ child.writeToDumpTarget(sInfo);
+ dtw.add(child);
+ }
+ if (fInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ hotseat.add(dtw);
+ } else if (fInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ workspaces.get(new Long(fInfo.screenId)).add(dtw);
+ }
+ }
+ // Add leaf nodes (L3): *Info
+ for (int i = 0; i < workspaceItems.size(); i++) {
+ ItemInfo info = workspaceItems.get(i);
+ if (info instanceof FolderInfo) {
+ continue;
+ }
+ dtw = new DumpTargetWrapper(info);
+ dtw.writeToDumpTarget(info);
+ if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ hotseat.add(dtw);
+ } else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ workspaces.get(new Long(info.screenId)).add(dtw);
+ }
+ }
+ for (int i = 0; i < appWidgets.size(); i++) {
+ ItemInfo info = appWidgets.get(i);
+ dtw = new DumpTargetWrapper(info);
+ dtw.writeToDumpTarget(info);
+ if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ hotseat.add(dtw);
+ } else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ workspaces.get(new Long(info.screenId)).add(dtw);
+ }
+ }
+
+
+ // Traverse target wrapper
+ ArrayList<DumpTarget> targetList = new ArrayList<>();
+ targetList.addAll(hotseat.getFlattenedList());
+ for (int i = 0; i < workspaces.size(); i++) {
+ targetList.addAll(workspaces.valueAt(i).getFlattenedList());
+ }
+
+ if (args.length > 1 && TextUtils.equals(args[1], "--debug")) {
+ for (int i = 0; i < targetList.size(); i++) {
+ writer.println(prefix + DumpTargetWrapper.getDumpTargetStr(targetList.get(i)));
+ }
+ return;
+ } else {
+ LauncherDumpProto.LauncherImpression proto = new LauncherDumpProto.LauncherImpression();
+ proto.targets = new DumpTarget[targetList.size()];
+ for (int i = 0; i < targetList.size(); i++) {
+ proto.targets[i] = targetList.get(i);
+ }
+ FileOutputStream fos = new FileOutputStream(fd);
+ try {
+
+ fos.write(MessageNano.toByteArray(proto));
+ Log.d(TAG, MessageNano.toByteArray(proto).length + "Bytes");
+ } catch (IOException e) {
+ Log.e(TAG, "Exception writing dumpsys --proto", e);
+ }
+ }
+ }
+
+ public synchronized void removeItem(Context context, ItemInfo... items) {
+ removeItem(context, Arrays.asList(items));
+ }
+
+ public synchronized void removeItem(Context context, Iterable<? extends ItemInfo> items) {
+ for (ItemInfo item : items) {
+ switch (item.itemType) {
+ case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+ folders.remove(item.id);
+ if (ProviderConfig.IS_DOGFOOD_BUILD) {
+ for (ItemInfo info : itemsIdMap) {
+ if (info.container == item.id) {
+ // We are deleting a folder which still contains items that
+ // think they are contained by that folder.
+ String msg = "deleting a folder (" + item + ") which still " +
+ "contains items (" + info + ")";
+ Log.e(TAG, msg);
+ }
+ }
+ }
+ workspaceItems.remove(item);
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
+ // Decrement pinned shortcut count
+ ShortcutKey pinnedShortcut = ShortcutKey.fromItemInfo(item);
+ MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
+ if ((count == null || --count.value == 0)
+ && !InstallShortcutReceiver.getPendingShortcuts(context)
+ .contains(pinnedShortcut)) {
+ DeepShortcutManager.getInstance(context).unpinShortcut(pinnedShortcut);
+ }
+ // Fall through.
+ }
+ case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ workspaceItems.remove(item);
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+ case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
+ appWidgets.remove(item);
+ break;
+ }
+ itemsIdMap.remove(item.id);
+ }
+ }
+
+ public synchronized void addItem(Context context, ItemInfo item, boolean newItem) {
+ itemsIdMap.put(item.id, item);
+ switch (item.itemType) {
+ case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+ folders.put(item.id, (FolderInfo) item);
+ workspaceItems.add(item);
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
+ // Increment the count for the given shortcut
+ ShortcutKey pinnedShortcut = ShortcutKey.fromItemInfo(item);
+ MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
+ if (count == null) {
+ count = new MutableInt(1);
+ pinnedShortcutCounts.put(pinnedShortcut, count);
+ } else {
+ count.value++;
+ }
+
+ // Since this is a new item, pin the shortcut in the system server.
+ if (newItem && count.value == 1) {
+ DeepShortcutManager.getInstance(context).pinShortcut(pinnedShortcut);
+ }
+ // Fall through
+ }
+ case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
+ item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ workspaceItems.add(item);
+ } else {
+ if (newItem) {
+ if (!folders.containsKey(item.container)) {
+ // Adding an item to a folder that doesn't exist.
+ String msg = "adding item: " + item + " to a folder that " +
+ " doesn't exist";
+ Log.e(TAG, msg);
+ }
+ } else {
+ findOrMakeFolder(item.container).add((ShortcutInfo) item, false);
+ }
+
+ }
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+ case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
+ appWidgets.add((LauncherAppWidgetInfo) item);
+ break;
+ }
+ }
+
+ /**
+ * Return an existing FolderInfo object if we have encountered this ID previously,
+ * or make a new one.
+ */
+ public synchronized FolderInfo findOrMakeFolder(long id) {
+ // See if a placeholder was created for us already
+ FolderInfo folderInfo = folders.get(id);
+ if (folderInfo == null) {
+ // No placeholder -- create a new instance
+ folderInfo = new FolderInfo();
+ folders.put(id, folderInfo);
+ }
+ return folderInfo;
+ }
+
+ /**
+ * Clear all the deep shortcuts for the given package, and re-add the new shortcuts.
+ */
+ public synchronized void updateDeepShortcutMap(
+ String packageName, UserHandle user, List<ShortcutInfoCompat> shortcuts) {
+ if (packageName != null) {
+ Iterator<ComponentKey> keysIter = deepShortcutMap.keySet().iterator();
+ while (keysIter.hasNext()) {
+ ComponentKey next = keysIter.next();
+ if (next.componentName.getPackageName().equals(packageName)
+ && next.user.equals(user)) {
+ keysIter.remove();
+ }
+ }
+ }
+
+ // Now add the new shortcuts to the map.
+ for (ShortcutInfoCompat shortcut : shortcuts) {
+ boolean shouldShowInContainer = shortcut.isEnabled()
+ && (shortcut.isDeclaredInManifest() || shortcut.isDynamic());
+ if (shouldShowInContainer) {
+ ComponentKey targetComponent
+ = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
+ deepShortcutMap.addToList(targetComponent, shortcut.getId());
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
new file mode 100644
index 0000000..46130fc
--- /dev/null
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -0,0 +1,97 @@
+/*
+ * 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.model;
+
+import android.content.ComponentName;
+import android.os.UserHandle;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.IconCache;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.ShortcutInfo;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+/**
+ * Handles changes due to cache updates.
+ */
+public class CacheDataUpdatedTask extends ExtendedModelTask {
+
+ public static final int OP_CACHE_UPDATE = 1;
+ public static final int OP_SESSION_UPDATE = 2;
+
+ private final int mOp;
+ private final UserHandle mUser;
+ private final HashSet<String> mPackages;
+
+ public CacheDataUpdatedTask(int op, UserHandle user, HashSet<String> packages) {
+ mOp = op;
+ mUser = user;
+ mPackages = packages;
+ }
+
+ @Override
+ public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+ IconCache iconCache = app.getIconCache();
+
+ final ArrayList<AppInfo> updatedApps = new ArrayList<>();
+
+ ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
+ synchronized (dataModel) {
+ for (ItemInfo info : dataModel.itemsIdMap) {
+ if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
+ ShortcutInfo si = (ShortcutInfo) info;
+ ComponentName cn = si.getTargetComponent();
+ if (si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+ && isValidShortcut(si) && cn != null
+ && mPackages.contains(cn.getPackageName())) {
+ iconCache.getTitleAndIcon(si, si.usingLowResIcon);
+ updatedShortcuts.add(si);
+ }
+ }
+ }
+ apps.updateIconsAndLabels(mPackages, mUser, updatedApps);
+ }
+ bindUpdatedShortcuts(updatedShortcuts, mUser);
+
+ if (!updatedApps.isEmpty()) {
+ scheduleCallbackTask(new CallbackTask() {
+ @Override
+ public void execute(Callbacks callbacks) {
+ callbacks.bindAppsUpdated(updatedApps);
+ }
+ });
+ }
+ }
+
+ public boolean isValidShortcut(ShortcutInfo si) {
+ switch (mOp) {
+ case OP_CACHE_UPDATE:
+ return true;
+ case OP_SESSION_UPDATE:
+ return si.isPromise();
+ default:
+ return false;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/ExtendedModelTask.java b/src/com/android/launcher3/model/ExtendedModelTask.java
new file mode 100644
index 0000000..0541966
--- /dev/null
+++ b/src/com/android/launcher3/model/ExtendedModelTask.java
@@ -0,0 +1,62 @@
+/*
+ * 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.model;
+
+import android.os.UserHandle;
+
+import com.android.launcher3.LauncherModel.BaseModelUpdateTask;
+import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.MultiHashMap;
+
+import java.util.ArrayList;
+
+/**
+ * Extension of {@link BaseModelUpdateTask} with some utility methods
+ */
+public abstract class ExtendedModelTask extends BaseModelUpdateTask {
+
+ public void bindUpdatedShortcuts(
+ ArrayList<ShortcutInfo> updatedShortcuts, UserHandle user) {
+ bindUpdatedShortcuts(updatedShortcuts, new ArrayList<ShortcutInfo>(), user);
+ }
+
+ public void bindUpdatedShortcuts(
+ final ArrayList<ShortcutInfo> updatedShortcuts,
+ final ArrayList<ShortcutInfo> removedShortcuts,
+ final UserHandle user) {
+ if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) {
+ scheduleCallbackTask(new CallbackTask() {
+ @Override
+ public void execute(Callbacks callbacks) {
+ callbacks.bindShortcutsChanged(updatedShortcuts, removedShortcuts, user);
+ }
+ });
+ }
+ }
+
+ public void bindDeepShortcuts(BgDataModel dataModel) {
+ final MultiHashMap<ComponentKey, String> shortcutMapCopy = dataModel.deepShortcutMap.clone();
+ scheduleCallbackTask(new CallbackTask() {
+ @Override
+ public void execute(Callbacks callbacks) {
+ callbacks.bindDeepShortcutMap(shortcutMapCopy);
+ }
+ });
+ }
+}
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index fd647c7..221798b 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -42,7 +42,7 @@
*/
public class GridSizeMigrationTask {
- public static boolean ENABLED = Utilities.isNycOrAbove();
+ public static boolean ENABLED = Utilities.ATLEAST_NOUGAT;
private static final String TAG = "GridSizeMigrationTask";
private static final boolean DEBUG = true;
@@ -895,12 +895,12 @@
*/
public static boolean migrateGridIfNeeded(Context context) {
SharedPreferences prefs = Utilities.getPrefs(context);
- InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile();
+ InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
String gridSizeString = getPointString(idp.numColumns, idp.numRows);
if (gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, "")) &&
- idp.numHotseatIcons != prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons)) {
+ idp.numHotseatIcons == prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons)) {
// Skip if workspace and hotseat sizes have not changed.
return true;
}
@@ -915,8 +915,7 @@
if (srcHotseatCount != idp.numHotseatIcons) {
// Migrate hotseat.
- dbChanged = new GridSizeMigrationTask(context,
- LauncherAppState.getInstance().getInvariantDeviceProfile(),
+ dbChanged = new GridSizeMigrationTask(context, LauncherAppState.getIDP(context),
validPackages, srcHotseatCount, idp.numHotseatIcons).migrateHotseat();
}
@@ -978,9 +977,9 @@
* @return a map with occupied hotseat position set to non-null value.
*/
public static LongArrayMap<Object> removeBrokenHotseatItems(Context context) throws Exception {
- GridSizeMigrationTask task = new GridSizeMigrationTask(context,
- LauncherAppState.getInstance().getInvariantDeviceProfile(),
- getValidPackages(context), Integer.MAX_VALUE, Integer.MAX_VALUE);
+ GridSizeMigrationTask task = new GridSizeMigrationTask(
+ context, LauncherAppState.getIDP(context), getValidPackages(context),
+ Integer.MAX_VALUE, Integer.MAX_VALUE);
// Load all the valid entries
ArrayList<DbEntry> items = task.loadHotseatEntries();
@@ -1038,8 +1037,7 @@
}
protected boolean runStepTask(Point sourceSize, Point nextSize) throws Exception {
- return new GridSizeMigrationTask(mContext,
- LauncherAppState.getInstance().getInvariantDeviceProfile(),
+ return new GridSizeMigrationTask(mContext, LauncherAppState.getIDP(mContext),
mValidPackages, sourceSize, nextSize).migrateWorkspace();
}
}
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
new file mode 100644
index 0000000..36f60b9
--- /dev/null
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -0,0 +1,470 @@
+/*
+ * 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.model;
+
+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.LauncherActivityInfo;
+import android.database.Cursor;
+import android.database.CursorWrapper;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.UserHandle;
+import android.provider.BaseColumns;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.LongSparseArray;
+
+import com.android.launcher3.IconCache;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.LongArrayMap;
+import com.android.launcher3.util.PackageManagerHelper;
+
+import java.net.URISyntaxException;
+import java.security.InvalidParameterException;
+import java.util.ArrayList;
+
+/**
+ * Extension of {@link Cursor} with utility methods for workspace loading.
+ */
+public class LoaderCursor extends CursorWrapper {
+
+ private static final String TAG = "LoaderCursor";
+
+ public final LongSparseArray<UserHandle> allUsers = new LongSparseArray<>();
+
+ private final Context mContext;
+ private final UserManagerCompat mUserManager;
+ private final IconCache mIconCache;
+ private final InvariantDeviceProfile mIDP;
+
+ private final ArrayList<Long> itemsToRemove = new ArrayList<>();
+ private final ArrayList<Long> restoredRows = new ArrayList<>();
+ private final LongArrayMap<GridOccupancy> occupied = new LongArrayMap<>();
+
+ private final int iconPackageIndex;
+ private final int iconResourceIndex;
+ private final int iconIndex;
+ public final int titleIndex;
+
+ private final int idIndex;
+ private final int containerIndex;
+ private final int itemTypeIndex;
+ private final int screenIndex;
+ private final int cellXIndex;
+ private final int cellYIndex;
+ private final int profileIdIndex;
+ private final int restoredIndex;
+ private final int intentIndex;
+
+ // Properties loaded per iteration
+ public long serialNumber;
+ public UserHandle user;
+ public long id;
+ public long container;
+ public int itemType;
+ public int restoreFlag;
+
+ public LoaderCursor(Cursor c, LauncherAppState app) {
+ super(c);
+ mContext = app.getContext();
+ mIconCache = app.getIconCache();
+ mIDP = app.getInvariantDeviceProfile();
+ mUserManager = UserManagerCompat.getInstance(mContext);
+
+ // Init column indices
+ iconIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
+ iconPackageIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
+ iconResourceIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
+ titleIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
+
+ idIndex = getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
+ containerIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
+ itemTypeIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
+ screenIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
+ cellXIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
+ cellYIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
+ profileIdIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.PROFILE_ID);
+ restoredIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.RESTORED);
+ intentIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
+ }
+
+ @Override
+ public boolean moveToNext() {
+ boolean result = super.moveToNext();
+ if (result) {
+ // Load common properties.
+ itemType = getInt(itemTypeIndex);
+ container = getInt(containerIndex);
+ id = getLong(idIndex);
+ serialNumber = getInt(profileIdIndex);
+ user = allUsers.get(serialNumber);
+ restoreFlag = getInt(restoredIndex);
+ }
+ return result;
+ }
+
+ public Intent parseIntent() {
+ String intentDescription = getString(intentIndex);
+ try {
+ return TextUtils.isEmpty(intentDescription) ?
+ null : Intent.parseUri(intentDescription, 0);
+ } catch (URISyntaxException e) {
+ Log.e(TAG, "Error parsing Intent");
+ return null;
+ }
+ }
+
+ public ShortcutInfo loadSimpleShortcut() {
+ final ShortcutInfo info = new ShortcutInfo();
+ // Non-app shortcuts are only supported for current user.
+ info.user = user;
+ info.itemType = itemType;
+ info.title = getTitle();
+ info.iconBitmap = loadIcon(info);
+ // the fallback icon
+ if (info.iconBitmap == null) {
+ info.iconBitmap = mIconCache.getDefaultIcon(info.user);
+ }
+
+ // TODO: If there's an explicit component and we can't install that, delete it.
+
+ return info;
+ }
+
+ /**
+ * Loads the icon from the cursor and updates the {@param info} if the icon is an app resource.
+ */
+ protected Bitmap loadIcon(ShortcutInfo info) {
+ Bitmap icon = null;
+ if (itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
+ String packageName = getString(iconPackageIndex);
+ String resourceName = getString(iconResourceIndex);
+ if (!TextUtils.isEmpty(packageName) || !TextUtils.isEmpty(resourceName)) {
+ info.iconResource = new ShortcutIconResource();
+ info.iconResource.packageName = packageName;
+ info.iconResource.resourceName = resourceName;
+ icon = LauncherIcons.createIconBitmap(info.iconResource, mContext);
+ }
+ }
+ if (icon == null) {
+ // Failed to load from resource, try loading from DB.
+ byte[] data = getBlob(iconIndex);
+ try {
+ icon = LauncherIcons.createIconBitmap(
+ BitmapFactory.decodeByteArray(data, 0, data.length), mContext);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+ return icon;
+ }
+
+ /**
+ * Returns the title or empty string
+ */
+ private String getTitle() {
+ String title = getString(titleIndex);
+ return TextUtils.isEmpty(title) ? "" : Utilities.trim(title);
+ }
+
+
+ /**
+ * Make an ShortcutInfo object for a restored application or shortcut item that points
+ * to a package that is not yet installed on the system.
+ */
+ public ShortcutInfo getRestoredItemInfo(Intent intent) {
+ final ShortcutInfo info = new ShortcutInfo();
+ info.user = user;
+ info.intent = intent;
+
+ info.iconBitmap = loadIcon(info);
+ // the fallback icon
+ if (info.iconBitmap == null) {
+ mIconCache.getTitleAndIcon(info, false /* useLowResIcon */);
+ }
+
+ if (hasRestoreFlag(ShortcutInfo.FLAG_RESTORED_ICON)) {
+ String title = getTitle();
+ if (!TextUtils.isEmpty(title)) {
+ info.title = Utilities.trim(title);
+ }
+ } else if (hasRestoreFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
+ if (TextUtils.isEmpty(info.title)) {
+ info.title = getTitle();
+ }
+ } else {
+ throw new InvalidParameterException("Invalid restoreType " + restoreFlag);
+ }
+
+ info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
+ info.itemType = itemType;
+ info.status = restoreFlag;
+ return info;
+ }
+
+ /**
+ * Make an ShortcutInfo object for a shortcut that is an application.
+ */
+ public ShortcutInfo getAppShortcutInfo(
+ Intent intent, boolean allowMissingTarget, boolean useLowResIcon) {
+ if (user == null) {
+ Log.d(TAG, "Null user found in getShortcutInfo");
+ return null;
+ }
+
+ ComponentName componentName = intent.getComponent();
+ if (componentName == null) {
+ Log.d(TAG, "Missing component found in getShortcutInfo");
+ return null;
+ }
+
+ Intent newIntent = new Intent(Intent.ACTION_MAIN, null);
+ newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ newIntent.setComponent(componentName);
+ LauncherActivityInfo lai = LauncherAppsCompat.getInstance(mContext)
+ .resolveActivity(newIntent, user);
+ if ((lai == null) && !allowMissingTarget) {
+ Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName);
+ return null;
+ }
+
+ final ShortcutInfo info = new ShortcutInfo();
+ info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+ info.user = user;
+ info.intent = newIntent;
+
+ mIconCache.getTitleAndIcon(info, lai, useLowResIcon);
+ if (mIconCache.isDefaultIcon(info.iconBitmap, user)) {
+ Bitmap icon = loadIcon(info);
+ info.iconBitmap = icon != null ? icon : info.iconBitmap;
+ }
+
+ if (lai != null && PackageManagerHelper.isAppSuspended(lai.getApplicationInfo())) {
+ info.isDisabled = ShortcutInfo.FLAG_DISABLED_SUSPENDED;
+ }
+
+ // from the db
+ if (TextUtils.isEmpty(info.title)) {
+ info.title = getTitle();
+ }
+
+ // fall back to the class name of the activity
+ if (info.title == null) {
+ info.title = componentName.getClassName();
+ }
+
+ info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
+ return info;
+ }
+
+ /**
+ * Returns a {@link ContentWriter} which can be used to update the current item.
+ */
+ public ContentWriter updater() {
+ return new ContentWriter(mContext, new ContentWriter.CommitParams(
+ BaseColumns._ID + "= ?", new String[]{Long.toString(id)}));
+ }
+
+ /**
+ * Marks the current item for removal
+ */
+ public void markDeleted(String reason) {
+ FileLog.e(TAG, reason);
+ itemsToRemove.add(id);
+ }
+
+ /**
+ * Removes any items marked for removal.
+ * @return true is any item was removed.
+ */
+ public boolean commitDeleted() {
+ if (itemsToRemove.size() > 0) {
+ // Remove dead items
+ mContext.getContentResolver().delete(LauncherSettings.Favorites.CONTENT_URI,
+ Utilities.createDbSelectionQuery(
+ LauncherSettings.Favorites._ID, itemsToRemove), null);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Marks the current item as restored
+ */
+ public void markRestored() {
+ if (restoreFlag != 0) {
+ restoredRows.add(id);
+ restoreFlag = 0;
+ }
+ }
+
+ public boolean hasRestoreFlag(int flagMask) {
+ return (restoreFlag & flagMask) != 0;
+ }
+
+ public void commitRestoredItems() {
+ if (restoredRows.size() > 0) {
+ // Update restored items that no longer require special handling
+ ContentValues values = new ContentValues();
+ values.put(LauncherSettings.Favorites.RESTORED, 0);
+ mContext.getContentResolver().update(LauncherSettings.Favorites.CONTENT_URI, values,
+ Utilities.createDbSelectionQuery(
+ LauncherSettings.Favorites._ID, restoredRows), null);
+ }
+ }
+
+ /**
+ * Returns true is the item is on workspace or hotseat
+ */
+ public boolean isOnWorkspaceOrHotseat() {
+ return container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
+ container == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+ }
+
+ /**
+ * Applies the following properties:
+ * {@link ItemInfo#id}
+ * {@link ItemInfo#container}
+ * {@link ItemInfo#screenId}
+ * {@link ItemInfo#cellX}
+ * {@link ItemInfo#cellY}
+ */
+ public void applyCommonProperties(ItemInfo info) {
+ info.id = id;
+ info.container = container;
+ info.screenId = getInt(screenIndex);
+ info.cellX = getInt(cellXIndex);
+ info.cellY = getInt(cellYIndex);
+ }
+
+ /**
+ * Adds the {@param info} to {@param dataModel} if it does not overlap with any other item,
+ * otherwise marks it for deletion.
+ */
+ public void checkAndAddItem(ItemInfo info, BgDataModel dataModel) {
+ if (checkItemPlacement(info, dataModel.workspaceScreens)) {
+ dataModel.addItem(mContext, info, false);
+ } else {
+ markDeleted("Item position overlap");
+ }
+ }
+
+ /**
+ * check & update map of what's occupied; used to discard overlapping/invalid items
+ */
+ protected boolean checkItemPlacement(ItemInfo item, ArrayList<Long> workspaceScreens) {
+ long containerIndex = item.screenId;
+ if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ // Return early if we detect that an item is under the hotseat button
+ if (!FeatureFlags.NO_ALL_APPS_ICON &&
+ mIDP.isAllAppsButtonRank((int) item.screenId)) {
+ Log.e(TAG, "Error loading shortcut into hotseat " + item
+ + " into position (" + item.screenId + ":" + item.cellX + ","
+ + item.cellY + ") occupied by all apps");
+ return false;
+ }
+
+ final GridOccupancy hotseatOccupancy =
+ occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
+
+ if (item.screenId >= mIDP.numHotseatIcons) {
+ Log.e(TAG, "Error loading shortcut " + item
+ + " into hotseat position " + item.screenId
+ + ", position out of bounds: (0 to " + (mIDP.numHotseatIcons - 1)
+ + ")");
+ return false;
+ }
+
+ if (hotseatOccupancy != null) {
+ if (hotseatOccupancy.cells[(int) item.screenId][0]) {
+ Log.e(TAG, "Error loading shortcut into hotseat " + item
+ + " into position (" + item.screenId + ":" + item.cellX + ","
+ + item.cellY + ") already occupied");
+ return false;
+ } else {
+ hotseatOccupancy.cells[(int) item.screenId][0] = true;
+ return true;
+ }
+ } else {
+ final GridOccupancy occupancy = new GridOccupancy(mIDP.numHotseatIcons, 1);
+ occupancy.cells[(int) item.screenId][0] = true;
+ occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, occupancy);
+ return true;
+ }
+ } else if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ if (!workspaceScreens.contains((Long) item.screenId)) {
+ // The item has an invalid screen id.
+ return false;
+ }
+ } else {
+ // Skip further checking if it is not the hotseat or workspace container
+ return true;
+ }
+
+ final int countX = mIDP.numColumns;
+ final int countY = mIDP.numRows;
+ if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
+ item.cellX < 0 || item.cellY < 0 ||
+ item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) {
+ Log.e(TAG, "Error loading shortcut " + item
+ + " into cell (" + containerIndex + "-" + item.screenId + ":"
+ + item.cellX + "," + item.cellY
+ + ") out of screen bounds ( " + countX + "x" + countY + ")");
+ return false;
+ }
+
+ if (!occupied.containsKey(item.screenId)) {
+ GridOccupancy screen = new GridOccupancy(countX + 1, countY + 1);
+ if (item.screenId == Workspace.FIRST_SCREEN_ID) {
+ // Mark the first row as occupied (if the feature is enabled)
+ // in order to account for the QSB.
+ screen.markCells(0, 0, countX + 1, 1, FeatureFlags.QSB_ON_FIRST_SCREEN);
+ }
+ occupied.put(item.screenId, screen);
+ }
+ final GridOccupancy occupancy = occupied.get(item.screenId);
+
+ // Check if any workspace icons overlap with each other
+ if (occupancy.isRegionVacant(item.cellX, item.cellY, item.spanX, item.spanY)) {
+ occupancy.markCells(item, true);
+ return true;
+ } else {
+ Log.e(TAG, "Error loading shortcut " + item
+ + " into cell (" + containerIndex + "-" + item.screenId + ":"
+ + item.cellX + "," + item.cellX + "," + item.spanX + "," + item.spanY
+ + ") already occupied");
+ return false;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
new file mode 100644
index 0000000..4931dca
--- /dev/null
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -0,0 +1,374 @@
+/*
+ * 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.model;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.util.Log;
+
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherProvider;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.LauncherSettings.Settings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.LooperExecuter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.Executor;
+
+/**
+ * Class for handling model updates.
+ */
+public class ModelWriter {
+
+ private static final String TAG = "ModelWriter";
+
+ private final Context mContext;
+ private final BgDataModel mBgDataModel;
+ private final Executor mWorkerExecutor;
+ private final boolean mHasVerticalHotseat;
+
+ public ModelWriter(Context context, BgDataModel dataModel, boolean hasVerticalHotseat) {
+ mContext = context;
+ mBgDataModel = dataModel;
+ mWorkerExecutor = new LooperExecuter(LauncherModel.getWorkerLooper());
+ mHasVerticalHotseat = hasVerticalHotseat;
+ }
+
+ private void updateItemInfoProps(
+ ItemInfo item, long container, long screenId, int cellX, int cellY) {
+ item.container = container;
+ item.cellX = cellX;
+ item.cellY = cellY;
+ // We store hotseat items in canonical form which is this orientation invariant position
+ // in the hotseat
+ if (container == Favorites.CONTAINER_HOTSEAT) {
+ item.screenId = mHasVerticalHotseat
+ ? LauncherAppState.getIDP(mContext).numHotseatIcons - cellY - 1 : cellX;
+ } else {
+ item.screenId = screenId;
+ }
+ }
+
+ /**
+ * Adds an item to the DB if it was not created previously, or move it to a new
+ * <container, screen, cellX, cellY>
+ */
+ public void addOrMoveItemInDatabase(ItemInfo item,
+ long container, long screenId, int cellX, int cellY) {
+ if (item.container == ItemInfo.NO_ID) {
+ // From all apps
+ addItemToDatabase(item, container, screenId, cellX, cellY);
+ } else {
+ // From somewhere else
+ moveItemInDatabase(item, container, screenId, cellX, cellY);
+ }
+ }
+
+ private void checkItemInfoLocked(long itemId, ItemInfo item, StackTraceElement[] stackTrace) {
+ ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId);
+ if (modelItem != null && item != modelItem) {
+ // check all the data is consistent
+ if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
+ ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
+ ShortcutInfo shortcut = (ShortcutInfo) item;
+ if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
+ modelShortcut.intent.filterEquals(shortcut.intent) &&
+ modelShortcut.id == shortcut.id &&
+ modelShortcut.itemType == shortcut.itemType &&
+ modelShortcut.container == shortcut.container &&
+ modelShortcut.screenId == shortcut.screenId &&
+ modelShortcut.cellX == shortcut.cellX &&
+ modelShortcut.cellY == shortcut.cellY &&
+ modelShortcut.spanX == shortcut.spanX &&
+ modelShortcut.spanY == shortcut.spanY) {
+ // For all intents and purposes, this is the same object
+ return;
+ }
+ }
+
+ // the modelItem needs to match up perfectly with item if our model is
+ // to be consistent with the database-- for now, just require
+ // modelItem == item or the equality check above
+ String msg = "item: " + ((item != null) ? item.toString() : "null") +
+ "modelItem: " +
+ ((modelItem != null) ? modelItem.toString() : "null") +
+ "Error: ItemInfo passed to checkItemInfo doesn't match original";
+ RuntimeException e = new RuntimeException(msg);
+ if (stackTrace != null) {
+ e.setStackTrace(stackTrace);
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * Move an item in the DB to a new <container, screen, cellX, cellY>
+ */
+ public void moveItemInDatabase(final ItemInfo item,
+ long container, long screenId, int cellX, int cellY) {
+ updateItemInfoProps(item, container, screenId, cellX, cellY);
+
+ final ContentWriter writer = new ContentWriter(mContext)
+ .put(Favorites.CONTAINER, item.container)
+ .put(Favorites.CELLX, item.cellX)
+ .put(Favorites.CELLY, item.cellY)
+ .put(Favorites.RANK, item.rank)
+ .put(Favorites.SCREEN, item.screenId);
+
+ mWorkerExecutor.execute(new UpdateItemRunnable(item, writer));
+ }
+
+ /**
+ * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
+ * cellX, cellY have already been updated on the ItemInfos.
+ */
+ public void moveItemsInDatabase(final ArrayList<ItemInfo> items, long container, int screen) {
+ ArrayList<ContentValues> contentValues = new ArrayList<>();
+ int count = items.size();
+
+ for (int i = 0; i < count; i++) {
+ ItemInfo item = items.get(i);
+ updateItemInfoProps(item, container, screen, item.cellX, item.cellY);
+
+ final ContentValues values = new ContentValues();
+ values.put(Favorites.CONTAINER, item.container);
+ values.put(Favorites.CELLX, item.cellX);
+ values.put(Favorites.CELLY, item.cellY);
+ values.put(Favorites.RANK, item.rank);
+ values.put(Favorites.SCREEN, item.screenId);
+
+ contentValues.add(values);
+ }
+ mWorkerExecutor.execute(new UpdateItemsRunnable(items, contentValues));
+ }
+
+ /**
+ * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
+ */
+ public void modifyItemInDatabase(final ItemInfo item,
+ long container, long screenId, int cellX, int cellY, int spanX, int spanY) {
+ updateItemInfoProps(item, container, screenId, cellX, cellY);
+ item.spanX = spanX;
+ item.spanY = spanY;
+
+ final ContentWriter writer = new ContentWriter(mContext)
+ .put(Favorites.CONTAINER, item.container)
+ .put(Favorites.CELLX, item.cellX)
+ .put(Favorites.CELLY, item.cellY)
+ .put(Favorites.RANK, item.rank)
+ .put(Favorites.SPANX, item.spanX)
+ .put(Favorites.SPANY, item.spanY)
+ .put(Favorites.SCREEN, item.screenId);
+
+ mWorkerExecutor.execute(new UpdateItemRunnable(item, writer));
+ }
+
+ /**
+ * Update an item to the database in a specified container.
+ */
+ public void updateItemInDatabase(ItemInfo item) {
+ ContentWriter writer = new ContentWriter(mContext);
+ item.onAddToDatabase(writer);
+ mWorkerExecutor.execute(new UpdateItemRunnable(item, writer));
+ }
+
+ /**
+ * Add an item to the database in a specified container. Sets the container, screen, cellX and
+ * cellY fields of the item. Also assigns an ID to the item.
+ */
+ public void addItemToDatabase(final ItemInfo item,
+ long container, long screenId, int cellX, int cellY) {
+ updateItemInfoProps(item, container, screenId, cellX, cellY);
+
+ final ContentWriter writer = new ContentWriter(mContext);
+ final ContentResolver cr = mContext.getContentResolver();
+ item.onAddToDatabase(writer);
+
+ item.id = Settings.call(cr, Settings.METHOD_NEW_ITEM_ID).getLong(Settings.EXTRA_VALUE);
+ writer.put(Favorites._ID, item.id);
+
+ final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
+ mWorkerExecutor.execute(new Runnable() {
+ public void run() {
+ cr.insert(Favorites.CONTENT_URI, writer.getValues(mContext));
+
+ synchronized (mBgDataModel) {
+ checkItemInfoLocked(item.id, item, stackTrace);
+ mBgDataModel.addItem(mContext, item, true);
+ }
+ }
+ });
+ }
+
+ /**
+ * Removes the specified item from the database
+ */
+ public void deleteItemFromDatabase(ItemInfo item) {
+ deleteItemsFromDatabase(Arrays.asList(item));
+ }
+
+ /**
+ * Removes all the items from the database matching {@param matcher}.
+ */
+ public void deleteItemsFromDatabase(ItemInfoMatcher matcher) {
+ deleteItemsFromDatabase(matcher.filterItemInfos(mBgDataModel.itemsIdMap));
+ }
+
+ /**
+ * Removes the specified items from the database
+ */
+ public void deleteItemsFromDatabase(final Iterable<? extends ItemInfo> items) {
+ mWorkerExecutor.execute(new Runnable() {
+ public void run() {
+ for (ItemInfo item : items) {
+ final Uri uri = Favorites.getContentUri(item.id);
+ mContext.getContentResolver().delete(uri, null, null);
+
+ mBgDataModel.removeItem(mContext, item);
+ }
+ }
+ });
+ }
+
+ /**
+ * Remove the specified folder and all its contents from the database.
+ */
+ public void deleteFolderAndContentsFromDatabase(final FolderInfo info) {
+ mWorkerExecutor.execute(new Runnable() {
+ public void run() {
+ ContentResolver cr = mContext.getContentResolver();
+ cr.delete(LauncherSettings.Favorites.CONTENT_URI,
+ LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
+ mBgDataModel.removeItem(mContext, info.contents);
+ info.contents.clear();
+
+ cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
+ mBgDataModel.removeItem(mContext, info);
+ }
+ });
+ }
+
+ private class UpdateItemRunnable extends UpdateItemBaseRunnable {
+ private final ItemInfo mItem;
+ private final ContentWriter mWriter;
+ private final long mItemId;
+
+ UpdateItemRunnable(ItemInfo item, ContentWriter writer) {
+ mItem = item;
+ mWriter = writer;
+ mItemId = item.id;
+ }
+
+ @Override
+ public void run() {
+ Uri uri = Favorites.getContentUri(mItemId);
+ mContext.getContentResolver().update(uri, mWriter.getValues(mContext), null, null);
+ updateItemArrays(mItem, mItemId);
+ }
+ }
+
+ private class UpdateItemsRunnable extends UpdateItemBaseRunnable {
+ private final ArrayList<ContentValues> mValues;
+ private final ArrayList<ItemInfo> mItems;
+
+ UpdateItemsRunnable(ArrayList<ItemInfo> items, ArrayList<ContentValues> values) {
+ mValues = values;
+ mItems = items;
+ }
+
+ @Override
+ public void run() {
+ ArrayList<ContentProviderOperation> ops = new ArrayList<>();
+ int count = mItems.size();
+ for (int i = 0; i < count; i++) {
+ ItemInfo item = mItems.get(i);
+ final long itemId = item.id;
+ final Uri uri = Favorites.getContentUri(itemId);
+ ContentValues values = mValues.get(i);
+
+ ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
+ updateItemArrays(item, itemId);
+ }
+ try {
+ mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, ops);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private abstract class UpdateItemBaseRunnable implements Runnable {
+ private final StackTraceElement[] mStackTrace;
+
+ UpdateItemBaseRunnable() {
+ mStackTrace = new Throwable().getStackTrace();
+ }
+
+ protected void updateItemArrays(ItemInfo item, long itemId) {
+ // Lock on mBgLock *after* the db operation
+ synchronized (mBgDataModel) {
+ checkItemInfoLocked(itemId, item, mStackTrace);
+
+ if (item.container != Favorites.CONTAINER_DESKTOP &&
+ item.container != Favorites.CONTAINER_HOTSEAT) {
+ // Item is in a folder, make sure this folder exists
+ if (!mBgDataModel.folders.containsKey(item.container)) {
+ // An items container is being set to a that of an item which is not in
+ // the list of Folders.
+ String msg = "item: " + item + " container being set to: " +
+ item.container + ", not in the list of folders";
+ Log.e(TAG, msg);
+ }
+ }
+
+ // Items are added/removed from the corresponding FolderInfo elsewhere, such
+ // as in Workspace.onDrop. Here, we just add/remove them from the list of items
+ // that are on the desktop, as appropriate
+ ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId);
+ if (modelItem != null &&
+ (modelItem.container == Favorites.CONTAINER_DESKTOP ||
+ modelItem.container == Favorites.CONTAINER_HOTSEAT)) {
+ switch (modelItem.itemType) {
+ case Favorites.ITEM_TYPE_APPLICATION:
+ case Favorites.ITEM_TYPE_SHORTCUT:
+ case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+ case Favorites.ITEM_TYPE_FOLDER:
+ if (!mBgDataModel.workspaceItems.contains(modelItem)) {
+ mBgDataModel.workspaceItems.add(modelItem);
+ }
+ break;
+ default:
+ break;
+ }
+ } else {
+ mBgDataModel.workspaceItems.remove(modelItem);
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
new file mode 100644
index 0000000..5d04325
--- /dev/null
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -0,0 +1,86 @@
+/*
+ * 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.model;
+
+import android.content.ComponentName;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
+
+import java.util.HashSet;
+
+/**
+ * Handles changes due to a sessions updates for a currently installing app.
+ */
+public class PackageInstallStateChangedTask extends ExtendedModelTask {
+
+ private final PackageInstallInfo mInstallInfo;
+
+ public PackageInstallStateChangedTask(PackageInstallInfo installInfo) {
+ mInstallInfo = installInfo;
+ }
+
+ @Override
+ public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+ if (mInstallInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
+ // Ignore install success events as they are handled by Package add events.
+ return;
+ }
+
+ synchronized (dataModel) {
+ final HashSet<ItemInfo> updates = new HashSet<>();
+ for (ItemInfo info : dataModel.itemsIdMap) {
+ if (info instanceof ShortcutInfo) {
+ ShortcutInfo si = (ShortcutInfo) info;
+ ComponentName cn = si.getTargetComponent();
+ if (si.isPromise() && (cn != null)
+ && mInstallInfo.packageName.equals(cn.getPackageName())) {
+ si.setInstallProgress(mInstallInfo.progress);
+
+ if (mInstallInfo.state == PackageInstallerCompat.STATUS_FAILED) {
+ // Mark this info as broken.
+ si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
+ }
+ updates.add(si);
+ }
+ }
+ }
+
+ for (LauncherAppWidgetInfo widget : dataModel.appWidgets) {
+ if (widget.providerName.getPackageName().equals(mInstallInfo.packageName)) {
+ widget.installProgress = mInstallInfo.progress;
+ updates.add(widget);
+ }
+ }
+
+ if (!updates.isEmpty()) {
+ scheduleCallbackTask(new CallbackTask() {
+ @Override
+ public void execute(Callbacks callbacks) {
+ callbacks.bindRestoreItemsChange(updates);
+ }
+ });
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/PackageItemInfo.java b/src/com/android/launcher3/model/PackageItemInfo.java
index c86ba86..baeaa94 100644
--- a/src/com/android/launcher3/model/PackageItemInfo.java
+++ b/src/com/android/launcher3/model/PackageItemInfo.java
@@ -16,37 +16,19 @@
package com.android.launcher3.model;
-import android.graphics.Bitmap;
-
-import com.android.launcher3.ItemInfo;
+import com.android.launcher3.ItemInfoWithIcon;
/**
* Represents a {@link Package} in the widget tray section.
*/
-public class PackageItemInfo extends ItemInfo {
+public class PackageItemInfo extends ItemInfoWithIcon {
/**
- * A bitmap version of the application icon.
- */
- public Bitmap iconBitmap;
-
- /**
- * Indicates whether we're using a low res icon.
- */
- public boolean usingLowResIcon;
-
- /**
- * Package name of the {@link ItemInfo}.
+ * Package name of the {@link PackageItemInfo}.
*/
public String packageName;
- /**
- * Character that is used as a section name for the {@link ItemInfo#title}.
- * (e.g., "G" will be stored if title is "Google")
- */
- public String titleSectionName;
-
- PackageItemInfo(String packageName) {
+ public PackageItemInfo(String packageName) {
this.packageName = packageName;
}
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
new file mode 100644
index 0000000..f03c9c7
--- /dev/null
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -0,0 +1,384 @@
+/*
+ * 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.model;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.IconCache;
+import com.android.launcher3.InstallShortcutReceiver;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.util.FlagOp;
+import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.ManagedProfileHeuristic;
+import com.android.launcher3.util.PackageUserKey;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * Handles updates due to changes in package manager (app installed/updated/removed)
+ * or when a user availability changes.
+ */
+public class PackageUpdatedTask extends ExtendedModelTask {
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = "PackageUpdatedTask";
+
+ public static final int OP_NONE = 0;
+ public static final int OP_ADD = 1;
+ public static final int OP_UPDATE = 2;
+ public static final int OP_REMOVE = 3; // uninstalled
+ public static final int OP_UNAVAILABLE = 4; // external media unmounted
+ public static final int OP_SUSPEND = 5; // package suspended
+ public static final int OP_UNSUSPEND = 6; // package unsuspended
+ public static final int OP_USER_AVAILABILITY_CHANGE = 7; // user available/unavailable
+
+ private final int mOp;
+ private final UserHandle mUser;
+ private final String[] mPackages;
+
+ public PackageUpdatedTask(int op, UserHandle user, String... packages) {
+ mOp = op;
+ mUser = user;
+ mPackages = packages;
+ }
+
+ @Override
+ public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {
+ final Context context = app.getContext();
+ final IconCache iconCache = app.getIconCache();
+
+ final String[] packages = mPackages;
+ final int N = packages.length;
+ FlagOp flagOp = FlagOp.NO_OP;
+ final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages));
+ ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packageSet, mUser);
+ switch (mOp) {
+ case OP_ADD: {
+ for (int i = 0; i < N; i++) {
+ if (DEBUG) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
+ iconCache.updateIconsForPkg(packages[i], mUser);
+ appsList.addPackage(context, packages[i], mUser);
+ }
+
+ ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
+ if (heuristic != null) {
+ heuristic.processPackageAdd(mPackages);
+ }
+ break;
+ }
+ case OP_UPDATE:
+ for (int i = 0; i < N; i++) {
+ if (DEBUG) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
+ iconCache.updateIconsForPkg(packages[i], mUser);
+ appsList.updatePackage(context, packages[i], mUser);
+ app.getWidgetCache().removePackage(packages[i], mUser);
+ }
+ // Since package was just updated, the target must be available now.
+ flagOp = FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
+ break;
+ case OP_REMOVE: {
+ ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
+ if (heuristic != null) {
+ heuristic.processPackageRemoved(mPackages);
+ }
+ for (int i = 0; i < N; i++) {
+ iconCache.removeIconsForPkg(packages[i], mUser);
+ }
+ // Fall through
+ }
+ case OP_UNAVAILABLE:
+ for (int i = 0; i < N; i++) {
+ if (DEBUG) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
+ appsList.removePackage(packages[i], mUser);
+ app.getWidgetCache().removePackage(packages[i], mUser);
+ }
+ flagOp = FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
+ break;
+ case OP_SUSPEND:
+ case OP_UNSUSPEND:
+ flagOp = mOp == OP_SUSPEND ?
+ FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED) :
+ FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED);
+ if (DEBUG) Log.d(TAG, "mAllAppsList.(un)suspend " + N);
+ appsList.updateDisabledFlags(matcher, flagOp);
+ break;
+ case OP_USER_AVAILABILITY_CHANGE:
+ flagOp = UserManagerCompat.getInstance(context).isQuietModeEnabled(mUser)
+ ? FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER)
+ : FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER);
+ // We want to update all packages for this user.
+ matcher = ItemInfoMatcher.ofUser(mUser);
+ appsList.updateDisabledFlags(matcher, flagOp);
+ break;
+ }
+
+ ArrayList<AppInfo> added = null;
+ ArrayList<AppInfo> modified = null;
+ final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
+
+ if (appsList.added.size() > 0) {
+ added = new ArrayList<>(appsList.added);
+ appsList.added.clear();
+ }
+ if (appsList.modified.size() > 0) {
+ modified = new ArrayList<>(appsList.modified);
+ appsList.modified.clear();
+ }
+ if (appsList.removed.size() > 0) {
+ removedApps.addAll(appsList.removed);
+ appsList.removed.clear();
+ }
+
+ final HashMap<ComponentName, AppInfo> addedOrUpdatedApps = new HashMap<>();
+
+ if (added != null) {
+ final ArrayList<AppInfo> addedApps = added;
+ scheduleCallbackTask(new CallbackTask() {
+ @Override
+ public void execute(Callbacks callbacks) {
+ callbacks.bindAppsAdded(null, null, null, addedApps);
+ }
+ });
+ for (AppInfo ai : added) {
+ addedOrUpdatedApps.put(ai.componentName, ai);
+ }
+ }
+
+ if (modified != null) {
+ final ArrayList<AppInfo> modifiedFinal = modified;
+ for (AppInfo ai : modified) {
+ addedOrUpdatedApps.put(ai.componentName, ai);
+ }
+ scheduleCallbackTask(new CallbackTask() {
+ @Override
+ public void execute(Callbacks callbacks) {
+ callbacks.bindAppsUpdated(modifiedFinal);
+ }
+ });
+ }
+
+ // Update shortcut infos
+ if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
+ final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
+ final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<>();
+ final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<>();
+
+ synchronized (dataModel) {
+ for (ItemInfo info : dataModel.itemsIdMap) {
+ if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
+ ShortcutInfo si = (ShortcutInfo) info;
+ boolean infoUpdated = false;
+ boolean shortcutUpdated = false;
+
+ // Update shortcuts which use iconResource.
+ if ((si.iconResource != null)
+ && packageSet.contains(si.iconResource.packageName)) {
+ Bitmap icon = LauncherIcons.createIconBitmap(si.iconResource, context);
+ if (icon != null) {
+ si.iconBitmap = icon;
+ infoUpdated = true;
+ }
+ }
+
+ ComponentName cn = si.getTargetComponent();
+ if (cn != null && matcher.matches(si, cn)) {
+ AppInfo appInfo = addedOrUpdatedApps.get(cn);
+
+ if (si.isPromise() && mOp == OP_ADD) {
+ if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
+ // Auto install icon
+ PackageManager pm = context.getPackageManager();
+ ResolveInfo matched = pm.resolveActivity(
+ new Intent(Intent.ACTION_MAIN)
+ .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
+ PackageManager.MATCH_DEFAULT_ONLY);
+ if (matched == null) {
+ // Try to find the best match activity.
+ Intent intent = pm.getLaunchIntentForPackage(
+ cn.getPackageName());
+ if (intent != null) {
+ cn = intent.getComponent();
+ appInfo = addedOrUpdatedApps.get(cn);
+ }
+
+ if ((intent == null) || (appInfo == null)) {
+ removedShortcuts.add(si);
+ continue;
+ }
+ si.intent = intent;
+ }
+ }
+
+ si.status = ShortcutInfo.DEFAULT;
+ infoUpdated = true;
+ if (si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
+ iconCache.getTitleAndIcon(si, si.usingLowResIcon);
+ }
+ }
+
+ if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
+ && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+ iconCache.getTitleAndIcon(si, si.usingLowResIcon);
+ infoUpdated = true;
+ }
+
+ int oldDisabledFlags = si.isDisabled;
+ si.isDisabled = flagOp.apply(si.isDisabled);
+ if (si.isDisabled != oldDisabledFlags) {
+ shortcutUpdated = true;
+ }
+ }
+
+ if (infoUpdated || shortcutUpdated) {
+ updatedShortcuts.add(si);
+ }
+ if (infoUpdated) {
+ getModelWriter().updateItemInDatabase(si);
+ }
+ } else if (info instanceof LauncherAppWidgetInfo && mOp == OP_ADD) {
+ LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
+ if (mUser.equals(widgetInfo.user)
+ && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
+ && packageSet.contains(widgetInfo.providerName.getPackageName())) {
+ widgetInfo.restoreStatus &=
+ ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY &
+ ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
+
+ // adding this flag ensures that launcher shows 'click to setup'
+ // if the widget has a config activity. In case there is no config
+ // activity, it will be marked as 'restored' during bind.
+ widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
+
+ widgets.add(widgetInfo);
+ getModelWriter().updateItemInDatabase(widgetInfo);
+ }
+ }
+ }
+ }
+
+ bindUpdatedShortcuts(updatedShortcuts, removedShortcuts, mUser);
+ if (!removedShortcuts.isEmpty()) {
+ getModelWriter().deleteItemsFromDatabase(removedShortcuts);
+ }
+
+ if (!widgets.isEmpty()) {
+ scheduleCallbackTask(new CallbackTask() {
+ @Override
+ public void execute(Callbacks callbacks) {
+ callbacks.bindWidgetsRestored(widgets);
+ }
+ });
+ }
+ }
+
+ final HashSet<String> removedPackages = new HashSet<>();
+ final HashSet<ComponentName> removedComponents = new HashSet<>();
+ if (mOp == OP_REMOVE) {
+ // Mark all packages in the broadcast to be removed
+ Collections.addAll(removedPackages, packages);
+
+ // No need to update the removedComponents as
+ // removedPackages is a super-set of removedComponents
+ } else if (mOp == OP_UPDATE) {
+ // Mark disabled packages in the broadcast to be removed
+ final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+ for (int i=0; i<N; i++) {
+ if (!launcherApps.isPackageEnabledForProfile(packages[i], mUser)) {
+ removedPackages.add(packages[i]);
+ }
+ }
+
+ // Update removedComponents as some components can get removed during package update
+ for (AppInfo info : removedApps) {
+ removedComponents.add(info.componentName);
+ }
+ }
+
+ if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
+ getModelWriter().deleteItemsFromDatabase(
+ ItemInfoMatcher.ofPackages(removedPackages, mUser));
+ getModelWriter().deleteItemsFromDatabase(
+ ItemInfoMatcher.ofComponents(removedComponents, mUser));
+
+ // Remove any queued items from the install queue
+ InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser);
+
+ // Call the components-removed callback
+ scheduleCallbackTask(new CallbackTask() {
+ @Override
+ public void execute(Callbacks callbacks) {
+ callbacks.bindWorkspaceComponentsRemoved(
+ removedPackages, removedComponents, mUser);
+ }
+ });
+ }
+
+ if (!removedApps.isEmpty()) {
+ // Remove corresponding apps from All-Apps
+ scheduleCallbackTask(new CallbackTask() {
+ @Override
+ public void execute(Callbacks callbacks) {
+ callbacks.bindAppInfosRemoved(removedApps);
+ }
+ });
+ }
+
+ // 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.isAtLeastO() && mOp == OP_ADD) {
+ // Load widgets for the new package.
+ for (int i = 0; i < N; i++) {
+ LauncherModel model = app.getModel();
+ model.refreshAndBindWidgetsAndShortcuts(
+ model.getCallback(), false /* bindFirst */,
+ new PackageUserKey(packages[i], mUser) /* packageUser */);
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/SdCardAvailableReceiver.java b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
new file mode 100644
index 0000000..278669b
--- /dev/null
+++ b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
@@ -0,0 +1,87 @@
+/*
+ * 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.model;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.util.MultiHashMap;
+import com.android.launcher3.util.PackageManagerHelper;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Map.Entry;
+
+/**
+ * Helper class to re-query app status when SD-card becomes available.
+ *
+ * During first load, just after reboot, some apps on sdcard might not be available immediately due
+ * to some race conditions in the system. We wait for ACTION_BOOT_COMPLETED and process such
+ * apps again.
+ */
+public class SdCardAvailableReceiver extends BroadcastReceiver {
+
+ private final LauncherModel mModel;
+ private final Context mContext;
+ private final MultiHashMap<UserHandle, String> mPackages;
+
+ public SdCardAvailableReceiver(LauncherModel model, Context context,
+ MultiHashMap<UserHandle, String> packages) {
+ mModel = model;
+ mContext = context;
+ mPackages = packages;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+ final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
+ for (Entry<UserHandle, ArrayList<String>> entry : mPackages.entrySet()) {
+ UserHandle user = entry.getKey();
+
+ final ArrayList<String> packagesRemoved = new ArrayList<>();
+ final ArrayList<String> packagesUnavailable = new ArrayList<>();
+
+ for (String pkg : new HashSet<>(entry.getValue())) {
+ if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
+ if (pmHelper.isAppOnSdcard(pkg, user)) {
+ packagesUnavailable.add(pkg);
+ } else {
+ packagesRemoved.add(pkg);
+ }
+ }
+ }
+ if (!packagesRemoved.isEmpty()) {
+ mModel.onPackagesRemoved(user,
+ packagesRemoved.toArray(new String[packagesRemoved.size()]));
+ }
+ if (!packagesUnavailable.isEmpty()) {
+ mModel.onPackagesUnavailable(
+ packagesUnavailable.toArray(new String[packagesUnavailable.size()]),
+ user, false);
+ }
+ }
+
+ // Unregister the broadcast receiver, just in case
+ mContext.unregisterReceiver(this);
+ }
+}
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
new file mode 100644
index 0000000..d8a429c
--- /dev/null
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -0,0 +1,116 @@
+/*
+ * 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.model;
+
+import android.content.Context;
+import android.os.UserHandle;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.util.MultiHashMap;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Handles changes due to shortcut manager updates (deep shortcut changes)
+ */
+public class ShortcutsChangedTask extends ExtendedModelTask {
+
+ private final String mPackageName;
+ private final List<ShortcutInfoCompat> mShortcuts;
+ private final UserHandle mUser;
+ private final boolean mUpdateIdMap;
+
+ public ShortcutsChangedTask(String packageName, List<ShortcutInfoCompat> shortcuts,
+ UserHandle user, boolean updateIdMap) {
+ mPackageName = packageName;
+ mShortcuts = shortcuts;
+ mUser = user;
+ mUpdateIdMap = updateIdMap;
+ }
+
+ @Override
+ public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+ final Context context = app.getContext();
+ DeepShortcutManager deepShortcutManager = DeepShortcutManager.getInstance(context);
+ deepShortcutManager.onShortcutsChanged(mShortcuts);
+
+ // Find ShortcutInfo's that have changed on the workspace.
+ final ArrayList<ShortcutInfo> removedShortcutInfos = new ArrayList<>();
+ MultiHashMap<String, ShortcutInfo> idsToWorkspaceShortcutInfos = new MultiHashMap<>();
+ for (ItemInfo itemInfo : dataModel.itemsIdMap) {
+ if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+ ShortcutInfo si = (ShortcutInfo) itemInfo;
+ if (si.getIntent().getPackage().equals(mPackageName)
+ && si.user.equals(mUser)) {
+ idsToWorkspaceShortcutInfos.addToList(si.getDeepShortcutId(), si);
+ }
+ }
+ }
+
+ final ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
+ if (!idsToWorkspaceShortcutInfos.isEmpty()) {
+ // Update the workspace to reflect the changes to updated shortcuts residing on it.
+ List<ShortcutInfoCompat> shortcuts = deepShortcutManager.queryForFullDetails(
+ mPackageName, new ArrayList<>(idsToWorkspaceShortcutInfos.keySet()), mUser);
+ for (ShortcutInfoCompat fullDetails : shortcuts) {
+ List<ShortcutInfo> shortcutInfos = idsToWorkspaceShortcutInfos
+ .remove(fullDetails.getId());
+ if (!fullDetails.isPinned()) {
+ // The shortcut was previously pinned but is no longer, so remove it from
+ // the workspace and our pinned shortcut counts.
+ // Note that we put this check here, after querying for full details,
+ // because there's a possible race condition between pinning and
+ // receiving this callback.
+ removedShortcutInfos.addAll(shortcutInfos);
+ continue;
+ }
+ for (ShortcutInfo shortcutInfo : shortcutInfos) {
+ shortcutInfo.updateFromDeepShortcutInfo(fullDetails, context);
+ shortcutInfo.iconBitmap =
+ LauncherIcons.createShortcutIcon(fullDetails, context);
+ updatedShortcutInfos.add(shortcutInfo);
+ }
+ }
+ }
+
+ // If there are still entries in idsToWorkspaceShortcutInfos, that means that
+ // the corresponding shortcuts weren't passed in onShortcutsChanged(). This
+ // means they were cleared, so we remove and unpin them now.
+ for (String id : idsToWorkspaceShortcutInfos.keySet()) {
+ removedShortcutInfos.addAll(idsToWorkspaceShortcutInfos.get(id));
+ }
+
+ bindUpdatedShortcuts(updatedShortcutInfos, removedShortcutInfos, mUser);
+ if (!removedShortcutInfos.isEmpty()) {
+ getModelWriter().deleteItemsFromDatabase(removedShortcutInfos);
+ }
+
+ if (mUpdateIdMap) {
+ // Update the deep shortcut map if the list of ids has changed for an activity.
+ dataModel.updateDeepShortcutMap(mPackageName, mUser, mShortcuts);
+ bindDeepShortcuts(dataModel);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
new file mode 100644
index 0000000..363f1ee
--- /dev/null
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -0,0 +1,115 @@
+/*
+ * 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.model;
+
+import android.content.Context;
+import android.os.UserHandle;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.util.ComponentKey;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Task to handle changing of lock state of the user
+ */
+public class UserLockStateChangedTask extends ExtendedModelTask {
+
+ private final UserHandle mUser;
+
+ public UserLockStateChangedTask(UserHandle user) {
+ mUser = user;
+ }
+
+ @Override
+ public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+ Context context = app.getContext();
+ boolean isUserUnlocked = UserManagerCompat.getInstance(context).isUserUnlocked(mUser);
+ DeepShortcutManager deepShortcutManager = DeepShortcutManager.getInstance(context);
+
+ HashMap<ShortcutKey, ShortcutInfoCompat> pinnedShortcuts = new HashMap<>();
+ if (isUserUnlocked) {
+ List<ShortcutInfoCompat> shortcuts =
+ deepShortcutManager.queryForPinnedShortcuts(null, mUser);
+ if (deepShortcutManager.wasLastCallSuccess()) {
+ for (ShortcutInfoCompat shortcut : shortcuts) {
+ pinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut);
+ }
+ } else {
+ // Shortcut manager can fail due to some race condition when the lock state
+ // changes too frequently. For the purpose of the update,
+ // consider it as still locked.
+ isUserUnlocked = false;
+ }
+ }
+
+ // Update the workspace to reflect the changes to updated shortcuts residing on it.
+ ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
+ ArrayList<ShortcutInfo> deletedShortcutInfos = new ArrayList<>();
+ for (ItemInfo itemInfo : dataModel.itemsIdMap) {
+ if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+ && mUser.equals(itemInfo.user)) {
+ ShortcutInfo si = (ShortcutInfo) itemInfo;
+ if (isUserUnlocked) {
+ ShortcutInfoCompat shortcut = pinnedShortcuts.get(ShortcutKey.fromItemInfo(si));
+ // We couldn't verify the shortcut during loader. If its no longer available
+ // (probably due to clear data), delete the workspace item as well
+ if (shortcut == null) {
+ deletedShortcutInfos.add(si);
+ continue;
+ }
+ si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
+ si.updateFromDeepShortcutInfo(shortcut, context);
+ si.iconBitmap = LauncherIcons.createShortcutIcon(shortcut, context);
+ } else {
+ si.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
+ }
+ updatedShortcutInfos.add(si);
+ }
+ }
+ bindUpdatedShortcuts(updatedShortcutInfos, deletedShortcutInfos, mUser);
+ if (!deletedShortcutInfos.isEmpty()) {
+ getModelWriter().deleteItemsFromDatabase(deletedShortcutInfos);
+ }
+
+ // Remove shortcut id map for that user
+ Iterator<ComponentKey> keysIter = dataModel.deepShortcutMap.keySet().iterator();
+ while (keysIter.hasNext()) {
+ if (keysIter.next().user.equals(mUser)) {
+ keysIter.remove();
+ }
+ }
+
+ if (isUserUnlocked) {
+ dataModel.updateDeepShortcutMap(
+ null, mUser, deepShortcutManager.queryForAllShortcuts(mUser));
+ }
+ bindDeepShortcuts(dataModel);
+ }
+}
diff --git a/src/com/android/launcher3/model/WidgetItem.java b/src/com/android/launcher3/model/WidgetItem.java
index 0d7ba1e..1e96dec 100644
--- a/src/com/android/launcher3/model/WidgetItem.java
+++ b/src/com/android/launcher3/model/WidgetItem.java
@@ -1,16 +1,14 @@
package com.android.launcher3.model;
-import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
+import android.os.Process;
+import android.os.UserHandle;
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.AppWidgetManagerCompat;
-import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.ShortcutConfigActivityInfo;
import com.android.launcher3.util.ComponentKey;
import java.text.Collator;
@@ -22,33 +20,32 @@
*/
public class WidgetItem extends ComponentKey implements Comparable<WidgetItem> {
- private static UserHandleCompat sMyUserHandle;
+ private static UserHandle sMyUserHandle;
private static Collator sCollator;
public final LauncherAppWidgetProviderInfo widgetInfo;
- public final ActivityInfo activityInfo;
+ public final ShortcutConfigActivityInfo activityInfo;
public final String label;
public final int spanX, spanY;
- public WidgetItem(LauncherAppWidgetProviderInfo info, AppWidgetManagerCompat widgetManager) {
- super(info.provider, widgetManager.getUser(info));
+ public WidgetItem(LauncherAppWidgetProviderInfo info, PackageManager pm,
+ InvariantDeviceProfile idp) {
+ super(info.provider, info.getProfile());
- label = Utilities.trim(widgetManager.loadLabel(info));
+ label = Utilities.trim(info.getLabel(pm));
widgetInfo = info;
activityInfo = null;
- InvariantDeviceProfile idv = LauncherAppState.getInstance().getInvariantDeviceProfile();
- spanX = Math.min(info.spanX, idv.numColumns);
- spanY = Math.min(info.spanY, idv.numRows);
+ spanX = Math.min(info.spanX, idp.numColumns);
+ spanY = Math.min(info.spanY, idp.numRows);
}
- public WidgetItem(ResolveInfo info, PackageManager pm) {
- super(new ComponentName(info.activityInfo.packageName, info.activityInfo.name),
- UserHandleCompat.myUserHandle());
- label = Utilities.trim(info.loadLabel(pm));
+ public WidgetItem(ShortcutConfigActivityInfo info) {
+ super(info.getComponent(), info.getUser());
+ label = Utilities.trim(info.getLabel());
widgetInfo = null;
- activityInfo = info.activityInfo;
+ activityInfo = info;
spanX = spanY = 1;
}
@@ -56,7 +53,7 @@
public int compareTo(WidgetItem another) {
if (sMyUserHandle == null) {
// Delay these object creation until required.
- sMyUserHandle = UserHandleCompat.myUserHandle();
+ sMyUserHandle = Process.myUserHandle();
sCollator = Collator.getInstance();
}
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index b2a94bb..e5215c7 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -3,29 +3,29 @@
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.DeadObjectException;
-import android.os.TransactionTooLargeException;
+import android.os.Process;
+import android.os.UserHandle;
+import android.support.annotation.Nullable;
import android.util.Log;
import com.android.launcher3.AppFilter;
import com.android.launcher3.IconCache;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.compat.AlphabeticIndexCompat;
+import com.android.launcher3.Utilities;
import com.android.launcher3.compat.AppWidgetManagerCompat;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.ShortcutConfigActivityInfo;
import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.util.MultiHashMap;
+import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.HashMap;
-import java.util.List;
+import java.util.Iterator;
/**
* Widgets data model that is used by the adapters of the widget views and controllers.
@@ -37,93 +37,53 @@
private static final String TAG = "WidgetsModel";
private static final boolean DEBUG = false;
- /* List of packages that is tracked by this model. */
- private final ArrayList<PackageItemInfo> mPackageItemInfos;
-
/* Map of widgets and shortcuts that are tracked per package. */
- private final HashMap<PackageItemInfo, ArrayList<WidgetItem>> mWidgetsList;
+ private final MultiHashMap<PackageItemInfo, WidgetItem> mWidgetsList;
- private final AppWidgetManagerCompat mAppWidgetMgr;
- private final Comparator<ItemInfo> mAppNameComparator;
private final IconCache mIconCache;
private final AppFilter mAppFilter;
- private final AlphabeticIndexCompat mIndexer;
- private ArrayList<WidgetItem> mRawList;
-
- public WidgetsModel(Context context, IconCache iconCache, AppFilter appFilter) {
- mAppWidgetMgr = AppWidgetManagerCompat.getInstance(context);
- mAppNameComparator = (new AppNameComparator(context)).getAppInfoComparator();
+ public WidgetsModel(IconCache iconCache, AppFilter appFilter) {
mIconCache = iconCache;
mAppFilter = appFilter;
- mIndexer = new AlphabeticIndexCompat(context);
- mPackageItemInfos = new ArrayList<>();
- mWidgetsList = new HashMap<>();
-
- mRawList = new ArrayList<>();
+ mWidgetsList = new MultiHashMap<>();
}
- @SuppressWarnings("unchecked")
- private WidgetsModel(WidgetsModel model) {
- mAppWidgetMgr = model.mAppWidgetMgr;
- mPackageItemInfos = (ArrayList<PackageItemInfo>) model.mPackageItemInfos.clone();
- mWidgetsList = (HashMap<PackageItemInfo, ArrayList<WidgetItem>>) model.mWidgetsList.clone();
- mAppNameComparator = model.mAppNameComparator;
- mIconCache = model.mIconCache;
- mAppFilter = model.mAppFilter;
- mIndexer = model.mIndexer;
- mRawList = (ArrayList<WidgetItem>) model.mRawList.clone();
- }
-
- // Access methods that may be deleted if the private fields are made package-private.
- public int getPackageSize() {
- return mPackageItemInfos.size();
- }
-
- // Access methods that may be deleted if the private fields are made package-private.
- public PackageItemInfo getPackageItemInfo(int pos) {
- if (pos >= mPackageItemInfos.size() || pos < 0) {
- return null;
- }
- return mPackageItemInfos.get(pos);
- }
-
- public List<WidgetItem> getSortedWidgets(int pos) {
- return mWidgetsList.get(mPackageItemInfos.get(pos));
- }
-
- public ArrayList<WidgetItem> getRawList() {
- return mRawList;
+ public MultiHashMap<PackageItemInfo, WidgetItem> getWidgetsMap() {
+ return mWidgetsList;
}
public boolean isEmpty() {
- return mRawList.isEmpty();
+ return mWidgetsList.isEmpty();
}
- public WidgetsModel updateAndClone(Context context) {
+ /**
+ * @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise
+ * only widgets and shortcuts associated with the package/user are.
+ */
+ public ArrayList<WidgetItem> update(Context context, @Nullable PackageUserKey packageUser) {
Preconditions.assertWorkerThread();
+ final ArrayList<WidgetItem> widgetsAndShortcuts = new ArrayList<>();
try {
- final ArrayList<WidgetItem> widgetsAndShortcuts = new ArrayList<>();
+ PackageManager pm = context.getPackageManager();
+ InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
+
// Widgets
AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(context);
- for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders()) {
- widgetsAndShortcuts.add(new WidgetItem(
- LauncherAppWidgetProviderInfo.fromProviderInfo(context, widgetInfo),
- widgetManager));
+ for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(packageUser)) {
+ widgetsAndShortcuts.add(new WidgetItem(LauncherAppWidgetProviderInfo
+ .fromProviderInfo(context, widgetInfo), pm, idp));
}
// Shortcuts
- PackageManager pm = context.getPackageManager();
- for (ResolveInfo info :
- pm.queryIntentActivities(new Intent(Intent.ACTION_CREATE_SHORTCUT), 0)) {
- widgetsAndShortcuts.add(new WidgetItem(info, pm));
+ for (ShortcutConfigActivityInfo info : LauncherAppsCompat.getInstance(context)
+ .getCustomShortcutActivityList(packageUser)) {
+ widgetsAndShortcuts.add(new WidgetItem(info));
}
- setWidgetsAndShortcuts(widgetsAndShortcuts);
+ setWidgetsAndShortcuts(widgetsAndShortcuts, context, packageUser);
} catch (Exception e) {
- if (!ProviderConfig.IS_DOGFOOD_BUILD &&
- (e.getCause() instanceof TransactionTooLargeException ||
- e.getCause() instanceof DeadObjectException)) {
+ if (!ProviderConfig.IS_DOGFOOD_BUILD && Utilities.isBinderSizeError(e)) {
// the returned value may be incomplete and will not be refreshed until the next
// time Launcher starts.
// TODO: after figuring out a repro step, introduce a dirty bit to check when
@@ -132,11 +92,11 @@
throw e;
}
}
- return clone();
+ return widgetsAndShortcuts;
}
- private void setWidgetsAndShortcuts(ArrayList<WidgetItem> rawWidgetsShortcuts) {
- mRawList = rawWidgetsShortcuts;
+ private void setWidgetsAndShortcuts(ArrayList<WidgetItem> rawWidgetsShortcuts,
+ Context context, @Nullable PackageUserKey packageUser) {
if (DEBUG) {
Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + rawWidgetsShortcuts.size());
}
@@ -146,13 +106,38 @@
HashMap<String, PackageItemInfo> tmpPackageItemInfos = new HashMap<>();
// clear the lists.
- mWidgetsList.clear();
- mPackageItemInfos.clear();
+ if (packageUser == null) {
+ mWidgetsList.clear();
+ } else {
+ // Only clear the widgets for the given package/user.
+ PackageItemInfo packageItem = null;
+ for (PackageItemInfo item : mWidgetsList.keySet()) {
+ if (item.packageName.equals(packageUser.mPackageName)) {
+ packageItem = item;
+ break;
+ }
+ }
+ if (packageItem != null) {
+ // We want to preserve the user that was on the packageItem previously,
+ // so add it to tmpPackageItemInfos here to avoid creating a new entry.
+ tmpPackageItemInfos.put(packageItem.packageName, packageItem);
- InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile();
+ Iterator<WidgetItem> widgetItemIterator = mWidgetsList.get(packageItem).iterator();
+ while (widgetItemIterator.hasNext()) {
+ WidgetItem nextWidget = widgetItemIterator.next();
+ if (nextWidget.componentName.getPackageName().equals(packageUser.mPackageName)
+ && nextWidget.user.equals(packageUser.mUser)) {
+ widgetItemIterator.remove();
+ }
+ }
+ }
+ }
+
+ InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
+ UserHandle myUser = Process.myUserHandle();
// add and update.
- for (WidgetItem item: rawWidgetsShortcuts) {
+ for (WidgetItem item : rawWidgetsShortcuts) {
if (item.widgetInfo != null) {
// Ensure that all widgets we show can be added on a workspace of this size
int minSpanX = Math.min(item.widgetInfo.spanX, item.widgetInfo.minSpanX);
@@ -167,7 +152,7 @@
}
}
- if (mAppFilter != null && !mAppFilter.shouldShowApp(item.componentName)) {
+ if (!mAppFilter.shouldShowApp(item.componentName)) {
if (DEBUG) {
Log.d(TAG, String.format("%s is filtered and not added to the widget tray.",
item.componentName));
@@ -177,43 +162,20 @@
String packageName = item.componentName.getPackageName();
PackageItemInfo pInfo = tmpPackageItemInfos.get(packageName);
- ArrayList<WidgetItem> widgetsShortcutsList = mWidgetsList.get(pInfo);
-
- if (widgetsShortcutsList == null) {
- widgetsShortcutsList = new ArrayList<>();
-
+ if (pInfo == null) {
pInfo = new PackageItemInfo(packageName);
+ pInfo.user = item.user;
tmpPackageItemInfos.put(packageName, pInfo);
-
- mPackageItemInfos.add(pInfo);
- mWidgetsList.put(pInfo, widgetsShortcutsList);
+ } else if (!myUser.equals(pInfo.user)) {
+ // Keep updating the user, until we get the primary user.
+ pInfo.user = item.user;
}
-
- widgetsShortcutsList.add(item);
+ mWidgetsList.addToList(pInfo, item);
}
// Update each package entry
- for (PackageItemInfo p : mPackageItemInfos) {
- ArrayList<WidgetItem> widgetsShortcutsList = mWidgetsList.get(p);
- Collections.sort(widgetsShortcutsList);
-
- // Update the package entry based on the first item.
- p.user = widgetsShortcutsList.get(0).user;
+ for (PackageItemInfo p : tmpPackageItemInfos.values()) {
mIconCache.getTitleAndIconForApp(p, true /* userLowResIcon */);
- p.titleSectionName = mIndexer.computeSectionName(p.title);
}
-
- // sort the package entries.
- Collections.sort(mPackageItemInfos, mAppNameComparator);
- }
-
- /**
- * Create a snapshot of the widgets model.
- * <p>
- * Usage case: view binding without being modified from package updates.
- */
- @Override
- public WidgetsModel clone(){
- return new WidgetsModel(this);
}
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/notification/FlingAnimationUtils.java b/src/com/android/launcher3/notification/FlingAnimationUtils.java
new file mode 100644
index 0000000..a1f7e49
--- /dev/null
+++ b/src/com/android/launcher3/notification/FlingAnimationUtils.java
@@ -0,0 +1,356 @@
+/*
+ * 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.animation.Animator;
+import android.content.Context;
+import android.view.ViewPropertyAnimator;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+/**
+ * Utility class to calculate general fling animation when the finger is released.
+ *
+ * This class was copied from com.android.systemui.statusbar.
+ */
+public class FlingAnimationUtils {
+
+ private static final float LINEAR_OUT_SLOW_IN_X2 = 0.35f;
+ private static final float LINEAR_OUT_SLOW_IN_X2_MAX = 0.68f;
+ private static final float LINEAR_OUT_FASTER_IN_X2 = 0.5f;
+ private static final float LINEAR_OUT_FASTER_IN_Y2_MIN = 0.4f;
+ private static final float LINEAR_OUT_FASTER_IN_Y2_MAX = 0.5f;
+ private static final float MIN_VELOCITY_DP_PER_SECOND = 250;
+ private static final float HIGH_VELOCITY_DP_PER_SECOND = 3000;
+
+ private static final float LINEAR_OUT_SLOW_IN_START_GRADIENT = 0.75f;
+ private final float mSpeedUpFactor;
+ private final float mY2;
+
+ private float mMinVelocityPxPerSecond;
+ private float mMaxLengthSeconds;
+ private float mHighVelocityPxPerSecond;
+ private float mLinearOutSlowInX2;
+
+ private AnimatorProperties mAnimatorProperties = new AnimatorProperties();
+ private PathInterpolator mInterpolator;
+ private float mCachedStartGradient = -1;
+ private float mCachedVelocityFactor = -1;
+
+ public FlingAnimationUtils(Context ctx, float maxLengthSeconds) {
+ this(ctx, maxLengthSeconds, 0.0f);
+ }
+
+ /**
+ * @param maxLengthSeconds the longest duration an animation can become in seconds
+ * @param speedUpFactor a factor from 0 to 1 how much the slow down should be shifted towards
+ * the end of the animation. 0 means it's at the beginning and no
+ * acceleration will take place.
+ */
+ public FlingAnimationUtils(Context ctx, float maxLengthSeconds, float speedUpFactor) {
+ this(ctx, maxLengthSeconds, speedUpFactor, -1.0f, 1.0f);
+ }
+
+ /**
+ * @param maxLengthSeconds the longest duration an animation can become in seconds
+ * @param speedUpFactor a factor from 0 to 1 how much the slow down should be shifted towards
+ * the end of the animation. 0 means it's at the beginning and no
+ * acceleration will take place.
+ * @param x2 the x value to take for the second point of the bezier spline. If a value below 0
+ * is provided, the value is automatically calculated.
+ * @param y2 the y value to take for the second point of the bezier spline
+ */
+ public FlingAnimationUtils(Context ctx, float maxLengthSeconds, float speedUpFactor, float x2,
+ float y2) {
+ mMaxLengthSeconds = maxLengthSeconds;
+ mSpeedUpFactor = speedUpFactor;
+ if (x2 < 0) {
+ mLinearOutSlowInX2 = interpolate(LINEAR_OUT_SLOW_IN_X2,
+ LINEAR_OUT_SLOW_IN_X2_MAX,
+ mSpeedUpFactor);
+ } else {
+ mLinearOutSlowInX2 = x2;
+ }
+ mY2 = y2;
+
+ mMinVelocityPxPerSecond
+ = MIN_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density;
+ mHighVelocityPxPerSecond
+ = HIGH_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density;
+ }
+
+ private static float interpolate(float start, float end, float amount) {
+ return start * (1.0f - amount) + end * amount;
+ }
+
+ /**
+ * Applies the interpolator and length to the animator, such that the fling animation is
+ * consistent with the finger motion.
+ *
+ * @param animator the animator to apply
+ * @param currValue the current value
+ * @param endValue the end value of the animator
+ * @param velocity the current velocity of the motion
+ */
+ public void apply(Animator animator, float currValue, float endValue, float velocity) {
+ apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
+ }
+
+ /**
+ * Applies the interpolator and length to the animator, such that the fling animation is
+ * consistent with the finger motion.
+ *
+ * @param animator the animator to apply
+ * @param currValue the current value
+ * @param endValue the end value of the animator
+ * @param velocity the current velocity of the motion
+ */
+ public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
+ float velocity) {
+ apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
+ }
+
+ /**
+ * Applies the interpolator and length to the animator, such that the fling animation is
+ * consistent with the finger motion.
+ *
+ * @param animator the animator to apply
+ * @param currValue the current value
+ * @param endValue the end value of the animator
+ * @param velocity the current velocity of the motion
+ * @param maxDistance the maximum distance for this interaction; the maximum animation length
+ * gets multiplied by the ratio between the actual distance and this value
+ */
+ public void apply(Animator animator, float currValue, float endValue, float velocity,
+ float maxDistance) {
+ AnimatorProperties properties = getProperties(currValue, endValue, velocity,
+ maxDistance);
+ animator.setDuration(properties.duration);
+ animator.setInterpolator(properties.interpolator);
+ }
+
+ /**
+ * Applies the interpolator and length to the animator, such that the fling animation is
+ * consistent with the finger motion.
+ *
+ * @param animator the animator to apply
+ * @param currValue the current value
+ * @param endValue the end value of the animator
+ * @param velocity the current velocity of the motion
+ * @param maxDistance the maximum distance for this interaction; the maximum animation length
+ * gets multiplied by the ratio between the actual distance and this value
+ */
+ public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
+ float velocity, float maxDistance) {
+ AnimatorProperties properties = getProperties(currValue, endValue, velocity,
+ maxDistance);
+ animator.setDuration(properties.duration);
+ animator.setInterpolator(properties.interpolator);
+ }
+
+ private AnimatorProperties getProperties(float currValue,
+ float endValue, float velocity, float maxDistance) {
+ float maxLengthSeconds = (float) (mMaxLengthSeconds
+ * Math.sqrt(Math.abs(endValue - currValue) / maxDistance));
+ float diff = Math.abs(endValue - currValue);
+ float velAbs = Math.abs(velocity);
+ float velocityFactor = mSpeedUpFactor == 0.0f
+ ? 1.0f : Math.min(velAbs / HIGH_VELOCITY_DP_PER_SECOND, 1.0f);
+ float startGradient = interpolate(LINEAR_OUT_SLOW_IN_START_GRADIENT,
+ mY2 / mLinearOutSlowInX2, velocityFactor);
+ float durationSeconds = startGradient * diff / velAbs;
+ Interpolator slowInInterpolator = getInterpolator(startGradient, velocityFactor);
+ if (durationSeconds <= maxLengthSeconds) {
+ mAnimatorProperties.interpolator = slowInInterpolator;
+ } else if (velAbs >= mMinVelocityPxPerSecond) {
+
+ // Cross fade between fast-out-slow-in and linear interpolator with current velocity.
+ durationSeconds = maxLengthSeconds;
+ VelocityInterpolator velocityInterpolator
+ = new VelocityInterpolator(durationSeconds, velAbs, diff);
+ InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator(
+ velocityInterpolator, slowInInterpolator, Interpolators.LINEAR_OUT_SLOW_IN);
+ mAnimatorProperties.interpolator = superInterpolator;
+ } else {
+
+ // Just use a normal interpolator which doesn't take the velocity into account.
+ durationSeconds = maxLengthSeconds;
+ mAnimatorProperties.interpolator = Interpolators.FAST_OUT_SLOW_IN;
+ }
+ mAnimatorProperties.duration = (long) (durationSeconds * 1000);
+ return mAnimatorProperties;
+ }
+
+ private Interpolator getInterpolator(float startGradient, float velocityFactor) {
+ if (startGradient != mCachedStartGradient
+ || velocityFactor != mCachedVelocityFactor) {
+ float speedup = mSpeedUpFactor * (1.0f - velocityFactor);
+ mInterpolator = new PathInterpolator(speedup,
+ speedup * startGradient,
+ mLinearOutSlowInX2, mY2);
+ mCachedStartGradient = startGradient;
+ mCachedVelocityFactor = velocityFactor;
+ }
+ return mInterpolator;
+ }
+
+ /**
+ * Applies the interpolator and length to the animator, such that the fling animation is
+ * consistent with the finger motion for the case when the animation is making something
+ * disappear.
+ *
+ * @param animator the animator to apply
+ * @param currValue the current value
+ * @param endValue the end value of the animator
+ * @param velocity the current velocity of the motion
+ * @param maxDistance the maximum distance for this interaction; the maximum animation length
+ * gets multiplied by the ratio between the actual distance and this value
+ */
+ public void applyDismissing(Animator animator, float currValue, float endValue,
+ float velocity, float maxDistance) {
+ AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity,
+ maxDistance);
+ animator.setDuration(properties.duration);
+ animator.setInterpolator(properties.interpolator);
+ }
+
+ /**
+ * Applies the interpolator and length to the animator, such that the fling animation is
+ * consistent with the finger motion for the case when the animation is making something
+ * disappear.
+ *
+ * @param animator the animator to apply
+ * @param currValue the current value
+ * @param endValue the end value of the animator
+ * @param velocity the current velocity of the motion
+ * @param maxDistance the maximum distance for this interaction; the maximum animation length
+ * gets multiplied by the ratio between the actual distance and this value
+ */
+ public void applyDismissing(ViewPropertyAnimator animator, float currValue, float endValue,
+ float velocity, float maxDistance) {
+ AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity,
+ maxDistance);
+ animator.setDuration(properties.duration);
+ animator.setInterpolator(properties.interpolator);
+ }
+
+ private AnimatorProperties getDismissingProperties(float currValue, float endValue,
+ float velocity, float maxDistance) {
+ float maxLengthSeconds = (float) (mMaxLengthSeconds
+ * Math.pow(Math.abs(endValue - currValue) / maxDistance, 0.5f));
+ float diff = Math.abs(endValue - currValue);
+ float velAbs = Math.abs(velocity);
+ float y2 = calculateLinearOutFasterInY2(velAbs);
+
+ float startGradient = y2 / LINEAR_OUT_FASTER_IN_X2;
+ Interpolator mLinearOutFasterIn = new PathInterpolator(0, 0, LINEAR_OUT_FASTER_IN_X2, y2);
+ float durationSeconds = startGradient * diff / velAbs;
+ if (durationSeconds <= maxLengthSeconds) {
+ mAnimatorProperties.interpolator = mLinearOutFasterIn;
+ } else if (velAbs >= mMinVelocityPxPerSecond) {
+
+ // Cross fade between linear-out-faster-in and linear interpolator with current
+ // velocity.
+ durationSeconds = maxLengthSeconds;
+ VelocityInterpolator velocityInterpolator
+ = new VelocityInterpolator(durationSeconds, velAbs, diff);
+ InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator(
+ velocityInterpolator, mLinearOutFasterIn, Interpolators.LINEAR_OUT_SLOW_IN);
+ mAnimatorProperties.interpolator = superInterpolator;
+ } else {
+
+ // Just use a normal interpolator which doesn't take the velocity into account.
+ durationSeconds = maxLengthSeconds;
+ mAnimatorProperties.interpolator = Interpolators.FAST_OUT_LINEAR_IN;
+ }
+ mAnimatorProperties.duration = (long) (durationSeconds * 1000);
+ return mAnimatorProperties;
+ }
+
+ /**
+ * Calculates the y2 control point for a linear-out-faster-in path interpolator depending on the
+ * velocity. The faster the velocity, the more "linear" the interpolator gets.
+ *
+ * @param velocity the velocity of the gesture.
+ * @return the y2 control point for a cubic bezier path interpolator
+ */
+ private float calculateLinearOutFasterInY2(float velocity) {
+ float t = (velocity - mMinVelocityPxPerSecond)
+ / (mHighVelocityPxPerSecond - mMinVelocityPxPerSecond);
+ t = Math.max(0, Math.min(1, t));
+ return (1 - t) * LINEAR_OUT_FASTER_IN_Y2_MIN + t * LINEAR_OUT_FASTER_IN_Y2_MAX;
+ }
+
+ /**
+ * @return the minimum velocity a gesture needs to have to be considered a fling
+ */
+ public float getMinVelocityPxPerSecond() {
+ return mMinVelocityPxPerSecond;
+ }
+
+ /**
+ * An interpolator which interpolates two interpolators with an interpolator.
+ */
+ private static final class InterpolatorInterpolator implements Interpolator {
+
+ private Interpolator mInterpolator1;
+ private Interpolator mInterpolator2;
+ private Interpolator mCrossfader;
+
+ InterpolatorInterpolator(Interpolator interpolator1, Interpolator interpolator2,
+ Interpolator crossfader) {
+ mInterpolator1 = interpolator1;
+ mInterpolator2 = interpolator2;
+ mCrossfader = crossfader;
+ }
+
+ @Override
+ public float getInterpolation(float input) {
+ float t = mCrossfader.getInterpolation(input);
+ return (1 - t) * mInterpolator1.getInterpolation(input)
+ + t * mInterpolator2.getInterpolation(input);
+ }
+ }
+
+ /**
+ * An interpolator which interpolates with a fixed velocity.
+ */
+ private static final class VelocityInterpolator implements Interpolator {
+
+ private float mDurationSeconds;
+ private float mVelocity;
+ private float mDiff;
+
+ private VelocityInterpolator(float durationSeconds, float velocity, float diff) {
+ mDurationSeconds = durationSeconds;
+ mVelocity = velocity;
+ mDiff = diff;
+ }
+
+ @Override
+ public float getInterpolation(float input) {
+ float time = input * mDurationSeconds;
+ return time * mVelocity / mDiff;
+ }
+ }
+
+ private static class AnimatorProperties {
+ Interpolator interpolator;
+ long duration;
+ }
+
+}
diff --git a/src/com/android/launcher3/notification/Interpolators.java b/src/com/android/launcher3/notification/Interpolators.java
new file mode 100644
index 0000000..5c3b22a
--- /dev/null
+++ b/src/com/android/launcher3/notification/Interpolators.java
@@ -0,0 +1,37 @@
+/*
+ * 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
new file mode 100644
index 0000000..1eef743
--- /dev/null
+++ b/src/com/android/launcher3/notification/NotificationFooterLayout.java
@@ -0,0 +1,233 @@
+/*
+ * 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.PropertyListBuilder;
+import com.android.launcher3.anim.PropertyResetListener;
+import com.android.launcher3.popup.PopupContainerWithArrow;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A {@link FrameLayout} that contains only icons of notifications.
+ * If there are more than {@link #MAX_FOOTER_NOTIFICATIONS} icons, we add a "..." overflow.
+ */
+public class NotificationFooterLayout extends FrameLayout {
+
+ public interface IconAnimationEndListener {
+ void onIconAnimationEnd(NotificationInfo animatedNotification);
+ }
+
+ private static final int MAX_FOOTER_NOTIFICATIONS = 5;
+
+ private static final Rect sTempRect = new Rect();
+
+ private final List<NotificationInfo> mNotifications = new ArrayList<>();
+ private final List<NotificationInfo> mOverflowNotifications = new ArrayList<>();
+ private final boolean mRtl;
+
+ FrameLayout.LayoutParams mIconLayoutParams;
+ private View mOverflowEllipsis;
+ private LinearLayout mIconRow;
+ private int mBackgroundColor;
+
+ public NotificationFooterLayout(Context context) {
+ this(context, null, 0);
+ }
+
+ public NotificationFooterLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public NotificationFooterLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ Resources res = getResources();
+ mRtl = Utilities.isRtl(res);
+
+ int iconSize = res.getDimensionPixelSize(R.dimen.notification_footer_icon_size);
+ mIconLayoutParams = new LayoutParams(iconSize, iconSize);
+ mIconLayoutParams.gravity = Gravity.CENTER_VERTICAL;
+ // Compute margin start for each icon such that the icons between the first one
+ // and the ellipsis are evenly spaced out.
+ int paddingEnd = res.getDimensionPixelSize(R.dimen.notification_footer_icon_row_padding);
+ int ellipsisSpace = res.getDimensionPixelSize(R.dimen.horizontal_ellipsis_offset)
+ + res.getDimensionPixelSize(R.dimen.horizontal_ellipsis_size);
+ int footerWidth = res.getDimensionPixelSize(R.dimen.bg_popup_item_width);
+ int availableIconRowSpace = footerWidth - paddingEnd - ellipsisSpace
+ - iconSize * MAX_FOOTER_NOTIFICATIONS;
+ mIconLayoutParams.setMarginStart(availableIconRowSpace / MAX_FOOTER_NOTIFICATIONS);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mOverflowEllipsis = findViewById(R.id.overflow);
+ mIconRow = (LinearLayout) findViewById(R.id.icon_row);
+ mBackgroundColor = ((ColorDrawable) getBackground()).getColor();
+ }
+
+ /**
+ * Keep track of the NotificationInfo, and then update the UI when
+ * {@link #commitNotificationInfos()} is called.
+ */
+ public void addNotificationInfo(final NotificationInfo notificationInfo) {
+ if (mNotifications.size() < MAX_FOOTER_NOTIFICATIONS) {
+ mNotifications.add(notificationInfo);
+ } else {
+ mOverflowNotifications.add(notificationInfo);
+ }
+ }
+
+ /**
+ * Adds icons and potentially overflow text for all of the NotificationInfo's
+ * added using {@link #addNotificationInfo(NotificationInfo)}.
+ */
+ public void commitNotificationInfos() {
+ mIconRow.removeAllViews();
+
+ for (int i = 0; i < mNotifications.size(); i++) {
+ NotificationInfo info = mNotifications.get(i);
+ addNotificationIconForInfo(info);
+ }
+ updateOverflowEllipsisVisibility();
+ }
+
+ private void updateOverflowEllipsisVisibility() {
+ mOverflowEllipsis.setVisibility(mOverflowNotifications.isEmpty() ? GONE : VISIBLE);
+ }
+
+ /**
+ * Creates an icon for the given NotificationInfo, and adds it to the icon row.
+ * @return the icon view that was added
+ */
+ private View addNotificationIconForInfo(NotificationInfo info) {
+ View icon = new View(getContext());
+ icon.setBackground(info.getIconForBackground(getContext(), mBackgroundColor));
+ icon.setOnClickListener(info);
+ icon.setTag(info);
+ mIconRow.addView(icon, 0, mIconLayoutParams);
+ return icon;
+ }
+
+ public void animateFirstNotificationTo(Rect toBounds,
+ final IconAnimationEndListener callback) {
+ AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
+ final View firstNotification = mIconRow.getChildAt(mIconRow.getChildCount() - 1);
+
+ Rect fromBounds = sTempRect;
+ firstNotification.getGlobalVisibleRect(fromBounds);
+ float scale = (float) toBounds.height() / fromBounds.height();
+ Animator moveAndScaleIcon = LauncherAnimUtils.ofPropertyValuesHolder(firstNotification,
+ new PropertyListBuilder().scale(scale).translationY(toBounds.top - fromBounds.top
+ + (fromBounds.height() * scale - fromBounds.height()) / 2).build());
+ moveAndScaleIcon.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ callback.onIconAnimationEnd((NotificationInfo) firstNotification.getTag());
+ removeViewFromIconRow(firstNotification);
+ }
+ });
+ animation.play(moveAndScaleIcon);
+
+ // Shift all notifications (not the overflow) over to fill the gap.
+ int gapWidth = mIconLayoutParams.width + mIconLayoutParams.getMarginStart();
+ if (mRtl) {
+ gapWidth = -gapWidth;
+ }
+ if (!mOverflowNotifications.isEmpty()) {
+ NotificationInfo notification = mOverflowNotifications.remove(0);
+ mNotifications.add(notification);
+ View iconFromOverflow = addNotificationIconForInfo(notification);
+ animation.play(ObjectAnimator.ofFloat(iconFromOverflow, ALPHA, 0, 1));
+ }
+ int numIcons = mIconRow.getChildCount() - 1; // All children besides the one leaving.
+ // We have to reset the translation X to 0 when the new main notification
+ // is removed from the footer.
+ PropertyResetListener<View, Float> propertyResetListener
+ = new PropertyResetListener<>(TRANSLATION_X, 0f);
+ for (int i = 0; i < numIcons; i++) {
+ final View child = mIconRow.getChildAt(i);
+ Animator shiftChild = ObjectAnimator.ofFloat(child, TRANSLATION_X, gapWidth);
+ shiftChild.addListener(propertyResetListener);
+ animation.play(shiftChild);
+ }
+ animation.start();
+ }
+
+ private void removeViewFromIconRow(View child) {
+ mIconRow.removeView(child);
+ mNotifications.remove((NotificationInfo) child.getTag());
+ updateOverflowEllipsisVisibility();
+ if (mIconRow.getChildCount() == 0) {
+ // There are no more icons in the footer, so hide it.
+ PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(
+ Launcher.getLauncher(getContext()));
+ if (popup != null) {
+ Animator collapseFooter = popup.reduceNotificationViewHeight(getHeight(),
+ getResources().getInteger(R.integer.config_removeNotificationViewDuration));
+ collapseFooter.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ ((ViewGroup) getParent()).removeView(NotificationFooterLayout.this);
+ }
+ });
+ collapseFooter.start();
+ }
+ }
+ }
+
+ public void trimNotifications(List<String> notifications) {
+ if (!isAttachedToWindow() || mIconRow.getChildCount() == 0) {
+ return;
+ }
+ Iterator<NotificationInfo> overflowIterator = mOverflowNotifications.iterator();
+ while (overflowIterator.hasNext()) {
+ if (!notifications.contains(overflowIterator.next().notificationKey)) {
+ overflowIterator.remove();
+ }
+ }
+ for (int i = mIconRow.getChildCount() - 1; i >= 0; i--) {
+ View child = mIconRow.getChildAt(i);
+ NotificationInfo childInfo = (NotificationInfo) child.getTag();
+ if (!notifications.contains(childInfo.notificationKey)) {
+ removeViewFromIconRow(child);
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/notification/NotificationInfo.java b/src/com/android/launcher3/notification/NotificationInfo.java
new file mode 100644
index 0000000..1a93e11
--- /dev/null
+++ b/src/com/android/launcher3/notification/NotificationInfo.java
@@ -0,0 +1,135 @@
+/*
+ * 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.app.ActivityOptions;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.service.notification.StatusBarNotification;
+import android.view.View;
+
+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;
+
+/**
+ * An object that contains relevant information from a {@link StatusBarNotification}. This should
+ * only be created when we need to show the notification contents on the UI; until then, a
+ * {@link com.android.launcher3.badge.BadgeInfo} with only the notification key should
+ * be passed around, and then this can be constructed using the StatusBarNotification from
+ * {@link NotificationListener#getNotificationsForKeys(java.util.List)}.
+ */
+public class NotificationInfo implements View.OnClickListener {
+
+ public final PackageUserKey packageUserKey;
+ public final String notificationKey;
+ public final CharSequence title;
+ public final CharSequence text;
+ public final PendingIntent intent;
+ public final boolean autoCancel;
+ public final boolean dismissable;
+
+ private int mBadgeIcon;
+ private Drawable mIconDrawable;
+ private int mIconColor;
+ private boolean mIsIconLarge;
+
+ /**
+ * Extracts the data that we need from the StatusBarNotification.
+ */
+ public NotificationInfo(Context context, StatusBarNotification statusBarNotification) {
+ packageUserKey = PackageUserKey.fromNotification(statusBarNotification);
+ notificationKey = statusBarNotification.getKey();
+ Notification notification = statusBarNotification.getNotification();
+ title = notification.extras.getCharSequence(Notification.EXTRA_TITLE);
+ text = notification.extras.getCharSequence(Notification.EXTRA_TEXT);
+
+ mBadgeIcon = notification.getBadgeIconType();
+ // Load the icon. Since it is backed by ashmem, we won't copy the entire bitmap
+ // into our process as long as we don't touch it and it exists in systemui.
+ Icon icon = mBadgeIcon == Notification.BADGE_ICON_SMALL ? null : notification.getLargeIcon();
+ if (icon == null) {
+ // Use the small icon.
+ icon = notification.getSmallIcon();
+ mIconDrawable = icon.loadDrawable(context);
+ mIconColor = statusBarNotification.getNotification().color;
+ mIsIconLarge = false;
+ } else {
+ // Use the large icon.
+ mIconDrawable = icon.loadDrawable(context);
+ mIsIconLarge = true;
+ }
+ if (mIconDrawable == null) {
+ mIconDrawable = new BitmapDrawable(context.getResources(), LauncherAppState
+ .getInstance(context).getIconCache()
+ .getDefaultIcon(statusBarNotification.getUser()));
+ mBadgeIcon = Notification.BADGE_ICON_NONE;
+ }
+ intent = notification.contentIntent;
+ autoCancel = (notification.flags & Notification.FLAG_AUTO_CANCEL) != 0;
+ dismissable = (notification.flags & Notification.FLAG_ONGOING_EVENT) == 0;
+ }
+
+ @Override
+ public void onClick(View view) {
+ final Launcher launcher = Launcher.getLauncher(view.getContext());
+ Bundle activityOptions = ActivityOptions.makeClipRevealAnimation(
+ view, 0, 0, view.getWidth(), view.getHeight()).toBundle();
+ try {
+ intent.send(null, 0, null, null, null, null, activityOptions);
+ launcher.getUserEventDispatcher().logNotificationLaunch(view, intent);
+ } catch (PendingIntent.CanceledException e) {
+ e.printStackTrace();
+ }
+ if (autoCancel) {
+ launcher.getPopupDataProvider().cancelNotification(notificationKey);
+ }
+ PopupContainerWithArrow.getOpen(launcher).close(true);
+ }
+
+ public Drawable getIconForBackground(Context context, int background) {
+ if (mIsIconLarge) {
+ // Only small icons should be tinted.
+ return mIconDrawable;
+ }
+ mIconColor = IconPalette.resolveContrastColor(context, mIconColor, background);
+ Drawable icon = mIconDrawable.mutate();
+ // DrawableContainer ignores the color filter if it's already set, so clear it first to
+ // get it set and invalidated properly.
+ icon.setTintList(null);
+ icon.setTint(mIconColor);
+ return icon;
+ }
+
+ public boolean isIconLarge() {
+ return mIsIconLarge;
+ }
+
+ public boolean shouldShowIconInBadge() {
+ // If the icon we're using for this notification matches what the Notification
+ // specified should show in the badge, then return true.
+ return mIsIconLarge && mBadgeIcon == Notification.BADGE_ICON_LARGE
+ || !mIsIconLarge && mBadgeIcon == Notification.BADGE_ICON_SMALL;
+ }
+}
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
new file mode 100644
index 0000000..dd272b3
--- /dev/null
+++ b/src/com/android/launcher3/notification/NotificationItemView.java
@@ -0,0 +1,174 @@
+/*
+ * 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.animation.Animator;
+import android.app.Notification;
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.anim.PillHeightRevealOutlineProvider;
+import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
+import com.android.launcher3.popup.PopupItemView;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+
+import java.util.List;
+
+/**
+ * A {@link FrameLayout} that contains a header, main view and a footer.
+ * The main view contains the icon and text (title + subtext) of the first notification.
+ * The footer contains: A list of just the icons of all the notifications past the first one.
+ * @see NotificationFooterLayout
+ */
+public class NotificationItemView extends PopupItemView implements LogContainerProvider {
+
+ private static final Rect sTempRect = new Rect();
+
+ private TextView mHeaderCount;
+ private NotificationMainView mMainView;
+ private NotificationFooterLayout mFooter;
+ private SwipeHelper mSwipeHelper;
+ private boolean mAnimatingNextIcon;
+ private int mNotificationHeaderTextColor = Notification.COLOR_DEFAULT;
+
+ public NotificationItemView(Context context) {
+ this(context, null, 0);
+ }
+
+ public NotificationItemView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public NotificationItemView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mHeaderCount = (TextView) findViewById(R.id.notification_count);
+ mMainView = (NotificationMainView) findViewById(R.id.main_view);
+ mFooter = (NotificationFooterLayout) findViewById(R.id.footer);
+ mSwipeHelper = new SwipeHelper(SwipeHelper.X, mMainView, getContext());
+ mSwipeHelper.setDisableHardwareLayers(true);
+ }
+
+ public int getHeightMinusFooter() {
+ int footerHeight = mFooter.getParent() == null ? 0 : mFooter.getHeight();
+ return getHeight() - footerHeight;
+ }
+
+ public Animator animateHeightRemoval(int heightToRemove) {
+ final int newHeight = getHeight() - heightToRemove;
+ return new PillHeightRevealOutlineProvider(mPillRect,
+ getBackgroundRadius(), newHeight).createRevealAnimator(this, true /* isReversed */);
+ }
+
+ public void updateHeader(int notificationCount, @Nullable IconPalette palette) {
+ mHeaderCount.setText(notificationCount <= 1 ? "" : String.valueOf(notificationCount));
+ if (palette != null) {
+ if (mNotificationHeaderTextColor == Notification.COLOR_DEFAULT) {
+ mNotificationHeaderTextColor =
+ IconPalette.resolveContrastColor(getContext(), palette.dominantColor,
+ getResources().getColor(R.color.popup_header_background_color));
+ }
+ mHeaderCount.setTextColor(mNotificationHeaderTextColor);
+ }
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (mMainView.getNotificationInfo() == null) {
+ // The notification hasn't been populated yet.
+ return false;
+ }
+ getParent().requestDisallowInterceptTouchEvent(true);
+ return mSwipeHelper.onInterceptTouchEvent(ev);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (mMainView.getNotificationInfo() == null) {
+ // The notification hasn't been populated yet.
+ return false;
+ }
+ return mSwipeHelper.onTouchEvent(ev) || super.onTouchEvent(ev);
+ }
+
+ public void applyNotificationInfos(final List<NotificationInfo> notificationInfos) {
+ if (notificationInfos.isEmpty()) {
+ return;
+ }
+
+ NotificationInfo mainNotification = notificationInfos.get(0);
+ mMainView.applyNotificationInfo(mainNotification, mIconView);
+
+ for (int i = 1; i < notificationInfos.size(); i++) {
+ mFooter.addNotificationInfo(notificationInfos.get(i));
+ }
+ mFooter.commitNotificationInfos();
+ }
+
+ public void trimNotifications(final List<String> notificationKeys) {
+ boolean dismissedMainNotification = !notificationKeys.contains(
+ mMainView.getNotificationInfo().notificationKey);
+ if (dismissedMainNotification && !mAnimatingNextIcon) {
+ // Animate the next icon into place as the new main notification.
+ mAnimatingNextIcon = true;
+ mMainView.setVisibility(INVISIBLE);
+ mMainView.setTranslationX(0);
+ mIconView.getGlobalVisibleRect(sTempRect);
+ mFooter.animateFirstNotificationTo(sTempRect,
+ new NotificationFooterLayout.IconAnimationEndListener() {
+ @Override
+ public void onIconAnimationEnd(NotificationInfo newMainNotification) {
+ if (newMainNotification != null) {
+ mMainView.applyNotificationInfo(newMainNotification, mIconView, true);
+ mMainView.setVisibility(VISIBLE);
+ }
+ mAnimatingNextIcon = false;
+ }
+ });
+ } else {
+ mFooter.trimNotifications(notificationKeys);
+ }
+ }
+
+ @Override
+ public int getArrowColor(boolean isArrowAttachedToBottom) {
+ return ContextCompat.getColor(getContext(), isArrowAttachedToBottom
+ ? R.color.popup_background_color
+ : R.color.popup_header_background_color);
+ }
+
+ @Override
+ public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
+ LauncherLogProto.Target targetParent) {
+ target.itemType = LauncherLogProto.ItemType.NOTIFICATION;
+ targetParent.containerType = LauncherLogProto.ContainerType.DEEPSHORTCUTS;
+ }
+}
diff --git a/src/com/android/launcher3/notification/NotificationKeyData.java b/src/com/android/launcher3/notification/NotificationKeyData.java
new file mode 100644
index 0000000..bf7ae1a
--- /dev/null
+++ b/src/com/android/launcher3/notification/NotificationKeyData.java
@@ -0,0 +1,64 @@
+/*
+ * 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.app.Notification;
+import android.service.notification.StatusBarNotification;
+import android.support.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The key data associated with the notification, used to determine what to include
+ * in badges and dummy popup views before they are populated.
+ *
+ * @see NotificationInfo for the full data used when populating the dummy views.
+ */
+public class NotificationKeyData {
+ public final String notificationKey;
+ public final String shortcutId;
+ public int count;
+
+ private NotificationKeyData(String notificationKey, String shortcutId, int count) {
+ this.notificationKey = notificationKey;
+ this.shortcutId = shortcutId;
+ this.count = Math.max(1, count);
+ }
+
+ public static NotificationKeyData fromNotification(StatusBarNotification sbn) {
+ Notification notif = sbn.getNotification();
+ return new NotificationKeyData(sbn.getKey(), notif.getShortcutId(), notif.number);
+ }
+
+ public static List<String> extractKeysOnly(@NonNull List<NotificationKeyData> notificationKeys) {
+ List<String> keysOnly = new ArrayList<>(notificationKeys.size());
+ for (NotificationKeyData notificationKeyData : notificationKeys) {
+ keysOnly.add(notificationKeyData.notificationKey);
+ }
+ return keysOnly;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof NotificationKeyData)) {
+ return false;
+ }
+ // Only compare the keys.
+ return ((NotificationKeyData) obj).notificationKey.equals(notificationKey);
+ }
+}
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
new file mode 100644
index 0000000..8dca699
--- /dev/null
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -0,0 +1,246 @@
+/*
+ * 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.app.Notification;
+import android.app.NotificationChannel;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.support.annotation.Nullable;
+import android.support.v4.util.Pair;
+import android.text.TextUtils;
+
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.PackageUserKey;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A {@link NotificationListenerService} that sends updates to its
+ * {@link NotificationsChangedListener} when notifications are posted or canceled,
+ * as well and when this service first connects. An instance of NotificationListener,
+ * and its methods for getting notifications, can be obtained via {@link #getInstanceIfConnected()}.
+ */
+public class NotificationListener extends NotificationListenerService {
+
+ private static final int MSG_NOTIFICATION_POSTED = 1;
+ private static final int MSG_NOTIFICATION_REMOVED = 2;
+ private static final int MSG_NOTIFICATION_FULL_REFRESH = 3;
+
+ private static NotificationListener sNotificationListenerInstance = null;
+ private static NotificationsChangedListener sNotificationsChangedListener;
+ private static boolean sIsConnected;
+
+ private final Handler mWorkerHandler;
+ private final Handler mUiHandler;
+
+ private Ranking mTempRanking = new Ranking();
+
+ private Handler.Callback mWorkerCallback = new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_NOTIFICATION_POSTED:
+ mUiHandler.obtainMessage(message.what, message.obj).sendToTarget();
+ break;
+ case MSG_NOTIFICATION_REMOVED:
+ mUiHandler.obtainMessage(message.what, message.obj).sendToTarget();
+ break;
+ case MSG_NOTIFICATION_FULL_REFRESH:
+ final List<StatusBarNotification> activeNotifications = sIsConnected
+ ? filterNotifications(getActiveNotifications())
+ : new ArrayList<StatusBarNotification>();
+ mUiHandler.obtainMessage(message.what, activeNotifications).sendToTarget();
+ break;
+ }
+ return true;
+ }
+ };
+
+ private Handler.Callback mUiCallback = new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_NOTIFICATION_POSTED:
+ if (sNotificationsChangedListener != null) {
+ NotificationPostedMsg msg = (NotificationPostedMsg) message.obj;
+ sNotificationsChangedListener.onNotificationPosted(msg.packageUserKey,
+ msg.notificationKey, msg.shouldBeFilteredOut);
+ }
+ break;
+ case MSG_NOTIFICATION_REMOVED:
+ if (sNotificationsChangedListener != null) {
+ Pair<PackageUserKey, NotificationKeyData> pair
+ = (Pair<PackageUserKey, NotificationKeyData>) message.obj;
+ sNotificationsChangedListener.onNotificationRemoved(pair.first, pair.second);
+ }
+ break;
+ case MSG_NOTIFICATION_FULL_REFRESH:
+ if (sNotificationsChangedListener != null) {
+ sNotificationsChangedListener.onNotificationFullRefresh(
+ (List<StatusBarNotification>) message.obj);
+ }
+ break;
+ }
+ return true;
+ }
+ };
+
+ public NotificationListener() {
+ super();
+ mWorkerHandler = new Handler(LauncherModel.getWorkerLooper(), mWorkerCallback);
+ mUiHandler = new Handler(Looper.getMainLooper(), mUiCallback);
+ sNotificationListenerInstance = this;
+ }
+
+ public static @Nullable NotificationListener getInstanceIfConnected() {
+ return sIsConnected ? sNotificationListenerInstance : null;
+ }
+
+ public static void setNotificationsChangedListener(NotificationsChangedListener listener) {
+ if (!FeatureFlags.BADGE_ICONS) {
+ return;
+ }
+ sNotificationsChangedListener = listener;
+
+ if (sNotificationListenerInstance != null) {
+ sNotificationListenerInstance.onNotificationFullRefresh();
+ }
+ }
+
+ public static void removeNotificationsChangedListener() {
+ sNotificationsChangedListener = null;
+ }
+
+ @Override
+ public void onListenerConnected() {
+ super.onListenerConnected();
+ sIsConnected = true;
+ onNotificationFullRefresh();
+ }
+
+ private void onNotificationFullRefresh() {
+ mWorkerHandler.obtainMessage(MSG_NOTIFICATION_FULL_REFRESH).sendToTarget();
+ }
+
+ @Override
+ public void onListenerDisconnected() {
+ super.onListenerDisconnected();
+ sIsConnected = false;
+ }
+
+ @Override
+ public void onNotificationPosted(final StatusBarNotification sbn) {
+ super.onNotificationPosted(sbn);
+ mWorkerHandler.obtainMessage(MSG_NOTIFICATION_POSTED, new NotificationPostedMsg(sbn))
+ .sendToTarget();
+ }
+
+ /**
+ * An object containing data to send to MSG_NOTIFICATION_POSTED targets.
+ */
+ private class NotificationPostedMsg {
+ PackageUserKey packageUserKey;
+ NotificationKeyData notificationKey;
+ boolean shouldBeFilteredOut;
+
+ NotificationPostedMsg(StatusBarNotification sbn) {
+ packageUserKey = PackageUserKey.fromNotification(sbn);
+ notificationKey = NotificationKeyData.fromNotification(sbn);
+ shouldBeFilteredOut = shouldBeFilteredOut(sbn);
+ }
+ }
+
+ @Override
+ public void onNotificationRemoved(final StatusBarNotification sbn) {
+ super.onNotificationRemoved(sbn);
+ Pair<PackageUserKey, NotificationKeyData> packageUserKeyAndNotificationKey
+ = new Pair<>(PackageUserKey.fromNotification(sbn),
+ NotificationKeyData.fromNotification(sbn));
+ mWorkerHandler.obtainMessage(MSG_NOTIFICATION_REMOVED, packageUserKeyAndNotificationKey)
+ .sendToTarget();
+ }
+
+ /** This makes a potentially expensive binder call and should be run on a background thread. */
+ public List<StatusBarNotification> getNotificationsForKeys(List<NotificationKeyData> keys) {
+ StatusBarNotification[] notifications = NotificationListener.this
+ .getActiveNotifications(NotificationKeyData.extractKeysOnly(keys)
+ .toArray(new String[keys.size()]));
+ return notifications == null ? Collections.EMPTY_LIST : Arrays.asList(notifications);
+ }
+
+ /**
+ * Filter out notifications that don't have an intent
+ * or are headers for grouped notifications.
+ *
+ * @see #shouldBeFilteredOut(StatusBarNotification)
+ */
+ private List<StatusBarNotification> filterNotifications(
+ StatusBarNotification[] notifications) {
+ if (notifications == null) return null;
+ Set<Integer> removedNotifications = new HashSet<>();
+ for (int i = 0; i < notifications.length; i++) {
+ if (shouldBeFilteredOut(notifications[i])) {
+ removedNotifications.add(i);
+ }
+ }
+ List<StatusBarNotification> filteredNotifications = new ArrayList<>(
+ notifications.length - removedNotifications.size());
+ for (int i = 0; i < notifications.length; i++) {
+ if (!removedNotifications.contains(i)) {
+ filteredNotifications.add(notifications[i]);
+ }
+ }
+ return filteredNotifications;
+ }
+
+ private boolean shouldBeFilteredOut(StatusBarNotification sbn) {
+ 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);
+ return (isGroupHeader || missingTitleAndText);
+ }
+
+ public interface NotificationsChangedListener {
+ void onNotificationPosted(PackageUserKey postedPackageUserKey,
+ NotificationKeyData notificationKey, boolean shouldBeFilteredOut);
+ void onNotificationRemoved(PackageUserKey removedPackageUserKey,
+ NotificationKeyData notificationKey);
+ void onNotificationFullRefresh(List<StatusBarNotification> activeNotifications);
+ }
+}
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
new file mode 100644
index 0000000..d6e0272
--- /dev/null
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -0,0 +1,166 @@
+/*
+ * 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.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.RippleDrawable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.util.Themes;
+
+/**
+ * A {@link android.widget.FrameLayout} that contains a single notification,
+ * e.g. icon + title + text.
+ */
+public class NotificationMainView extends FrameLayout implements SwipeHelper.Callback {
+
+ private NotificationInfo mNotificationInfo;
+ private ViewGroup mTextAndBackground;
+ private int mBackgroundColor;
+ private TextView mTitleView;
+ private TextView mTextView;
+
+ public NotificationMainView(Context context) {
+ this(context, null, 0);
+ }
+
+ public NotificationMainView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public NotificationMainView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mTextAndBackground = (ViewGroup) findViewById(R.id.text_and_background);
+ ColorDrawable colorBackground = (ColorDrawable) mTextAndBackground.getBackground();
+ mBackgroundColor = colorBackground.getColor();
+ RippleDrawable rippleBackground = new RippleDrawable(ColorStateList.valueOf(
+ Themes.getAttrColor(getContext(), android.R.attr.colorControlHighlight)),
+ colorBackground, null);
+ mTextAndBackground.setBackground(rippleBackground);
+ mTitleView = (TextView) mTextAndBackground.findViewById(R.id.title);
+ mTextView = (TextView) mTextAndBackground.findViewById(R.id.text);
+ }
+
+ public void applyNotificationInfo(NotificationInfo mainNotification, View iconView) {
+ applyNotificationInfo(mainNotification, iconView, false);
+ }
+
+ /**
+ * Sets the content of this view, animating it after a new icon shifts up if necessary.
+ */
+ public void applyNotificationInfo(NotificationInfo mainNotification, View iconView,
+ boolean animate) {
+ mNotificationInfo = mainNotification;
+ CharSequence title = mNotificationInfo.title;
+ CharSequence text = mNotificationInfo.text;
+ if (!TextUtils.isEmpty(title) && !TextUtils.isEmpty(text)) {
+ mTitleView.setText(title);
+ mTextView.setText(text);
+ } else {
+ mTitleView.setMaxLines(2);
+ mTitleView.setText(TextUtils.isEmpty(title) ? text : title);
+ mTextView.setVisibility(GONE);
+ }
+ iconView.setBackground(mNotificationInfo.getIconForBackground(getContext(),
+ mBackgroundColor));
+ if (mNotificationInfo.intent != null) {
+ setOnClickListener(mNotificationInfo);
+ }
+ setTranslationX(0);
+ // Add a dummy ItemInfo so that logging populates the correct container and item types
+ // instead of DEFAULT_CONTAINERTYPE and DEFAULT_ITEMTYPE, respectively.
+ setTag(new ItemInfo());
+ if (animate) {
+ ObjectAnimator.ofFloat(mTextAndBackground, ALPHA, 0, 1).setDuration(150).start();
+ }
+ }
+
+ public NotificationInfo getNotificationInfo() {
+ return mNotificationInfo;
+ }
+
+
+ // SwipeHelper.Callback's
+
+ @Override
+ public View getChildAtPosition(MotionEvent ev) {
+ return this;
+ }
+
+ @Override
+ public boolean canChildBeDismissed(View v) {
+ return mNotificationInfo.dismissable;
+ }
+
+ @Override
+ public boolean isAntiFalsingNeeded() {
+ return false;
+ }
+
+ @Override
+ public void onBeginDrag(View v) {
+ }
+
+ @Override
+ public void onChildDismissed(View v) {
+ Launcher launcher = Launcher.getLauncher(getContext());
+ launcher.getPopupDataProvider().cancelNotification(
+ mNotificationInfo.notificationKey);
+ launcher.getUserEventDispatcher().logActionOnItem(
+ LauncherLogProto.Action.Touch.SWIPE,
+ LauncherLogProto.Action.Direction.RIGHT, // Assume all swipes are right for logging.
+ LauncherLogProto.ItemType.NOTIFICATION);
+ }
+
+ @Override
+ public void onDragCancelled(View v) {
+ }
+
+ @Override
+ public void onChildSnappedBack(View animView, float targetLeft) {
+ }
+
+ @Override
+ public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
+ // Don't fade out.
+ return true;
+ }
+
+ @Override
+ public float getFalsingThresholdFactor() {
+ return 1;
+ }
+}
diff --git a/src/com/android/launcher3/notification/SwipeHelper.java b/src/com/android/launcher3/notification/SwipeHelper.java
new file mode 100644
index 0000000..5f03252
--- /dev/null
+++ b/src/com/android/launcher3/notification/SwipeHelper.java
@@ -0,0 +1,690 @@
+/*
+ * 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.graphics.RectF;
+import android.os.Handler;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.launcher3.R;
+
+import java.util.HashMap;
+
+/**
+ * This class was copied from com.android.systemui.
+ */
+public class SwipeHelper {
+ static final String TAG = "SwipeHelper";
+ private static final boolean DEBUG = false;
+ private static final boolean DEBUG_INVALIDATE = false;
+ private static final boolean SLOW_ANIMATIONS = false; // DEBUG;
+ private static final boolean CONSTRAIN_SWIPE = true;
+ private static final boolean FADE_OUT_DURING_SWIPE = true;
+ private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true;
+
+ public static final int X = 0;
+ public static final int Y = 1;
+
+ private float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec
+ private int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms
+ private int MAX_ESCAPE_ANIMATION_DURATION = 400; // ms
+ private int MAX_DISMISS_VELOCITY = 4000; // dp/sec
+ private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms
+
+ static final float SWIPE_PROGRESS_FADE_END = 0.5f; // fraction of thumbnail width
+ // beyond which swipe progress->0
+ private float mMinSwipeProgress = 0f;
+ private float mMaxSwipeProgress = 1f;
+
+ private FlingAnimationUtils mFlingAnimationUtils;
+ private float mPagingTouchSlop;
+ private Callback mCallback;
+ private Handler mHandler;
+ private int mSwipeDirection;
+ private VelocityTracker mVelocityTracker;
+
+ private float mInitialTouchPos;
+ private float mPerpendicularInitialTouchPos;
+ private boolean mDragging;
+ private boolean mSnappingChild;
+ private View mCurrView;
+ private boolean mCanCurrViewBeDimissed;
+ private float mDensityScale;
+ private float mTranslation = 0;
+
+ private boolean mLongPressSent;
+ private LongPressListener mLongPressListener;
+ private Runnable mWatchLongPress;
+ private long mLongPressTimeout;
+
+ final private int[] mTmpPos = new int[2];
+ private int mFalsingThreshold;
+ private boolean mTouchAboveFalsingThreshold;
+ private boolean mDisableHwLayers;
+
+ private HashMap<View, Animator> mDismissPendingMap = new HashMap<>();
+
+ public SwipeHelper(int swipeDirection, Callback callback, Context context) {
+ mCallback = callback;
+ mHandler = new Handler();
+ mSwipeDirection = swipeDirection;
+ mVelocityTracker = VelocityTracker.obtain();
+ mDensityScale = context.getResources().getDisplayMetrics().density;
+ mPagingTouchSlop = ViewConfiguration.get(context).getScaledPagingTouchSlop();
+
+ mLongPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f); // extra long-press!
+ mFalsingThreshold = context.getResources().getDimensionPixelSize(
+ R.dimen.swipe_helper_falsing_threshold);
+ mFlingAnimationUtils = new FlingAnimationUtils(context, getMaxEscapeAnimDuration() / 1000f);
+ }
+
+ public void setLongPressListener(LongPressListener listener) {
+ mLongPressListener = listener;
+ }
+
+ public void setDensityScale(float densityScale) {
+ mDensityScale = densityScale;
+ }
+
+ public void setPagingTouchSlop(float pagingTouchSlop) {
+ mPagingTouchSlop = pagingTouchSlop;
+ }
+
+ public void setDisableHardwareLayers(boolean disableHwLayers) {
+ mDisableHwLayers = disableHwLayers;
+ }
+
+ private float getPos(MotionEvent ev) {
+ return mSwipeDirection == X ? ev.getX() : ev.getY();
+ }
+
+ private float getPerpendicularPos(MotionEvent ev) {
+ return mSwipeDirection == X ? ev.getY() : ev.getX();
+ }
+
+ protected float getTranslation(View v) {
+ return mSwipeDirection == X ? v.getTranslationX() : v.getTranslationY();
+ }
+
+ private float getVelocity(VelocityTracker vt) {
+ return mSwipeDirection == X ? vt.getXVelocity() :
+ vt.getYVelocity();
+ }
+
+ protected ObjectAnimator createTranslationAnimation(View v, float newPos) {
+ ObjectAnimator anim = ObjectAnimator.ofFloat(v,
+ mSwipeDirection == X ? View.TRANSLATION_X : View.TRANSLATION_Y, newPos);
+ return anim;
+ }
+
+ private float getPerpendicularVelocity(VelocityTracker vt) {
+ return mSwipeDirection == X ? vt.getYVelocity() :
+ vt.getXVelocity();
+ }
+
+ protected Animator getViewTranslationAnimator(View v, float target,
+ AnimatorUpdateListener listener) {
+ ObjectAnimator anim = createTranslationAnimation(v, target);
+ if (listener != null) {
+ anim.addUpdateListener(listener);
+ }
+ return anim;
+ }
+
+ protected void setTranslation(View v, float translate) {
+ if (v == null) {
+ return;
+ }
+ if (mSwipeDirection == X) {
+ v.setTranslationX(translate);
+ } else {
+ v.setTranslationY(translate);
+ }
+ }
+
+ protected float getSize(View v) {
+ return mSwipeDirection == X ? v.getMeasuredWidth() :
+ v.getMeasuredHeight();
+ }
+
+ public void setMinSwipeProgress(float minSwipeProgress) {
+ mMinSwipeProgress = minSwipeProgress;
+ }
+
+ public void setMaxSwipeProgress(float maxSwipeProgress) {
+ mMaxSwipeProgress = maxSwipeProgress;
+ }
+
+ private float getSwipeProgressForOffset(View view, float translation) {
+ float viewSize = getSize(view);
+ float result = Math.abs(translation / viewSize);
+ return Math.min(Math.max(mMinSwipeProgress, result), mMaxSwipeProgress);
+ }
+
+ private float getSwipeAlpha(float progress) {
+ return Math.min(0, Math.max(1, progress / SWIPE_PROGRESS_FADE_END));
+ }
+
+ private void updateSwipeProgressFromOffset(View animView, boolean dismissable) {
+ updateSwipeProgressFromOffset(animView, dismissable, getTranslation(animView));
+ }
+
+ private void updateSwipeProgressFromOffset(View animView, boolean dismissable,
+ float translation) {
+ float swipeProgress = getSwipeProgressForOffset(animView, translation);
+ if (!mCallback.updateSwipeProgress(animView, dismissable, swipeProgress)) {
+ if (FADE_OUT_DURING_SWIPE && dismissable) {
+ float alpha = swipeProgress;
+ if (!mDisableHwLayers) {
+ if (alpha != 0f && alpha != 1f) {
+ animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ } else {
+ animView.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ }
+ animView.setAlpha(getSwipeAlpha(swipeProgress));
+ }
+ }
+ invalidateGlobalRegion(animView);
+ }
+
+ // invalidate the view's own bounds all the way up the view hierarchy
+ public static void invalidateGlobalRegion(View view) {
+ invalidateGlobalRegion(
+ view,
+ new RectF(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()));
+ }
+
+ // invalidate a rectangle relative to the view's coordinate system all the way up the view
+ // hierarchy
+ public static void invalidateGlobalRegion(View view, RectF childBounds) {
+ //childBounds.offset(view.getTranslationX(), view.getTranslationY());
+ if (DEBUG_INVALIDATE)
+ Log.v(TAG, "-------------");
+ while (view.getParent() != null && view.getParent() instanceof View) {
+ view = (View) view.getParent();
+ view.getMatrix().mapRect(childBounds);
+ view.invalidate((int) Math.floor(childBounds.left),
+ (int) Math.floor(childBounds.top),
+ (int) Math.ceil(childBounds.right),
+ (int) Math.ceil(childBounds.bottom));
+ if (DEBUG_INVALIDATE) {
+ Log.v(TAG, "INVALIDATE(" + (int) Math.floor(childBounds.left)
+ + "," + (int) Math.floor(childBounds.top)
+ + "," + (int) Math.ceil(childBounds.right)
+ + "," + (int) Math.ceil(childBounds.bottom));
+ }
+ }
+ }
+
+ public void removeLongPressCallback() {
+ if (mWatchLongPress != null) {
+ mHandler.removeCallbacks(mWatchLongPress);
+ mWatchLongPress = null;
+ }
+ }
+
+ public boolean onInterceptTouchEvent(final MotionEvent ev) {
+ final int action = ev.getAction();
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mTouchAboveFalsingThreshold = false;
+ mDragging = false;
+ mSnappingChild = false;
+ mLongPressSent = false;
+ mVelocityTracker.clear();
+ mCurrView = mCallback.getChildAtPosition(ev);
+
+ if (mCurrView != null) {
+ onDownUpdate(mCurrView);
+ mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
+ mVelocityTracker.addMovement(ev);
+ mInitialTouchPos = getPos(ev);
+ mPerpendicularInitialTouchPos = getPerpendicularPos(ev);
+ mTranslation = getTranslation(mCurrView);
+ if (mLongPressListener != null) {
+ if (mWatchLongPress == null) {
+ mWatchLongPress = new Runnable() {
+ @Override
+ public void run() {
+ if (mCurrView != null && !mLongPressSent) {
+ mLongPressSent = true;
+ mCurrView.sendAccessibilityEvent(
+ AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
+ mCurrView.getLocationOnScreen(mTmpPos);
+ final int x = (int) ev.getRawX() - mTmpPos[0];
+ final int y = (int) ev.getRawY() - mTmpPos[1];
+ mLongPressListener.onLongPress(mCurrView, x, y);
+ }
+ }
+ };
+ }
+ mHandler.postDelayed(mWatchLongPress, mLongPressTimeout);
+ }
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (mCurrView != null && !mLongPressSent) {
+ mVelocityTracker.addMovement(ev);
+ float pos = getPos(ev);
+ float perpendicularPos = getPerpendicularPos(ev);
+ float delta = pos - mInitialTouchPos;
+ float deltaPerpendicular = perpendicularPos - mPerpendicularInitialTouchPos;
+ if (Math.abs(delta) > mPagingTouchSlop
+ && Math.abs(delta) > Math.abs(deltaPerpendicular)) {
+ mCallback.onBeginDrag(mCurrView);
+ mDragging = true;
+ mInitialTouchPos = getPos(ev);
+ mTranslation = getTranslation(mCurrView);
+ removeLongPressCallback();
+ }
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ final boolean captured = (mDragging || mLongPressSent);
+ mDragging = false;
+ mCurrView = null;
+ mLongPressSent = false;
+ removeLongPressCallback();
+ if (captured) return true;
+ break;
+ }
+ return mDragging || mLongPressSent;
+ }
+
+ /**
+ * @param view The view to be dismissed
+ * @param velocity The desired pixels/second speed at which the view should move
+ * @param useAccelerateInterpolator Should an accelerating Interpolator be used
+ */
+ public void dismissChild(final View view, float velocity, boolean useAccelerateInterpolator) {
+ dismissChild(view, velocity, null /* endAction */, 0 /* delay */,
+ useAccelerateInterpolator, 0 /* fixedDuration */, false /* isDismissAll */);
+ }
+
+ /**
+ * @param animView The view to be dismissed
+ * @param velocity The desired pixels/second speed at which the view should move
+ * @param endAction The action to perform at the end
+ * @param delay The delay after which we should start
+ * @param useAccelerateInterpolator Should an accelerating Interpolator be used
+ * @param fixedDuration If not 0, this exact duration will be taken
+ */
+ public void dismissChild(final View animView, float velocity, final Runnable endAction,
+ long delay, boolean useAccelerateInterpolator, long fixedDuration,
+ boolean isDismissAll) {
+ final boolean canBeDismissed = mCallback.canChildBeDismissed(animView);
+ float newPos;
+ boolean isLayoutRtl = animView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+
+ // if we use the Menu to dismiss an item in landscape, animate up
+ boolean animateUpForMenu = velocity == 0 && (getTranslation(animView) == 0 || isDismissAll)
+ && mSwipeDirection == Y;
+ // if the language is rtl we prefer swiping to the left
+ boolean animateLeftForRtl = velocity == 0 && (getTranslation(animView) == 0 || isDismissAll)
+ && isLayoutRtl;
+ boolean animateLeft = velocity < 0
+ || (velocity == 0 && getTranslation(animView) < 0 && !isDismissAll);
+
+ if (animateLeft || animateLeftForRtl || animateUpForMenu) {
+ newPos = -getSize(animView);
+ } else {
+ newPos = getSize(animView);
+ }
+ long duration;
+ if (fixedDuration == 0) {
+ duration = MAX_ESCAPE_ANIMATION_DURATION;
+ if (velocity != 0) {
+ duration = Math.min(duration,
+ (int) (Math.abs(newPos - getTranslation(animView)) * 1000f / Math
+ .abs(velocity))
+ );
+ } else {
+ duration = DEFAULT_ESCAPE_ANIMATION_DURATION;
+ }
+ } else {
+ duration = fixedDuration;
+ }
+
+ if (!mDisableHwLayers) {
+ animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ }
+ AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
+ public void onAnimationUpdate(ValueAnimator animation) {
+ onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed);
+ }
+ };
+
+ Animator anim = getViewTranslationAnimator(animView, newPos, updateListener);
+ if (anim == null) {
+ return;
+ }
+ if (useAccelerateInterpolator) {
+ anim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+ anim.setDuration(duration);
+ } else {
+ mFlingAnimationUtils.applyDismissing(anim, getTranslation(animView),
+ newPos, velocity, getSize(animView));
+ }
+ if (delay > 0) {
+ anim.setStartDelay(delay);
+ }
+ anim.addListener(new AnimatorListenerAdapter() {
+ private boolean mCancelled;
+
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+
+ public void onAnimationEnd(Animator animation) {
+ updateSwipeProgressFromOffset(animView, canBeDismissed);
+ mDismissPendingMap.remove(animView);
+ if (!mCancelled) {
+ mCallback.onChildDismissed(animView);
+ }
+ if (endAction != null) {
+ endAction.run();
+ }
+ if (!mDisableHwLayers) {
+ animView.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ }
+ });
+
+ prepareDismissAnimation(animView, anim);
+ mDismissPendingMap.put(animView, anim);
+ anim.start();
+ }
+
+ /**
+ * Called to update the dismiss animation.
+ */
+ protected void prepareDismissAnimation(View view, Animator anim) {
+ // Do nothing
+ }
+
+ public void snapChild(final View animView, final float targetLeft, float velocity) {
+ final boolean canBeDismissed = mCallback.canChildBeDismissed(animView);
+ AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
+ public void onAnimationUpdate(ValueAnimator animation) {
+ onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed);
+ }
+ };
+
+ Animator anim = getViewTranslationAnimator(animView, targetLeft, updateListener);
+ if (anim == null) {
+ return;
+ }
+ int duration = SNAP_ANIM_LEN;
+ anim.setDuration(duration);
+ anim.addListener(new AnimatorListenerAdapter() {
+ public void onAnimationEnd(Animator animator) {
+ mSnappingChild = false;
+ updateSwipeProgressFromOffset(animView, canBeDismissed);
+ mCallback.onChildSnappedBack(animView, targetLeft);
+ }
+ });
+ prepareSnapBackAnimation(animView, anim);
+ mSnappingChild = true;
+ anim.start();
+ }
+
+ /**
+ * Called to update the snap back animation.
+ */
+ protected void prepareSnapBackAnimation(View view, Animator anim) {
+ // Do nothing
+ }
+
+ /**
+ * Called when there's a down event.
+ */
+ public void onDownUpdate(View currView) {
+ // Do nothing
+ }
+
+ /**
+ * Called on a move event.
+ */
+ protected void onMoveUpdate(View view, float totalTranslation, float delta) {
+ // Do nothing
+ }
+
+ /**
+ * Called in {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)} when the current
+ * view is being animated to dismiss or snap.
+ */
+ public void onTranslationUpdate(View animView, float value, boolean canBeDismissed) {
+ updateSwipeProgressFromOffset(animView, canBeDismissed, value);
+ }
+
+ private void snapChildInstantly(final View view) {
+ final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
+ setTranslation(view, 0);
+ updateSwipeProgressFromOffset(view, canAnimViewBeDismissed);
+ }
+
+ /**
+ * Called when a view is updated to be non-dismissable, if the view was being dismissed before
+ * the update this will handle snapping it back into place.
+ *
+ * @param view the view to snap if necessary.
+ * @param animate whether to animate the snap or not.
+ * @param targetLeft the target to snap to.
+ */
+ public void snapChildIfNeeded(final View view, boolean animate, float targetLeft) {
+ if ((mDragging && mCurrView == view) || mSnappingChild) {
+ return;
+ }
+ boolean needToSnap = false;
+ Animator dismissPendingAnim = mDismissPendingMap.get(view);
+ if (dismissPendingAnim != null) {
+ needToSnap = true;
+ dismissPendingAnim.cancel();
+ } else if (getTranslation(view) != 0) {
+ needToSnap = true;
+ }
+ if (needToSnap) {
+ if (animate) {
+ snapChild(view, targetLeft, 0.0f /* velocity */);
+ } else {
+ snapChildInstantly(view);
+ }
+ }
+ }
+
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (mLongPressSent) {
+ return true;
+ }
+
+ if (!mDragging) {
+ if (mCallback.getChildAtPosition(ev) != null) {
+
+ // We are dragging directly over a card, make sure that we also catch the gesture
+ // even if nobody else wants the touch event.
+ onInterceptTouchEvent(ev);
+ return true;
+ } else {
+
+ // We are not doing anything, make sure the long press callback
+ // is not still ticking like a bomb waiting to go off.
+ removeLongPressCallback();
+ return false;
+ }
+ }
+
+ mVelocityTracker.addMovement(ev);
+ final int action = ev.getAction();
+ switch (action) {
+ case MotionEvent.ACTION_OUTSIDE:
+ case MotionEvent.ACTION_MOVE:
+ if (mCurrView != null) {
+ float delta = getPos(ev) - mInitialTouchPos;
+ float absDelta = Math.abs(delta);
+ if (absDelta >= getFalsingThreshold()) {
+ mTouchAboveFalsingThreshold = true;
+ }
+ // don't let items that can't be dismissed be dragged more than
+ // maxScrollDistance
+ if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissed(mCurrView)) {
+ float size = getSize(mCurrView);
+ float maxScrollDistance = 0.25f * size;
+ if (absDelta >= size) {
+ delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
+ } else {
+ delta = maxScrollDistance * (float) Math.sin((delta/size)*(Math.PI/2));
+ }
+ }
+
+ setTranslation(mCurrView, mTranslation + delta);
+ updateSwipeProgressFromOffset(mCurrView, mCanCurrViewBeDimissed);
+ onMoveUpdate(mCurrView, mTranslation + delta, delta);
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ if (mCurrView == null) {
+ break;
+ }
+ mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, getMaxVelocity());
+ float velocity = getVelocity(mVelocityTracker);
+
+ if (!handleUpEvent(ev, mCurrView, velocity, getTranslation(mCurrView))) {
+ if (isDismissGesture(ev)) {
+ // flingadingy
+ dismissChild(mCurrView, velocity,
+ !swipedFastEnough() /* useAccelerateInterpolator */);
+ } else {
+ // snappity
+ mCallback.onDragCancelled(mCurrView);
+ snapChild(mCurrView, 0 /* leftTarget */, velocity);
+ }
+ mCurrView = null;
+ }
+ mDragging = false;
+ break;
+ }
+ return true;
+ }
+
+ private int getFalsingThreshold() {
+ float factor = mCallback.getFalsingThresholdFactor();
+ return (int) (mFalsingThreshold * factor);
+ }
+
+ private float getMaxVelocity() {
+ return MAX_DISMISS_VELOCITY * mDensityScale;
+ }
+
+ protected float getEscapeVelocity() {
+ return getUnscaledEscapeVelocity() * mDensityScale;
+ }
+
+ protected float getUnscaledEscapeVelocity() {
+ return SWIPE_ESCAPE_VELOCITY;
+ }
+
+ protected long getMaxEscapeAnimDuration() {
+ return MAX_ESCAPE_ANIMATION_DURATION;
+ }
+
+ protected boolean swipedFarEnough() {
+ float translation = getTranslation(mCurrView);
+ return DISMISS_IF_SWIPED_FAR_ENOUGH && Math.abs(translation) > 0.4 * getSize(mCurrView);
+ }
+
+ protected boolean isDismissGesture(MotionEvent ev) {
+ boolean falsingDetected = mCallback.isAntiFalsingNeeded() && !mTouchAboveFalsingThreshold;
+ return !falsingDetected && (swipedFastEnough() || swipedFarEnough())
+ && ev.getActionMasked() == MotionEvent.ACTION_UP
+ && mCallback.canChildBeDismissed(mCurrView);
+ }
+
+ protected boolean swipedFastEnough() {
+ float velocity = getVelocity(mVelocityTracker);
+ float translation = getTranslation(mCurrView);
+ boolean ret = (Math.abs(velocity) > getEscapeVelocity())
+ && (velocity > 0) == (translation > 0);
+ return ret;
+ }
+
+ protected boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
+ float translation) {
+ return false;
+ }
+
+ public interface Callback {
+ View getChildAtPosition(MotionEvent ev);
+
+ boolean canChildBeDismissed(View v);
+
+ boolean isAntiFalsingNeeded();
+
+ void onBeginDrag(View v);
+
+ void onChildDismissed(View v);
+
+ void onDragCancelled(View v);
+
+ /**
+ * Called when the child is snapped to a position.
+ *
+ * @param animView the view that was snapped.
+ * @param targetLeft the left position the view was snapped to.
+ */
+ void onChildSnappedBack(View animView, float targetLeft);
+
+ /**
+ * Updates the swipe progress on a child.
+ *
+ * @return if true, prevents the default alpha fading.
+ */
+ boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress);
+
+ /**
+ * @return The factor the falsing threshold should be multiplied with
+ */
+ float getFalsingThresholdFactor();
+ }
+
+ /**
+ * Equivalent to View.OnLongClickListener with coordinates
+ */
+ public interface LongPressListener {
+ /**
+ * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates
+ * @return whether the longpress was handled
+ */
+ boolean onLongPress(View v, int x, int y);
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/pageindicators/CaretDrawable.java b/src/com/android/launcher3/pageindicators/CaretDrawable.java
index 4789f69..0a00e24 100644
--- a/src/com/android/launcher3/pageindicators/CaretDrawable.java
+++ b/src/com/android/launcher3/pageindicators/CaretDrawable.java
@@ -24,6 +24,7 @@
import android.graphics.PixelFormat;
import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
import android.graphics.drawable.Drawable;
@@ -45,14 +46,15 @@
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(res.getColor(R.color.all_apps_caret_color));
+ mCaretPaint.setColor(res.getColor(R.color.workspace_icon_text_color));
mCaretPaint.setAntiAlias(true);
mCaretPaint.setStrokeWidth(strokeWidth);
mCaretPaint.setStyle(Paint.Style.STROKE);
mCaretPaint.setStrokeCap(Paint.Cap.SQUARE);
mCaretPaint.setStrokeJoin(Paint.Join.MITER);
- mShadowPaint.setColor(res.getColor(R.color.all_apps_caret_shadow_color));
+ 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);
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index fb9d2f7..6276c80 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -37,6 +37,7 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.util.Themes;
/**
* {@link PageIndicator} which shows dots per page. The active page is shown with the current
@@ -72,18 +73,6 @@
}
};
- /**
- * Listener for keep running the animation until the final state is reached.
- */
- private final AnimatorListenerAdapter mAnimCycleListener = new AnimatorListenerAdapter() {
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mAnimator = null;
- animateToPostion(mFinalPosition);
- }
- };
-
private final Paint mCirclePaint;
private final float mDotRadius;
private final int mActiveColor;
@@ -123,8 +112,8 @@
mDotRadius = getResources().getDimension(R.dimen.page_indicator_dot_size) / 2;
setOutlineProvider(new MyOutlineProver());
- mActiveColor = Utilities.getColorAccent(context);
- mInActiveColor = getResources().getColor(R.color.page_indicator_dot_color);
+ mActiveColor = Themes.getColorAccent(context);
+ mInActiveColor = Themes.getAttrColor(context, android.R.attr.colorControlHighlight);
mIsRtl = Utilities.isRtl(getResources());
}
@@ -136,22 +125,25 @@
currentScroll = totalScroll - currentScroll;
}
int scrollPerPage = totalScroll / (mNumPages - 1);
- int absScroll = mActivePage * scrollPerPage;
- float scrollThreshold = SHIFT_THRESHOLD * scrollPerPage;
+ int pageToLeft = currentScroll / scrollPerPage;
+ int pageToLeftScroll = pageToLeft * scrollPerPage;
+ int pageToRightScroll = pageToLeftScroll + scrollPerPage;
- if ((absScroll - currentScroll) > scrollThreshold) {
- // current scroll is before absolute scroll
- animateToPostion(mActivePage - SHIFT_PER_ANIMATION);
- } else if ((currentScroll - absScroll) > scrollThreshold) {
- // current scroll is ahead of absolute scroll
- animateToPostion(mActivePage + SHIFT_PER_ANIMATION);
+ float scrollThreshold = SHIFT_THRESHOLD * scrollPerPage;
+ if (currentScroll < pageToLeftScroll + scrollThreshold) {
+ // scroll is within the left page's threshold
+ animateToPosition(pageToLeft);
+ } else if (currentScroll > pageToRightScroll - scrollThreshold) {
+ // scroll is far enough from left page to go to the right page
+ animateToPosition(pageToLeft + 1);
} else {
- animateToPostion(mActivePage);
+ // scroll is between left and right page
+ animateToPosition(pageToLeft + SHIFT_PER_ANIMATION);
}
}
}
- private void animateToPostion(float position) {
+ private void animateToPosition(float position) {
mFinalPosition = position;
if (Math.abs(mCurrentPosition - mFinalPosition) < SHIFT_THRESHOLD) {
mCurrentPosition = mFinalPosition;
@@ -160,7 +152,7 @@
float positionForThisAnim = mCurrentPosition > mFinalPosition ?
mCurrentPosition - SHIFT_PER_ANIMATION : mCurrentPosition + SHIFT_PER_ANIMATION;
mAnimator = ObjectAnimator.ofFloat(this, CURRENT_POSITION, positionForThisAnim);
- mAnimator.addListener(mAnimCycleListener);
+ mAnimator.addListener(new AnimationCycleListener());
mAnimator.setDuration(ANIMATION_DURATION);
mAnimator.start();
}
@@ -168,7 +160,6 @@
public void stopAllAnimations() {
if (mAnimator != null) {
- mAnimator.removeAllListeners();
mAnimator.cancel();
mAnimator = null;
}
@@ -323,4 +314,25 @@
}
}
}
+
+ /**
+ * Listener for keep running the animation until the final state is reached.
+ */
+ private class AnimationCycleListener extends AnimatorListenerAdapter {
+
+ private boolean mCancelled = false;
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!mCancelled) {
+ mAnimator = null;
+ animateToPosition(mFinalPosition);
+ }
+ }
+ }
}
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
new file mode 100644
index 0000000..d4ee3b8
--- /dev/null
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -0,0 +1,849 @@
+/*
+ * 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.popup;
+
+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.Paint;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.drawable.ShapeDrawable;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.FrameLayout;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.FastBitmapDrawable;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LogAccelerateInterpolator;
+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.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.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.DeepShortcutView;
+import com.android.launcher3.shortcuts.ShortcutsItemView;
+import com.android.launcher3.util.PackageUserKey;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+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.
+ */
+@TargetApi(Build.VERSION_CODES.N)
+public class PopupContainerWithArrow extends AbstractFloatingView implements DragSource,
+ DragController.DragListener {
+
+ 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;
+
+ protected Animator mOpenCloseAnimator;
+ private boolean mDeferContainerRemoval;
+ private AnimatorSet mReduceHeightAnimatorSet;
+
+ 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);
+ // TODO: make sure the delegate works for all items, not just shortcuts.
+ mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher);
+ mIsRtl = Utilities.isRtl(getResources());
+ }
+
+ public PopupContainerWithArrow(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PopupContainerWithArrow(Context context) {
+ 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.
+ */
+ public static PopupContainerWithArrow showForIcon(BubbleTextView icon) {
+ Launcher launcher = Launcher.getLauncher(icon.getContext());
+ if (getOpen(launcher) != null) {
+ // There is already an items container open, so don't open this one.
+ icon.clearFocus();
+ return null;
+ }
+ ItemInfo itemInfo = (ItemInfo) icon.getTag();
+ if (!DeepShortcutManager.supportsShortcuts(itemInfo)) {
+ return null;
+ }
+
+ PopupDataProvider popupDataProvider = launcher.getPopupDataProvider();
+ List<String> shortcutIds = popupDataProvider.getShortcutIdsForItem(itemInfo);
+ List<NotificationKeyData> notificationKeys = popupDataProvider
+ .getNotificationKeysForItem(itemInfo);
+ List<SystemShortcut> systemShortcuts = popupDataProvider
+ .getEnabledSystemShortcutsForItem(itemInfo);
+
+ 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<String> shortcutIds,
+ final List<NotificationKeyData> notificationKeys, List<SystemShortcut> 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.
+ PopupPopulator.Item[] itemsToPopulate = PopupPopulator
+ .getItemsToPopulate(shortcutIds, notificationKeys, systemShortcuts);
+ addDummyViews(originalIcon, itemsToPopulate, notificationKeys.size() > 1);
+
+ measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset);
+
+ boolean reverseOrder = mIsAboveIcon;
+ if (reverseOrder) {
+ removeAllViews();
+ mNotificationItemView = null;
+ mShortcutsItemView = null;
+ itemsToPopulate = PopupPopulator.reverseItems(itemsToPopulate);
+ addDummyViews(originalIcon, itemsToPopulate, notificationKeys.size() > 1);
+
+ measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset);
+ }
+
+ ItemInfo originalItemInfo = (ItemInfo) originalIcon.getTag();
+ List<DeepShortcutView> shortcutViews = mShortcutsItemView == null
+ ? Collections.EMPTY_LIST
+ : mShortcutsItemView.getDeepShortcutViews(reverseOrder);
+ List<View> systemShortcutViews = mShortcutsItemView == null
+ ? Collections.EMPTY_LIST
+ : mShortcutsItemView.getSystemShortcutViews(reverseOrder);
+ if (mNotificationItemView != null) {
+ updateNotificationHeader();
+ }
+
+ // 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);
+
+ animateOpen();
+
+ mLauncher.getDragController().addDragListener(this);
+ mOriginalIcon.forceHideBadge(true);
+
+ // Load the shortcuts on a background thread and update the container as it animates.
+ final Looper workerLooper = LauncherModel.getWorkerLooper();
+ new Handler(workerLooper).postAtFrontOfQueue(PopupPopulator.createUpdateRunnable(
+ mLauncher, originalItemInfo, new Handler(Looper.getMainLooper()),
+ this, shortcutIds, shortcutViews, notificationKeys, mNotificationItemView,
+ systemShortcuts, systemShortcutViews));
+ }
+
+ private void addDummyViews(BubbleTextView originalIcon,
+ PopupPopulator.Item[] itemTypesToPopulate, boolean notificationFooterHasIcons) {
+ final Resources res = getResources();
+ final int spacing = res.getDimensionPixelSize(R.dimen.popup_items_spacing);
+ final LayoutInflater inflater = mLauncher.getLayoutInflater();
+
+ int numItems = itemTypesToPopulate.length;
+ for (int i = 0; i < numItems; i++) {
+ PopupPopulator.Item itemTypeToPopulate = itemTypesToPopulate[i];
+ PopupPopulator.Item nextItemTypeToPopulate =
+ i < numItems - 1 ? itemTypesToPopulate[i + 1] : null;
+ final View item = inflater.inflate(itemTypeToPopulate.layoutId, this, false);
+
+ if (itemTypeToPopulate == PopupPopulator.Item.NOTIFICATION) {
+ mNotificationItemView = (NotificationItemView) item;
+ int footerHeight = notificationFooterHasIcons ?
+ res.getDimensionPixelSize(R.dimen.notification_footer_height) : 0;
+ item.findViewById(R.id.footer).getLayoutParams().height = footerHeight;
+ }
+
+ boolean shouldAddBottomMargin = nextItemTypeToPopulate != null
+ && itemTypeToPopulate.isShortcut ^ nextItemTypeToPopulate.isShortcut;
+
+ item.setAccessibilityDelegate(mAccessibilityDelegate);
+ if (itemTypeToPopulate.isShortcut) {
+ if (mShortcutsItemView == null) {
+ mShortcutsItemView = (ShortcutsItemView) inflater.inflate(
+ R.layout.shortcuts_item, this, false);
+ addView(mShortcutsItemView);
+ }
+ mShortcutsItemView.addShortcutView(item, itemTypeToPopulate);
+ if (shouldAddBottomMargin) {
+ ((LayoutParams) mShortcutsItemView.getLayoutParams()).bottomMargin = spacing;
+ }
+ } else {
+ addView(item);
+ if (shouldAddBottomMargin) {
+ ((LayoutParams) item.getLayoutParams()).bottomMargin = spacing;
+ }
+ }
+ }
+ // TODO: update this, since not all items are shortcuts
+ setContentDescription(getContext().getString(R.string.shortcuts_menu_description,
+ numItems, originalIcon.getContentDescription().toString()));
+ }
+
+ 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;
+ }
+
+ private void animateOpen() {
+ setVisibility(View.VISIBLE);
+ mIsOpen = true;
+
+ final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet();
+ final int itemCount = getItemCount();
+
+ final long duration = getResources().getInteger(
+ R.integer.config_deepShortcutOpenDuration);
+ final long arrowScaleDuration = getResources().getInteger(
+ R.integer.config_deepShortcutArrowOpenDuration);
+ final long arrowScaleDelay = duration - arrowScaleDuration;
+ final long stagger = getResources().getInteger(
+ R.integer.config_deepShortcutOpenStagger);
+ final TimeInterpolator fadeInterpolator = new LogAccelerateInterpolator(100, 0);
+
+ // Animate shortcuts
+ DecelerateInterpolator interpolator = new DecelerateInterpolator();
+ for (int i = 0; i < itemCount; i++) {
+ final PopupItemView popupItemView = getItemViewAt(i);
+ popupItemView.setVisibility(INVISIBLE);
+ popupItemView.setAlpha(0);
+
+ Animator anim = popupItemView.createOpenAnimation(mIsAboveIcon, mIsLeftAligned);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ popupItemView.setVisibility(VISIBLE);
+ }
+ });
+ anim.setDuration(duration);
+ int animationIndex = mIsAboveIcon ? itemCount - i - 1 : i;
+ anim.setStartDelay(stagger * animationIndex);
+ anim.setInterpolator(interpolator);
+ shortcutAnims.play(anim);
+
+ Animator fadeAnim = ObjectAnimator.ofFloat(popupItemView, View.ALPHA, 1);
+ fadeAnim.setInterpolator(fadeInterpolator);
+ // We want the shortcut to be fully opaque before the arrow starts animating.
+ fadeAnim.setDuration(arrowScaleDelay);
+ shortcutAnims.play(fadeAnim);
+ }
+ shortcutAnims.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));
+ }
+ });
+
+ // Animate the arrow
+ mArrow.setScaleX(0);
+ mArrow.setScaleY(0);
+ Animator arrowScale = createArrowScaleAnim(1).setDuration(arrowScaleDuration);
+ arrowScale.setStartDelay(arrowScaleDelay);
+ shortcutAnims.play(arrowScale);
+
+ mOpenCloseAnimator = shortcutAnims;
+ shortcutAnims.start();
+ }
+
+ /**
+ * 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().getBounds().height();
+ 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;
+
+ if (y < dragLayer.getTop() || y + height > dragLayer.getBottom()) {
+ // The container is opening off the screen, so just center it in the drag layer instead.
+ ((FrameLayout.LayoutParams) getLayoutParams()).gravity = 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;
+ }
+
+ if (x < dragLayer.getLeft() || x + width > dragLayer.getRight()) {
+ // If we are still off screen, center horizontally too.
+ ((FrameLayout.LayoutParams) getLayoutParams()).gravity |= Gravity.CENTER_HORIZONTAL;
+ }
+
+ int gravity = ((FrameLayout.LayoutParams) getLayoutParams()).gravity;
+ if (!Gravity.isHorizontal(gravity)) {
+ setX(x);
+ }
+ if (!Gravity.isVertical(gravity)) {
+ 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(((FrameLayout.LayoutParams) getLayoutParams()).gravity)) {
+ // 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(itemAttachedToArrow.getArrowColor(mIsAboveIcon));
+ // 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;
+ }
+
+ /**
+ * Determines when the deferred drag should be started.
+ *
+ * Current behavior:
+ * - Start the drag if the touch passes a certain distance from the original touch down.
+ */
+ public DragOptions.PreDragCondition createPreDragCondition() {
+ return new DragOptions.PreDragCondition() {
+ @Override
+ public boolean shouldStartDrag(double distanceDragged) {
+ return distanceDragged > mStartDragThreshold;
+ }
+
+ @Override
+ public void onPreDragStart(DropTarget.DragObject dragObject) {
+ mOriginalIcon.setVisibility(INVISIBLE);
+ }
+
+ @Override
+ public void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted) {
+ if (!dragStarted) {
+ mOriginalIcon.setVisibility(VISIBLE);
+ mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mOriginalIcon);
+ if (!mIsAboveIcon) {
+ mOriginalIcon.setTextVisibility(false);
+ }
+ }
+ }
+ };
+ }
+
+ @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.
+ */
+ public void updateNotificationHeader(Set<PackageUserKey> updatedBadges) {
+ ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
+ PackageUserKey packageUser = PackageUserKey.fromItemInfo(itemInfo);
+ if (updatedBadges.contains(packageUser)) {
+ updateNotificationHeader();
+ }
+ }
+
+ private void updateNotificationHeader() {
+ ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
+ BadgeInfo badgeInfo = mLauncher.getPopupDataProvider().getBadgeInfoForItem(itemInfo);
+ if (mNotificationItemView != null && badgeInfo != null) {
+ IconPalette palette = mOriginalIcon.getIcon() instanceof FastBitmapDrawable
+ ? ((FastBitmapDrawable) mOriginalIcon.getIcon()).getIconPalette()
+ : null;
+ mNotificationItemView.updateHeader(badgeInfo.getNotificationCount(), palette);
+ }
+ }
+
+ public void trimNotifications(Map<PackageUserKey, BadgeInfo> updatedBadges) {
+ if (mNotificationItemView == null) {
+ return;
+ }
+ ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag();
+ BadgeInfo badgeInfo = updatedBadges.get(PackageUserKey.fromItemInfo(originalInfo));
+ if (badgeInfo == null || badgeInfo.getNotificationKeys().size() == 0) {
+ AnimatorSet removeNotification = LauncherAnimUtils.createAnimatorSet();
+ final int duration = getResources().getInteger(
+ R.integer.config_removeNotificationViewDuration);
+ final int spacing = getResources().getDimensionPixelSize(R.dimen.popup_items_spacing);
+ removeNotification.play(reduceNotificationViewHeight(
+ mNotificationItemView.getHeightMinusFooter() + spacing, duration));
+ final View removeMarginView = mIsAboveIcon ? getItemViewAt(getItemCount() - 2)
+ : mNotificationItemView;
+ if (removeMarginView != null) {
+ ValueAnimator removeMargin = ValueAnimator.ofFloat(1, 0).setDuration(duration);
+ removeMargin.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ ((MarginLayoutParams) removeMarginView.getLayoutParams()).bottomMargin
+ = (int) (spacing * (float) valueAnimator.getAnimatedValue());
+ }
+ });
+ removeNotification.play(removeMargin);
+ }
+ Animator fade = ObjectAnimator.ofFloat(mNotificationItemView, ALPHA, 0)
+ .setDuration(duration);
+ fade.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ removeView(mNotificationItemView);
+ mNotificationItemView = null;
+ if (getItemCount() == 0) {
+ close(false);
+ return;
+ }
+ }
+ });
+ removeNotification.play(fade);
+ final long arrowScaleDuration = getResources().getInteger(
+ R.integer.config_deepShortcutArrowOpenDuration);
+ Animator hideArrow = createArrowScaleAnim(0).setDuration(arrowScaleDuration);
+ hideArrow.setStartDelay(0);
+ Animator showArrow = createArrowScaleAnim(1).setDuration(arrowScaleDuration);
+ showArrow.setStartDelay((long) (duration - arrowScaleDuration * 1.5));
+ removeNotification.playSequentially(hideArrow, showArrow);
+ removeNotification.start();
+ return;
+ }
+ mNotificationItemView.trimNotifications(NotificationKeyData.extractKeysOnly(
+ 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());
+ }
+
+ /**
+ * Animates the height of the notification item and the translationY of other items accordingly.
+ */
+ public Animator reduceNotificationViewHeight(int heightToRemove, int duration) {
+ if (mReduceHeightAnimatorSet != null) {
+ mReduceHeightAnimatorSet.cancel();
+ }
+ final int translateYBy = mIsAboveIcon ? heightToRemove : -heightToRemove;
+ mReduceHeightAnimatorSet = LauncherAnimUtils.createAnimatorSet();
+ mReduceHeightAnimatorSet.play(mNotificationItemView.animateHeightRemoval(heightToRemove));
+ PropertyResetListener<View, Float> resetTranslationYListener
+ = new PropertyResetListener<>(TRANSLATION_Y, 0f);
+ for (int i = 0; i < getItemCount(); i++) {
+ final PopupItemView itemView = getItemViewAt(i);
+ if (!mIsAboveIcon && itemView == mNotificationItemView) {
+ // The notification view is already in the right place when container is below icon.
+ continue;
+ }
+ ValueAnimator translateItem = ObjectAnimator.ofFloat(itemView, TRANSLATION_Y,
+ itemView.getTranslationY() + translateYBy).setDuration(duration);
+ translateItem.addListener(resetTranslationYListener);
+ mReduceHeightAnimatorSet.play(translateItem);
+ }
+ mReduceHeightAnimatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mIsAboveIcon) {
+ // All the items, including the notification item, translated down, but the
+ // container itself did not. This means the items would jump back to their
+ // original translation unless we update the container's translationY here.
+ setTranslationY(getTranslationY() + translateYBy);
+ }
+ mReduceHeightAnimatorSet = null;
+ }
+ });
+ return mReduceHeightAnimatorSet;
+ }
+
+ @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();
+ }
+ }
+
+ @Override
+ public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+ // Either the original icon or one of the shortcuts was dragged.
+ // Hide the container, but don't remove it yet because that interferes with touch events.
+ mDeferContainerRemoval = true;
+ animateClose();
+ }
+
+ @Override
+ public void onDragEnd() {
+ if (!mIsOpen) {
+ if (mOpenCloseAnimator != null) {
+ // Close animation is running.
+ mDeferContainerRemoval = false;
+ } else {
+ // Close animation is not running.
+ if (mDeferContainerRemoval) {
+ closeComplete();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
+ target.itemType = ItemType.DEEPSHORTCUT;
+ targetParent.containerType = ContainerType.DEEPSHORTCUTS;
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ if (animate) {
+ animateClose();
+ } else {
+ closeComplete();
+ }
+ }
+
+ protected void animateClose() {
+ if (!mIsOpen) {
+ return;
+ }
+ if (mOpenCloseAnimator != null) {
+ mOpenCloseAnimator.cancel();
+ }
+ mIsOpen = false;
+
+ final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet();
+ final int itemCount = getItemCount();
+ int numOpenShortcuts = 0;
+ for (int i = 0; i < itemCount; i++) {
+ if (getItemViewAt(i).isOpenOrOpening()) {
+ numOpenShortcuts++;
+ }
+ }
+ final long duration = getResources().getInteger(
+ R.integer.config_deepShortcutCloseDuration);
+ final long arrowScaleDuration = getResources().getInteger(
+ R.integer.config_deepShortcutArrowOpenDuration);
+ final long stagger = getResources().getInteger(
+ R.integer.config_deepShortcutCloseStagger);
+ final TimeInterpolator fadeInterpolator = new LogAccelerateInterpolator(100, 0);
+
+ int firstOpenItemIndex = mIsAboveIcon ? itemCount - numOpenShortcuts : 0;
+ for (int i = firstOpenItemIndex; i < firstOpenItemIndex + numOpenShortcuts; i++) {
+ final PopupItemView view = getItemViewAt(i);
+ Animator anim;
+ anim = view.createCloseAnimation(mIsAboveIcon, mIsLeftAligned, duration);
+ int animationIndex = mIsAboveIcon ? i - firstOpenItemIndex
+ : numOpenShortcuts - i - 1;
+ anim.setStartDelay(stagger * animationIndex);
+
+ Animator fadeAnim = ObjectAnimator.ofFloat(view, View.ALPHA, 0);
+ // Don't start fading until the arrow is gone.
+ fadeAnim.setStartDelay(stagger * animationIndex + arrowScaleDuration);
+ fadeAnim.setDuration(duration - arrowScaleDuration);
+ fadeAnim.setInterpolator(fadeInterpolator);
+ shortcutAnims.play(fadeAnim);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setVisibility(INVISIBLE);
+ }
+ });
+ shortcutAnims.play(anim);
+ }
+ Animator arrowAnim = createArrowScaleAnim(0).setDuration(arrowScaleDuration);
+ arrowAnim.setStartDelay(0);
+ shortcutAnims.play(arrowAnim);
+
+ shortcutAnims.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mOpenCloseAnimator = null;
+ if (mDeferContainerRemoval) {
+ setVisibility(INVISIBLE);
+ } else {
+ closeComplete();
+ }
+ }
+ });
+ mOpenCloseAnimator = shortcutAnims;
+ shortcutAnims.start();
+ mOriginalIcon.forceHideBadge(false);
+ }
+
+ /**
+ * Closes the folder without animation.
+ */
+ protected void closeComplete() {
+ if (mOpenCloseAnimator != null) {
+ mOpenCloseAnimator.cancel();
+ mOpenCloseAnimator = null;
+ }
+ mIsOpen = false;
+ mDeferContainerRemoval = false;
+ boolean isInHotseat = ((ItemInfo) mOriginalIcon.getTag()).container
+ == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+ mOriginalIcon.setTextVisibility(!isInHotseat);
+ 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;
+ }
+}
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
new file mode 100644
index 0000000..de9f25e
--- /dev/null
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.popup;
+
+import android.content.ComponentName;
+import android.service.notification.StatusBarNotification;
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+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.notification.NotificationInfo;
+import com.android.launcher3.notification.NotificationKeyData;
+import com.android.launcher3.notification.NotificationListener;
+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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Provides data for the popup menu that appears after long-clicking on apps.
+ */
+public class PopupDataProvider implements NotificationListener.NotificationsChangedListener {
+
+ private static final boolean LOGD = false;
+ private static final String TAG = "PopupDataProvider";
+
+ /** Note that these are in order of priority. */
+ private static final SystemShortcut[] SYSTEM_SHORTCUTS = new SystemShortcut[] {
+ new SystemShortcut.AppInfo(),
+ new SystemShortcut.Widgets(),
+ };
+
+ private final Launcher mLauncher;
+
+ /** Maps launcher activity components to their list of shortcut ids. */
+ private MultiHashMap<ComponentKey, String> mDeepShortcutMap = new MultiHashMap<>();
+ /** Maps packages to their BadgeInfo's . */
+ private Map<PackageUserKey, BadgeInfo> mPackageUserToBadgeInfos = new HashMap<>();
+
+ public PopupDataProvider(Launcher launcher) {
+ mLauncher = launcher;
+ }
+
+ @Override
+ public void onNotificationPosted(PackageUserKey postedPackageUserKey,
+ NotificationKeyData notificationKey, boolean shouldBeFilteredOut) {
+ BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(postedPackageUserKey);
+ boolean badgeShouldBeRefreshed;
+ if (badgeInfo == null) {
+ if (!shouldBeFilteredOut) {
+ BadgeInfo newBadgeInfo = new BadgeInfo(postedPackageUserKey);
+ newBadgeInfo.addOrUpdateNotificationKey(notificationKey);
+ mPackageUserToBadgeInfos.put(postedPackageUserKey, newBadgeInfo);
+ badgeShouldBeRefreshed = true;
+ } else {
+ badgeShouldBeRefreshed = false;
+ }
+ } else {
+ badgeShouldBeRefreshed = shouldBeFilteredOut
+ ? badgeInfo.removeNotificationKey(notificationKey)
+ : badgeInfo.addOrUpdateNotificationKey(notificationKey);
+ if (badgeInfo.getNotificationKeys().size() == 0) {
+ mPackageUserToBadgeInfos.remove(postedPackageUserKey);
+ }
+ }
+ updateLauncherIconBadges(Utilities.singletonHashSet(postedPackageUserKey),
+ badgeShouldBeRefreshed);
+ }
+
+ @Override
+ public void onNotificationRemoved(PackageUserKey removedPackageUserKey,
+ NotificationKeyData notificationKey) {
+ BadgeInfo oldBadgeInfo = mPackageUserToBadgeInfos.get(removedPackageUserKey);
+ if (oldBadgeInfo != null && oldBadgeInfo.removeNotificationKey(notificationKey)) {
+ if (oldBadgeInfo.getNotificationKeys().size() == 0) {
+ mPackageUserToBadgeInfos.remove(removedPackageUserKey);
+ }
+ updateLauncherIconBadges(Utilities.singletonHashSet(removedPackageUserKey));
+
+ PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher);
+ if (openContainer != null) {
+ openContainer.trimNotifications(mPackageUserToBadgeInfos);
+ }
+ }
+ }
+
+ @Override
+ public void onNotificationFullRefresh(List<StatusBarNotification> activeNotifications) {
+ if (activeNotifications == null) return;
+ // This will contain the PackageUserKeys which have updated badges.
+ HashMap<PackageUserKey, BadgeInfo> updatedBadges = new HashMap<>(mPackageUserToBadgeInfos);
+ mPackageUserToBadgeInfos.clear();
+ for (StatusBarNotification notification : activeNotifications) {
+ PackageUserKey packageUserKey = PackageUserKey.fromNotification(notification);
+ BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(packageUserKey);
+ if (badgeInfo == null) {
+ badgeInfo = new BadgeInfo(packageUserKey);
+ mPackageUserToBadgeInfos.put(packageUserKey, badgeInfo);
+ }
+ badgeInfo.addOrUpdateNotificationKey(NotificationKeyData
+ .fromNotification(notification));
+ }
+
+ // Add and remove from updatedBadges so it contains the PackageUserKeys of updated badges.
+ for (PackageUserKey packageUserKey : mPackageUserToBadgeInfos.keySet()) {
+ BadgeInfo prevBadge = updatedBadges.get(packageUserKey);
+ BadgeInfo newBadge = mPackageUserToBadgeInfos.get(packageUserKey);
+ if (prevBadge == null) {
+ updatedBadges.put(packageUserKey, newBadge);
+ } else {
+ if (!prevBadge.shouldBeInvalidated(newBadge)) {
+ updatedBadges.remove(packageUserKey);
+ }
+ }
+ }
+
+ if (!updatedBadges.isEmpty()) {
+ updateLauncherIconBadges(updatedBadges.keySet());
+ }
+
+ PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher);
+ if (openContainer != null) {
+ openContainer.trimNotifications(updatedBadges);
+ }
+ }
+
+ private void updateLauncherIconBadges(Set<PackageUserKey> updatedBadges) {
+ updateLauncherIconBadges(updatedBadges, true);
+ }
+
+ /**
+ * Updates the icons on launcher (workspace, folders, all apps) to refresh their badges.
+ * @param updatedBadges The packages whose badges should be refreshed (either a notification was
+ * added or removed, or the badge should show the notification icon).
+ * @param shouldRefresh An optional parameter that will allow us to only refresh badges that
+ * have actually changed. If a notification updated its content but not
+ * its count or icon, then the badge doesn't change.
+ */
+ private void updateLauncherIconBadges(Set<PackageUserKey> updatedBadges,
+ boolean shouldRefresh) {
+ Iterator<PackageUserKey> iterator = updatedBadges.iterator();
+ while (iterator.hasNext()) {
+ BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(iterator.next());
+ if (badgeInfo != null && !updateBadgeIcon(badgeInfo) && !shouldRefresh) {
+ // The notification icon isn't used, and the badge hasn't changed
+ // so there is no update to be made.
+ iterator.remove();
+ }
+ }
+ if (!updatedBadges.isEmpty()) {
+ mLauncher.updateIconBadges(updatedBadges);
+ }
+ }
+
+ /**
+ * Determines whether the badge should show a notification icon rather than a number,
+ * and sets that icon on the BadgeInfo if so.
+ * @param badgeInfo The badge to update with an icon (null if it shouldn't show one).
+ * @return Whether the badge icon potentially changed (true unless it stayed null).
+ */
+ private boolean updateBadgeIcon(BadgeInfo badgeInfo) {
+ boolean hadNotificationToShow = badgeInfo.hasNotificationToShow();
+ NotificationInfo notificationInfo = null;
+ NotificationListener notificationListener = NotificationListener.getInstanceIfConnected();
+ if (notificationListener != null && badgeInfo.getNotificationKeys().size() >= 1) {
+ // Look for the most recent notification that has an icon that should be shown in badge.
+ for (NotificationKeyData notificationKeyData : badgeInfo.getNotificationKeys()) {
+ String notificationKey = notificationKeyData.notificationKey;
+ StatusBarNotification[] activeNotifications = notificationListener
+ .getActiveNotifications(new String[]{notificationKey});
+ if (activeNotifications.length == 1) {
+ notificationInfo = new NotificationInfo(mLauncher, activeNotifications[0]);
+ if (notificationInfo.shouldShowIconInBadge()) {
+ // Found an appropriate icon.
+ break;
+ } else {
+ // Keep looking.
+ notificationInfo = null;
+ }
+ }
+ }
+ }
+ badgeInfo.setNotificationToShow(notificationInfo);
+ return hadNotificationToShow || badgeInfo.hasNotificationToShow();
+ }
+
+ public void setDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy) {
+ mDeepShortcutMap = deepShortcutMapCopy;
+ if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap);
+ }
+
+ public List<String> getShortcutIdsForItem(ItemInfo info) {
+ if (!DeepShortcutManager.supportsShortcuts(info)) {
+ return Collections.EMPTY_LIST;
+ }
+ ComponentName component = info.getTargetComponent();
+ if (component == null) {
+ return Collections.EMPTY_LIST;
+ }
+
+ List<String> ids = mDeepShortcutMap.get(new ComponentKey(component, info.user));
+ return ids == null ? Collections.EMPTY_LIST : ids;
+ }
+
+ public BadgeInfo getBadgeInfoForItem(ItemInfo info) {
+ if (!DeepShortcutManager.supportsShortcuts(info)) {
+ return null;
+ }
+
+ return mPackageUserToBadgeInfos.get(PackageUserKey.fromItemInfo(info));
+ }
+
+ public @NonNull List<NotificationKeyData> getNotificationKeysForItem(ItemInfo info) {
+ BadgeInfo badgeInfo = getBadgeInfoForItem(info);
+ return badgeInfo == null ? Collections.EMPTY_LIST : badgeInfo.getNotificationKeys();
+ }
+
+ /** This makes a potentially expensive binder call and should be run on a background thread. */
+ public @NonNull List<StatusBarNotification> getStatusBarNotificationsForKeys(
+ List<NotificationKeyData> notificationKeys) {
+ NotificationListener notificationListener = NotificationListener.getInstanceIfConnected();
+ return notificationListener == null ? Collections.EMPTY_LIST
+ : notificationListener.getNotificationsForKeys(notificationKeys);
+ }
+
+ public @NonNull List<SystemShortcut> getEnabledSystemShortcutsForItem(ItemInfo info) {
+ List<SystemShortcut> systemShortcuts = new ArrayList<>();
+ for (SystemShortcut systemShortcut : SYSTEM_SHORTCUTS) {
+ if (systemShortcut.getOnClickListener(mLauncher, info) != null) {
+ systemShortcuts.add(systemShortcut);
+ }
+ }
+ return systemShortcuts;
+ }
+
+ public void cancelNotification(String notificationKey) {
+ NotificationListener notificationListener = NotificationListener.getInstanceIfConnected();
+ if (notificationListener == null) {
+ return;
+ }
+ notificationListener.cancelNotification(notificationKey);
+ }
+}
diff --git a/src/com/android/launcher3/popup/PopupItemView.java b/src/com/android/launcher3/popup/PopupItemView.java
new file mode 100644
index 0000000..384f554
--- /dev/null
+++ b/src/com/android/launcher3/popup/PopupItemView.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.popup;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.launcher3.LogAccelerateInterpolator;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.PillRevealOutlineProvider;
+
+/**
+ * An abstract {@link FrameLayout} that supports animating an item's content
+ * (e.g. icon and text) separate from the item's background.
+ */
+public abstract class PopupItemView extends FrameLayout
+ implements ValueAnimator.AnimatorUpdateListener {
+
+ protected static final Point sTempPoint = new Point();
+
+ protected final Rect mPillRect;
+ private float mOpenAnimationProgress;
+ protected final boolean mIsRtl;
+ protected View mIconView;
+
+ private final Paint mBackgroundClipPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
+ private final Matrix mMatrix = new Matrix();
+ private Bitmap mRoundedCornerBitmap;
+
+ public PopupItemView(Context context) {
+ this(context, null, 0);
+ }
+
+ public PopupItemView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PopupItemView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ mPillRect = new Rect();
+
+ // Initialize corner clipping Bitmap and Paint.
+ int radius = (int) getBackgroundRadius();
+ mRoundedCornerBitmap = Bitmap.createBitmap(radius, radius, Bitmap.Config.ALPHA_8);
+ Canvas canvas = new Canvas();
+ canvas.setBitmap(mRoundedCornerBitmap);
+ canvas.drawArc(0, 0, radius*2, radius*2, 180, 90, true, mBackgroundClipPaint);
+ mBackgroundClipPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
+
+ mIsRtl = Utilities.isRtl(getResources());
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mIconView = findViewById(R.id.popup_item_icon);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ mPillRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null);
+ super.dispatchDraw(canvas);
+
+ int cornerWidth = mRoundedCornerBitmap.getWidth();
+ int cornerHeight = mRoundedCornerBitmap.getHeight();
+ // Clip top left corner.
+ mMatrix.reset();
+ canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
+ // Clip top right corner.
+ mMatrix.setRotate(90, cornerWidth / 2, cornerHeight / 2);
+ mMatrix.postTranslate(canvas.getWidth() - cornerWidth, 0);
+ canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
+ // Clip bottom right corner.
+ mMatrix.setRotate(180, cornerWidth / 2, cornerHeight / 2);
+ mMatrix.postTranslate(canvas.getWidth() - cornerWidth, canvas.getHeight() - cornerHeight);
+ canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
+ // Clip bottom left corner.
+ mMatrix.setRotate(270, cornerWidth / 2, cornerHeight / 2);
+ mMatrix.postTranslate(0, canvas.getHeight() - cornerHeight);
+ canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
+
+ canvas.restoreToCount(saveCount);
+ }
+
+ /**
+ * Creates an animator to play when the shortcut container is being opened.
+ */
+ public Animator createOpenAnimation(boolean isContainerAboveIcon, boolean pivotLeft) {
+ Point center = getIconCenter();
+ int arrowCenter = getResources().getDimensionPixelSize(pivotLeft ^ mIsRtl ?
+ R.dimen.popup_arrow_horizontal_center_start:
+ R.dimen.popup_arrow_horizontal_center_end);
+ ValueAnimator openAnimator = new ZoomRevealOutlineProvider(center.x, center.y,
+ mPillRect, this, mIconView, isContainerAboveIcon, pivotLeft, arrowCenter)
+ .createRevealAnimator(this, false);
+ mOpenAnimationProgress = 0f;
+ openAnimator.addUpdateListener(this);
+ return openAnimator;
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ mOpenAnimationProgress = valueAnimator.getAnimatedFraction();
+ }
+
+ public boolean isOpenOrOpening() {
+ return mOpenAnimationProgress > 0;
+ }
+
+ /**
+ * Creates an animator to play when the shortcut container is being closed.
+ */
+ public Animator createCloseAnimation(boolean isContainerAboveIcon, boolean pivotLeft,
+ long duration) {
+ Point center = getIconCenter();
+ int arrowCenter = getResources().getDimensionPixelSize(pivotLeft ^ mIsRtl ?
+ R.dimen.popup_arrow_horizontal_center_start :
+ R.dimen.popup_arrow_horizontal_center_end);
+ ValueAnimator closeAnimator = new ZoomRevealOutlineProvider(center.x, center.y,
+ mPillRect, this, mIconView, isContainerAboveIcon, pivotLeft, arrowCenter)
+ .createRevealAnimator(this, true);
+ // Scale down the duration and interpolator according to the progress
+ // that the open animation was at when the close started.
+ closeAnimator.setDuration((long) (duration * mOpenAnimationProgress));
+ closeAnimator.setInterpolator(new CloseInterpolator(mOpenAnimationProgress));
+ closeAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mOpenAnimationProgress = 0;
+ }
+ });
+ return closeAnimator;
+ }
+
+ /**
+ * Returns the position of the center of the icon relative to the container.
+ */
+ public Point getIconCenter() {
+ sTempPoint.y = getMeasuredHeight() / 2;
+ sTempPoint.x = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height) / 2;
+ if (Utilities.isRtl(getResources())) {
+ sTempPoint.x = getMeasuredWidth() - sTempPoint.x;
+ }
+ return sTempPoint;
+ }
+
+ protected float getBackgroundRadius() {
+ return getResources().getDimensionPixelSize(R.dimen.bg_round_rect_radius);
+ }
+
+ public abstract int getArrowColor(boolean isArrowAttachedToBottom);
+
+ /**
+ * Extension of {@link PillRevealOutlineProvider} which scales the icon based on the height.
+ */
+ private static class ZoomRevealOutlineProvider extends PillRevealOutlineProvider {
+
+ private final View mTranslateView;
+ private final View mZoomView;
+
+ private final float mFullHeight;
+ private final float mTranslateYMultiplier;
+
+ private final boolean mPivotLeft;
+ private final float mTranslateX;
+ private final float mArrowCenter;
+
+ public ZoomRevealOutlineProvider(int x, int y, Rect pillRect, PopupItemView translateView,
+ View zoomView, boolean isContainerAboveIcon, boolean pivotLeft, float arrowCenter) {
+ super(x, y, pillRect, translateView.getBackgroundRadius());
+ mTranslateView = translateView;
+ mZoomView = zoomView;
+ mFullHeight = pillRect.height();
+
+ mTranslateYMultiplier = isContainerAboveIcon ? 0.5f : -0.5f;
+
+ mPivotLeft = pivotLeft;
+ mTranslateX = pivotLeft ? arrowCenter : pillRect.right - arrowCenter;
+ mArrowCenter = arrowCenter;
+ }
+
+ @Override
+ public void setProgress(float progress) {
+ super.setProgress(progress);
+
+ if (mZoomView != null) {
+ mZoomView.setScaleX(progress);
+ mZoomView.setScaleY(progress);
+ }
+
+ float height = mOutline.height();
+ mTranslateView.setTranslationY(mTranslateYMultiplier * (mFullHeight - height));
+
+ float offsetX = Math.min(mOutline.width(), mArrowCenter);
+ float pivotX = mPivotLeft ? (mOutline.left + offsetX) : (mOutline.right - offsetX);
+ mTranslateView.setTranslationX(mTranslateX - pivotX);
+ }
+ }
+
+ /**
+ * An interpolator that reverses the current open animation progress.
+ */
+ private static class CloseInterpolator extends LogAccelerateInterpolator {
+ private float mStartProgress;
+ private float mRemainingProgress;
+
+ /**
+ * @param openAnimationProgress The progress that the open interpolator ended at.
+ */
+ public CloseInterpolator(float openAnimationProgress) {
+ super(100, 0);
+ mStartProgress = 1f - openAnimationProgress;
+ mRemainingProgress = openAnimationProgress;
+ }
+
+ @Override
+ public float getInterpolation(float v) {
+ return mStartProgress + super.getInterpolation(v) * mRemainingProgress;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
new file mode 100644
index 0000000..c62d877
--- /dev/null
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.popup;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.notification.NotificationInfo;
+import com.android.launcher3.notification.NotificationItemView;
+import com.android.launcher3.notification.NotificationKeyData;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.DeepShortcutView;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.util.PackageUserKey;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Contains logic relevant to populating a {@link PopupContainerWithArrow}. In particular,
+ * this class determines which items appear in the container, and in what order.
+ */
+public class PopupPopulator {
+
+ public static final int MAX_ITEMS = 4;
+ @VisibleForTesting static final int NUM_DYNAMIC = 2;
+ private static final int MAX_SHORTCUTS_IF_NOTIFICATIONS = 2;
+
+ public enum Item {
+ SHORTCUT(R.layout.deep_shortcut, true),
+ NOTIFICATION(R.layout.notification, false),
+ SYSTEM_SHORTCUT(R.layout.system_shortcut, true),
+ SYSTEM_SHORTCUT_ICON(R.layout.system_shortcut_icon_only, true);
+
+ public final int layoutId;
+ public final boolean isShortcut;
+
+ Item(int layoutId, boolean isShortcut) {
+ this.layoutId = layoutId;
+ this.isShortcut = isShortcut;
+ }
+ }
+
+ public static @NonNull Item[] getItemsToPopulate(@NonNull List<String> shortcutIds,
+ @NonNull List<NotificationKeyData> notificationKeys,
+ @NonNull List<SystemShortcut> systemShortcuts) {
+ boolean hasNotifications = notificationKeys.size() > 0;
+ int numNotificationItems = hasNotifications ? 1 : 0;
+ int numShortcuts = shortcutIds.size();
+ if (hasNotifications && numShortcuts > MAX_SHORTCUTS_IF_NOTIFICATIONS) {
+ numShortcuts = MAX_SHORTCUTS_IF_NOTIFICATIONS;
+ }
+ int numItems = Math.min(MAX_ITEMS, numShortcuts + numNotificationItems)
+ + systemShortcuts.size();
+ Item[] items = new Item[numItems];
+ for (int i = 0; i < numItems; i++) {
+ items[i] = Item.SHORTCUT;
+ }
+ if (hasNotifications) {
+ // The notification layout is always first.
+ items[0] = Item.NOTIFICATION;
+ }
+ // The system shortcuts are always last.
+ boolean iconsOnly = !shortcutIds.isEmpty();
+ for (int i = 0; i < systemShortcuts.size(); i++) {
+ items[numItems - 1 - i] = iconsOnly ? Item.SYSTEM_SHORTCUT_ICON : Item.SYSTEM_SHORTCUT;
+ }
+ return items;
+ }
+
+ public static Item[] reverseItems(Item[] items) {
+ if (items == null) return null;
+ int numItems = items.length;
+ Item[] reversedArray = new Item[numItems];
+ for (int i = 0; i < numItems; i++) {
+ reversedArray[i] = items[numItems - i - 1];
+ }
+ return reversedArray;
+ }
+
+ /**
+ * Sorts shortcuts in rank order, with manifest shortcuts coming before dynamic shortcuts.
+ */
+ private static final Comparator<ShortcutInfoCompat> SHORTCUT_RANK_COMPARATOR
+ = new Comparator<ShortcutInfoCompat>() {
+ @Override
+ public int compare(ShortcutInfoCompat a, ShortcutInfoCompat b) {
+ if (a.isDeclaredInManifest() && !b.isDeclaredInManifest()) {
+ return -1;
+ }
+ if (!a.isDeclaredInManifest() && b.isDeclaredInManifest()) {
+ return 1;
+ }
+ return Integer.compare(a.getRank(), b.getRank());
+ }
+ };
+
+ /**
+ * Filters the shortcuts so that only MAX_ITEMS or fewer shortcuts are retained.
+ * We want the filter to include both static and dynamic shortcuts, so we always
+ * include NUM_DYNAMIC dynamic shortcuts, if at least that many are present.
+ *
+ * @param shortcutIdToRemoveFirst An id that should be filtered out first, if any.
+ * @return a subset of shortcuts, in sorted order, with size <= MAX_ITEMS.
+ */
+ public static List<ShortcutInfoCompat> sortAndFilterShortcuts(
+ List<ShortcutInfoCompat> shortcuts, @Nullable String shortcutIdToRemoveFirst) {
+ // Remove up to one specific shortcut before sorting and doing somewhat fancy filtering.
+ if (shortcutIdToRemoveFirst != null) {
+ Iterator<ShortcutInfoCompat> shortcutIterator = shortcuts.iterator();
+ while (shortcutIterator.hasNext()) {
+ if (shortcutIterator.next().getId().equals(shortcutIdToRemoveFirst)) {
+ shortcutIterator.remove();
+ break;
+ }
+ }
+ }
+
+ Collections.sort(shortcuts, SHORTCUT_RANK_COMPARATOR);
+ if (shortcuts.size() <= MAX_ITEMS) {
+ return shortcuts;
+ }
+
+ // The list of shortcuts is now sorted with static shortcuts followed by dynamic
+ // shortcuts. We want to preserve this order, but only keep MAX_ITEMS.
+ List<ShortcutInfoCompat> filteredShortcuts = new ArrayList<>(MAX_ITEMS);
+ int numDynamic = 0;
+ int size = shortcuts.size();
+ for (int i = 0; i < size; i++) {
+ ShortcutInfoCompat shortcut = shortcuts.get(i);
+ int filteredSize = filteredShortcuts.size();
+ if (filteredSize < MAX_ITEMS) {
+ // Always add the first MAX_ITEMS to the filtered list.
+ filteredShortcuts.add(shortcut);
+ if (shortcut.isDynamic()) {
+ numDynamic++;
+ }
+ continue;
+ }
+ // At this point, we have MAX_ITEMS already, but they may all be static.
+ // If there are dynamic shortcuts, remove static shortcuts to add them.
+ if (shortcut.isDynamic() && numDynamic < NUM_DYNAMIC) {
+ numDynamic++;
+ int lastStaticIndex = filteredSize - numDynamic;
+ filteredShortcuts.remove(lastStaticIndex);
+ filteredShortcuts.add(shortcut);
+ }
+ }
+ return filteredShortcuts;
+ }
+
+ public static Runnable createUpdateRunnable(final Launcher launcher, final ItemInfo originalInfo,
+ final Handler uiHandler, final PopupContainerWithArrow container,
+ final List<String> shortcutIds, final List<DeepShortcutView> shortcutViews,
+ final List<NotificationKeyData> notificationKeys,
+ final NotificationItemView notificationView, final List<SystemShortcut> systemShortcuts,
+ final List<View> systemShortcutViews) {
+ final ComponentName activity = originalInfo.getTargetComponent();
+ final UserHandle user = originalInfo.user;
+ return new Runnable() {
+ @Override
+ public void run() {
+ if (notificationView != null) {
+ List<StatusBarNotification> notifications = launcher.getPopupDataProvider()
+ .getStatusBarNotificationsForKeys(notificationKeys);
+ List<NotificationInfo> infos = new ArrayList<>(notifications.size());
+ for (int i = 0; i < notifications.size(); i++) {
+ StatusBarNotification notification = notifications.get(i);
+ infos.add(new NotificationInfo(launcher, notification));
+ }
+ uiHandler.post(new UpdateNotificationChild(notificationView, infos));
+ }
+
+ List<ShortcutInfoCompat> shortcuts = DeepShortcutManager.getInstance(launcher)
+ .queryForShortcutsContainer(activity, shortcutIds, user);
+ String shortcutIdToDeDupe = notificationKeys.isEmpty() ? null
+ : notificationKeys.get(0).shortcutId;
+ shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts, shortcutIdToDeDupe);
+ for (int i = 0; i < shortcuts.size() && i < shortcutViews.size(); i++) {
+ final ShortcutInfoCompat shortcut = shortcuts.get(i);
+ ShortcutInfo si = new ShortcutInfo(shortcut, launcher);
+ // Use unbadged icon for the menu.
+ si.iconBitmap = LauncherIcons.createShortcutIcon(
+ shortcut, launcher, false /* badged */);
+ si.rank = i;
+ uiHandler.post(new UpdateShortcutChild(container, shortcutViews.get(i),
+ si, shortcut));
+ }
+
+ // This ensures that mLauncher.getWidgetsForPackageUser()
+ // doesn't return null (it puts all the widgets in memory).
+ for (int i = 0; i < systemShortcuts.size(); i++) {
+ final SystemShortcut systemShortcut = systemShortcuts.get(i);
+ uiHandler.post(new UpdateSystemShortcutChild(container,
+ systemShortcutViews.get(i), systemShortcut, launcher, originalInfo));
+ }
+ uiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ launcher.refreshAndBindWidgetsForPackageUser(
+ PackageUserKey.fromItemInfo(originalInfo));
+ }
+ });
+ }
+ };
+ }
+
+ /** Updates the shortcut child of this container based on the given shortcut info. */
+ private static class UpdateShortcutChild implements Runnable {
+ private final PopupContainerWithArrow mContainer;
+ private final DeepShortcutView mShortcutChild;
+ private final ShortcutInfo mShortcutChildInfo;
+ private final ShortcutInfoCompat mDetail;
+
+ public UpdateShortcutChild(PopupContainerWithArrow container, DeepShortcutView shortcutChild,
+ ShortcutInfo shortcutChildInfo, ShortcutInfoCompat detail) {
+ mContainer = container;
+ mShortcutChild = shortcutChild;
+ mShortcutChildInfo = shortcutChildInfo;
+ mDetail = detail;
+ }
+
+ @Override
+ public void run() {
+ mShortcutChild.applyShortcutInfo(mShortcutChildInfo, mDetail,
+ mContainer.mShortcutsItemView);
+ }
+ }
+
+ /** Updates the notification child based on the given notification info. */
+ private static class UpdateNotificationChild implements Runnable {
+ private NotificationItemView mNotificationView;
+ private List<NotificationInfo> mNotificationInfos;
+
+ public UpdateNotificationChild(NotificationItemView notificationView,
+ List<NotificationInfo> notificationInfos) {
+ mNotificationView = notificationView;
+ mNotificationInfos = notificationInfos;
+ }
+
+ @Override
+ public void run() {
+ mNotificationView.applyNotificationInfos(mNotificationInfos);
+ }
+ }
+
+ /** Updates the system shortcut child based on the given shortcut info. */
+ private static class UpdateSystemShortcutChild implements Runnable {
+
+ private final PopupContainerWithArrow mContainer;
+ private final View mSystemShortcutChild;
+ private final SystemShortcut mSystemShortcutInfo;
+ private final Launcher mLauncher;
+ private final ItemInfo mItemInfo;
+
+ public UpdateSystemShortcutChild(PopupContainerWithArrow container, View systemShortcutChild,
+ SystemShortcut systemShortcut, Launcher launcher, ItemInfo originalInfo) {
+ mContainer = container;
+ mSystemShortcutChild = systemShortcutChild;
+ mSystemShortcutInfo = systemShortcut;
+ mLauncher = launcher;
+ mItemInfo = originalInfo;
+ }
+
+ @Override
+ public void run() {
+ final Context context = mSystemShortcutChild.getContext();
+ initializeSystemShortcut(context, mSystemShortcutChild, mSystemShortcutInfo);
+ mSystemShortcutChild.setOnClickListener(mSystemShortcutInfo
+ .getOnClickListener(mLauncher, mItemInfo));
+ }
+ }
+
+ public static void initializeSystemShortcut(Context context, View view, SystemShortcut info) {
+ if (view instanceof DeepShortcutView) {
+ // Expanded system shortcut, with both icon and text shown on white background.
+ final DeepShortcutView shortcutView = (DeepShortcutView) view;
+ shortcutView.getIconView().setBackground(info.getIcon(context,
+ android.R.attr.textColorTertiary));
+ shortcutView.getBubbleText().setText(info.getLabel(context));
+ } else if (view instanceof ImageView) {
+ // Only the system shortcut icon shows on a gray background header.
+ final ImageView shortcutIcon = (ImageView) view;
+ shortcutIcon.setImageDrawable(info.getIcon(context,
+ android.R.attr.textColorHint));
+ shortcutIcon.setContentDescription(info.getLabel(context));
+ }
+ view.setTag(info);
+ }
+}
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
new file mode 100644
index 0000000..f158f71
--- /dev/null
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -0,0 +1,94 @@
+package com.android.launcher3.popup;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.View;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.InfoDropTarget;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.Themes;
+import com.android.launcher3.widget.WidgetsBottomSheet;
+
+import java.util.List;
+
+/**
+ * Represents a system shortcut for a given app. The shortcut should have a static label and
+ * icon, and an onClickListener that depends on the item that the shortcut services.
+ *
+ * Example system shortcuts, defined as inner classes, include Widgets and AppInfo.
+ */
+public abstract class SystemShortcut {
+ private final int mIconResId;
+ private final int mLabelResId;
+
+ public SystemShortcut(int iconResId, int labelResId) {
+ mIconResId = iconResId;
+ mLabelResId = labelResId;
+ }
+
+ public Drawable getIcon(Context context, int colorAttr) {
+ Drawable icon = context.getResources().getDrawable(mIconResId, context.getTheme()).mutate();
+ icon.setTint(Themes.getAttrColor(context, colorAttr));
+ return icon;
+ }
+
+ public String getLabel(Context context) {
+ return context.getString(mLabelResId);
+ }
+
+ public abstract View.OnClickListener getOnClickListener(final Launcher launcher,
+ final ItemInfo itemInfo);
+
+ public static class Widgets extends SystemShortcut {
+
+ public Widgets() {
+ super(R.drawable.ic_widget, R.string.widget_button_text);
+ }
+
+ @Override
+ public View.OnClickListener getOnClickListener(final Launcher launcher,
+ final ItemInfo itemInfo) {
+ final List<WidgetItem> widgets = launcher.getWidgetsForPackageUser(new PackageUserKey(
+ itemInfo.getTargetComponent().getPackageName(), itemInfo.user));
+ if (widgets == null) {
+ return null;
+ }
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ AbstractFloatingView.closeAllOpenViews(launcher);
+ WidgetsBottomSheet widgetsBottomSheet =
+ (WidgetsBottomSheet) launcher.getLayoutInflater().inflate(
+ R.layout.widgets_bottom_sheet, launcher.getDragLayer(), false);
+ widgetsBottomSheet.populateAndShow(itemInfo);
+ }
+ };
+ }
+ }
+
+ public static class AppInfo extends SystemShortcut {
+ public AppInfo() {
+ super(R.drawable.ic_info_no_shadow, R.string.app_info_drop_target_label);
+ }
+
+ @Override
+ public View.OnClickListener getOnClickListener(final Launcher launcher,
+ final ItemInfo itemInfo) {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Rect sourceBounds = launcher.getViewBounds(view);
+ Bundle opts = launcher.getActivityLaunchOptions(view);
+ InfoDropTarget.startDetailsActivityForInfo(itemInfo, launcher, null, sourceBounds, opts);
+ }
+ };
+ }
+ }
+}
diff --git a/src/com/android/launcher3/provider/ImportDataTask.java b/src/com/android/launcher3/provider/ImportDataTask.java
index 5cb34e8..b0482f8 100644
--- a/src/com/android/launcher3/provider/ImportDataTask.java
+++ b/src/com/android/launcher3/provider/ImportDataTask.java
@@ -44,7 +44,6 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
-import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.config.ProviderConfig;
@@ -90,10 +89,12 @@
ArrayList<Long> allScreens = LauncherDbUtils.getScreenIdsFromCursor(
mContext.getContentResolver().query(mOtherScreensUri, null, null, null,
LauncherSettings.WorkspaceScreens.SCREEN_RANK));
+ FileLog.d(TAG, "Importing DB from " + mOtherFavoritesUri);
// During import we reset the screen IDs to 0-indexed values.
if (allScreens.isEmpty()) {
// No thing to migrate
+ FileLog.e(TAG, "No data found to import");
return false;
}
@@ -130,7 +131,7 @@
private void importWorkspaceItems(
long firsetScreenId, LongSparseArray<Long> screenIdMap) throws Exception {
String profileId = Long.toString(UserManagerCompat.getInstance(mContext)
- .getSerialNumberForUser(UserHandleCompat.myUserHandle()));
+ .getSerialNumberForUser(Process.myUserHandle()));
boolean createEmptyRowOnFirstScreen = false;
if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
@@ -294,6 +295,7 @@
}
}
}
+ FileLog.d(TAG, totalItemsOnWorkspace + " items imported from external source");
if (totalItemsOnWorkspace < MIN_ITEM_COUNT_FOR_SUCCESSFUL_MIGRATION) {
throw new Exception("Insufficient data");
}
@@ -304,7 +306,7 @@
}
LongArrayMap<Object> hotseatItems = GridSizeMigrationTask.removeBrokenHotseatItems(mContext);
- int myHotseatCount = LauncherAppState.getInstance().getInvariantDeviceProfile().numHotseatIcons;
+ int myHotseatCount = LauncherAppState.getIDP(mContext).numHotseatIcons;
if (!FeatureFlags.NO_ALL_APPS_ICON) {
myHotseatCount--;
}
@@ -379,8 +381,8 @@
return c.getSharedPreferences(LauncherFiles.DEVICE_PREFERENCES_KEY, Context.MODE_PRIVATE);
}
- private static final int getMyHotseatLayoutId() {
- return LauncherAppState.getInstance().getInvariantDeviceProfile().numHotseatIcons <= 5
+ private static final int getMyHotseatLayoutId(Context context) {
+ return LauncherAppState.getIDP(context).numHotseatIcons <= 5
? R.xml.dw_phone_hotseat
: R.xml.dw_tablet_hotseat;
}
@@ -390,7 +392,7 @@
*/
private static class HotseatLayoutParser extends DefaultLayoutParser {
public HotseatLayoutParser(Context context, LayoutParserCallback callback) {
- super(context, null, callback, context.getResources(), getMyHotseatLayoutId());
+ super(context, null, callback, context.getResources(), getMyHotseatLayoutId(context));
}
@Override
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
index faa5fad..1758350 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.java
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.java
@@ -17,6 +17,7 @@
package com.android.launcher3.provider;
import android.content.ContentValues;
+import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
@@ -42,7 +43,7 @@
* the first row. The items in the first screen are moved and resized but the carry-forward
* items are simply deleted.
*/
- public static boolean prepareScreenZeroToHostQsb(SQLiteDatabase db) {
+ public static boolean prepareScreenZeroToHostQsb(Context context, SQLiteDatabase db) {
db.beginTransaction();
try {
// Get the existing screens
@@ -56,7 +57,7 @@
if (screenIds.get(0) != 0) {
// First screen is not 0, we need to rename screens
if (screenIds.indexOf(0L) > -1) {
- // There is already a screen 0. First rename it to a differen screen.
+ // There is already a screen 0. First rename it to a different screen.
long newScreenId = 1;
while (screenIds.indexOf(newScreenId) > -1) newScreenId++;
renameScreen(db, 0, newScreenId);
@@ -75,8 +76,7 @@
}
}
- LauncherAppState app = LauncherAppState.getInstance();
- new LossyScreenMigrationTask(app.getContext(), app.getInvariantDeviceProfile(), db)
+ new LossyScreenMigrationTask(context, LauncherAppState.getIDP(context), db)
.migrateScreen0();
db.setTransactionSuccessful();
return true;
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 47bee06..dc85aba 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -27,6 +27,7 @@
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.util.LogConfig;
import java.io.InvalidObjectException;
@@ -44,13 +45,6 @@
private static final String INFO_COLUMN_NAME = "name";
private static final String INFO_COLUMN_DEFAULT_VALUE = "dflt_value";
- /**
- * When enabled all icons are kept on the home screen, even if they don't have an active
- * session. To enable use:
- * adb shell setprop log.tag.launcher_keep_all_icons VERBOSE
- */
- private static final String KEEP_ALL_ICONS = "launcher_keep_all_icons";
-
public static boolean performRestore(DatabaseHelper helper) {
SQLiteDatabase db = helper.getWritableDatabase();
db.beginTransaction();
@@ -85,7 +79,7 @@
}
// Mark all items as restored.
- boolean keepAllIcons = Utilities.isPropertyEnabled(KEEP_ALL_ICONS);
+ boolean keepAllIcons = Utilities.isPropertyEnabled(LogConfig.KEEP_ALL_ICONS);
ContentValues values = new ContentValues();
values.put(Favorites.RESTORED, ShortcutInfo.FLAG_RESTORED_ICON
| (keepAllIcons ? ShortcutInfo.FLAG_RESTORE_STARTED : 0));
@@ -142,6 +136,7 @@
}
public static void setPending(Context context, boolean isPending) {
+ FileLog.d(TAG, "Restore data received through full backup");
Utilities.getPrefs(context).edit().putBoolean(RESTORE_TASK_PENDING, isPending).commit();
}
}
diff --git a/src/com/android/launcher3/QsbBlockerView.java b/src/com/android/launcher3/qsb/QsbBlockerView.java
similarity index 91%
rename from src/com/android/launcher3/QsbBlockerView.java
rename to src/com/android/launcher3/qsb/QsbBlockerView.java
index 6a2bce0..5379336 100644
--- a/src/com/android/launcher3/QsbBlockerView.java
+++ b/src/com/android/launcher3/qsb/QsbBlockerView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.launcher3;
+package com.android.launcher3.qsb;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
@@ -27,12 +27,15 @@
import android.util.AttributeSet;
import android.view.View;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.Workspace.OnStateChangeListener;
import com.android.launcher3.Workspace.State;
/**
* A simple view used to show the region blocked by QSB during drag and drop.
*/
-public class QsbBlockerView extends View implements Workspace.OnStateChangeListener {
+public class QsbBlockerView extends View implements OnStateChangeListener {
private static final int VISIBLE_ALPHA = 100;
diff --git a/src/com/android/launcher3/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java
similarity index 60%
rename from src/com/android/launcher3/QsbContainerView.java
rename to src/com/android/launcher3/qsb/QsbContainerView.java
index 02d8a13..4dc3c1c 100644
--- a/src/com/android/launcher3/QsbContainerView.java
+++ b/src/com/android/launcher3/qsb/QsbContainerView.java
@@ -14,19 +14,18 @@
* limitations under the License.
*/
-package com.android.launcher3;
+package com.android.launcher3.qsb;
import android.app.Activity;
import android.app.Fragment;
import android.app.SearchManager;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
import android.graphics.Rect;
import android.os.Bundle;
import android.util.AttributeSet;
@@ -35,7 +34,13 @@
import android.view.ViewGroup;
import android.widget.FrameLayout;
+import com.android.launcher3.AppWidgetResizeFrame;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.compat.AppWidgetManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
/**
* A frame layout which contains a QSB. This internally uses fragment to bind the view, which
@@ -68,25 +73,14 @@
private static final int REQUEST_BIND_QSB = 1;
private static final String QSB_WIDGET_ID = "qsb_widget_id";
- private static int sSavedWidgetId = -1;
-
+ private QsbWidgetHost mQsbWidgetHost;
private AppWidgetProviderInfo mWidgetInfo;
- private LauncherAppWidgetHostView mQsb;
-
- private BroadcastReceiver mRebindReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- rebindFragment();
- }
- };
+ private QsbWidgetHostView mQsb;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
- IntentFilter filter = new IntentFilter(Launcher.ACTION_APPWIDGET_HOST_RESET);
- filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
- getActivity().registerReceiver(mRebindReceiver, filter);
+ mQsbWidgetHost = new QsbWidgetHost(getActivity());
}
private FrameLayout mWrapper;
@@ -95,108 +89,100 @@
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- if (savedInstanceState != null) {
- sSavedWidgetId = savedInstanceState.getInt(QSB_WIDGET_ID, -1);
- }
mWrapper = new FrameLayout(getActivity());
- mWrapper.addView(createQsb(inflater, mWrapper));
+
+ // Only add the view when enabled
+ if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
+ mWrapper.addView(createQsb(mWrapper));
+ }
return mWrapper;
}
- private View createQsb(LayoutInflater inflater, ViewGroup container) {
- Launcher launcher = Launcher.getLauncher(getActivity());
- mWidgetInfo = getSearchWidgetProvider(launcher);
+ private View createQsb(ViewGroup container) {
+ Activity activity = getActivity();
+ mWidgetInfo = getSearchWidgetProvider(activity);
if (mWidgetInfo == null) {
// There is no search provider, just show the default widget.
- return getDefaultView(inflater, container, false);
+ return QsbWidgetHostView.getDefaultView(container);
}
- SharedPreferences prefs = Utilities.getPrefs(launcher);
- AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(launcher);
- LauncherAppWidgetHost widgetHost = launcher.getAppWidgetHost();
- InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile();
+ AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(activity);
+ InvariantDeviceProfile idp = LauncherAppState.getIDP(activity);
Bundle opts = new Bundle();
- Rect size = AppWidgetResizeFrame.getWidgetSizeRanges(launcher, idp.numColumns, 1, null);
+ Rect size = AppWidgetResizeFrame.getWidgetSizeRanges(activity, idp.numColumns, 1, null);
opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, size.left);
opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, size.top);
opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, size.right);
opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, size.bottom);
- int widgetId = prefs.getInt(QSB_WIDGET_ID, -1);
+ int widgetId = Utilities.getPrefs(activity).getInt(QSB_WIDGET_ID, -1);
AppWidgetProviderInfo widgetInfo = widgetManager.getAppWidgetInfo(widgetId);
boolean isWidgetBound = (widgetInfo != null) &&
widgetInfo.provider.equals(mWidgetInfo.provider);
+ int oldWidgetId = widgetId;
if (!isWidgetBound) {
- // widgetId is already bound and its not the correct provider.
- // Delete the widget id.
if (widgetId > -1) {
- widgetHost.deleteAppWidgetId(widgetId);
+ // widgetId is already bound and its not the correct provider. reset host.
+ mQsbWidgetHost.deleteHost();
+ }
+
+ widgetId = mQsbWidgetHost.allocateAppWidgetId();
+ isWidgetBound = widgetManager.bindAppWidgetIdIfAllowed(widgetId, mWidgetInfo, opts);
+ if (!isWidgetBound) {
+ mQsbWidgetHost.deleteAppWidgetId(widgetId);
widgetId = -1;
}
- widgetId = widgetHost.allocateAppWidgetId();
- isWidgetBound = widgetManager.bindAppWidgetIdIfAllowed(widgetId, mWidgetInfo, opts);
- if (!isWidgetBound) {
- widgetHost.deleteAppWidgetId(widgetId);
- widgetId = -1;
+ if (oldWidgetId != widgetId) {
+ saveWidgetId(widgetId);
}
}
if (isWidgetBound) {
- mQsb = (LauncherAppWidgetHostView)
- widgetHost.createView(launcher, widgetId, mWidgetInfo);
+ mQsb = (QsbWidgetHostView) mQsbWidgetHost.createView(activity, widgetId, mWidgetInfo);
mQsb.setId(R.id.qsb_widget);
- mQsb.mErrorViewId = R.layout.qsb_default_view;
- if (!Utilities.containsAll(AppWidgetManager.getInstance(launcher)
+ if (!Utilities.containsAll(AppWidgetManager.getInstance(activity)
.getAppWidgetOptions(widgetId), opts)) {
mQsb.updateAppWidgetOptions(opts);
}
mQsb.setPadding(0, 0, 0, 0);
+ mQsbWidgetHost.startListening();
return mQsb;
}
// Return a default widget with setup icon.
- return getDefaultView(inflater, container, true);
+ View v = QsbWidgetHostView.getDefaultView(container);
+ View setupButton = v.findViewById(R.id.btn_qsb_setup);
+ setupButton.setVisibility(View.VISIBLE);
+ setupButton.setOnClickListener(this);
+ return v;
+ }
+
+ private void saveWidgetId(int widgetId) {
+ Utilities.getPrefs(getActivity()).edit().putInt(QSB_WIDGET_ID, widgetId).apply();
}
@Override
public void onClick(View view) {
- if (view.getId() == R.id.btn_qsb_search) {
- getActivity().startSearch("", false, null, true);
- } else if (view.getId() == R.id.btn_qsb_setup) {
- // Allocate a new widget id for QSB
- sSavedWidgetId = Launcher.getLauncher(getActivity())
- .getAppWidgetHost().allocateAppWidgetId();
- // Start intent for bind the widget
- Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, sSavedWidgetId);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, mWidgetInfo.provider);
- startActivityForResult(intent, REQUEST_BIND_QSB);
- }
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putInt(QSB_WIDGET_ID, sSavedWidgetId);
+ // Start intent for bind the widget
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
+ // Allocate a new widget id for QSB
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mQsbWidgetHost.allocateAppWidgetId());
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, mWidgetInfo.provider);
+ startActivityForResult(intent, REQUEST_BIND_QSB);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_BIND_QSB) {
if (resultCode == Activity.RESULT_OK) {
- int widgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
- sSavedWidgetId);
- Utilities.getPrefs(getActivity()).edit().putInt(QSB_WIDGET_ID, widgetId).apply();
- sSavedWidgetId = -1;
+ saveWidgetId(data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1));
rebindFragment();
- } else if (sSavedWidgetId != -1) {
- Launcher.getLauncher(getActivity()).getAppWidgetHost()
- .deleteAppWidgetId(sSavedWidgetId);
- sSavedWidgetId = -1;
+ } else {
+ mQsbWidgetHost.deleteHost();
}
}
}
@@ -211,27 +197,21 @@
@Override
public void onDestroy() {
- getActivity().unregisterReceiver(mRebindReceiver);
+ mQsbWidgetHost.stopListening();
super.onDestroy();
}
private void rebindFragment() {
+ // Exit if the embedded qsb is disabled
+ if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
+ return;
+ }
+
if (mWrapper != null && getActivity() != null) {
mWrapper.removeAllViews();
- mWrapper.addView(createQsb(getActivity().getLayoutInflater(), mWrapper));
+ mWrapper.addView(createQsb(mWrapper));
}
}
-
- private View getDefaultView(LayoutInflater inflater, ViewGroup parent, boolean showSetup) {
- View v = inflater.inflate(R.layout.qsb_default_view, parent, false);
- if (showSetup) {
- View setupButton = v.findViewById(R.id.btn_qsb_setup);
- setupButton.setVisibility(View.VISIBLE);
- setupButton.setOnClickListener(this);
- }
- v.findViewById(R.id.btn_qsb_search).setOnClickListener(this);
- return v;
- }
}
/**
@@ -261,4 +241,19 @@
}
return defaultWidgetForSearchPackage;
}
+
+ private static class QsbWidgetHost extends AppWidgetHost {
+
+ private static final int QSB_WIDGET_HOST_ID = 1026;
+
+ public QsbWidgetHost(Context context) {
+ super(context, QSB_WIDGET_HOST_ID);
+ }
+
+ @Override
+ protected AppWidgetHostView onCreateView(
+ Context context, int appWidgetId, AppWidgetProviderInfo appWidget) {
+ return new QsbWidgetHostView(context);
+ }
+ }
}
diff --git a/src/com/android/launcher3/qsb/QsbWidgetHostView.java b/src/com/android/launcher3/qsb/QsbWidgetHostView.java
new file mode 100644
index 0000000..8b6fa16
--- /dev/null
+++ b/src/com/android/launcher3/qsb/QsbWidgetHostView.java
@@ -0,0 +1,87 @@
+/*
+ * 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.qsb;
+
+import android.appwidget.AppWidgetHostView;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.widget.RemoteViews;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+
+/**
+ * Appwidget host view with QSB specific logic.
+ */
+public class QsbWidgetHostView extends AppWidgetHostView {
+
+ @ViewDebug.ExportedProperty(category = "launcher")
+ private int mPreviousOrientation;
+
+ public QsbWidgetHostView(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void updateAppWidget(RemoteViews remoteViews) {
+ // Store the orientation in which the widget was inflated
+ mPreviousOrientation = getResources().getConfiguration().orientation;
+ super.updateAppWidget(remoteViews);
+ }
+
+
+ public boolean isReinflateRequired() {
+ // Re-inflate is required if the orientation has changed since last inflation.
+ return mPreviousOrientation != getResources().getConfiguration().orientation;
+ }
+
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ try {
+ super.onLayout(changed, left, top, right, bottom);
+ } catch (final RuntimeException e) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ // Update the widget with 0 Layout id, to reset the view to error view.
+ updateAppWidget(new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0));
+ }
+ });
+ }
+ }
+
+ @Override
+ protected View getErrorView() {
+ return getDefaultView(this);
+ }
+
+ public static View getDefaultView(ViewGroup parent) {
+ View v = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.qsb_default_view, parent, false);
+ v.findViewById(R.id.btn_qsb_search).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Launcher.getLauncher(view.getContext()).startSearch("", false, null, true);
+ }
+ });
+ return v;
+ }
+}
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
index 49d6fa9..df7f695 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
@@ -25,12 +25,12 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.os.UserHandle;
import android.util.Log;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.UserHandleCompat;
import java.util.ArrayList;
import java.util.Collections;
@@ -42,23 +42,31 @@
public class DeepShortcutManager {
private static final String TAG = "DeepShortcutManager";
- // TODO: Replace this with platform constants when the new sdk is available.
- public static final int FLAG_MATCH_DYNAMIC = 1 << 0;
- public static final int FLAG_MATCH_MANIFEST = 1 << 3;
- public static final int FLAG_MATCH_PINNED = 1 << 1;
+ private static final int FLAG_GET_ALL = ShortcutQuery.FLAG_MATCH_DYNAMIC
+ | ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_PINNED;
- private static final int FLAG_GET_ALL =
- FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST;
+ private static DeepShortcutManager sInstance;
+ private static final Object sInstanceLock = new Object();
+
+ public static DeepShortcutManager getInstance(Context context) {
+ synchronized (sInstanceLock) {
+ if (sInstance == null) {
+ sInstance = new DeepShortcutManager(context.getApplicationContext());
+ }
+ return sInstance;
+ }
+ }
private final LauncherApps mLauncherApps;
private boolean mWasLastCallSuccess;
- public DeepShortcutManager(Context context, ShortcutCache shortcutCache) {
+ private DeepShortcutManager(Context context) {
mLauncherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
}
public static boolean supportsShortcuts(ItemInfo info) {
- return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+ return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+ && !info.isDisabled();
}
public boolean wasLastCallSuccess() {
@@ -76,7 +84,7 @@
* because we only get "key" fields in onShortcutsChanged().
*/
public List<ShortcutInfoCompat> queryForFullDetails(String packageName,
- List<String> shortcutIds, UserHandleCompat user) {
+ List<String> shortcutIds, UserHandle user) {
return query(FLAG_GET_ALL, packageName, null, shortcutIds, user);
}
@@ -85,8 +93,8 @@
* to be displayed in the shortcuts container on long press.
*/
public List<ShortcutInfoCompat> queryForShortcutsContainer(ComponentName activity,
- List<String> ids, UserHandleCompat user) {
- return query(FLAG_MATCH_MANIFEST | FLAG_MATCH_DYNAMIC,
+ List<String> ids, UserHandle user) {
+ return query(ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_DYNAMIC,
activity.getPackageName(), activity, ids, user);
}
@@ -96,14 +104,14 @@
*/
@TargetApi(25)
public void unpinShortcut(final ShortcutKey key) {
- if (Utilities.isNycMR1OrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT_MR1) {
String packageName = key.componentName.getPackageName();
String id = key.getId();
- UserHandleCompat user = key.user;
+ UserHandle user = key.user;
List<String> pinnedIds = extractIds(queryForPinnedShortcuts(packageName, user));
pinnedIds.remove(id);
try {
- mLauncherApps.pinShortcuts(packageName, pinnedIds, user.getUser());
+ mLauncherApps.pinShortcuts(packageName, pinnedIds, user);
mWasLastCallSuccess = true;
} catch (SecurityException|IllegalStateException e) {
Log.w(TAG, "Failed to unpin shortcut", e);
@@ -118,14 +126,14 @@
*/
@TargetApi(25)
public void pinShortcut(final ShortcutKey key) {
- if (Utilities.isNycMR1OrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT_MR1) {
String packageName = key.componentName.getPackageName();
String id = key.getId();
- UserHandleCompat user = key.user;
+ UserHandle user = key.user;
List<String> pinnedIds = extractIds(queryForPinnedShortcuts(packageName, user));
pinnedIds.add(id);
try {
- mLauncherApps.pinShortcuts(packageName, pinnedIds, user.getUser());
+ mLauncherApps.pinShortcuts(packageName, pinnedIds, user);
mWasLastCallSuccess = true;
} catch (SecurityException|IllegalStateException e) {
Log.w(TAG, "Failed to pin shortcut", e);
@@ -136,11 +144,11 @@
@TargetApi(25)
public void startShortcut(String packageName, String id, Rect sourceBounds,
- Bundle startActivityOptions, UserHandleCompat user) {
- if (Utilities.isNycMR1OrAbove()) {
+ Bundle startActivityOptions, UserHandle user) {
+ if (Utilities.ATLEAST_NOUGAT_MR1) {
try {
mLauncherApps.startShortcut(packageName, id, sourceBounds,
- startActivityOptions, user.getUser());
+ startActivityOptions, user);
mWasLastCallSuccess = true;
} catch (SecurityException|IllegalStateException e) {
Log.e(TAG, "Failed to start shortcut", e);
@@ -151,7 +159,7 @@
@TargetApi(25)
public Drawable getShortcutIconDrawable(ShortcutInfoCompat shortcutInfo, int density) {
- if (Utilities.isNycMR1OrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT_MR1) {
try {
Drawable icon = mLauncherApps.getShortcutIconDrawable(
shortcutInfo.getShortcutInfo(), density);
@@ -170,12 +178,11 @@
*
* If packageName is null, returns all pinned shortcuts regardless of package.
*/
- public List<ShortcutInfoCompat> queryForPinnedShortcuts(String packageName,
- UserHandleCompat user) {
- return query(FLAG_MATCH_PINNED, packageName, null, null, user);
+ public List<ShortcutInfoCompat> queryForPinnedShortcuts(String packageName, UserHandle user) {
+ return query(ShortcutQuery.FLAG_MATCH_PINNED, packageName, null, null, user);
}
- public List<ShortcutInfoCompat> queryForAllShortcuts(UserHandleCompat user) {
+ public List<ShortcutInfoCompat> queryForAllShortcuts(UserHandle user) {
return query(FLAG_GET_ALL, null, null, null, user);
}
@@ -195,8 +202,8 @@
*/
@TargetApi(25)
private List<ShortcutInfoCompat> query(int flags, String packageName,
- ComponentName activity, List<String> shortcutIds, UserHandleCompat user) {
- if (Utilities.isNycMR1OrAbove()) {
+ ComponentName activity, List<String> shortcutIds, UserHandle user) {
+ if (Utilities.ATLEAST_NOUGAT_MR1) {
ShortcutQuery q = new ShortcutQuery();
q.setQueryFlags(flags);
if (packageName != null) {
@@ -206,7 +213,7 @@
}
List<ShortcutInfo> shortcutInfos = null;
try {
- shortcutInfos = mLauncherApps.getShortcuts(q, user.getUser());
+ shortcutInfos = mLauncherApps.getShortcuts(q, user);
mWasLastCallSuccess = true;
} catch (SecurityException|IllegalStateException e) {
Log.e(TAG, "Failed to query for shortcuts", e);
@@ -227,7 +234,7 @@
@TargetApi(25)
public boolean hasHostPermission() {
- if (Utilities.isNycMR1OrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT_MR1) {
try {
return mLauncherApps.hasShortcutHostPermission();
} catch (SecurityException|IllegalStateException e) {
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
index 42086fc..1a5297d 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
@@ -47,7 +47,7 @@
super(context, attrs, defStyle);
Resources resources = getResources();
- mDragHandleWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcut_padding_end)
+ mDragHandleWidth = resources.getDimensionPixelSize(R.dimen.popup_padding_end)
+ resources.getDimensionPixelSize(R.dimen.deep_shortcut_drag_handle_size)
+ resources.getDimensionPixelSize(R.dimen.deep_shortcut_drawable_padding) / 2;
}
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
index e7fc415..75a4886 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -16,8 +16,6 @@
package com.android.launcher3.shortcuts;
-import android.animation.Animator;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
@@ -26,32 +24,27 @@
import android.view.View;
import android.widget.FrameLayout;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LogAccelerateInterpolator;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
-import com.android.launcher3.shortcuts.DeepShortcutsContainer.UnbadgedShortcutInfo;
-import com.android.launcher3.util.PillRevealOutlineProvider;
-import com.android.launcher3.util.PillWidthRevealOutlineProvider;
/**
* A {@link android.widget.FrameLayout} that contains a {@link DeepShortcutView}.
* This lets us animate the DeepShortcutView (icon and text) separately from the background.
*/
-public class DeepShortcutView extends FrameLayout implements ValueAnimator.AnimatorUpdateListener {
+public class DeepShortcutView extends FrameLayout {
private static final Point sTempPoint = new Point();
private final Rect mPillRect;
- private DeepShortcutTextView mBubbleText;
+ private BubbleTextView mBubbleText;
private View mIconView;
- private float mOpenAnimationProgress;
- private UnbadgedShortcutInfo mInfo;
+ private ShortcutInfo mInfo;
+ private ShortcutInfoCompat mDetail;
public DeepShortcutView(Context context) {
this(context, null, 0);
@@ -70,11 +63,11 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mIconView = findViewById(R.id.deep_shortcut_icon);
- mBubbleText = (DeepShortcutTextView) findViewById(R.id.deep_shortcut);
+ mBubbleText = findViewById(R.id.bubble_text);
+ mIconView = findViewById(R.id.icon);
}
- public DeepShortcutTextView getBubbleText() {
+ public BubbleTextView getBubbleText() {
return mBubbleText;
}
@@ -86,97 +79,6 @@
return mIconView.getVisibility() == View.VISIBLE;
}
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- mPillRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
- }
-
- /** package private **/
- void applyShortcutInfo(UnbadgedShortcutInfo info, DeepShortcutsContainer container) {
- mInfo = info;
- IconCache cache = LauncherAppState.getInstance().getIconCache();
- mBubbleText.applyFromShortcutInfo(info, cache);
- mIconView.setBackground(mBubbleText.getIcon());
-
- // Use the long label as long as it exists and fits.
- CharSequence longLabel = info.mDetail.getLongLabel();
- int availableWidth = mBubbleText.getWidth() - mBubbleText.getTotalPaddingLeft()
- - mBubbleText.getTotalPaddingRight();
- boolean usingLongLabel = !TextUtils.isEmpty(longLabel)
- && mBubbleText.getPaint().measureText(longLabel.toString()) <= availableWidth;
- mBubbleText.setText(usingLongLabel ? longLabel : info.mDetail.getShortLabel());
-
- // TODO: Add the click handler to this view directly and not the child view.
- mBubbleText.setOnClickListener(Launcher.getLauncher(getContext()));
- mBubbleText.setOnLongClickListener(container);
- mBubbleText.setOnTouchListener(container);
- }
-
- /**
- * Returns the shortcut info that is suitable to be added on the homescreen
- */
- public ShortcutInfo getFinalInfo() {
- ShortcutInfo badged = new ShortcutInfo(mInfo);
- // Queue an update task on the worker thread. This ensures that the badged
- // shortcut eventually gets its icon updated.
- Launcher.getLauncher(getContext()).getModel().updateShortcutInfo(mInfo.mDetail, badged);
- return badged;
- }
-
- public View getIconView() {
- return mIconView;
- }
-
- /**
- * Creates an animator to play when the shortcut container is being opened.
- */
- public Animator createOpenAnimation(boolean isContainerAboveIcon, boolean pivotLeft) {
- Point center = getIconCenter();
- ValueAnimator openAnimator = new ZoomRevealOutlineProvider(center.x, center.y,
- mPillRect, this, mIconView, isContainerAboveIcon, pivotLeft)
- .createRevealAnimator(this, false);
- mOpenAnimationProgress = 0f;
- openAnimator.addUpdateListener(this);
- return openAnimator;
- }
-
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- mOpenAnimationProgress = valueAnimator.getAnimatedFraction();
- }
-
- public boolean isOpenOrOpening() {
- return mOpenAnimationProgress > 0;
- }
-
- /**
- * Creates an animator to play when the shortcut container is being closed.
- */
- public Animator createCloseAnimation(boolean isContainerAboveIcon, boolean pivotLeft,
- long duration) {
- Point center = getIconCenter();
- ValueAnimator closeAnimator = new ZoomRevealOutlineProvider(center.x, center.y,
- mPillRect, this, mIconView, isContainerAboveIcon, pivotLeft)
- .createRevealAnimator(this, true);
- // Scale down the duration and interpolator according to the progress
- // that the open animation was at when the close started.
- closeAnimator.setDuration((long) (duration * mOpenAnimationProgress));
- closeAnimator.setInterpolator(new CloseInterpolator(mOpenAnimationProgress));
- return closeAnimator;
- }
-
- /**
- * Creates an animator which clips the container to form a circle around the icon.
- */
- public Animator collapseToIcon() {
- int halfHeight = getMeasuredHeight() / 2;
- int iconCenterX = getIconCenter().x;
- return new PillWidthRevealOutlineProvider(mPillRect,
- iconCenterX - halfHeight, iconCenterX + halfHeight)
- .createRevealAnimator(this, true);
- }
-
/**
* Returns the position of the center of the icon relative to the container.
*/
@@ -188,67 +90,47 @@
return sTempPoint;
}
- /**
- * Extension of {@link PillRevealOutlineProvider} which scales the icon based on the height.
- */
- private static class ZoomRevealOutlineProvider extends PillRevealOutlineProvider {
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ mPillRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
+ }
- private final View mTranslateView;
- private final View mZoomView;
+ /** package private **/
+ public void applyShortcutInfo(ShortcutInfo info, ShortcutInfoCompat detail,
+ ShortcutsItemView container) {
+ mInfo = info;
+ mDetail = detail;
+ mBubbleText.applyFromShortcutInfo(info);
+ mIconView.setBackground(mBubbleText.getIcon());
- private final float mFullHeight;
- private final float mTranslateYMultiplier;
+ // Use the long label as long as it exists and fits.
+ CharSequence longLabel = mDetail.getLongLabel();
+ int availableWidth = mBubbleText.getWidth() - mBubbleText.getTotalPaddingLeft()
+ - mBubbleText.getTotalPaddingRight();
+ boolean usingLongLabel = !TextUtils.isEmpty(longLabel)
+ && mBubbleText.getPaint().measureText(longLabel.toString()) <= availableWidth;
+ mBubbleText.setText(usingLongLabel ? longLabel : mDetail.getShortLabel());
- private final boolean mPivotLeft;
- private final float mTranslateX;
-
- public ZoomRevealOutlineProvider(int x, int y, Rect pillRect,
- View translateView, View zoomView, boolean isContainerAboveIcon, boolean pivotLeft) {
- super(x, y, pillRect);
- mTranslateView = translateView;
- mZoomView = zoomView;
- mFullHeight = pillRect.height();
-
- mTranslateYMultiplier = isContainerAboveIcon ? 0.5f : -0.5f;
-
- mPivotLeft = pivotLeft;
- mTranslateX = pivotLeft ? pillRect.height() / 2 : pillRect.right - pillRect.height() / 2;
- }
-
- @Override
- public void setProgress(float progress) {
- super.setProgress(progress);
-
- mZoomView.setScaleX(progress);
- mZoomView.setScaleY(progress);
-
- float height = mOutline.height();
- mTranslateView.setTranslationY(mTranslateYMultiplier * (mFullHeight - height));
-
- float pivotX = mPivotLeft ? (mOutline.left + height / 2) : (mOutline.right - height / 2);
- mTranslateView.setTranslationX(mTranslateX - pivotX);
- }
+ // TODO: Add the click handler to this view directly and not the child view.
+ mBubbleText.setOnClickListener(Launcher.getLauncher(getContext()));
+ mBubbleText.setOnLongClickListener(container);
+ mBubbleText.setOnTouchListener(container);
}
/**
- * An interpolator that reverses the current open animation progress.
+ * Returns the shortcut info that is suitable to be added on the homescreen
*/
- private static class CloseInterpolator extends LogAccelerateInterpolator {
- private float mStartProgress;
- private float mRemainingProgress;
+ public ShortcutInfo getFinalInfo() {
+ final ShortcutInfo badged = new ShortcutInfo(mInfo);
+ // Queue an update task on the worker thread. This ensures that the badged
+ // shortcut eventually gets its icon updated.
+ Launcher.getLauncher(getContext()).getModel()
+ .updateAndBindShortcutInfo(badged, mDetail);
+ return badged;
+ }
- /**
- * @param openAnimationProgress The progress that the open interpolator ended at.
- */
- public CloseInterpolator(float openAnimationProgress) {
- super(100, 0);
- mStartProgress = 1f - openAnimationProgress;
- mRemainingProgress = openAnimationProgress;
- }
-
- @Override
- public float getInterpolation(float v) {
- return mStartProgress + super.getInterpolation(v) * mRemainingProgress;
- }
+ public View getIconView() {
+ return mIconView;
}
}
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
deleted file mode 100644
index 2702d4e..0000000
--- a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
+++ /dev/null
@@ -1,675 +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.shortcuts;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.TimeInterpolator;
-import android.annotation.TargetApi;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.drawable.ShapeDrawable;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.DecelerateInterpolator;
-import android.widget.LinearLayout;
-
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.DragSource;
-import com.android.launcher3.DropTarget;
-import com.android.launcher3.IconCache;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.LauncherViewPropertyAnimator;
-import com.android.launcher3.LogAccelerateInterpolator;
-import com.android.launcher3.R;
-import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
-import com.android.launcher3.compat.UserHandleCompat;
-import com.android.launcher3.dragndrop.DragController;
-import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.dragndrop.DragView;
-import com.android.launcher3.graphics.TriangleShape;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A container for shortcuts to deep links within apps.
- */
-@TargetApi(Build.VERSION_CODES.N)
-public class DeepShortcutsContainer extends LinearLayout implements View.OnLongClickListener,
- View.OnTouchListener, DragSource, DragController.DragListener {
- private static final String TAG = "ShortcutsContainer";
-
- private final Point mIconShift = new Point();
-
- private final Launcher mLauncher;
- private final DeepShortcutManager mDeepShortcutsManager;
- private final int mStartDragThreshold;
- private final ShortcutMenuAccessibilityDelegate mAccessibilityDelegate;
- private final boolean mIsRtl;
-
- private BubbleTextView mDeferredDragIcon;
- private final Rect mTempRect = new Rect();
- private Point mIconLastTouchPos = new Point();
- private boolean mIsLeftAligned;
- private boolean mIsAboveIcon;
- private View mArrow;
-
- private Animator mOpenCloseAnimator;
- private boolean mDeferContainerRemoval;
- private boolean mIsOpen;
-
- public DeepShortcutsContainer(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- mLauncher = Launcher.getLauncher(context);
- mDeepShortcutsManager = LauncherAppState.getInstance().getShortcutManager();
-
- mStartDragThreshold = getResources().getDimensionPixelSize(
- R.dimen.deep_shortcuts_start_drag_threshold);
- mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher);
- mIsRtl = Utilities.isRtl(getResources());
- }
-
- public DeepShortcutsContainer(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public DeepShortcutsContainer(Context context) {
- this(context, null, 0);
- }
-
- public void populateAndShow(final BubbleTextView originalIcon, final List<String> ids) {
- final Resources resources = getResources();
- final int arrowWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcuts_arrow_width);
- final int arrowHeight = resources.getDimensionPixelSize(R.dimen.deep_shortcuts_arrow_height);
- final int arrowHorizontalOffset = resources.getDimensionPixelSize(
- R.dimen.deep_shortcuts_arrow_horizontal_offset);
- final int arrowVerticalOffset = resources.getDimensionPixelSize(
- R.dimen.deep_shortcuts_arrow_vertical_offset);
-
- // Add dummy views first, and populate with real shortcut info when ready.
- final int spacing = getResources().getDimensionPixelSize(R.dimen.deep_shortcuts_spacing);
- final LayoutInflater inflater = mLauncher.getLayoutInflater();
- int numShortcuts = Math.min(ids.size(), ShortcutFilter.MAX_SHORTCUTS);
- for (int i = 0; i < numShortcuts; i++) {
- final DeepShortcutView shortcut =
- (DeepShortcutView) inflater.inflate(R.layout.deep_shortcut, this, false);
- if (i < numShortcuts - 1) {
- ((LayoutParams) shortcut.getLayoutParams()).bottomMargin = spacing;
- }
- shortcut.getBubbleText().setAccessibilityDelegate(mAccessibilityDelegate);
- addView(shortcut);
- }
- setContentDescription(getContext().getString(R.string.shortcuts_menu_description,
- numShortcuts, originalIcon.getContentDescription().toString()));
-
- measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset);
-
- // Add the arrow.
- mArrow = addArrowView(arrowHorizontalOffset, arrowVerticalOffset, arrowWidth, arrowHeight);
- mArrow.setPivotX(arrowWidth / 2);
- mArrow.setPivotY(mIsAboveIcon ? 0 : arrowHeight);
-
- animateOpen();
-
- deferDrag(originalIcon);
-
- // Load the shortcuts on a background thread and update the container as it animates.
- final Looper workerLooper = LauncherModel.getWorkerLooper();
- final Handler uiHandler = new Handler(Looper.getMainLooper());
- final ItemInfo originalInfo = (ItemInfo) originalIcon.getTag();
- final UserHandleCompat user = originalInfo.user;
- final ComponentName activity = originalInfo.getTargetComponent();
- new Handler(workerLooper).postAtFrontOfQueue(new Runnable() {
- @Override
- public void run() {
- final List<ShortcutInfoCompat> shortcuts = ShortcutFilter.sortAndFilterShortcuts(
- mDeepShortcutsManager.queryForShortcutsContainer(activity, ids, user));
- // We want the lowest rank to be closest to the user's finger.
- if (mIsAboveIcon) {
- Collections.reverse(shortcuts);
- }
- for (int i = 0; i < shortcuts.size(); i++) {
- final ShortcutInfoCompat shortcut = shortcuts.get(i);
- uiHandler.post(new UpdateShortcutChild(
- i, new UnbadgedShortcutInfo(shortcut, mLauncher)));
- }
- }
- });
- }
-
- /** Updates the child of this container at the given index based on the given shortcut info. */
- private class UpdateShortcutChild implements Runnable {
- private int mShortcutChildIndex;
- private UnbadgedShortcutInfo mShortcutChildInfo;
-
- public UpdateShortcutChild(int shortcutChildIndex, UnbadgedShortcutInfo shortcutChildInfo) {
- mShortcutChildIndex = shortcutChildIndex;
- mShortcutChildInfo = shortcutChildInfo;
- }
-
- @Override
- public void run() {
- getShortcutAt(mShortcutChildIndex)
- .applyShortcutInfo(mShortcutChildInfo, DeepShortcutsContainer.this);
- }
- }
-
- private DeepShortcutView getShortcutAt(int index) {
- if (!mIsAboveIcon) {
- // Opening down, so arrow is the first view.
- index++;
- }
- return (DeepShortcutView) getChildAt(index);
- }
-
- private int getShortcutCount() {
- // All children except the arrow are shortcuts.
- return getChildCount() - 1;
- }
-
- private void animateOpen() {
- setVisibility(View.VISIBLE);
- mIsOpen = true;
-
- final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet();
- final int shortcutCount = getShortcutCount();
-
- final long duration = getResources().getInteger(
- R.integer.config_deepShortcutOpenDuration);
- final long arrowScaleDuration = getResources().getInteger(
- R.integer.config_deepShortcutArrowOpenDuration);
- final long arrowScaleDelay = duration - arrowScaleDuration;
- final long stagger = getResources().getInteger(
- R.integer.config_deepShortcutOpenStagger);
- final TimeInterpolator fadeInterpolator = new LogAccelerateInterpolator(100, 0);
-
- // Animate shortcuts
- DecelerateInterpolator interpolator = new DecelerateInterpolator();
- for (int i = 0; i < shortcutCount; i++) {
- final DeepShortcutView deepShortcutView = getShortcutAt(i);
- deepShortcutView.setVisibility(INVISIBLE);
- deepShortcutView.setAlpha(0);
-
- Animator anim = deepShortcutView.createOpenAnimation(mIsAboveIcon, mIsLeftAligned);
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- deepShortcutView.setVisibility(VISIBLE);
- }
- });
- anim.setDuration(duration);
- int animationIndex = mIsAboveIcon ? shortcutCount - i - 1 : i;
- anim.setStartDelay(stagger * animationIndex);
- anim.setInterpolator(interpolator);
- shortcutAnims.play(anim);
-
- Animator fadeAnim = new LauncherViewPropertyAnimator(deepShortcutView).alpha(1);
- fadeAnim.setInterpolator(fadeInterpolator);
- // We want the shortcut to be fully opaque before the arrow starts animating.
- fadeAnim.setDuration(arrowScaleDelay);
- shortcutAnims.play(fadeAnim);
- }
- shortcutAnims.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mOpenCloseAnimator = null;
- Utilities.sendCustomAccessibilityEvent(
- DeepShortcutsContainer.this,
- AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
- getContext().getString(R.string.action_deep_shortcut));
- }
- });
-
- // Animate the arrow
- mArrow.setScaleX(0);
- mArrow.setScaleY(0);
- Animator arrowScale = new LauncherViewPropertyAnimator(mArrow).scaleX(1).scaleY(1);
- arrowScale.setStartDelay(arrowScaleDelay);
- arrowScale.setDuration(arrowScaleDuration);
- shortcutAnims.play(arrowScale);
-
- mOpenCloseAnimator = shortcutAnims;
- shortcutAnims.start();
- }
-
- /**
- * 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 < 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.deep_shortcut_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.deep_shortcut_padding_end);
- xOffset = iconWidth / 2 - shortcutDragHandleWidth / 2 - shortcutPaddingEnd;
- }
- x += mIsLeftAligned ? xOffset : -xOffset;
-
- // Open above icon if there is room.
- int iconHeight = icon.getIcon().getBounds().height();
- 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.
- y -= insets.top;
-
- 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) {
- LinearLayout.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());
- ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
- width, height, !mIsAboveIcon));
- arrowDrawable.getPaint().setColor(Color.WHITE);
- arrowView.setBackground(arrowDrawable);
- arrowView.setElevation(getElevation());
- addView(arrowView, mIsAboveIcon ? getChildCount() : 0, layoutParams);
- return arrowView;
- }
-
- private void deferDrag(BubbleTextView originalIcon) {
- mDeferredDragIcon = originalIcon;
- mLauncher.getDragController().addDragListener(this);
- }
-
- public BubbleTextView getDeferredDragIcon() {
- return mDeferredDragIcon;
- }
-
- /**
- * Determines when the deferred drag should be started.
- *
- * Current behavior:
- * - Start the drag if the touch passes a certain distance from the original touch down.
- */
- public DragOptions.DeferDragCondition createDeferDragCondition(final Runnable onDragStart) {
- return new DragOptions.DeferDragCondition() {
- @Override
- public boolean shouldStartDeferredDrag(double distanceDragged) {
- return distanceDragged > mStartDragThreshold;
- }
-
- @Override
- public void onDeferredDragStart() {
- mDeferredDragIcon.setVisibility(INVISIBLE);
- }
-
- @Override
- public void onDropBeforeDeferredDrag() {
- mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mDeferredDragIcon);
- if (!mIsAboveIcon) {
- mDeferredDragIcon.setTextVisibility(false);
- }
- }
-
- @Override
- public void onDragStart() {
- if (onDragStart != null) {
- onDragStart.run();
- }
- }
- };
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent ev) {
- // Touched a shortcut, update where it was touched so we can drag from there on long click.
- switch (ev.getAction()) {
- case MotionEvent.ACTION_DOWN:
- case MotionEvent.ACTION_MOVE:
- mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
- break;
- }
- return false;
- }
-
- public boolean onLongClick(View v) {
- // Return early if this is not initiated from a touch or not the correct view
- if (!v.isInTouchMode() || !(v.getParent() instanceof DeepShortcutView)) return false;
- // Return if global dragging is not enabled
- if (!mLauncher.isDraggingEnabled()) return false;
-
- // Long clicked on a shortcut.
- mDeferContainerRemoval = true;
- DeepShortcutView sv = (DeepShortcutView) v.getParent();
- sv.setWillDrawIcon(false);
-
- // Move the icon to align with the center-top of the touch point
- mIconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
- mIconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
-
- DragView dv = mLauncher.getWorkspace().beginDragShared(
- sv.getBubbleText(), this, sv.getFinalInfo(),
- new ShortcutDragPreviewProvider(sv.getIconView(), mIconShift), new DragOptions());
- dv.animateShift(-mIconShift.x, -mIconShift.y);
-
- // TODO: support dragging from within folder without having to close it
- mLauncher.closeFolder();
- return false;
- }
-
- @Override
- public boolean supportsFlingToDelete() {
- return true;
- }
-
- @Override
- public boolean supportsAppInfoDropTarget() {
- return true;
- }
-
- @Override
- public boolean supportsDeleteDropTarget() {
- return false;
- }
-
- @Override
- public float getIntrinsicIconScaleFactor() {
- return 1f;
- }
-
- @Override
- public void onFlingToDeleteCompleted() {
- // Don't care; ignore.
- }
-
- @Override
- public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
- boolean success) {
- if (!success) {
- d.dragView.remove();
- mLauncher.showWorkspace(true);
- mLauncher.getDropTargetBar().onDragEnd();
- }
- }
-
- @Override
- public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
- // Either the original icon or one of the shortcuts was dragged.
- // Hide the container, but don't remove it yet because that interferes with touch events.
- animateClose();
- }
-
- @Override
- public void onDragEnd() {
- if (!mIsOpen) {
- if (mOpenCloseAnimator != null) {
- // Close animation is running.
- mDeferContainerRemoval = false;
- } else {
- // Close animation is not running.
- if (mDeferContainerRemoval) {
- close();
- }
- }
- }
- mDeferredDragIcon.setVisibility(VISIBLE);
- }
-
- @Override
- public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
- target.itemType = LauncherLogProto.DEEPSHORTCUT;
- // TODO: add target.rank
- targetParent.containerType = LauncherLogProto.DEEPSHORTCUTS;
- }
-
- public void animateClose() {
- if (!mIsOpen) {
- return;
- }
- if (mOpenCloseAnimator != null) {
- mOpenCloseAnimator.cancel();
- }
- mIsOpen = false;
-
- final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet();
- final int shortcutCount = getShortcutCount();
- int numOpenShortcuts = 0;
- for (int i = 0; i < shortcutCount; i++) {
- if (getShortcutAt(i).isOpenOrOpening()) {
- numOpenShortcuts++;
- }
- }
- final long duration = getResources().getInteger(
- R.integer.config_deepShortcutCloseDuration);
- final long arrowScaleDuration = getResources().getInteger(
- R.integer.config_deepShortcutArrowOpenDuration);
- final long stagger = getResources().getInteger(
- R.integer.config_deepShortcutCloseStagger);
- final TimeInterpolator fadeInterpolator = new LogAccelerateInterpolator(100, 0);
-
- int firstOpenShortcutIndex = mIsAboveIcon ? shortcutCount - numOpenShortcuts : 0;
- for (int i = firstOpenShortcutIndex; i < firstOpenShortcutIndex + numOpenShortcuts; i++) {
- final DeepShortcutView view = getShortcutAt(i);
- Animator anim;
- if (view.willDrawIcon()) {
- anim = view.createCloseAnimation(mIsAboveIcon, mIsLeftAligned, duration);
- int animationIndex = mIsAboveIcon ? i - firstOpenShortcutIndex
- : numOpenShortcuts - i - 1;
- anim.setStartDelay(stagger * animationIndex);
-
- Animator fadeAnim = new LauncherViewPropertyAnimator(view).alpha(0);
- // Don't start fading until the arrow is gone.
- fadeAnim.setStartDelay(stagger * animationIndex + arrowScaleDuration);
- fadeAnim.setDuration(duration - arrowScaleDuration);
- fadeAnim.setInterpolator(fadeInterpolator);
- shortcutAnims.play(fadeAnim);
- } else {
- // The view is being dragged. Animate it such that it collapses with the drag view
- anim = view.collapseToIcon();
- anim.setDuration(DragView.VIEW_ZOOM_DURATION);
-
- // Scale and translate the view to follow the drag view.
- Point iconCenter = view.getIconCenter();
- view.setPivotX(iconCenter.x);
- view.setPivotY(iconCenter.y);
-
- float scale = ((float) mLauncher.getDeviceProfile().iconSizePx) / view.getHeight();
- LauncherViewPropertyAnimator anim2 = new LauncherViewPropertyAnimator(view)
- .scaleX(scale)
- .scaleY(scale)
- .translationX(mIconShift.x)
- .translationY(mIconShift.y);
- anim2.setDuration(DragView.VIEW_ZOOM_DURATION);
- shortcutAnims.play(anim2);
- }
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- view.setVisibility(INVISIBLE);
- }
- });
- shortcutAnims.play(anim);
- }
- Animator arrowAnim = new LauncherViewPropertyAnimator(mArrow)
- .scaleX(0).scaleY(0).setDuration(arrowScaleDuration);
- arrowAnim.setStartDelay(0);
- shortcutAnims.play(arrowAnim);
-
- shortcutAnims.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mOpenCloseAnimator = null;
- if (mDeferContainerRemoval) {
- setVisibility(INVISIBLE);
- } else {
- close();
- }
- }
- });
- mOpenCloseAnimator = shortcutAnims;
- shortcutAnims.start();
- }
-
- /**
- * Closes the folder without animation.
- */
- public void close() {
- if (mOpenCloseAnimator != null) {
- mOpenCloseAnimator.cancel();
- mOpenCloseAnimator = null;
- }
- mIsOpen = false;
- mDeferContainerRemoval = false;
- boolean isInHotseat = ((ItemInfo) mDeferredDragIcon.getTag()).container
- == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
- mDeferredDragIcon.setTextVisibility(!isInHotseat);
- mLauncher.getDragController().removeDragListener(this);
- mLauncher.getDragLayer().removeView(this);
- }
-
- public boolean isOpen() {
- return mIsOpen;
- }
-
- /**
- * Shows the shortcuts container for {@param icon}
- * @return the container if shown or null.
- */
- public static DeepShortcutsContainer showForIcon(BubbleTextView icon) {
- Launcher launcher = Launcher.getLauncher(icon.getContext());
- if (launcher.getOpenShortcutsContainer() != null) {
- // There is already a shortcuts container open, so don't open this one.
- icon.clearFocus();
- return null;
- }
- List<String> ids = launcher.getShortcutIdsForItem((ItemInfo) icon.getTag());
- if (!ids.isEmpty()) {
- // There are shortcuts associated with the app, so defer its drag.
- final DeepShortcutsContainer container =
- (DeepShortcutsContainer) launcher.getLayoutInflater().inflate(
- R.layout.deep_shortcuts_container, launcher.getDragLayer(), false);
- container.setVisibility(View.INVISIBLE);
- launcher.getDragLayer().addView(container);
- container.populateAndShow(icon, ids);
- return container;
- }
- return null;
- }
-
- /**
- * Extension of {@link ShortcutInfo} which does not badge the icons.
- */
- static class UnbadgedShortcutInfo extends ShortcutInfo {
- public final ShortcutInfoCompat mDetail;
-
- public UnbadgedShortcutInfo(ShortcutInfoCompat shortcutInfo, Context context) {
- super(shortcutInfo, context);
- mDetail = shortcutInfo;
- }
-
- @Override
- protected Bitmap getBadgedIcon(Bitmap unbadgedBitmap, ShortcutInfoCompat shortcutInfo,
- IconCache cache, Context context) {
- return unbadgedBitmap;
- }
- }
-}
diff --git a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
index 2adb82e..ab8de6b 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
@@ -23,7 +23,7 @@
import android.graphics.drawable.Drawable;
import android.view.View;
-import com.android.launcher3.HolographicOutlineHelper;
+import com.android.launcher3.graphics.HolographicOutlineHelper;
import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
import com.android.launcher3.graphics.DragPreviewProvider;
@@ -44,7 +44,7 @@
public Bitmap createDragOutline(Canvas canvas) {
Bitmap b = drawScaledPreview(canvas, Bitmap.Config.ALPHA_8);
- HolographicOutlineHelper.obtain(mView.getContext())
+ HolographicOutlineHelper.getInstance(mView.getContext())
.applyExpensiveOutlineWithBlur(b, canvas);
canvas.setBitmap(null);
return b;
@@ -64,13 +64,13 @@
int size = Launcher.getLauncher(mView.getContext()).getDeviceProfile().iconSizePx;
final Bitmap b = Bitmap.createBitmap(
- size + DRAG_BITMAP_PADDING,
- size + DRAG_BITMAP_PADDING,
+ size + blurSizeOutline,
+ size + blurSizeOutline,
config);
canvas.setBitmap(b);
canvas.save(Canvas.MATRIX_SAVE_FLAG);
- canvas.translate(DRAG_BITMAP_PADDING / 2, DRAG_BITMAP_PADDING / 2);
+ canvas.translate(blurSizeOutline / 2, blurSizeOutline / 2);
canvas.scale(((float) size) / bounds.width(), ((float) size) / bounds.height(), 0, 0);
canvas.translate(bounds.left, bounds.top);
d.draw(canvas);
diff --git a/src/com/android/launcher3/shortcuts/ShortcutFilter.java b/src/com/android/launcher3/shortcuts/ShortcutFilter.java
deleted file mode 100644
index ec68817..0000000
--- a/src/com/android/launcher3/shortcuts/ShortcutFilter.java
+++ /dev/null
@@ -1,92 +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.shortcuts;
-
-import android.support.annotation.VisibleForTesting;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * Sorts and filters shortcuts.
- */
-public class ShortcutFilter {
-
- public static final int MAX_SHORTCUTS = 4;
- @VisibleForTesting static final int NUM_DYNAMIC = 2;
-
- /**
- * Sorts shortcuts in rank order, with manifest shortcuts coming before dynamic shortcuts.
- */
- private static final Comparator<ShortcutInfoCompat> RANK_COMPARATOR
- = new Comparator<ShortcutInfoCompat>() {
- @Override
- public int compare(ShortcutInfoCompat a, ShortcutInfoCompat b) {
- if (a.isDeclaredInManifest() && !b.isDeclaredInManifest()) {
- return -1;
- }
- if (!a.isDeclaredInManifest() && b.isDeclaredInManifest()) {
- return 1;
- }
- return Integer.compare(a.getRank(), b.getRank());
- }
- };
-
- /**
- * Filters the shortcuts so that only MAX_SHORTCUTS or fewer shortcuts are retained.
- * We want the filter to include both static and dynamic shortcuts, so we always
- * include NUM_DYNAMIC dynamic shortcuts, if at least that many are present.
- *
- * @return a subset of shortcuts, in sorted order, with size <= MAX_SHORTCUTS.
- */
- public static List<ShortcutInfoCompat> sortAndFilterShortcuts(
- List<ShortcutInfoCompat> shortcuts) {
- Collections.sort(shortcuts, RANK_COMPARATOR);
- if (shortcuts.size() <= MAX_SHORTCUTS) {
- return shortcuts;
- }
-
- // The list of shortcuts is now sorted with static shortcuts followed by dynamic
- // shortcuts. We want to preserve this order, but only keep MAX_SHORTCUTS.
- List<ShortcutInfoCompat> filteredShortcuts = new ArrayList<>(MAX_SHORTCUTS);
- int numDynamic = 0;
- int size = shortcuts.size();
- for (int i = 0; i < size; i++) {
- ShortcutInfoCompat shortcut = shortcuts.get(i);
- int filteredSize = filteredShortcuts.size();
- if (filteredSize < MAX_SHORTCUTS) {
- // Always add the first MAX_SHORTCUTS to the filtered list.
- filteredShortcuts.add(shortcut);
- if (shortcut.isDynamic()) {
- numDynamic++;
- }
- continue;
- }
- // At this point, we have MAX_SHORTCUTS already, but they may all be static.
- // If there are dynamic shortcuts, remove static shortcuts to add them.
- if (shortcut.isDynamic() && numDynamic < NUM_DYNAMIC) {
- numDynamic++;
- int lastStaticIndex = filteredSize - numDynamic;
- filteredShortcuts.remove(lastStaticIndex);
- filteredShortcuts.add(shortcut);
- }
- }
- return filteredShortcuts;
- }
-}
diff --git a/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java b/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
index a6da668..37047bb 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
@@ -22,11 +22,9 @@
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.os.Build;
+import android.os.UserHandle;
import com.android.launcher3.ItemInfo;
-import com.android.launcher3.compat.DeferredLauncherActivityInfo;
-import com.android.launcher3.compat.LauncherActivityInfoCompat;
-import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
/**
@@ -46,15 +44,12 @@
}
@TargetApi(Build.VERSION_CODES.N)
- public Intent makeIntent(Context context) {
- long serialNumber = UserManagerCompat.getInstance(context)
- .getSerialNumberForUser(getUserHandle());
+ public Intent makeIntent() {
return new Intent(Intent.ACTION_MAIN)
.addCategory(INTENT_CATEGORY)
.setComponent(getActivity())
.setPackage(getPackage())
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
- .putExtra(ItemInfo.EXTRA_PROFILE, serialNumber)
.putExtra(EXTRA_SHORTCUT_ID, getId());
}
@@ -86,8 +81,8 @@
return mShortcutInfo.getActivity();
}
- public UserHandleCompat getUserHandle() {
- return UserHandleCompat.fromUser(mShortcutInfo.getUserHandle());
+ public UserHandle getUserHandle() {
+ return mShortcutInfo.getUserHandle();
}
public boolean hasKeyFieldsOnly() {
@@ -122,8 +117,4 @@
public String toString() {
return mShortcutInfo.toString();
}
-
- public LauncherActivityInfoCompat getActivityInfo(Context context) {
- return new DeferredLauncherActivityInfo(getActivity(), getUserHandle(), context);
- }
}
diff --git a/src/com/android/launcher3/shortcuts/ShortcutKey.java b/src/com/android/launcher3/shortcuts/ShortcutKey.java
index a219c54..e86bfb2 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutKey.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutKey.java
@@ -2,9 +2,9 @@
import android.content.ComponentName;
import android.content.Intent;
+import android.os.UserHandle;
-import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.ItemInfo;
import com.android.launcher3.util.ComponentKey;
/**
@@ -12,7 +12,7 @@
*/
public class ShortcutKey extends ComponentKey {
- public ShortcutKey(String packageName, UserHandleCompat user, String id) {
+ public ShortcutKey(String packageName, UserHandle user, String id) {
// Use the id as the class name.
super(new ComponentName(packageName, id), user);
}
@@ -26,13 +26,13 @@
shortcutInfo.getId());
}
- public static ShortcutKey fromIntent(Intent intent, UserHandleCompat user) {
+ public static ShortcutKey fromIntent(Intent intent, UserHandle user) {
String shortcutId = intent.getStringExtra(
ShortcutInfoCompat.EXTRA_SHORTCUT_ID);
return new ShortcutKey(intent.getPackage(), user, shortcutId);
}
- public static ShortcutKey fromShortcutInfo(ShortcutInfo info) {
- return fromIntent(info.getPromisedIntent(), info.user);
+ public static ShortcutKey fromItemInfo(ItemInfo info) {
+ return fromIntent(info.getIntent(), info.user);
}
}
diff --git a/src/com/android/launcher3/shortcuts/ShortcutsItemView.java b/src/com/android/launcher3/shortcuts/ShortcutsItemView.java
new file mode 100644
index 0000000..5b3b02e
--- /dev/null
+++ b/src/com/android/launcher3/shortcuts/ShortcutsItemView.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.shortcuts;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.content.Context;
+import android.graphics.Point;
+import android.support.v4.content.ContextCompat;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.R;
+import com.android.launcher3.anim.PropertyListBuilder;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
+import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.popup.PopupItemView;
+import com.android.launcher3.popup.PopupPopulator;
+import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A {@link PopupItemView} that contains all of the {@link DeepShortcutView}s for an app,
+ * as well as the system shortcuts such as Widgets and App Info.
+ */
+public class ShortcutsItemView extends PopupItemView implements View.OnLongClickListener,
+ View.OnTouchListener, LogContainerProvider {
+
+ private Launcher mLauncher;
+ private LinearLayout mShortcutsLayout;
+ private LinearLayout mSystemShortcutIcons;
+ private final Point mIconShift = new Point();
+ private final Point mIconLastTouchPos = new Point();
+ private final List<DeepShortcutView> mDeepShortcutViews = new ArrayList<>();
+ private final List<View> mSystemShortcutViews = new ArrayList<>();
+
+ public ShortcutsItemView(Context context) {
+ this(context, null, 0);
+ }
+
+ public ShortcutsItemView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ShortcutsItemView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ mLauncher = Launcher.getLauncher(context);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mShortcutsLayout = findViewById(R.id.deep_shortcuts);
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent ev) {
+ // Touched a shortcut, update where it was touched so we can drag from there on long click.
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
+ break;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ // Return early if this is not initiated from a touch or not the correct view
+ if (!v.isInTouchMode() || !(v.getParent() instanceof DeepShortcutView)) return false;
+ // Return early if global dragging is not enabled
+ if (!mLauncher.isDraggingEnabled()) return false;
+ // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
+ if (mLauncher.getDragController().isDragging()) return false;
+
+ // Long clicked on a shortcut.
+ DeepShortcutView sv = (DeepShortcutView) v.getParent();
+ sv.setWillDrawIcon(false);
+
+ // Move the icon to align with the center-top of the touch point
+ mIconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
+ mIconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
+
+ DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
+ (PopupContainerWithArrow) getParent(), sv.getFinalInfo(),
+ new ShortcutDragPreviewProvider(sv.getIconView(), mIconShift), new DragOptions());
+ dv.animateShift(-mIconShift.x, -mIconShift.y);
+
+ // TODO: support dragging from within folder without having to close it
+ AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
+ return false;
+ }
+
+ public void addShortcutView(View shortcutView, PopupPopulator.Item shortcutType) {
+ addShortcutView(shortcutView, shortcutType, -1);
+ }
+
+ private void addShortcutView(View shortcutView, PopupPopulator.Item shortcutType, int index) {
+ if (shortcutType == PopupPopulator.Item.SHORTCUT) {
+ mDeepShortcutViews.add((DeepShortcutView) shortcutView);
+ } else {
+ mSystemShortcutViews.add(shortcutView);
+ }
+ if (shortcutType == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
+ // System shortcut icons are added to a header that is separate from the full shortcuts.
+ if (mSystemShortcutIcons == null) {
+ mSystemShortcutIcons = (LinearLayout) mLauncher.getLayoutInflater().inflate(
+ R.layout.system_shortcut_icons, mShortcutsLayout, false);
+ mShortcutsLayout.addView(mSystemShortcutIcons, 0);
+ }
+ mSystemShortcutIcons.addView(shortcutView, index);
+ } else {
+ if (mShortcutsLayout.getChildCount() > 0) {
+ View prevChild = mShortcutsLayout.getChildAt(mShortcutsLayout.getChildCount() - 1);
+ if (prevChild instanceof DeepShortcutView) {
+ prevChild.findViewById(R.id.divider).setVisibility(VISIBLE);
+ }
+ }
+ mShortcutsLayout.addView(shortcutView, index);
+ }
+ }
+
+ public List<DeepShortcutView> getDeepShortcutViews(boolean reverseOrder) {
+ if (reverseOrder) {
+ Collections.reverse(mDeepShortcutViews);
+ }
+ return mDeepShortcutViews;
+ }
+
+ public List<View> getSystemShortcutViews(boolean reverseOrder) {
+ // Always reverse system shortcut icons (in the header)
+ // so they are in priority order from right to left.
+ if (reverseOrder || mSystemShortcutIcons != null) {
+ Collections.reverse(mSystemShortcutViews);
+ }
+ return mSystemShortcutViews;
+ }
+
+ /**
+ * Adds a {@link SystemShortcut.Widgets} item if there are widgets for the given ItemInfo.
+ */
+ public void enableWidgetsIfExist(final BubbleTextView originalIcon) {
+ ItemInfo itemInfo = (ItemInfo) originalIcon.getTag();
+ SystemShortcut widgetInfo = new SystemShortcut.Widgets();
+ View.OnClickListener onClickListener = widgetInfo.getOnClickListener(mLauncher, itemInfo);
+ View widgetsView = null;
+ for (View systemShortcutView : mSystemShortcutViews) {
+ if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) {
+ widgetsView = systemShortcutView;
+ break;
+ }
+ }
+ final PopupPopulator.Item widgetsItem = mSystemShortcutIcons == null
+ ? PopupPopulator.Item.SYSTEM_SHORTCUT
+ : PopupPopulator.Item.SYSTEM_SHORTCUT_ICON;
+ if (onClickListener != null && widgetsView == null) {
+ // We didn't have any widgets cached but now there are some, so enable the shortcut.
+ widgetsView = mLauncher.getLayoutInflater().inflate(widgetsItem.layoutId, this, false);
+ PopupPopulator.initializeSystemShortcut(getContext(), widgetsView, widgetInfo);
+ widgetsView.setOnClickListener(onClickListener);
+ if (widgetsItem == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
+ addShortcutView(widgetsView, widgetsItem, 0);
+ } else {
+ // If using the expanded system shortcut (as opposed to just the icon), we need to
+ // reopen the container to ensure measurements etc. all work out. While this could
+ // be quite janky, in practice the user would typically see a small flicker as the
+ // animation restarts partway through, and this is a very rare edge case anyway.
+ ((PopupContainerWithArrow) getParent()).close(false);
+ PopupContainerWithArrow.showForIcon(originalIcon);
+ }
+ } else if (onClickListener == null && widgetsView != null) {
+ // No widgets exist, but we previously added the shortcut so remove it.
+ if (widgetsItem == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
+ mSystemShortcutViews.remove(widgetsView);
+ mSystemShortcutIcons.removeView(widgetsView);
+ } else {
+ ((PopupContainerWithArrow) getParent()).close(false);
+ PopupContainerWithArrow.showForIcon(originalIcon);
+ }
+ }
+ }
+
+ @Override
+ public Animator createOpenAnimation(boolean isContainerAboveIcon, boolean pivotLeft) {
+ AnimatorSet openAnimation = LauncherAnimUtils.createAnimatorSet();
+ openAnimation.play(super.createOpenAnimation(isContainerAboveIcon, pivotLeft));
+ for (int i = 0; i < mShortcutsLayout.getChildCount(); i++) {
+ if (!(mShortcutsLayout.getChildAt(i) instanceof DeepShortcutView)) {
+ continue;
+ }
+ DeepShortcutView shortcutView = ((DeepShortcutView) mShortcutsLayout.getChildAt(i));
+ View deepShortcutIcon = shortcutView.getIconView();
+ deepShortcutIcon.setScaleX(0);
+ deepShortcutIcon.setScaleY(0);
+ openAnimation.play(LauncherAnimUtils.ofPropertyValuesHolder(
+ deepShortcutIcon, new PropertyListBuilder().scale(1).build()));
+ }
+ return openAnimation;
+ }
+
+ @Override
+ public Animator createCloseAnimation(boolean isContainerAboveIcon, boolean pivotLeft,
+ long duration) {
+ AnimatorSet closeAnimation = LauncherAnimUtils.createAnimatorSet();
+ closeAnimation.play(super.createCloseAnimation(isContainerAboveIcon, pivotLeft, duration));
+ for (int i = 0; i < mShortcutsLayout.getChildCount(); i++) {
+ if (!(mShortcutsLayout.getChildAt(i) instanceof DeepShortcutView)) {
+ continue;
+ }
+ DeepShortcutView shortcutView = ((DeepShortcutView) mShortcutsLayout.getChildAt(i));
+ View deepShortcutIcon = shortcutView.getIconView();
+ deepShortcutIcon.setScaleX(1);
+ deepShortcutIcon.setScaleY(1);
+ closeAnimation.play(LauncherAnimUtils.ofPropertyValuesHolder(
+ deepShortcutIcon, new PropertyListBuilder().scale(0).build()));
+ }
+ return closeAnimation;
+ }
+
+ @Override
+ public int getArrowColor(boolean isArrowAttachedToBottom) {
+ return ContextCompat.getColor(getContext(),
+ isArrowAttachedToBottom || mSystemShortcutIcons == null
+ ? R.color.popup_background_color
+ : R.color.popup_header_background_color);
+ }
+
+ @Override
+ public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
+ LauncherLogProto.Target targetParent) {
+ target.itemType = LauncherLogProto.ItemType.DEEPSHORTCUT;
+ target.rank = info.rank;
+ targetParent.containerType = LauncherLogProto.ContainerType.DEEPSHORTCUTS;
+ }
+}
diff --git a/src/com/android/launcher3/testing/LauncherExtension.java b/src/com/android/launcher3/testing/LauncherExtension.java
index 6797c7b..aedca8d 100644
--- a/src/com/android/launcher3/testing/LauncherExtension.java
+++ b/src/com/android/launcher3/testing/LauncherExtension.java
@@ -180,9 +180,6 @@
}
@Override
- public UserEventDispatcher getUserEventDispatcher() { return null; }
-
- @Override
public View getQsbBar() {
return null;
}
diff --git a/src/com/android/launcher3/testing/ToggleWeightWatcher.java b/src/com/android/launcher3/testing/ToggleWeightWatcher.java
index e08ec3a..f0c3920 100644
--- a/src/com/android/launcher3/testing/ToggleWeightWatcher.java
+++ b/src/com/android/launcher3/testing/ToggleWeightWatcher.java
@@ -22,7 +22,7 @@
show = !show;
sp.edit().putBoolean(TestingUtils.SHOW_WEIGHT_WATCHER, show).apply();
- Launcher launcher = (Launcher) LauncherAppState.getInstance().getModel().getCallback();
+ Launcher launcher = (Launcher) LauncherAppState.getInstance(this).getModel().getCallback();
if (launcher != null && launcher.mWeightWatcher != null) {
launcher.mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
}
diff --git a/src/com/android/launcher3/util/CachedPackageTracker.java b/src/com/android/launcher3/util/CachedPackageTracker.java
index 293714e..314b4c0 100644
--- a/src/com/android/launcher3/util/CachedPackageTracker.java
+++ b/src/com/android/launcher3/util/CachedPackageTracker.java
@@ -18,12 +18,12 @@
import android.content.Context;
import android.content.SharedPreferences;
+import android.content.pm.LauncherActivityInfo;
+import android.os.UserHandle;
import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.LauncherActivityInfoCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.LauncherAppsCompat.OnAppsChangedCallbackCompat;
-import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
import java.util.ArrayList;
@@ -55,7 +55,7 @@
* Checks the list of user apps, and generates package event accordingly.
* {@see #onLauncherAppsAdded}, {@see #onLauncherPackageRemoved}
*/
- public void processUserApps(List<LauncherActivityInfoCompat> apps, UserHandleCompat user) {
+ public void processUserApps(List<LauncherActivityInfo> apps, UserHandle user) {
String prefKey = INSTALLED_PACKAGES_PREFIX + mUserManager.getSerialNumberForUser(user);
HashSet<String> oldPackageSet = new HashSet<>();
final boolean userAppsExisted = getUserApps(oldPackageSet, prefKey);
@@ -64,7 +64,7 @@
HashSet<String> newPackageSet = new HashSet<>();
ArrayList<LauncherActivityInstallInfo> packagesAdded = new ArrayList<>();
- for (LauncherActivityInfoCompat info : apps) {
+ for (LauncherActivityInfo info : apps) {
String packageName = info.getComponentName().getPackageName();
newPackageSet.add(packageName);
packagesRemoved.remove(packageName);
@@ -107,7 +107,7 @@
}
@Override
- public void onPackageRemoved(String packageName, UserHandleCompat user) {
+ public void onPackageRemoved(String packageName, UserHandle user) {
String prefKey = INSTALLED_PACKAGES_PREFIX + mUserManager.getSerialNumberForUser(user);
HashSet<String> packageSet = new HashSet<>();
if (getUserApps(packageSet, prefKey) && packageSet.remove(packageName)) {
@@ -118,15 +118,15 @@
}
@Override
- public void onPackageAdded(String packageName, UserHandleCompat user) {
+ public void onPackageAdded(String packageName, UserHandle user) {
String prefKey = INSTALLED_PACKAGES_PREFIX + mUserManager.getSerialNumberForUser(user);
HashSet<String> packageSet = new HashSet<>();
final boolean userAppsExisted = getUserApps(packageSet, prefKey);
if (!packageSet.contains(packageName)) {
- List<LauncherActivityInfoCompat> activities =
+ List<LauncherActivityInfo> activities =
mLauncherApps.getActivityList(packageName, user);
if (!activities.isEmpty()) {
- LauncherActivityInfoCompat activityInfo = activities.get(0);
+ LauncherActivityInfo activityInfo = activities.get(0);
packageSet.add(packageName);
mPrefs.edit().putStringSet(prefKey, packageSet).apply();
@@ -138,21 +138,21 @@
}
@Override
- public void onPackageChanged(String packageName, UserHandleCompat user) { }
+ public void onPackageChanged(String packageName, UserHandle user) { }
@Override
public void onPackagesAvailable(
- String[] packageNames, UserHandleCompat user, boolean replacing) { }
+ String[] packageNames, UserHandle user, boolean replacing) { }
@Override
public void onPackagesUnavailable(
- String[] packageNames, UserHandleCompat user, boolean replacing) { }
+ String[] packageNames, UserHandle user, boolean replacing) { }
@Override
- public void onPackagesSuspended(String[] packageNames, UserHandleCompat user) { }
+ public void onPackagesSuspended(String[] packageNames, UserHandle user) { }
@Override
- public void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user) { }
+ public void onPackagesUnsuspended(String[] packageNames, UserHandle user) { }
/**
* Called when new launcher apps are added.
@@ -163,19 +163,19 @@
* when Launcher was newly installed or a new user was added.
*/
protected abstract void onLauncherAppsAdded(List<LauncherActivityInstallInfo> apps,
- UserHandleCompat user, boolean userAppsExisted);
+ UserHandle user, boolean userAppsExisted);
/**
* Called when apps are removed from the system.
*/
- protected abstract void onLauncherPackageRemoved(String packageName, UserHandleCompat user);
+ protected abstract void onLauncherPackageRemoved(String packageName, UserHandle user);
public static class LauncherActivityInstallInfo
implements Comparable<LauncherActivityInstallInfo> {
- public final LauncherActivityInfoCompat info;
+ public final LauncherActivityInfo info;
public final long installTime;
- public LauncherActivityInstallInfo(LauncherActivityInfoCompat info, long installTime) {
+ public LauncherActivityInstallInfo(LauncherActivityInfo info, long installTime) {
this.info = info;
this.installTime = installTime;
}
diff --git a/src/com/android/launcher3/util/CircleRevealOutlineProvider.java b/src/com/android/launcher3/util/CircleRevealOutlineProvider.java
index c192120..9fe5147 100644
--- a/src/com/android/launcher3/util/CircleRevealOutlineProvider.java
+++ b/src/com/android/launcher3/util/CircleRevealOutlineProvider.java
@@ -16,10 +16,6 @@
package com.android.launcher3.util;
-import android.annotation.TargetApi;
-import android.os.Build;
-
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class CircleRevealOutlineProvider extends RevealOutlineAnimation {
private int mCenterX;
diff --git a/src/com/android/launcher3/util/ComponentKey.java b/src/com/android/launcher3/util/ComponentKey.java
index 5882f21..242df2e 100644
--- a/src/com/android/launcher3/util/ComponentKey.java
+++ b/src/com/android/launcher3/util/ComponentKey.java
@@ -18,8 +18,9 @@
import android.content.ComponentName;
import android.content.Context;
+import android.os.Process;
+import android.os.UserHandle;
-import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
import java.util.Arrays;
@@ -27,11 +28,11 @@
public class ComponentKey {
public final ComponentName componentName;
- public final UserHandleCompat user;
+ public final UserHandle user;
private final int mHashCode;
- public ComponentKey(ComponentName componentName, UserHandleCompat user) {
+ public ComponentKey(ComponentName componentName, UserHandle user) {
Preconditions.assertNotNull(componentName);
Preconditions.assertNotNull(user);
this.componentName = componentName;
@@ -56,7 +57,7 @@
} else {
// No user provided, default to the current user
componentName = ComponentName.unflattenFromString(componentKeyStr);
- user = UserHandleCompat.myUserHandle();
+ user = Process.myUserHandle();
}
Preconditions.assertNotNull(componentName);
Preconditions.assertNotNull(user);
diff --git a/src/com/android/launcher3/util/ConfigMonitor.java b/src/com/android/launcher3/util/ConfigMonitor.java
index bdb1639..2489d30 100644
--- a/src/com/android/launcher3/util/ConfigMonitor.java
+++ b/src/com/android/launcher3/util/ConfigMonitor.java
@@ -23,8 +23,6 @@
import android.content.res.Configuration;
import android.util.Log;
-import com.android.launcher3.Utilities;
-
/**
* {@link BroadcastReceiver} which watches configuration changes and
* restarts the process in case changes which affect the device profile occur.
@@ -40,13 +38,13 @@
Configuration config = context.getResources().getConfiguration();
mFontScale = config.fontScale;
- mDensity = getDensity(config);
+ mDensity = config.densityDpi;
}
@Override
public void onReceive(Context context, Intent intent) {
Configuration config = context.getResources().getConfiguration();
- if (mFontScale != config.fontScale || mDensity != getDensity(config)) {
+ if (mFontScale != config.fontScale || mDensity != config.densityDpi) {
Log.d("ConfigMonitor", "Configuration changed, restarting launcher");
mContext.unregisterReceiver(this);
android.os.Process.killProcess(android.os.Process.myPid());
@@ -56,8 +54,4 @@
public void register() {
mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
}
-
- private static int getDensity(Configuration config) {
- return Utilities.ATLEAST_JB_MR1 ? config.densityDpi : 0;
- }
}
diff --git a/src/com/android/launcher3/util/ContentWriter.java b/src/com/android/launcher3/util/ContentWriter.java
new file mode 100644
index 0000000..4384328
--- /dev/null
+++ b/src/com/android/launcher3/util/ContentWriter.java
@@ -0,0 +1,126 @@
+package com.android.launcher3.util;
+
+/**
+ * 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.
+ */
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.UserHandle;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.UserManagerCompat;
+
+/**
+ * A wrapper around {@link ContentValues} with some utility methods.
+ */
+public class ContentWriter {
+
+ private final ContentValues mValues;
+ private final Context mContext;
+
+ private CommitParams mCommitParams;
+ private Bitmap mIcon;
+ private UserHandle mUser;
+
+ public ContentWriter(Context context, CommitParams commitParams) {
+ this(context);
+ mCommitParams = commitParams;
+ }
+
+ public ContentWriter(Context context) {
+ this(new ContentValues(), context);
+ }
+
+ public ContentWriter(ContentValues values, Context context) {
+ mValues = values;
+ mContext = context;
+ }
+
+ public ContentWriter put(String key, Integer value) {
+ mValues.put(key, value);
+ return this;
+ }
+
+ public ContentWriter put(String key, Long value) {
+ mValues.put(key, value);
+ return this;
+ }
+
+ public ContentWriter put(String key, String value) {
+ mValues.put(key, value);
+ return this;
+ }
+
+ public ContentWriter put(String key, CharSequence value) {
+ mValues.put(key, value == null ? null : value.toString());
+ return this;
+ }
+
+ public ContentWriter put(String key, Intent value) {
+ mValues.put(key, value == null ? null : value.toUri(0));
+ return this;
+ }
+
+ public ContentWriter putIcon(Bitmap value, UserHandle user) {
+ mIcon = value;
+ mUser = user;
+ return this;
+ }
+
+ public ContentWriter put(String key, UserHandle user) {
+ return put(key, UserManagerCompat.getInstance(mContext).getSerialNumberForUser(user));
+ }
+
+ /**
+ * Commits any pending validation and returns the final values.
+ * Must not be called on UI thread.
+ */
+ public ContentValues getValues(Context context) {
+ Preconditions.assertNonUiThread();
+ if (mIcon != null && !LauncherAppState.getInstance(context).getIconCache()
+ .isDefaultIcon(mIcon, mUser)) {
+ mValues.put(LauncherSettings.Favorites.ICON, Utilities.flattenBitmap(mIcon));
+ mIcon = null;
+ }
+ return mValues;
+ }
+
+ public int commit() {
+ if (mCommitParams != null) {
+ return mContext.getContentResolver().update(mCommitParams.mUri, getValues(mContext),
+ mCommitParams.mWhere, mCommitParams.mSelectionArgs);
+ }
+ return 0;
+ }
+
+ public static final class CommitParams {
+
+ final Uri mUri = LauncherSettings.Favorites.CONTENT_URI;
+ String mWhere;
+ String[] mSelectionArgs;
+
+ public CommitParams(String where, String[] selectionArgs) {
+ mWhere = where;
+ mSelectionArgs = selectionArgs;
+ }
+
+ }
+}
diff --git a/src/com/android/launcher3/util/CursorIconInfo.java b/src/com/android/launcher3/util/CursorIconInfo.java
deleted file mode 100644
index 4fefa98..0000000
--- a/src/com/android/launcher3/util/CursorIconInfo.java
+++ /dev/null
@@ -1,85 +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.util;
-
-import android.content.Context;
-import android.content.Intent.ShortcutIconResource;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.text.TextUtils;
-
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.Utilities;
-
-/**
- * Utility class to load icon from a cursor.
- */
-public class CursorIconInfo {
- public final int iconPackageIndex;
- public final int iconResourceIndex;
- public final int iconIndex;
-
- public final int titleIndex;
-
- private final Context mContext;
-
- public CursorIconInfo(Context context, Cursor c) {
- mContext = context;
-
- iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
- iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
- iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
-
- titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
- }
-
- /**
- * Loads the icon from the cursor and updates the {@param info} if the icon is an app resource.
- */
- public Bitmap loadIcon(Cursor c, ShortcutInfo info) {
- Bitmap icon = null;
- String packageName = c.getString(iconPackageIndex);
- String resourceName = c.getString(iconResourceIndex);
- if (!TextUtils.isEmpty(packageName) || !TextUtils.isEmpty(resourceName)) {
- info.iconResource = new ShortcutIconResource();
- info.iconResource.packageName = packageName;
- info.iconResource.resourceName = resourceName;
- icon = Utilities.createIconBitmap(packageName, resourceName, mContext);
- }
- if (icon == null) {
- // Failed to load from resource, try loading from DB.
- icon = loadIcon(c);
- }
- return icon;
- }
-
- /**
- * Loads the fixed bitmap from the icon if available.
- */
- public Bitmap loadIcon(Cursor c) {
- return Utilities.createIconBitmap(c, iconIndex, mContext);
- }
-
- /**
- * Returns the title or empty string
- */
- public String getTitle(Cursor c) {
- String title = c.getString(titleIndex);
- return TextUtils.isEmpty(title) ? "" : Utilities.trim(c.getString(titleIndex));
- }
-}
diff --git a/src/com/android/launcher3/util/FlingAnimation.java b/src/com/android/launcher3/util/FlingAnimation.java
index da8bae7..d475ee9 100644
--- a/src/com/android/launcher3/util/FlingAnimation.java
+++ b/src/com/android/launcher3/util/FlingAnimation.java
@@ -5,13 +5,16 @@
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
+import com.android.launcher3.ButtonDropTarget;
import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.Launcher;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragView;
-public class FlingAnimation implements AnimatorUpdateListener {
+public class FlingAnimation implements AnimatorUpdateListener, Runnable {
/**
* Maximum acceleration in one dimension (pixels per milliseconds)
@@ -19,40 +22,86 @@
private static final float MAX_ACCELERATION = 0.5f;
private static final int DRAG_END_DELAY = 300;
+ private final ButtonDropTarget mDropTarget;
+ private final Launcher mLauncher;
+
protected final DragObject mDragObject;
- protected final Rect mIconRect;
protected final DragLayer mDragLayer;
- protected final Rect mFrom;
- protected final int mDuration;
- protected final float mUX, mUY;
- protected final float mAnimationTimeFraction;
protected final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);
+ protected final float mUX, mUY;
+
+ protected Rect mIconRect;
+ protected Rect mFrom;
+ protected int mDuration;
+ protected float mAnimationTimeFraction;
protected float mAX, mAY;
- /**
- * @param vel initial fling velocity in pixels per second.
- */
- public FlingAnimation(DragObject d, PointF vel, Rect iconRect, DragLayer dragLayer) {
+ public FlingAnimation(DragObject d, PointF vel, ButtonDropTarget dropTarget, Launcher launcher) {
+ mDropTarget = dropTarget;
+ mLauncher = launcher;
mDragObject = d;
mUX = vel.x / 1000;
mUY = vel.y / 1000;
- mIconRect = iconRect;
+ mDragLayer = mLauncher.getDragLayer();
+ }
- mDragLayer = dragLayer;
+ @Override
+ public void run() {
+ mIconRect = mDropTarget.getIconRect(mDragObject);
+
+ // Initiate from
mFrom = new Rect();
- dragLayer.getViewRectRelativeToSelf(d.dragView, mFrom);
-
- float scale = d.dragView.getScaleX();
- float xOffset = ((scale - 1f) * d.dragView.getMeasuredWidth()) / 2f;
- float yOffset = ((scale - 1f) * d.dragView.getMeasuredHeight()) / 2f;
+ mDragLayer.getViewRectRelativeToSelf(mDragObject.dragView, mFrom);
+ float scale = mDragObject.dragView.getScaleX();
+ float xOffset = ((scale - 1f) * mDragObject.dragView.getMeasuredWidth()) / 2f;
+ float yOffset = ((scale - 1f) * mDragObject.dragView.getMeasuredHeight()) / 2f;
mFrom.left += xOffset;
mFrom.right -= xOffset;
mFrom.top += yOffset;
mFrom.bottom -= yOffset;
+ mDuration = Math.abs(mUY) > Math.abs(mUX) ? initFlingUpDuration() : initFlingLeftDuration();
- mDuration = Math.abs(vel.y) > Math.abs(vel.x) ? initFlingUpDuration() : initFlingLeftDuration();
mAnimationTimeFraction = ((float) mDuration) / (mDuration + DRAG_END_DELAY);
+
+ // Don't highlight the icon as it's animating
+ mDragObject.dragView.setColor(0);
+
+ final int duration = mDuration + DRAG_END_DELAY;
+ final long startTime = AnimationUtils.currentAnimationTimeMillis();
+
+ // NOTE: Because it takes time for the first frame of animation to actually be
+ // called and we expect the animation to be a continuation of the fling, we have
+ // to account for the time that has elapsed since the fling finished. And since
+ // we don't have a startDelay, we will always get call to update when we call
+ // start() (which we want to ignore).
+ final TimeInterpolator tInterpolator = new TimeInterpolator() {
+ private int mCount = -1;
+ private float mOffset = 0f;
+
+ @Override
+ public float getInterpolation(float t) {
+ if (mCount < 0) {
+ mCount++;
+ } else if (mCount == 0) {
+ mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() -
+ startTime) / duration);
+ mCount++;
+ }
+ return Math.min(1f, mOffset + t);
+ }
+ };
+
+ Runnable onAnimationEndRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mLauncher.exitSpringLoadedDragMode();
+ mDropTarget.completeDrop(mDragObject);
+ }
+ };
+
+ mDragLayer.animateView(mDragObject.dragView, this, duration, tInterpolator,
+ onAnimationEndRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null);
}
/**
@@ -111,10 +160,6 @@
return (int) Math.round(t);
}
- public final int getDuration() {
- return mDuration + DRAG_END_DELAY;
- }
-
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float t = animation.getAnimatedFraction();
diff --git a/src/com/android/launcher3/util/FocusLogic.java b/src/com/android/launcher3/util/FocusLogic.java
index 163c953..afc45fe 100644
--- a/src/com/android/launcher3/util/FocusLogic.java
+++ b/src/com/android/launcher3/util/FocusLogic.java
@@ -79,8 +79,7 @@
return (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ||
keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN ||
keyCode == KeyEvent.KEYCODE_MOVE_HOME || keyCode == KeyEvent.KEYCODE_MOVE_END ||
- keyCode == KeyEvent.KEYCODE_PAGE_UP || keyCode == KeyEvent.KEYCODE_PAGE_DOWN ||
- keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL);
+ keyCode == KeyEvent.KEYCODE_PAGE_UP || keyCode == KeyEvent.KEYCODE_PAGE_DOWN);
}
public static int handleKeyEvent(int keyCode, int [][] map, int iconIdx, int pageIndex,
diff --git a/src/com/android/launcher3/util/IconNormalizer.java b/src/com/android/launcher3/util/IconNormalizer.java
deleted file mode 100644
index 040a1b5..0000000
--- a/src/com/android/launcher3/util/IconNormalizer.java
+++ /dev/null
@@ -1,250 +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.util;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-
-import com.android.launcher3.LauncherAppState;
-
-import java.nio.ByteBuffer;
-
-public class IconNormalizer {
-
- // Ratio of icon visible area to full icon size for a square shaped icon
- private static final float MAX_SQUARE_AREA_FACTOR = 375.0f / 576;
- // Ratio of icon visible area to full icon size for a circular shaped icon
- private static final float MAX_CIRCLE_AREA_FACTOR = 380.0f / 576;
-
- private static final float CIRCLE_AREA_BY_RECT = (float) Math.PI / 4;
-
- // Slope used to calculate icon visible area to full icon size for any generic shaped icon.
- private static final float LINEAR_SCALE_SLOPE =
- (MAX_CIRCLE_AREA_FACTOR - MAX_SQUARE_AREA_FACTOR) / (1 - CIRCLE_AREA_BY_RECT);
-
- private static final int MIN_VISIBLE_ALPHA = 40;
-
- private static final Object LOCK = new Object();
- private static IconNormalizer sIconNormalizer;
-
- private final int mMaxSize;
- private final Bitmap mBitmap;
- private final Canvas mCanvas;
- private final byte[] mPixels;
-
- // for each y, stores the position of the leftmost x and the rightmost x
- private final float[] mLeftBorder;
- private final float[] mRightBorder;
-
- private IconNormalizer() {
- // Use twice the icon size as maximum size to avoid scaling down twice.
- mMaxSize = LauncherAppState.getInstance().getInvariantDeviceProfile().iconBitmapSize * 2;
- mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
- mCanvas = new Canvas(mBitmap);
- mPixels = new byte[mMaxSize * mMaxSize];
-
- mLeftBorder = new float[mMaxSize];
- mRightBorder = new float[mMaxSize];
- }
-
- /**
- * Returns the amount by which the {@param d} should be scaled (in both dimensions) so that it
- * matches the design guidelines for a launcher icon.
- *
- * We first calculate the convex hull of the visible portion of the icon.
- * This hull then compared with the bounding rectangle of the hull to find how closely it
- * resembles a circle and a square, by comparing the ratio of the areas. Note that this is not an
- * ideal solution but it gives satisfactory result without affecting the performance.
- *
- * This closeness is used to determine the ratio of hull area to the full icon size.
- * Refer {@link #MAX_CIRCLE_AREA_FACTOR} and {@link #MAX_SQUARE_AREA_FACTOR}
- *
- * @param outBounds optional rect to receive the fraction distance from each edge.
- */
- public synchronized float getScale(Drawable d, RectF outBounds) {
- int width = d.getIntrinsicWidth();
- int height = d.getIntrinsicHeight();
- if (width <= 0 || height <= 0) {
- width = width <= 0 || width > mMaxSize ? mMaxSize : width;
- height = height <= 0 || height > mMaxSize ? mMaxSize : height;
- } else if (width > mMaxSize || height > mMaxSize) {
- int max = Math.max(width, height);
- width = mMaxSize * width / max;
- height = mMaxSize * height / max;
- }
-
- mBitmap.eraseColor(Color.TRANSPARENT);
- d.setBounds(0, 0, width, height);
- d.draw(mCanvas);
-
- ByteBuffer buffer = ByteBuffer.wrap(mPixels);
- buffer.rewind();
- mBitmap.copyPixelsToBuffer(buffer);
-
- // Overall bounds of the visible icon.
- int topY = -1;
- int bottomY = -1;
- int leftX = mMaxSize + 1;
- int rightX = -1;
-
- // Create border by going through all pixels one row at a time and for each row find
- // the first and the last non-transparent pixel. Set those values to mLeftBorder and
- // mRightBorder and use -1 if there are no visible pixel in the row.
-
- // buffer position
- int index = 0;
- // buffer shift after every row, width of buffer = mMaxSize
- int rowSizeDiff = mMaxSize - width;
- // first and last position for any row.
- int firstX, lastX;
-
- for (int y = 0; y < height; y++) {
- firstX = lastX = -1;
- for (int x = 0; x < width; x++) {
- if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
- if (firstX == -1) {
- firstX = x;
- }
- lastX = x;
- }
- index++;
- }
- index += rowSizeDiff;
-
- mLeftBorder[y] = firstX;
- mRightBorder[y] = lastX;
-
- // If there is at least one visible pixel, update the overall bounds.
- if (firstX != -1) {
- bottomY = y;
- if (topY == -1) {
- topY = y;
- }
-
- leftX = Math.min(leftX, firstX);
- rightX = Math.max(rightX, lastX);
- }
- }
-
- if (topY == -1 || rightX == -1) {
- // No valid pixels found. Do not scale.
- return 1;
- }
-
- convertToConvexArray(mLeftBorder, 1, topY, bottomY);
- convertToConvexArray(mRightBorder, -1, topY, bottomY);
-
- // Area of the convex hull
- float area = 0;
- for (int y = 0; y < height; y++) {
- if (mLeftBorder[y] <= -1) {
- continue;
- }
- area += mRightBorder[y] - mLeftBorder[y] + 1;
- }
-
- // Area of the rectangle required to fit the convex hull
- float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX);
- float hullByRect = area / rectArea;
-
- float scaleRequired;
- if (hullByRect < CIRCLE_AREA_BY_RECT) {
- scaleRequired = MAX_CIRCLE_AREA_FACTOR;
- } else {
- scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect);
- }
-
- if (outBounds != null) {
- outBounds.left = ((float) leftX) / width;
- outBounds.right = 1 - ((float) rightX) / width;
-
- outBounds.top = ((float) topY) / height;
- outBounds.bottom = 1 - ((float) bottomY) / height;
- }
-
- float areaScale = area / (width * height);
- // Use sqrt of the final ratio as the images is scaled across both width and height.
- float scale = areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
- return scale;
- }
-
- /**
- * Modifies {@param xCordinates} to represent a convex border. Fills in all missing values
- * (except on either ends) with appropriate values.
- * @param xCordinates map of x coordinate per y.
- * @param direction 1 for left border and -1 for right border.
- * @param topY the first Y position (inclusive) with a valid value.
- * @param bottomY the last Y position (inclusive) with a valid value.
- */
- private static void convertToConvexArray(
- float[] xCordinates, int direction, int topY, int bottomY) {
- int total = xCordinates.length;
- // The tangent at each pixel.
- float[] angles = new float[total - 1];
-
- int first = topY; // First valid y coordinate
- int last = -1; // Last valid y coordinate which didn't have a missing value
-
- float lastAngle = Float.MAX_VALUE;
-
- for (int i = topY + 1; i <= bottomY; i++) {
- if (xCordinates[i] <= -1) {
- continue;
- }
- int start;
-
- if (lastAngle == Float.MAX_VALUE) {
- start = first;
- } else {
- float currentAngle = (xCordinates[i] - xCordinates[last]) / (i - last);
- start = last;
- // If this position creates a concave angle, keep moving up until we find a
- // position which creates a convex angle.
- if ((currentAngle - lastAngle) * direction < 0) {
- while (start > first) {
- start --;
- currentAngle = (xCordinates[i] - xCordinates[start]) / (i - start);
- if ((currentAngle - angles[start]) * direction >= 0) {
- break;
- }
- }
- }
- }
-
- // Reset from last check
- lastAngle = (xCordinates[i] - xCordinates[start]) / (i - start);
- // Update all the points from start.
- for (int j = start; j < i; j++) {
- angles[j] = lastAngle;
- xCordinates[j] = xCordinates[start] + lastAngle * (j - start);
- }
- last = i;
- }
- }
-
- public static IconNormalizer getInstance() {
- synchronized (LOCK) {
- if (sIconNormalizer == null) {
- sIconNormalizer = new IconNormalizer();
- }
- }
- return sIconNormalizer;
- }
-}
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index 46e9184..42de284 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -17,11 +17,13 @@
package com.android.launcher3.util;
import android.content.ComponentName;
+import android.os.UserHandle;
+import com.android.launcher3.FolderInfo;
import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.shortcuts.ShortcutKey;
import java.util.HashSet;
@@ -33,8 +35,48 @@
public abstract boolean matches(ItemInfo info, ComponentName cn);
+ /**
+ * Filters {@param infos} to those satisfying the {@link #matches(ItemInfo, ComponentName)}.
+ */
+ public final HashSet<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos) {
+ HashSet<ItemInfo> filtered = new HashSet<>();
+ for (ItemInfo i : infos) {
+ if (i instanceof ShortcutInfo) {
+ ShortcutInfo info = (ShortcutInfo) i;
+ ComponentName cn = info.getTargetComponent();
+ if (cn != null && matches(info, cn)) {
+ filtered.add(info);
+ }
+ } else if (i instanceof FolderInfo) {
+ FolderInfo info = (FolderInfo) i;
+ for (ShortcutInfo s : info.contents) {
+ ComponentName cn = s.getTargetComponent();
+ if (cn != null && matches(s, cn)) {
+ filtered.add(s);
+ }
+ }
+ } else if (i instanceof LauncherAppWidgetInfo) {
+ LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
+ ComponentName cn = info.providerName;
+ if (cn != null && matches(info, cn)) {
+ filtered.add(info);
+ }
+ }
+ }
+ return filtered;
+ }
+
+ public static ItemInfoMatcher ofUser(final UserHandle user) {
+ return new ItemInfoMatcher() {
+ @Override
+ public boolean matches(ItemInfo info, ComponentName cn) {
+ return info.user.equals(user);
+ }
+ };
+ }
+
public static ItemInfoMatcher ofComponents(
- final HashSet<ComponentName> components, final UserHandleCompat user) {
+ final HashSet<ComponentName> components, final UserHandle user) {
return new ItemInfoMatcher() {
@Override
public boolean matches(ItemInfo info, ComponentName cn) {
@@ -44,7 +86,7 @@
}
public static ItemInfoMatcher ofPackages(
- final HashSet<String> packageNames, final UserHandleCompat user) {
+ final HashSet<String> packageNames, final UserHandle user) {
return new ItemInfoMatcher() {
@Override
public boolean matches(ItemInfo info, ComponentName cn) {
@@ -58,7 +100,7 @@
@Override
public boolean matches(ItemInfo info, ComponentName cn) {
return info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT &&
- keys.contains(ShortcutKey.fromShortcutInfo((ShortcutInfo) info));
+ keys.contains(ShortcutKey.fromItemInfo(info));
}
};
}
diff --git a/src/com/android/launcher3/util/LabelComparator.java b/src/com/android/launcher3/util/LabelComparator.java
new file mode 100644
index 0000000..5da9ddf
--- /dev/null
+++ b/src/com/android/launcher3/util/LabelComparator.java
@@ -0,0 +1,46 @@
+/*
+ * 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 java.text.Collator;
+import java.util.Comparator;
+
+/**
+ * Extension of {@link java.text.Collator} with special handling for digits. Used for comparing
+ * user visible labels.
+ */
+public class LabelComparator implements Comparator<String> {
+
+ private final Collator mCollator = Collator.getInstance();
+
+ @Override
+ public int compare(String titleA, String titleB) {
+ // Ensure that we de-prioritize any titles that don't start with a
+ // linguistic letter or digit
+ boolean aStartsWithLetter = (titleA.length() > 0) &&
+ Character.isLetterOrDigit(titleA.codePointAt(0));
+ boolean bStartsWithLetter = (titleB.length() > 0) &&
+ Character.isLetterOrDigit(titleB.codePointAt(0));
+ if (aStartsWithLetter && !bStartsWithLetter) {
+ return -1;
+ } else if (!aStartsWithLetter && bStartsWithLetter) {
+ return 1;
+ }
+
+ // Order by the title in the current locale
+ return mCollator.compare(titleA, titleB);
+ }
+}
diff --git a/src/com/android/launcher3/util/LogConfig.java b/src/com/android/launcher3/util/LogConfig.java
new file mode 100644
index 0000000..4acdb5c
--- /dev/null
+++ b/src/com/android/launcher3/util/LogConfig.java
@@ -0,0 +1,31 @@
+package com.android.launcher3.util;
+
+/**
+ * This is a utility class that keeps track of all the tag that can be enabled to debug
+ * a behavior in runtime.
+ *
+ * To use any of the strings defined in this class, execute the following command.
+ *
+ * $ adb shell setprop log.tag.TAGNAME VERBOSE
+ */
+
+public class LogConfig {
+ // These are list of strings that can be used to replace TAGNAME.
+
+ /**
+ * After this tag is turned on, whenever there is n user event, debug information is
+ * printed out to logcat.
+ */
+ public static final String USEREVENT = "UserEvent";
+
+ /**
+ * When turned on, all icons are kept on the home screen, even if they don't have an active
+ * session.
+ */
+ public static final String KEEP_ALL_ICONS = "KeepAllIcons";
+
+ /**
+ * When turned on, icon cache is only fetched from memory and not disk.
+ */
+ public static final String MEMORY_ONLY_ICON_CACHE = "MemoryOnlyIconCache";
+}
diff --git a/src/com/android/launcher3/util/LooperExecuter.java b/src/com/android/launcher3/util/LooperExecuter.java
new file mode 100644
index 0000000..4db999b
--- /dev/null
+++ b/src/com/android/launcher3/util/LooperExecuter.java
@@ -0,0 +1,81 @@
+/*
+ * 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.Handler;
+import android.os.Looper;
+
+import java.util.List;
+import java.util.concurrent.AbstractExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Extension of {@link AbstractExecutorService} which executed on a provided looper.
+ */
+public class LooperExecuter extends AbstractExecutorService {
+
+ private final Handler mHandler;
+
+ public LooperExecuter(Looper looper) {
+ mHandler = new Handler(looper);
+ }
+
+ @Override
+ public void execute(Runnable runnable) {
+ if (mHandler.getLooper() == Looper.myLooper()) {
+ runnable.run();
+ } else {
+ mHandler.post(runnable);
+ }
+ }
+
+ /**
+ * Not supported and throws an exception when used.
+ */
+ @Override
+ @Deprecated
+ public void shutdown() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Not supported and throws an exception when used.
+ */
+ @Override
+ @Deprecated
+ public List<Runnable> shutdownNow() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return false;
+ }
+
+ @Override
+ public boolean isTerminated() {
+ return false;
+ }
+
+ /**
+ * Not supported and throws an exception when used.
+ */
+ @Override
+ @Deprecated
+ public boolean awaitTermination(long l, TimeUnit timeUnit) throws InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
index 6661429..ce603c4 100644
--- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -18,20 +18,24 @@
import android.content.Context;
import android.content.SharedPreferences;
+import android.content.pm.LauncherActivityInfo;
+import android.os.Process;
+import android.os.UserHandle;
+import android.support.v4.os.BuildCompat;
+import com.android.launcher3.AppInfo;
import com.android.launcher3.FolderInfo;
+import com.android.launcher3.IconCache;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherFiles;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.R;
+import com.android.launcher3.SessionCommitReceiver;
import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.LauncherActivityInfoCompat;
-import com.android.launcher3.shortcuts.ShortcutInfoCompat;
-import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
import java.util.ArrayList;
import java.util.HashSet;
@@ -55,8 +59,8 @@
*/
private static final long AUTO_ADD_TO_FOLDER_DURATION = 8 * 60 * 60 * 1000;
- public static ManagedProfileHeuristic get(Context context, UserHandleCompat user) {
- if (Utilities.ATLEAST_LOLLIPOP && !UserHandleCompat.myUserHandle().equals(user)) {
+ public static ManagedProfileHeuristic get(Context context, UserHandle user) {
+ if (!Process.myUserHandle().equals(user)) {
return new ManagedProfileHeuristic(context, user);
}
return null;
@@ -64,12 +68,17 @@
private final Context mContext;
private final LauncherModel mModel;
- private final UserHandleCompat mUser;
+ private final UserHandle mUser;
+ private final IconCache mIconCache;
+ private final boolean mAddIconsToHomescreen;
- private ManagedProfileHeuristic(Context context, UserHandleCompat user) {
+ private ManagedProfileHeuristic(Context context, UserHandle user) {
mContext = context;
mUser = user;
- mModel = LauncherAppState.getInstance().getModel();
+ mModel = LauncherAppState.getInstance(context).getModel();
+ mIconCache = LauncherAppState.getInstance(context).getIconCache();
+ mAddIconsToHomescreen =
+ !BuildCompat.isAtLeastO() || SessionCommitReceiver.isEnabled(context);
}
public void processPackageRemoved(String[] packages) {
@@ -88,7 +97,7 @@
}
}
- public void processUserApps(List<LauncherActivityInfoCompat> apps) {
+ public void processUserApps(List<LauncherActivityInfo> apps) {
Preconditions.assertWorkerThread();
new ManagedProfilePackageHandler().processUserApps(apps, mUser);
}
@@ -100,7 +109,7 @@
}
protected void onLauncherAppsAdded(
- List<LauncherActivityInstallInfo> apps, UserHandleCompat user, boolean userAppsExisted) {
+ List<LauncherActivityInstallInfo> apps, UserHandle user, boolean userAppsExisted) {
ArrayList<ShortcutInfo> workFolderApps = new ArrayList<>();
ArrayList<ShortcutInfo> homescreenApps = new ArrayList<>();
@@ -108,10 +117,13 @@
long folderCreationTime =
mUserManager.getUserCreationTime(user) + AUTO_ADD_TO_FOLDER_DURATION;
+ boolean quietModeEnabled = UserManagerCompat.getInstance(mContext)
+ .isQuietModeEnabled(user);
for (int i = 0; i < count; i++) {
LauncherActivityInstallInfo info = apps.get(i);
-
- ShortcutInfo si = new ShortcutInfo(info.info, mContext);
+ AppInfo appInfo = new AppInfo(info.info, user, quietModeEnabled);
+ mIconCache.getTitleAndIcon(appInfo, info.info, false /* useLowResIcon */);
+ ShortcutInfo si = appInfo.makeShortcut();
((info.installTime <= folderCreationTime) ? workFolderApps : homescreenApps).add(si);
}
@@ -120,26 +132,33 @@
// Do not add shortcuts on the homescreen for the first time. This prevents the launcher
// getting filled with the managed user apps, when it start with a fresh DB (or after
// a very long time).
- if (userAppsExisted && !homescreenApps.isEmpty()) {
- mModel.addAndBindAddedWorkspaceItems(mContext, homescreenApps);
+ if (userAppsExisted && !homescreenApps.isEmpty() && mAddIconsToHomescreen) {
+ mModel.addAndBindAddedWorkspaceItems(new ArrayList<ItemInfo>(homescreenApps));
}
}
@Override
- protected void onLauncherPackageRemoved(String packageName, UserHandleCompat user) {
+ protected void onLauncherPackageRemoved(String packageName, UserHandle user) {
}
/**
* Adds and binds shortcuts marked to be added to the work folder.
*/
private void finalizeWorkFolder(
- UserHandleCompat user, final ArrayList<ShortcutInfo> workFolderApps,
+ UserHandle user, final ArrayList<ShortcutInfo> workFolderApps,
ArrayList<ShortcutInfo> homescreenApps) {
if (workFolderApps.isEmpty()) {
return;
}
// Try to get a work folder.
String folderIdKey = USER_FOLDER_ID_PREFIX + mUserManager.getSerialNumberForUser(user);
+ if (!mAddIconsToHomescreen) {
+ if (!mPrefs.contains(folderIdKey)) {
+ // Just mark the folder id preference to avoid new folder creation later.
+ mPrefs.edit().putLong(folderIdKey, -1).apply();
+ }
+ return;
+ }
if (mPrefs.contains(folderIdKey)) {
long folderId = mPrefs.getLong(folderIdKey, 0);
final FolderInfo workFolder = mModel.findFolderById(folderId);
@@ -156,6 +175,7 @@
@Override
public void run() {
+ workFolder.prepareAutoUpdate();
for (ShortcutInfo info : workFolderApps) {
workFolder.add(info, false);
}
@@ -173,9 +193,9 @@
}
// Add the item to home screen and DB. This also generates an item id synchronously.
- ArrayList<ItemInfo> itemList = new ArrayList<ItemInfo>(1);
+ ArrayList<ItemInfo> itemList = new ArrayList<>(1);
itemList.add(workFolder);
- mModel.addAndBindAddedWorkspaceItems(mContext, itemList);
+ mModel.addAndBindAddedWorkspaceItems(itemList);
mPrefs.edit().putLong(folderIdKey, workFolder.id).apply();
saveWorkFolderShortcuts(workFolder.id, 0, workFolderApps);
@@ -184,7 +204,7 @@
@Override
public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
- UserHandleCompat user) {
+ UserHandle user) {
// Do nothing
}
}
@@ -196,21 +216,17 @@
long workFolderId, int startingRank, ArrayList<ShortcutInfo> workFolderApps) {
for (ItemInfo info : workFolderApps) {
info.rank = startingRank++;
- LauncherModel.addItemToDatabase(mContext, info, workFolderId, 0, 0, 0);
+ mModel.getWriter(false).addItemToDatabase(info, workFolderId, 0, 0, 0);
}
}
-
/**
* Verifies that entries corresponding to {@param users} exist and removes all invalid entries.
*/
- public static void processAllUsers(List<UserHandleCompat> users, Context context) {
- if (!Utilities.ATLEAST_LOLLIPOP) {
- return;
- }
+ public static void processAllUsers(List<UserHandle> users, Context context) {
UserManagerCompat userManager = UserManagerCompat.getInstance(context);
HashSet<String> validKeys = new HashSet<String>();
- for (UserHandleCompat user : users) {
+ for (UserHandle user : users) {
addAllUserKeys(userManager.getSerialNumberForUser(user), validKeys);
}
@@ -237,10 +253,10 @@
*/
public static void markExistingUsersForNoFolderCreation(Context context) {
UserManagerCompat userManager = UserManagerCompat.getInstance(context);
- UserHandleCompat myUser = UserHandleCompat.myUserHandle();
+ UserHandle myUser = Process.myUserHandle();
SharedPreferences prefs = null;
- for (UserHandleCompat user : userManager.getUserProfiles()) {
+ for (UserHandle user : userManager.getUserProfiles()) {
if (myUser.equals(user)) {
continue;
}
diff --git a/src/com/android/launcher3/util/NoLocaleSqliteContext.java b/src/com/android/launcher3/util/NoLocaleSqliteContext.java
index 3b258e4..c8a5ffb 100644
--- a/src/com/android/launcher3/util/NoLocaleSqliteContext.java
+++ b/src/com/android/launcher3/util/NoLocaleSqliteContext.java
@@ -11,9 +11,6 @@
*/
public class NoLocaleSqliteContext extends ContextWrapper {
- // TODO: Use the flag defined in Context when the new SDK is available
- private static final int MODE_NO_LOCALIZED_COLLATORS = 0x0010;
-
public NoLocaleSqliteContext(Context context) {
super(context);
}
@@ -22,6 +19,6 @@
public SQLiteDatabase openOrCreateDatabase(
String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler) {
return super.openOrCreateDatabase(
- name, mode | MODE_NO_LOCALIZED_COLLATORS, factory, errorHandler);
+ name, mode | Context.MODE_NO_LOCALIZED_COLLATORS, factory, errorHandler);
}
}
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 3e15d05..e12b2d4 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -20,97 +20,87 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
+import android.net.Uri;
import android.os.Build;
+import android.os.UserHandle;
import android.text.TextUtils;
+import com.android.launcher3.AppInfo;
import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.LauncherAppsCompat;
-import java.util.ArrayList;
+import java.util.List;
/**
* Utility methods using package manager
*/
public class PackageManagerHelper {
- private static final int FLAG_SUSPENDED = 1<<30;
- private static final String LIVE_WALLPAPER_PICKER_PKG = "com.android.wallpaper.livepicker";
+ private final Context mContext;
+ private final PackageManager mPm;
+ private final LauncherAppsCompat mLauncherApps;
+
+ public PackageManagerHelper(Context context) {
+ mContext = context;
+ mPm = context.getPackageManager();
+ mLauncherApps = LauncherAppsCompat.getInstance(context);
+ }
/**
* Returns true if the app can possibly be on the SDCard. This is just a workaround and doesn't
* guarantee that the app is on SD card.
*/
- public static boolean isAppOnSdcard(PackageManager pm, String packageName) {
- return isAppEnabled(pm, packageName, PackageManager.GET_UNINSTALLED_PACKAGES);
+ public boolean isAppOnSdcard(String packageName, UserHandle user) {
+ ApplicationInfo info = mLauncherApps.getApplicationInfo(
+ packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, user);
+ return info != null && (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
}
- public static boolean isAppEnabled(PackageManager pm, String packageName) {
- return isAppEnabled(pm, packageName, 0);
+ /**
+ * Returns whether the target app is suspended for a given user as per
+ * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}.
+ */
+ public boolean isAppSuspended(String packageName, UserHandle user) {
+ ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, 0, user);
+ return info != null && isAppSuspended(info);
}
- public static boolean isAppEnabled(PackageManager pm, String packageName, int flags) {
- try {
- ApplicationInfo info = pm.getApplicationInfo(packageName, flags);
- return info != null && info.enabled;
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
+ public boolean isSafeMode() {
+ return mContext.getPackageManager().isSafeMode();
}
- public static boolean isAppSuspended(PackageManager pm, String packageName) {
- try {
- ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
- return info != null && isAppSuspended(info);
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
+ public Intent getAppLaunchIntent(String pkg, UserHandle user) {
+ List<LauncherActivityInfo> activities = mLauncherApps.getActivityList(pkg, user);
+ return activities.isEmpty() ? null :
+ AppInfo.makeLaunchIntent(activities.get(0));
}
+ /**
+ * Returns whether an application is suspended as per
+ * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}.
+ */
public static boolean isAppSuspended(ApplicationInfo info) {
// The value of FLAG_SUSPENDED was reused by a hidden constant
// ApplicationInfo.FLAG_PRIVILEGED prior to N, so only check for suspended flag on N
// or later.
- if (Utilities.isNycOrAbove()) {
- return (info.flags & FLAG_SUSPENDED) != 0;
+ if (Utilities.ATLEAST_NOUGAT) {
+ return (info.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
} else {
return false;
}
}
/**
- * Returns the package for a wallpaper picker system app giving preference to a app which
- * is not as image picker.
- */
- public static String getWallpaperPickerPackage(PackageManager pm) {
- ArrayList<String> excludePackages = new ArrayList<>();
- // Exclude packages which contain an image picker
- for (ResolveInfo info : pm.queryIntentActivities(
- new Intent(Intent.ACTION_GET_CONTENT).setType("image/*"), 0)) {
- excludePackages.add(info.activityInfo.packageName);
- }
- excludePackages.add(LIVE_WALLPAPER_PICKER_PKG);
-
- for (ResolveInfo info : pm.queryIntentActivities(
- new Intent(Intent.ACTION_SET_WALLPAPER), 0)) {
- if (!excludePackages.contains(info.activityInfo.packageName) &&
- (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- return info.activityInfo.packageName;
- }
- }
- return excludePackages.get(0);
- }
-
- /**
* Returns true if {@param srcPackage} has the permission required to start the activity from
* {@param intent}. If {@param srcPackage} is null, then the activity should not need
* any permissions
*/
- public static boolean hasPermissionForActivity(Context context, Intent intent,
- String srcPackage) {
- PackageManager pm = context.getPackageManager();
- ResolveInfo target = pm.resolveActivity(intent, 0);
+ public boolean hasPermissionForActivity(Intent intent, String srcPackage) {
+ ResolveInfo target = mPm.resolveActivity(intent, 0);
if (target == null) {
// Not a valid target
return false;
@@ -125,7 +115,7 @@
}
// Source does not have sufficient permissions.
- if(pm.checkPermission(target.activityInfo.permission, srcPackage) !=
+ if(mPm.checkPermission(target.activityInfo.permission, srcPackage) !=
PackageManager.PERMISSION_GRANTED) {
return false;
}
@@ -145,9 +135,18 @@
// app-op is only enabled for apps running in compatibility mode, simply block such apps.
try {
- return pm.getApplicationInfo(srcPackage, 0).targetSdkVersion >= Build.VERSION_CODES.M;
+ return mPm.getApplicationInfo(srcPackage, 0).targetSdkVersion >= Build.VERSION_CODES.M;
} catch (NameNotFoundException e) { }
return false;
}
+
+ public static Intent getMarketIntent(String packageName) {
+ return new Intent(Intent.ACTION_VIEW)
+ .setData(new Uri.Builder()
+ .scheme("market")
+ .authority("details")
+ .appendQueryParameter("id", packageName)
+ .build());
+ }
}
diff --git a/src/com/android/launcher3/util/PackageUserKey.java b/src/com/android/launcher3/util/PackageUserKey.java
new file mode 100644
index 0000000..1ce2822
--- /dev/null
+++ b/src/com/android/launcher3/util/PackageUserKey.java
@@ -0,0 +1,59 @@
+package com.android.launcher3.util;
+
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+
+import java.util.Arrays;
+
+/** Creates a hash key based on package name and user. */
+public class PackageUserKey {
+
+ public String mPackageName;
+ public UserHandle mUser;
+ private int mHashCode;
+
+ public static PackageUserKey fromItemInfo(ItemInfo info) {
+ return new PackageUserKey(info.getTargetComponent().getPackageName(), info.user);
+ }
+
+ public static PackageUserKey fromNotification(StatusBarNotification notification) {
+ return new PackageUserKey(notification.getPackageName(), notification.getUser());
+ }
+
+ public PackageUserKey(String packageName, UserHandle user) {
+ update(packageName, user);
+ }
+
+ private void update(String packageName, UserHandle user) {
+ mPackageName = packageName;
+ mUser = user;
+ mHashCode = Arrays.hashCode(new Object[] {packageName, user});
+ }
+
+ /**
+ * This should only be called to avoid new object creations in a loop.
+ * @return Whether this PackageUserKey was successfully updated - it shouldn't be used if not.
+ */
+ public boolean updateFromItemInfo(ItemInfo info) {
+ if (DeepShortcutManager.supportsShortcuts(info)) {
+ update(info.getTargetComponent().getPackageName(), info.user);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mHashCode;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof PackageUserKey)) return false;
+ PackageUserKey otherKey = (PackageUserKey) obj;
+ return mPackageName.equals(otherKey.mPackageName) && mUser.equals(otherKey.mUser);
+ }
+}
diff --git a/src/com/android/launcher3/util/PendingRequestArgs.java b/src/com/android/launcher3/util/PendingRequestArgs.java
index bade967..dabd40d 100644
--- a/src/com/android/launcher3/util/PendingRequestArgs.java
+++ b/src/com/android/launcher3/util/PendingRequestArgs.java
@@ -21,7 +21,7 @@
import android.os.Parcelable;
import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.WidgetAddFlowHandler;
/**
* Utility class to store information regarding a pending request made by launcher. This information
@@ -53,16 +53,11 @@
public PendingRequestArgs(Parcel parcel) {
readFromValues(ContentValues.CREATOR.createFromParcel(parcel));
+ user = parcel.readParcelable(null);
mArg1 = parcel.readInt();
mObjectType = parcel.readInt();
- if (parcel.readInt() != 0) {
- mObject = mObjectType == TYPE_INTENT
- ? Intent.CREATOR.createFromParcel(parcel)
- : new LauncherAppWidgetProviderInfo(parcel);
- } else {
- mObject = null;
- }
+ mObject = parcel.readParcelable(null);
}
@Override
@@ -73,21 +68,17 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
ContentValues itemValues = new ContentValues();
- writeToValues(itemValues);
+ writeToValues(new ContentWriter(itemValues, null));
itemValues.writeToParcel(dest, flags);
+ dest.writeParcelable(user, flags);
dest.writeInt(mArg1);
dest.writeInt(mObjectType);
- if (mObject != null) {
- dest.writeInt(1);
- mObject.writeToParcel(dest, flags);
- } else {
- dest.writeInt(0);
- }
+ dest.writeParcelable(mObject, flags);
}
- public LauncherAppWidgetProviderInfo getWidgetProvider() {
- return mObjectType == TYPE_APP_WIDGET ? (LauncherAppWidgetProviderInfo) mObject : null;
+ public WidgetAddFlowHandler getWidgetHandler() {
+ return mObjectType == TYPE_APP_WIDGET ? (WidgetAddFlowHandler) mObject : null;
}
public int getWidgetId() {
@@ -103,8 +94,9 @@
}
public static PendingRequestArgs forWidgetInfo(
- int appWidgetId, LauncherAppWidgetProviderInfo widgetInfo, ItemInfo info) {
- PendingRequestArgs args = new PendingRequestArgs(appWidgetId, TYPE_APP_WIDGET, widgetInfo);
+ int appWidgetId, WidgetAddFlowHandler widgetHandler, ItemInfo info) {
+ PendingRequestArgs args =
+ new PendingRequestArgs(appWidgetId, TYPE_APP_WIDGET, widgetHandler);
args.copyFrom(info);
return args;
}
diff --git a/src/com/android/launcher3/util/PillRevealOutlineProvider.java b/src/com/android/launcher3/util/PillRevealOutlineProvider.java
index 3f1e11a..a57d69f 100644
--- a/src/com/android/launcher3/util/PillRevealOutlineProvider.java
+++ b/src/com/android/launcher3/util/PillRevealOutlineProvider.java
@@ -16,12 +16,9 @@
package com.android.launcher3.util;
-import android.annotation.TargetApi;
import android.graphics.Rect;
-import android.os.Build;
import android.view.ViewOutlineProvider;
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
/**
* A {@link ViewOutlineProvider} that animates a reveal in a "pill" shape.
* A pill is simply a round rect, but we assume the width is greater than
@@ -31,6 +28,7 @@
private int mCenterX;
private int mCenterY;
+ private float mFinalRadius;
protected Rect mPillRect;
/**
@@ -39,10 +37,14 @@
* @param pillRect round rect that represents the final pill shape
*/
public PillRevealOutlineProvider(int x, int y, Rect pillRect) {
+ this(x, y, pillRect, pillRect.height() / 2f);
+ }
+
+ public PillRevealOutlineProvider(int x, int y, Rect pillRect, float radius) {
mCenterX = x;
mCenterY = y;
mPillRect = pillRect;
- mOutlineRadius = pillRect.height() / 2f;
+ mOutlineRadius = mFinalRadius = radius;
}
@Override
@@ -61,6 +63,6 @@
mOutline.top = Math.max(mPillRect.top, mCenterY - currentSize);
mOutline.right = Math.min(mPillRect.right, mCenterX + currentSize);
mOutline.bottom = Math.min(mPillRect.bottom, mCenterY + currentSize);
- mOutlineRadius = mOutline.height() / 2;
+ mOutlineRadius = Math.min(mFinalRadius, mOutline.height() / 2);
}
}
diff --git a/src/com/android/launcher3/util/PillWidthRevealOutlineProvider.java b/src/com/android/launcher3/util/PillWidthRevealOutlineProvider.java
deleted file mode 100644
index 89dda3b..0000000
--- a/src/com/android/launcher3/util/PillWidthRevealOutlineProvider.java
+++ /dev/null
@@ -1,41 +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.graphics.Rect;
-
-/**
- * Extension of {@link PillRevealOutlineProvider} which only changes the width of the pill.
- */
-public class PillWidthRevealOutlineProvider extends PillRevealOutlineProvider {
-
- private final int mStartLeft;
- private final int mStartRight;
-
- public PillWidthRevealOutlineProvider(Rect pillRect, int left, int right) {
- super(0, 0, pillRect);
- mOutline.set(pillRect);
- mStartLeft = left;
- mStartRight = right;
- }
-
- @Override
- public void setProgress(float progress) {
- mOutline.left = (int) (progress * mPillRect.left + (1 - progress) * mStartLeft);
- mOutline.right = (int) (progress * mPillRect.right + (1 - progress) * mStartRight);
- }
-}
diff --git a/src/com/android/launcher3/util/Provider.java b/src/com/android/launcher3/util/Provider.java
new file mode 100644
index 0000000..1cdd8d6
--- /dev/null
+++ b/src/com/android/launcher3/util/Provider.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+/**
+ * Utility class to allow lazy initialization of objects.
+ */
+public abstract class Provider<T> {
+
+ /**
+ * Initializes and returns the object. This may contain expensive operations not suitable
+ * to UI thread.
+ */
+ public abstract T get();
+
+ public static <T> Provider<T> of (final T value) {
+ return new Provider<T>() {
+ @Override
+ public T get() {
+ return value;
+ }
+ };
+ }
+}
diff --git a/src/com/android/launcher3/util/SQLiteCacheHelper.java b/src/com/android/launcher3/util/SQLiteCacheHelper.java
index d1cfe42..ef10f97 100644
--- a/src/com/android/launcher3/util/SQLiteCacheHelper.java
+++ b/src/com/android/launcher3/util/SQLiteCacheHelper.java
@@ -9,6 +9,9 @@
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.ProviderConfig;
+
/**
* An extension of {@link SQLiteOpenHelper} with utility methods for a single table cache DB.
* Any exception during write operations are ignored, and any version change causes a DB reset.
@@ -16,12 +19,18 @@
public abstract class SQLiteCacheHelper {
private static final String TAG = "SQLiteCacheHelper";
+ private static final boolean NO_ICON_CACHE = ProviderConfig.IS_DOGFOOD_BUILD &&
+ Utilities.isPropertyEnabled(LogConfig.MEMORY_ONLY_ICON_CACHE);
+
private final String mTableName;
private final MySQLiteOpenHelper mOpenHelper;
private boolean mIgnoreWrites;
public SQLiteCacheHelper(Context context, String name, int version, String tableName) {
+ if (NO_ICON_CACHE) {
+ name = null;
+ }
mTableName = tableName;
mOpenHelper = new MySQLiteOpenHelper(context, name, version);
@@ -74,6 +83,10 @@
mTableName, columns, selection, selectionArgs, null, null, null);
}
+ public void clear() {
+ mOpenHelper.clearDB(mOpenHelper.getWritableDatabase());
+ }
+
protected abstract void onCreateTable(SQLiteDatabase db);
/**
diff --git a/src/com/android/launcher3/util/StringFilter.java b/src/com/android/launcher3/util/StringFilter.java
deleted file mode 100644
index f539ad1..0000000
--- a/src/com/android/launcher3/util/StringFilter.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.android.launcher3.util;
-
-import java.util.Set;
-
-/**
- * Abstract class to filter a set of strings.
- */
-public abstract class StringFilter {
-
- private StringFilter() { }
-
- public abstract boolean matches(String str);
-
- public static StringFilter matchesAll() {
- return new StringFilter() {
- @Override
- public boolean matches(String str) {
- return true;
- }
- };
- }
-
- public static StringFilter of(final Set<String> validEntries) {
- return new StringFilter() {
- @Override
- public boolean matches(String str) {
- return validEntries.contains(str);
- }
- };
- }
-}
diff --git a/src/com/android/launcher3/util/Themes.java b/src/com/android/launcher3/util/Themes.java
new file mode 100644
index 0000000..d863339
--- /dev/null
+++ b/src/com/android/launcher3/util/Themes.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.view.ContextThemeWrapper;
+
+/**
+ * Various utility methods associated with theming.
+ */
+public class Themes {
+
+ public static int getColorAccent(Context context) {
+ return getAttrColor(context, android.R.attr.colorAccent);
+ }
+
+ public static int getColorPrimary(Context context, int theme) {
+ return getAttrColor(new ContextThemeWrapper(context, theme), android.R.attr.colorPrimary);
+ }
+
+ public static int getAttrColor(Context context, int attr) {
+ TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+ int colorAccent = ta.getColor(0, 0);
+ ta.recycle();
+ return colorAccent;
+ }
+
+ /**
+ * Returns the alpha corresponding to the theme attribute {@param attr}, in the range [0, 255].
+ */
+ public static int getAlpha(Context context, int attr) {
+ TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+ float alpha = ta.getFloat(0, 0);
+ ta.recycle();
+ return (int) (255 * alpha + 0.5f);
+ }
+
+ /**
+ * Scales a color matrix such that, when applied to color R G B A, it produces R' G' B' A' where
+ * R' = r * R
+ * G' = g * G
+ * B' = b * B
+ * A' = a * A
+ *
+ * The matrix will, for instance, turn white into r g b a, and black will remain black.
+ *
+ * @param color The color r g b a
+ * @param target The ColorMatrix to scale
+ */
+ public static void setColorScaleOnMatrix(int color, ColorMatrix target) {
+ target.setScale(Color.red(color) / 255f, Color.green(color) / 255f,
+ Color.blue(color) / 255f, Color.alpha(color) / 255f);
+ }
+}
diff --git a/src/com/android/launcher3/util/TouchController.java b/src/com/android/launcher3/util/TouchController.java
index d1409c8..3cca215 100644
--- a/src/com/android/launcher3/util/TouchController.java
+++ b/src/com/android/launcher3/util/TouchController.java
@@ -1,8 +1,32 @@
+/*
+ * 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.view.MotionEvent;
public interface TouchController {
- boolean onTouchEvent(MotionEvent ev);
- boolean onInterceptTouchEvent(MotionEvent ev);
+
+ /**
+ * Called when the draglayer receives touch event.
+ */
+ boolean onControllerTouchEvent(MotionEvent ev);
+
+ /**
+ * Called when the draglayer receives a intercept touch event.
+ */
+ boolean onControllerInterceptTouchEvent(MotionEvent ev);
}
diff --git a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
index 486b18e..e8f13a1 100644
--- a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
@@ -15,11 +15,9 @@
*/
package com.android.launcher3.widget;
-import android.content.ComponentName;
-import android.content.pm.ActivityInfo;
-
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.PendingAddItemInfo;
+import com.android.launcher3.compat.ShortcutConfigActivityInfo;
/**
* Meta data used for late binding of the short cuts.
@@ -28,11 +26,12 @@
*/
public class PendingAddShortcutInfo extends PendingAddItemInfo {
- ActivityInfo activityInfo;
+ public ShortcutConfigActivityInfo activityInfo;
- public PendingAddShortcutInfo(ActivityInfo activityInfo) {
+ public PendingAddShortcutInfo(ShortcutConfigActivityInfo activityInfo) {
this.activityInfo = activityInfo;
- componentName = new ComponentName(activityInfo.packageName, activityInfo.name);
- itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+ componentName = activityInfo.getComponent();
+ user = activityInfo.getUser();
+ itemType = activityInfo.getItemType();
}
}
diff --git a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
index f800ac4..ad05ce9 100644
--- a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
@@ -16,15 +16,11 @@
package com.android.launcher3.widget;
import android.appwidget.AppWidgetHostView;
-import android.content.Context;
import android.os.Bundle;
-import android.os.Parcelable;
-import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.PendingAddItemInfo;
-import com.android.launcher3.compat.AppWidgetManagerCompat;
/**
* Meta data used for late binding of {@link LauncherAppWidgetProviderInfo}.
@@ -38,14 +34,14 @@
public AppWidgetHostView boundWidget;
public Bundle bindOptions = null;
- public PendingAddWidgetInfo(Context context, LauncherAppWidgetProviderInfo i) {
+ public PendingAddWidgetInfo(LauncherAppWidgetProviderInfo i) {
if (i.isCustomWidget) {
itemType = LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
} else {
itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
}
this.info = i;
- user = AppWidgetManagerCompat.getInstance(context).getUser(i);
+ user = i.getUser();
componentName = i.provider;
previewImage = i.previewImage;
icon = i.icon;
@@ -56,7 +52,7 @@
minSpanY = i.minSpanY;
}
- public boolean isCustomWidget() {
- return itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
+ public WidgetAddFlowHandler getHandler() {
+ return new WidgetAddFlowHandler(info);
}
}
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
new file mode 100644
index 0000000..6628971
--- /dev/null
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -0,0 +1,208 @@
+/*
+ * 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.widget;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+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;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.DragSource;
+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;
+
+/**
+ * Extension of {@link DragPreviewProvider} with logic specific to pending widgets/shortcuts
+ * dragged from the widget tray.
+ */
+public class PendingItemDragHelper extends DragPreviewProvider {
+
+ private static final float MAX_WIDGET_SCALE = 1.25f;
+
+ private final PendingAddItemInfo mAddInfo;
+
+ private Bitmap mPreviewBitmap;
+ private RemoteViews mPreview;
+
+ public PendingItemDragHelper(View view) {
+ super(view);
+ mAddInfo = (PendingAddItemInfo) view.getTag();
+ }
+
+ public void setPreview(RemoteViews preview) {
+ mPreview = preview;
+ }
+
+ /**
+ * Starts the drag for the pending item associated with the view.
+ *
+ * @param previewBounds The bounds where the image was displayed,
+ * {@link WidgetImageView#getBitmapBounds()}
+ * @param previewBitmapWidth The actual width of the bitmap displayed in the view.
+ * @param previewViewWidth The width of {@link WidgetImageView} displaying the preview
+ * @param screenPos Position of {@link WidgetImageView} on the screen
+ */
+ public void startDrag(Rect previewBounds, int previewBitmapWidth, int previewViewWidth,
+ Point screenPos, DragSource source, DragOptions options) {
+ final Launcher launcher = Launcher.getLauncher(mView.getContext());
+ LauncherAppState app = LauncherAppState.getInstance(launcher);
+
+ Bitmap preview = null;
+ final float scale;
+ final Point dragOffset;
+ final Rect dragRegion;
+
+
+ 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[] previewSizeBeforeScale = new int[1];
+
+ if (mPreview != null) {
+ preview = LivePreviewWidgetCell.generateFromRemoteViews(launcher, mPreview,
+ createWidgetInfo.info, maxWidth, previewSizeBeforeScale);
+ }
+ if (preview == null) {
+ preview = app.getWidgetCache().generateWidgetPreview(
+ launcher, createWidgetInfo.info, maxWidth, null, previewSizeBeforeScale);
+ }
+
+ if (previewSizeBeforeScale[0] < previewBitmapWidth) {
+ // The icon has extra padding around it.
+ int padding = (previewBitmapWidth - previewSizeBeforeScale[0]) / 2;
+ if (previewBitmapWidth > previewViewWidth) {
+ padding = padding * previewViewWidth / previewBitmapWidth;
+ }
+
+ previewBounds.left += padding;
+ previewBounds.right -= padding;
+ }
+ scale = previewBounds.width() / (float) preview.getWidth();
+ launcher.getDragController().addDragListener(new WidgetHostViewLoader(launcher, mView));
+
+ dragOffset = null;
+ dragRegion = null;
+ } else {
+ PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) mAddInfo;
+ Drawable icon = createShortcutInfo.activityInfo.getFullResIcon(app.getIconCache());
+ preview = LauncherIcons.createScaledBitmapWithoutShadow(icon, launcher, Build.VERSION_CODES.O);
+ 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;
+
+ int padding = launcher.getResources()
+ .getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
+ previewBounds.left += padding;
+ previewBounds.top += padding;
+
+ dragRegion = new Rect();
+ dragRegion.left = (size[0] - iconSize) / 2;
+ dragRegion.right = dragRegion.left + iconSize;
+ dragRegion.top = (size[1] - iconSize - dp.iconTextSizePx - dp.iconDrawablePaddingPx) / 2;
+ dragRegion.bottom = dragRegion.top + iconSize;
+ }
+
+ // Since we are not going through the workspace for starting the drag, set drag related
+ // information on the workspace before starting the drag.
+ launcher.getWorkspace().prepareDragWithProvider(this);
+
+ int dragLayerX = screenPos.x + previewBounds.left
+ + (int) ((scale * preview.getWidth() - preview.getWidth()) / 2);
+ 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;
+ }
+
+ Workspace workspace = Launcher.getLauncher(mView.getContext()).getWorkspace();
+ int[] size = workspace.estimateItemSize(mAddInfo, false, false);
+
+ int w = size[0];
+ int h = size[1];
+ final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ALPHA_8);
+ canvas.setBitmap(b);
+
+ 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());
+ 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);
+
+ return b;
+ }
+}
diff --git a/src/com/android/launcher3/widget/PendingItemPreviewProvider.java b/src/com/android/launcher3/widget/PendingItemPreviewProvider.java
deleted file mode 100644
index eaa0bb3..0000000
--- a/src/com/android/launcher3/widget/PendingItemPreviewProvider.java
+++ /dev/null
@@ -1,76 +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.widget;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.view.View;
-
-import com.android.launcher3.HolographicOutlineHelper;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.PendingAddItemInfo;
-import com.android.launcher3.Workspace;
-import com.android.launcher3.graphics.DragPreviewProvider;
-
-/**
- * Extension of {@link DragPreviewProvider} with logic specific to pending widgets/shortcuts
- * dragged from the widget tray.
- */
-public class PendingItemPreviewProvider extends DragPreviewProvider {
-
- private final PendingAddItemInfo mAddInfo;
- private final Bitmap mPreviewBitmap;
-
- public PendingItemPreviewProvider(View view, PendingAddItemInfo addInfo, Bitmap preview) {
- super(view);
- mAddInfo = addInfo;
- mPreviewBitmap = preview;
- }
-
- @Override
- public Bitmap createDragOutline(Canvas canvas) {
- Workspace workspace = Launcher.getLauncher(mView.getContext()).getWorkspace();
- int[] size = workspace.estimateItemSize(mAddInfo, false);
-
- int w = size[0];
- int h = size[1];
- final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ALPHA_8);
- canvas.setBitmap(b);
-
- Rect src = new Rect(0, 0, mPreviewBitmap.getWidth(), mPreviewBitmap.getHeight());
- float scaleFactor = Math.min((w - DRAG_BITMAP_PADDING) / (float) mPreviewBitmap.getWidth(),
- (h - DRAG_BITMAP_PADDING) / (float) mPreviewBitmap.getHeight());
- int scaledWidth = (int) (scaleFactor * mPreviewBitmap.getWidth());
- int scaledHeight = (int) (scaleFactor * mPreviewBitmap.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);
-
- // Don't clip alpha values for the drag outline if we're using the default widget preview
- boolean clipAlpha = !(mAddInfo instanceof PendingAddWidgetInfo &&
- (((PendingAddWidgetInfo) mAddInfo).previewImage == 0));
- HolographicOutlineHelper.obtain(mView.getContext())
- .applyExpensiveOutlineWithBlur(b, canvas, clipAlpha);
- canvas.setBitmap(null);
-
- return b;
- }
-}
diff --git a/src/com/android/launcher3/widget/WidgetAddFlowHandler.java b/src/com/android/launcher3/widget/WidgetAddFlowHandler.java
new file mode 100644
index 0000000..629f30c
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetAddFlowHandler.java
@@ -0,0 +1,112 @@
+/*
+ * 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.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.compat.AppWidgetManagerCompat;
+import com.android.launcher3.util.PendingRequestArgs;
+
+/**
+ * Utility class to handle app widget add flow.
+ */
+public class WidgetAddFlowHandler implements Parcelable {
+
+ private final AppWidgetProviderInfo mProviderInfo;
+
+ public WidgetAddFlowHandler(AppWidgetProviderInfo providerInfo) {
+ mProviderInfo = providerInfo;
+ }
+
+ protected WidgetAddFlowHandler(Parcel parcel) {
+ mProviderInfo = AppWidgetProviderInfo.CREATOR.createFromParcel(parcel);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int i) {
+ mProviderInfo.writeToParcel(parcel, i);
+ }
+
+ public void startBindFlow(Launcher launcher, int appWidgetId, ItemInfo info, int requestCode) {
+ launcher.setWaitingForResult(PendingRequestArgs.forWidgetInfo(appWidgetId, this, info));
+
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, mProviderInfo.provider);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE,
+ mProviderInfo.getProfile());
+ // TODO: we need to make sure that this accounts for the options bundle.
+ // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
+ launcher.startActivityForResult(intent, requestCode);
+ }
+
+ /**
+ * @see #startConfigActivity(Launcher, int, ItemInfo, int)
+ */
+ public boolean startConfigActivity(Launcher launcher, LauncherAppWidgetInfo info,
+ int requestCode) {
+ return startConfigActivity(launcher, info.appWidgetId, info, requestCode);
+ }
+
+ /**
+ * Starts the widget configuration flow if needed.
+ * @return true if the configuration flow was started, false otherwise.
+ */
+ public boolean startConfigActivity(Launcher launcher, int appWidgetId, ItemInfo info,
+ int requestCode) {
+ if (!needsConfigure()) {
+ return false;
+ }
+ launcher.setWaitingForResult(PendingRequestArgs.forWidgetInfo(appWidgetId, this, info));
+
+ AppWidgetManagerCompat.getInstance(launcher).startConfigActivity(
+ mProviderInfo, appWidgetId, launcher, launcher.getAppWidgetHost(), requestCode);
+ return true;
+ }
+
+ public boolean needsConfigure() {
+ return mProviderInfo.configure != null;
+ }
+
+ public LauncherAppWidgetProviderInfo getProviderInfo(Context context) {
+ return LauncherAppWidgetProviderInfo.fromProviderInfo(context, mProviderInfo);
+ }
+
+ public static final Parcelable.Creator<WidgetAddFlowHandler> CREATOR =
+ new Parcelable.Creator<WidgetAddFlowHandler>() {
+ public WidgetAddFlowHandler createFromParcel(Parcel source) {
+ return new WidgetAddFlowHandler(source);
+ }
+
+ public WidgetAddFlowHandler[] newArray(int size) {
+ return new WidgetAddFlowHandler[size];
+ }
+ };
+}
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 293585d..40dbd52 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -17,26 +17,25 @@
package com.android.launcher3.widget;
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.os.CancellationSignal;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnLayoutChangeListener;
+import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.widget.LinearLayout;
import android.widget.TextView;
+import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.SimpleOnStylusPressListener;
import com.android.launcher3.R;
+import com.android.launcher3.SimpleOnStylusPressListener;
import com.android.launcher3.StylusEventHelper;
import com.android.launcher3.WidgetPreviewLoader;
-import com.android.launcher3.WidgetPreviewLoader.PreviewLoadRequest;
+import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.model.WidgetItem;
/**
@@ -61,20 +60,22 @@
/** Widget preview width is calculated by multiplying this factor to the widget cell width. */
private static final float PREVIEW_SCALE = 0.8f;
- private int mPresetPreviewSize;
- int cellSize;
+ protected int mPresetPreviewSize;
+ private int mCellSize;
private WidgetImageView mWidgetImage;
private TextView mWidgetName;
private TextView mWidgetDims;
- private WidgetItem mItem;
+ protected WidgetItem mItem;
private WidgetPreviewLoader mWidgetPreviewLoader;
- private PreviewLoadRequest mActiveRequest;
private StylusEventHelper mStylusEventHelper;
- private final Launcher mLauncher;
+ protected CancellationSignal mActiveRequest;
+ private boolean mAnimatePreview = true;
+
+ protected final BaseActivity mActivity;
public WidgetCell(Context context) {
this(context, null);
@@ -87,20 +88,19 @@
public WidgetCell(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- final Resources r = context.getResources();
- mLauncher = Launcher.getLauncher(context);
+ mActivity = BaseActivity.fromContext(context);
mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
setContainerWidth();
setWillNotDraw(false);
setClipToPadding(false);
- setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
+ setAccessibilityDelegate(mActivity.getAccessibilityDelegate());
}
private void setContainerWidth() {
- DeviceProfile profile = mLauncher.getDeviceProfile();
- cellSize = (int) (profile.cellWidthPx * WIDTH_SCALE);
- mPresetPreviewSize = (int) (cellSize * PREVIEW_SCALE);
+ DeviceProfile profile = mActivity.getDeviceProfile();
+ mCellSize = (int) (profile.cellWidthPx * WIDTH_SCALE);
+ mPresetPreviewSize = (int) (mCellSize * PREVIEW_SCALE);
}
@Override
@@ -120,12 +120,12 @@
Log.d(TAG, "reset called on:" + mWidgetName.getText());
}
mWidgetImage.animate().cancel();
- mWidgetImage.setBitmap(null);
+ mWidgetImage.setBitmap(null, null);
mWidgetName.setText(null);
mWidgetDims.setText(null);
if (mActiveRequest != null) {
- mActiveRequest.cleanup();
+ mActiveRequest.cancel();
mActiveRequest = null;
}
}
@@ -142,37 +142,46 @@
if (item.activityInfo != null) {
setTag(new PendingAddShortcutInfo(item.activityInfo));
} else {
- setTag(new PendingAddWidgetInfo(mLauncher, item.widgetInfo));
+ setTag(new PendingAddWidgetInfo(item.widgetInfo));
}
}
- public int[] getPreviewSize() {
- int[] maxSize = new int[2];
+ public WidgetImageView getWidgetView() {
+ return mWidgetImage;
+ }
- maxSize[0] = mPresetPreviewSize;
- maxSize[1] = mPresetPreviewSize;
- return maxSize;
+ public void setAnimatePreview(boolean shouldAnimate) {
+ mAnimatePreview = shouldAnimate;
}
public void applyPreview(Bitmap bitmap) {
+ applyPreview(bitmap, true);
+ }
+
+ public void applyPreview(Bitmap bitmap, boolean animate) {
if (bitmap != null) {
- mWidgetImage.setBitmap(bitmap);
- mWidgetImage.setAlpha(0f);
- ViewPropertyAnimator anim = mWidgetImage.animate();
- anim.alpha(1.0f).setDuration(FADE_IN_DURATION_MS);
+ mWidgetImage.setBitmap(bitmap,
+ DrawableFactory.get(getContext()).getBadgeForUser(mItem.user, getContext()));
+ if (mAnimatePreview) {
+ mWidgetImage.setAlpha(0f);
+ ViewPropertyAnimator anim = mWidgetImage.animate();
+ anim.alpha(1.0f).setDuration(FADE_IN_DURATION_MS);
+ } else {
+ mWidgetImage.setAlpha(1f);
+ }
}
}
public void ensurePreview() {
+ ensurePreview(true);
+ }
+
+ public void ensurePreview(boolean animate) {
if (mActiveRequest != null) {
return;
}
- int[] size = getPreviewSize();
- if (DEBUG) {
- Log.d(TAG, String.format("[tag=%s] ensurePreview (%d, %d):",
- getTagToString(), size[0], size[1]));
- }
- mActiveRequest = mWidgetPreviewLoader.getPreview(mItem, size[0], size[1], this);
+ mActiveRequest = mWidgetPreviewLoader.getPreview(
+ mItem, mPresetPreviewSize, mPresetPreviewSize, this, animate);
}
@Override
@@ -182,14 +191,6 @@
ensurePreview();
}
- public int getActualItemWidth() {
- ItemInfo info = (ItemInfo) getTag();
- int[] size = getPreviewSize();
- int cellWidth = mLauncher.getDeviceProfile().cellWidthPx;
-
- return Math.min(size[0], info.spanX * cellWidth);
- }
-
@Override
public boolean onTouchEvent(MotionEvent ev) {
boolean handled = super.onTouchEvent(ev);
@@ -211,6 +212,12 @@
}
@Override
+ public void setLayoutParams(ViewGroup.LayoutParams params) {
+ params.width = params.height = mCellSize;
+ super.setLayoutParams(params);
+ }
+
+ @Override
public CharSequence getAccessibilityClassName() {
return WidgetCell.class.getName();
}
diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
index 049871f..5eeea44 100644
--- a/src/com/android/launcher3/widget/WidgetHostViewLoader.java
+++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
@@ -10,12 +10,9 @@
import android.view.View;
import com.android.launcher3.AppWidgetResizeFrame;
-import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.Utilities;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
@@ -49,7 +46,9 @@
}
@Override
- public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { }
+ public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+ preloadWidget();
+ }
@Override
public void onDragEnd() {
@@ -83,7 +82,7 @@
/**
* Start preloading the widget.
*/
- public boolean preloadWidget() {
+ private boolean preloadWidget() {
final LauncherAppWidgetProviderInfo pInfo = mInfo.info;
if (pInfo.isCustomWidget) {
@@ -92,7 +91,7 @@
final Bundle options = getDefaultOptionsForWidget(mLauncher, mInfo);
// If there is a configuration activity, do not follow thru bound and inflate.
- if (pInfo.configure != null) {
+ if (mInfo.getHandler().needsConfigure()) {
mInfo.bindOptions = options;
return false;
}
@@ -130,7 +129,7 @@
mWidgetLoadingId = -1;
hostView.setVisibility(View.INVISIBLE);
- int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(mInfo, false);
+ int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(mInfo, false, true);
// 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],
@@ -154,27 +153,24 @@
}
public static Bundle getDefaultOptionsForWidget(Context context, PendingAddWidgetInfo info) {
- Bundle options = null;
- if (Utilities.ATLEAST_JB_MR1) {
- Rect rect = new Rect();
- AppWidgetResizeFrame.getWidgetSizeRanges(context, info.spanX, info.spanY, rect);
- Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context,
- info.componentName, null);
+ Rect rect = new Rect();
+ AppWidgetResizeFrame.getWidgetSizeRanges(context, info.spanX, info.spanY, rect);
+ Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context,
+ info.componentName, null);
- float density = context.getResources().getDisplayMetrics().density;
- int xPaddingDips = (int) ((padding.left + padding.right) / density);
- int yPaddingDips = (int) ((padding.top + padding.bottom) / density);
+ float density = context.getResources().getDisplayMetrics().density;
+ int xPaddingDips = (int) ((padding.left + padding.right) / density);
+ int yPaddingDips = (int) ((padding.top + padding.bottom) / density);
- options = new Bundle();
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
- rect.left - xPaddingDips);
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
- rect.top - yPaddingDips);
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
- rect.right - xPaddingDips);
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
- rect.bottom - yPaddingDips);
- }
+ Bundle options = new Bundle();
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
+ rect.left - xPaddingDips);
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
+ rect.top - yPaddingDips);
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
+ rect.right - xPaddingDips);
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
+ rect.bottom - yPaddingDips);
return options;
}
}
diff --git a/src/com/android/launcher3/widget/WidgetImageView.java b/src/com/android/launcher3/widget/WidgetImageView.java
index b0fbe1e..df2bcff 100644
--- a/src/com/android/launcher3/widget/WidgetImageView.java
+++ b/src/com/android/launcher3/widget/WidgetImageView.java
@@ -22,9 +22,13 @@
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+
/**
* View that draws a bitmap horizontally centered. If the image width is greater than the view
* width, the image is scaled down appropriately.
@@ -33,22 +37,29 @@
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
private final RectF mDstRectF = new RectF();
+ private final int mBadgeMargin;
+
private Bitmap mBitmap;
+ private Drawable mBadge;
public WidgetImageView(Context context) {
- super(context);
+ this(context, null);
}
public WidgetImageView(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, 0);
}
public WidgetImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+
+ mBadgeMargin = context.getResources()
+ .getDimensionPixelSize(R.dimen.profile_badge_margin);
}
- public void setBitmap(Bitmap bitmap) {
+ public void setBitmap(Bitmap bitmap, Drawable badge) {
mBitmap = bitmap;
+ mBadge = badge;
invalidate();
}
@@ -61,6 +72,11 @@
if (mBitmap != null) {
updateDstRectF();
canvas.drawBitmap(mBitmap, null, mDstRectF, mPaint);
+
+ // Only draw the badge if a preview was drawn.
+ if (mBadge != null) {
+ mBadge.draw(canvas);
+ }
}
}
@@ -73,15 +89,34 @@
}
private void updateDstRectF() {
- if (mBitmap.getWidth() > getWidth()) {
- float scale = ((float) getWidth()) / mBitmap.getWidth();
- mDstRectF.set(0, 0, getWidth(), scale * mBitmap.getHeight());
+ float myWidth = getWidth();
+ float myHeight = getHeight();
+ float bitmapWidth = mBitmap.getWidth();
+
+ final float scale = bitmapWidth > myWidth ? myWidth / bitmapWidth : 1;
+ float scaledWidth = bitmapWidth * scale;
+ float scaledHeight = mBitmap.getHeight() * scale;
+
+ mDstRectF.left = (myWidth - scaledWidth) / 2;
+ mDstRectF.right = (myWidth + scaledWidth) / 2;
+
+ if (scaledHeight > myHeight) {
+ mDstRectF.top = 0;
+ mDstRectF.bottom = scaledHeight;
} else {
- mDstRectF.set(
- (getWidth() - mBitmap.getWidth()) * 0.5f,
- 0,
- (getWidth() + mBitmap.getWidth()) * 0.5f,
- mBitmap.getHeight());
+ mDstRectF.top = (myHeight - scaledHeight) / 2;
+ mDstRectF.bottom = (myHeight + scaledHeight) / 2;
+ }
+
+ if (mBadge != null) {
+ Rect bounds = mBadge.getBounds();
+ int left = Utilities.boundToRange(
+ (int) (mDstRectF.right + mBadgeMargin - bounds.width()),
+ mBadgeMargin, getWidth() - bounds.width());
+ int top = Utilities.boundToRange(
+ (int) (mDstRectF.bottom + mBadgeMargin - bounds.height()),
+ mBadgeMargin, getHeight() - bounds.height());
+ mBadge.setBounds(left, top, bounds.width() + left, bounds.height() + top);
}
}
diff --git a/src/com/android/launcher3/widget/WidgetItemComparator.java b/src/com/android/launcher3/widget/WidgetItemComparator.java
new file mode 100644
index 0000000..ee23cfb
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetItemComparator.java
@@ -0,0 +1,57 @@
+/*
+ * 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.widget;
+
+import android.os.Process;
+import android.os.UserHandle;
+
+import com.android.launcher3.model.WidgetItem;
+
+import java.text.Collator;
+import java.util.Comparator;
+
+/**
+ * Comparator for sorting WidgetItem based on their user, title and size.
+ */
+public class WidgetItemComparator implements Comparator<WidgetItem> {
+
+ private final UserHandle mMyUserHandle = Process.myUserHandle();
+ private final Collator mCollator = Collator.getInstance();
+
+ @Override
+ public int compare(WidgetItem a, WidgetItem b) {
+ // Independent of how the labels compare, if only one of the two widget info belongs to
+ // work profile, put that one in the back.
+ boolean thisWorkProfile = !mMyUserHandle.equals(a.user);
+ boolean otherWorkProfile = !mMyUserHandle.equals(b.user);
+ if (thisWorkProfile ^ otherWorkProfile) {
+ return thisWorkProfile ? 1 : -1;
+ }
+
+ int labelCompare = mCollator.compare(a.label, b.label);
+ if (labelCompare != 0) {
+ return labelCompare;
+ }
+
+ // If the label is same, put the smaller widget before the larger widget. If the area is
+ // also same, put the widget with smaller height before.
+ int thisArea = a.spanX * a.spanY;
+ int otherArea = b.spanX * b.spanY;
+ return thisArea == otherArea
+ ? Integer.compare(a.spanY, b.spanY)
+ : Integer.compare(thisArea, otherArea);
+ }
+}
diff --git a/src/com/android/launcher3/widget/WidgetListRowEntry.java b/src/com/android/launcher3/widget/WidgetListRowEntry.java
new file mode 100644
index 0000000..3e89eeb
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetListRowEntry.java
@@ -0,0 +1,44 @@
+/*
+ * 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.widget;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.model.WidgetItem;
+
+import java.util.ArrayList;
+
+/**
+ * Holder class to store all the information related to a single row in the widget list
+ */
+public class WidgetListRowEntry {
+
+ public final PackageItemInfo pkgItem;
+
+ public final ArrayList<WidgetItem> widgets;
+
+ /**
+ * Character that is used as a section name for the {@link ItemInfo#title}.
+ * (e.g., "G" will be stored if title is "Google")
+ */
+ public String titleSectionName;
+
+ public WidgetListRowEntry(PackageItemInfo pkgItem, ArrayList<WidgetItem> items) {
+ this.pkgItem = pkgItem;
+ this.widgets = items;
+ }
+
+}
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
new file mode 100644
index 0000000..5fe00c2
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -0,0 +1,313 @@
+/*
+ * 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.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.v4.view.animation.FastOutSlowInInterpolator;
+import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+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.allapps.VerticalPullDetector;
+import com.android.launcher3.anim.PropertyListBuilder;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.util.PackageUserKey;
+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,
+ VerticalPullDetector.Listener, View.OnClickListener, View.OnLongClickListener,
+ DragController.DragListener {
+
+ private int mTranslationYOpen;
+ private int mTranslationYClosed;
+ private float mTranslationYRange;
+
+ private Launcher mLauncher;
+ private ItemInfo mOriginalItemInfo;
+ private ObjectAnimator mOpenCloseAnimator;
+ private Interpolator mFastOutSlowInInterpolator;
+ private VerticalPullDetector.ScrollInterpolator mScrollInterpolator;
+ private Rect mInsets;
+ private boolean mWasNavBarLight;
+ private VerticalPullDetector mVerticalPullDetector;
+
+ public WidgetsBottomSheet(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public WidgetsBottomSheet(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(new ContextThemeWrapper(context, R.style.WidgetContainerTheme), attrs, defStyleAttr);
+ setWillNotDraw(false);
+ mLauncher = Launcher.getLauncher(context);
+ mOpenCloseAnimator = LauncherAnimUtils.ofPropertyValuesHolder(this);
+ mFastOutSlowInInterpolator = new FastOutSlowInInterpolator();
+ mScrollInterpolator = new VerticalPullDetector.ScrollInterpolator();
+ mInsets = new Rect();
+ mVerticalPullDetector = new VerticalPullDetector(context);
+ mVerticalPullDetector.setListener(this);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ mTranslationYOpen = 0;
+ mTranslationYClosed = getMeasuredHeight();
+ mTranslationYRange = mTranslationYClosed - mTranslationYOpen;
+ }
+
+ public void populateAndShow(ItemInfo itemInfo) {
+ mOriginalItemInfo = itemInfo;
+ ((TextView) findViewById(R.id.title)).setText(getContext().getString(
+ R.string.widgets_bottom_sheet_title, mOriginalItemInfo.title));
+
+ onWidgetsBound();
+
+ mWasNavBarLight = (mLauncher.getWindow().getDecorView().getSystemUiVisibility()
+ & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0;
+ mLauncher.getDragLayer().addView(this);
+ measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ setTranslationY(mTranslationYClosed);
+ mIsOpen = false;
+ open(true);
+ }
+
+ @Override
+ protected void onWidgetsBound() {
+ List<WidgetItem> widgets = mLauncher.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);
+
+ widgetCells.removeAllViews();
+
+ for (int i = 0; i < widgets.size(); i++) {
+ WidgetCell widget = addItemCell(widgetCells);
+ widget.applyFromCellItem(widgets.get(i), LauncherAppState.getInstance(mLauncher)
+ .getWidgetCache());
+ widget.ensurePreview();
+ widget.setVisibility(View.VISIBLE);
+ if (i < widgets.size() - 1) {
+ addDivider(widgetCells);
+ }
+ }
+
+ if (widgets.size() == 1) {
+ // If there is only one widget, we want to center it instead of left-align.
+ WidgetsBottomSheet.LayoutParams params = (WidgetsBottomSheet.LayoutParams)
+ widgetRow.getLayoutParams();
+ params.gravity = Gravity.CENTER_HORIZONTAL;
+ } else {
+ // Otherwise, add an empty view to the start as padding (but still scroll edge to edge).
+ View leftPaddingView = LayoutInflater.from(getContext()).inflate(
+ R.layout.widget_list_divider, widgetRow, false);
+ leftPaddingView.getLayoutParams().width = Utilities.pxFromDp(
+ 16, getResources().getDisplayMetrics());
+ widgetCells.addView(leftPaddingView, 0);
+ }
+ }
+
+ private void addDivider(ViewGroup parent) {
+ LayoutInflater.from(getContext()).inflate(R.layout.widget_list_divider, parent, true);
+ }
+
+ private WidgetCell addItemCell(ViewGroup parent) {
+ WidgetCell widget = (WidgetCell) LayoutInflater.from(getContext()).inflate(
+ R.layout.widget_cell, parent, false);
+
+ widget.setOnClickListener(this);
+ widget.setOnLongClickListener(this);
+ widget.setAnimatePreview(false);
+
+ parent.addView(widget);
+ 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;
+ setLightNavBar(true);
+ if (animate) {
+ mOpenCloseAnimator.setValues(new PropertyListBuilder()
+ .translationY(mTranslationYOpen).build());
+ mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mVerticalPullDetector.finishedScrolling();
+ }
+ });
+ mOpenCloseAnimator.setInterpolator(mFastOutSlowInInterpolator);
+ mOpenCloseAnimator.start();
+ } else {
+ setTranslationY(mTranslationYOpen);
+ }
+ }
+
+ @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) {
+ mIsOpen = false;
+ mVerticalPullDetector.finishedScrolling();
+ ((ViewGroup) getParent()).removeView(WidgetsBottomSheet.this);
+ setLightNavBar(mWasNavBarLight);
+ }
+ });
+ mOpenCloseAnimator.setInterpolator(mVerticalPullDetector.isIdleState()
+ ? mFastOutSlowInInterpolator : mScrollInterpolator);
+ mOpenCloseAnimator.start();
+ } else {
+ setTranslationY(mTranslationYClosed);
+ setLightNavBar(mWasNavBarLight);
+ mIsOpen = false;
+ }
+ }
+
+ private void setLightNavBar(boolean lightNavBar) {
+ mLauncher.activateLightSystemBars(lightNavBar, false /* statusBar */, true /* navBar */);
+ }
+
+ @Override
+ protected boolean isOfType(@FloatingViewType int type) {
+ 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.
+ int leftInset = insets.left - mInsets.left;
+ int rightInset = insets.right - mInsets.right;
+ int bottomInset = insets.bottom - mInsets.bottom;
+ mInsets.set(insets);
+ setPadding(getPaddingLeft() + leftInset, getPaddingTop(),
+ getPaddingRight() + rightInset, getPaddingBottom() + bottomInset);
+ }
+
+ /* VerticalPullDetector.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 onDragEnd(float velocity, boolean fling) {
+ if ((fling && velocity > 0) || getTranslationY() > (mTranslationYRange) / 2) {
+ mScrollInterpolator.setVelocityAtZero(velocity);
+ mOpenCloseAnimator.setDuration(mVerticalPullDetector.calculateDuration(velocity,
+ (mTranslationYClosed - getTranslationY()) / mTranslationYRange));
+ close(true);
+ } else {
+ mIsOpen = false;
+ mOpenCloseAnimator.setDuration(mVerticalPullDetector.calculateDuration(velocity,
+ (getTranslationY() - mTranslationYOpen) / mTranslationYRange));
+ open(true);
+ }
+ }
+
+ @Override
+ public boolean onControllerTouchEvent(MotionEvent ev) {
+ return mVerticalPullDetector.onTouchEvent(ev);
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ int directionsToDetectScroll = mVerticalPullDetector.isIdleState() ?
+ VerticalPullDetector.DIRECTION_DOWN : 0;
+ mVerticalPullDetector.setDetectableScrollConditions(
+ directionsToDetectScroll, false);
+ mVerticalPullDetector.onTouchEvent(ev);
+ return mVerticalPullDetector.isDraggingOrSettling();
+ }
+
+ /* DragListener */
+
+ @Override
+ public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+ // A widget or custom shortcut was dragged.
+ close(true);
+ }
+
+ @Override
+ public void onDragEnd() {
+ }
+}
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index 89c44c8..4e296bf 100644
--- a/src/com/android/launcher3/widget/WidgetsContainerView.java
+++ b/src/com/android/launcher3/widget/WidgetsContainerView.java
@@ -17,11 +17,8 @@
package com.android.launcher3.widget;
import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
+import android.graphics.Point;
import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView.State;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
@@ -29,27 +26,24 @@
import android.widget.Toast;
import com.android.launcher3.BaseContainerView;
-import com.android.launcher3.CellLayout;
import com.android.launcher3.DeleteDropTarget;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.folder.Folder;
-import com.android.launcher3.IconCache;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.WidgetPreviewLoader;
-import com.android.launcher3.Workspace;
-import com.android.launcher3.dragndrop.DragController;
-import com.android.launcher3.model.WidgetsModel;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
+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 com.android.launcher3.util.TransformingTouchDelegate;
+
+import java.util.List;
/**
* The widgets list view container.
@@ -61,22 +55,14 @@
/* Global instances that are used inside this container. */
@Thunk Launcher mLauncher;
- private DragController mDragController;
- private IconCache mIconCache;
-
- private final Rect mTmpBgPaddingRect = new Rect();
/* Recycler view related member variables */
private WidgetsRecyclerView mRecyclerView;
private WidgetsListAdapter mAdapter;
- private TransformingTouchDelegate mRecyclerViewTouchDelegate;
/* Touch handling related member variables. */
private Toast mWidgetInstructionToast;
- /* Rendering related. */
- private WidgetPreviewLoader mWidgetPreviewLoader;
-
public WidgetsContainerView(Context context) {
this(context, null);
}
@@ -88,23 +74,15 @@
public WidgetsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mLauncher = Launcher.getLauncher(context);
- mDragController = mLauncher.getDragController();
mAdapter = new WidgetsListAdapter(this, this, context);
- mIconCache = (LauncherAppState.getInstance()).getIconCache();
if (LOGD) {
Log.d(TAG, "WidgetsContainerView constructor");
}
}
@Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- getRevealView().getBackground().getPadding(mTmpBgPaddingRect);
- mRecyclerViewTouchDelegate.setBounds(
- mRecyclerView.getLeft() - mTmpBgPaddingRect.left,
- mRecyclerView.getTop() - mTmpBgPaddingRect.top,
- mRecyclerView.getRight() + mTmpBgPaddingRect.right,
- mRecyclerView.getBottom() + mTmpBgPaddingRect.bottom);
+ public View getTouchDelegateTargetView() {
+ return mRecyclerView;
}
@Override
@@ -113,13 +91,6 @@
mRecyclerView = (WidgetsRecyclerView) getContentView().findViewById(R.id.widgets_list_view);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
- mRecyclerViewTouchDelegate = new TransformingTouchDelegate(mRecyclerView);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- ((View) mRecyclerView.getParent()).setTouchDelegate(mRecyclerViewTouchDelegate);
}
//
@@ -141,6 +112,10 @@
|| 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();
@@ -155,27 +130,23 @@
@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("onLonglick [v=%s]", v));
+ Log.d(TAG, String.format("onLongClick [v=%s]", v));
}
// Return early if this is not initiated from a touch
if (!v.isInTouchMode()) return false;
- // When we have exited all apps or are in transition, disregard long clicks
- if (!mLauncher.isWidgetsViewVisible() ||
- mLauncher.getWorkspace().isSwitchingState()) return false;
+ // 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;
- boolean status = beginDragging(v);
- if (status && v.getTag() instanceof PendingAddWidgetInfo) {
- WidgetHostViewLoader hostLoader = new WidgetHostViewLoader(mLauncher, v);
- boolean preloadStatus = hostLoader.preloadWidget();
- if (LOGD) {
- Log.d(TAG, String.format("preloading widget [status=%s]", preloadStatus));
- }
- mLauncher.getDragController().addDragListener(hostLoader);
- }
- return status;
+ return beginDragging(v);
}
private boolean beginDragging(View v) {
@@ -199,7 +170,6 @@
private boolean beginDraggingWidget(WidgetCell v) {
// Get the widget preview as the drag representation
WidgetImageView image = (WidgetImageView) v.findViewById(R.id.widget_preview);
- PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag();
// If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
// we abort the drag.
@@ -207,53 +177,12 @@
return false;
}
- // Compose the drag image
- Bitmap preview;
- final float scale;
- final Rect bounds = image.getBitmapBounds();
+ int[] loc = new int[2];
+ mLauncher.getDragLayer().getLocationInDragLayer(image, loc);
- if (createItemInfo instanceof PendingAddWidgetInfo) {
- // This can happen in some weird cases involving multi-touch. We can't start dragging
- // the widget if this is null, so we break out.
-
- PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) createItemInfo;
- int[] size = mLauncher.getWorkspace().estimateItemSize(createWidgetInfo, true);
-
- Bitmap icon = image.getBitmap();
- float minScale = 1.25f;
- int maxWidth = Math.min((int) (icon.getWidth() * minScale), size[0]);
-
- int[] previewSizeBeforeScale = new int[1];
- preview = getWidgetPreviewLoader().generateWidgetPreview(mLauncher,
- createWidgetInfo.info, maxWidth, null, previewSizeBeforeScale);
-
- if (previewSizeBeforeScale[0] < icon.getWidth()) {
- // The icon has extra padding around it.
- int padding = (icon.getWidth() - previewSizeBeforeScale[0]) / 2;
- if (icon.getWidth() > image.getWidth()) {
- padding = padding * image.getWidth() / icon.getWidth();
- }
-
- bounds.left += padding;
- bounds.right -= padding;
- }
- scale = bounds.width() / (float) preview.getWidth();
- } else {
- PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) v.getTag();
- Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.activityInfo);
- preview = Utilities.createIconBitmap(icon, mLauncher);
- createItemInfo.spanX = createItemInfo.spanY = 1;
- scale = ((float) mLauncher.getDeviceProfile().iconSizePx) / preview.getWidth();
- }
-
- // Since we are not going through the workspace for starting the drag, set drag related
- // information on the workspace before starting the drag.
- mLauncher.getWorkspace().prepareDragWithProvider(
- new PendingItemPreviewProvider(v, createItemInfo, preview));
-
- // Start the drag
- mDragController.startDrag(image, preview, this, createItemInfo,
- bounds, scale, new DragOptions());
+ new PendingItemDragHelper(v).startDrag(
+ image.getBitmapBounds(), image.getBitmap().getWidth(), image.getWidth(),
+ new Point(loc[0], loc[1]), this, new DragOptions());
return true;
}
@@ -262,11 +191,6 @@
//
@Override
- public boolean supportsFlingToDelete() {
- return true;
- }
-
- @Override
public boolean supportsAppInfoDropTarget() {
return true;
}
@@ -286,14 +210,6 @@
}
@Override
- public void onFlingToDeleteCompleted() {
- // We just dismiss the drag when we fling, so cleanup here
- mLauncher.exitSpringLoadedDragModeDelayed(true,
- Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
- mLauncher.unlockScreenOrientation(false);
- }
-
- @Override
public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
boolean success) {
if (LOGD) {
@@ -308,23 +224,7 @@
}
mLauncher.unlockScreenOrientation(false);
- // Display an error message if the drag failed due to there not being enough space on the
- // target layout we were dropping on.
if (!success) {
- boolean showOutOfSpaceMessage = false;
- if (target instanceof Workspace) {
- int currentScreen = mLauncher.getCurrentWorkspaceScreen();
- Workspace workspace = (Workspace) target;
- CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
- ItemInfo itemInfo = d.dragInfo;
- if (layout != null) {
- showOutOfSpaceMessage =
- !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
- }
- }
- if (showOutOfSpaceMessage) {
- mLauncher.showOutOfSpaceMessage(false);
- }
d.deferDragViewCleanupPostAnimation = false;
}
}
@@ -332,9 +232,8 @@
/**
* Initialize the widget data model.
*/
- public void addWidgets(WidgetsModel model) {
- mRecyclerView.setWidgets(model);
- mAdapter.setWidgetsModel(model);
+ public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> model) {
+ mAdapter.setWidgets(model);
mAdapter.notifyDataSetChanged();
View loader = getContentView().findViewById(R.id.loader);
@@ -347,15 +246,12 @@
return mAdapter.getItemCount() == 0;
}
- private WidgetPreviewLoader getWidgetPreviewLoader() {
- if (mWidgetPreviewLoader == null) {
- mWidgetPreviewLoader = LauncherAppState.getInstance().getWidgetCache();
- }
- return mWidgetPreviewLoader;
+ public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
+ return mAdapter.copyWidgetsForPackageUser(packageUserKey);
}
@Override
- public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
- targetParent.containerType = LauncherLogProto.WIDGETS;
+ 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/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index 6b8ea49..a1eb0ab 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -15,28 +15,30 @@
*/
package com.android.launcher3.widget;
-import android.annotation.TargetApi;
import android.content.Context;
-import android.os.Build;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.Adapter;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.LinearLayout;
-import com.android.launcher3.BubbleTextView;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
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.model.WidgetsModel;
+import com.android.launcher3.util.LabelComparator;
+import com.android.launcher3.util.MultiHashMap;
+import com.android.launcher3.util.PackageUserKey;
+import java.util.ArrayList;
+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.
@@ -57,7 +59,8 @@
private final View.OnClickListener mIconClickListener;
private final View.OnLongClickListener mIconLongClickListener;
- private WidgetsModel mWidgetsModel;
+ private final ArrayList<WidgetListRowEntry> mEntries = new ArrayList<>();
+ private final AlphabeticIndexCompat mIndexer;
private final int mIndent;
@@ -65,28 +68,62 @@
View.OnLongClickListener iconLongClickListener,
Context context) {
mLayoutInflater = LayoutInflater.from(context);
- mWidgetPreviewLoader = LauncherAppState.getInstance().getWidgetCache();
+ mWidgetPreviewLoader = LauncherAppState.getInstance(context).getWidgetCache();
+
+ mIndexer = new AlphabeticIndexCompat(context);
mIconClickListener = iconClickListener;
mIconLongClickListener = iconLongClickListener;
mIndent = context.getResources().getDimensionPixelSize(R.dimen.widget_section_indent);
}
- public void setWidgetsModel(WidgetsModel w) {
- mWidgetsModel = w;
+ public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets) {
+ mEntries.clear();
+ WidgetItemComparator widgetComparator = new WidgetItemComparator();
+
+ for (Map.Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : widgets.entrySet()) {
+ WidgetListRowEntry row = new WidgetListRowEntry(entry.getKey(), entry.getValue());
+ row.titleSectionName = mIndexer.computeSectionName(row.pkgItem.title);
+ Collections.sort(row.widgets, widgetComparator);
+ mEntries.add(row);
+ }
+
+ Collections.sort(mEntries, new WidgetListRowEntryComparator());
}
@Override
public int getItemCount() {
- if (mWidgetsModel == null) {
- return 0;
+ return mEntries.size();
+ }
+
+ public String getSectionName(int pos) {
+ return mEntries.get(pos).titleSectionName;
+ }
+
+ /**
+ * Copies and returns the widgets associated with the package and user of the ComponentKey.
+ */
+ public List<WidgetItem> copyWidgetsForPackageUser(PackageUserKey packageUserKey) {
+ for (WidgetListRowEntry entry : mEntries) {
+ if (entry.pkgItem.packageName.equals(packageUserKey.mPackageName)) {
+ ArrayList<WidgetItem> widgets = new ArrayList<>(entry.widgets);
+ // Remove widgets not associated with the correct user.
+ Iterator<WidgetItem> iterator = widgets.iterator();
+ while (iterator.hasNext()) {
+ if (!iterator.next().user.equals(packageUserKey.mUser)) {
+ iterator.remove();
+ }
+ }
+ return widgets.isEmpty() ? null : widgets;
+ }
}
- return mWidgetsModel.getPackageSize();
+ return null;
}
@Override
public void onBindViewHolder(WidgetsRowViewHolder holder, int pos) {
- List<WidgetItem> infoList = mWidgetsModel.getSortedWidgets(pos);
+ WidgetListRowEntry entry = mEntries.get(pos);
+ List<WidgetItem> infoList = entry.widgets;
ViewGroup row = holder.cellContainer;
if (DEBUG) {
@@ -97,42 +134,47 @@
// Add more views.
// if there are too many, hide them.
- int diff = infoList.size() - row.getChildCount();
+ int expectedChildCount = infoList.size() + Math.max(0, infoList.size() - 1);
+ int childCount = row.getChildCount();
- if (diff > 0) {
- for (int i = 0; i < diff; i++) {
- WidgetCell widget = (WidgetCell) mLayoutInflater.inflate(
- R.layout.widget_cell, row, false);
+ if (expectedChildCount > childCount) {
+ for (int i = childCount ; i < expectedChildCount; i++) {
+ if ((i & 1) == 1) {
+ // Add a divider for odd index
+ mLayoutInflater.inflate(R.layout.widget_list_divider, row);
+ } else {
+ // Add cell for even index
+ WidgetCell widget = (WidgetCell) mLayoutInflater.inflate(
+ R.layout.widget_cell, row, false);
- // set up touch.
- widget.setOnClickListener(mIconClickListener);
- widget.setOnLongClickListener(mIconLongClickListener);
- LayoutParams lp = widget.getLayoutParams();
- lp.height = widget.cellSize;
- lp.width = widget.cellSize;
- widget.setLayoutParams(lp);
-
- row.addView(widget);
+ // set up touch.
+ widget.setOnClickListener(mIconClickListener);
+ widget.setOnLongClickListener(mIconLongClickListener);
+ row.addView(widget);
+ }
}
- } else if (diff < 0) {
- for (int i=infoList.size() ; i < row.getChildCount(); i++) {
+ } else if (expectedChildCount < childCount) {
+ for (int i = expectedChildCount ; i < childCount; i++) {
row.getChildAt(i).setVisibility(View.GONE);
}
}
// Bind the views in the application info section.
- holder.title.applyFromPackageItemInfo(mWidgetsModel.getPackageItemInfo(pos));
+ holder.title.applyFromPackageItemInfo(entry.pkgItem);
// Bind the view in the widget horizontal tray region.
for (int i=0; i < infoList.size(); i++) {
- WidgetCell widget = (WidgetCell) row.getChildAt(i);
+ WidgetCell widget = (WidgetCell) row.getChildAt(2*i);
widget.applyFromCellItem(infoList.get(i), mWidgetPreviewLoader);
widget.ensurePreview();
widget.setVisibility(View.VISIBLE);
+
+ if (i > 0) {
+ row.getChildAt(2*i - 1).setVisibility(View.VISIBLE);
+ }
}
}
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
@Override
public WidgetsRowViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (DEBUG) {
@@ -141,15 +183,10 @@
ViewGroup container = (ViewGroup) mLayoutInflater.inflate(
R.layout.widgets_list_row_view, parent, false);
- LinearLayout cellList = (LinearLayout) container.findViewById(R.id.widgets_cell_list);
// if the end padding is 0, then container view (horizontal scroll view) doesn't respect
// the end of the linear layout width + the start padding and doesn't allow scrolling.
- if (Utilities.ATLEAST_JB_MR1) {
- cellList.setPaddingRelative(mIndent, 0, 1, 0);
- } else {
- cellList.setPadding(mIndent, 0, 1, 0);
- }
+ container.findViewById(R.id.widgets_cell_list).setPaddingRelative(mIndent, 0, 1, 0);
return new WidgetsRowViewHolder(container);
}
@@ -157,7 +194,7 @@
@Override
public void onViewRecycled(WidgetsRowViewHolder holder) {
int total = holder.cellContainer.getChildCount();
- for (int i = 0; i < total; i++) {
+ for (int i = 0; i < total; i+=2) {
WidgetCell widget = (WidgetCell) holder.cellContainer.getChildAt(i);
widget.clear();
}
@@ -175,4 +212,18 @@
public long getItemId(int pos) {
return pos;
}
+
+ /**
+ * Comparator for sorting WidgetListRowEntry based on package title
+ */
+ public static class WidgetListRowEntryComparator implements Comparator<WidgetListRowEntry> {
+
+ private final LabelComparator mComparator = new LabelComparator();
+
+ @Override
+ public int compare(WidgetListRowEntry a, WidgetListRowEntry b) {
+ 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 2560661..e0a80c6 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -17,7 +17,6 @@
package com.android.launcher3.widget;
import android.content.Context;
-import android.graphics.Canvas;
import android.graphics.Color;
import android.support.v7.widget.LinearLayoutManager;
import android.util.AttributeSet;
@@ -32,7 +31,7 @@
public class WidgetsRecyclerView extends BaseRecyclerView {
private static final String TAG = "WidgetsRecyclerView";
- private WidgetsModel mWidgets;
+ private WidgetsListAdapter mAdapter;
public WidgetsRecyclerView(Context context) {
this(context, null);
@@ -65,23 +64,10 @@
return Color.WHITE;
}
- /**
- * Sets the widget model in this view, used to determine the fast scroll position.
- */
- public void setWidgets(WidgetsModel widgets) {
- mWidgets = widgets;
- }
-
- /**
- * We need to override the draw to ensure that we don't draw the overscroll effect beyond the
- * background bounds.
- */
@Override
- protected void dispatchDraw(Canvas canvas) {
- canvas.clipRect(mBackgroundPadding.left, mBackgroundPadding.top,
- getWidth() - mBackgroundPadding.right,
- getHeight() - mBackgroundPadding.bottom);
- super.dispatchDraw(canvas);
+ public void setAdapter(Adapter adapter) {
+ super.setAdapter(adapter);
+ mAdapter = (WidgetsListAdapter) adapter;
}
/**
@@ -97,15 +83,14 @@
// Stop the scroller if it is scrolling
stopScroll();
- int rowCount = mWidgets.getPackageSize();
+ int rowCount = mAdapter.getItemCount();
float pos = rowCount * touchFraction;
int availableScrollHeight = getAvailableScrollHeight();
LinearLayoutManager layoutManager = ((LinearLayoutManager) getLayoutManager());
layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
int posInt = (int) ((touchFraction == 1)? pos -1 : pos);
- PackageItemInfo p = mWidgets.getPackageItemInfo(posInt);
- return p.titleSectionName;
+ return mAdapter.getSectionName(posInt);
}
/**
@@ -121,7 +106,7 @@
// Skip early if, there no child laid out in the container.
int scrollY = getCurrentScrollY();
if (scrollY < 0) {
- mScrollbar.setThumbOffset(-1, -1);
+ mScrollbar.setThumbOffsetY(-1);
return;
}
@@ -150,13 +135,13 @@
@Override
protected int getAvailableScrollHeight() {
View child = getChildAt(0);
- int height = child.getMeasuredHeight() * mWidgets.getPackageSize();
+ int height = child.getMeasuredHeight() * mAdapter.getItemCount();
int totalHeight = getPaddingTop() + height + getPaddingBottom();
- int availableScrollHeight = totalHeight - getVisibleHeight();
+ int availableScrollHeight = totalHeight - getScrollbarTrackHeight();
return availableScrollHeight;
}
private boolean isModelNotReady() {
- return mWidgets == null || mWidgets.getPackageSize() == 0;
+ return mAdapter.getItemCount() == 0;
}
}
\ No newline at end of file
diff --git a/src_config/com/android/launcher3/config/FeatureFlags.java b/src_config/com/android/launcher3/config/FeatureFlags.java
index 8ee5497..4e337a2 100644
--- a/src_config/com/android/launcher3/config/FeatureFlags.java
+++ b/src_config/com/android/launcher3/config/FeatureFlags.java
@@ -24,12 +24,16 @@
// Custom flags go below this
public static boolean LAUNCHER3_DISABLE_ICON_NORMALIZATION = false;
- // As opposed to the new spring-loaded workspace.
- public static boolean LAUNCHER3_LEGACY_WORKSPACE_DND = false;
public static boolean LAUNCHER3_LEGACY_FOLDER_ICON = false;
public static boolean LAUNCHER3_USE_SYSTEM_DRAG_DRIVER = true;
public static boolean LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW = false;
public static boolean LAUNCHER3_ALL_APPS_PULL_UP = true;
+ public static boolean LAUNCHER3_NEW_FOLDER_ANIMATION = false;
+ // When enabled allows to use any point on the fast scrollbar to start dragging.
+ public static boolean LAUNCHER3_DIRECT_SCROLL = true;
+ // When enabled while all-apps open, the soft input will be set to adjust resize .
+ public static boolean LAUNCHER3_UPDATE_SOFT_INPUT_MODE = false;
+
// Feature flag to enable moving the QSB on the 0th screen of the workspace.
public static final boolean QSB_ON_FIRST_SCREEN = true;
@@ -39,4 +43,12 @@
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 are badged with the number of notifications associated with that app.
+ public static final boolean BADGE_ICONS = true;
+ // When enabled, icons not supporting {@link AdaptiveIconDrawable} will be wrapped in this class.
+ 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;
}
diff --git a/src_config/com/android/launcher3/config/ProviderConfig.java b/src_config/com/android/launcher3/config/ProviderConfig.java
index 1d964b1..491fa65 100644
--- a/src_config/com/android/launcher3/config/ProviderConfig.java
+++ b/src_config/com/android/launcher3/config/ProviderConfig.java
@@ -20,5 +20,5 @@
public static final String AUTHORITY = "com.android.launcher3.settings".intern();
- public static boolean IS_DOGFOOD_BUILD = true;
+ public static final boolean IS_DOGFOOD_BUILD = true;
}
diff --git a/tests/Android.mk b/tests/Android.mk
index c91e793..6e6453c 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -20,11 +20,14 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
ub-uiautomator \
- legacy-android-test
+ legacy-android-test \
+ mockito-target-minus-junit4
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/AndroidManifest-common.xml
LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 21
LOCAL_PACKAGE_NAME := Launcher3Tests
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
new file mode 100644
index 0000000..0a29147
--- /dev/null
+++ b/tests/AndroidManifest-common.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.launcher3.tests">
+
+ <uses-sdk android:targetSdkVersion="25" android:minSdkVersion="21"/>
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+
+ <receiver
+ android:name="com.android.launcher3.testcomponent.AppWidgetNoConfig"
+ android:label="No Config">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data android:name="android.appwidget.provider"
+ android:resource="@xml/appwidget_no_config" />
+ </receiver>
+
+ <receiver
+ android:name="com.android.launcher3.testcomponent.AppWidgetWithConfig"
+ android:label="With Config">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data android:name="android.appwidget.provider"
+ android:resource="@xml/appwidget_with_config" />
+ </receiver>
+
+ <activity
+ android:name="com.android.launcher3.testcomponent.WidgetConfigActivity">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
+ </intent-filter>
+ </activity>
+ <activity
+ android:name="com.android.launcher3.testcomponent.RequestPinItemActivity"
+ android:label="Test Pin Item"
+ android:icon="@drawable/test_drawable_pin_item">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index afe8952..43030ae 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -18,6 +18,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.android.launcher3.tests">
+ <uses-sdk android:targetSdkVersion="25" android:minSdkVersion="21"/>
<uses-sdk tools:overrideLibrary="android.support.test.uiautomator.v18"/>
<application android:debuggable="true">
diff --git a/tests/res/drawable/test_drawable_pin_item.xml b/tests/res/drawable/test_drawable_pin_item.xml
new file mode 100644
index 0000000..1d07256
--- /dev/null
+++ b/tests/res/drawable/test_drawable_pin_item.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0">
+
+ <path
+ android:fillColor="#66000000"
+ android:pathData="M0,24a24,24 0 1,0 48,0a24,24 0 1,0 -48,0"/>
+ <path
+ android:fillColor="#FF1B5E20"
+ android:pathData="M2,24a22,22 0 1,0 44,0a22,22 0 1,0 -44,0"/>
+
+ <group
+ android:translateX="12"
+ android:translateY="12">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z"/>
+ </group>
+</vector>
\ No newline at end of file
diff --git a/tests/res/layout/test_layout_appwidget_blue.xml b/tests/res/layout/test_layout_appwidget_blue.xml
new file mode 100644
index 0000000..8111978
--- /dev/null
+++ b/tests/res/layout/test_layout_appwidget_blue.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:background="#FF0000FF"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
\ No newline at end of file
diff --git a/tests/res/layout/test_layout_appwidget_red.xml b/tests/res/layout/test_layout_appwidget_red.xml
new file mode 100644
index 0000000..48d3e81
--- /dev/null
+++ b/tests/res/layout/test_layout_appwidget_red.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:background="#FFFF0000"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
\ No newline at end of file
diff --git a/tests/res/layout/test_layout_appwidget_view.xml b/tests/res/layout/test_layout_appwidget_view.xml
new file mode 100644
index 0000000..7c87e60
--- /dev/null
+++ b/tests/res/layout/test_layout_appwidget_view.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#FFFF00">
+
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="20dp"
+ android:background="#FF0000"
+ android:id="@android:id/icon" />
+</FrameLayout>
\ No newline at end of file
diff --git a/tests/res/raw/cache_data_updated_task_data.txt b/tests/res/raw/cache_data_updated_task_data.txt
new file mode 100644
index 0000000..8199687
--- /dev/null
+++ b/tests/res/raw/cache_data_updated_task_data.txt
@@ -0,0 +1,28 @@
+# Model data used by CacheDataUpdatedTaskTest
+
+classMap s com.android.launcher3.ShortcutInfo
+
+# Items for the BgDataModel
+
+# App shortcuts
+bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
+bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
+bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
+bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
+
+# Auto install app shortcut
+bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
+bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
+
+# Custom shortcuts
+bgItem s itemType=1 title=app1-shrt intent=component=app1/class3 id=7
+bgItem s itemType=1 title=app4-shrt intent=component=app4/class1 id=8
+
+# Restored custom shortcut
+bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=9
+bgItem s itemType=1 status=1 title=app5-shrt intent=component=app5/class1 id=10
+
+allApps componentName=app1/class1 intent=component=app1/class1
+allApps componentName=app1/class2 intent=component=app1/class2
+allApps componentName=app2/class1 intent=component=app2/class1
+allApps componentName=app2/class2 intent=component=app2/class2
\ No newline at end of file
diff --git a/tests/res/raw/package_install_state_change_task_data.txt b/tests/res/raw/package_install_state_change_task_data.txt
new file mode 100644
index 0000000..84f9c16
--- /dev/null
+++ b/tests/res/raw/package_install_state_change_task_data.txt
@@ -0,0 +1,24 @@
+# Model data used by PackageInstallStateChangeTaskTest
+
+classMap s com.android.launcher3.ShortcutInfo
+classMap w com.android.launcher3.LauncherAppWidgetInfo
+
+# Items for the BgDataModel
+
+# App shortcuts
+bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
+bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
+bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
+bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
+
+# Promise icons for app3
+bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
+bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
+bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=7
+
+# Promise icon for app4
+bgItem s itemType=1 status=1 title=app4-shrt intent=component=app4/class1 id=8
+
+# Widget
+bgItem w providerName=app4/provider1 id=9
+bgItem w providerName=app5/provider1 id=10
\ No newline at end of file
diff --git a/tests/res/xml/appwidget_no_config.xml b/tests/res/xml/appwidget_no_config.xml
new file mode 100644
index 0000000..d24dfe3
--- /dev/null
+++ b/tests/res/xml/appwidget_no_config.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<appwidget-provider
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:minWidth="180dp"
+ android:minHeight="110dp"
+ android:updatePeriodMillis="86400000"
+ android:initialLayout="@layout/test_layout_appwidget_red"
+ android:resizeMode="horizontal|vertical"
+ android:widgetCategory="home_screen">
+</appwidget-provider>
\ No newline at end of file
diff --git a/tests/res/xml/appwidget_with_config.xml b/tests/res/xml/appwidget_with_config.xml
new file mode 100644
index 0000000..3e96c6f
--- /dev/null
+++ b/tests/res/xml/appwidget_with_config.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<appwidget-provider
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:minWidth="180dp"
+ android:minHeight="110dp"
+ android:updatePeriodMillis="86400000"
+ android:initialLayout="@layout/test_layout_appwidget_blue"
+ android:configure="com.android.launcher3.testcomponent.WidgetConfigActivity"
+ android:resizeMode="horizontal|vertical"
+ android:widgetCategory="home_screen">
+</appwidget-provider>
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
new file mode 100644
index 0000000..d0ba907
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
@@ -0,0 +1,183 @@
+package com.android.launcher3.model;
+
+import android.content.ComponentName;
+import android.content.ContentProviderOperation;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.util.Pair;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.LongArrayMap;
+import com.android.launcher3.util.Provider;
+
+import org.mockito.ArgumentCaptor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link AddWorkspaceItemsTask}
+ */
+public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
+
+ private final ComponentName mComponent1 = new ComponentName("a", "b");
+ private final ComponentName mComponent2 = new ComponentName("b", "b");
+
+ private ArrayList<Long> existingScreens;
+ private ArrayList<Long> newScreens;
+ private LongArrayMap<GridOccupancy> screenOccupancy;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ existingScreens = new ArrayList<>();
+ screenOccupancy = new LongArrayMap<>();
+ newScreens = new ArrayList<>();
+
+ idp.numColumns = 5;
+ idp.numRows = 5;
+ }
+
+ private AddWorkspaceItemsTask newTask(ItemInfo... items) {
+ return new AddWorkspaceItemsTask(Provider.of(Arrays.asList(items))) {
+
+ @Override
+ protected void updateScreens(Context context, ArrayList<Long> workspaceScreens) { }
+ };
+ }
+
+ 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));
+
+ // Second screen has 2 holes of sizes 3x2 and 2x3
+ setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
+
+ Pair<Long, int[]> spaceFound = newTask()
+ .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 1, 1);
+ assertEquals(2L, (long) spaceFound.first);
+ assertTrue(screenOccupancy.get(spaceFound.first)
+ .isRegionVacant(spaceFound.second[0], spaceFound.second[1], 1, 1));
+
+ // Find a larger space
+ spaceFound = newTask()
+ .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 2, 3);
+ assertEquals(2L, (long) spaceFound.first);
+ assertTrue(screenOccupancy.get(spaceFound.first)
+ .isRegionVacant(spaceFound.second[0], spaceFound.second[1], 2, 3));
+ }
+
+ public void testFindSpaceForItem_adds_new_screen() throws Exception {
+ // First screen has 2 holes of sizes 3x2 and 2x3
+ setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
+ commitScreensToDb();
+
+ when(appState.getContext()).thenReturn(getMockContext());
+
+ ArrayList<Long> oldScreens = new ArrayList<>(existingScreens);
+ Pair<Long, int[]> spaceFound = newTask()
+ .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 3, 3);
+ assertFalse(oldScreens.contains(spaceFound.first));
+ assertTrue(newScreens.contains(spaceFound.first));
+ }
+
+ public void testAddItem_existing_item_ignored() throws Exception {
+ ShortcutInfo info = new ShortcutInfo();
+ info.intent = new Intent().setComponent(mComponent1);
+
+ // Setup a screen with a hole
+ setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
+ commitScreensToDb();
+
+ when(appState.getContext()).thenReturn(getMockContext());
+
+ // Nothing was added
+ assertTrue(executeTaskForTest(newTask(info)).isEmpty());
+ }
+
+ public void testAddItem_some_items_added() throws Exception {
+ ShortcutInfo info = new ShortcutInfo();
+ info.intent = new Intent().setComponent(mComponent1);
+
+ ShortcutInfo info2 = new ShortcutInfo();
+ info2.intent = new Intent().setComponent(mComponent2);
+
+ // Setup a screen with a hole
+ setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
+ commitScreensToDb();
+
+ when(appState.getContext()).thenReturn(getMockContext());
+
+ executeTaskForTest(newTask(info, info2)).get(0).run();
+ ArgumentCaptor<ArrayList> notAnimated = ArgumentCaptor.forClass(ArrayList.class);
+ ArgumentCaptor<ArrayList> animated = ArgumentCaptor.forClass(ArrayList.class);
+
+ // only info2 should be added because info was already added to the workspace
+ // in setupWorkspaceWithHoles()
+ verify(callbacks).bindAppsAdded(any(ArrayList.class), notAnimated.capture(),
+ animated.capture(), any(ArrayList.class));
+ assertTrue(notAnimated.getValue().isEmpty());
+
+ assertEquals(1, animated.getValue().size());
+ assertTrue(animated.getValue().contains(info2));
+ }
+
+ private int setupWorkspaceWithHoles(int startId, long screenId, Rect... holes) {
+ GridOccupancy occupancy = new GridOccupancy(idp.numColumns, idp.numRows);
+ occupancy.markCells(0, 0, idp.numColumns, idp.numRows, true);
+ for (Rect r : holes) {
+ occupancy.markCells(r, false);
+ }
+
+ existingScreens.add(screenId);
+ screenOccupancy.append(screenId, occupancy);
+
+ for (int x = 0; x < idp.numColumns; x++) {
+ for (int y = 0; y < idp.numRows; y++) {
+ if (!occupancy.cells[x][y]) {
+ continue;
+ }
+
+ ShortcutInfo info = new ShortcutInfo();
+ info.intent = new Intent().setComponent(mComponent1);
+ info.id = startId++;
+ info.screenId = screenId;
+ info.cellX = x;
+ info.cellY = y;
+ info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
+ bgDataModel.addItem(targetContext, info, false);
+ }
+ }
+ return startId;
+ }
+
+ private void commitScreensToDb() throws Exception {
+ LauncherSettings.Settings.call(getMockContentResolver(),
+ LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
+
+ Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
+ ArrayList<ContentProviderOperation> ops = new ArrayList<>();
+ // Clear the table
+ ops.add(ContentProviderOperation.newDelete(uri).build());
+ int count = existingScreens.size();
+ for (int i = 0; i < count; i++) {
+ ContentValues v = new ContentValues();
+ long screenId = existingScreens.get(i);
+ v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
+ v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
+ ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
+ }
+ getMockContentResolver().applyBatch(ProviderConfig.AUTHORITY, ops);
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
new file mode 100644
index 0000000..b9944db
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -0,0 +1,226 @@
+package com.android.launcher3.model;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherActivityInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.os.Process;
+import android.os.UserHandle;
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+import android.test.ProviderTestCase2;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.AppFilter;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.DeferredHandler;
+import com.android.launcher3.IconCache;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherModel.BaseModelUpdateTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.Provider;
+import com.android.launcher3.util.TestLauncherProvider;
+
+import org.mockito.ArgumentCaptor;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.List;
+
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Base class for writing tests for Model update tasks.
+ */
+public class BaseModelUpdateTaskTestCase extends ProviderTestCase2<TestLauncherProvider> {
+
+ public final HashMap<Class, HashMap<String, Field>> fieldCache = new HashMap<>();
+
+ public Context targetContext;
+ public UserHandle myUser;
+
+ public InvariantDeviceProfile idp;
+ public LauncherAppState appState;
+ public LauncherModel model;
+ public ModelWriter modelWriter;
+ public MyIconCache iconCache;
+
+ public BgDataModel bgDataModel;
+ public AllAppsList allAppsList;
+ public Callbacks callbacks;
+
+ public BaseModelUpdateTaskTestCase() {
+ super(TestLauncherProvider.class, ProviderConfig.AUTHORITY);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ callbacks = mock(Callbacks.class);
+ appState = mock(LauncherAppState.class);
+ model = mock(LauncherModel.class);
+ modelWriter = mock(ModelWriter.class);
+ when(appState.getModel()).thenReturn(model);
+ when(model.getWriter(anyBoolean())).thenReturn(modelWriter);
+
+ myUser = Process.myUserHandle();
+
+ bgDataModel = new BgDataModel();
+ targetContext = InstrumentationRegistry.getTargetContext();
+ idp = new InvariantDeviceProfile();
+ iconCache = new MyIconCache(targetContext, idp);
+
+ allAppsList = new AllAppsList(iconCache, new AppFilter());
+
+ when(appState.getIconCache()).thenReturn(iconCache);
+ when(appState.getInvariantDeviceProfile()).thenReturn(idp);
+ }
+
+ /**
+ * Synchronously executes the task and returns all the UI callbacks posted.
+ */
+ public List<Runnable> executeTaskForTest(BaseModelUpdateTask task) throws Exception {
+ LauncherModel mockModel = mock(LauncherModel.class);
+ when(mockModel.getCallback()).thenReturn(callbacks);
+
+ Field f = BaseModelUpdateTask.class.getDeclaredField("mModel");
+ f.setAccessible(true);
+ f.set(task, mockModel);
+
+ DeferredHandler mockHandler = mock(DeferredHandler.class);
+ f = BaseModelUpdateTask.class.getDeclaredField("mUiHandler");
+ f.setAccessible(true);
+ f.set(task, mockHandler);
+
+ task.execute(appState, bgDataModel, allAppsList);
+ ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mockHandler, atLeast(0)).post(captor.capture());
+
+ return captor.getAllValues();
+ }
+
+ /**
+ * Initializes mock data for the test.
+ */
+ public void initializeData(String resourceName) throws Exception {
+ Context myContext = InstrumentationRegistry.getContext();
+ Resources res = myContext.getResources();
+ int id = res.getIdentifier(resourceName, "raw", myContext.getPackageName());
+ try (BufferedReader reader =
+ new BufferedReader(new InputStreamReader(res.openRawResource(id)))) {
+ String line;
+ HashMap<String, Class> classMap = new HashMap<>();
+ while((line = reader.readLine()) != null) {
+ line = line.trim();
+ if (line.startsWith("#") || line.isEmpty()) {
+ continue;
+ }
+ String[] commands = line.split(" ");
+ switch (commands[0]) {
+ case "classMap":
+ classMap.put(commands[1], Class.forName(commands[2]));
+ break;
+ case "bgItem":
+ bgDataModel.addItem(targetContext,
+ (ItemInfo) initItem(classMap.get(commands[1]), commands, 2), false);
+ break;
+ case "allApps":
+ allAppsList.add((AppInfo) initItem(AppInfo.class, commands, 1), null);
+ break;
+ }
+ }
+ }
+ }
+
+ private Object initItem(Class clazz, String[] fieldDef, int startIndex) throws Exception {
+ HashMap<String, Field> cache = fieldCache.get(clazz);
+ if (cache == null) {
+ cache = new HashMap<>();
+ Class c = clazz;
+ while (c != null) {
+ for (Field f : c.getDeclaredFields()) {
+ f.setAccessible(true);
+ cache.put(f.getName(), f);
+ }
+ c = c.getSuperclass();
+ }
+ fieldCache.put(clazz, cache);
+ }
+
+ Object item = clazz.newInstance();
+ for (int i = startIndex; i < fieldDef.length; i++) {
+ String[] fieldData = fieldDef[i].split("=", 2);
+ Field f = cache.get(fieldData[0]);
+ Class type = f.getType();
+ if (type == int.class || type == long.class) {
+ f.set(item, Integer.parseInt(fieldData[1]));
+ } else if (type == CharSequence.class || type == String.class) {
+ f.set(item, fieldData[1]);
+ } else if (type == Intent.class) {
+ if (!fieldData[1].startsWith("#Intent")) {
+ fieldData[1] = "#Intent;" + fieldData[1] + ";end";
+ }
+ f.set(item, Intent.parseUri(fieldData[1], 0));
+ } else if (type == ComponentName.class) {
+ f.set(item, ComponentName.unflattenFromString(fieldData[1]));
+ } else {
+ throw new Exception("Added parsing logic for "
+ + f.getName() + " of type " + f.getType());
+ }
+ }
+ return item;
+ }
+
+ public static class MyIconCache extends IconCache {
+
+ private final HashMap<ComponentKey, CacheEntry> mCache = new HashMap<>();
+
+ public MyIconCache(Context context, InvariantDeviceProfile idp) {
+ super(context, idp);
+ }
+
+ @Override
+ protected CacheEntry cacheLocked(
+ @NonNull ComponentName componentName,
+ @NonNull Provider<LauncherActivityInfo> infoProvider,
+ UserHandle user, boolean usePackageIcon, boolean useLowResIcon) {
+ CacheEntry entry = mCache.get(new ComponentKey(componentName, user));
+ if (entry == null) {
+ entry = new CacheEntry();
+ entry.icon = getDefaultIcon(user);
+ }
+ return entry;
+ }
+
+ public void addCache(ComponentName key, String title) {
+ CacheEntry entry = new CacheEntry();
+ entry.icon = newIcon();
+ entry.title = title;
+ mCache.put(new ComponentKey(key, Process.myUserHandle()), entry);
+ }
+
+ public Bitmap newIcon() {
+ return Bitmap.createBitmap(1, 1, Config.ARGB_8888);
+ }
+
+ @Override
+ protected Bitmap makeDefaultIcon(UserHandle user) {
+ return newIcon();
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
new file mode 100644
index 0000000..d595e6c
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -0,0 +1,82 @@
+package com.android.launcher3.model;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.ShortcutInfo;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+/**
+ * Tests for {@link CacheDataUpdatedTask}
+ */
+public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase {
+
+ private static final String NEW_LABEL_PREFIX = "new-label-";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ initializeData("cache_data_updated_task_data");
+ // Add dummy entries in the cache to simulate update
+ for (ItemInfo info : bgDataModel.itemsIdMap) {
+ iconCache.addCache(info.getTargetComponent(), NEW_LABEL_PREFIX + info.id);
+ }
+ }
+
+ private CacheDataUpdatedTask newTask(int op, String... pkg) {
+ return new CacheDataUpdatedTask(op, myUser, new HashSet<>(Arrays.asList(pkg)));
+ }
+
+ 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) {
+ info.iconBitmap = null;
+ }
+
+ executeTaskForTest(newTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, "app1"));
+
+ // Verify that only the app icons of app1 (id 1 & 2) are updated. Custom shortcut (id 7)
+ // is not updated
+ verifyUpdate(1L, 2L);
+
+ // Verify that only app1 var updated in allAppsList
+ assertFalse(allAppsList.data.isEmpty());
+ for (AppInfo info : allAppsList.data) {
+ if (info.componentName.getPackageName().equals("app1")) {
+ assertNotNull(info.iconBitmap);
+ } else {
+ assertNull(info.iconBitmap);
+ }
+ }
+ }
+
+ public void testSessionUpdate_ignores_normal_apps() throws Exception {
+ executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app1"));
+
+ // app1 has no restored shortcuts. Verify that nothing was updated.
+ verifyUpdate();
+ }
+
+ public void testSessionUpdate_updates_pending_apps() throws Exception {
+ executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app3"));
+
+ // app3 has only restored apps (id 5, 6) and shortcuts (id 9). Verify that only apps were
+ // were updated
+ verifyUpdate(5L, 6L);
+ }
+
+ private void verifyUpdate(Long... idsUpdated) {
+ HashSet<Long> updates = new HashSet<>(Arrays.asList(idsUpdated));
+ for (ItemInfo info : bgDataModel.itemsIdMap) {
+ if (updates.contains(info.id)) {
+ assertEquals(NEW_LABEL_PREFIX + info.id, info.title);
+ assertNotNull(((ShortcutInfo) info).iconBitmap);
+ } else {
+ assertNotSame(NEW_LABEL_PREFIX + info.id, info.title);
+ assertNull(((ShortcutInfo) info).iconBitmap);
+ }
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
new file mode 100644
index 0000000..173c556
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -0,0 +1,236 @@
+package com.android.launcher3.model;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.database.MatrixCursor;
+import android.graphics.Bitmap;
+import android.os.Process;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.IconCache;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.LauncherAppsCompat;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import static com.android.launcher3.LauncherSettings.BaseLauncherColumns.INTENT;
+import static com.android.launcher3.LauncherSettings.Favorites.CELLX;
+import static com.android.launcher3.LauncherSettings.Favorites.CELLY;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.LauncherSettings.Favorites.ICON;
+import static com.android.launcher3.LauncherSettings.Favorites.ICON_PACKAGE;
+import static com.android.launcher3.LauncherSettings.Favorites.ICON_RESOURCE;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+import static com.android.launcher3.LauncherSettings.Favorites.PROFILE_ID;
+import static com.android.launcher3.LauncherSettings.Favorites.RESTORED;
+import static com.android.launcher3.LauncherSettings.Favorites.SCREEN;
+import static com.android.launcher3.LauncherSettings.Favorites.TITLE;
+import static com.android.launcher3.LauncherSettings.Favorites._ID;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link LoaderCursor}
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LoaderCursorTest {
+
+ private LauncherAppState mMockApp;
+ private IconCache mMockIconCache;
+
+ private MatrixCursor mCursor;
+ private InvariantDeviceProfile mIDP;
+ private Context mContext;
+ private LauncherAppsCompat mLauncherApps;
+
+ private LoaderCursor mLoaderCursor;
+
+ @Before
+ public void setup() {
+ mIDP = new InvariantDeviceProfile();
+ mCursor = new MatrixCursor(new String[] {
+ ICON, ICON_PACKAGE, ICON_RESOURCE, TITLE,
+ _ID, CONTAINER, ITEM_TYPE, PROFILE_ID,
+ SCREEN, CELLX, CELLY, RESTORED, INTENT
+ });
+ mContext = InstrumentationRegistry.getTargetContext();
+
+ mMockApp = mock(LauncherAppState.class);
+ mMockIconCache = mock(IconCache.class);
+ when(mMockApp.getIconCache()).thenReturn(mMockIconCache);
+ when(mMockApp.getInvariantDeviceProfile()).thenReturn(mIDP);
+ when(mMockApp.getContext()).thenReturn(mContext);
+ mLauncherApps = LauncherAppsCompat.getInstance(mContext);
+
+ mLoaderCursor = new LoaderCursor(mCursor, mMockApp);
+ mLoaderCursor.allUsers.put(0, Process.myUserHandle());
+ }
+
+ private void initCursor(int itemType, String title) {
+ mCursor.newRow()
+ .add(_ID, 1)
+ .add(PROFILE_ID, 0)
+ .add(ITEM_TYPE, itemType)
+ .add(TITLE, title)
+ .add(CONTAINER, CONTAINER_DESKTOP);
+ }
+
+ @Test
+ public void getAppShortcutInfo_dontAllowMissing_invalidComponent() {
+ initCursor(ITEM_TYPE_APPLICATION, "");
+ assertTrue(mLoaderCursor.moveToNext());
+ ComponentName cn = new ComponentName(mContext.getPackageName(), "dummy-do");
+ assertNull(mLoaderCursor.getAppShortcutInfo(
+ new Intent().setComponent(cn), false /* allowMissingTarget */, true));
+ }
+
+ @Test
+ public void getAppShortcutInfo_dontAllowMissing_validComponent() {
+ initCursor(ITEM_TYPE_APPLICATION, "");
+ assertTrue(mLoaderCursor.moveToNext());
+
+ ComponentName cn = mLauncherApps.getActivityList(null, mLoaderCursor.user)
+ .get(0).getComponentName();
+ ShortcutInfo info = mLoaderCursor.getAppShortcutInfo(
+ new Intent().setComponent(cn), false /* allowMissingTarget */, true);
+ assertNotNull(info);
+ assertTrue(Utilities.isLauncherAppTarget(info.intent));
+ }
+
+ @Test
+ public void getAppShortcutInfo_allowMissing_invalidComponent() {
+ initCursor(ITEM_TYPE_APPLICATION, "");
+ assertTrue(mLoaderCursor.moveToNext());
+
+ ComponentName cn = new ComponentName(mContext.getPackageName(), "dummy-do");
+ ShortcutInfo info = mLoaderCursor.getAppShortcutInfo(
+ new Intent().setComponent(cn), true /* allowMissingTarget */, true);
+ assertNotNull(info);
+ assertTrue(Utilities.isLauncherAppTarget(info.intent));
+ }
+
+ @Test
+ public void loadSimpleShortcut() {
+ initCursor(ITEM_TYPE_SHORTCUT, "my-shortcut");
+ assertTrue(mLoaderCursor.moveToNext());
+
+ Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
+ when(mMockIconCache.getDefaultIcon(eq(mLoaderCursor.user))).thenReturn(icon);
+ ShortcutInfo info = mLoaderCursor.loadSimpleShortcut();
+ assertEquals(icon, info.iconBitmap);
+ assertEquals("my-shortcut", info.title);
+ assertEquals(ITEM_TYPE_SHORTCUT, info.itemType);
+ }
+
+ @Test
+ public void checkItemPlacement_wrongWorkspaceScreen() {
+ ArrayList<Long> workspaceScreens = new ArrayList<>(Arrays.asList(1L, 3L));
+ mIDP.numRows = 4;
+ mIDP.numColumns = 4;
+ mIDP.numHotseatIcons = 3;
+
+ // Item on unknown screen are not placed
+ assertFalse(mLoaderCursor.checkItemPlacement(
+ newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 4L), workspaceScreens));
+ assertFalse(mLoaderCursor.checkItemPlacement(
+ newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 5L), workspaceScreens));
+ assertFalse(mLoaderCursor.checkItemPlacement(
+ newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2L), workspaceScreens));
+
+ assertTrue(mLoaderCursor.checkItemPlacement(
+ newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens));
+ assertTrue(mLoaderCursor.checkItemPlacement(
+ newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 3L), workspaceScreens));
+
+ }
+ @Test
+ public void checkItemPlacement_outsideBounds() {
+ ArrayList<Long> workspaceScreens = new ArrayList<>(Arrays.asList(1L, 2L));
+ mIDP.numRows = 4;
+ mIDP.numColumns = 4;
+ mIDP.numHotseatIcons = 3;
+
+ // Item outside screen bounds are not placed
+ assertFalse(mLoaderCursor.checkItemPlacement(
+ newItemInfo(4, 4, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens));
+ }
+
+ @Test
+ public void checkItemPlacement_overlappingItems() {
+ ArrayList<Long> workspaceScreens = new ArrayList<>(Arrays.asList(1L, 2L));
+ mIDP.numRows = 4;
+ mIDP.numColumns = 4;
+ mIDP.numHotseatIcons = 3;
+
+ // Overlapping items are not placed
+ assertTrue(mLoaderCursor.checkItemPlacement(
+ newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens));
+ assertFalse(mLoaderCursor.checkItemPlacement(
+ newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens));
+
+ assertTrue(mLoaderCursor.checkItemPlacement(
+ newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2L), workspaceScreens));
+ assertFalse(mLoaderCursor.checkItemPlacement(
+ newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2L), workspaceScreens));
+
+ assertTrue(mLoaderCursor.checkItemPlacement(
+ newItemInfo(1, 1, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens));
+ assertTrue(mLoaderCursor.checkItemPlacement(
+ newItemInfo(2, 2, 2, 2, CONTAINER_DESKTOP, 1L), workspaceScreens));
+
+ assertFalse(mLoaderCursor.checkItemPlacement(
+ newItemInfo(3, 2, 1, 2, CONTAINER_DESKTOP, 1L), workspaceScreens));
+ }
+
+ @Test
+ public void checkItemPlacement_hotseat() {
+ ArrayList<Long> workspaceScreens = new ArrayList<>();
+ mIDP.numRows = 4;
+ mIDP.numColumns = 4;
+ mIDP.numHotseatIcons = 3;
+
+ // Hotseat items are only placed based on screenId
+ assertTrue(mLoaderCursor.checkItemPlacement(
+ newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 1L), workspaceScreens));
+ assertTrue(mLoaderCursor.checkItemPlacement(
+ newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 2L), workspaceScreens));
+
+ assertFalse(mLoaderCursor.checkItemPlacement(
+ newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 3L), workspaceScreens));
+ }
+
+ private ItemInfo newItemInfo(int cellX, int cellY, int spanX, int spanY,
+ long container, long screenId) {
+ ItemInfo info = new ItemInfo();
+ info.cellX = cellX;
+ info.cellY = cellY;
+ info.spanX = spanX;
+ info.spanY = spanY;
+ info.container = container;
+ info.screenId = screenId;
+ return info;
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
new file mode 100644
index 0000000..d655562
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
@@ -0,0 +1,61 @@
+package com.android.launcher3.model;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+/**
+ * Tests for {@link PackageInstallStateChangedTask}
+ */
+public class PackageInstallStateChangedTaskTest extends BaseModelUpdateTaskTestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ initializeData("package_install_state_change_task_data");
+ }
+
+ private PackageInstallStateChangedTask newTask(String pkg, int progress) {
+ PackageInstallInfo installInfo = new PackageInstallInfo(pkg);
+ installInfo.progress = progress;
+ installInfo.state = PackageInstallerCompat.STATUS_INSTALLING;
+ return new PackageInstallStateChangedTask(installInfo);
+ }
+
+ public void testSessionUpdate_ignore_installed() throws Exception {
+ executeTaskForTest(newTask("app1", 30));
+
+ // No shortcuts were updated
+ verifyProgressUpdate(0);
+ }
+
+ public void testSessionUpdate_shortcuts_updated() throws Exception {
+ executeTaskForTest(newTask("app3", 30));
+
+ verifyProgressUpdate(30, 5L, 6L, 7L);
+ }
+
+ public void testSessionUpdate_widgets_updated() throws Exception {
+ executeTaskForTest(newTask("app4", 30));
+
+ verifyProgressUpdate(30, 8L, 9L);
+ }
+
+ private void verifyProgressUpdate(int progress, Long... idsUpdated) {
+ HashSet<Long> updates = new HashSet<>(Arrays.asList(idsUpdated));
+ for (ItemInfo info : bgDataModel.itemsIdMap) {
+ if (info instanceof ShortcutInfo) {
+ assertEquals(updates.contains(info.id) ? progress: 0,
+ ((ShortcutInfo) info).getInstallProgress());
+ } else {
+ assertEquals(updates.contains(info.id) ? progress: -1,
+ ((LauncherAppWidgetInfo) info).installProgress);
+ }
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/shortcuts/ShortcutFilterTest.java b/tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
similarity index 63%
rename from tests/src/com/android/launcher3/shortcuts/ShortcutFilterTest.java
rename to tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
index 05d0ffb..2ad9b35 100644
--- a/tests/src/com/android/launcher3/shortcuts/ShortcutFilterTest.java
+++ b/tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
@@ -14,11 +14,13 @@
* limitations under the License.
*/
-package com.android.launcher3.shortcuts;
+package com.android.launcher3.popup;
import android.content.pm.ShortcutInfo;
import android.support.test.runner.AndroidJUnit4;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -26,40 +28,64 @@
import java.util.Collections;
import java.util.List;
-import static com.android.launcher3.shortcuts.ShortcutFilter.MAX_SHORTCUTS;
-import static com.android.launcher3.shortcuts.ShortcutFilter.NUM_DYNAMIC;
+import static com.android.launcher3.popup.PopupPopulator.MAX_ITEMS;
+import static com.android.launcher3.popup.PopupPopulator.NUM_DYNAMIC;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
- * Tests the sorting and filtering of shortcuts in {@link ShortcutFilter}.
+ * Tests the sorting and filtering of shortcuts in {@link PopupPopulator}.
*/
@RunWith(AndroidJUnit4.class)
-public class ShortcutFilterTest {
+public class PopupPopulatorTest {
@Test
public void testSortAndFilterShortcuts() {
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 0), 3, 0);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(0, 3), 0, 3);
- filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 0), MAX_SHORTCUTS, 0);
- filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(0, 5), 0, MAX_SHORTCUTS);
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 0), MAX_ITEMS, 0);
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(0, 5), 0, MAX_ITEMS);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 3),
- MAX_SHORTCUTS - NUM_DYNAMIC, NUM_DYNAMIC);
+ MAX_ITEMS - NUM_DYNAMIC, NUM_DYNAMIC);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 5),
- MAX_SHORTCUTS - NUM_DYNAMIC, NUM_DYNAMIC);
- filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 1), MAX_SHORTCUTS - 1, 1);
- filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(1, 5), 1, MAX_SHORTCUTS - 1);
+ MAX_ITEMS - NUM_DYNAMIC, NUM_DYNAMIC);
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 1), MAX_ITEMS - 1, 1);
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(1, 5), 1, MAX_ITEMS - 1);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 3),
- MAX_SHORTCUTS - NUM_DYNAMIC, NUM_DYNAMIC);
+ MAX_ITEMS - NUM_DYNAMIC, NUM_DYNAMIC);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 5),
- MAX_SHORTCUTS - NUM_DYNAMIC, NUM_DYNAMIC);
+ MAX_ITEMS - NUM_DYNAMIC, NUM_DYNAMIC);
+ }
+
+ @Test
+ public void testDeDupeShortcutId() {
+ // Successfully remove one of the shortcuts
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 0), 2, 0, generateId(true, 1));
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(0, 3), 0, 2, generateId(false, 1));
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(2, 2), 2, 1, generateId(false, 1));
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(2, 2), 1, 2, generateId(true, 1));
+ // Successfully keep all shortcuts when id doesn't exist
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 0), 3, 0, generateId(false, 1));
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 0), 3, 0, generateId(true, 4));
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(2, 2), 2, 2, generateId(false, 4));
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(2, 2), 2, 2, generateId(true, 4));
+ }
+
+ private String generateId(boolean isStatic, int rank) {
+ return (isStatic ? "static" : "dynamic") + rank;
}
private void filterShortcutsAndAssertNumStaticAndDynamic(
List<ShortcutInfoCompat> shortcuts, int expectedStatic, int expectedDynamic) {
+ filterShortcutsAndAssertNumStaticAndDynamic(shortcuts, expectedStatic, expectedDynamic, null);
+ }
+
+ private void filterShortcutsAndAssertNumStaticAndDynamic(List<ShortcutInfoCompat> shortcuts,
+ int expectedStatic, int expectedDynamic, String shortcutIdToRemove) {
Collections.shuffle(shortcuts);
- List<ShortcutInfoCompat> filteredShortcuts = ShortcutFilter.sortAndFilterShortcuts(shortcuts);
+ List<ShortcutInfoCompat> filteredShortcuts = PopupPopulator.sortAndFilterShortcuts(
+ shortcuts, shortcutIdToRemove);
assertIsSorted(filteredShortcuts);
int numStatic = 0;
@@ -110,6 +136,7 @@
private class Shortcut extends ShortcutInfoCompat {
private boolean mIsStatic;
private int mRank;
+ private String mId;
public Shortcut(ShortcutInfo shortcutInfo) {
super(shortcutInfo);
@@ -119,6 +146,7 @@
this(null);
mIsStatic = isStatic;
mRank = rank;
+ mId = generateId(isStatic, rank);
}
@Override
@@ -135,5 +163,10 @@
public int getRank() {
return mRank;
}
+
+ @Override
+ public String getId() {
+ return mId;
+ }
}
}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/testcomponent/AppWidgetNoConfig.java b/tests/src/com/android/launcher3/testcomponent/AppWidgetNoConfig.java
new file mode 100644
index 0000000..9b320d8
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/AppWidgetNoConfig.java
@@ -0,0 +1,26 @@
+/*
+ * 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.testcomponent;
+
+import android.appwidget.AppWidgetProvider;
+
+/**
+ * A simple app widget without any configuration screen.
+ */
+public class AppWidgetNoConfig extends AppWidgetProvider {
+
+
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/AppWidgetWithConfig.java b/tests/src/com/android/launcher3/testcomponent/AppWidgetWithConfig.java
new file mode 100644
index 0000000..033e6e6
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/AppWidgetWithConfig.java
@@ -0,0 +1,23 @@
+/*
+ * 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.testcomponent;
+
+/**
+ * A simple app widget with configuration sceen.
+ */
+public class AppWidgetWithConfig extends AppWidgetNoConfig {
+
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java b/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java
new file mode 100644
index 0000000..904590c
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java
@@ -0,0 +1,126 @@
+/*
+ * 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.testcomponent;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/**
+ * Base activity with utility methods to help automate testing.
+ */
+public class BaseTestingActivity extends Activity implements View.OnClickListener {
+
+ public static final String SUFFIX_COMMAND = "-command";
+ public static final String EXTRA_METHOD = "method";
+ public static final String EXTRA_PARAM = "param_";
+
+ private static final int MARGIN_DP = 20;
+
+ private final String mAction = this.getClass().getName();
+
+ private LinearLayout mView;
+ private int mMargin;
+
+ private final BroadcastReceiver mCommandReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ handleCommand(intent);
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mMargin = Math.round(TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP, MARGIN_DP, getResources().getDisplayMetrics()));
+ mView = new LinearLayout(this);
+ mView.setPadding(mMargin, mMargin, mMargin, mMargin);
+ mView.setOrientation(LinearLayout.VERTICAL);
+ setContentView(mView);
+
+ registerReceiver(mCommandReceiver, new IntentFilter(mAction + SUFFIX_COMMAND));
+ }
+
+ protected void addButton(String title, String method) {
+ Button button = new Button(this);
+ button.setText(title);
+ button.setTag(method);
+ button.setOnClickListener(this);
+
+ LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ lp.bottomMargin = mMargin;
+ mView.addView(button, lp);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ sendBroadcast(new Intent(mAction).putExtra(Intent.EXTRA_INTENT, getIntent()));
+ }
+
+ @Override
+ protected void onDestroy() {
+ unregisterReceiver(mCommandReceiver);
+ super.onDestroy();
+ }
+
+ @Override
+ public void onClick(View view) {
+ handleCommand(new Intent().putExtra(EXTRA_METHOD, (String) view.getTag()));
+ }
+
+ private void handleCommand(Intent cmd) {
+ String methodName = cmd.getStringExtra(EXTRA_METHOD);
+ try {
+ Method method = null;
+ for (Method m : this.getClass().getDeclaredMethods()) {
+ if (methodName.equals(m.getName()) &&
+ !Modifier.isStatic(m.getModifiers()) &&
+ Modifier.isPublic(m.getModifiers())) {
+ method = m;
+ break;
+ }
+ }
+ Object[] args = new Object[method.getParameterTypes().length];
+ Bundle extras = cmd.getExtras();
+ for (int i = 0; i < args.length; i++) {
+ args[i] = extras.get(EXTRA_PARAM + i);
+ }
+ method.invoke(this, args);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static Intent getCommandIntent(Class<?> clazz, String method) {
+ return new Intent(clazz.getName() + SUFFIX_COMMAND)
+ .putExtra(EXTRA_METHOD, method);
+ }
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/RequestPinItemActivity.java b/tests/src/com/android/launcher3/testcomponent/RequestPinItemActivity.java
new file mode 100644
index 0000000..8580992
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/RequestPinItemActivity.java
@@ -0,0 +1,105 @@
+/*
+ * 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.testcomponent;
+
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
+import android.content.IntentSender;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.widget.RemoteViews;
+
+/**
+ * Sample activity to request pinning an item.
+ */
+@TargetApi(26)
+public class RequestPinItemActivity extends BaseTestingActivity {
+
+ private PendingIntent mCallback = null;
+ private String mShortcutId = "test-id";
+ private int mRemoteViewColor = Color.TRANSPARENT;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ addButton("Pin Shortcut", "pinShortcut");
+ addButton("Pin Widget without config ", "pinWidgetNoConfig");
+ addButton("Pin Widget with config", "pinWidgetWithConfig");
+ }
+
+ public void setCallback(PendingIntent callback) {
+ mCallback = callback;
+ }
+
+ public void setRemoteViewColor(int color) {
+ mRemoteViewColor = color;
+ }
+
+ public void setShortcutId(String id) {
+ mShortcutId = id;
+ }
+
+ public void pinShortcut() {
+ ShortcutManager sm = getSystemService(ShortcutManager.class);
+
+ // Generate icon
+ int r = sm.getIconMaxWidth() / 2;
+ Bitmap icon = Bitmap.createBitmap(r * 2, r * 2, Bitmap.Config.ARGB_8888);
+ Paint p = new Paint();
+ p.setColor(Color.RED);
+ new Canvas(icon).drawCircle(r, r, r, p);
+
+ ShortcutInfo info = new ShortcutInfo.Builder(this, mShortcutId)
+ .setIntent(getPackageManager().getLaunchIntentForPackage(getPackageName()))
+ .setIcon(Icon.createWithBitmap(icon))
+ .setShortLabel("Test shortcut")
+ .build();
+
+ IntentSender callback = mCallback == null ? null : mCallback.getIntentSender();
+ sm.requestPinShortcut(info, callback);
+ }
+
+ public void pinWidgetNoConfig() {
+ requestWidget(new ComponentName(this, AppWidgetNoConfig.class));
+ }
+
+ public void pinWidgetWithConfig() {
+ requestWidget(new ComponentName(this, AppWidgetWithConfig.class));
+ }
+
+ private void requestWidget(ComponentName cn) {
+ Bundle extras = null;
+ if (mRemoteViewColor != Color.TRANSPARENT) {
+ int layoutId = getResources().getIdentifier(
+ "test_layout_appwidget_view", "layout", getPackageName());
+ RemoteViews views = new RemoteViews(getPackageName(), layoutId);
+ views.setInt(android.R.id.icon, "setBackgroundColor", mRemoteViewColor);
+ extras = new Bundle();
+ extras.putParcelable(AppWidgetManager.EXTRA_APPWIDGET_PREVIEW, views);
+ }
+
+ AppWidgetManager.getInstance(this).requestPinAppWidget(cn, extras, mCallback);
+ }
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/WidgetConfigActivity.java b/tests/src/com/android/launcher3/testcomponent/WidgetConfigActivity.java
new file mode 100644
index 0000000..d76ad04
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/WidgetConfigActivity.java
@@ -0,0 +1,44 @@
+/*
+ * 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.testcomponent;
+
+import android.os.Bundle;
+
+/**
+ * Simple activity for widget configuration
+ */
+public class WidgetConfigActivity extends BaseTestingActivity {
+
+ public static final String SUFFIX_FINISH = "-finish";
+ public static final String EXTRA_CODE = "code";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addButton("Cancel", "clickCancel");
+ addButton("OK", "clickOK");
+ }
+
+ public void clickCancel() {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+
+ public void clickOK() {
+ setResult(RESULT_OK);
+ finish();
+ }
+}
diff --git a/tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java b/tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java
index abe6b95..0ced7cf 100644
--- a/tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java
+++ b/tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java
@@ -1,13 +1,13 @@
package com.android.launcher3.ui;
+import android.content.pm.LauncherActivityInfo;
+import android.os.Process;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
import android.test.suitebuilder.annotation.LargeTest;
-import com.android.launcher3.compat.LauncherActivityInfoCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.util.Condition;
import com.android.launcher3.util.Wait;
@@ -17,14 +17,14 @@
@LargeTest
public class AllAppsAppLaunchTest extends LauncherInstrumentationTestCase {
- private LauncherActivityInfoCompat mSettingsApp;
+ private LauncherActivityInfo mSettingsApp;
@Override
protected void setUp() throws Exception {
super.setUp();
mSettingsApp = LauncherAppsCompat.getInstance(mTargetContext)
- .getActivityList("com.android.settings", UserHandleCompat.myUserHandle()).get(0);
+ .getActivityList("com.android.settings", Process.myUserHandle()).get(0);
}
public void testAppLauncher_portrait() throws Exception {
diff --git a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
index 56fc90a..9361750 100644
--- a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
+++ b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
@@ -1,13 +1,13 @@
package com.android.launcher3.ui;
+import android.content.pm.LauncherActivityInfo;
+import android.os.Process;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
import android.test.suitebuilder.annotation.LargeTest;
-import com.android.launcher3.compat.LauncherActivityInfoCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.util.Condition;
import com.android.launcher3.util.Wait;
@@ -17,14 +17,15 @@
@LargeTest
public class AllAppsIconToHomeTest extends LauncherInstrumentationTestCase {
- private LauncherActivityInfoCompat mSettingsApp;
+ private LauncherActivityInfo mSettingsApp;
@Override
protected void setUp() throws Exception {
super.setUp();
+ setDefaultLauncher();
mSettingsApp = LauncherAppsCompat.getInstance(mTargetContext)
- .getActivityList("com.android.settings", UserHandleCompat.myUserHandle()).get(0);
+ .getActivityList("com.android.settings", Process.myUserHandle()).get(0);
}
public void testDragIcon_portrait() throws Throwable {
@@ -47,7 +48,7 @@
// Drag icon to homescreen.
UiObject2 icon = scrollAndFind(appsContainer, By.text(mSettingsApp.getLabel().toString()));
- dragToWorkspace(icon);
+ dragToWorkspace(icon, true);
// Verify that the icon works on homescreen.
mDevice.findObject(By.text(mSettingsApp.getLabel().toString())).click();
diff --git a/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java b/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java
index e858d17..47b43f5 100644
--- a/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java
+++ b/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java
@@ -1,13 +1,31 @@
+/*
+ * 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.ui;
-import android.app.SearchManager;
-import android.appwidget.AppWidgetProviderInfo;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.graphics.Point;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.support.test.uiautomator.By;
@@ -19,30 +37,39 @@
import android.test.InstrumentationTestCase;
import android.view.MotionEvent;
-import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.LauncherClings;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.testcomponent.AppWidgetNoConfig;
+import com.android.launcher3.testcomponent.AppWidgetWithConfig;
import com.android.launcher3.util.ManagedProfileHeuristic;
+import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
import java.util.Locale;
import java.util.concurrent.Callable;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* Base class for all instrumentation tests providing various utility methods.
*/
public class LauncherInstrumentationTestCase extends InstrumentationTestCase {
+ public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
+ public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5;
+
public static final long DEFAULT_UI_TIMEOUT = 3000;
+ public static final long DEFAULT_WORKER_TIMEOUT_SECS = 5;
protected UiDevice mDevice;
protected Context mTargetContext;
@@ -74,11 +101,14 @@
* Starts the launcher activity in the target package and returns the Launcher instance.
*/
protected Launcher startLauncher() {
- Intent homeIntent = new Intent(Intent.ACTION_MAIN)
+ return (Launcher) getInstrumentation().startActivitySync(getHomeIntent());
+ }
+
+ protected Intent getHomeIntent() {
+ return new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME)
.setPackage(mTargetPackage)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- return (Launcher) getInstrumentation().startActivitySync(homeIntent);
}
/**
@@ -89,16 +119,31 @@
if (mTargetContext.getPackageManager().checkPermission(
mTargetPackage, android.Manifest.permission.BIND_APPWIDGET)
!= PackageManager.PERMISSION_GRANTED) {
- ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation().executeShellCommand(
- "appwidget grantbind --package " + mTargetPackage);
- // Read the input stream fully.
- FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
- while (fis.read() != -1);
- fis.close();
+ runShellCommand("appwidget grantbind --package " + mTargetPackage);
}
}
/**
+ * Sets the target launcher as default launcher.
+ */
+ protected void setDefaultLauncher() throws IOException {
+ ActivityInfo launcher = mTargetContext.getPackageManager()
+ .queryIntentActivities(getHomeIntent(), 0).get(0).activityInfo;
+ runShellCommand("cmd package set-home-activity " +
+ new ComponentName(launcher.packageName, launcher.name).flattenToString());
+ }
+
+ protected void runShellCommand(String command) throws IOException {
+ ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
+ .executeShellCommand(command);
+
+ // Read the input stream fully.
+ FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+ while (fis.read() != -1);
+ fis.close();
+ }
+
+ /**
* Opens all apps and returns the recycler view
*/
protected UiObject2 openAllApps() {
@@ -141,29 +186,52 @@
/**
* Drags an icon to the center of homescreen.
*/
- protected void dragToWorkspace(UiObject2 icon) {
+ protected void dragToWorkspace(UiObject2 icon, boolean expectedToShowShortcuts) {
Point center = icon.getVisibleCenter();
// Action Down
sendPointer(MotionEvent.ACTION_DOWN, center);
- // Wait until "Remove/Delete target is visible
+ UiObject2 dragLayer = findViewById(R.id.drag_layer);
+
+ if (expectedToShowShortcuts) {
+ // Make sure shortcuts show up, and then move a bit to hide them.
+ assertNotNull(findViewById(R.id.deep_shortcuts_container));
+
+ Point moveLocation = new Point(center);
+ int distanceToMove = mTargetContext.getResources().getDimensionPixelSize(
+ R.dimen.deep_shortcuts_start_drag_threshold) + 50;
+ if (moveLocation.y - distanceToMove >= dragLayer.getVisibleBounds().top) {
+ moveLocation.y -= distanceToMove;
+ } else {
+ moveLocation.y += distanceToMove;
+ }
+ movePointer(center, moveLocation);
+
+ assertNull(findViewById(R.id.deep_shortcuts_container));
+ }
+
+ // Wait until Remove/Delete target is visible
assertNotNull(findViewById(R.id.delete_target_text));
- Point moveLocation = findViewById(R.id.drag_layer).getVisibleCenter();
+ Point moveLocation = dragLayer.getVisibleCenter();
// Move to center
- while(!moveLocation.equals(center)) {
- center.x = getNextMoveValue(moveLocation.x, center.x);
- center.y = getNextMoveValue(moveLocation.y, center.y);
- sendPointer(MotionEvent.ACTION_MOVE, center);
- }
+ movePointer(center, moveLocation);
sendPointer(MotionEvent.ACTION_UP, center);
// Wait until remove target is gone.
mDevice.wait(Until.gone(getSelectorForId(R.id.delete_target_text)), DEFAULT_UI_TIMEOUT);
}
+ private void movePointer(Point from, Point to) {
+ while(!from.equals(to)) {
+ from.x = getNextMoveValue(to.x, from.x);
+ from.y = getNextMoveValue(to.y, from.y);
+ sendPointer(MotionEvent.ACTION_MOVE, from);
+ }
+ }
+
private int getNextMoveValue(int targetValue, int oldValue) {
if (targetValue - oldValue > 10) {
return oldValue + 10;
@@ -174,7 +242,7 @@
}
}
- private void sendPointer(int action, Point point) {
+ protected void sendPointer(int action, Point point) {
MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(), action, point.x, point.y, 0);
getInstrumentation().sendPointerSync(event);
@@ -189,34 +257,32 @@
LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
- LauncherClings.markFirstRunClingDismissed(mTargetContext);
- ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext);
+ resetLoaderState();
+ }
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- // Reset the loader state
- LauncherAppState.getInstance().getModel().resetLoadedState(true, true);
- }
- });
+ protected void resetLoaderState() {
+ try {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext);
+ LauncherAppState.getInstance(mTargetContext).getModel().forceReload();
+ }
+ });
+ } catch (Throwable t) {
+ throw new IllegalArgumentException(t);
+ }
}
/**
* Runs the callback on the UI thread and returns the result.
*/
protected <T> T getOnUiThread(final Callable<T> callback) {
- final AtomicReference<T> result = new AtomicReference<>(null);
try {
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- try {
- result.set(callback.call());
- } catch (Exception e) { }
- }
- });
- } catch (Throwable t) { }
- return result.get();
+ return new MainThreadExecutor().submit(callback).get();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
}
/**
@@ -224,36 +290,14 @@
* @param hasConfigureScreen if true, a provider with a config screen is returned.
*/
protected LauncherAppWidgetProviderInfo findWidgetProvider(final boolean hasConfigureScreen) {
- LauncherAppWidgetProviderInfo info = getOnUiThread(new Callable<LauncherAppWidgetProviderInfo>() {
+ LauncherAppWidgetProviderInfo info =
+ getOnUiThread(new Callable<LauncherAppWidgetProviderInfo>() {
@Override
public LauncherAppWidgetProviderInfo call() throws Exception {
- InvariantDeviceProfile idv =
- LauncherAppState.getInstance().getInvariantDeviceProfile();
-
- ComponentName searchComponent = ((SearchManager) mTargetContext
- .getSystemService(Context.SEARCH_SERVICE)).getGlobalSearchActivity();
- String searchPackage = searchComponent == null
- ? null : searchComponent.getPackageName();
-
- for (AppWidgetProviderInfo info :
- AppWidgetManagerCompat.getInstance(mTargetContext).getAllProviders()) {
- if ((info.configure != null) ^ hasConfigureScreen) {
- continue;
- }
- // Exclude the widgets in search package, as Launcher already binds them in
- // QSB, so they can cause conflicts.
- if (info.provider.getPackageName().equals(searchPackage)) {
- continue;
- }
- LauncherAppWidgetProviderInfo widgetInfo = LauncherAppWidgetProviderInfo
- .fromProviderInfo(mTargetContext, info);
- if (widgetInfo.minSpanX >= idv.numColumns
- || widgetInfo.minSpanY >= idv.numRows) {
- continue;
- }
- return widgetInfo;
- }
- return null;
+ ComponentName cn = new ComponentName(getInstrumentation().getContext(),
+ hasConfigureScreen ? AppWidgetWithConfig.class : AppWidgetNoConfig.class);
+ return AppWidgetManagerCompat.getInstance(mTargetContext)
+ .findProvider(cn, Process.myUserHandle());
}
});
if (info == null) {
@@ -270,4 +314,35 @@
String name = mTargetContext.getResources().getResourceEntryName(id);
return By.res(mTargetPackage, name);
}
+
+
+ /**
+ * Broadcast receiver which blocks until the result is received.
+ */
+ public class BlockingBroadcastReceiver extends BroadcastReceiver {
+
+ private final CountDownLatch latch = new CountDownLatch(1);
+ private Intent mIntent;
+
+ public BlockingBroadcastReceiver(String action) {
+ mTargetContext.registerReceiver(this, new IntentFilter(action));
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mIntent = intent;
+ latch.countDown();
+ }
+
+ public Intent blockingGetIntent() throws InterruptedException {
+ latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS);
+ mTargetContext.unregisterReceiver(this);
+ return mIntent;
+ }
+
+ public Intent blockingGetExtraIntent() throws InterruptedException {
+ Intent intent = blockingGetIntent();
+ return intent == null ? null : (Intent) intent.getParcelableExtra(Intent.EXTRA_INTENT);
+ }
+ }
}
diff --git a/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java b/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java
new file mode 100644
index 0000000..3a0b613
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java
@@ -0,0 +1,69 @@
+package com.android.launcher3.ui;
+
+import android.content.pm.LauncherActivityInfo;
+import android.graphics.Point;
+import android.os.Process;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.MotionEvent;
+
+import com.android.launcher3.R;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.util.Condition;
+import com.android.launcher3.util.Wait;
+
+/**
+ * Test for verifying that shortcuts are shown and can be launched after long pressing an app
+ */
+@LargeTest
+public class ShortcutsLaunchTest extends LauncherInstrumentationTestCase {
+
+ private LauncherActivityInfo mSettingsApp;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ setDefaultLauncher();
+
+ mSettingsApp = LauncherAppsCompat.getInstance(mTargetContext)
+ .getActivityList("com.android.settings", Process.myUserHandle()).get(0);
+ }
+
+ public void testAppLauncher_portrait() throws Exception {
+ lockRotation(true);
+ performTest();
+ }
+
+ public void testAppLauncher_landscape() throws Exception {
+ lockRotation(false);
+ performTest();
+ }
+
+ private void performTest() throws Exception {
+ startLauncher();
+
+ // Open all apps and wait for load complete
+ final UiObject2 appsContainer = openAllApps();
+ assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT));
+
+ // Find settings app and verify shortcuts appear when long pressed
+ UiObject2 icon = scrollAndFind(appsContainer, By.text(mSettingsApp.getLabel().toString()));
+ // Press icon center until shortcuts appear
+ Point iconCenter = icon.getVisibleCenter();
+ sendPointer(MotionEvent.ACTION_DOWN, iconCenter);
+ UiObject2 deepShortcutsContainer = findViewById(R.id.deep_shortcuts_container);
+ assertNotNull(deepShortcutsContainer);
+ sendPointer(MotionEvent.ACTION_UP, iconCenter);
+
+ // Verify that launching a shortcut opens a page with the same text
+ assertTrue(deepShortcutsContainer.getChildCount() > 0);
+ UiObject2 shortcut = deepShortcutsContainer.getChildren().get(0)
+ .findObject(getSelectorForId(R.id.bubble_text));
+ shortcut.click();
+ assertTrue(mDevice.wait(Until.hasObject(By.pkg(
+ mSettingsApp.getComponentName().getPackageName())
+ .text(shortcut.getText())), DEFAULT_UI_TIMEOUT));
+ }
+}
diff --git a/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java b/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java
new file mode 100644
index 0000000..5d86d1e
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java
@@ -0,0 +1,75 @@
+package com.android.launcher3.ui;
+
+import android.content.pm.LauncherActivityInfo;
+import android.graphics.Point;
+import android.os.Process;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.MotionEvent;
+
+import com.android.launcher3.R;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.util.Condition;
+import com.android.launcher3.util.Wait;
+
+/**
+ * Test for dragging a deep shortcut to the home screen.
+ */
+@LargeTest
+public class ShortcutsToHomeTest extends LauncherInstrumentationTestCase {
+
+ private LauncherActivityInfo mSettingsApp;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ setDefaultLauncher();
+
+ mSettingsApp = LauncherAppsCompat.getInstance(mTargetContext)
+ .getActivityList("com.android.settings", Process.myUserHandle()).get(0);
+ }
+
+ public void testDragIcon_portrait() throws Throwable {
+ lockRotation(true);
+ performTest();
+ }
+
+ public void testDragIcon_landscape() throws Throwable {
+ lockRotation(false);
+ performTest();
+ }
+
+ private void performTest() throws Throwable {
+ clearHomescreen();
+ startLauncher();
+
+ // Open all apps and wait for load complete.
+ final UiObject2 appsContainer = openAllApps();
+ assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT));
+
+ // Find the app and long press it to show shortcuts.
+ UiObject2 icon = scrollAndFind(appsContainer, By.text(mSettingsApp.getLabel().toString()));
+ // Press icon center until shortcuts appear
+ Point iconCenter = icon.getVisibleCenter();
+ sendPointer(MotionEvent.ACTION_DOWN, iconCenter);
+ UiObject2 deepShortcutsContainer = findViewById(R.id.deep_shortcuts_container);
+ assertNotNull(deepShortcutsContainer);
+ sendPointer(MotionEvent.ACTION_UP, iconCenter);
+
+ // Drag the first shortcut to the home screen.
+ assertTrue(deepShortcutsContainer.getChildCount() > 0);
+ UiObject2 shortcut = deepShortcutsContainer.getChildren().get(0)
+ .findObject(getSelectorForId(R.id.bubble_text));
+ String shortcutName = shortcut.getText();
+ dragToWorkspace(shortcut, false);
+
+ // Verify that the shortcut works on home screen
+ // (the app opens and has the same text as the shortcut).
+ mDevice.findObject(By.text(shortcutName)).click();
+ assertTrue(mDevice.wait(Until.hasObject(By.pkg(
+ mSettingsApp.getComponentName().getPackageName())
+ .text(shortcutName)), DEFAULT_UI_TIMEOUT));
+ }
+}
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
new file mode 100644
index 0000000..0b4e34f
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.launcher3.ui.widget;
+
+import android.app.Activity;
+import android.app.Application;
+import android.appwidget.AppWidgetManager;
+import android.content.Intent;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.View;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.testcomponent.WidgetConfigActivity;
+import com.android.launcher3.ui.LauncherInstrumentationTestCase;
+import com.android.launcher3.util.Condition;
+import com.android.launcher3.util.SimpleActivityMonitor;
+import com.android.launcher3.util.Wait;
+import com.android.launcher3.widget.WidgetCell;
+
+import java.util.concurrent.Callable;
+
+/**
+ * Test to verify widget configuration is properly shown.
+ */
+@LargeTest
+public class AddConfigWidgetTest extends LauncherInstrumentationTestCase {
+
+ private LauncherAppWidgetProviderInfo mWidgetInfo;
+ private SimpleActivityMonitor mActivityMonitor;
+ private MainThreadExecutor mMainThreadExecutor;
+ private AppWidgetManager mAppWidgetManager;
+
+ private int mWidgetId;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mWidgetInfo = findWidgetProvider(true /* hasConfigureScreen */);
+ mActivityMonitor = new SimpleActivityMonitor();
+ ((Application) getInstrumentation().getTargetContext().getApplicationContext())
+ .registerActivityLifecycleCallbacks(mActivityMonitor);
+ mMainThreadExecutor = new MainThreadExecutor();
+ mAppWidgetManager = AppWidgetManager.getInstance(mTargetContext);
+
+ grantWidgetPermission();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ ((Application) getInstrumentation().getTargetContext().getApplicationContext())
+ .unregisterActivityLifecycleCallbacks(mActivityMonitor);
+ super.tearDown();
+ }
+
+ public void testWidgetConfig() throws Throwable {
+ runTest(false, true);
+ }
+
+ public void testWidgetConfig_rotate() throws Throwable {
+ runTest(true, true);
+ }
+
+ public void testConfigCancelled() throws Throwable {
+ runTest(false, false);
+ }
+
+ public void testConfigCancelled_rotate() throws Throwable {
+ runTest(true, false);
+ }
+
+ /**
+ * @param rotateConfig should the config screen be rotated
+ * @param acceptConfig accept the config activity
+ */
+ private void runTest(boolean rotateConfig, boolean acceptConfig) throws Throwable {
+ lockRotation(true);
+
+ clearHomescreen();
+ startLauncher();
+
+ // Open widget tray and wait for load complete.
+ final UiObject2 widgetContainer = openWidgetsTray();
+ assertTrue(Wait.atMost(Condition.minChildCount(widgetContainer, 2), DEFAULT_UI_TIMEOUT));
+
+ // Drag widget to homescreen
+ WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor();
+ UiObject2 widget = scrollAndFind(widgetContainer, By.clazz(WidgetCell.class)
+ .hasDescendant(By.text(mWidgetInfo.getLabel(mTargetContext.getPackageManager()))));
+ dragToWorkspace(widget, false);
+ // Widget id for which the config activity was opened
+ mWidgetId = monitor.getWidgetId();
+
+ if (rotateConfig) {
+ // Rotate the screen and verify that the config activity is recreated
+ monitor = new WidgetConfigStartupMonitor();
+ lockRotation(false);
+ assertEquals(mWidgetId, monitor.getWidgetId());
+ }
+
+ // Verify that the widget id is valid and bound
+ assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId));
+
+ setResult(acceptConfig);
+ if (acceptConfig) {
+ assertTrue(Wait.atMost(new WidgetSearchCondition(), DEFAULT_ACTIVITY_TIMEOUT));
+ assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId));
+ } else {
+ // Verify that the widget id is deleted.
+ assertTrue(Wait.atMost(new Condition() {
+ @Override
+ public boolean isTrue() throws Throwable {
+ return mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null;
+ }
+ }, DEFAULT_ACTIVITY_TIMEOUT));
+ }
+ }
+
+ private void setResult(boolean success) {
+
+ getInstrumentation().getTargetContext().sendBroadcast(
+ WidgetConfigActivity.getCommandIntent(WidgetConfigActivity.class,
+ success ? "clickOK" : "clickCancel"));
+ }
+
+ /**
+ * Condition for searching widget id
+ */
+ private class WidgetSearchCondition extends Condition
+ implements Callable<Boolean>, Workspace.ItemOperator {
+
+ @Override
+ public boolean isTrue() throws Throwable {
+ return mMainThreadExecutor.submit(this).get();
+ }
+
+ @Override
+ public boolean evaluate(ItemInfo info, View view) {
+ return info instanceof LauncherAppWidgetInfo &&
+ ((LauncherAppWidgetInfo) info).providerName.equals(mWidgetInfo.provider) &&
+ ((LauncherAppWidgetInfo) info).appWidgetId == mWidgetId;
+ }
+
+ @Override
+ public Boolean call() throws Exception {
+ // Find the resumed launcher
+ Launcher launcher = null;
+ for (Activity a : mActivityMonitor.resumed) {
+ if (a instanceof Launcher) {
+ launcher = (Launcher) a;
+ }
+ }
+ if (launcher == null) {
+ return false;
+ }
+ return launcher.getWorkspace().getFirstMatch(this) != null;
+ }
+ }
+
+ /**
+ * Broadcast receiver for receiving widget config activity status.
+ */
+ private class WidgetConfigStartupMonitor extends BlockingBroadcastReceiver {
+
+ public WidgetConfigStartupMonitor() {
+ super(WidgetConfigActivity.class.getName());
+ }
+
+ public int getWidgetId() throws InterruptedException {
+ Intent intent = blockingGetExtraIntent();
+ assertNotNull(intent);
+ assertEquals(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE, intent.getAction());
+ int widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
+ LauncherAppWidgetInfo.NO_ID);
+ assertNotSame(widgetId, LauncherAppWidgetInfo.NO_ID);
+ return widgetId;
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/ui/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
similarity index 72%
rename from tests/src/com/android/launcher3/ui/AddWidgetTest.java
rename to tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index a0ca60c..3c92c57 100644
--- a/tests/src/com/android/launcher3/ui/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -1,16 +1,31 @@
-package com.android.launcher3.ui;
+/*
+ * 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.ui.widget;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiObject2;
import android.test.suitebuilder.annotation.LargeTest;
import android.view.View;
-import com.android.launcher3.CellLayout;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.Workspace.ItemOperator;
+import com.android.launcher3.ui.LauncherInstrumentationTestCase;
import com.android.launcher3.util.Condition;
import com.android.launcher3.util.Wait;
import com.android.launcher3.widget.WidgetCell;
@@ -26,6 +41,7 @@
@Override
protected void setUp() throws Exception {
super.setUp();
+ grantWidgetPermission();
widgetInfo = findWidgetProvider(false /* hasConfigureScreen */);
}
@@ -51,7 +67,7 @@
// Drag widget to homescreen
UiObject2 widget = scrollAndFind(widgetContainer, By.clazz(WidgetCell.class)
.hasDescendant(By.text(widgetInfo.getLabel(mTargetContext.getPackageManager()))));
- dragToWorkspace(widget);
+ dragToWorkspace(widget, false);
assertNotNull(launcher.getWorkspace().getFirstMatch(new ItemOperator() {
@Override
diff --git a/tests/src/com/android/launcher3/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
similarity index 88%
rename from tests/src/com/android/launcher3/BindWidgetTest.java
rename to tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index 5c5069f..df2b662 100644
--- a/tests/src/com/android/launcher3/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -1,6 +1,20 @@
-package com.android.launcher3;
+/*
+ * 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.ui.widget;
-import android.annotation.TargetApi;
import android.appwidget.AppWidgetHost;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -9,15 +23,23 @@
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.database.Cursor;
-import android.os.Build;
import android.os.Bundle;
import android.support.test.uiautomator.UiSelector;
import android.test.suitebuilder.annotation.LargeTest;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppWidgetHostView;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.PendingAppWidgetHostView;
+import com.android.launcher3.Workspace;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.ui.LauncherInstrumentationTestCase;
-import com.android.launcher3.util.ManagedProfileHeuristic;
+import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.util.LooperExecuter;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.WidgetHostViewLoader;
@@ -32,7 +54,6 @@
* Note running these tests will clear the workspace on the device.
*/
@LargeTest
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class BindWidgetTest extends LauncherInstrumentationTestCase {
private ContentResolver mResolver;
@@ -218,28 +239,16 @@
mResolver.insert(LauncherSettings.WorkspaceScreens.CONTENT_URI, v);
// Insert the item
- v = new ContentValues();
+ ContentWriter writer = new ContentWriter(mTargetContext);
item.id = LauncherSettings.Settings.call(
mResolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
.getLong(LauncherSettings.Settings.EXTRA_VALUE);
item.screenId = screenId;
- item.onAddToDatabase(mTargetContext, v);
- v.put(LauncherSettings.Favorites._ID, item.id);
- mResolver.insert(LauncherSettings.Favorites.CONTENT_URI, v);
+ item.onAddToDatabase(writer);
+ writer.put(LauncherSettings.Favorites._ID, item.id);
+ mResolver.insert(LauncherSettings.Favorites.CONTENT_URI, writer.getValues(mTargetContext));
+ resetLoaderState();
- // Reset loader
- try {
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- LauncherClings.markFirstRunClingDismissed(mTargetContext);
- ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext);
- LauncherAppState.getInstance().getModel().resetLoadedState(true, true);
- }
- });
- } catch (Throwable t) {
- throw new IllegalArgumentException(t);
- }
// Launch the home activity
startLauncher();
// Verify UI
@@ -264,13 +273,13 @@
item.spanY = info.minSpanY;
item.minSpanX = info.minSpanX;
item.minSpanY = info.minSpanY;
- item.user = mWidgetManager.getUser(info);
+ item.user = info.getUser();
item.cellX = 0;
item.cellY = 1;
item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
if (bindWidget) {
- PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(mTargetContext, info);
+ PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(info);
pendingInfo.spanX = item.spanX;
pendingInfo.spanY = item.spanY;
pendingInfo.minSpanX = item.minSpanX;
@@ -330,14 +339,11 @@
/**
* Blocks the current thread until all the jobs in the main worker thread are complete.
*/
- private void waitUntilLoaderIdle() throws InterruptedException {
- final CountDownLatch latch = new CountDownLatch(1);
- LauncherModel.sWorker.post(new Runnable() {
- @Override
- public void run() {
- latch.countDown();
- }
- });
- assertTrue(latch.await(5, TimeUnit.SECONDS));
+ private void waitUntilLoaderIdle() throws Exception {
+ new LooperExecuter(LauncherModel.getWorkerLooper())
+ .submit(new Runnable() {
+ @Override
+ public void run() { }
+ }).get(DEFAULT_WORKER_TIMEOUT_SECS, TimeUnit.SECONDS);
}
}
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
new file mode 100644
index 0000000..b798dfa
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -0,0 +1,229 @@
+/*
+ * 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.ui.widget;
+
+import android.app.Activity;
+import android.app.Application;
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.content.Intent;
+import android.graphics.Color;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.View;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.R;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.Workspace.ItemOperator;
+import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.testcomponent.AppWidgetNoConfig;
+import com.android.launcher3.testcomponent.AppWidgetWithConfig;
+import com.android.launcher3.testcomponent.RequestPinItemActivity;
+import com.android.launcher3.ui.LauncherInstrumentationTestCase;
+import com.android.launcher3.util.Condition;
+import com.android.launcher3.util.SimpleActivityMonitor;
+import com.android.launcher3.util.Wait;
+import com.android.launcher3.widget.WidgetCell;
+
+import java.util.UUID;
+import java.util.concurrent.Callable;
+
+/**
+ * Test to verify pin item request flow.
+ */
+@LargeTest
+public class RequestPinItemTest extends LauncherInstrumentationTestCase {
+
+ private SimpleActivityMonitor mActivityMonitor;
+ private MainThreadExecutor mMainThreadExecutor;
+
+ private String mCallbackAction;
+ private String mShortcutId;
+ private int mAppWidgetId;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ grantWidgetPermission();
+ setDefaultLauncher();
+
+ mActivityMonitor = new SimpleActivityMonitor();
+ ((Application) getInstrumentation().getTargetContext().getApplicationContext())
+ .registerActivityLifecycleCallbacks(mActivityMonitor);
+ mMainThreadExecutor = new MainThreadExecutor();
+
+ mCallbackAction = UUID.randomUUID().toString();
+ mShortcutId = UUID.randomUUID().toString();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ ((Application) getInstrumentation().getTargetContext().getApplicationContext())
+ .unregisterActivityLifecycleCallbacks(mActivityMonitor);
+ super.tearDown();
+ }
+
+ public void testPinWidgetNoConfig() throws Throwable {
+ runTest("pinWidgetNoConfig", true, new ItemOperator() {
+ @Override
+ public boolean evaluate(ItemInfo info, View view) {
+ return info instanceof LauncherAppWidgetInfo &&
+ ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
+ ((LauncherAppWidgetInfo) info).providerName.getClassName()
+ .equals(AppWidgetNoConfig.class.getName());
+ }
+ });
+ }
+
+ public void testPinWidgetNoConfig_customPreview() throws Throwable {
+ // Command to set custom preview
+ Intent command = RequestPinItemActivity.getCommandIntent(
+ RequestPinItemActivity.class, "setRemoteViewColor").putExtra(
+ RequestPinItemActivity.EXTRA_PARAM + "0", Color.RED);
+
+ runTest("pinWidgetNoConfig", true, new ItemOperator() {
+ @Override
+ public boolean evaluate(ItemInfo info, View view) {
+ return info instanceof LauncherAppWidgetInfo &&
+ ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
+ ((LauncherAppWidgetInfo) info).providerName.getClassName()
+ .equals(AppWidgetNoConfig.class.getName());
+ }
+ }, command);
+ }
+
+ public void testPinWidgetWithConfig() throws Throwable {
+ runTest("pinWidgetWithConfig", true, new ItemOperator() {
+ @Override
+ public boolean evaluate(ItemInfo info, View view) {
+ return info instanceof LauncherAppWidgetInfo &&
+ ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
+ ((LauncherAppWidgetInfo) info).providerName.getClassName()
+ .equals(AppWidgetWithConfig.class.getName());
+ }
+ });
+ }
+
+ public void testPinShortcut() throws Throwable {
+ // Command to set the shortcut id
+ Intent command = RequestPinItemActivity.getCommandIntent(
+ RequestPinItemActivity.class, "setShortcutId").putExtra(
+ RequestPinItemActivity.EXTRA_PARAM + "0", mShortcutId);
+
+ runTest("pinShortcut", false, new ItemOperator() {
+ @Override
+ public boolean evaluate(ItemInfo info, View view) {
+ return info instanceof ShortcutInfo &&
+ info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT &&
+ ShortcutKey.fromItemInfo(info).getId().equals(mShortcutId);
+ }
+ }, command);
+ }
+
+ private void runTest(String activityMethod, boolean isWidget, ItemOperator itemMatcher,
+ Intent... commandIntents) throws Throwable {
+ if (!Utilities.isAtLeastO()) {
+ return;
+ }
+ lockRotation(true);
+
+ clearHomescreen();
+ startLauncher();
+
+ // Open all apps and wait for load complete
+ final UiObject2 appsContainer = openAllApps();
+ assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT));
+
+ // Open Pin item activity
+ BlockingBroadcastReceiver openMonitor = new BlockingBroadcastReceiver(
+ RequestPinItemActivity.class.getName());
+ scrollAndFind(appsContainer, By.text("Test Pin Item")).click();
+ assertNotNull(openMonitor.blockingGetExtraIntent());
+
+ // Set callback
+ PendingIntent callback = PendingIntent.getBroadcast(mTargetContext, 0,
+ new Intent(mCallbackAction), PendingIntent.FLAG_ONE_SHOT);
+ mTargetContext.sendBroadcast(RequestPinItemActivity.getCommandIntent(
+ RequestPinItemActivity.class, "setCallback").putExtra(
+ RequestPinItemActivity.EXTRA_PARAM + "0", callback));
+
+ for (Intent command : commandIntents) {
+ mTargetContext.sendBroadcast(command);
+ }
+
+ // call the requested method to start the flow
+ mTargetContext.sendBroadcast(RequestPinItemActivity.getCommandIntent(
+ RequestPinItemActivity.class, activityMethod));
+ UiObject2 widgetCell = mDevice.wait(
+ Until.findObject(By.clazz(WidgetCell.class)), DEFAULT_ACTIVITY_TIMEOUT);
+ assertNotNull(widgetCell);
+
+ // Accept confirmation:
+ BlockingBroadcastReceiver resultReceiver = new BlockingBroadcastReceiver(mCallbackAction);
+ mDevice.wait(Until.findObject(By.text(mTargetContext.getString(
+ R.string.place_automatically).toUpperCase())), DEFAULT_UI_TIMEOUT).click();
+ Intent result = resultReceiver.blockingGetIntent();
+ assertNotNull(result);
+ mAppWidgetId = result.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
+ if (isWidget) {
+ assertNotSame(-1, mAppWidgetId);
+ }
+
+ // Go back to home
+ mTargetContext.startActivity(getHomeIntent());
+ assertTrue(Wait.atMost(new ItemSearchCondition(itemMatcher), DEFAULT_ACTIVITY_TIMEOUT));
+ }
+
+ /**
+ * Condition for for an item
+ */
+ private class ItemSearchCondition extends Condition implements Callable<Boolean> {
+
+ private final ItemOperator mOp;
+
+ ItemSearchCondition(ItemOperator op) {
+ mOp = op;
+ }
+
+ @Override
+ public boolean isTrue() throws Throwable {
+ return mMainThreadExecutor.submit(this).get();
+ }
+
+ @Override
+ public Boolean call() throws Exception {
+ // Find the resumed launcher
+ Launcher launcher = null;
+ for (Activity a : mActivityMonitor.resumed) {
+ if (a instanceof Launcher) {
+ launcher = (Launcher) a;
+ }
+ }
+ if (launcher == null) {
+ return false;
+ }
+ return launcher.getWorkspace().getFirstMatch(mOp) != null;
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/FocusLogicTest.java b/tests/src/com/android/launcher3/util/FocusLogicTest.java
index eee567f..79aed80 100644
--- a/tests/src/com/android/launcher3/util/FocusLogicTest.java
+++ b/tests/src/com/android/launcher3/util/FocusLogicTest.java
@@ -49,8 +49,6 @@
assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_MOVE_END));
assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_UP));
assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_DOWN));
- assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DEL));
- assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_FORWARD_DEL));
}
public void testCreateSparseMatrix() {
diff --git a/tests/src/com/android/launcher3/util/SimpleActivityMonitor.java b/tests/src/com/android/launcher3/util/SimpleActivityMonitor.java
new file mode 100644
index 0000000..6154ab6
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/SimpleActivityMonitor.java
@@ -0,0 +1,65 @@
+/*
+ * 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.app.Activity;
+import android.app.Application.*;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+
+/**
+ * Simple monitor to keep a list of active activities.
+ */
+public class SimpleActivityMonitor implements ActivityLifecycleCallbacks {
+
+ public final ArrayList<Activity> created = new ArrayList<>();
+ public final ArrayList<Activity> started = new ArrayList<>();
+ public final ArrayList<Activity> resumed = new ArrayList<>();
+
+ @Override
+ public void onActivityCreated(Activity activity, Bundle bundle) {
+ created.add(activity);
+ }
+
+ @Override
+ public void onActivityStarted(Activity activity) {
+ started.add(activity);
+ }
+
+ @Override
+ public void onActivityResumed(Activity activity) {
+ resumed.add(activity);
+ }
+
+ @Override
+ public void onActivityPaused(Activity activity) {
+ resumed.remove(activity);
+ }
+
+ @Override
+ public void onActivityStopped(Activity activity) {
+ started.remove(activity);
+ }
+
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { }
+
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+ created.remove(activity);
+ }
+}