am f70d0619: (-s ours) am e0bc7603: (-s ours) Import translations. DO NOT MERGE
* commit 'f70d061975f732456888bfa31c81998985ed5ddf':
Import translations. DO NOT MERGE
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
index 11684c3..6c58da0 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
@@ -824,17 +824,28 @@
editor.commit();
suggestWallpaperDimension(getResources(),
- sp, getWindowManager(), WallpaperManager.getInstance(this));
+ sp, getWindowManager(), WallpaperManager.getInstance(this), true);
}
static public void suggestWallpaperDimension(Resources res,
final SharedPreferences sharedPrefs,
WindowManager windowManager,
- final WallpaperManager wallpaperManager) {
+ final WallpaperManager wallpaperManager, boolean fallBackToDefaults) {
final Point defaultWallpaperSize = getDefaultWallpaperSize(res, windowManager);
// If we have saved a wallpaper width/height, use that instead
- int savedWidth = sharedPrefs.getInt(WALLPAPER_WIDTH_KEY, defaultWallpaperSize.x);
- int savedHeight = sharedPrefs.getInt(WALLPAPER_HEIGHT_KEY, defaultWallpaperSize.y);
+
+ int savedWidth = sharedPrefs.getInt(WALLPAPER_WIDTH_KEY, -1);
+ int savedHeight = sharedPrefs.getInt(WALLPAPER_HEIGHT_KEY, -1);
+
+ if (savedWidth == -1 || savedHeight == -1) {
+ if (!fallBackToDefaults) {
+ return;
+ } else {
+ savedWidth = defaultWallpaperSize.x;
+ savedHeight = defaultWallpaperSize.y;
+ }
+ }
+
if (savedWidth != wallpaperManager.getDesiredMinimumWidth() ||
savedHeight != wallpaperManager.getDesiredMinimumHeight()) {
wallpaperManager.suggestDesiredDimensions(savedWidth, savedHeight);
diff --git a/res/anim/no_anim.xml b/res/anim/no_anim.xml
new file mode 100644
index 0000000..02b1625
--- /dev/null
+++ b/res/anim/no_anim.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+ android:duration="417" />
diff --git a/res/anim/task_open_enter.xml b/res/anim/task_open_enter.xml
new file mode 100644
index 0000000..b2aadd7
--- /dev/null
+++ b/res/anim/task_open_enter.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:background="#ff000000" android:shareInterpolator="false" android:zAdjustment="top">
+
+ <alpha android:fromAlpha="0" android:toAlpha="1.0"
+ android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+ android:interpolator="@interpolator/decelerate_quart"
+ android:startOffset="300"
+ android:duration="167"/>
+
+ <translate android:fromYDelta="110%" android:toYDelta="0"
+ android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:startOffset="300"
+ android:duration="417" />
+</set>
\ No newline at end of file
diff --git a/res/drawable-hdpi/ic_allapps_l.png b/res/drawable-hdpi/ic_allapps_l.png
new file mode 100644
index 0000000..4fe3bf0
--- /dev/null
+++ b/res/drawable-hdpi/ic_allapps_l.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_allapps_pressed_l.png b/res/drawable-hdpi/ic_allapps_pressed_l.png
new file mode 100644
index 0000000..af49dbb
--- /dev/null
+++ b/res/drawable-hdpi/ic_allapps_pressed_l.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_setting_l.png b/res/drawable-hdpi/ic_setting_l.png
new file mode 100644
index 0000000..1c12a5b
--- /dev/null
+++ b/res/drawable-hdpi/ic_setting_l.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_setting_pressed_l.png b/res/drawable-hdpi/ic_setting_pressed_l.png
new file mode 100644
index 0000000..d5b5ca2
--- /dev/null
+++ b/res/drawable-hdpi/ic_setting_pressed_l.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_wallpaper_l.png b/res/drawable-hdpi/ic_wallpaper_l.png
new file mode 100644
index 0000000..34d5943
--- /dev/null
+++ b/res/drawable-hdpi/ic_wallpaper_l.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_wallpaper_pressed_l.png b/res/drawable-hdpi/ic_wallpaper_pressed_l.png
new file mode 100644
index 0000000..1588ce7
--- /dev/null
+++ b/res/drawable-hdpi/ic_wallpaper_pressed_l.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_widget_l.png b/res/drawable-hdpi/ic_widget_l.png
new file mode 100644
index 0000000..ed7e1ca
--- /dev/null
+++ b/res/drawable-hdpi/ic_widget_l.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_widget_pressed_l.png b/res/drawable-hdpi/ic_widget_pressed_l.png
new file mode 100644
index 0000000..19d6fed
--- /dev/null
+++ b/res/drawable-hdpi/ic_widget_pressed_l.png
Binary files differ
diff --git a/res/drawable-hdpi/screenpanel_hover_l.9.png b/res/drawable-hdpi/screenpanel_hover_l.9.png
new file mode 100644
index 0000000..2cea8a4
--- /dev/null
+++ b/res/drawable-hdpi/screenpanel_hover_l.9.png
Binary files differ
diff --git a/res/drawable-hdpi/screenpanel_l.9.png b/res/drawable-hdpi/screenpanel_l.9.png
new file mode 100644
index 0000000..eed0f2c
--- /dev/null
+++ b/res/drawable-hdpi/screenpanel_l.9.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_allapps_l.png b/res/drawable-mdpi/ic_allapps_l.png
new file mode 100644
index 0000000..09cd82a
--- /dev/null
+++ b/res/drawable-mdpi/ic_allapps_l.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_allapps_pressed_l.png b/res/drawable-mdpi/ic_allapps_pressed_l.png
new file mode 100644
index 0000000..d7ea96f
--- /dev/null
+++ b/res/drawable-mdpi/ic_allapps_pressed_l.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_setting_l.png b/res/drawable-mdpi/ic_setting_l.png
new file mode 100644
index 0000000..c614e91
--- /dev/null
+++ b/res/drawable-mdpi/ic_setting_l.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_setting_pressed_l.png b/res/drawable-mdpi/ic_setting_pressed_l.png
new file mode 100644
index 0000000..61e574a
--- /dev/null
+++ b/res/drawable-mdpi/ic_setting_pressed_l.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_wallpaper_l.png b/res/drawable-mdpi/ic_wallpaper_l.png
new file mode 100644
index 0000000..8f2a00a
--- /dev/null
+++ b/res/drawable-mdpi/ic_wallpaper_l.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_wallpaper_pressed_l.png b/res/drawable-mdpi/ic_wallpaper_pressed_l.png
new file mode 100644
index 0000000..aa598c3
--- /dev/null
+++ b/res/drawable-mdpi/ic_wallpaper_pressed_l.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_widget_l.png b/res/drawable-mdpi/ic_widget_l.png
new file mode 100644
index 0000000..1bd3935
--- /dev/null
+++ b/res/drawable-mdpi/ic_widget_l.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_widget_pressed_l.png b/res/drawable-mdpi/ic_widget_pressed_l.png
new file mode 100644
index 0000000..9b690d9
--- /dev/null
+++ b/res/drawable-mdpi/ic_widget_pressed_l.png
Binary files differ
diff --git a/res/drawable-mdpi/screenpanel_hover_l.9.png b/res/drawable-mdpi/screenpanel_hover_l.9.png
new file mode 100644
index 0000000..8a94984
--- /dev/null
+++ b/res/drawable-mdpi/screenpanel_hover_l.9.png
Binary files differ
diff --git a/res/drawable-mdpi/screenpanel_l.9.png b/res/drawable-mdpi/screenpanel_l.9.png
new file mode 100644
index 0000000..6f8b7e6
--- /dev/null
+++ b/res/drawable-mdpi/screenpanel_l.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_allapps_l.png b/res/drawable-xhdpi/ic_allapps_l.png
new file mode 100644
index 0000000..eff3bea
--- /dev/null
+++ b/res/drawable-xhdpi/ic_allapps_l.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_allapps_pressed_l.png b/res/drawable-xhdpi/ic_allapps_pressed_l.png
new file mode 100644
index 0000000..15a8aa9
--- /dev/null
+++ b/res/drawable-xhdpi/ic_allapps_pressed_l.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_setting_l.png b/res/drawable-xhdpi/ic_setting_l.png
new file mode 100644
index 0000000..3a7310b
--- /dev/null
+++ b/res/drawable-xhdpi/ic_setting_l.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_setting_pressed_l.png b/res/drawable-xhdpi/ic_setting_pressed_l.png
new file mode 100644
index 0000000..005d49c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_setting_pressed_l.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_wallpaper_l.png b/res/drawable-xhdpi/ic_wallpaper_l.png
new file mode 100644
index 0000000..d2bf246
--- /dev/null
+++ b/res/drawable-xhdpi/ic_wallpaper_l.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_wallpaper_pressed_l.png b/res/drawable-xhdpi/ic_wallpaper_pressed_l.png
new file mode 100644
index 0000000..5a9b84d
--- /dev/null
+++ b/res/drawable-xhdpi/ic_wallpaper_pressed_l.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_widget_l.png b/res/drawable-xhdpi/ic_widget_l.png
new file mode 100644
index 0000000..cf6be81
--- /dev/null
+++ b/res/drawable-xhdpi/ic_widget_l.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_widget_pressed_l.png b/res/drawable-xhdpi/ic_widget_pressed_l.png
new file mode 100644
index 0000000..633c9c6
--- /dev/null
+++ b/res/drawable-xhdpi/ic_widget_pressed_l.png
Binary files differ
diff --git a/res/drawable-xhdpi/screenpanel_hover_l.9.png b/res/drawable-xhdpi/screenpanel_hover_l.9.png
new file mode 100644
index 0000000..0032fff
--- /dev/null
+++ b/res/drawable-xhdpi/screenpanel_hover_l.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/screenpanel_l.9.png b/res/drawable-xhdpi/screenpanel_l.9.png
new file mode 100644
index 0000000..2d70d7a
--- /dev/null
+++ b/res/drawable-xhdpi/screenpanel_l.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_allapps_l.png b/res/drawable-xxhdpi/ic_allapps_l.png
new file mode 100644
index 0000000..2461984
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_allapps_l.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_allapps_pressed_l.png b/res/drawable-xxhdpi/ic_allapps_pressed_l.png
new file mode 100644
index 0000000..929a0e6
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_allapps_pressed_l.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_setting_l.png b/res/drawable-xxhdpi/ic_setting_l.png
new file mode 100644
index 0000000..01bdcd5
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_setting_l.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_setting_pressed_l.png b/res/drawable-xxhdpi/ic_setting_pressed_l.png
new file mode 100644
index 0000000..d0cad5e
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_setting_pressed_l.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_wallpaper_l.png b/res/drawable-xxhdpi/ic_wallpaper_l.png
new file mode 100644
index 0000000..490c45a
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_wallpaper_l.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_wallpaper_pressed_l.png b/res/drawable-xxhdpi/ic_wallpaper_pressed_l.png
new file mode 100644
index 0000000..e5d200b
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_wallpaper_pressed_l.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_widget_l.png b/res/drawable-xxhdpi/ic_widget_l.png
new file mode 100644
index 0000000..d4b8324
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_widget_l.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_widget_pressed_l.png b/res/drawable-xxhdpi/ic_widget_pressed_l.png
new file mode 100644
index 0000000..b8dd35d
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_widget_pressed_l.png
Binary files differ
diff --git a/res/drawable-xxhdpi/screenpanel_hover_l.9.png b/res/drawable-xxhdpi/screenpanel_hover_l.9.png
new file mode 100644
index 0000000..24d2266
--- /dev/null
+++ b/res/drawable-xxhdpi/screenpanel_hover_l.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/screenpanel_l.9.png b/res/drawable-xxhdpi/screenpanel_l.9.png
new file mode 100644
index 0000000..7ed058e
--- /dev/null
+++ b/res/drawable-xxhdpi/screenpanel_l.9.png
Binary files differ
diff --git a/res/drawable/all_apps_button_icon_l.xml b/res/drawable/all_apps_button_icon_l.xml
new file mode 100644
index 0000000..91de1b5
--- /dev/null
+++ b/res/drawable/all_apps_button_icon_l.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true" android:drawable="@drawable/ic_allapps_pressed_l" />
+ <item android:state_pressed="true" android:drawable="@drawable/ic_allapps_pressed_l" />
+ <item android:drawable="@drawable/ic_allapps_l" />
+</selector>
diff --git a/res/drawable/setting_button_l.xml b/res/drawable/setting_button_l.xml
new file mode 100644
index 0000000..01661db
--- /dev/null
+++ b/res/drawable/setting_button_l.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true" android:drawable="@drawable/ic_setting_pressed_l" />
+ <item android:state_pressed="true" android:drawable="@drawable/ic_setting_pressed_l" />
+ <item android:drawable="@drawable/ic_setting_l" />
+</selector>
diff --git a/res/drawable/wallpaper_button_l.xml b/res/drawable/wallpaper_button_l.xml
new file mode 100644
index 0000000..c539b61
--- /dev/null
+++ b/res/drawable/wallpaper_button_l.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true" android:drawable="@drawable/ic_wallpaper_pressed_l" />
+ <item android:state_pressed="true" android:drawable="@drawable/ic_wallpaper_pressed_l" />
+ <item android:drawable="@drawable/ic_wallpaper_l" />
+</selector>
diff --git a/res/drawable/widget_button_l.xml b/res/drawable/widget_button_l.xml
new file mode 100644
index 0000000..92521b9
--- /dev/null
+++ b/res/drawable/widget_button_l.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true" android:drawable="@drawable/ic_widget_pressed_l" />
+ <item android:state_pressed="true" android:drawable="@drawable/ic_widget_pressed_l" />
+ <item android:drawable="@drawable/ic_widget_l" />
+</selector>
diff --git a/res/interpolator/decelerate_quart.xml b/res/interpolator/decelerate_quart.xml
new file mode 100644
index 0000000..5dc5d38
--- /dev/null
+++ b/res/interpolator/decelerate_quart.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<decelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:factor="2" />
diff --git a/res/interpolator/decelerate_quint.xml b/res/interpolator/decelerate_quint.xml
new file mode 100644
index 0000000..fa89a64
--- /dev/null
+++ b/res/interpolator/decelerate_quint.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<decelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:factor="2.5" />
diff --git a/res/xml-sw720dp/default_workspace.xml b/res/xml-sw720dp/default_workspace.xml
deleted file mode 100644
index 1c1d70e..0000000
--- a/res/xml-sw720dp/default_workspace.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
- <!-- Far-left screen [0] -->
-
- <!-- Left screen [1] -->
- <appwidget
- launcher:packageName="com.android.settings"
- launcher:className="com.android.settings.widget.SettingsAppWidgetProvider"
- launcher:screen="1"
- launcher:x="0"
- launcher:y="3"
- launcher:spanX="4"
- launcher:spanY="1" />
-
- <!-- Middle screen [2] -->
- <appwidget
- launcher:packageName="com.android.deskclock"
- launcher:className="com.android.alarmclock.AnalogAppWidgetProvider"
- launcher:screen="2"
- launcher:x="1"
- launcher:y="0"
- launcher:spanX="2"
- launcher:spanY="2" />
- <favorite
- launcher:packageName="com.android.camera"
- launcher:className="com.android.camera.Camera"
- launcher:screen="2"
- launcher:x="0"
- launcher:y="3" />
-
- <!-- Right screen [3] -->
- <favorite
- launcher:packageName="com.android.gallery3d"
- launcher:className="com.android.gallery3d.app.Gallery"
- launcher:screen="3"
- launcher:x="1"
- launcher:y="3" />
- <favorite
- launcher:packageName="com.android.settings"
- launcher:className="com.android.settings.Settings"
- launcher:screen="3"
- launcher:x="2"
- launcher:y="3" />
-
- <!-- Far-right screen [4] -->
-</favorites>
diff --git a/res/xml/default_workspace.xml b/res/xml/default_workspace_4x4.xml
similarity index 100%
rename from res/xml/default_workspace.xml
rename to res/xml/default_workspace_4x4.xml
diff --git a/res/xml/default_workspace_no_all_apps.xml b/res/xml/default_workspace_4x4_no_all_apps.xml
similarity index 100%
rename from res/xml/default_workspace_no_all_apps.xml
rename to res/xml/default_workspace_4x4_no_all_apps.xml
diff --git a/res/xml/default_workspace.xml b/res/xml/default_workspace_5x5.xml
similarity index 100%
copy from res/xml/default_workspace.xml
copy to res/xml/default_workspace_5x5.xml
diff --git a/res/xml/default_workspace_5x5_no_all_apps.xml b/res/xml/default_workspace_5x5_no_all_apps.xml
new file mode 100644
index 0000000..f54a204
--- /dev/null
+++ b/res/xml/default_workspace_5x5_no_all_apps.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
+ <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+ <!-- Dialer Hangouts Maps Chrome Camera -->
+ <favorite
+ launcher:packageName="com.google.android.dialer"
+ launcher:className="com.google.android.dialer.extensions.GoogleDialtactsActivity"
+ launcher:container="-101"
+ launcher:screen="1"
+ launcher:x="1"
+ launcher:y="0" />
+ <favorite
+ launcher:packageName="com.google.android.talk"
+ launcher:className="com.google.android.talk.SigningInActivity"
+ launcher:container="-101"
+ launcher:screen="2"
+ launcher:x="2"
+ launcher:y="0" />
+ <favorite
+ launcher:packageName="com.google.android.apps.maps"
+ launcher:className="com.google.android.maps.MapsActivity"
+ launcher:container="-101"
+ launcher:screen="3"
+ launcher:x="3"
+ launcher:y="0"/>
+ <favorite
+ launcher:packageName="com.android.chrome"
+ launcher:className="com.google.android.apps.chrome.Main"
+ launcher:container="-101"
+ launcher:screen="4"
+ launcher:x="4"
+ launcher:y="0" />
+ <favorite
+ launcher:packageName="com.google.android.GoogleCamera"
+ launcher:className="com.android.camera.CameraLauncher"
+ launcher:container="-101"
+ launcher:screen="5"
+ launcher:x="5"
+ launcher:y="0" />
+</favorites>
+
diff --git a/res/xml-sw600dp/default_workspace.xml b/res/xml/default_workspace_5x6.xml
similarity index 100%
rename from res/xml-sw600dp/default_workspace.xml
rename to res/xml/default_workspace_5x6.xml
diff --git a/res/xml/default_workspace_5x6_no_all_apps.xml b/res/xml/default_workspace_5x6_no_all_apps.xml
new file mode 100644
index 0000000..f54a204
--- /dev/null
+++ b/res/xml/default_workspace_5x6_no_all_apps.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
+ <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+ <!-- Dialer Hangouts Maps Chrome Camera -->
+ <favorite
+ launcher:packageName="com.google.android.dialer"
+ launcher:className="com.google.android.dialer.extensions.GoogleDialtactsActivity"
+ launcher:container="-101"
+ launcher:screen="1"
+ launcher:x="1"
+ launcher:y="0" />
+ <favorite
+ launcher:packageName="com.google.android.talk"
+ launcher:className="com.google.android.talk.SigningInActivity"
+ launcher:container="-101"
+ launcher:screen="2"
+ launcher:x="2"
+ launcher:y="0" />
+ <favorite
+ launcher:packageName="com.google.android.apps.maps"
+ launcher:className="com.google.android.maps.MapsActivity"
+ launcher:container="-101"
+ launcher:screen="3"
+ launcher:x="3"
+ launcher:y="0"/>
+ <favorite
+ launcher:packageName="com.android.chrome"
+ launcher:className="com.google.android.apps.chrome.Main"
+ launcher:container="-101"
+ launcher:screen="4"
+ launcher:x="4"
+ launcher:y="0" />
+ <favorite
+ launcher:packageName="com.google.android.GoogleCamera"
+ launcher:className="com.android.camera.CameraLauncher"
+ launcher:container="-101"
+ launcher:screen="5"
+ launcher:x="5"
+ launcher:y="0" />
+</favorites>
+
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index 89b291f..c25aa40 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -23,6 +23,10 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+
import java.util.ArrayList;
import java.util.List;
@@ -66,7 +70,7 @@
if (mAppFilter != null && !mAppFilter.shouldShowApp(info.componentName)) {
return;
}
- if (findActivity(data, info.componentName)) {
+ if (findActivity(data, info.componentName, info.user)) {
return;
}
data.add(info);
@@ -92,12 +96,14 @@
/**
* Add the icons for the supplied apk called packageName.
*/
- public void addPackage(Context context, String packageName) {
- final List<ResolveInfo> matches = findActivitiesForPackage(context, packageName);
+ public void addPackage(Context context, String packageName, UserHandleCompat user) {
+ final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+ final List<LauncherActivityInfoCompat> matches = launcherApps.getActivityList(packageName,
+ user);
if (matches.size() > 0) {
- for (ResolveInfo info : matches) {
- add(new AppInfo(context.getPackageManager(), info, mIconCache, null));
+ for (LauncherActivityInfoCompat info : matches) {
+ add(new AppInfo(context, info, user, mIconCache, null));
}
}
}
@@ -105,34 +111,37 @@
/**
* Remove the apps for the given apk identified by packageName.
*/
- public void removePackage(String packageName) {
+ public void removePackage(String packageName, UserHandleCompat 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 (packageName.equals(component.getPackageName())) {
+ if (info.user.equals(user) && packageName.equals(component.getPackageName())) {
removed.add(info);
data.remove(i);
}
}
- mIconCache.remove(packageName);
+ mIconCache.remove(packageName, user);
}
/**
* Add and remove icons for this package which has been updated.
*/
- public void updatePackage(Context context, String packageName) {
- final List<ResolveInfo> matches = findActivitiesForPackage(context, packageName);
+ public void updatePackage(Context context, String packageName, UserHandleCompat user) {
+ final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+ final List<LauncherActivityInfoCompat> 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 (packageName.equals(component.getPackageName())) {
+ if (user.equals(applicationInfo.user)
+ && packageName.equals(component.getPackageName())) {
if (!findActivity(matches, component)) {
removed.add(applicationInfo);
- mIconCache.remove(component);
+ mIconCache.remove(component, user);
data.remove(i);
}
}
@@ -142,14 +151,14 @@
// Also updates existing activities with new labels/icons
int count = matches.size();
for (int i = 0; i < count; i++) {
- final ResolveInfo info = matches.get(i);
+ final LauncherActivityInfoCompat info = matches.get(i);
AppInfo applicationInfo = findApplicationInfoLocked(
- info.activityInfo.applicationInfo.packageName,
- info.activityInfo.name);
+ info.getComponentName().getPackageName(), user,
+ info.getComponentName().getShortClassName());
if (applicationInfo == null) {
- add(new AppInfo(context.getPackageManager(), info, mIconCache, null));
+ add(new AppInfo(context, info, user, mIconCache, null));
} else {
- mIconCache.remove(applicationInfo.componentName);
+ mIconCache.remove(applicationInfo.componentName, user);
mIconCache.getTitleAndIcon(applicationInfo, info, null);
modified.add(applicationInfo);
}
@@ -159,37 +168,24 @@
for (int i = data.size() - 1; i >= 0; i--) {
final AppInfo applicationInfo = data.get(i);
final ComponentName component = applicationInfo.intent.getComponent();
- if (packageName.equals(component.getPackageName())) {
+ if (user.equals(applicationInfo.user)
+ && packageName.equals(component.getPackageName())) {
removed.add(applicationInfo);
- mIconCache.remove(component);
+ mIconCache.remove(component, user);
data.remove(i);
}
}
}
}
- /**
- * Query the package manager for MAIN/LAUNCHER activities in the supplied package.
- */
- static List<ResolveInfo> findActivitiesForPackage(Context context, String packageName) {
- final PackageManager packageManager = context.getPackageManager();
-
- final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
- mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
- mainIntent.setPackage(packageName);
-
- final List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
- return apps != null ? apps : new ArrayList<ResolveInfo>();
- }
/**
* Returns whether <em>apps</em> contains <em>component</em>.
*/
- private static boolean findActivity(List<ResolveInfo> apps, ComponentName component) {
- final String className = component.getClassName();
- for (ResolveInfo info : apps) {
- final ActivityInfo activityInfo = info.activityInfo;
- if (activityInfo.name.equals(className)) {
+ private static boolean findActivity(List<LauncherActivityInfoCompat> apps,
+ ComponentName component) {
+ for (LauncherActivityInfoCompat info : apps) {
+ if (info.getComponentName().equals(component)) {
return true;
}
}
@@ -197,13 +193,24 @@
}
/**
+ * 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) {
+ private static boolean findActivity(ArrayList<AppInfo> apps, ComponentName component,
+ UserHandleCompat user) {
final int N = apps.size();
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < N; i++) {
final AppInfo info = apps.get(i);
- if (info.componentName.equals(component)) {
+ if (info.user.equals(user) && info.componentName.equals(component)) {
return true;
}
}
@@ -213,10 +220,11 @@
/**
* Find an ApplicationInfo object for the given packageName and className.
*/
- private AppInfo findApplicationInfoLocked(String packageName, String className) {
+ private AppInfo findApplicationInfoLocked(String packageName, UserHandleCompat user,
+ String className) {
for (AppInfo info: data) {
final ComponentName component = info.intent.getComponent();
- if (packageName.equals(component.getPackageName())
+ if (user.equals(info.user) && packageName.equals(component.getPackageName())
&& className.equals(component.getClassName())) {
return info;
}
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index f85f691..c85626b 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -17,15 +17,20 @@
package com.android.launcher3;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.util.Log;
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
/**
@@ -71,28 +76,25 @@
/**
* Must not hold the Context.
*/
- public AppInfo(PackageManager pm, ResolveInfo info, IconCache iconCache,
- HashMap<Object, CharSequence> labelCache) {
- final String packageName = info.activityInfo.applicationInfo.packageName;
-
- this.componentName = new ComponentName(packageName, info.activityInfo.name);
+ public AppInfo(Context context, LauncherActivityInfoCompat info, UserHandleCompat user,
+ IconCache iconCache, HashMap<Object, CharSequence> labelCache) {
+ this.componentName = info.getComponentName();
this.container = ItemInfo.NO_ID;
- this.setActivity(componentName,
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
- try {
- PackageInfo pi = pm.getPackageInfo(packageName, 0);
- flags = initFlags(pi);
- firstInstallTime = initFirstInstallTime(pi);
- } catch (NameNotFoundException e) {
- Log.d(TAG, "PackageManager.getApplicationInfo failed for " + packageName);
- }
-
+ flags = initFlags(info);
+ firstInstallTime = info.getFirstInstallTime();
iconCache.getTitleAndIcon(this, info, labelCache);
+ intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ intent.setComponent(info.getComponentName());
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ long serialNumber = UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
+ intent.putExtra(EXTRA_PROFILE, serialNumber);
+ this.user = user;
}
- public static int initFlags(PackageInfo pi) {
- int appFlags = pi.applicationInfo.flags;
+ private static int initFlags(LauncherActivityInfoCompat info) {
+ int appFlags = info.getApplicationFlags();
int flags = 0;
if ((appFlags & android.content.pm.ApplicationInfo.FLAG_SYSTEM) == 0) {
flags |= DOWNLOADED_FLAG;
@@ -104,10 +106,6 @@
return flags;
}
- public static long initFirstInstallTime(PackageInfo pi) {
- return pi.firstInstallTime;
- }
-
public AppInfo(AppInfo info) {
super(info);
componentName = info.componentName;
@@ -115,21 +113,7 @@
intent = new Intent(info.intent);
flags = info.flags;
firstInstallTime = info.firstInstallTime;
- }
-
- /**
- * Creates the application intent based on a component name and various launch flags.
- * Sets {@link #itemType} to {@link LauncherSettings.BaseLauncherColumns#ITEM_TYPE_APPLICATION}.
- *
- * @param className the class name of the component representing the intent
- * @param launchFlags the launch flags
- */
- final void setActivity(ComponentName className, int launchFlags) {
- intent = new Intent(Intent.ACTION_MAIN);
- intent.addCategory(Intent.CATEGORY_LAUNCHER);
- intent.setComponent(className);
- intent.setFlags(launchFlags);
- itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION;
+ iconBitmap = info.iconBitmap;
}
@Override
@@ -137,7 +121,8 @@
return "ApplicationInfo(title=" + title.toString() + " id=" + this.id
+ " type=" + this.itemType + " container=" + this.container
+ " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY
- + " spanX=" + spanX + " spanY=" + spanY + " dropPos=" + dropPos + ")";
+ + " spanX=" + spanX + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos)
+ + " user=" + user + ")";
}
public static void dumpApplicationInfoList(String tag, String label, ArrayList<AppInfo> list) {
diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java
index d6e0bb4..04426a8 100644
--- a/src/com/android/launcher3/AppsCustomizePagedView.java
+++ b/src/com/android/launcher3/AppsCustomizePagedView.java
@@ -808,13 +808,8 @@
!(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.getWorkspace().removeExtraEmptyScreen(true, new Runnable() {
- @Override
- public void run() {
- mLauncher.exitSpringLoadedDragMode();
- mLauncher.unlockScreenOrientation(false);
- }
- });
+ mLauncher.exitSpringLoadedDragMode();
+ mLauncher.unlockScreenOrientation(false);
} else {
mLauncher.unlockScreenOrientation(false);
}
@@ -1575,7 +1570,8 @@
int length = list.size();
for (int i = 0; i < length; ++i) {
AppInfo info = list.get(i);
- if (info.intent.getComponent().equals(removeComponent)) {
+ if (info.user.equals(item.user)
+ && info.intent.getComponent().equals(removeComponent)) {
return i;
}
}
diff --git a/src/com/android/launcher3/AppsCustomizeTabHost.java b/src/com/android/launcher3/AppsCustomizeTabHost.java
index bb7f045..c6455c2 100644
--- a/src/com/android/launcher3/AppsCustomizeTabHost.java
+++ b/src/com/android/launcher3/AppsCustomizeTabHost.java
@@ -29,6 +29,7 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TabHost;
@@ -430,6 +431,14 @@
// prevent slowing down the animation)
mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage());
+ // Opening apps, need to announce what page we are on.
+ AccessibilityManager am = (AccessibilityManager)
+ getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+ if (am.isEnabled()) {
+ // Notify the user when the page changes
+ announceForAccessibility(mAppsCustomizePane.getCurrentPageDescription());
+ }
+
// Going from Workspace -> All Apps
// NOTE: We should do this at the end since we check visibility state in some of the
// cling initialization/dismiss code above.
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index c180d32..95300c1 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -28,6 +28,7 @@
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
+import android.view.ViewConfiguration;
import android.widget.TextView;
/**
@@ -60,6 +61,8 @@
private int mPressedOutlineColor;
private int mPressedGlowColor;
+ private float mSlop;
+
private int mTextColor;
private boolean mShadowsEnabled = true;
private boolean mIsTextVisible;
@@ -272,6 +275,11 @@
mLongPressHelper.cancelLongPress();
break;
+ case MotionEvent.ACTION_MOVE:
+ if (!Utilities.pointInView(this, event.getX(), event.getY(), mSlop)) {
+ mLongPressHelper.cancelLongPress();
+ }
+ break;
}
return result;
}
@@ -356,6 +364,7 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mBackground != null) mBackground.setCallback(this);
+ mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
@Override
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 2436a51..a0c9c2e 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -76,9 +76,6 @@
private boolean mScrollingTransformsDirty = false;
private boolean mDropPending = false;
- private final Rect mRect = new Rect();
- private final CellInfo mCellInfo = new CellInfo();
-
// 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[] mTmpXY = new int[2];
@@ -220,6 +217,11 @@
mNormalBackground = res.getDrawable(R.drawable.screenpanel);
mActiveGlowBackground = res.getDrawable(R.drawable.screenpanel_hover);
+ if (Utilities.isLmp()) {
+ mNormalBackground = res.getDrawable(R.drawable.screenpanel_l);
+ mActiveGlowBackground = res.getDrawable(R.drawable.screenpanel_hover_l);
+ }
+
mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
mForegroundPadding =
@@ -699,103 +701,20 @@
}
@Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- if (getParent() instanceof Workspace) {
- Workspace workspace = (Workspace) getParent();
- mCellInfo.screenId = workspace.getIdForScreen(this);
- }
- }
-
- public void setTagToCellInfoForPoint(int touchX, int touchY) {
- final CellInfo cellInfo = mCellInfo;
- Rect frame = mRect;
- final int x = touchX + getScrollX();
- final int y = touchY + getScrollY();
- final int count = mShortcutsAndWidgets.getChildCount();
-
- boolean found = false;
- for (int i = count - 1; i >= 0; i--) {
- final View child = mShortcutsAndWidgets.getChildAt(i);
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
- if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) &&
- lp.isLockedToGrid) {
- child.getHitRect(frame);
-
- float scale = child.getScaleX();
- frame = new Rect(child.getLeft(), child.getTop(), child.getRight(),
- child.getBottom());
- // The child hit rect is relative to the CellLayoutChildren parent, so we need to
- // offset that by this CellLayout's padding to test an (x,y) point that is relative
- // to this view.
- frame.offset(getPaddingLeft(), getPaddingTop());
- frame.inset((int) (frame.width() * (1f - scale) / 2),
- (int) (frame.height() * (1f - scale) / 2));
-
- if (frame.contains(x, y)) {
- cellInfo.cell = child;
- cellInfo.cellX = lp.cellX;
- cellInfo.cellY = lp.cellY;
- cellInfo.spanX = lp.cellHSpan;
- cellInfo.spanY = lp.cellVSpan;
- found = true;
- break;
- }
- }
- }
-
- mLastDownOnOccupiedCell = found;
-
- if (!found) {
- final int cellXY[] = mTmpXY;
- pointToCellExact(x, y, cellXY);
-
- cellInfo.cell = null;
- cellInfo.cellX = cellXY[0];
- cellInfo.cellY = cellXY[1];
- cellInfo.spanX = 1;
- cellInfo.spanY = 1;
- }
- setTag(cellInfo);
- }
-
- @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// First we clear the tag to ensure that on every touch down we start with a fresh slate,
// even in the case where we return early. Not clearing here was causing bugs whereby on
// long-press we'd end up picking up an item from a previous drag operation.
final int action = ev.getAction();
- if (action == MotionEvent.ACTION_DOWN) {
- clearTagCellInfo();
- }
if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
return true;
}
- if (action == MotionEvent.ACTION_DOWN) {
- setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
- }
-
return false;
}
- private void clearTagCellInfo() {
- final CellInfo cellInfo = mCellInfo;
- cellInfo.cell = null;
- cellInfo.cellX = -1;
- cellInfo.cellY = -1;
- cellInfo.spanX = 0;
- cellInfo.spanY = 0;
- setTag(cellInfo);
- }
-
- public CellInfo getTag() {
- return (CellInfo) super.getTag();
- }
-
/**
* Given a point, return the cell that strictly encloses that point
* @param x X coordinate of the point
@@ -3360,6 +3279,16 @@
long screenId;
long container;
+ CellInfo(View v, ItemInfo info) {
+ cell = v;
+ cellX = info.cellX;
+ cellY = info.cellY;
+ spanX = info.spanX;
+ spanY = info.spanY;
+ screenId = info.screenId;
+ container = info.container;
+ }
+
@Override
public String toString() {
return "Cell[view=" + (cell == null ? "null" : cell.getClass())
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 75d906b..20546b8 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -38,6 +38,9 @@
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LinearInterpolator;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+
import java.util.List;
import java.util.Set;
@@ -184,6 +187,11 @@
if (!willAcceptDrop(info) || isAllAppsWidget(source, info)) {
isVisible = false;
}
+ if (useUninstallLabel &&
+ !(((ItemInfo) info).user.equals(UserHandleCompat.myUserHandle()))) {
+ // Don't support uninstall for apps from other profiles.
+ isVisible = false;
+ }
if (useUninstallLabel) {
setCompoundDrawablesRelativeWithIntrinsicBounds(mUninstallDrawable, null, null, null);
@@ -279,25 +287,27 @@
if (isAllAppsApplication(d.dragSource, item)) {
// Uninstall the application if it is being dragged from AppsCustomize
AppInfo appInfo = (AppInfo) item;
- mLauncher.startApplicationUninstallActivity(appInfo.componentName, appInfo.flags);
+ // We don't support uninstalling apps from other profiles.
+ if (item.user.equals(UserHandleCompat.myUserHandle())) {
+ mLauncher.startApplicationUninstallActivity(appInfo.componentName, appInfo.flags);
+ }
} else if (isUninstallFromWorkspace(d)) {
ShortcutInfo shortcut = (ShortcutInfo) item;
- if (shortcut.intent != null && shortcut.intent.getComponent() != null) {
+ // We don't support uninstalling apps from other profiles.
+ if (shortcut.intent != null && shortcut.intent.getComponent() != null &&
+ shortcut.user.equals(UserHandleCompat.myUserHandle())) {
final ComponentName componentName = shortcut.intent.getComponent();
final DragSource dragSource = d.dragSource;
- int flags = AppInfo.initFlags(
- ShortcutInfo.getPackageInfo(getContext(), componentName.getPackageName()));
mWaitingForUninstall =
- mLauncher.startApplicationUninstallActivity(componentName, flags);
+ mLauncher.startApplicationUninstallActivity(componentName, shortcut.flags);
if (mWaitingForUninstall) {
final Runnable checkIfUninstallWasSuccess = new Runnable() {
@Override
public void run() {
mWaitingForUninstall = false;
String packageName = componentName.getPackageName();
- List<ResolveInfo> activities =
- AllAppsList.findActivitiesForPackage(getContext(), packageName);
- boolean uninstallSuccessful = activities.size() == 0;
+ boolean uninstallSuccessful = !AllAppsList.packageHasActivities(
+ getContext(), packageName, UserHandleCompat.myUserHandle());
if (dragSource instanceof Folder) {
((Folder) dragSource).
onUninstallActivityReturned(uninstallSuccessful);
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index e67ec19..8470b39 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -43,16 +43,18 @@
class DeviceProfileQuery {
+ DeviceProfile profile;
float widthDps;
float heightDps;
float value;
PointF dimens;
- DeviceProfileQuery(float w, float h, float v) {
- widthDps = w;
- heightDps = h;
+ DeviceProfileQuery(DeviceProfile p, float v) {
+ widthDps = p.minWidthDps;
+ heightDps = p.minHeightDps;
value = v;
- dimens = new PointF(w, h);
+ dimens = new PointF(widthDps, heightDps);
+ profile = p;
}
}
@@ -72,6 +74,9 @@
private int iconDrawablePaddingOriginalPx;
private float hotseatIconSize;
+ int defaultLayoutId;
+ int defaultNoAllAppsLayoutId;
+
boolean isLandscape;
boolean isTablet;
boolean isLargeTablet;
@@ -127,7 +132,7 @@
private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>();
DeviceProfile(String n, float w, float h, float r, float c,
- float is, float its, float hs, float his) {
+ float is, float its, float hs, float his, int dlId, int dnalId) {
// Ensure that we have an odd number of hotseat items (since we need to place all apps)
if (!LauncherAppState.isDisableAllApps() && hs % 2 == 0) {
throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
@@ -142,6 +147,8 @@
iconTextSize = its;
numHotseatIcons = hs;
hotseatIconSize = his;
+ defaultLayoutId = dlId;
+ defaultNoAllAppsLayoutId = dnalId;
}
DeviceProfile(Context context,
@@ -182,29 +189,32 @@
overviewModeScaleFactor =
res.getInteger(R.integer.config_dynamic_grid_overview_scale_percentage) / 100f;
- // Interpolate the rows
+ // Find the closes profile given the width/height
for (DeviceProfile p : profiles) {
- points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numRows));
+ points.add(new DeviceProfileQuery(p, 0f));
}
- numRows = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
- // Interpolate the columns
- points.clear();
- for (DeviceProfile p : profiles) {
- points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numColumns));
- }
- numColumns = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
- // Interpolate the hotseat length
- points.clear();
- for (DeviceProfile p : profiles) {
- points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numHotseatIcons));
- }
- numHotseatIcons = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
+ DeviceProfile closestProfile = findClosestDeviceProfile(minWidth, minHeight, points);
+
+ // Snap to the closest row count
+ numRows = closestProfile.numRows;
+
+ // Snap to the closest column count
+ numColumns = closestProfile.numColumns;
+
+ // Snap to the closest hotseat size
+ numHotseatIcons = closestProfile.numHotseatIcons;
hotseatAllAppsRank = (int) (numHotseatIcons / 2);
+ // Snap to the closest default layout id
+ defaultLayoutId = closestProfile.defaultLayoutId;
+
+ // Snap to the closest default no all-apps layout id
+ defaultNoAllAppsLayoutId = closestProfile.defaultNoAllAppsLayoutId;
+
// Interpolate the icon size
points.clear();
for (DeviceProfile p : profiles) {
- points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconSize));
+ points.add(new DeviceProfileQuery(p, p.iconSize));
}
iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
// AllApps uses the original non-scaled icon size
@@ -213,7 +223,7 @@
// Interpolate the icon text size
points.clear();
for (DeviceProfile p : profiles) {
- points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconTextSize));
+ points.add(new DeviceProfileQuery(p, p.iconTextSize));
}
iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
iconDrawablePaddingOriginalPx =
@@ -224,7 +234,7 @@
// Interpolate the hotseat icon size
points.clear();
for (DeviceProfile p : profiles) {
- points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.hotseatIconSize));
+ points.add(new DeviceProfileQuery(p, p.hotseatIconSize));
}
// Hotseat
hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
@@ -398,6 +408,28 @@
return (float) (1f / Math.pow(d, pow));
}
+ /** Returns the closest device profile given the width and height and a list of profiles */
+ private DeviceProfile findClosestDeviceProfile(float width, float height,
+ ArrayList<DeviceProfileQuery> points) {
+ return findClosestDeviceProfiles(width, height, points).get(0).profile;
+ }
+
+ /** Returns the closest device profiles ordered by closeness to the specified width and height */
+ private ArrayList<DeviceProfileQuery> findClosestDeviceProfiles(float width, float height,
+ ArrayList<DeviceProfileQuery> points) {
+ final PointF xy = new PointF(width, height);
+
+ // Sort the profiles by their closeness to the dimensions
+ ArrayList<DeviceProfileQuery> pointsByNearness = points;
+ Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
+ public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
+ return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
+ }
+ });
+
+ return pointsByNearness;
+ }
+
private float invDistWeightedInterpolate(float width, float height,
ArrayList<DeviceProfileQuery> points) {
float sum = 0;
@@ -406,12 +438,8 @@
float kNearestNeighbors = 3;
final PointF xy = new PointF(width, height);
- ArrayList<DeviceProfileQuery> pointsByNearness = points;
- Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
- public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
- return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
- }
- });
+ ArrayList<DeviceProfileQuery> pointsByNearness = findClosestDeviceProfiles(width, height,
+ points);
for (int i = 0; i < pointsByNearness.size(); ++i) {
DeviceProfileQuery p = pointsByNearness.get(i);
diff --git a/src/com/android/launcher3/DynamicGrid.java b/src/com/android/launcher3/DynamicGrid.java
index 447bb1c..94a07d7 100644
--- a/src/com/android/launcher3/DynamicGrid.java
+++ b/src/com/android/launcher3/DynamicGrid.java
@@ -60,36 +60,41 @@
DEFAULT_ICON_SIZE_PX = pxFromDp(DEFAULT_ICON_SIZE_DP, dm);
// Our phone profiles include the bar sizes in each orientation
deviceProfiles.add(new DeviceProfile("Super Short Stubby",
- 255, 300, 2, 3, 48, 13, (hasAA ? 5 : 5), 48));
+ 255, 300, 2, 3, 48, 13, (hasAA ? 3 : 5), 48, R.xml.default_workspace_4x4,
+ R.xml.default_workspace_4x4_no_all_apps));
deviceProfiles.add(new DeviceProfile("Shorter Stubby",
- 255, 400, 3, 3, 48, 13, (hasAA ? 5 : 5), 48));
+ 255, 400, 3, 3, 48, 13, (hasAA ? 3 : 5), 48, R.xml.default_workspace_4x4,
+ R.xml.default_workspace_4x4_no_all_apps));
deviceProfiles.add(new DeviceProfile("Short Stubby",
- 275, 420, 3, 4, 48, 13, (hasAA ? 5 : 5), 48));
+ 275, 420, 3, 4, 48, 13, (hasAA ? 5 : 5), 48, R.xml.default_workspace_4x4,
+ R.xml.default_workspace_4x4_no_all_apps));
deviceProfiles.add(new DeviceProfile("Stubby",
- 255, 450, 3, 4, 48, 13, (hasAA ? 5 : 5), 48));
+ 255, 450, 3, 4, 48, 13, (hasAA ? 5 : 5), 48, R.xml.default_workspace_4x4,
+ R.xml.default_workspace_4x4_no_all_apps));
deviceProfiles.add(new DeviceProfile("Nexus S",
- 296, 491.33f, 4, 4, 48, 13, (hasAA ? 5 : 5), 48));
+ 296, 491.33f, 4, 4, 48, 13, (hasAA ? 5 : 5), 48, R.xml.default_workspace_4x4,
+ R.xml.default_workspace_4x4_no_all_apps));
deviceProfiles.add(new DeviceProfile("Nexus 4",
- 335, 567, 4, 4, DEFAULT_ICON_SIZE_DP, 13, (hasAA ? 5 : 5), 56));
+ 335, 567, 4, 4, DEFAULT_ICON_SIZE_DP, 13, (hasAA ? 5 : 5), 56, R.xml.default_workspace_4x4,
+ R.xml.default_workspace_4x4_no_all_apps));
deviceProfiles.add(new DeviceProfile("Nexus 5",
- 359, 567, 4, 4, DEFAULT_ICON_SIZE_DP, 13, (hasAA ? 5 : 5), 56));
+ 359, 567, 4, 4, DEFAULT_ICON_SIZE_DP, 13, (hasAA ? 5 : 5), 56, R.xml.default_workspace_4x4,
+ R.xml.default_workspace_4x4_no_all_apps));
deviceProfiles.add(new DeviceProfile("Large Phone",
- 406, 694, 5, 5, 64, 14.4f, 5, 56));
+ 406, 694, 5, 5, 64, 14.4f, 5, 56, R.xml.default_workspace_5x5,
+ R.xml.default_workspace_5x5_no_all_apps));
// The tablet profile is odd in that the landscape orientation
// also includes the nav bar on the side
deviceProfiles.add(new DeviceProfile("Nexus 7",
- 575, 904, 5, 6, 72, 14.4f, 7, 60));
+ 575, 904, 5, 6, 72, 14.4f, 7, 60, R.xml.default_workspace_5x6,
+ R.xml.default_workspace_5x6_no_all_apps));
// Larger tablet profiles always have system bars on the top & bottom
deviceProfiles.add(new DeviceProfile("Nexus 10",
- 727, 1207, 5, 6, 76, 14.4f, 7, 64));
- /*
- deviceProfiles.add(new DeviceProfile("Nexus 7",
- 600, 960, 5, 5, 72, 14.4f, 5, 60));
- deviceProfiles.add(new DeviceProfile("Nexus 10",
- 800, 1280, 5, 5, 80, 14.4f, (hasAA ? 7 : 6), 64));
- */
+ 727, 1207, 5, 6, 76, 14.4f, 7, 64, R.xml.default_workspace_5x6,
+ R.xml.default_workspace_5x6_no_all_apps));
deviceProfiles.add(new DeviceProfile("20-inch Tablet",
- 1527, 2527, 7, 7, 100, 20, 7, 72));
+ 1527, 2527, 7, 7, 100, 20, 7, 72, R.xml.default_workspace_4x4,
+ R.xml.default_workspace_4x4_no_all_apps));
mMinWidth = dpiFromPx(minWidthPx, dm);
mMinHeight = dpiFromPx(minHeightPx, dm);
mProfile = new DeviceProfile(context, deviceProfiles,
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index fb226e5..e900c2b 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -391,7 +391,7 @@
// We rearrange the items in case there are any empty gaps
setupContentForNumItems(count);
- // If our folder has too many items we prune them from the list. This is an issue
+ // If our folder has too many items we prune them from the list. This is an issue
// when upgrading from the old Folders implementation which could contain an unlimited
// number of items.
for (ShortcutInfo item: overflow) {
@@ -583,7 +583,7 @@
// by another item.
if (mContent.getChildAt(item.cellX, item.cellY) != null || item.cellX < 0 || item.cellY < 0
|| item.cellX >= mContent.getCountX() || item.cellY >= mContent.getCountY()) {
- // This shouldn't happen, log it.
+ // This shouldn't happen, log it.
Log.e(TAG, "Folder order not properly persisted during bind");
if (!findAndSetEmptyCells(item)) {
return null;
@@ -797,12 +797,6 @@
}
}
- // This is kind of hacky, but in general, dropping on the workspace handles removing
- // the extra screen, but dropping elsewhere (back to self, or onto delete) doesn't.
- if (target != mLauncher.getWorkspace()) {
- mLauncher.getWorkspace().removeExtraEmptyScreen(true, null);
- }
-
mDeleteFolderOnDropCompleted = false;
mDragInProgress = false;
mItemAddedBackToSelfViaIcon = false;
@@ -1176,21 +1170,15 @@
public void onDrop(DragObject d) {
Runnable cleanUpRunnable = null;
- // If we are coming from All Apps space, we need to remove the extra empty screen (which is
- // normally done in Workspace#onDropExternal, as well zoom back in and close the folder.
+ // If we are coming from All Apps space, we defer removing the extra empty screen
+ // until the folder closes
if (d.dragSource != mLauncher.getWorkspace() && !(d.dragSource instanceof Folder)) {
cleanUpRunnable = new Runnable() {
@Override
public void run() {
- mLauncher.getWorkspace().removeExtraEmptyScreen(false, new Runnable() {
- @Override
- public void run() {
- mLauncher.closeFolder();
- mLauncher.exitSpringLoadedDragModeDelayed(true,
- Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT_FOLDER_CLOSE,
- null);
- }
- }, CLOSE_FOLDER_DELAY_MS, false);
+ mLauncher.exitSpringLoadedDragModeDelayed(true,
+ Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT,
+ null);
}
};
}
diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java
index 71a7461..be6cf48 100644
--- a/src/com/android/launcher3/FolderIcon.java
+++ b/src/com/android/launcher3/FolderIcon.java
@@ -33,6 +33,7 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
@@ -72,6 +73,8 @@
// The amount of vertical spread between items in the stack [0...1]
private static final float PERSPECTIVE_SHIFT_FACTOR = 0.24f;
+ private static final float PERSPECTIVE_SHIFT_FACTOR_L = 0.18f;
+
// Flag as to whether or not to draw an outer ring. Currently none is designed.
public static final boolean HAS_OUTER_RING = true;
@@ -105,6 +108,8 @@
boolean mAnimating = false;
private Rect mOldBounds = new Rect();
+ private float mSlop;
+
private PreviewItemDrawingParams mParams = new PreviewItemDrawingParams(0, 0, 0, 0);
private PreviewItemDrawingParams mAnimParams = new PreviewItemDrawingParams(0, 0, 0, 0);
private ArrayList<ShortcutInfo> mHiddenItems = new ArrayList<ShortcutInfo>();
@@ -341,7 +346,12 @@
mFolderRingAnimator.animateToAcceptState();
layout.showFolderAccept(mFolderRingAnimator);
mOpenAlarm.setOnAlarmListener(mOnOpenListener);
- if (SPRING_LOADING_ENABLED) {
+ if (SPRING_LOADING_ENABLED &&
+ ((dragInfo instanceof AppInfo) || (dragInfo instanceof ShortcutInfo))) {
+ // TODO: we currently don't support spring-loading for PendingAddShortcutInfos even
+ // though widget-style shortcuts can be added to folders. The issue is that we need
+ // to deal with configuration activities which are currently handled in
+ // Workspace#onDropExternal.
mOpenAlarm.setAlarm(ON_OPEN_DELAY);
}
mDragInfo = (ItemInfo) dragInfo;
@@ -359,6 +369,7 @@
item.spanX = 1;
item.spanY = 1;
} else {
+ // ShortcutInfo
item = (ShortcutInfo) mDragInfo;
}
mFolder.beginExternalDrag(item);
@@ -386,7 +397,7 @@
public void performDestroyAnimation(final View finalView, Runnable onCompleteRunnable) {
Drawable animateDrawable = ((TextView) finalView).getCompoundDrawables()[1];
- computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
+ computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
finalView.getMeasuredWidth());
// This will animate the first item from it's position as an icon into its
@@ -492,10 +503,16 @@
int adjustedAvailableSpace = (int) ((mAvailableSpaceInPreview / 2) * (1 + 0.8f));
int unscaledHeight = (int) (mIntrinsicIconSize * (1 + PERSPECTIVE_SHIFT_FACTOR));
+ if (Utilities.isLmp()) {
+ unscaledHeight = (int) (mIntrinsicIconSize * (1 + PERSPECTIVE_SHIFT_FACTOR_L));
+ }
mBaselineIconScale = (1.0f * adjustedAvailableSpace / unscaledHeight);
mBaselineIconSize = (int) (mIntrinsicIconSize * mBaselineIconScale);
mMaxPerspectiveShift = mBaselineIconSize * PERSPECTIVE_SHIFT_FACTOR;
+ if (Utilities.isLmp()) {
+ mMaxPerspectiveShift = mBaselineIconSize * PERSPECTIVE_SHIFT_FACTOR_L;
+ }
mPreviewOffsetX = (mTotalWidth - mAvailableSpaceInPreview) / 2;
mPreviewOffsetY = previewPadding + grid.folderBackgroundOffset;
@@ -550,6 +567,10 @@
float totalScale = mBaselineIconScale * scale;
final int overlayAlpha = (int) (80 * (1 - r));
+ if (Utilities.isLmp()) {
+ transX = (mAvailableSpaceInPreview - scaledSize) / 2;
+ }
+
if (params == null) {
params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha);
} else {
@@ -703,11 +724,22 @@
case MotionEvent.ACTION_UP:
mLongPressHelper.cancelLongPress();
break;
+ case MotionEvent.ACTION_MOVE:
+ if (!Utilities.pointInView(this, event.getX(), event.getY(), mSlop)) {
+ mLongPressHelper.cancelLongPress();
+ }
+ break;
}
return result;
}
@Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ }
+
+ @Override
public void cancelLongPress() {
super.cancelLongPress();
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 42e2ec3..85a792f 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -17,8 +17,12 @@
package com.android.launcher3;
import android.content.ContentValues;
+import android.content.Context;
+
+import com.android.launcher3.compat.UserHandleCompat;
import java.util.ArrayList;
+import java.util.Arrays;
/**
* Represents a folder containing shortcuts or apps.
@@ -39,6 +43,7 @@
FolderInfo() {
itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
+ user = UserHandleCompat.myUserHandle();
}
/**
@@ -75,8 +80,8 @@
}
@Override
- void onAddToDatabase(ContentValues values) {
- super.onAddToDatabase(values);
+ void onAddToDatabase(Context context, ContentValues values) {
+ super.onAddToDatabase(context, values);
values.put(LauncherSettings.Favorites.TITLE, title.toString());
}
@@ -114,6 +119,6 @@
return "FolderInfo(id=" + this.id + " type=" + this.itemType
+ " container=" + this.container + " screen=" + screenId
+ " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX
- + " spanY=" + spanY + " dropPos=" + dropPos + ")";
+ + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos) + ")";
}
}
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 2ac2f00..4b25433 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -150,6 +150,11 @@
TextView allAppsButton = (TextView)
inflater.inflate(R.layout.all_apps_button, mContent, false);
Drawable d = context.getResources().getDrawable(R.drawable.all_apps_button_icon);
+
+ if (Utilities.isLmp()) {
+ d = context.getResources().getDrawable(R.drawable.all_apps_button_icon_l);
+ }
+
Utilities.resizeIconDrawable(d);
allAppsButton.setCompoundDrawables(null, d, null, null);
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index ee9f4d4..be02d35 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -31,8 +31,14 @@
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
+import android.os.Build;
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 java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
@@ -61,11 +67,35 @@
public String title;
}
- private final Bitmap mDefaultIcon;
+ private static class CacheKey {
+ public ComponentName componentName;
+ public UserHandleCompat user;
+
+ CacheKey(ComponentName componentName, UserHandleCompat user) {
+ this.componentName = componentName;
+ this.user = user;
+ }
+
+ @Override
+ public int hashCode() {
+ return componentName.hashCode() + user.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ CacheKey other = (CacheKey) o;
+ return other.componentName.equals(componentName) && other.user.equals(user);
+ }
+ }
+
+ private final HashMap<UserHandleCompat, Bitmap> mDefaultIcons =
+ new HashMap<UserHandleCompat, Bitmap>();
private final Context mContext;
private final PackageManager mPackageManager;
- private final HashMap<ComponentName, CacheEntry> mCache =
- new HashMap<ComponentName, CacheEntry>(INITIAL_ICON_CACHE_CAPACITY);
+ private final UserManagerCompat mUserManager;
+ private final LauncherAppsCompat mLauncherApps;
+ private final HashMap<CacheKey, CacheEntry> mCache =
+ new HashMap<CacheKey, CacheEntry>(INITIAL_ICON_CACHE_CAPACITY);
private int mIconDpi;
public IconCache(Context context) {
@@ -74,10 +104,13 @@
mContext = context;
mPackageManager = context.getPackageManager();
+ mUserManager = UserManagerCompat.getInstance(mContext);
+ mLauncherApps = LauncherAppsCompat.getInstance(mContext);
mIconDpi = activityManager.getLauncherLargeIconDensity();
// need to set mIconDpi before getting default icon
- mDefaultIcon = makeDefaultIcon();
+ UserHandleCompat myUser = UserHandleCompat.myUserHandle();
+ mDefaultIcons.put(myUser, makeDefaultIcon(myUser));
}
public Drawable getFullResDefaultActivityIcon() {
@@ -134,8 +167,9 @@
return getFullResDefaultActivityIcon();
}
- private Bitmap makeDefaultIcon() {
- Drawable d = getFullResDefaultActivityIcon();
+ private Bitmap makeDefaultIcon(UserHandleCompat user) {
+ Drawable unbadged = getFullResDefaultActivityIcon();
+ Drawable d = mUserManager.getBadgedDrawableForUser(unbadged, user);
Bitmap b = Bitmap.createBitmap(Math.max(d.getIntrinsicWidth(), 1),
Math.max(d.getIntrinsicHeight(), 1),
Bitmap.Config.ARGB_8888);
@@ -149,24 +183,25 @@
/**
* Remove any records for the supplied ComponentName.
*/
- public void remove(ComponentName componentName) {
+ public void remove(ComponentName componentName, UserHandleCompat user) {
synchronized (mCache) {
- mCache.remove(componentName);
+ mCache.remove(new CacheKey(componentName, user));
}
}
/**
* Remove any records for the supplied package name.
*/
- public void remove(String packageName) {
- HashSet<ComponentName> forDeletion = new HashSet<ComponentName>();
- for (ComponentName componentName: mCache.keySet()) {
- if (componentName.getPackageName().equals(packageName)) {
- forDeletion.add(componentName);
+ public void remove(String packageName, UserHandleCompat user) {
+ HashSet<CacheKey> forDeletion = new HashSet<CacheKey>();
+ for (CacheKey key: mCache.keySet()) {
+ if (key.componentName.getPackageName().equals(packageName)
+ && key.user.equals(user)) {
+ forDeletion.add(key);
}
}
- for (ComponentName condemned: forDeletion) {
- remove(condemned);
+ for (CacheKey condemned: forDeletion) {
+ mCache.remove(condemned);
}
}
@@ -184,7 +219,7 @@
*/
public void flushInvalidIcons(DeviceProfile grid) {
synchronized (mCache) {
- Iterator<Entry<ComponentName, CacheEntry>> it = mCache.entrySet().iterator();
+ Iterator<Entry<CacheKey, CacheEntry>> it = mCache.entrySet().iterator();
while (it.hasNext()) {
final CacheEntry e = it.next().getValue();
if (e.icon.getWidth() < grid.iconSizePx || e.icon.getHeight() < grid.iconSizePx) {
@@ -197,30 +232,34 @@
/**
* Fill in "application" with the icon and label for "info."
*/
- public void getTitleAndIcon(AppInfo application, ResolveInfo info,
+ public void getTitleAndIcon(AppInfo application, LauncherActivityInfoCompat info,
HashMap<Object, CharSequence> labelCache) {
synchronized (mCache) {
- CacheEntry entry = cacheLocked(application.componentName, info, labelCache);
+ CacheEntry entry = cacheLocked(application.componentName, info, labelCache,
+ info.getUser());
application.title = entry.title;
application.iconBitmap = entry.icon;
}
}
- public Bitmap getIcon(Intent intent) {
- return getIcon(intent, null);
+ public Bitmap getIcon(Intent intent, UserHandleCompat user) {
+ return getIcon(intent, null, user);
}
- public Bitmap getIcon(Intent intent, String title) {
+ public Bitmap getIcon(Intent intent, String title, UserHandleCompat user) {
synchronized (mCache) {
- final ResolveInfo resolveInfo = mPackageManager.resolveActivity(intent, 0);
+ LauncherActivityInfoCompat launcherActInfo =
+ mLauncherApps.resolveActivity(intent, user);
ComponentName component = intent.getComponent();
- if (component == null) {
- return mDefaultIcon;
+ // 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 (launcherActInfo == null && component == null) {
+ return getDefaultIcon(user);
}
- CacheEntry entry = cacheLocked(component, resolveInfo, null);
+ CacheEntry entry = cacheLocked(component, launcherActInfo, null, user);
if (title != null) {
entry.title = title;
}
@@ -228,49 +267,54 @@
}
}
- public Bitmap getIcon(ComponentName component, ResolveInfo resolveInfo,
+ public Bitmap getDefaultIcon(UserHandleCompat user) {
+ if (!mDefaultIcons.containsKey(user)) {
+ mDefaultIcons.put(user, makeDefaultIcon(user));
+ }
+ return mDefaultIcons.get(user);
+ }
+
+ public Bitmap getIcon(ComponentName component, LauncherActivityInfoCompat info,
HashMap<Object, CharSequence> labelCache) {
synchronized (mCache) {
- if (resolveInfo == null || component == null) {
+ if (info == null || component == null) {
return null;
}
- CacheEntry entry = cacheLocked(component, resolveInfo, labelCache);
+ CacheEntry entry = cacheLocked(component, info, labelCache, info.getUser());
return entry.icon;
}
}
- public boolean isDefaultIcon(Bitmap icon) {
- return mDefaultIcon == icon;
+ public boolean isDefaultIcon(Bitmap icon, UserHandleCompat user) {
+ return mDefaultIcons.get(user) == icon;
}
- private CacheEntry cacheLocked(ComponentName componentName, ResolveInfo info,
- HashMap<Object, CharSequence> labelCache) {
- CacheEntry entry = mCache.get(componentName);
+ private CacheEntry cacheLocked(ComponentName componentName, LauncherActivityInfoCompat info,
+ HashMap<Object, CharSequence> labelCache, UserHandleCompat user) {
+ CacheKey cacheKey = new CacheKey(componentName, user);
+ CacheEntry entry = mCache.get(cacheKey);
if (entry == null) {
entry = new CacheEntry();
- mCache.put(componentName, entry);
+ mCache.put(cacheKey, entry);
if (info != null) {
- ComponentName key = LauncherModel.getComponentNameFromResolveInfo(info);
- if (labelCache != null && labelCache.containsKey(key)) {
- entry.title = labelCache.get(key).toString();
+ ComponentName labelKey = info.getComponentName();
+ if (labelCache != null && labelCache.containsKey(labelKey)) {
+ entry.title = labelCache.get(labelKey).toString();
} else {
- entry.title = info.loadLabel(mPackageManager).toString();
+ entry.title = info.getLabel().toString();
if (labelCache != null) {
- labelCache.put(key, entry.title);
+ labelCache.put(labelKey, entry.title);
}
}
- if (entry.title == null) {
- entry.title = info.activityInfo.name;
- }
entry.icon = Utilities.createIconBitmap(
- getFullResIcon(info), mContext);
+ info.getBadgedIcon(mIconDpi), mContext);
} else {
entry.title = "";
- Bitmap preloaded = getPreloadedIcon(componentName);
+ Bitmap preloaded = getPreloadedIcon(componentName, user);
if (preloaded != null) {
if (DEBUG) Log.d(TAG, "using preloaded icon for " +
componentName.toShortString());
@@ -278,7 +322,7 @@
} else {
if (DEBUG) Log.d(TAG, "using default icon for " +
componentName.toShortString());
- entry.icon = mDefaultIcon;
+ entry.icon = getDefaultIcon(user);
}
}
}
@@ -288,9 +332,9 @@
public HashMap<ComponentName,Bitmap> getAllIcons() {
synchronized (mCache) {
HashMap<ComponentName,Bitmap> set = new HashMap<ComponentName,Bitmap>();
- for (ComponentName cn : mCache.keySet()) {
- final CacheEntry e = mCache.get(cn);
- set.put(cn, e.icon);
+ for (CacheKey ck : mCache.keySet()) {
+ final CacheEntry e = mCache.get(ck);
+ set.put(ck.componentName, e.icon);
}
return set;
}
@@ -353,9 +397,14 @@
* @param componentName the component that should own the icon
* @returns a bitmap if one is cached, or null.
*/
- private Bitmap getPreloadedIcon(ComponentName componentName) {
+ private Bitmap getPreloadedIcon(ComponentName componentName, UserHandleCompat user) {
final String key = componentName.flattenToShortString();
+ // We don't keep icons for other profiles in persistent cache.
+ if (!user.equals(UserHandleCompat.myUserHandle())) {
+ return null;
+ }
+
if (DEBUG) Log.v(TAG, "looking for pre-load icon for " + key);
Bitmap icon = null;
FileInputStream resourceFile = null;
@@ -396,7 +445,11 @@
* @param componentName the component that should own the icon
* @returns true on success
*/
- public boolean deletePreloadedIcon(ComponentName componentName) {
+ public boolean deletePreloadedIcon(ComponentName componentName, UserHandleCompat user) {
+ // We don't keep icons for other profiles in persistent cache.
+ if (!user.equals(UserHandleCompat.myUserHandle())) {
+ return false;
+ }
if (componentName == null) {
return false;
}
diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java
index 374238c..c8541a9 100644
--- a/src/com/android/launcher3/InfoDropTarget.java
+++ b/src/com/android/launcher3/InfoDropTarget.java
@@ -49,6 +49,13 @@
Resources r = getResources();
mHoverColor = r.getColor(R.color.info_target_hover_tint);
mDrawable = (TransitionDrawable) getCurrentDrawable();
+
+ if (mDrawable == null) {
+ // TODO: investigate why this is ever happening. Presently only on one known device.
+ mDrawable = (TransitionDrawable) r.getDrawable(R.drawable.info_target_selector);
+ setCompoundDrawablesRelativeWithIntrinsicBounds(mDrawable, null, null, null);
+ }
+
if (null != mDrawable) {
mDrawable.setCrossFadeEnabled(true);
}
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 28cef13..2edde4f 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -17,6 +17,7 @@
package com.android.launcher3;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -29,6 +30,8 @@
import android.util.Log;
import android.widget.Toast;
+import com.android.launcher3.compat.UserHandleCompat;
+
import org.json.JSONObject;
import org.json.JSONStringer;
import org.json.JSONTokener;
@@ -280,19 +283,27 @@
final boolean exists = LauncherModel.shortcutExists(context, name, intent);
//final boolean allowDuplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true);
- // TODO-XXX: Disable duplicates for now
- if (!exists /* && allowDuplicate */) {
+ // If the intent specifies a package, make sure the package exists
+ String packageName = intent.getPackage();
+ if (packageName == null) {
+ packageName = intent.getComponent() == null ? null :
+ intent.getComponent().getPackageName();
+ }
+ if (packageName != null && !packageName.isEmpty()) {
+ UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
+ if (!LauncherModel.isValidPackage(context, packageName, myUserHandle)) {
+ if (DBG) Log.d(TAG, "Ignoring shortcut for absent package:" + intent);
+ continue;
+ }
+ }
+
+ if (!exists) {
// Generate a shortcut info to add into the model
ShortcutInfo info = getShortcutInfo(context, pendingInfo.data,
pendingInfo.launchIntent);
addShortcuts.add(info);
}
- /*
- else if (exists && !allowDuplicate) {
- result = INSTALL_SHORTCUT_IS_DUPLICATE;
- duplicateName = name;
- }
- */
+
}
// Notify the user once if we weren't able to place any duplicates
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 3fe4c60..74f16e3 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -17,17 +17,27 @@
package com.android.launcher3;
import android.content.ContentValues;
+import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.util.Log;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.util.Arrays;
/**
* Represents an item in the launcher.
*/
public class ItemInfo {
+
+ /**
+ * Intent extra to store the profile. Format: UserHandle
+ */
+ static final String EXTRA_PROFILE = "profile";
static final int NO_ID = -1;
@@ -102,6 +112,8 @@
*/
int[] dropPos = null;
+ UserHandleCompat user;
+
ItemInfo() {
}
@@ -114,6 +126,7 @@
screenId = info.screenId;
itemType = info.itemType;
container = info.container;
+ user = info.user;
// tempdebug:
LauncherModel.checkItemInfo(this);
}
@@ -129,9 +142,11 @@
/**
* Write the fields of this item to the DB
*
+ * @param context A context object to use for getting UserManagerCompat
* @param values
*/
- void onAddToDatabase(ContentValues values) {
+
+ void onAddToDatabase(Context context, ContentValues values) {
values.put(LauncherSettings.BaseLauncherColumns.ITEM_TYPE, itemType);
values.put(LauncherSettings.Favorites.CONTAINER, container);
values.put(LauncherSettings.Favorites.SCREEN, screenId);
@@ -139,6 +154,13 @@
values.put(LauncherSettings.Favorites.CELLY, cellY);
values.put(LauncherSettings.Favorites.SPANX, spanX);
values.put(LauncherSettings.Favorites.SPANY, spanY);
+ long serialNumber = UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
+ values.put(LauncherSettings.Favorites.PROFILE_ID, serialNumber);
+
+ 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");
+ }
}
void updateValuesWithCoordinates(ContentValues values, int cellX, int cellY) {
@@ -182,6 +204,7 @@
public String toString() {
return "Item(id=" + this.id + " type=" + this.itemType + " container=" + this.container
+ " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX
- + " spanY=" + spanY + " dropPos=" + dropPos + ")";
+ + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos)
+ + " user=" + user + ")";
}
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ff5b1eb..60efcea 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -24,6 +24,7 @@
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.TargetApi;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -44,12 +45,12 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.Rect;
@@ -79,6 +80,7 @@
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
+import android.view.Window;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
@@ -94,10 +96,12 @@
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
-
+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.DropTarget.DragObject;
import com.android.launcher3.PagedView.PageSwitchListener;
-
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
@@ -106,6 +110,9 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collection;
@@ -134,7 +141,6 @@
private static final int REQUEST_CREATE_SHORTCUT = 1;
private static final int REQUEST_CREATE_APPWIDGET = 5;
- private static final int REQUEST_PICK_APPLICATION = 6;
private static final int REQUEST_PICK_SHORTCUT = 7;
private static final int REQUEST_PICK_APPWIDGET = 9;
private static final int REQUEST_PICK_WALLPAPER = 10;
@@ -212,7 +218,6 @@
static final int APPWIDGET_HOST_ID = 1024;
public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
- public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT_FOLDER_CLOSE = 400;
private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
private static final int ACTIVITY_START_DELAY = 1000;
@@ -355,8 +360,7 @@
}
};
- private static ArrayList<PendingAddArguments> sPendingAddList
- = new ArrayList<PendingAddArguments>();
+ private static PendingAddArguments sPendingAddItem;
public static boolean sForceEnableRotation = isPropertyEnabled(FORCE_ENABLE_ROTATION_PROPERTY);
@@ -367,6 +371,7 @@
long screenId;
int cellX;
int cellY;
+ int appWidgetId;
}
private Stats mStats;
@@ -731,40 +736,31 @@
* Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
* a configuration step, this allows the proper animations to run after other transitions.
*/
- private boolean completeAdd(PendingAddArguments args) {
- boolean result = false;
+ private long completeAdd(PendingAddArguments args) {
+
+ long screenId = ensurePendingDropLayoutExists(args.screenId);
switch (args.requestCode) {
- case REQUEST_PICK_APPLICATION:
- completeAddApplication(args.intent, args.container, args.screenId, args.cellX,
- args.cellY);
- break;
- case REQUEST_PICK_SHORTCUT:
- processShortcut(args.intent);
- break;
case REQUEST_CREATE_SHORTCUT:
- completeAddShortcut(args.intent, args.container, args.screenId, args.cellX,
+ completeAddShortcut(args.intent, args.container, screenId, args.cellX,
args.cellY);
- result = true;
break;
case REQUEST_CREATE_APPWIDGET:
- int appWidgetId = args.intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
- completeAddAppWidget(appWidgetId, args.container, args.screenId, null, null);
- result = true;
+ completeAddAppWidget(args.appWidgetId, args.container, screenId, null, null);
break;
}
// Before adding this resetAddInfo(), after a shortcut was added to a workspace screen,
// if you turned the screen off and then back while in All Apps, Launcher would not
// return to the workspace. Clearing mAddInfo.container here fixes this issue
resetAddInfo();
- return result;
+ return screenId;
}
@Override
protected void onActivityResult(
final int requestCode, final int resultCode, final Intent data) {
// Reset the startActivity waiting flag
- mWaitingForResult = false;
- int pendingAddWidgetId = mPendingAddWidgetId;
+ setWaitingForResult(false);
+ final int pendingAddWidgetId = mPendingAddWidgetId;
mPendingAddWidgetId = -1;
Runnable exitSpringLoaded = new Runnable() {
@@ -780,7 +776,7 @@
data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
if (resultCode == RESULT_CANCELED) {
completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
- mWorkspace.removeExtraEmptyScreen(true, exitSpringLoaded,
+ mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
} else if (resultCode == RESULT_OK) {
addAppWidgetImpl(appWidgetId, mPendingAddInfo, null,
@@ -797,6 +793,7 @@
boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
requestCode == REQUEST_CREATE_APPWIDGET);
+ final boolean workspaceLocked = isWorkspaceLocked();
// We have special handling for widgets
if (isWidgetDrop) {
final int appWidgetId;
@@ -809,33 +806,46 @@
}
final int result;
- final Runnable onComplete;
if (appWidgetId < 0 || resultCode == RESULT_CANCELED) {
- Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not returned from the \\" +
- "widget configuration activity.");
+ Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " +
+ "returned from the widget configuration activity.");
result = RESULT_CANCELED;
completeTwoStageWidgetDrop(result, appWidgetId);
- onComplete = new Runnable() {
+ final Runnable onComplete = new Runnable() {
@Override
public void run() {
exitSpringLoadedDragModeDelayed(false, 0, null);
}
};
+ if (workspaceLocked) {
+ // No need to remove the empty screen if we're mid-binding, as the
+ // the bind will not add the empty screen.
+ mWorkspace.postDelayed(onComplete, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
+ } else {
+ mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
+ ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
+ }
} else {
- result = resultCode;
- final CellLayout dropLayout =
- (CellLayout) mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
- dropLayout.setDropPending(true);
- onComplete = new Runnable() {
- @Override
- public void run() {
- completeTwoStageWidgetDrop(result, appWidgetId);
- dropLayout.setDropPending(false);
- }
- };
+ if (!workspaceLocked) {
+ mPendingAddInfo.screenId = ensurePendingDropLayoutExists(mPendingAddInfo.screenId);
+ final CellLayout dropLayout = mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
+
+ dropLayout.setDropPending(true);
+ final Runnable onComplete = new Runnable() {
+ @Override
+ public void run() {
+ completeTwoStageWidgetDrop(resultCode, appWidgetId);
+ dropLayout.setDropPending(false);
+ }
+ };
+ mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
+ ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
+ } else {
+ PendingAddArguments args = preparePendingAddArgs(requestCode, data, appWidgetId,
+ mPendingAddInfo);
+ sPendingAddItem = args;
+ }
}
- mWorkspace.removeExtraEmptyScreen(true, onComplete, ON_ACTIVITY_RESULT_ANIMATION_DELAY,
- false);
return;
}
@@ -845,27 +855,54 @@
// For example, the user would PICK_SHORTCUT for "Music playlist", and we
// launch over to the Music app to actually CREATE_SHORTCUT.
if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) {
- final PendingAddArguments args = new PendingAddArguments();
- args.requestCode = requestCode;
- args.intent = data;
- args.container = mPendingAddInfo.container;
- args.screenId = mPendingAddInfo.screenId;
- args.cellX = mPendingAddInfo.cellX;
- args.cellY = mPendingAddInfo.cellY;
+ final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1,
+ mPendingAddInfo);
if (isWorkspaceLocked()) {
- sPendingAddList.add(args);
+ sPendingAddItem = args;
} else {
completeAdd(args);
+ mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
+ ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
}
- mWorkspace.removeExtraEmptyScreen(true, exitSpringLoaded,
- ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
} else if (resultCode == RESULT_CANCELED) {
- mWorkspace.removeExtraEmptyScreen(true, exitSpringLoaded,
+ mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
}
mDragLayer.clearAnimatedView();
}
+ private PendingAddArguments preparePendingAddArgs(int requestCode, Intent data, int
+ appWidgetId, ItemInfo info) {
+ PendingAddArguments args = new PendingAddArguments();
+ args.requestCode = requestCode;
+ args.intent = data;
+ args.container = info.container;
+ args.screenId = info.screenId;
+ args.cellX = info.cellX;
+ args.cellY = info.cellY;
+ args.appWidgetId = appWidgetId;
+ return args;
+ }
+
+ /**
+ * Check to see if a given screen id exists. If not, create it at the end, return the new id.
+ *
+ * @param screenId the screen id to check
+ * @return the new screen, or screenId if it exists
+ */
+ private long ensurePendingDropLayoutExists(long screenId) {
+ CellLayout dropLayout =
+ (CellLayout) mWorkspace.getScreenWithId(screenId);
+ if (dropLayout == null) {
+ // it's possible that the add screen was removed because it was
+ // empty and a re-bind occurred
+ mWorkspace.addExtraEmptyScreen();
+ return mWorkspace.commitExtraEmptyScreen();
+ } else {
+ return screenId;
+ }
+ }
+
private void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
CellLayout cellLayout =
(CellLayout) mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
@@ -936,7 +973,7 @@
mPaused = false;
sPausedFromUserAction = false;
if (mRestoring || mOnResumeNeedsLoad) {
- mWorkspaceLoading = true;
+ setWorkspaceLoading(true);
mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
mRestoring = false;
mOnResumeNeedsLoad = false;
@@ -1190,7 +1227,7 @@
mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
mPendingAddWidgetInfo = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID);
- mWaitingForResult = true;
+ setWaitingForResult(true);
mRestoring = true;
}
@@ -1255,6 +1292,10 @@
}
});
widgetButton.setOnTouchListener(getHapticFeedbackTouchListener());
+ if (Utilities.isLmp()) {
+ ((TextView) widgetButton).setCompoundDrawablesWithIntrinsicBounds(0,
+ R.drawable.widget_button_l, 0, 0);
+ }
View wallpaperButton = findViewById(R.id.wallpaper_button);
wallpaperButton.setOnClickListener(new OnClickListener() {
@@ -1267,6 +1308,11 @@
});
wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
+ if (Utilities.isLmp()) {
+ ((TextView) wallpaperButton).setCompoundDrawablesWithIntrinsicBounds(0,
+ R.drawable.wallpaper_button_l, 0, 0);
+ }
+
View settingsButton = findViewById(R.id.settings_button);
if (hasSettings()) {
settingsButton.setOnClickListener(new OnClickListener() {
@@ -1278,6 +1324,10 @@
}
});
settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
+ if (Utilities.isLmp()) {
+ ((TextView) settingsButton).setCompoundDrawablesWithIntrinsicBounds(0,
+ R.drawable.setting_button_l, 0, 0);
+ }
} else {
settingsButton.setVisibility(View.GONE);
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) widgetButton.getLayoutParams();
@@ -1368,38 +1418,6 @@
}
/**
- * Add an application shortcut to the workspace.
- *
- * @param data The intent describing the application.
- * @param cellInfo The position on screen where to create the shortcut.
- */
- void completeAddApplication(Intent data, long container, long screenId, int cellX, int cellY) {
- final int[] cellXY = mTmpAddItemCellCoordinates;
- final CellLayout layout = getCellLayout(container, screenId);
-
- // First we check if we already know the exact location where we want to add this item.
- if (cellX >= 0 && cellY >= 0) {
- cellXY[0] = cellX;
- cellXY[1] = cellY;
- } else if (!layout.findCellForSpan(cellXY, 1, 1)) {
- showOutOfSpaceMessage(isHotseatLayout(layout));
- return;
- }
-
- final ShortcutInfo info = mModel.getShortcutInfo(getPackageManager(), data, this);
-
- if (info != null) {
- info.setActivity(this, data.getComponent(), Intent.FLAG_ACTIVITY_NEW_TASK |
- Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
- info.container = ItemInfo.NO_ID;
- mWorkspace.addApplicationShortcut(info, layout, container, screenId, cellXY[0], cellXY[1],
- isWorkspaceLocked(), cellX, cellY);
- } else {
- Log.e(TAG, "Couldn't find ActivityInfo for selected application: " + data);
- }
- }
-
- /**
* Add a shortcut to the workspace.
*
* @param data The intent describing the shortcut.
@@ -1601,6 +1619,9 @@
mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE
| LauncherModel.LOADER_FLAG_MIGRATE_SHORTCUTS);
+ } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
+ || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
+ getModel().forceReload();
}
}
};
@@ -1613,16 +1634,61 @@
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_USER_PRESENT);
+ // For handling managed profiles
+ filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
+ filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
if (ENABLE_DEBUG_INTENTS) {
filter.addAction(DebugIntents.DELETE_DATABASE);
filter.addAction(DebugIntents.MIGRATE_DATABASE);
}
registerReceiver(mReceiver, filter);
FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
+ setupTransparentSystemBarsForLmp();
mAttached = true;
mVisible = true;
}
+ /**
+ * Sets up transparent navigation and status bars in LMP.
+ * This method is a no-op for other platform versions.
+ */
+ @TargetApi(19)
+ private void setupTransparentSystemBarsForLmp() {
+ // TODO(sansid): use the APIs directly when compiling against L sdk.
+ // Currently we use reflection to access the flags and the API to set the transparency
+ // on the System bars.
+ if (Utilities.isLmp()) {
+ try {
+ getWindow().getAttributes().systemUiVisibility |=
+ (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
+ | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
+ Field drawsSysBackgroundsField = WindowManager.LayoutParams.class.getField(
+ "FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS");
+ getWindow().addFlags(drawsSysBackgroundsField.getInt(null));
+
+ Method setStatusBarColorMethod =
+ Window.class.getDeclaredMethod("setStatusBarColor", int.class);
+ Method setNavigationBarColorMethod =
+ Window.class.getDeclaredMethod("setNavigationBarColor", int.class);
+ setStatusBarColorMethod.invoke(getWindow(), Color.TRANSPARENT);
+ setNavigationBarColorMethod.invoke(getWindow(), Color.TRANSPARENT);
+ } catch (NoSuchFieldException e) {
+ Log.w(TAG, "NoSuchFieldException while setting up transparent bars");
+ } catch (NoSuchMethodException ex) {
+ Log.w(TAG, "NoSuchMethodException while setting up transparent bars");
+ } catch (IllegalAccessException e) {
+ Log.w(TAG, "IllegalAccessException while setting up transparent bars");
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "IllegalArgumentException while setting up transparent bars");
+ } catch (InvocationTargetException e) {
+ Log.w(TAG, "InvocationTargetException while setting up transparent bars");
+ } finally {}
+ }
+ }
+
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
@@ -1796,7 +1862,7 @@
getWindow().closeAllPanels();
// Whatever we were doing is hereby canceled.
- mWaitingForResult = false;
+ setWaitingForResult(false);
}
@Override
@@ -1974,7 +2040,9 @@
@Override
public void startActivityForResult(Intent intent, int requestCode) {
- if (requestCode >= 0) mWaitingForResult = true;
+ if (requestCode >= 0) {
+ setWaitingForResult(true);
+ }
super.startActivityForResult(intent, requestCode);
}
@@ -2088,6 +2156,24 @@
return mWorkspaceLoading;
}
+ private void setWorkspaceLoading(boolean value) {
+ boolean isLocked = isWorkspaceLocked();
+ mWorkspaceLoading = value;
+ if (isLocked != isWorkspaceLocked()) {
+ onWorkspaceLockedChanged();
+ }
+ }
+
+ private void setWaitingForResult(boolean value) {
+ boolean isLocked = isWorkspaceLocked();
+ mWaitingForResult = value;
+ if (isLocked != isWorkspaceLocked()) {
+ onWorkspaceLockedChanged();
+ }
+ }
+
+ protected void onWorkspaceLockedChanged() { }
+
private void resetAddInfo() {
mPendingAddInfo.container = ItemInfo.NO_ID;
mPendingAddInfo.screenId = -1;
@@ -2126,7 +2212,7 @@
};
completeAddAppWidget(appWidgetId, info.container, info.screenId, boundWidget,
appWidgetInfo);
- mWorkspace.removeExtraEmptyScreen(true, onComplete, delay, false);
+ mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
}
}
@@ -2220,21 +2306,7 @@
}
void processShortcut(Intent intent) {
- // Handle case where user selected "Applications"
- String applicationName = getResources().getString(R.string.group_applications);
- String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
-
- if (applicationName != null && applicationName.equals(shortcutName)) {
- Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
- mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-
- Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
- pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent);
- pickIntent.putExtra(Intent.EXTRA_TITLE, getText(R.string.title_select_application));
- Utilities.startActivityForResultSafely(this, pickIntent, REQUEST_PICK_APPLICATION);
- } else {
- Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT);
- }
+ Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT);
}
void processWallpaper(Intent intent) {
@@ -2652,19 +2724,37 @@
boolean startActivity(View v, Intent intent, Object tag) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
try {
// Only launch using the new animation if the shortcut has not opted out (this is a
// private contract between launcher and may be ignored in the future).
boolean useLaunchAnimation = (v != null) &&
!intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
+ LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
+ UserManagerCompat userManager = UserManagerCompat.getInstance(this);
+
+ UserHandleCompat user = null;
+ if (intent.hasExtra(AppInfo.EXTRA_PROFILE)) {
+ long serialNumber = intent.getLongExtra(AppInfo.EXTRA_PROFILE, -1);
+ user = userManager.getUserForSerialNumber(serialNumber);
+ }
+
+ Bundle optsBundle = null;
if (useLaunchAnimation) {
ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0,
v.getMeasuredWidth(), v.getMeasuredHeight());
+ optsBundle = opts.toBundle();
+ }
+ if (useLaunchAnimation && Utilities.isLmp()) {
+ ActivityOptions opts = ActivityOptions.makeCustomAnimation(this, R.anim.task_open_enter, R.anim.no_anim);
+ optsBundle = opts.toBundle();
+ }
- startActivity(intent, opts.toBundle());
+ if (user == null || user.equals(UserHandleCompat.myUserHandle())) {
+ // Could be launching some bookkeeping activity
+ startActivity(intent, optsBundle);
} else {
- startActivity(intent);
+ launcherApps.startActivityForProfile(intent.getComponent(),
+ intent.getSourceBounds(), optsBundle, user);
}
return true;
} catch (SecurityException e) {
@@ -2864,23 +2954,22 @@
} else {
return false;
}
+ } else {
+ return false;
}
}
- if (!(v instanceof CellLayout)) {
- v = (View) v.getParent().getParent();
- }
-
- resetAddInfo();
- CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag();
- // This happens when long clicking an item with the dpad/trackball
- if (longClickCellInfo == null) {
- return true;
+ CellLayout.CellInfo longClickCellInfo = null;
+ View itemUnderLongClick = null;
+ if (v.getTag() instanceof ItemInfo) {
+ ItemInfo info = (ItemInfo) v.getTag();
+ longClickCellInfo = new CellLayout.CellInfo(v, info);;
+ itemUnderLongClick = longClickCellInfo.cell;
+ resetAddInfo();
}
// The hotseat touch handling does not go through Workspace, and we always allow long press
// on hotseat items.
- final View itemUnderLongClick = longClickCellInfo.cell;
final boolean inHotseat = isHotseatLayout(v);
boolean allowLongPress = inHotseat || mWorkspace.allowLongPress();
if (allowLongPress && !mDragController.isDragging()) {
@@ -2888,7 +2977,6 @@
// User long pressed on empty space
mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
- // Disabling reordering until we sort out some issues.
if (mWorkspace.isInOverviewMode()) {
mWorkspace.startReordering(v);
} else {
@@ -3831,7 +3919,7 @@
* Implementation of the method from LauncherModel.Callbacks.
*/
public void startBinding() {
- mWorkspaceLoading = true;
+ setWorkspaceLoading(true);
// If we're starting binding all over again, clear any bind calls we'd postponed in
// the past (see waitUntilResume) -- we don't need them since we're starting binding
@@ -3931,7 +4019,7 @@
}
// Remove the extra empty screen
- mWorkspace.removeExtraEmptyScreen(false, null);
+ mWorkspace.removeExtraEmptyScreen(false, false);
if (!LauncherAppState.isDisableAllApps() &&
addedApps != null && mAppsCustomizeContent != null) {
@@ -4135,20 +4223,31 @@
mWorkspace.restoreInstanceStateForRemainingPages();
- // If we received the result of any pending adds while the loader was running (e.g. the
- // widget configuration forced an orientation change), process them now.
- for (int i = 0; i < sPendingAddList.size(); i++) {
- completeAdd(sPendingAddList.get(i));
- }
- sPendingAddList.clear();
-
// Update the market app icon as necessary (the other icons will be managed in response to
// package changes in bindSearchablesChanged()
if (!DISABLE_MARKET_BUTTON) {
updateAppMarketIcon();
}
- mWorkspaceLoading = false;
+ setWorkspaceLoading(false);
+
+ // If we received the result of any pending adds while the loader was running (e.g. the
+ // widget configuration forced an orientation change), process them now.
+ if (sPendingAddItem != null) {
+ final long screenId = completeAdd(sPendingAddItem);
+
+ // TODO: this moves the user to the page where the pending item was added. Ideally,
+ // the screen would be guaranteed to exist after bind, and the page would be set through
+ // the workspace restore process.
+ mWorkspace.post(new Runnable() {
+ @Override
+ public void run() {
+ mWorkspace.snapToScreenId(screenId);
+ }
+ });
+ sPendingAddItem = null;
+ }
+
if (upgradePath) {
mWorkspace.getUniqueComponents(true, null);
mIntentsOnWorkspaceFromUpgradePath = mWorkspace.getUniqueComponents(true, null);
@@ -4270,10 +4369,10 @@
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindComponentsRemoved(final ArrayList<String> packageNames,
- final ArrayList<AppInfo> appInfos) {
+ final ArrayList<AppInfo> appInfos, final UserHandleCompat user) {
Runnable r = new Runnable() {
public void run() {
- bindComponentsRemoved(packageNames, appInfos);
+ bindComponentsRemoved(packageNames, appInfos, user);
}
};
if (waitUntilResume(r)) {
@@ -4281,10 +4380,10 @@
}
if (!packageNames.isEmpty()) {
- mWorkspace.removeItemsByPackageName(packageNames);
+ mWorkspace.removeItemsByPackageName(packageNames, user);
}
if (!appInfos.isEmpty()) {
- mWorkspace.removeItemsByApplicationInfo(appInfos);
+ mWorkspace.removeItemsByApplicationInfo(appInfos, user);
}
// Notify the drag controller
@@ -4400,6 +4499,17 @@
}
}
+ /**
+ * This method indicates whether or not we should suggest default wallpaper dimensions
+ * when our wallpaper cropper was not yet used to set a wallpaper.
+ */
+ protected boolean overrideWallpaperDimensions() {
+ return true;
+ }
+
+ protected boolean shouldClingFocusHotseatApp() {
+ return false;
+ }
protected String getFirstRunClingSearchBarHint() {
return "";
}
@@ -4440,7 +4550,9 @@
public void dismissFolderCling(View v) {
mLauncherClings.dismissFolderCling(v);
}
-
+ public void markFolderClingDismissedIfNecessary() {
+ mLauncherClings.markFolderClingDismissedIfNecessary();
+ }
/**
* To be overridden by subclasses to indicate that there is an activity to launch
@@ -4555,16 +4667,27 @@
}
public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
- ResolveInfo ri = getPackageManager().resolveActivity(appLaunchIntent, 0);
- if (ri == null) {
+ // Called from search suggestion, not supported in other profiles.
+ final UserHandleCompat myUser = UserHandleCompat.myUserHandle();
+ LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
+ LauncherActivityInfoCompat activityInfo = launcherApps.resolveActivity(appLaunchIntent,
+ myUser);
+ if (activityInfo == null) {
return null;
}
- return new AppInfo(getPackageManager(), ri, mIconCache, null);
+ return new AppInfo(this, activityInfo, myUser, mIconCache, null);
}
public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
Bitmap icon) {
- return new ShortcutInfo(shortcutIntent, caption, icon);
+ // Called from search suggestion, not supported in other profiles.
+ return createShortcutDragInfo(shortcutIntent, caption, icon,
+ UserHandleCompat.myUserHandle());
+ }
+
+ public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
+ Bitmap icon, UserHandleCompat user) {
+ return new ShortcutInfo(shortcutIntent, caption, icon, user);
}
protected void moveWorkspaceToDefaultScreen() {
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 5ddafea..79bc084 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -24,13 +24,15 @@
import android.os.Handler;
import android.util.Log;
+import com.android.launcher3.compat.LauncherAppsCompat;
+
import java.lang.ref.WeakReference;
public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
private static final String TAG = "LauncherAppState";
private static final String SHARED_PREFERENCES_KEY = "com.android.launcher3.prefs";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true; // STOPSHIP(cwren) temporary for debugging
private final AppFilter mAppFilter;
private final BuildInfo mBuildInfo;
@@ -92,16 +94,11 @@
mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
mBuildInfo = BuildInfo.loadByName(sContext.getString(R.string.build_info_class));
mModel = new LauncherModel(this, mIconCache, mAppFilter);
+ final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(sContext);
+ launcherApps.addOnAppsChangedListener(mModel);
// Register intent receivers
- IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
- filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- filter.addDataScheme("package");
- sContext.registerReceiver(mModel, filter);
- filter = new IntentFilter();
- filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
- filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_LOCALE_CHANGED);
filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
sContext.registerReceiver(mModel, filter);
@@ -117,7 +114,7 @@
resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true,
mFavoritesObserver);
}
-
+
public void recreateWidgetPreviewDb() {
if (mWidgetPreviewCacheDb != null) {
mWidgetPreviewCacheDb.close();
@@ -130,6 +127,8 @@
*/
public void onTerminate() {
sContext.unregisterReceiver(mModel);
+ final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(sContext);
+ launcherApps.removeOnAppsChangedListener(mModel);
ContentResolver resolver = sContext.getContentResolver();
resolver.unregisterContentObserver(mFavoritesObserver);
diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java
index 51a649a..f47fd13 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java
@@ -21,6 +21,7 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.RemoteViews;
@@ -36,6 +37,8 @@
private int mPreviousOrientation;
private DragLayer mDragLayer;
+ private float mSlop;
+
public LauncherAppWidgetHostView(Context context) {
super(context);
mContext = context;
@@ -90,6 +93,11 @@
case MotionEvent.ACTION_CANCEL:
mLongPressHelper.cancelLongPress();
break;
+ case MotionEvent.ACTION_MOVE:
+ if (!Utilities.pointInView(this, ev.getX(), ev.getY(), mSlop)) {
+ mLongPressHelper.cancelLongPress();
+ }
+ break;
}
// Otherwise continue letting touch events fall through to children
@@ -104,11 +112,22 @@
case MotionEvent.ACTION_CANCEL:
mLongPressHelper.cancelLongPress();
break;
+ case MotionEvent.ACTION_MOVE:
+ if (!Utilities.pointInView(this, ev.getX(), ev.getY(), mSlop)) {
+ mLongPressHelper.cancelLongPress();
+ }
+ break;
}
return false;
}
@Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ }
+
+ @Override
public void cancelLongPress() {
super.cancelLongPress();
mLongPressHelper.cancelLongPress();
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
index 950828e..bec1f9e 100644
--- a/src/com/android/launcher3/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -19,6 +19,9 @@
import android.appwidget.AppWidgetHostView;
import android.content.ComponentName;
import android.content.ContentValues;
+import android.content.Context;
+
+import com.android.launcher3.compat.UserHandleCompat;
/**
* Represents a widget (either instantiated or about to be) in the Launcher.
@@ -59,11 +62,13 @@
// to indicate that they should be calculated based on the layout and minWidth/minHeight
spanX = -1;
spanY = -1;
+ // We only support app widgets on current user.
+ user = UserHandleCompat.myUserHandle();
}
@Override
- void onAddToDatabase(ContentValues values) {
- super.onAddToDatabase(values);
+ 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());
}
diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java
index cab55c7..5663314 100644
--- a/src/com/android/launcher3/LauncherBackupHelper.java
+++ b/src/com/android/launcher3/LauncherBackupHelper.java
@@ -28,6 +28,8 @@
import com.android.launcher3.backup.BackupProtos.Resource;
import com.android.launcher3.backup.BackupProtos.Screen;
import com.android.launcher3.backup.BackupProtos.Widget;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.compat.UserHandleCompat;
import android.app.backup.BackupDataInputStream;
import android.app.backup.BackupDataOutput;
@@ -108,6 +110,7 @@
Favorites.SPANX, // 14
Favorites.SPANY, // 15
Favorites.TITLE, // 16
+ Favorites.PROFILE_ID, // 17
};
private static final int ID_INDEX = 0;
@@ -127,6 +130,7 @@
private static final int SPANX_INDEX = 14;
private static final int SPANY_INDEX = 15;
private static final int TITLE_INDEX = 16;
+ private static final int PROFILE_ID_INDEX = 17;
private static final String[] SCREEN_PROJECTION = {
WorkspaceScreens._ID, // 0
@@ -295,6 +299,11 @@
Set<String> savedIds = getSavedIdsByType(Key.FAVORITE, in);
if (DEBUG) Log.d(TAG, "favorite savedIds.size()=" + savedIds.size());
+ // Don't backup apps in other profiles for now.
+ UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
+ long userSerialNumber =
+ UserManagerCompat.getInstance(mContext).getSerialNumberForUser(myUserHandle);
+
// persist things that have changed since the last backup
ContentResolver cr = mContext.getContentResolver();
Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
@@ -304,16 +313,22 @@
cursor.moveToPosition(-1);
while(cursor.moveToNext()) {
final long id = cursor.getLong(ID_INDEX);
- final long updateTime = cursor.getLong(ID_MODIFIED);
- Key key = getKey(Key.FAVORITE, id);
- keys.add(key);
- final String backupKey = keyToBackupKey(key);
- currentIds.add(backupKey);
- if (!savedIds.contains(backupKey) || updateTime >= in.t) {
- byte[] blob = packFavorite(cursor);
- writeRowToBackup(key, blob, out, data);
+ final long profileId = cursor.getLong(PROFILE_ID_INDEX);
+ if (userSerialNumber == profileId) {
+ final long updateTime = cursor.getLong(ID_MODIFIED);
+ Key key = getKey(Key.FAVORITE, id);
+ keys.add(key);
+ final String backupKey = keyToBackupKey(key);
+ currentIds.add(backupKey);
+ if (!savedIds.contains(backupKey) || updateTime >= in.t) {
+ byte[] blob = packFavorite(cursor);
+ writeRowToBackup(key, blob, out, data);
+ } else {
+ if (VERBOSE) Log.v(TAG, "favorite " + id + " was too old: " + updateTime);
+ }
} else {
- if (VERBOSE) Log.v(TAG, "favorite " + id + " was too old: " + updateTime);
+ if (VERBOSE) Log.v(TAG, "favorite " + id + " is for other profile: "
+ + profileId);
}
}
} finally {
@@ -459,10 +474,15 @@
Set<String> savedIds = getSavedIdsByType(Key.ICON, in);
if (DEBUG) Log.d(TAG, "icon savedIds.size()=" + savedIds.size());
+ // Don't backup apps in other profiles for now.
+ UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
+ long userSerialNumber =
+ UserManagerCompat.getInstance(mContext).getSerialNumberForUser(myUserHandle);
int startRows = out.rows;
if (DEBUG) Log.d(TAG, "starting here: " + startRows);
- String where = Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPLICATION + " OR " +
- Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_SHORTCUT;
+ String where = "(" + Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPLICATION + " OR " +
+ Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_SHORTCUT + ") AND " +
+ Favorites.PROFILE_ID + "=" + userSerialNumber;
Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
where, null, null);
Set<String> currentIds = new HashSet<String>(cursor.getCount());
@@ -492,9 +512,9 @@
if (DEBUG) Log.d(TAG, "I can count this high: " + out.rows);
if ((out.rows - startRows) < MAX_ICONS_PER_PASS) {
if (VERBOSE) Log.v(TAG, "saving icon " + backupKey);
- Bitmap icon = mIconCache.getIcon(intent);
+ Bitmap icon = mIconCache.getIcon(intent, myUserHandle);
keys.add(key);
- if (icon != null && !mIconCache.isDefaultIcon(icon)) {
+ if (icon != null && !mIconCache.isDefaultIcon(icon, myUserHandle)) {
byte[] blob = packIcon(dpi, icon);
writeRowToBackup(key, blob, out, data);
}
@@ -557,6 +577,7 @@
}
return;
} else {
+ if (VERBOSE) Log.v(TAG, "saving restored icon as: " + key.name);
IconCache.preloadIcon(mContext, ComponentName.unflattenFromString(key.name),
icon, res.dpi);
}
@@ -799,9 +820,15 @@
if (!TextUtils.isEmpty(title)) {
favorite.title = title;
}
- String intent = c.getString(INTENT_INDEX);
- if (!TextUtils.isEmpty(intent)) {
- favorite.intent = intent;
+ String intentDescription = c.getString(INTENT_INDEX);
+ if (!TextUtils.isEmpty(intentDescription)) {
+ try {
+ Intent intent = Intent.parseUri(intentDescription, 0);
+ intent.removeExtra(ItemInfo.EXTRA_PROFILE);
+ favorite.intent = intent.toUri(0);
+ } catch (URISyntaxException e) {
+ Log.e(TAG, "Invalid intent", e);
+ }
}
favorite.itemType = c.getInt(ITEM_TYPE_INDEX);
if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
@@ -854,6 +881,11 @@
values.put(Favorites.APPWIDGET_ID, favorite.appWidgetId);
}
+ UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
+ long userSerialNumber =
+ UserManagerCompat.getInstance(mContext).getSerialNumberForUser(myUserHandle);
+ values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber);
+
// Let LauncherModel know we've been here.
values.put(LauncherSettings.Favorites.RESTORED, 1);
diff --git a/src/com/android/launcher3/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java
index 97138ee..1176aa5 100644
--- a/src/com/android/launcher3/LauncherClings.java
+++ b/src/com/android/launcher3/LauncherClings.java
@@ -264,21 +264,13 @@
WORKSPACE_CLING_DISMISSED_KEY, false)) {
Cling c = initCling(R.id.workspace_cling, 0, false, true);
c.updateWorkspaceBubblePosition();
-
- try {
- // We only enable the focused hotseat app if we are preinstalled
- PackageManager pm = mLauncher.getPackageManager();
- ApplicationInfo ai = pm.getApplicationInfo(mLauncher.getPackageName(), 0);
- if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- // Set the focused hotseat app
- c.setFocusedHotseatApp(mLauncher.getFirstRunFocusedHotseatAppDrawableId(),
- mLauncher.getFirstRunFocusedHotseatAppRank(),
- mLauncher.getFirstRunFocusedHotseatAppComponentName(),
- mLauncher.getFirstRunFocusedHotseatAppBubbleTitle(),
- mLauncher.getFirstRunFocusedHotseatAppBubbleDescription());
- }
- } catch (PackageManager.NameNotFoundException e) {
- e.printStackTrace();
+ if (mLauncher.shouldClingFocusHotseatApp()) {
+ // Set the focused hotseat app
+ c.setFocusedHotseatApp(mLauncher.getFirstRunFocusedHotseatAppDrawableId(),
+ mLauncher.getFirstRunFocusedHotseatAppRank(),
+ mLauncher.getFirstRunFocusedHotseatAppComponentName(),
+ mLauncher.getFirstRunFocusedHotseatAppBubbleTitle(),
+ mLauncher.getFirstRunFocusedHotseatAppBubbleDescription());
}
} else {
removeCling(R.id.workspace_cling);
@@ -315,12 +307,6 @@
editor.commit();
}
- public void markFolderClingDismissed() {
- SharedPreferences.Editor editor = mLauncher.getSharedPrefs().edit();
- editor.putBoolean(LauncherClings.FOLDER_CLING_DISMISSED_KEY, true);
- editor.apply();
- }
-
/** Removes the cling outright from the DragLayer */
private void removeCling(int id) {
final View cling = mLauncher.findViewById(id);
@@ -415,6 +401,15 @@
mLauncher.getSearchBar().showSearchBar(true);
}
+ public void markFolderClingDismissedIfNecessary() {
+ SharedPreferences prefs = mLauncher.getSharedPrefs();
+ if (!prefs.getBoolean(FOLDER_CLING_DISMISSED_KEY, false)) {
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putBoolean(FOLDER_CLING_DISMISSED_KEY, true);
+ editor.apply();
+ }
+ }
+
public void dismissMigrationClingCopyApps(View v) {
// Copy the shortcuts from the old database
LauncherModel model = mLauncher.getModel();
@@ -435,11 +430,8 @@
}
public void dismissMigrationClingUseDefault(View v) {
- // Clear the workspace
- LauncherModel model = mLauncher.getModel();
- model.resetLoadedState(false, true);
- model.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
- LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE);
+ // Don't need to do anything special here. We've already loaded the default workspace,
+ // (which is the default loader behavior triggered from Launcher#onCreate.).
// Disable the migration cling
dismissMigrationCling();
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index d8645aa..141368c 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -25,6 +25,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -44,6 +45,10 @@
import android.util.Log;
import android.util.Pair;
+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.InstallWidgetReceiver.WidgetMimeTypeHandlerData;
import java.lang.ref.WeakReference;
@@ -67,14 +72,16 @@
* LauncherModel object held in a static. Also provide APIs for updating the database state
* for the Launcher.
*/
-public class LauncherModel extends BroadcastReceiver {
+public class LauncherModel extends BroadcastReceiver
+ implements LauncherAppsCompat.OnAppsChangedListenerCompat {
static final boolean DEBUG_LOADERS = false;
+ private static final boolean DEBUG_RECEIVER = true; // STOPSHIP(cwren) temporary for debugging
+
static final String TAG = "Launcher.Model";
// true = use a "More Apps" folder for non-workspace apps on upgrade
// false = strew non-workspace apps across the workspace on upgrade
public static final boolean UPGRADE_USE_MORE_APPS_FOLDER = false;
-
public static final int LOADER_FLAG_NONE = 0;
public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0;
public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1;
@@ -97,6 +104,7 @@
private static final int MAIN_THREAD_NORMAL_RUNNABLE = 0;
private static final int MAIN_THREAD_BINDING_RUNNABLE = 1;
+ private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings";
private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
static {
@@ -152,10 +160,12 @@
// </ only access in worker thread >
private IconCache mIconCache;
- private Bitmap mDefaultIcon;
protected int mPreviousConfigMcc;
+ private final LauncherAppsCompat mLauncherApps;
+ private final UserManagerCompat mUserManager;
+
public interface Callbacks {
public boolean setLoadOnResume();
public int getCurrentWorkspaceScreen();
@@ -175,7 +185,7 @@
public void bindAppsUpdated(ArrayList<AppInfo> apps);
public void updatePackageState(String pkgName, int state);
public void bindComponentsRemoved(ArrayList<String> packageNames,
- ArrayList<AppInfo> appInfos);
+ ArrayList<AppInfo> appInfos, UserHandleCompat user);
public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts);
public void bindSearchablesChanged();
public boolean isAllAppsButtonRank(int rank);
@@ -189,15 +199,26 @@
LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
Context context = app.getContext();
- ContentResolver contentResolver = context.getContentResolver();
mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
- ContentProviderClient client = contentResolver.acquireContentProviderClient(
- Uri.parse(context.getString(R.string.old_launcher_provider_uri)));
- mOldContentProviderExists = (client != null);
- if (client != null) {
- client.release();
+ String oldProvider = context.getString(R.string.old_launcher_provider_uri);
+ // This may be the same as MIGRATE_AUTHORITY, or it may be replaced by a different
+ // resource string.
+ String redirectAuthority = Uri.parse(oldProvider).getAuthority();
+ ProviderInfo providerInfo =
+ context.getPackageManager().resolveContentProvider(MIGRATE_AUTHORITY, 0);
+ ProviderInfo redirectProvider =
+ context.getPackageManager().resolveContentProvider(redirectAuthority, 0);
+
+ Log.d(TAG, "Old launcher provider: " + oldProvider);
+ mOldContentProviderExists = (providerInfo != null) && (redirectProvider != null);
+
+ if (mOldContentProviderExists) {
+ Log.d(TAG, "Old launcher provider exists.");
+ } else {
+ Log.d(TAG, "Old launcher provider does not exist.");
}
+
mApp = app;
mBgAllAppsList = new AllAppsList(iconCache, appFilter);
mIconCache = iconCache;
@@ -205,6 +226,8 @@
final Resources res = context.getResources();
Configuration config = res.getConfiguration();
mPreviousConfigMcc = config.mcc;
+ mLauncherApps = LauncherAppsCompat.getInstance(context);
+ mUserManager = UserManagerCompat.getInstance(context);
}
/** Runs the specified runnable immediately if called from the main thread, otherwise it is
@@ -324,7 +347,7 @@
Iterator<AppInfo> iter = allAppsApps.iterator();
while (iter.hasNext()) {
ItemInfo a = iter.next();
- if (LauncherModel.appWasRestored(ctx, a.getIntent())) {
+ if (LauncherModel.appWasRestored(ctx, a.getIntent(), a.user)) {
restoredAppsFinal.add((AppInfo) a);
}
}
@@ -340,7 +363,8 @@
for (AppInfo info : restoredAppsFinal) {
final Intent intent = info.getIntent();
if (intent != null) {
- mIconCache.deletePreloadedIcon(intent.getComponent());
+ mIconCache.deletePreloadedIcon(intent.getComponent(),
+ info.user);
}
}
callbacks.bindAppsUpdated(restoredAppsFinal);
@@ -392,7 +416,7 @@
if (LauncherModel.shortcutExists(context, name, launchIntent)) {
// Only InstallShortcutReceiver sends us shortcutInfos, ignore them
if (a instanceof AppInfo &&
- LauncherModel.appWasRestored(context, launchIntent)) {
+ LauncherModel.appWasRestored(context, launchIntent, a.user)) {
restoredAppsFinal.add((AppInfo) a);
}
continue;
@@ -482,15 +506,6 @@
runOnWorkerThread(r);
}
- public Bitmap getFallbackIcon() {
- if (mDefaultIcon == null) {
- final Context context = LauncherAppState.getInstance().getContext();
- mDefaultIcon = Utilities.createIconBitmap(
- mIconCache.getFullResDefaultActivityIcon(), context);
- }
- return Bitmap.createBitmap(mDefaultIcon);
- }
-
public void unbindItemInfosAndClearQueuedBindRunnables() {
if (sWorkerThread.getThreadId() == Process.myTid()) {
throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " +
@@ -498,7 +513,9 @@
}
// Clear any deferred bind runnables
- mDeferredBindRunnables.clear();
+ synchronized (mDeferredBindRunnables) {
+ mDeferredBindRunnables.clear();
+ }
// Remove any queued bind runnables
mHandler.cancelAllRunnablesOfType(MAIN_THREAD_BINDING_RUNNABLE);
// Unbind all the workspace items
@@ -814,7 +831,7 @@
*/
static void updateItemInDatabase(Context context, final ItemInfo item) {
final ContentValues values = new ContentValues();
- item.onAddToDatabase(values);
+ item.onAddToDatabase(context, values);
item.updateValuesWithCoordinates(values, item.cellX, item.cellY);
updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
}
@@ -839,18 +856,24 @@
/**
* Returns true if the shortcuts already exists in the database.
- * we identify a shortcut by the component name of the intent.
+ * we identify a shortcut by the component name of the intent
+ * and the user.
*/
- static boolean appWasRestored(Context context, Intent intent) {
+ static boolean appWasRestored(Context context, Intent intent, UserHandleCompat user) {
final ContentResolver cr = context.getContentResolver();
final ComponentName component = intent.getComponent();
if (component == null) {
return false;
}
String componentName = component.flattenToString();
- final String where = "intent glob \"*component=" + componentName + "*\" and restored = 1";
+ String shortName = component.flattenToShortString();
+ long serialNumber = UserManagerCompat.getInstance(context)
+ .getSerialNumberForUser(user);
+ final String where = "(intent glob \"*component=" + componentName + "*\" or " +
+ "intent glob \"*component=" + shortName + "*\")" +
+ "and restored = 1 and profileId = " + serialNumber;
Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
- new String[]{"intent", "restored"}, where, null, null);
+ new String[]{"intent", "restored", "profileId"}, where, null, null);
boolean result = false;
try {
result = c.moveToFirst();
@@ -870,8 +893,10 @@
final ContentResolver cr = context.getContentResolver();
Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, new String[] {
LauncherSettings.Favorites.ITEM_TYPE, LauncherSettings.Favorites.CONTAINER,
- LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.CELLX, LauncherSettings.Favorites.CELLY,
- LauncherSettings.Favorites.SPANX, LauncherSettings.Favorites.SPANY }, null, null, null);
+ LauncherSettings.Favorites.SCREEN,
+ LauncherSettings.Favorites.CELLX, LauncherSettings.Favorites.CELLY,
+ LauncherSettings.Favorites.SPANX, LauncherSettings.Favorites.SPANY,
+ LauncherSettings.Favorites.PROFILE_ID }, null, null, null);
final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
@@ -880,7 +905,8 @@
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 profileIdIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.PROFILE_ID);
+ UserManagerCompat userManager = UserManagerCompat.getInstance(context);
try {
while (c.moveToNext()) {
ItemInfo item = new ItemInfo();
@@ -891,8 +917,12 @@
item.container = c.getInt(containerIndex);
item.itemType = c.getInt(itemTypeIndex);
item.screenId = c.getInt(screenIndex);
-
- items.add(item);
+ long serialNumber = c.getInt(profileIdIndex);
+ item.user = userManager.getUserForSerialNumber(serialNumber);
+ // Skip if user has been deleted.
+ if (item.user != null) {
+ items.add(item);
+ }
}
} catch (Exception e) {
items.clear();
@@ -965,7 +995,7 @@
final ContentValues values = new ContentValues();
final ContentResolver cr = context.getContentResolver();
- item.onAddToDatabase(values);
+ item.onAddToDatabase(context, values);
item.id = LauncherAppState.getLauncherProvider().generateNewItemId();
values.put(LauncherSettings.Favorites._ID, item.id);
@@ -1155,74 +1185,67 @@
}
}
+ @Override
+ public void onPackageChanged(UserHandleCompat user, String packageName) {
+ int op = PackageUpdatedTask.OP_UPDATE;
+ enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
+ user));
+ }
+
+ @Override
+ public void onPackageRemoved(UserHandleCompat user, String packageName) {
+ int op = PackageUpdatedTask.OP_REMOVE;
+ enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
+ user));
+ }
+
+ @Override
+ public void onPackageAdded(UserHandleCompat user, String packageName) {
+ int op = PackageUpdatedTask.OP_ADD;
+ enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
+ user));
+ }
+
+ @Override
+ public void onPackagesAvailable(UserHandleCompat user, String[] packageNames,
+ boolean replacing) {
+ if (!replacing) {
+ enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packageNames,
+ user));
+ if (mAppsCanBeOnRemoveableStorage) {
+ // Only rebind if we support removable storage. It catches the
+ // case where
+ // apps on the external sd card need to be reloaded
+ startLoaderFromBackground();
+ }
+ } else {
+ // If we are replacing then just update the packages in the list
+ enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE,
+ packageNames, user));
+ }
+ }
+
+ @Override
+ public void onPackagesUnavailable(UserHandleCompat user, String[] packageNames,
+ boolean replacing) {
+ if (!replacing) {
+ enqueuePackageUpdated(new PackageUpdatedTask(
+ PackageUpdatedTask.OP_UNAVAILABLE, packageNames,
+ user));
+ }
+
+ }
+
/**
* Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
* ACTION_PACKAGE_CHANGED.
*/
@Override
public void onReceive(Context context, Intent intent) {
- if (DEBUG_LOADERS) Log.d(TAG, "onReceive intent=" + intent);
+ if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
final String action = intent.getAction();
-
- 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);
-
- int op = PackageUpdatedTask.OP_NONE;
-
- if (packageName == null || packageName.length() == 0) {
- // they sent us a bad intent
- return;
- }
-
- if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
- op = PackageUpdatedTask.OP_UPDATE;
- } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
- if (!replacing) {
- op = PackageUpdatedTask.OP_REMOVE;
- }
- // 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) {
- op = PackageUpdatedTask.OP_ADD;
- } else {
- op = PackageUpdatedTask.OP_UPDATE;
- }
- }
-
- if (op != PackageUpdatedTask.OP_NONE) {
- enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName }));
- }
-
- } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
- final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
- String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
- if (!replacing) {
- enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packages));
- if (mAppsCanBeOnRemoveableStorage) {
- // Only rebind if we support removable storage. It catches the case where
- // apps on the external sd card need to be reloaded
- startLoaderFromBackground();
- }
- } else {
- // If we are replacing then just update the packages in the list
- enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE,
- packages));
- }
- } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
- final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
- if (!replacing) {
- String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
- enqueuePackageUpdated(new PackageUpdatedTask(
- PackageUpdatedTask.OP_UNAVAILABLE, packages));
- }
- // else, we are replacing the packages, so ignore this event and wait for
- // EXTERNAL_APPLICATIONS_AVAILABLE to update the packages at that time
- } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
+ if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
// If we have changed locale we need to clear out the labels in all apps/workspace.
forceReload();
} else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
@@ -1248,7 +1271,7 @@
}
}
- private void forceReload() {
+ void forceReload() {
resetLoadedState(true, true);
// Do this here because if the launcher activity is running it will be restarted.
@@ -1315,7 +1338,9 @@
// Clear any deferred bind-runnables from the synchronized load process
// We must do this before any loading/binding is scheduled below.
- mDeferredBindRunnables.clear();
+ synchronized (mDeferredBindRunnables) {
+ mDeferredBindRunnables.clear();
+ }
// Don't bother to start the thread if we know it's not going to do anything
if (mCallbacks != null && mCallbacks.get() != null) {
@@ -1337,10 +1362,15 @@
void bindRemainingSynchronousPages() {
// Post the remaining side pages to be loaded
if (!mDeferredBindRunnables.isEmpty()) {
- for (final Runnable r : mDeferredBindRunnables) {
+ Runnable[] deferredBindRunnables = null;
+ synchronized (mDeferredBindRunnables) {
+ deferredBindRunnables = mDeferredBindRunnables.toArray(
+ new Runnable[mDeferredBindRunnables.size()]);
+ mDeferredBindRunnables.clear();
+ }
+ for (final Runnable r : deferredBindRunnables) {
mHandler.post(r, MAIN_THREAD_BINDING_RUNNABLE);
}
- mDeferredBindRunnables.clear();
}
}
@@ -1649,7 +1679,7 @@
ArrayList<ItemInfo> added = new ArrayList<ItemInfo>();
synchronized (sBgLock) {
for (AppInfo app : mBgAllAppsList.data) {
- tmpInfos = getItemInfoForComponentName(app.componentName);
+ tmpInfos = getItemInfoForComponentName(app.componentName, app.user);
if (tmpInfos.isEmpty()) {
// We are missing an application icon, so add this to the workspace
added.add(app);
@@ -1800,9 +1830,8 @@
LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary(0);
}
- // Check if we need to do any upgrade-path logic
- // (Includes having just imported default favorites)
- boolean loadedOldDb = LauncherAppState.getLauncherProvider().justLoadedOldDb();
+ // This code path is for our old migration code and should no longer be exercised
+ boolean loadedOldDb = false;
// Log to disk
Launcher.addDumpLog(TAG, "11683562 - loadedOldDb: " + loadedOldDb, true);
@@ -1854,6 +1883,8 @@
LauncherSettings.Favorites.SPANY);
final int restoredIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.RESTORED);
+ final int profileIdIndex = c.getColumnIndexOrThrow(
+ LauncherSettings.Favorites.PROFILE_ID);
//final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
//final int displayModeIndex = c.getColumnIndexOrThrow(
// LauncherSettings.Favorites.DISPLAY_MODE);
@@ -1864,6 +1895,7 @@
int container;
long id;
Intent intent;
+ UserHandleCompat user;
while (!mStopped && c.moveToNext()) {
AtomicBoolean deleteOnInvalidPlacement = new AtomicBoolean(false);
@@ -1876,10 +1908,17 @@
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
id = c.getLong(idIndex);
intentDescription = c.getString(intentIndex);
+ long serialNumber = c.getInt(profileIdIndex);
+ user = mUserManager.getUserForSerialNumber(serialNumber);
+ if (user == null) {
+ // User has been deleted remove the item.
+ itemsToRemove.add(id);
+ continue;
+ }
try {
intent = Intent.parseUri(intentDescription, 0);
ComponentName cn = intent.getComponent();
- if (cn != null && !isValidPackageComponent(manager, cn)) {
+ if (cn != null && !isValidPackageActivity(context, cn, user)) {
if (restored) {
// might be installed later
Launcher.addDumpLog(TAG,
@@ -1911,14 +1950,20 @@
}
if (restored) {
- Launcher.addDumpLog(TAG,
- "constructing info for partially restored package",
- true);
- info = getRestoredItemInfo(c, titleIndex, intent);
- intent = getRestoredItemIntent(c, context, intent);
+ if (user.equals(UserHandleCompat.myUserHandle())) {
+ Launcher.addDumpLog(TAG,
+ "constructing info for partially restored package",
+ true);
+ info = getRestoredItemInfo(c, titleIndex, intent);
+ intent = getRestoredItemIntent(c, context, intent);
+ } else {
+ // Don't restore items for other profiles.
+ itemsToRemove.add(id);
+ continue;
+ }
} else if (itemType ==
LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
- info = getShortcutInfo(manager, intent, context, c, iconIndex,
+ info = getShortcutInfo(manager, intent, user, context, c, iconIndex,
titleIndex, mLabelCache);
} else {
info = getShortcutInfo(c, context, iconTypeIndex,
@@ -1948,6 +1993,7 @@
info.cellY = c.getInt(cellYIndex);
info.spanX = 1;
info.spanY = 1;
+ info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
// check & update map of what's occupied
deleteOnInvalidPlacement.set(false);
@@ -2367,7 +2413,9 @@
}
};
if (postOnMainThread) {
- deferredBindRunnables.add(r);
+ synchronized (deferredBindRunnables) {
+ deferredBindRunnables.add(r);
+ }
} else {
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
}
@@ -2384,7 +2432,9 @@
}
};
if (postOnMainThread) {
- deferredBindRunnables.add(r);
+ synchronized (deferredBindRunnables) {
+ deferredBindRunnables.add(r);
+ }
} else {
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
}
@@ -2506,7 +2556,9 @@
// Load all the remaining pages (if we are loading synchronously, we want to defer this
// work until after the first render)
- mDeferredBindRunnables.clear();
+ synchronized (mDeferredBindRunnables) {
+ mDeferredBindRunnables.clear();
+ }
bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
(isLoadingSynchronously ? mDeferredBindRunnables : null));
@@ -2528,7 +2580,9 @@
}
};
if (isLoadingSynchronously) {
- mDeferredBindRunnables.add(r);
+ synchronized (mDeferredBindRunnables) {
+ mDeferredBindRunnables.add(r);
+ }
} else {
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
}
@@ -2594,42 +2648,42 @@
return;
}
- final PackageManager packageManager = mContext.getPackageManager();
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
+
// Clear the list of apps
mBgAllAppsList.clear();
+ for (UserHandleCompat user : profiles) {
+ // Query for the set of apps
+ final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
+ List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "getActivityList took "
+ + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
+ Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
+ }
+ // Fail if we don't have any apps
+ if (apps == null || apps.isEmpty()) {
+ return;
+ }
+ // Sort the applications by name
+ final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
+ Collections.sort(apps,
+ new LauncherModel.ShortcutNameComparator(mLabelCache));
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "sort took "
+ + (SystemClock.uptimeMillis()-sortTime) + "ms");
+ }
- // Query for the set of apps
- final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
- List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
- if (DEBUG_LOADERS) {
- Log.d(TAG, "queryIntentActivities took "
- + (SystemClock.uptimeMillis()-qiaTime) + "ms");
- Log.d(TAG, "queryIntentActivities got " + apps.size() + " apps");
+ // Create the ApplicationInfos
+ for (int i = 0; i < apps.size(); i++) {
+ LauncherActivityInfoCompat app = apps.get(i);
+ // This builds the icon bitmaps.
+ mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache, mLabelCache));
+ }
}
- // Fail if we don't have any apps
- if (apps == null || apps.isEmpty()) {
- return;
- }
- // Sort the applications by name
- final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
- Collections.sort(apps,
- new LauncherModel.ShortcutNameComparator(packageManager, mLabelCache));
- if (DEBUG_LOADERS) {
- Log.d(TAG, "sort took "
- + (SystemClock.uptimeMillis()-sortTime) + "ms");
- }
-
- // Create the ApplicationInfos
- for (int i = 0; i < apps.size(); i++) {
- ResolveInfo app = apps.get(i);
- // This builds the icon bitmaps.
- mBgAllAppsList.add(new AppInfo(packageManager, app,
- mIconCache, mLabelCache));
- }
-
// Huh? Shouldn't this be inside the Runnable below?
final ArrayList<AppInfo> added = mBgAllAppsList.added;
mBgAllAppsList.added = new ArrayList<AppInfo>();
@@ -2675,6 +2729,7 @@
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;
@@ -2683,9 +2738,10 @@
public static final int OP_UNAVAILABLE = 4; // external media unmounted
- public PackageUpdatedTask(int op, String[] packages) {
+ public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) {
mOp = op;
mPackages = packages;
+ mUser = user;
}
public void run() {
@@ -2697,14 +2753,14 @@
case OP_ADD:
for (int i=0; i<N; i++) {
if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
- mIconCache.remove(packages[i]);
- mBgAllAppsList.addPackage(context, packages[i]);
+ mIconCache.remove(packages[i], mUser);
+ mBgAllAppsList.addPackage(context, packages[i], mUser);
}
break;
case OP_UPDATE:
for (int i=0; i<N; i++) {
if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
- mBgAllAppsList.updatePackage(context, packages[i]);
+ mBgAllAppsList.updatePackage(context, packages[i], mUser);
WidgetPreviewLoader.removePackageFromDb(
mApp.getWidgetPreviewCacheDb(), packages[i]);
}
@@ -2713,7 +2769,7 @@
case OP_UNAVAILABLE:
for (int i=0; i<N; i++) {
if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
- mBgAllAppsList.removePackage(packages[i]);
+ mBgAllAppsList.removePackage(packages[i], mUser);
WidgetPreviewLoader.removePackageFromDb(
mApp.getWidgetPreviewCacheDb(), packages[i]);
}
@@ -2759,7 +2815,7 @@
// Update the launcher db to reflect the changes
for (AppInfo a : modifiedFinal) {
ArrayList<ItemInfo> infos =
- getItemInfoForComponentName(a.componentName);
+ getItemInfoForComponentName(a.componentName, mUser);
for (ItemInfo i : infos) {
if (isShortcutInfoUpdateable(i)) {
ShortcutInfo info = (ShortcutInfo) i;
@@ -2788,21 +2844,21 @@
// Mark disabled packages in the broadcast to be removed
final PackageManager pm = context.getPackageManager();
for (int i=0; i<N; i++) {
- if (isPackageDisabled(pm, packages[i])) {
+ if (isPackageDisabled(context, packages[i], mUser)) {
removedPackageNames.add(packages[i]);
}
}
}
// Remove all the components associated with this package
for (String pn : removedPackageNames) {
- ArrayList<ItemInfo> infos = getItemInfoForPackageName(pn);
+ ArrayList<ItemInfo> infos = getItemInfoForPackageName(pn, mUser);
for (ItemInfo i : infos) {
deleteItemFromDatabase(context, i);
}
}
// Remove all the specific components
for (AppInfo a : removedApps) {
- ArrayList<ItemInfo> infos = getItemInfoForComponentName(a.componentName);
+ ArrayList<ItemInfo> infos = getItemInfoForComponentName(a.componentName, mUser);
for (ItemInfo i : infos) {
deleteItemFromDatabase(context, i);
}
@@ -2818,14 +2874,14 @@
public void run() {
Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
if (callbacks == cb && cb != null) {
- callbacks.bindComponentsRemoved(removedPackageNames, removedApps);
+ callbacks.bindComponentsRemoved(removedPackageNames, removedApps, mUser);
}
}
});
}
final ArrayList<Object> widgetsAndShortcuts =
- getSortedWidgetsAndShortcuts(context);
+ getSortedWidgetsAndShortcuts(context);
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -2860,31 +2916,31 @@
return widgetsAndShortcuts;
}
- private static boolean isPackageDisabled(PackageManager pm, String packageName) {
- try {
- PackageInfo pi = pm.getPackageInfo(packageName, 0);
- return !pi.applicationInfo.enabled;
- } catch (NameNotFoundException e) {
- // Fall through
- }
- return false;
+ private static boolean isPackageDisabled(Context context, String packageName,
+ UserHandleCompat user) {
+ final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+ return !launcherApps.isPackageEnabledForProfile(packageName, user);
}
- public static boolean isValidPackageComponent(PackageManager pm, ComponentName cn) {
+ public static boolean isValidPackageActivity(Context context, ComponentName cn,
+ UserHandleCompat user) {
if (cn == null) {
return false;
}
- if (isPackageDisabled(pm, cn.getPackageName())) {
+ final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+ if (!launcherApps.isPackageEnabledForProfile(cn.getPackageName(), user)) {
return false;
}
+ return launcherApps.isActivityEnabledForProfile(cn, user);
+ }
- try {
- // Check the activity
- PackageInfo pi = pm.getPackageInfo(cn.getPackageName(), 0);
- return (pm.getActivityInfo(cn, 0) != null);
- } catch (NameNotFoundException e) {
+ 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);
}
/**
@@ -2898,7 +2954,8 @@
} else {
info.title = "";
}
- info.setIcon(mIconCache.getIcon(intent, info.title.toString()));
+ info.user = UserHandleCompat.myUserHandle();
+ info.setIcon(mIconCache.getIcon(intent, info.title.toString(), info.user));
info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
info.restoredIntent = intent;
return info;
@@ -2926,8 +2983,9 @@
* This is called from the code that adds shortcuts from the intent receiver. This
* doesn't have a Cursor, but
*/
- public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context) {
- return getShortcutInfo(manager, intent, context, null, -1, -1, null);
+ public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent,
+ UserHandleCompat user, Context context) {
+ return getShortcutInfo(manager, intent, user, context, null, -1, -1, null);
}
/**
@@ -2935,54 +2993,37 @@
*
* If c is not null, then it will be used to fill in missing data like the title and icon.
*/
- public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context,
- Cursor c, int iconIndex, int titleIndex, HashMap<Object, CharSequence> labelCache) {
- ComponentName componentName = intent.getComponent();
- final ShortcutInfo info = new ShortcutInfo();
- if (componentName != null && !isValidPackageComponent(manager, componentName)) {
- Log.d(TAG, "Invalid package found in getShortcutInfo: " + componentName);
+ public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent,
+ UserHandleCompat user, Context context, Cursor c, int iconIndex, int titleIndex,
+ HashMap<Object, CharSequence> labelCache) {
+ if (user == null) {
+ Log.d(TAG, "Null user found in getShortcutInfo");
return null;
- } else {
- try {
- PackageInfo pi = manager.getPackageInfo(componentName.getPackageName(), 0);
- info.initFlagsAndFirstInstallTime(pi);
- } catch (NameNotFoundException e) {
- Log.d(TAG, "getPackInfo failed for package " +
- componentName.getPackageName());
- }
}
- // TODO: See if the PackageManager knows about this case. If it doesn't
- // then return null & delete this.
+ ComponentName componentName = intent.getComponent();
+ if (componentName == null) {
+ Log.d(TAG, "Missing component found in getShortcutInfo: " + componentName);
+ 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) {
+ Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName);
+ return null;
+ }
+
+ final ShortcutInfo info = new ShortcutInfo();
// the resource -- This may implicitly give us back the fallback icon,
// but don't worry about that. All we're doing with usingFallbackIcon is
// to avoid saving lots of copies of that in the database, and most apps
// have icons anyway.
+ Bitmap icon = mIconCache.getIcon(componentName, lai, labelCache);
- // Attempt to use queryIntentActivities to get the ResolveInfo (with IntentFilter info) and
- // if that fails, or is ambiguious, fallback to the standard way of getting the resolve info
- // via resolveActivity().
- Bitmap icon = null;
- ResolveInfo resolveInfo = null;
- ComponentName oldComponent = intent.getComponent();
- Intent newIntent = new Intent(intent.getAction(), null);
- newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
- newIntent.setPackage(oldComponent.getPackageName());
- List<ResolveInfo> infos = manager.queryIntentActivities(newIntent, 0);
- for (ResolveInfo i : infos) {
- ComponentName cn = new ComponentName(i.activityInfo.packageName,
- i.activityInfo.name);
- if (cn.equals(oldComponent)) {
- resolveInfo = i;
- }
- }
- if (resolveInfo == null) {
- resolveInfo = manager.resolveActivity(intent, 0);
- }
- if (resolveInfo != null) {
- icon = mIconCache.getIcon(componentName, resolveInfo, labelCache);
- }
// the db
if (icon == null) {
if (c != null) {
@@ -2991,21 +3032,21 @@
}
// the fallback icon
if (icon == null) {
- icon = getFallbackIcon();
+ icon = mIconCache.getDefaultIcon(user);
info.usingFallbackIcon = true;
}
info.setIcon(icon);
+ // From the cache.
+ if (labelCache != null) {
+ info.title = labelCache.get(componentName);
+ }
+
// from the resource
- if (resolveInfo != null) {
- ComponentName key = LauncherModel.getComponentNameFromResolveInfo(resolveInfo);
- if (labelCache != null && labelCache.containsKey(key)) {
- info.title = labelCache.get(key);
- } else {
- info.title = resolveInfo.activityInfo.loadLabel(manager);
- if (labelCache != null) {
- labelCache.put(key, info.title);
- }
+ if (info.title == null && lai != null) {
+ info.title = lai.getLabel();
+ if (labelCache != null) {
+ labelCache.put(componentName, info.title);
}
}
// from the db
@@ -3019,6 +3060,7 @@
info.title = componentName.getClassName();
}
info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+ info.user = user;
return info;
}
@@ -3051,21 +3093,27 @@
return new ArrayList<ItemInfo>(filtered);
}
- private ArrayList<ItemInfo> getItemInfoForPackageName(final String pn) {
+ private ArrayList<ItemInfo> getItemInfoForPackageName(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);
+ return cn.getPackageName().equals(pn) && info.user.equals(user);
}
};
return filterItemInfos(sBgItemsIdMap.values(), filter);
}
- private ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname) {
+ private ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname,
+ final UserHandleCompat user) {
ItemInfoFilter filter = new ItemInfoFilter() {
@Override
public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
- return cn.equals(cname);
+ if (info.user == null) {
+ return cn.equals(cname);
+ } else {
+ return cn.equals(cname) && info.user.equals(user);
+ }
}
};
return filterItemInfos(sBgItemsIdMap.values(), filter);
@@ -3100,6 +3148,8 @@
Bitmap icon = null;
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.
@@ -3130,14 +3180,14 @@
}
// the fallback icon
if (icon == null) {
- icon = getFallbackIcon();
+ icon = mIconCache.getDefaultIcon(info.user);
info.usingFallbackIcon = true;
}
break;
case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
icon = getIconFromCursor(c, iconIndex, context);
if (icon == null) {
- icon = getFallbackIcon();
+ icon = mIconCache.getDefaultIcon(info.user);
info.customIcon = false;
info.usingFallbackIcon = true;
} else {
@@ -3145,7 +3195,7 @@
}
break;
default:
- icon = getFallbackIcon();
+ icon = mIconCache.getDefaultIcon(info.user);
info.usingFallbackIcon = true;
info.customIcon = false;
break;
@@ -3262,7 +3312,8 @@
iconResource.packageName);
final int id = resources.getIdentifier(iconResource.resourceName, null, null);
icon = Utilities.createIconBitmap(
- mIconCache.getFullResIcon(resources, id), context);
+ mIconCache.getFullResIcon(resources, id),
+ context);
} catch (Exception e) {
Log.w(TAG, "Could not load shortcut icon: " + extra);
}
@@ -3271,11 +3322,14 @@
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) {
if (fallbackIcon != null) {
icon = fallbackIcon;
} else {
- icon = getFallbackIcon();
+ icon = mIconCache.getDefaultIcon(info.user);
info.usingFallbackIcon = true;
}
}
@@ -3347,12 +3401,18 @@
final Collator collator = Collator.getInstance();
return new Comparator<AppInfo>() {
public final int compare(AppInfo a, AppInfo b) {
- int result = collator.compare(a.title.toString().trim(),
- b.title.toString().trim());
- if (result == 0) {
- result = a.componentName.compareTo(b.componentName);
+ if (a.user.equals(b.user)) {
+ int result = collator.compare(a.title.toString().trim(),
+ b.title.toString().trim());
+ if (result == 0) {
+ result = a.componentName.compareTo(b.componentName);
+ }
+ return result;
+ } else {
+ // TODO Need to figure out rules for sorting
+ // profiles, this puts work second.
+ return a.user.toString().compareTo(b.user.toString());
}
- return result;
}
};
}
@@ -3379,35 +3439,32 @@
return new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name);
}
}
- public static class ShortcutNameComparator implements Comparator<ResolveInfo> {
+ public static class ShortcutNameComparator implements Comparator<LauncherActivityInfoCompat> {
private Collator mCollator;
- private PackageManager mPackageManager;
private HashMap<Object, CharSequence> mLabelCache;
ShortcutNameComparator(PackageManager pm) {
- mPackageManager = pm;
mLabelCache = new HashMap<Object, CharSequence>();
mCollator = Collator.getInstance();
}
- ShortcutNameComparator(PackageManager pm, HashMap<Object, CharSequence> labelCache) {
- mPackageManager = pm;
+ ShortcutNameComparator(HashMap<Object, CharSequence> labelCache) {
mLabelCache = labelCache;
mCollator = Collator.getInstance();
}
- public final int compare(ResolveInfo a, ResolveInfo b) {
+ public final int compare(LauncherActivityInfoCompat a, LauncherActivityInfoCompat b) {
CharSequence labelA, labelB;
- ComponentName keyA = LauncherModel.getComponentNameFromResolveInfo(a);
- ComponentName keyB = LauncherModel.getComponentNameFromResolveInfo(b);
+ ComponentName keyA = a.getComponentName();
+ ComponentName keyB = b.getComponentName();
if (mLabelCache.containsKey(keyA)) {
labelA = mLabelCache.get(keyA);
} else {
- labelA = a.loadLabel(mPackageManager).toString().trim();
+ labelA = a.getLabel().toString().trim();
mLabelCache.put(keyA, labelA);
}
if (mLabelCache.containsKey(keyB)) {
labelB = mLabelCache.get(keyB);
} else {
- labelB = b.loadLabel(mPackageManager).toString().trim();
+ labelB = b.getLabel().toString().trim();
mLabelCache.put(keyB, labelB);
}
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 9a004f2..bbc75b8 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -56,8 +56,10 @@
import android.util.SparseArray;
import android.util.Xml;
-import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.LauncherSettings.Favorites;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -66,6 +68,7 @@
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -75,7 +78,7 @@
private static final String DATABASE_NAME = "launcher.db";
- private static final int DATABASE_VERSION = 18;
+ private static final int DATABASE_VERSION = 20;
static final String OLD_AUTHORITY = "com.android.launcher2.settings";
static final String AUTHORITY = ProviderConfig.AUTHORITY;
@@ -155,7 +158,7 @@
if (values == null) {
throw new RuntimeException("Error: attempting to insert null values");
}
- if (!values.containsKey(LauncherSettings.BaseLauncherColumns._ID)) {
+ if (!values.containsKey(LauncherSettings.ChangeLogColumns._ID)) {
throw new RuntimeException("Error: attempting to add item without specifying an id");
}
helper.checkId(table, values);
@@ -323,7 +326,6 @@
}
mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), workspaceResId);
- mOpenHelper.setFlagJustLoadedOldDb();
editor.commit();
}
}
@@ -334,10 +336,12 @@
}
private static int getDefaultWorkspaceResourceId() {
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
if (LauncherAppState.isDisableAllApps()) {
- return R.xml.default_workspace_no_all_apps;
+ return grid.defaultNoAllAppsLayoutId;
} else {
- return R.xml.default_workspace;
+ return grid.defaultLayoutId;
}
}
@@ -429,6 +433,10 @@
mMaxScreenId = 0;
mNewDbCreated = true;
+ UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
+ long userSerialNumber = userManager.getSerialNumberForUser(
+ UserHandleCompat.myUserHandle());
+
db.execSQL("CREATE TABLE favorites (" +
"_id INTEGER PRIMARY KEY," +
"title TEXT," +
@@ -450,7 +458,8 @@
"displayMode INTEGER," +
"appWidgetProvider TEXT," +
"modified INTEGER NOT NULL DEFAULT 0," +
- "restored INTEGER NOT NULL DEFAULT 0" +
+ "restored INTEGER NOT NULL DEFAULT 0," +
+ "profileId INTEGER DEFAULT " + userSerialNumber +
");");
addWorkspacesTable(db);
@@ -503,10 +512,34 @@
}
private void removeOrphanedItems(SQLiteDatabase db) {
- db.execSQL("DELETE FROM " + TABLE_FAVORITES + " WHERE " +
+ // Delete items directly on the workspace who's screen id doesn't exist
+ // "DELETE FROM favorites WHERE screen NOT IN (SELECT _id FROM workspaceScreens)
+ // AND container = -100"
+ String removeOrphanedDesktopItems = "DELETE FROM " + TABLE_FAVORITES +
+ " WHERE " +
LauncherSettings.Favorites.SCREEN + " NOT IN (SELECT " +
- LauncherSettings.WorkspaceScreens._ID + " FROM " + TABLE_WORKSPACE_SCREENS +
- ")");
+ LauncherSettings.WorkspaceScreens._ID + " FROM " + TABLE_WORKSPACE_SCREENS + ")" +
+ " AND " +
+ LauncherSettings.Favorites.CONTAINER + " = " +
+ LauncherSettings.Favorites.CONTAINER_DESKTOP;
+ db.execSQL(removeOrphanedDesktopItems);
+
+ // Delete items contained in folders which no longer exist (after above statement)
+ // "DELETE FROM favorites WHERE container <> -100 AND container <> -101 AND container
+ // NOT IN (SELECT _id FROM favorites WHERE itemType = 2)"
+ String removeOrphanedFolderItems = "DELETE FROM " + TABLE_FAVORITES +
+ " WHERE " +
+ LauncherSettings.Favorites.CONTAINER + " <> " +
+ LauncherSettings.Favorites.CONTAINER_DESKTOP +
+ " AND "
+ + LauncherSettings.Favorites.CONTAINER + " <> " +
+ LauncherSettings.Favorites.CONTAINER_HOTSEAT +
+ " AND "
+ + LauncherSettings.Favorites.CONTAINER + " NOT IN (SELECT " +
+ LauncherSettings.Favorites._ID + " FROM " + TABLE_FAVORITES +
+ " WHERE " + LauncherSettings.Favorites.ITEM_TYPE + " = " +
+ LauncherSettings.Favorites.ITEM_TYPE_FOLDER + ")";
+ db.execSQL(removeOrphanedFolderItems);
}
private void setFlagJustLoadedOldDb() {
@@ -810,14 +843,25 @@
}
if (version < 18) {
+ // No-op
+ version = 18;
+ }
+
+ if (version < 19) {
// Due to a data loss bug, some users may have items associated with screen ids
// which no longer exist. Since this can cause other problems, and since the user
// will never see these items anyway, we use database upgrade as an opportunity to
// clean things up.
+ removeOrphanedItems(db);
+ version = 19;
+ }
- // TODO: this needs to be fixed, currently causes data loss.
- //removeOrphanedItems(db);
- version = 18;
+ if (version < 20) {
+ // Add userId column
+ if (addProfileColumn(db)) {
+ version = 20;
+ }
+ // else old version remains, which means we wipe old data
}
if (version != DATABASE_VERSION) {
@@ -829,6 +873,40 @@
}
}
+ @Override
+ public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ // 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.");
+
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS);
+ onCreate(db);
+ }
+
+ private boolean addProfileColumn(SQLiteDatabase db) {
+ db.beginTransaction();
+ try {
+ UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
+ // Default to the serial number of this user, for older
+ // shortcuts.
+ long userSerialNumber = userManager.getSerialNumberForUser(
+ UserHandleCompat.myUserHandle());
+ // Insert new column for holding user serial number
+ db.execSQL("ALTER TABLE favorites " +
+ "ADD COLUMN profileId INTEGER DEFAULT "
+ + userSerialNumber + ";");
+ db.setTransactionSuccessful();
+ } catch (SQLException ex) {
+ // Old version remains, which means we wipe old data
+ Log.e(TAG, ex.getMessage(), ex);
+ return false;
+ } finally {
+ db.endTransaction();
+ }
+ return true;
+ }
+
private boolean updateContactsShortcuts(SQLiteDatabase db) {
final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE,
new int[] { Favorites.ITEM_TYPE_SHORTCUT });
@@ -1166,18 +1244,47 @@
return intent;
}
+ private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) {
+ ArrayList<Long> screenIds = new ArrayList<Long>();
+ int count = loadFavoritesRecursive(db, workspaceResourceId, screenIds);
+
+ // Add the screens specified by the items above
+ Collections.sort(screenIds);
+ int rank = 0;
+ ContentValues values = new ContentValues();
+ for (Long id : screenIds) {
+ values.clear();
+ values.put(LauncherSettings.WorkspaceScreens._ID, id);
+ values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank);
+ if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values) < 0) {
+ throw new RuntimeException("Failed initialize screen table"
+ + "from default layout");
+ }
+ rank++;
+ }
+
+ // Ensure that the max ids are initialized
+ mMaxItemId = initializeMaxItemId(db);
+ mMaxScreenId = initializeMaxScreenId(db);
+ return count;
+ }
+
/**
* Loads the default set of favorite packages from an xml file.
*
* @param db The database to write the values into
* @param filterContainerId The specific container id of items to load
+ * @param the set of screenIds which are used by the favorites
*/
- private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) {
- ContentValues values = new ContentValues();
+ private int loadFavoritesRecursive(SQLiteDatabase db, int workspaceResourceId,
+ ArrayList<Long> screenIds) {
+
+
+ ContentValues values = new ContentValues();
if (LOGD) Log.v(TAG, String.format("Loading favorites from resid=0x%08x", workspaceResourceId));
- int i = 0;
+ int count = 0;
try {
XmlResourceParser parser = mContext.getResources().getXml(workspaceResourceId);
AttributeSet attrs = Xml.asAttributeSet(parser);
@@ -1206,7 +1313,7 @@
if (resId != 0 && resId != workspaceResourceId) {
// recursively load some more favorites, why not?
- i += loadFavorites(db, resId);
+ count += loadFavoritesRecursive(db, resId, screenIds);
added = false;
} else {
Log.w(TAG, String.format("Skipping <include workspace=0x%08x>", resId));
@@ -1302,7 +1409,15 @@
}
}
}
- if (added) i++;
+ if (added) {
+ long screenId = Long.parseLong(screen);
+ // Keep track of the set of screens which need to be added to the db.
+ if (!screenIds.contains(screenId) &&
+ container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ screenIds.add(screenId);
+ }
+ count++;
+ }
a.recycle();
}
} catch (XmlPullParserException e) {
@@ -1312,13 +1427,7 @@
} catch (RuntimeException e) {
Log.w(TAG, "Got exception parsing favorites.", e);
}
-
- // Update the max item id after we have loaded the database
- if (mMaxItemId == -1) {
- mMaxItemId = initializeMaxItemId(db);
- }
-
- return i;
+ return count;
}
/**
@@ -1759,6 +1868,8 @@
= c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
final int displayModeIndex
= c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
+ final int profileIndex
+ = c.getColumnIndex(LauncherSettings.Favorites.PROFILE_ID);
int i = 0;
int curX = 0;
@@ -1790,6 +1901,19 @@
final int screen = c.getInt(screenIndex);
int container = c.getInt(containerIndex);
final String intentStr = c.getString(intentIndex);
+
+ UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
+ UserHandleCompat userHandle;
+ final long userSerialNumber;
+ if (profileIndex != -1 && !c.isNull(profileIndex)) {
+ userSerialNumber = c.getInt(profileIndex);
+ userHandle = userManager.getUserForSerialNumber(userSerialNumber);
+ } else {
+ // Default to the serial number of this user, for older
+ // shortcuts.
+ userHandle = UserHandleCompat.myUserHandle();
+ userSerialNumber = userManager.getSerialNumberForUser(userHandle);
+ }
Launcher.addDumpLog(TAG, "migrating \""
+ c.getString(titleIndex) + "\" ("
+ cellX + "," + cellY + "@"
@@ -1816,7 +1940,8 @@
Launcher.addDumpLog(TAG, "skipping empty intent", true);
continue;
} else if (cn != null &&
- !LauncherModel.isValidPackageComponent(pm, cn)) {
+ !LauncherModel.isValidPackageActivity(mContext, cn,
+ userHandle)) {
// component no longer exists.
Launcher.addDumpLog(TAG, "skipping item whose component " +
"no longer exists.", true);
@@ -1855,6 +1980,7 @@
values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
values.put(LauncherSettings.Favorites.DISPLAY_MODE,
c.getInt(displayModeIndex));
+ values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber);
if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
hotseat.put(screen, values);
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 2a768a2..3553702 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -212,6 +212,14 @@
static final String SPANY = "spanY";
/**
+ * The profile id of the item in the cell.
+ * <P>
+ * Type: INTEGER
+ * </P>
+ */
+ static final String PROFILE_ID = "profileId";
+
+ /**
* The favorite is a user created folder
*/
static final int ITEM_TYPE_FOLDER = 2;
diff --git a/src/com/android/launcher3/MainThreadExecutor.java b/src/com/android/launcher3/MainThreadExecutor.java
new file mode 100644
index 0000000..866b17c
--- /dev/null
+++ b/src/com/android/launcher3/MainThreadExecutor.java
@@ -0,0 +1,80 @@
+/*
+ * 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;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.List;
+import java.util.concurrent.AbstractExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An executor service that executes its tasks on the main thread.
+ *
+ * Shutting down this executor is not supported.
+ */
+public class MainThreadExecutor extends AbstractExecutorService {
+
+ 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();
+ }
+}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 8d5d8dd..9763126 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -2482,7 +2482,7 @@
public boolean startReordering(View v) {
int dragViewIndex = indexOfChild(v);
- if (mTouchState != TOUCH_STATE_REST) return false;
+ if (mTouchState != TOUCH_STATE_REST || dragViewIndex == -1) return false;
mTempVisiblePagesRange[0] = 0;
mTempVisiblePagesRange[1] = getPageCount() - 1;
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index 5afa784..f40cf9f 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -26,7 +26,10 @@
import android.graphics.Bitmap;
import android.util.Log;
+import com.android.launcher3.compat.UserHandleCompat;
+
import java.util.ArrayList;
+import java.util.Arrays;
/**
* Represents a launchable icon on the workspaces and in folders.
@@ -110,12 +113,12 @@
}
}
-
- ShortcutInfo(Intent intent, CharSequence title, Bitmap icon) {
+ ShortcutInfo(Intent intent, CharSequence title, Bitmap icon, UserHandleCompat user) {
this();
this.intent = intent;
this.title = title;
mIcon = icon;
+ this.user = user;
}
public ShortcutInfo(Context context, ShortcutInfo info) {
@@ -129,8 +132,9 @@
}
mIcon = info.mIcon; // TODO: should make a copy here. maybe we don't need this ctor at all
customIcon = info.customIcon;
- initFlagsAndFirstInstallTime(
- getPackageInfo(context, intent.getComponent().getPackageName()));
+ flags = info.flags;
+ firstInstallTime = info.firstInstallTime;
+ user = info.user;
}
/** TODO: Remove this. It's only called by ApplicationInfo.makeShortcut. */
@@ -143,22 +147,6 @@
firstInstallTime = info.firstInstallTime;
}
- public static PackageInfo getPackageInfo(Context context, String packageName) {
- PackageInfo pi = null;
- try {
- PackageManager pm = context.getPackageManager();
- pi = pm.getPackageInfo(packageName, 0);
- } catch (NameNotFoundException e) {
- Log.d("ShortcutInfo", "PackageManager.getPackageInfo failed for " + packageName);
- }
- return pi;
- }
-
- void initFlagsAndFirstInstallTime(PackageInfo pi) {
- flags = AppInfo.initFlags(pi);
- firstInstallTime = AppInfo.initFirstInstallTime(pi);
- }
-
public void setIcon(Bitmap b) {
mIcon = b;
}
@@ -171,30 +159,13 @@
}
public void updateIcon(IconCache iconCache) {
- mIcon = iconCache.getIcon(intent);
- usingFallbackIcon = iconCache.isDefaultIcon(mIcon);
- }
-
- /**
- * Creates the application intent based on a component name and various launch flags.
- * Sets {@link #itemType} to {@link LauncherSettings.BaseLauncherColumns#ITEM_TYPE_APPLICATION}.
- *
- * @param className the class name of the component representing the intent
- * @param launchFlags the launch flags
- */
- final void setActivity(Context context, ComponentName className, int launchFlags) {
- intent = new Intent(Intent.ACTION_MAIN);
- intent.addCategory(Intent.CATEGORY_LAUNCHER);
- intent.setComponent(className);
- intent.setFlags(launchFlags);
- itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION;
- initFlagsAndFirstInstallTime(
- getPackageInfo(context, intent.getComponent().getPackageName()));
+ mIcon = iconCache.getIcon(intent, user);
+ usingFallbackIcon = iconCache.isDefaultIcon(mIcon, user);
}
@Override
- void onAddToDatabase(ContentValues values) {
- super.onAddToDatabase(values);
+ void onAddToDatabase(Context context, ContentValues values) {
+ super.onAddToDatabase(context, values);
String titleStr = title != null ? title.toString() : null;
values.put(LauncherSettings.BaseLauncherColumns.TITLE, titleStr);
@@ -223,10 +194,10 @@
@Override
public String toString() {
- return "ShortcutInfo(title=" + title.toString() + "intent=" + intent + "id=" + this.id
+ return "ShortcutInfo(title=" + title + "intent=" + intent + "id=" + this.id
+ " type=" + this.itemType + " container=" + this.container + " screen=" + screenId
+ " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX + " spanY=" + spanY
- + " dropPos=" + dropPos + ")";
+ + " dropPos=" + Arrays.toString(dropPos) + " user=" + user + ")";
}
public static void dumpShortcutInfoList(String tag, String label,
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index cbc9785..74b6e47 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -33,6 +33,7 @@
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.PaintDrawable;
+import android.os.Build;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
@@ -99,6 +100,14 @@
}
/**
+ * Indicates if the device is running LMP or not.
+ * TODO(sansid): Change the check to a VERSION_CODES code check once we have a version for L.
+ */
+ public static boolean isLmp() {
+ return "L".equals(Build.VERSION.CODENAME);
+ }
+
+ /**
* Returns a bitmap suitable for the all apps view. Used to convert pre-ICS
* icon bitmaps that are stored in the database (which were 74x74 pixels at hdpi size)
* to the proper size (48dp)
@@ -305,6 +314,17 @@
return scale;
}
+ /**
+ * Utility method to determine whether the given point, in local coordinates,
+ * is inside the view, where the area of the view is expanded by the slop factor.
+ * This method is called while processing touch-move events to determine if the event
+ * is still within the view.
+ */
+ public static boolean pointInView(View v, float localX, float localY, float slop) {
+ return localX >= -slop && localY >= -slop && localX < (v.getWidth() + slop) &&
+ localY < (v.getHeight() + slop);
+ }
+
private static void initStatics(Context context) {
final Resources resources = context.getResources();
final DisplayMetrics metrics = resources.getDisplayMetrics();
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 36152f8..1b37700 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -9,6 +9,7 @@
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.database.Cursor;
+import android.database.sqlite.SQLiteCantOpenDatabaseException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDiskIOException;
import android.database.sqlite.SQLiteOpenHelper;
@@ -30,11 +31,16 @@
import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.IOException;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
abstract class SoftReferenceThreadLocal<T> {
private ThreadLocal<SoftReference<T>> mThreadLocal;
@@ -137,6 +143,8 @@
private final ArrayList<SoftReference<Bitmap>> mUnusedBitmaps;
private final static HashSet<String> sInvalidPackages;
+ private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
+
static {
sInvalidPackages = new HashSet<String>();
}
@@ -358,6 +366,9 @@
db.insert(CacheDb.TABLE_NAME, null, values);
} catch (SQLiteDiskIOException e) {
recreateDb();
+ } catch (SQLiteCantOpenDatabaseException e) {
+ dumpOpenFiles();
+ throw e;
}
}
@@ -367,6 +378,9 @@
try {
db.delete(CacheDb.TABLE_NAME, null, null);
} catch (SQLiteDiskIOException e) {
+ } catch (SQLiteCantOpenDatabaseException e) {
+ dumpOpenFiles();
+ throw e;
}
}
@@ -387,6 +401,9 @@
} // args to SELECT query
);
} catch (SQLiteDiskIOException e) {
+ } catch (SQLiteCantOpenDatabaseException e) {
+ dumpOpenFiles();
+ throw e;
}
synchronized(sInvalidPackages) {
sInvalidPackages.remove(packageName);
@@ -405,6 +422,9 @@
CacheDb.COLUMN_NAME + " = ? ", // SELECT query
new String[] { objectName }); // args to SELECT query
} catch (SQLiteDiskIOException e) {
+ } catch (SQLiteCantOpenDatabaseException e) {
+ dumpOpenFiles();
+ throw e;
}
return null;
}
@@ -430,6 +450,9 @@
} catch (SQLiteDiskIOException e) {
recreateDb();
return null;
+ } catch (SQLiteCantOpenDatabaseException e) {
+ dumpOpenFiles();
+ throw e;
}
if (result.getCount() > 0) {
result.moveToFirst();
@@ -493,7 +516,9 @@
Drawable drawable = null;
if (previewImage != 0) {
drawable = mPackageManager.getDrawable(packageName, previewImage, null);
- if (drawable == null) {
+ if (drawable != null) {
+ drawable = mutateOnMainThread(drawable);
+ } else {
Log.w(TAG, "Can't load widget preview drawable 0x" +
Integer.toHexString(previewImage) + " for provider: " + provider);
}
@@ -511,6 +536,7 @@
if (cellHSpan < 1) cellHSpan = 1;
if (cellVSpan < 1) cellVSpan = 1;
+ // This Drawable is not directly drawn, so there's no need to mutate it.
BitmapDrawable previewDrawable = (BitmapDrawable) mContext.getResources()
.getDrawable(R.drawable.widget_tile);
final int previewDrawableWidth = previewDrawable
@@ -547,9 +573,11 @@
(int) ((previewDrawableWidth - mAppIconSize * iconScale) / 2);
int yoffset =
(int) ((previewDrawableHeight - mAppIconSize * iconScale) / 2);
- if (iconId > 0)
+ if (iconId > 0) {
icon = mIconCache.getFullResIcon(packageName, iconId);
+ }
if (icon != null) {
+ icon = mutateOnMainThread(icon);
renderDrawableToBitmap(icon, defaultPreview, hoffset,
yoffset, (int) (mAppIconSize * iconScale),
(int) (mAppIconSize * iconScale));
@@ -617,7 +645,7 @@
c.setBitmap(null);
}
// Render the icon
- Drawable icon = mIconCache.getFullResIcon(info);
+ Drawable icon = mutateOnMainThread(mIconCache.getFullResIcon(info));
int paddingTop = mContext.
getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_top);
@@ -677,4 +705,98 @@
}
}
+ private Drawable mutateOnMainThread(final Drawable drawable) {
+ try {
+ return mMainThreadExecutor.submit(new Callable<Drawable>() {
+ @Override
+ public Drawable call() throws Exception {
+ return drawable.mutate();
+ }
+ }).get();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException(e);
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static final int MAX_OPEN_FILES = 1024;
+ private static final int SAMPLE_RATE = 23;
+ /**
+ * Dumps all files that are open in this process without allocating a file descriptor.
+ */
+ private static void dumpOpenFiles() {
+ try {
+ Log.i(TAG, "DUMP OF OPEN FILES (sample rate: 1 every " + SAMPLE_RATE + "):");
+ final String TYPE_APK = "apk";
+ final String TYPE_JAR = "jar";
+ final String TYPE_PIPE = "pipe";
+ final String TYPE_SOCKET = "socket";
+ final String TYPE_DB = "db";
+ final String TYPE_ANON_INODE = "anon_inode";
+ final String TYPE_DEV = "dev";
+ final String TYPE_NON_FS = "non-fs";
+ final String TYPE_OTHER = "other";
+ List<String> types = Arrays.asList(TYPE_APK, TYPE_JAR, TYPE_PIPE, TYPE_SOCKET, TYPE_DB,
+ TYPE_ANON_INODE, TYPE_DEV, TYPE_NON_FS, TYPE_OTHER);
+ int[] count = new int[types.size()];
+ int[] duplicates = new int[types.size()];
+ HashSet<String> files = new HashSet<String>();
+ int total = 0;
+ for (int i = 0; i < MAX_OPEN_FILES; i++) {
+ // This is a gigantic hack but unfortunately the only way to resolve an fd
+ // to a file name. Note that we have to loop over all possible fds because
+ // reading the directory would require allocating a new fd. The kernel is
+ // currently implemented such that no fd is larger then the current rlimit,
+ // which is why it's safe to loop over them in such a way.
+ String fd = "/proc/self/fd/" + i;
+ try {
+ // getCanonicalPath() uses readlink behind the scene which doesn't require
+ // a file descriptor.
+ String resolved = new File(fd).getCanonicalPath();
+ int type = types.indexOf(TYPE_OTHER);
+ if (resolved.startsWith("/dev/")) {
+ type = types.indexOf(TYPE_DEV);
+ } else if (resolved.endsWith(".apk")) {
+ type = types.indexOf(TYPE_APK);
+ } else if (resolved.endsWith(".jar")) {
+ type = types.indexOf(TYPE_JAR);
+ } else if (resolved.contains("/fd/pipe:")) {
+ type = types.indexOf(TYPE_PIPE);
+ } else if (resolved.contains("/fd/socket:")) {
+ type = types.indexOf(TYPE_SOCKET);
+ } else if (resolved.contains("/fd/anon_inode:")) {
+ type = types.indexOf(TYPE_ANON_INODE);
+ } else if (resolved.endsWith(".db") || resolved.contains("/databases/")) {
+ type = types.indexOf(TYPE_DB);
+ } else if (resolved.startsWith("/proc/") && resolved.contains("/fd/")) {
+ // Those are the files that don't point anywhere on the file system.
+ // getCanonicalPath() wrongly interprets these as relative symlinks and
+ // resolves them within /proc/<pid>/fd/.
+ type = types.indexOf(TYPE_NON_FS);
+ }
+ count[type]++;
+ total++;
+ if (files.contains(resolved)) {
+ duplicates[type]++;
+ }
+ files.add(resolved);
+ if (total % SAMPLE_RATE == 0) {
+ Log.i(TAG, " fd " + i + ": " + resolved
+ + " (" + types.get(type) + ")");
+ }
+ } catch (IOException e) {
+ // Ignoring exceptions for non-existing file descriptors.
+ }
+ }
+ for (int i = 0; i < types.size(); i++) {
+ Log.i(TAG, String.format("Open %10s files: %4d total, %4d duplicates",
+ types.get(i), count[i], duplicates[i]));
+ }
+ } catch (Throwable t) {
+ // Catch everything. This is called from an exception handler that we shouldn't upset.
+ Log.e(TAG, "Unable to log open files.", t);
+ }
+ }
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 9800cf3..0015418 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -60,6 +60,8 @@
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.TextView;
+
+import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.FolderIcon.FolderRingAnimator;
import com.android.launcher3.Launcher.CustomContentCallbacks;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -126,13 +128,14 @@
private static boolean sAccessibilityEnabled;
// The screen id used for the empty screen always present to the right.
- private final static long EXTRA_EMPTY_SCREEN_ID = -201;
+ final static long EXTRA_EMPTY_SCREEN_ID = -201;
private final static long CUSTOM_CONTENT_SCREEN_ID = -301;
private HashMap<Long, CellLayout> mWorkspaceScreens = new HashMap<Long, CellLayout>();
private ArrayList<Long> mScreenOrder = new ArrayList<Long>();
private Runnable mRemoveEmptyScreenRunnable;
+ private boolean mDeferRemoveExtraEmptyScreen = false;
/**
* CellInfo for the cell that is currently being dragged
@@ -395,13 +398,23 @@
@Override
public void run() {
if (mIsDragOccuring) {
+ mDeferRemoveExtraEmptyScreen = false;
addExtraEmptyScreenOnDrag();
}
}
});
}
+
+ public void deferRemoveExtraEmptyScreen() {
+ mDeferRemoveExtraEmptyScreen = true;
+ }
+
public void onDragEnd() {
+ if (!mDeferRemoveExtraEmptyScreen) {
+ removeExtraEmptyScreen(true, mDragSourceInternal != null);
+ }
+
mIsDragOccuring = false;
updateChildrenLayersEnabled(false);
mLauncher.unlockScreenOrientation(false);
@@ -724,11 +737,11 @@
}
}
- public void removeExtraEmptyScreen(final boolean animate, final Runnable onComplete) {
- removeExtraEmptyScreen(animate, onComplete, 0, false);
+ public void removeExtraEmptyScreen(final boolean animate, boolean stripEmptyScreens) {
+ removeExtraEmptyScreenDelayed(animate, null, 0, stripEmptyScreens);
}
- public void removeExtraEmptyScreen(final boolean animate, final Runnable onComplete,
+ public void removeExtraEmptyScreenDelayed(final boolean animate, final Runnable onComplete,
final int delay, final boolean stripEmptyScreens) {
// Log to disk
Launcher.addDumpLog(TAG, "11683562 - removeExtraEmptyScreen()", true);
@@ -742,9 +755,8 @@
postDelayed(new Runnable() {
@Override
public void run() {
- removeExtraEmptyScreen(animate, onComplete, 0, stripEmptyScreens);
+ removeExtraEmptyScreenDelayed(animate, onComplete, 0, stripEmptyScreens);
}
-
}, delay);
return;
}
@@ -1264,7 +1276,8 @@
SharedPreferences sp =
mLauncher.getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
LauncherWallpaperPickerActivity.suggestWallpaperDimension(mLauncher.getResources(),
- sp, mLauncher.getWindowManager(), mWallpaperManager);
+ sp, mLauncher.getWindowManager(), mWallpaperManager,
+ mLauncher.overrideWallpaperDimensions());
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
@@ -1282,6 +1295,10 @@
snapToPage(whichPage, duration);
}
+ public void snapToScreenId(long screenId) {
+ snapToScreenId(screenId, null);
+ }
+
protected void snapToScreenId(long screenId, Runnable r) {
snapToPage(getPageIndexForScreenId(screenId), r);
}
@@ -1466,6 +1483,14 @@
mWallpaperOffset.syncWithScroll();
}
+ @Override
+ public void announceForAccessibility(CharSequence text) {
+ // Don't announce if apps is on top of us.
+ if (!mLauncher.isAllAppsVisible()) {
+ super.announceForAccessibility(text);
+ }
+ }
+
void showOutlines() {
if (!isSmall() && !mIsSwitchingState) {
if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
@@ -2699,8 +2724,9 @@
BubbleTextView icon = (BubbleTextView) child;
icon.clearPressedOrFocusedBackground();
} else if (child instanceof FolderIcon) {
- // Dismiss the folder cling if we haven't already
- mLauncher.getLauncherClings().markFolderClingDismissed();
+ // The folder cling isn't flexible enough to be shown in non-default workspace positions
+ // Also if they are dragging it a folder, we assume they don't need to see the cling.
+ mLauncher.markFolderClingDismissedIfNecessary();
}
if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) {
@@ -3046,13 +3072,15 @@
// cell also contains a shortcut, then create a folder with the two shortcuts.
if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
- removeExtraEmptyScreen(true, null, 0, true);
+ // The folder cling isn't flexible enough to be shown in non-default workspace
+ // positions. Also if they are creating a folder, we assume they don't need to
+ // see the cling.
+ mLauncher.markFolderClingDismissedIfNecessary();
return;
}
if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
distance, d, false)) {
- removeExtraEmptyScreen(true, null, 0, true);
return;
}
@@ -3158,7 +3186,6 @@
if (finalResizeRunnable != null) {
finalResizeRunnable.run();
}
- removeExtraEmptyScreen(true, null, 0, true);
}
};
mAnimatingViewIntoPlace = true;
@@ -3812,13 +3839,8 @@
final Runnable exitSpringLoadedRunnable = new Runnable() {
@Override
public void run() {
- removeExtraEmptyScreen(false, new Runnable() {
- @Override
- public void run() {
- mLauncher.exitSpringLoadedDragModeDelayed(true,
- Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
- }
- });
+ mLauncher.exitSpringLoadedDragModeDelayed(true,
+ Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
}
};
@@ -3880,6 +3902,11 @@
Runnable onAnimationCompleteRunnable = new Runnable() {
@Override
public void run() {
+ // Normally removeExtraEmptyScreen is called in Workspace#onDragEnd, but when
+ // adding an item that may not be dropped right away (due to a config activity)
+ // we defer the removal until the activity returns.
+ deferRemoveExtraEmptyScreen();
+
// When dragging and dropping from customization tray, we deal with creating
// widgets/shortcuts/folders in a slightly different way
switch (pendingInfo.itemType) {
@@ -3948,6 +3975,10 @@
d.postAnimationRunnable = exitSpringLoadedRunnable;
if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
true, d.dragView, d.postAnimationRunnable)) {
+ // The folder cling isn't flexible enough to be shown in non-default workspace
+ // positions. Also if they are creating a folder, we assume they don't need to
+ // see the cling.
+ mLauncher.markFolderClingDismissedIfNecessary();
return;
}
if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
@@ -4180,10 +4211,6 @@
if (mDragInfo.cell instanceof DropTarget) {
mDragController.removeDropTarget((DropTarget) mDragInfo.cell);
}
- // If we move the item to anything not on the Workspace, check if any empty
- // screens need to be removed. If we dropped back on the workspace, this will
- // be done post drop animation.
- removeExtraEmptyScreen(true, null, 0, true);
}
} else if (mDragInfo != null) {
CellLayout cellLayout;
@@ -4613,7 +4640,7 @@
// Removes ALL items that match a given package name, this is usually called when a package
// has been removed and we want to remove all components (widgets, shortcuts, apps) that
// belong to that package.
- void removeItemsByPackageName(final ArrayList<String> packages) {
+ void removeItemsByPackageName(final ArrayList<String> packages, final UserHandleCompat user) {
final HashSet<String> packageNames = new HashSet<String>();
packageNames.addAll(packages);
@@ -4633,7 +4660,8 @@
@Override
public boolean filterItem(ItemInfo parent, ItemInfo info,
ComponentName cn) {
- if (packageNames.contains(cn.getPackageName())) {
+ if (packageNames.contains(cn.getPackageName())
+ && info.user.equals(user)) {
cns.add(cn);
return true;
}
@@ -4643,13 +4671,13 @@
LauncherModel.filterItemInfos(infos, filter);
// Remove the affected components
- removeItemsByComponentName(cns);
+ removeItemsByComponentName(cns, user);
}
// Removes items that match the application info specified, when applications are removed
// as a part of an update, this is called to ensure that other widgets and application
// shortcuts are not removed.
- void removeItemsByApplicationInfo(final ArrayList<AppInfo> appInfos) {
+ void removeItemsByApplicationInfo(final ArrayList<AppInfo> appInfos, UserHandleCompat user) {
// Just create a hash table of all the specific components that this will affect
HashSet<ComponentName> cns = new HashSet<ComponentName>();
for (AppInfo info : appInfos) {
@@ -4657,10 +4685,11 @@
}
// Remove all the things
- removeItemsByComponentName(cns);
+ removeItemsByComponentName(cns, user);
}
- void removeItemsByComponentName(final HashSet<ComponentName> componentNames) {
+ void removeItemsByComponentName(final HashSet<ComponentName> componentNames,
+ final UserHandleCompat user) {
ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
for (final CellLayout layoutParent: cellLayouts) {
final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
@@ -4679,7 +4708,7 @@
public boolean filterItem(ItemInfo parent, ItemInfo info,
ComponentName cn) {
if (parent instanceof FolderInfo) {
- if (componentNames.contains(cn)) {
+ if (componentNames.contains(cn) && info.user.equals(user)) {
FolderInfo folder = (FolderInfo) parent;
ArrayList<ShortcutInfo> appsToRemove;
if (folderAppsToRemove.containsKey(folder)) {
@@ -4692,7 +4721,7 @@
return true;
}
} else {
- if (componentNames.contains(cn)) {
+ if (componentNames.contains(cn) && info.user.equals(user)) {
childrenToRemove.add(children.get(info));
return true;
}
diff --git a/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java b/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java
new file mode 100644
index 0000000..3ba93ea
--- /dev/null
+++ b/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java
@@ -0,0 +1,34 @@
+/*
+ * 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.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 int getApplicationFlags();
+ public abstract long getFirstInstallTime();
+ public abstract Drawable getBadgedIcon(int density);
+}
diff --git a/src/com/android/launcher3/compat/LauncherActivityInfoCompatV16.java b/src/com/android/launcher3/compat/LauncherActivityInfoCompatV16.java
new file mode 100644
index 0000000..052d434
--- /dev/null
+++ b/src/com/android/launcher3/compat/LauncherActivityInfoCompatV16.java
@@ -0,0 +1,98 @@
+/*
+ * 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.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 ActivityInfo mActivityInfo;
+ private ComponentName mComponentName;
+ private PackageManager mPm;
+
+ LauncherActivityInfoCompatV16(Context context, ResolveInfo info) {
+ super();
+ this.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 mActivityInfo.loadLabel(mPm);
+ }
+
+ public Drawable getIcon(int density) {
+ Drawable d = null;
+ if (mActivityInfo.getIconResource() != 0) {
+ Resources resources;
+ try {
+ resources = mPm.getResourcesForApplication(mActivityInfo.packageName);
+ } catch (PackageManager.NameNotFoundException e) {
+ resources = null;
+ }
+ if (resources != null) {
+ try {
+ d = resources.getDrawableForDensity(mActivityInfo.getIconResource(), density);
+ } catch (Resources.NotFoundException e) {
+ // Return default icon below.
+ }
+ }
+ }
+ if (d == null) {
+ Resources resources = Resources.getSystem();
+ d = resources.getDrawableForDensity(android.R.mipmap.sym_def_app_icon, density);
+ }
+ return d;
+ }
+
+ public int getApplicationFlags() {
+ return mActivityInfo.applicationInfo.flags;
+ }
+
+ 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;
+ }
+
+ public Drawable getBadgedIcon(int density) {
+ return getIcon(density);
+ }
+}
diff --git a/src/com/android/launcher3/compat/LauncherActivityInfoCompatVL.java b/src/com/android/launcher3/compat/LauncherActivityInfoCompatVL.java
new file mode 100644
index 0000000..76125bd
--- /dev/null
+++ b/src/com/android/launcher3/compat/LauncherActivityInfoCompatVL.java
@@ -0,0 +1,82 @@
+/*
+ * 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.graphics.drawable.Drawable;
+import android.os.UserHandle;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class LauncherActivityInfoCompatVL extends LauncherActivityInfoCompat {
+ private Object mLauncherActivityInfo;
+ private Class mLauncherActivityInfoClass;
+ private Method mGetComponentName;
+ private Method mGetUser;
+ private Method mGetLabel;
+ private Method mGetIcon;
+ private Method mGetApplicationFlags;
+ private Method mGetFirstInstallTime;
+ private Method mGetBadgedIcon;
+
+ LauncherActivityInfoCompatVL(Object launcherActivityInfo) {
+ super();
+ mLauncherActivityInfo = launcherActivityInfo;
+ mLauncherActivityInfoClass = ReflectUtils.getClassForName(
+ "android.content.pm.LauncherActivityInfo");
+ mGetComponentName = ReflectUtils.getMethod(mLauncherActivityInfoClass, "getComponentName");
+ mGetUser = ReflectUtils.getMethod(mLauncherActivityInfoClass, "getUser");
+ mGetLabel = ReflectUtils.getMethod(mLauncherActivityInfoClass, "getLabel");
+ mGetIcon = ReflectUtils.getMethod(mLauncherActivityInfoClass, "getIcon", int.class);
+ mGetApplicationFlags = ReflectUtils.getMethod(mLauncherActivityInfoClass,
+ "getApplicationFlags");
+ mGetFirstInstallTime = ReflectUtils.getMethod(mLauncherActivityInfoClass,
+ "getFirstInstallTime");
+ mGetBadgedIcon = ReflectUtils.getMethod(mLauncherActivityInfoClass, "getBadgedIcon",
+ int.class);
+ }
+
+ public ComponentName getComponentName() {
+ return (ComponentName) ReflectUtils.invokeMethod(mLauncherActivityInfo, mGetComponentName);
+ }
+
+ public UserHandleCompat getUser() {
+ return UserHandleCompat.fromUser((UserHandle) ReflectUtils.invokeMethod(
+ mLauncherActivityInfo, mGetUser));
+ }
+
+ public CharSequence getLabel() {
+ return (CharSequence) ReflectUtils.invokeMethod(mLauncherActivityInfo, mGetLabel);
+ }
+
+ public Drawable getIcon(int density) {
+ return (Drawable) ReflectUtils.invokeMethod(mLauncherActivityInfo, mGetIcon, density);
+ }
+
+ public int getApplicationFlags() {
+ return (Integer) ReflectUtils.invokeMethod(mLauncherActivityInfo, mGetApplicationFlags);
+ }
+
+ public long getFirstInstallTime() {
+ return (Long) ReflectUtils.invokeMethod(mLauncherActivityInfo, mGetFirstInstallTime);
+ }
+
+ public Drawable getBadgedIcon(int density) {
+ return (Drawable) ReflectUtils.invokeMethod(mLauncherActivityInfo, mGetBadgedIcon, density);
+ }
+}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java
new file mode 100644
index 0000000..069e3de
--- /dev/null
+++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java
@@ -0,0 +1,79 @@
+/*
+ * 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.Intent;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public abstract class LauncherAppsCompat {
+
+ public static final String ACTION_MANAGED_PROFILE_ADDED =
+ "android.intent.action.MANAGED_PROFILE_ADDED";
+ public static final String ACTION_MANAGED_PROFILE_REMOVED =
+ "android.intent.action.MANAGED_PROFILE_REMOVED";
+
+ public interface OnAppsChangedListenerCompat {
+ void onPackageRemoved(UserHandleCompat user, String packageName);
+ void onPackageAdded(UserHandleCompat user, String packageName);
+ void onPackageChanged(UserHandleCompat user, String packageName);
+ void onPackagesAvailable(UserHandleCompat user, String[] packageNames, boolean replacing);
+ void onPackagesUnavailable(UserHandleCompat user, String[] packageNames, boolean replacing);
+ }
+
+ protected LauncherAppsCompat() {
+ }
+
+ public static LauncherAppsCompat getInstance(Context context) {
+ // TODO change this to use api version once L gets an API number.
+ if ("L".equals(Build.VERSION.CODENAME)) {
+ Object launcherApps = context.getSystemService("launcherapps");
+ if (launcherApps != null) {
+ LauncherAppsCompatVL compat = LauncherAppsCompatVL.build(context, launcherApps);
+ if (compat != null) {
+ return compat;
+ }
+ }
+ }
+ // Pre L or lunacher apps service not running, or reflection failed to find something.
+ return new LauncherAppsCompatV16(context);
+ }
+
+ public abstract List<LauncherActivityInfoCompat> getActivityList(String packageName,
+ UserHandleCompat user);
+ public abstract LauncherActivityInfoCompat resolveActivity(Intent intent,
+ UserHandleCompat user);
+ public abstract void startActivityForProfile(ComponentName component, Rect sourceBounds,
+ Bundle opts, UserHandleCompat user);
+ public abstract void addOnAppsChangedListener(OnAppsChangedListenerCompat listener);
+ public abstract void removeOnAppsChangedListener(OnAppsChangedListenerCompat listener);
+ public abstract boolean isPackageEnabledForProfile(String packageName, UserHandleCompat user);
+ public abstract boolean isActivityEnabledForProfile(ComponentName component,
+ UserHandleCompat user);
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
new file mode 100644
index 0000000..c739eb9
--- /dev/null
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
@@ -0,0 +1,191 @@
+/*
+ * 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.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageInfo;
+import android.content.pm.ResolveInfo;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class LauncherAppsCompatV16 extends LauncherAppsCompat {
+
+ private PackageManager mPm;
+ private Context mContext;
+ private List<OnAppsChangedListenerCompat> mListeners
+ = new ArrayList<OnAppsChangedListenerCompat>();
+ 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, Rect sourceBounds,
+ Bundle opts, UserHandleCompat user) {
+ 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 synchronized void addOnAppsChangedListener(OnAppsChangedListenerCompat listener) {
+ if (listener != null && !mListeners.contains(listener)) {
+ mListeners.add(listener);
+ if (mListeners.size() == 1) {
+ registerForPackageIntents();
+ }
+ }
+ }
+
+ public synchronized void removeOnAppsChangedListener(OnAppsChangedListenerCompat listener) {
+ mListeners.remove(listener);
+ if (mListeners.size() == 0) {
+ unregisterForPackageIntents();
+ }
+ }
+
+ public boolean isPackageEnabledForProfile(String packageName, UserHandleCompat user) {
+ try {
+ PackageInfo info = mPm.getPackageInfo(packageName, 0);
+ return info != null && info.applicationInfo.enabled;
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ public boolean isActivityEnabledForProfile(ComponentName component, UserHandleCompat user) {
+ try {
+ ActivityInfo info = mPm.getActivityInfo(component, 0);
+ return info != null && info.isEnabled();
+ } catch (NameNotFoundException e) {
+ 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);
+ }
+
+ private synchronized List<OnAppsChangedListenerCompat> getListeners() {
+ return new ArrayList<OnAppsChangedListenerCompat>(mListeners);
+ }
+
+ private 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 (OnAppsChangedListenerCompat listener : getListeners()) {
+ listener.onPackageChanged(user, packageName);
+ }
+ } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+ if (!replacing) {
+ for (OnAppsChangedListenerCompat listener : getListeners()) {
+ listener.onPackageRemoved(user, packageName);
+ }
+ }
+ // 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 (OnAppsChangedListenerCompat listener : getListeners()) {
+ listener.onPackageAdded(user, packageName);
+ }
+ } else {
+ for (OnAppsChangedListenerCompat listener : getListeners()) {
+ listener.onPackageChanged(user, packageName);
+ }
+ }
+ }
+ } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
+ final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+ String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ for (OnAppsChangedListenerCompat listener : getListeners()) {
+ listener.onPackagesAvailable(user, packages, replacing);
+ }
+ } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
+ final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+ String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ for (OnAppsChangedListenerCompat listener : getListeners()) {
+ listener.onPackagesUnavailable(user, packages, replacing);
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
new file mode 100644
index 0000000..21f2659
--- /dev/null
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
@@ -0,0 +1,209 @@
+/*
+ * 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.Intent;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import java.lang.reflect.InvocationHandler;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Method;
+
+public class LauncherAppsCompatVL extends LauncherAppsCompat {
+
+ private Object mLauncherApps;
+ private Class mLauncherAppsClass;
+ private Class mListenerClass;
+ private Method mGetActivityList;
+ private Method mResolveActivity;
+ private Method mStartActivityForProfile;
+ private Method mAddOnAppsChangedListener;
+ private Method mRemoveOnAppsChangedListener;
+ private Method mIsPackageEnabledForProfile;
+ private Method mIsActivityEnabledForProfile;
+
+ private Map<OnAppsChangedListenerCompat, Object> mListeners
+ = new HashMap<OnAppsChangedListenerCompat, Object>();
+
+ static LauncherAppsCompatVL build(Context context, Object launcherApps) {
+ LauncherAppsCompatVL compat = new LauncherAppsCompatVL(context, launcherApps);
+
+ compat.mListenerClass = ReflectUtils.getClassForName(
+ "android.content.pm.LauncherApps$OnAppsChangedListener");
+ compat.mLauncherAppsClass = ReflectUtils.getClassForName("android.content.pm.LauncherApps");
+
+ compat.mGetActivityList = ReflectUtils.getMethod(compat.mLauncherAppsClass,
+ "getActivityList",
+ String.class, UserHandle.class);
+ compat.mResolveActivity = ReflectUtils.getMethod(compat.mLauncherAppsClass,
+ "resolveActivity",
+ Intent.class, UserHandle.class);
+ compat.mStartActivityForProfile = ReflectUtils.getMethod(compat.mLauncherAppsClass,
+ "startActivityForProfile",
+ ComponentName.class, Rect.class, Bundle.class, UserHandle.class);
+ compat.mAddOnAppsChangedListener = ReflectUtils.getMethod(compat.mLauncherAppsClass,
+ "addOnAppsChangedListener", compat.mListenerClass);
+ compat.mRemoveOnAppsChangedListener = ReflectUtils.getMethod(compat.mLauncherAppsClass,
+ "removeOnAppsChangedListener", compat.mListenerClass);
+ compat.mIsPackageEnabledForProfile = ReflectUtils.getMethod(compat.mLauncherAppsClass,
+ "isPackageEnabledForProfile", String.class, UserHandle.class);
+ compat.mIsActivityEnabledForProfile = ReflectUtils.getMethod(compat.mLauncherAppsClass,
+ "isActivityEnabledForProfile", ComponentName.class, UserHandle.class);
+
+ if (compat.mListenerClass != null
+ && compat.mLauncherAppsClass != null
+ && compat.mGetActivityList != null
+ && compat.mResolveActivity != null
+ && compat.mStartActivityForProfile != null
+ && compat.mAddOnAppsChangedListener != null
+ && compat.mRemoveOnAppsChangedListener != null
+ && compat.mIsPackageEnabledForProfile != null
+ && compat.mIsActivityEnabledForProfile != null) {
+ return compat;
+ }
+ return null;
+ }
+
+ private LauncherAppsCompatVL(Context context, Object launcherApps) {
+ super();
+ mLauncherApps = launcherApps;
+ }
+
+ public List<LauncherActivityInfoCompat> getActivityList(String packageName,
+ UserHandleCompat user) {
+ List<Object> list = (List<Object>) ReflectUtils.invokeMethod(mLauncherApps,
+ mGetActivityList, packageName, user.getUser());
+ if (list.size() == 0) {
+ return Collections.EMPTY_LIST;
+ }
+ ArrayList<LauncherActivityInfoCompat> compatList =
+ new ArrayList<LauncherActivityInfoCompat>(list.size());
+ for (Object info : list) {
+ compatList.add(new LauncherActivityInfoCompatVL(info));
+ }
+ return compatList;
+ }
+
+ public LauncherActivityInfoCompat resolveActivity(Intent intent, UserHandleCompat user) {
+ Object activity = ReflectUtils.invokeMethod(mLauncherApps, mResolveActivity,
+ intent, user.getUser());
+ if (activity != null) {
+ return new LauncherActivityInfoCompatVL(activity);
+ } else {
+ return null;
+ }
+ }
+
+ public void startActivityForProfile(ComponentName component, Rect sourceBounds,
+ Bundle opts, UserHandleCompat user) {
+ ReflectUtils.invokeMethod(mLauncherApps, mStartActivityForProfile,
+ component, sourceBounds, opts, user.getUser());
+ }
+
+ public void addOnAppsChangedListener(LauncherAppsCompat.OnAppsChangedListenerCompat listener) {
+ Object wrappedListener = Proxy.newProxyInstance(mListenerClass.getClassLoader(),
+ new Class[]{mListenerClass}, new WrappedListener(listener));
+ synchronized (mListeners) {
+ mListeners.put(listener, wrappedListener);
+ }
+ ReflectUtils.invokeMethod(mLauncherApps, mAddOnAppsChangedListener, wrappedListener);
+ }
+
+ public void removeOnAppsChangedListener(
+ LauncherAppsCompat.OnAppsChangedListenerCompat listener) {
+ Object wrappedListener = null;
+ synchronized (mListeners) {
+ wrappedListener = mListeners.remove(listener);
+ }
+ if (wrappedListener != null) {
+ ReflectUtils.invokeMethod(mLauncherApps, mRemoveOnAppsChangedListener, wrappedListener);
+ }
+ }
+
+ public boolean isPackageEnabledForProfile(String packageName, UserHandleCompat user) {
+ return (Boolean) ReflectUtils.invokeMethod(mLauncherApps, mIsPackageEnabledForProfile,
+ packageName, user.getUser());
+ }
+
+ public boolean isActivityEnabledForProfile(ComponentName component, UserHandleCompat user) {
+ return (Boolean) ReflectUtils.invokeMethod(mLauncherApps, mIsActivityEnabledForProfile,
+ component, user.getUser());
+ }
+
+ private static class WrappedListener implements InvocationHandler {
+ private LauncherAppsCompat.OnAppsChangedListenerCompat mListener;
+
+ public WrappedListener(LauncherAppsCompat.OnAppsChangedListenerCompat listener) {
+ mListener = listener;
+ }
+
+ public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
+ try {
+ String methodName = m.getName();
+ if ("onPackageRemoved".equals(methodName)) {
+ onPackageRemoved((UserHandle) args[0], (String) args[1]);
+ } else if ("onPackageAdded".equals(methodName)) {
+ onPackageAdded((UserHandle) args[0], (String) args[1]);
+ } else if ("onPackageChanged".equals(methodName)) {
+ onPackageChanged((UserHandle) args[0], (String) args[1]);
+ } else if ("onPackagesAvailable".equals(methodName)) {
+ onPackagesAvailable((UserHandle) args[0], (String []) args[1],
+ (Boolean) args[2]);
+ } else if ("onPackagesUnavailable".equals(methodName)) {
+ onPackagesUnavailable((UserHandle) args[0], (String []) args[1],
+ (Boolean) args[2]);
+ }
+ } finally {
+ return null;
+ }
+ }
+
+ public void onPackageRemoved(UserHandle user, String packageName) {
+ mListener.onPackageRemoved(UserHandleCompat.fromUser(user), packageName);
+ }
+
+ public void onPackageAdded(UserHandle user, String packageName) {
+ mListener.onPackageAdded(UserHandleCompat.fromUser(user), packageName);
+ }
+
+ public void onPackageChanged(UserHandle user, String packageName) {
+ mListener.onPackageChanged(UserHandleCompat.fromUser(user), packageName);
+ }
+
+ public void onPackagesAvailable(UserHandle user, String[] packageNames, boolean replacing) {
+ mListener.onPackagesAvailable(UserHandleCompat.fromUser(user), packageNames, replacing);
+ }
+
+ public void onPackagesUnavailable(UserHandle user, String[] packageNames,
+ boolean replacing) {
+ mListener.onPackagesUnavailable(UserHandleCompat.fromUser(user), packageNames,
+ replacing);
+ }
+ }
+}
+
diff --git a/src/com/android/launcher3/compat/ReflectUtils.java b/src/com/android/launcher3/compat/ReflectUtils.java
new file mode 100644
index 0000000..e1b8a1f
--- /dev/null
+++ b/src/com/android/launcher3/compat/ReflectUtils.java
@@ -0,0 +1,159 @@
+/*
+ * 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.util.Log;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class ReflectUtils {
+ private static final String TAG = "LauncherReflect";
+
+ public static Class getClassForName(String className) {
+ try {
+ return Class.forName(className);
+ } catch (ClassNotFoundException e) {
+ Log.e(TAG, "Couldn't find class " + className, e);
+ return null;
+ }
+ }
+
+ public static Method getMethod(Class clazz, String method) {
+ try {
+ return clazz.getMethod(method);
+ } catch (NoSuchMethodException e) {
+ Log.e(TAG, "Couldn't find methid " + clazz.getName() + " " + method, e);
+ return null;
+ }
+ }
+
+ public static Method getMethod(Class clazz, String method, Class param1) {
+ try {
+ return clazz.getMethod(method, param1);
+ } catch (NoSuchMethodException e) {
+ Log.e(TAG, "Couldn't find methid " + clazz.getName() + " " + method, e);
+ return null;
+ }
+ }
+
+ public static Method getMethod(Class clazz, String method, Class param1, Class param2) {
+ try {
+ return clazz.getMethod(method, param1, param2);
+ } catch (NoSuchMethodException e) {
+ Log.e(TAG, "Couldn't find methid " + clazz.getName() + " " + method, e);
+ return null;
+ }
+ }
+
+ public static Method getMethod(Class clazz, String method, Class param1, Class param2,
+ Class param3) {
+ try {
+ return clazz.getMethod(method, param1, param2, param3);
+ } catch (NoSuchMethodException e) {
+ Log.e(TAG, "Couldn't find methid " + clazz.getName() + " " + method, e);
+ return null;
+ }
+ }
+
+ public static Method getMethod(Class clazz, String method, Class param1, Class param2,
+ Class param3, Class param4) {
+ try {
+ return clazz.getMethod(method, param1, param2, param3, param4);
+ } catch (NoSuchMethodException e) {
+ Log.e(TAG, "Couldn't find methid " + clazz.getName() + " " + method, e);
+ return null;
+ }
+ }
+
+ public static Object invokeMethod(Object object, Method method) {
+ try {
+ return method.invoke(object);
+ } catch (SecurityException e) {
+ Log.e(TAG, "Couldn't invoke method " + method, e);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Couldn't invoke method " + method, e);
+ } catch (IllegalAccessException e) {
+ Log.e(TAG, "Couldn't invoke method " + method, e);
+ } catch (InvocationTargetException e) {
+ Log.e(TAG, "Couldn't invoke method " + method, e);
+ }
+ return null;
+ }
+
+ public static Object invokeMethod(Object object, Method method, Object param1) {
+ try {
+ return method.invoke(object, param1);
+ } catch (SecurityException e) {
+ Log.e(TAG, "Couldn't invoke method " + method, e);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Couldn't invoke method " + method, e);
+ } catch (IllegalAccessException e) {
+ Log.e(TAG, "Couldn't invoke method " + method, e);
+ } catch (InvocationTargetException e) {
+ Log.e(TAG, "Couldn't invoke method " + method, e);
+ }
+ return null;
+ }
+
+ public static Object invokeMethod(Object object, Method method, Object param1, Object param2) {
+ try {
+ return method.invoke(object, param1, param2);
+ } catch (SecurityException e) {
+ Log.e(TAG, "Couldn't invoke method " + method, e);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Couldn't invoke method " + method, e);
+ } catch (IllegalAccessException e) {
+ Log.e(TAG, "Couldn't invoke method " + method, e);
+ } catch (InvocationTargetException e) {
+ Log.e(TAG, "Couldn't invoke method " + method, e);
+ }
+ return null;
+ }
+
+ public static Object invokeMethod(Object object, Method method, Object param1, Object param2,
+ Object param3) {
+ try {
+ return method.invoke(object, param1, param2, param3);
+ } catch (SecurityException e) {
+ Log.e(TAG, "Couldn't invoke method " + method, e);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Couldn't invoke method " + method, e);
+ } catch (IllegalAccessException e) {
+ Log.e(TAG, "Couldn't invoke method " + method, e);
+ } catch (InvocationTargetException e) {
+ Log.e(TAG, "Couldn't invoke method " + method, e);
+ }
+ return null;
+ }
+
+ public static Object invokeMethod(Object object, Method method, Object param1, Object param2,
+ Object param3, Object param4) {
+ try {
+ return method.invoke(object, param1, param2, param3, param4);
+ } catch (SecurityException e) {
+ Log.e(TAG, "Couldn't invoke method " + method, e);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Couldn't invoke method " + method, e);
+ } catch (IllegalAccessException e) {
+ Log.e(TAG, "Couldn't invoke method " + method, e);
+ } catch (InvocationTargetException e) {
+ Log.e(TAG, "Couldn't invoke method " + method, e);
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/compat/UserHandleCompat.java b/src/com/android/launcher3/compat/UserHandleCompat.java
new file mode 100644
index 0000000..8f5dda2
--- /dev/null
+++ b/src/com/android/launcher3/compat/UserHandleCompat.java
@@ -0,0 +1,81 @@
+/*
+ * 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.os.Build;
+import android.os.UserHandle;
+
+public class UserHandleCompat {
+ private UserHandle mUser;
+
+ private UserHandleCompat(UserHandle user) {
+ mUser = user;
+ }
+
+ private UserHandleCompat() {
+ }
+
+ public static UserHandleCompat myUserHandle() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return new UserHandleCompat(android.os.Process.myUserHandle());
+ } else {
+ return new UserHandleCompat();
+ }
+ }
+
+ static UserHandleCompat fromUser(UserHandle user) {
+ if (user == null) {
+ return null;
+ } else {
+ return new UserHandleCompat(user);
+ }
+ }
+
+ UserHandle getUser() {
+ return mUser;
+ }
+
+ @Override
+ public String toString() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return mUser.toString();
+ } else {
+ return "";
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof UserHandleCompat)) {
+ return false;
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return mUser.equals(((UserHandleCompat) other).mUser);
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return mUser.hashCode();
+ } else {
+ return 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/compat/UserManagerCompat.java b/src/com/android/launcher3/compat/UserManagerCompat.java
new file mode 100644
index 0000000..256e04a
--- /dev/null
+++ b/src/com/android/launcher3/compat/UserManagerCompat.java
@@ -0,0 +1,44 @@
+/*
+ * 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.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+
+import java.util.List;
+
+public abstract class UserManagerCompat {
+ protected UserManagerCompat() {
+ }
+
+ public static UserManagerCompat getInstance(Context context) {
+ // TODO change this to use api version once L gets an API number.
+ if ("L".equals(Build.VERSION.CODENAME)) {
+ return new UserManagerCompatVL(context);
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return new UserManagerCompatV17(context);
+ } else {
+ return new UserManagerCompatV16();
+ }
+ }
+
+ public abstract List<UserHandleCompat> getUserProfiles();
+ public abstract long getSerialNumberForUser(UserHandleCompat user);
+ public abstract UserHandleCompat getUserForSerialNumber(long serialNumber);
+ public abstract Drawable getBadgedDrawableForUser(Drawable unbadged, UserHandleCompat user);
+}
diff --git a/src/com/android/launcher3/compat/UserManagerCompatV16.java b/src/com/android/launcher3/compat/UserManagerCompatV16.java
new file mode 100644
index 0000000..2009e4e
--- /dev/null
+++ b/src/com/android/launcher3/compat/UserManagerCompatV16.java
@@ -0,0 +1,47 @@
+/*
+ * 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.graphics.drawable.Drawable;
+
+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 Drawable getBadgedDrawableForUser(Drawable unbadged,
+ UserHandleCompat user) {
+ return unbadged;
+ }
+
+ public long getSerialNumberForUser(UserHandleCompat user) {
+ return 0;
+ }
+}
diff --git a/src/com/android/launcher3/compat/UserManagerCompatV17.java b/src/com/android/launcher3/compat/UserManagerCompatV17.java
new file mode 100644
index 0000000..055359a
--- /dev/null
+++ b/src/com/android/launcher3/compat/UserManagerCompatV17.java
@@ -0,0 +1,42 @@
+/*
+ * 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.Context;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class UserManagerCompatV17 extends UserManagerCompatV16 {
+ protected UserManager mUserManager;
+
+ UserManagerCompatV17(Context context) {
+ mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ }
+
+ public long getSerialNumberForUser(UserHandleCompat user) {
+ return mUserManager.getSerialNumberForUser(user.getUser());
+ }
+
+ public UserHandleCompat getUserForSerialNumber(long serialNumber) {
+ return UserHandleCompat.fromUser(mUserManager.getUserForSerialNumber(serialNumber));
+ }
+}
+
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVL.java b/src/com/android/launcher3/compat/UserManagerCompatVL.java
new file mode 100644
index 0000000..8d3ca85
--- /dev/null
+++ b/src/com/android/launcher3/compat/UserManagerCompatVL.java
@@ -0,0 +1,68 @@
+
+/*
+ * 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.Context;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+public class UserManagerCompatVL extends UserManagerCompatV17 {
+
+ UserManagerCompatVL(Context context) {
+ super(context);
+ }
+
+ public List<UserHandleCompat> getUserProfiles() {
+ Method method = ReflectUtils.getMethod(mUserManager.getClass(), "getUserProfiles");
+ if (method != null) {
+ List<UserHandle> users = (List<UserHandle>) ReflectUtils.invokeMethod(
+ mUserManager, method);
+ if (users != null) {
+ ArrayList<UserHandleCompat> compatUsers = new ArrayList<UserHandleCompat>(
+ users.size());
+ for (UserHandle user : users) {
+ compatUsers.add(UserHandleCompat.fromUser(user));
+ }
+ return compatUsers;
+ }
+ }
+ // Fall back to non L version.
+ return super.getUserProfiles();
+ }
+
+ public Drawable getBadgedDrawableForUser(Drawable unbadged, UserHandleCompat user) {
+ Method method = ReflectUtils.getMethod(mUserManager.getClass(), "getBadgedDrawableForUser",
+ Drawable.class, UserHandle.class);
+ if (method != null) {
+ Drawable d = (Drawable) ReflectUtils.invokeMethod(mUserManager, method, unbadged,
+ user.getUser());
+ if (d != null) {
+ return d;
+ }
+ }
+ // Fall back to non L version.
+ return super.getBadgedDrawableForUser(unbadged, user);
+ }
+}
+