Merge "Do not hold synchronization locks if tracing is not enabled" into ub-launcher3-master
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 71f638f..d7c16e6 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -132,16 +132,6 @@
</intent-filter>
</activity>
- <!-- TODO: Remove -->
- <activity android:name="com.android.launcher3.util.MyActivity"
- android:theme="@style/AppTheme">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </activity>
-
<!--
Should point to the content provider which can be used to dump Launcher3 compatible
worspace configuration to the dump's file descriptor by using launcher_dump.proto
diff --git a/SecondaryDisplayLauncher/res/values-as/strings.xml b/SecondaryDisplayLauncher/res/values-as/strings.xml
new file mode 100644
index 0000000..5e4f690
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-as/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2018 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
+ <string name="couldnt_launch" msgid="7873588052226763866">"কাৰ্যকলাপটো লঞ্চ কৰিব পৰা নগ’ল"</string>
+ <string name="add_app_shortcut" msgid="2756755330707509435">"এপৰ শ্বর্টকাট যোগ কৰক"</string>
+ <string name="set_wallpaper" msgid="6475195450505435904">"ৱালপেপাৰ ছেট কৰক"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-bn/strings.xml b/SecondaryDisplayLauncher/res/values-bn/strings.xml
new file mode 100644
index 0000000..5500754
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-bn/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2018 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
+ <string name="couldnt_launch" msgid="7873588052226763866">"অ্যাক্টিভিটি চালু করা যায়নি"</string>
+ <string name="add_app_shortcut" msgid="2756755330707509435">"অ্যাপ শর্টকাট যোগ করুন"</string>
+ <string name="set_wallpaper" msgid="6475195450505435904">"ওয়ালপেপার সেট করুন"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-bs/strings.xml b/SecondaryDisplayLauncher/res/values-bs/strings.xml
index 2d700fd..7d2f011 100644
--- a/SecondaryDisplayLauncher/res/values-bs/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-bs/strings.xml
@@ -20,7 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="8116646702313548897">"Pokretač za sekundarni ekran"</string>
- <string name="couldnt_launch" msgid="7873588052226763866">"Nije bilo moguće pokrenuti aktivnost"</string>
+ <string name="couldnt_launch" msgid="7873588052226763866">"Pokretanje aktivnosti nije uspjelo"</string>
<string name="add_app_shortcut" msgid="2756755330707509435">"Dodaj prečicu aplikacije"</string>
<string name="set_wallpaper" msgid="6475195450505435904">"Postavi pozadinsku sliku"</string>
</resources>
diff --git a/SecondaryDisplayLauncher/res/values-gu/strings.xml b/SecondaryDisplayLauncher/res/values-gu/strings.xml
new file mode 100644
index 0000000..f13422e
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-gu/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2018 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
+ <string name="couldnt_launch" msgid="7873588052226763866">"પ્રવૃત્તિ લૉન્ચ કરી શકાઈ નથી"</string>
+ <string name="add_app_shortcut" msgid="2756755330707509435">"ઍપ શૉર્ટકટ ઉમેરો"</string>
+ <string name="set_wallpaper" msgid="6475195450505435904">"વૉલપેપર સેટ કરો"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-iw/strings.xml b/SecondaryDisplayLauncher/res/values-iw/strings.xml
new file mode 100644
index 0000000..f904c38
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-iw/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2018 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
+ <string name="couldnt_launch" msgid="7873588052226763866">"לא ניתן היה להפעיל את הפעילות"</string>
+ <string name="add_app_shortcut" msgid="2756755330707509435">"הוספת קיצור דרך של אפליקציה"</string>
+ <string name="set_wallpaper" msgid="6475195450505435904">"הגדרת טפט"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-kk/strings.xml b/SecondaryDisplayLauncher/res/values-kk/strings.xml
new file mode 100644
index 0000000..a679b09
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-kk/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2018 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
+ <string name="couldnt_launch" msgid="7873588052226763866">"Әрекет іске қосылмады"</string>
+ <string name="add_app_shortcut" msgid="2756755330707509435">"Қолданба таңбашасын енгізу"</string>
+ <string name="set_wallpaper" msgid="6475195450505435904">"Тұсқағаз орнату"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-kn/strings.xml b/SecondaryDisplayLauncher/res/values-kn/strings.xml
new file mode 100644
index 0000000..aa0ccc1
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-kn/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2018 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
+ <string name="couldnt_launch" msgid="7873588052226763866">"ಚಟುವಟಿಕೆಯನ್ನು ಲಾಂಚ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
+ <string name="add_app_shortcut" msgid="2756755330707509435">"ಆ್ಯಪ್ ಶಾರ್ಟ್ಕಟ್ ಸೇರಿಸಿ"</string>
+ <string name="set_wallpaper" msgid="6475195450505435904">"ವಾಲ್ಪೇಪರ್ ಹೊಂದಿಸಿ"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-mr/strings.xml b/SecondaryDisplayLauncher/res/values-mr/strings.xml
new file mode 100644
index 0000000..d54cc89
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-mr/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2018 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
+ <string name="couldnt_launch" msgid="7873588052226763866">"अॅक्टिव्हिटी लाँच करता आली नाही"</string>
+ <string name="add_app_shortcut" msgid="2756755330707509435">"अॅप शॉर्टकट जोडा"</string>
+ <string name="set_wallpaper" msgid="6475195450505435904">"वॉलपेपर सेट करा"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-ne/strings.xml b/SecondaryDisplayLauncher/res/values-ne/strings.xml
new file mode 100644
index 0000000..d40d806
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-ne/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2018 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
+ <string name="couldnt_launch" msgid="7873588052226763866">"उक्त क्रियाकलाप सुरु गर्न सकिएन"</string>
+ <string name="add_app_shortcut" msgid="2756755330707509435">"अनुप्रयोगको सर्टकट थप्नुहोस्"</string>
+ <string name="set_wallpaper" msgid="6475195450505435904">"वालपेपर सेट गर्नुहोस्"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-or/strings.xml b/SecondaryDisplayLauncher/res/values-or/strings.xml
new file mode 100644
index 0000000..6f04f99
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-or/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2018 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
+ <string name="couldnt_launch" msgid="7873588052226763866">"ଗତିବିଧିକୁ ଲଞ୍ଚ କରାଯାଇପାରିଲା ନାହିଁ"</string>
+ <string name="add_app_shortcut" msgid="2756755330707509435">"ଆପ୍ ସର୍ଟକଟ୍ ଯୋଗ କରନ୍ତୁ"</string>
+ <string name="set_wallpaper" msgid="6475195450505435904">"ୱାଲ୍ପେପର୍କୁ ସେଟ୍ କରନ୍ତୁ"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-ta/strings.xml b/SecondaryDisplayLauncher/res/values-ta/strings.xml
new file mode 100644
index 0000000..e2cfb4b
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-ta/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2018 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
+ <string name="couldnt_launch" msgid="7873588052226763866">"நடவடிக்கையைத் துவக்க இயலவில்லை"</string>
+ <string name="add_app_shortcut" msgid="2756755330707509435">"ஆப்ஸ் குறுக்குவழியைச் சேர்"</string>
+ <string name="set_wallpaper" msgid="6475195450505435904">"வால்பேப்பரை அமை"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-te/strings.xml b/SecondaryDisplayLauncher/res/values-te/strings.xml
new file mode 100644
index 0000000..f204e2a
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-te/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2018 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
+ <string name="couldnt_launch" msgid="7873588052226763866">"కార్యకలాపాన్ని ప్రారంభించడం సాధ్యం కాలేదు"</string>
+ <string name="add_app_shortcut" msgid="2756755330707509435">"యాప్ షార్ట్కట్ని జోడించు"</string>
+ <string name="set_wallpaper" msgid="6475195450505435904">"వాల్పేపర్ను సెట్ చేయండి"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values/strings.xml b/SecondaryDisplayLauncher/res/values/strings.xml
index 0f906c4..b68918a 100644
--- a/SecondaryDisplayLauncher/res/values/strings.xml
+++ b/SecondaryDisplayLauncher/res/values/strings.xml
@@ -18,7 +18,6 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name">SecondaryDisplayLauncher</string>
<string name="couldnt_launch">Couldn\'t launch the activity</string>
<string name="add_app_shortcut">Add app shortcut</string>
<string name="set_wallpaper">Set wallpaper</string>
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index f60572c..4646fd7f 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -45,7 +45,6 @@
import android.os.Looper;
import android.view.View;
-import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
@@ -194,7 +193,7 @@
int topMargin = context.getResources()
.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
int paddingTop = targetRect.rect.top - topMargin - dp.getInsets().top;
- int paddingBottom = dp.availableHeightPx + dp.getInsets().top - targetRect.rect.bottom;
+ int paddingBottom = dp.heightPx - dp.getInsets().bottom - targetRect.rect.bottom;
return FastOverviewState.OVERVIEW_TRANSLATION_FACTOR * (paddingBottom - paddingTop);
}
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index fec38bf..cedd952 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -16,8 +16,10 @@
package com.android.quickstep;
+import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.Context;
+import android.os.Build;
import android.os.Process;
import android.util.SparseBooleanArray;
import com.android.launcher3.MainThreadExecutor;
@@ -36,16 +38,20 @@
/**
* Manages the recent task list from the system, caching it as necessary.
*/
+@TargetApi(Build.VERSION_CODES.P)
public class RecentTasksList extends TaskStackChangeListener {
private final KeyguardManagerCompat mKeyguardManager;
private final MainThreadExecutor mMainThreadExecutor;
private final BackgroundExecutor mBgThreadExecutor;
+ private final TaskListStabilizer mStabilizer = new TaskListStabilizer();
// The list change id, increments as the task list changes in the system
private int mChangeId;
// The last change id when the list was last loaded completely, must be <= the list change id
private int mLastLoadedId;
+ // The last change id was loaded with keysOnly = true
+ private boolean mLastLoadHadKeysOnly;
ArrayList<Task> mTasks = new ArrayList<>();
@@ -57,41 +63,43 @@
}
/**
- * Asynchronously fetches the list of recent tasks.
+ * Fetches the task keys skipping any local cache.
+ */
+ public void getTaskKeys(int numTasks, Consumer<ArrayList<Task>> callback) {
+ // Kick off task loading in the background
+ mBgThreadExecutor.submit(() -> {
+ ArrayList<Task> tasks = loadTasksInBackground(numTasks, true /* loadKeysOnly */);
+ mMainThreadExecutor.execute(() -> callback.accept(tasks));
+ });
+ }
+
+ /**
+ * Asynchronously fetches the list of recent tasks, reusing cached list if available.
*
- * @param numTasks The maximum number of tasks to fetch
* @param loadKeysOnly Whether to load other associated task data, or just the key
* @param callback The callback to receive the list of recent tasks
* @return The change id of the current task list
*/
- public synchronized int getTasks(int numTasks, boolean loadKeysOnly,
- Consumer<ArrayList<Task>> callback) {
+ public synchronized int getTasks(boolean loadKeysOnly, Consumer<ArrayList<Task>> callback) {
final int requestLoadId = mChangeId;
- final int numLoadTasks = numTasks > 0
- ? numTasks
- : Integer.MAX_VALUE;
+ Runnable resultCallback = callback == null
+ ? () -> { }
+ : () -> callback.accept(mStabilizer.reorder(mTasks));
- if (mLastLoadedId == mChangeId) {
+ if (mLastLoadedId == mChangeId && (!mLastLoadHadKeysOnly || loadKeysOnly)) {
// The list is up to date, callback with the same list
- mMainThreadExecutor.execute(() -> {
- if (callback != null) {
- callback.accept(mTasks);
- }
- });
+ mMainThreadExecutor.execute(resultCallback);
}
// Kick off task loading in the background
mBgThreadExecutor.submit(() -> {
- ArrayList<Task> tasks = loadTasksInBackground(numLoadTasks,
- loadKeysOnly);
+ ArrayList<Task> tasks = loadTasksInBackground(Integer.MAX_VALUE, loadKeysOnly);
mMainThreadExecutor.execute(() -> {
mTasks = tasks;
mLastLoadedId = requestLoadId;
-
- if (callback != null) {
- callback.accept(tasks);
- }
+ mLastLoadHadKeysOnly = loadKeysOnly;
+ resultCallback.run();
});
});
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index a62e6ad..75ccba6 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -98,7 +98,7 @@
* @return the request id associated with this call.
*/
public int getTasks(Consumer<ArrayList<Task>> callback) {
- return mTaskList.getTasks(-1, false /* loadKeysOnly */, callback);
+ return mTaskList.getTasks(false /* loadKeysOnly */, callback);
}
/**
@@ -124,7 +124,7 @@
* called on the UI thread.
*/
public void findTaskWithId(int taskId, Consumer<Task.TaskKey> callback) {
- mTaskList.getTasks(-1, true /* loadKeysOnly */, (tasks) -> {
+ mTaskList.getTasks(true /* loadKeysOnly */, (tasks) -> {
for (Task task : tasks) {
if (task.key.id == taskId) {
callback.accept(task.key);
@@ -150,7 +150,7 @@
// Keep the cache up to date with the latest thumbnails
int runningTaskId = RecentsModel.getRunningTaskId();
- mTaskList.getTasks(mThumbnailCache.getCacheSize(), true /* keysOnly */, (tasks) -> {
+ mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), tasks -> {
for (Task task : tasks) {
if (task.key.id == runningTaskId) {
// Skip the running task, it's not going to have an up-to-date snapshot by the
diff --git a/quickstep/src/com/android/quickstep/TaskListStabilizer.java b/quickstep/src/com/android/quickstep/TaskListStabilizer.java
new file mode 100644
index 0000000..0d23973
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TaskListStabilizer.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TASK_STABILIZER;
+
+import android.os.SystemClock;
+import android.util.SparseArray;
+
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
+import com.android.systemui.shared.recents.model.Task;
+
+import java.util.ArrayList;
+
+public class TaskListStabilizer {
+
+ private static final long TASK_CACHE_TIMEOUT_MS = 5000;
+
+ private final SparseArray<Task> mTempMap = new SparseArray<>();
+ private final IntArray mTempArray = new IntArray();
+ private final IntSet mTempSet = new IntSet();
+
+ private final IntArray mLastStableOrder = new IntArray();
+ private final IntSet mLastSet = new IntSet();
+ private final IntArray mLastUnstableOrder = new IntArray();
+
+ private long mLastReorderTime;
+
+ public ArrayList<Task> reorder(ArrayList<Task> tasks) {
+ if (!ENABLE_TASK_STABILIZER.get()) {
+ return tasks;
+ }
+
+ // Create task id array
+ int count = tasks.size();
+ mTempArray.clear();
+ mTempSet.clear();
+ mTempMap.clear();
+
+ for (int i = 0; i < count; i++) {
+ Task t = tasks.get(i);
+ mTempMap.put(t.key.id, t);
+
+ mTempSet.add(t.key.id);
+ mTempArray.add(t.key.id);
+ }
+
+ if (mLastSet.equals(mTempSet) && isStabilizationQuickEnough()) {
+ if (mLastStableOrder.equals(mTempArray)) {
+ // Everything is same
+ return tasks;
+ }
+
+ if (!mLastUnstableOrder.equals(mTempArray)) {
+ // Fast reordering, record the current time.
+ mLastUnstableOrder.copyFrom(mTempArray);
+ mLastReorderTime = SystemClock.uptimeMillis();
+ }
+
+ // Reorder the tasks based on the last stable order.
+ ArrayList<Task> sorted = new ArrayList<>(count);
+ for (int i = 0; i < count; i++) {
+ sorted.add(mTempMap.get(mLastStableOrder.get(i)));
+ }
+ return sorted;
+ }
+
+ // Cache the data
+ mLastStableOrder.copyFrom(mTempArray);
+ mLastUnstableOrder.copyFrom(mTempArray);
+ mLastSet.copyFrom(mTempSet);
+
+ mLastReorderTime = SystemClock.uptimeMillis();
+
+ return tasks;
+ }
+
+ private boolean isStabilizationQuickEnough() {
+ return (SystemClock.uptimeMillis() - mLastReorderTime) < TASK_CACHE_TIMEOUT_MS;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index e1ffcf0..686b52b 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -27,6 +27,7 @@
import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
@@ -1028,10 +1029,17 @@
mRecentsView.animateUpRunningTaskIconScale();
if (mQuickScrubController.isQuickSwitch()) {
+ // Adjust the running task so that it is centered and fills the screen.
TaskView runningTask = mRecentsView.getRunningTaskView();
if (runningTask != null) {
- runningTask.setTranslationY(-mActivity.getResources().getDimension(
- R.dimen.task_thumbnail_half_top_margin) * 1f / mRecentsView.getScaleX());
+ float insetHeight = mDp.heightPx - mDp.getInsets().top - mDp.getInsets().bottom;
+ // Usually insetDiff will be 0, unless we allow apps to draw under the insets. In
+ // that case (insetDiff != 0), we need to center in the system-specified available
+ // height rather than launcher's inset height by adding half the insetDiff.
+ float insetDiff = mDp.availableHeightPx - insetHeight;
+ float topMargin = mActivity.getResources().getDimension(
+ R.dimen.task_thumbnail_half_top_margin);
+ runningTask.setTranslationY((insetDiff / 2 - topMargin) / mRecentsView.getScaleX());
}
}
RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
diff --git a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
index 898de97..d8a3282 100644
--- a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -329,13 +329,14 @@
canvas.concat(mTmpMatrix);
canvas.translate(mTargetRect.left, mTargetRect.top);
+ float scale = mTargetRect.width() / mSourceRect.width();
float insetProgress = (1 - progress);
ttv.drawOnCanvas(canvas,
-mSourceWindowClipInsets.left * insetProgress,
-mSourceWindowClipInsets.top * insetProgress,
ttv.getMeasuredWidth() + mSourceWindowClipInsets.right * insetProgress,
ttv.getMeasuredHeight() + mSourceWindowClipInsets.bottom * insetProgress,
- Utilities.mapRange(progress, mWindowCornerRadius, ttv.getCornerRadius()));
+ Utilities.mapRange(progress, mWindowCornerRadius * scale, ttv.getCornerRadius()));
}
public RectF getTargetRect() {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 7842fa4..6861bc1 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -24,6 +24,7 @@
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
+
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.LayoutTransition;
@@ -60,6 +61,7 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ListView;
import androidx.annotation.Nullable;
+
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
@@ -545,8 +547,8 @@
// Keep this logic in sync with ActivityControlHelper.getTranslationYForQuickScrub.
mTempRect.top -= mTaskTopMargin;
setPadding(mTempRect.left - mInsets.left, mTempRect.top - mInsets.top,
- dp.availableWidthPx + mInsets.left - mTempRect.right,
- dp.availableHeightPx + mInsets.top - mTempRect.bottom);
+ dp.widthPx - mInsets.right - mTempRect.right,
+ dp.heightPx - mInsets.bottom - mTempRect.bottom);
}
protected abstract void getTaskSize(DeviceProfile dp, Rect outRect);
@@ -749,7 +751,8 @@
setRunningTaskIconScaledDown(runningTaskIconScaledDown);
setRunningTaskHidden(runningTaskTileHidden);
- setCurrentPage(0);
+ TaskView tv = getRunningTaskView();
+ setCurrentPage(tv == null ? 0 : indexOfChild(tv));
// Load the tasks (if the loading is already
mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
diff --git a/res/values-v26/styles.xml b/res/values-v26/styles.xml
index e810ab2..8fb408b 100644
--- a/res/values-v26/styles.xml
+++ b/res/values-v26/styles.xml
@@ -19,6 +19,7 @@
<resources>
<!-- Theme for the widget container. -->
<style name="WidgetContainerTheme" parent="@android:style/Theme.DeviceDefault.Settings">
+ <item name="android:colorPrimaryDark">#E8EAED</item>
<item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
</style>
<style name="WidgetContainerTheme.Dark" parent="AppTheme.Dark">
diff --git a/robolectric_tests/src/com/android/launcher3/model/BaseGridChangesTestCase.java b/robolectric_tests/src/com/android/launcher3/model/BaseGridChangesTestCase.java
new file mode 100644
index 0000000..07834fc
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/model/BaseGridChangesTestCase.java
@@ -0,0 +1,121 @@
+package com.android.launcher3.model;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.sqlite.SQLiteDatabase;
+
+import com.android.launcher3.LauncherProvider;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.util.TestLauncherProvider;
+
+import org.junit.Before;
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowContentResolver;
+import org.robolectric.shadows.ShadowLog;
+
+public abstract class BaseGridChangesTestCase {
+
+
+ public static final int DESKTOP = LauncherSettings.Favorites.CONTAINER_DESKTOP;
+ public static final int HOTSEAT = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+
+ public static final int APP_ICON = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+ public static final int SHORTCUT = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+ public static final int NO__ICON = -1;
+
+ public static final String TEST_PACKAGE = "com.android.launcher3.validpackage";
+
+ public Context mContext;
+ public TestLauncherProvider mProvider;
+ public SQLiteDatabase mDb;
+
+ @Before
+ public void setUpBaseCase() {
+ ShadowLog.stream = System.out;
+
+ mContext = RuntimeEnvironment.application;
+ mProvider = Robolectric.setupContentProvider(TestLauncherProvider.class);
+ ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, mProvider);
+ mDb = mProvider.getDb();
+ }
+
+ /**
+ * Adds a dummy item in the DB.
+ * @param type {@link #APP_ICON} or {@link #SHORTCUT} or >= 2 for
+ * folder (where the type represents the number of items in the folder).
+ */
+ public int addItem(int type, int screen, int container, int x, int y) {
+ int id = LauncherSettings.Settings.call(mContext.getContentResolver(),
+ LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
+ .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+
+ ContentValues values = new ContentValues();
+ values.put(LauncherSettings.Favorites._ID, id);
+ values.put(LauncherSettings.Favorites.CONTAINER, container);
+ values.put(LauncherSettings.Favorites.SCREEN, screen);
+ values.put(LauncherSettings.Favorites.CELLX, x);
+ values.put(LauncherSettings.Favorites.CELLY, y);
+ values.put(LauncherSettings.Favorites.SPANX, 1);
+ values.put(LauncherSettings.Favorites.SPANY, 1);
+
+ if (type == APP_ICON || type == SHORTCUT) {
+ values.put(LauncherSettings.Favorites.ITEM_TYPE, type);
+ values.put(LauncherSettings.Favorites.INTENT,
+ new Intent(Intent.ACTION_MAIN).setPackage(TEST_PACKAGE).toUri(0));
+ } else {
+ values.put(LauncherSettings.Favorites.ITEM_TYPE,
+ LauncherSettings.Favorites.ITEM_TYPE_FOLDER);
+ // Add folder items.
+ for (int i = 0; i < type; i++) {
+ addItem(APP_ICON, 0, id, 0, 0);
+ }
+ }
+
+ mContext.getContentResolver().insert(LauncherSettings.Favorites.CONTENT_URI, values);
+ return id;
+ }
+
+ public int[][][] createGrid(int[][][] typeArray) {
+ return createGrid(typeArray, 1);
+ }
+
+ /**
+ * Initializes the DB with dummy elements to represent the provided grid structure.
+ * @param typeArray A 3d array of item types. {@see #addItem(int, long, long, int, int)} for
+ * type definitions. The first dimension represents the screens and the next
+ * two represent the workspace grid.
+ * @param startScreen First screen id from where the icons will be added.
+ * @return the same grid representation where each entry is the corresponding item id.
+ */
+ public int[][][] createGrid(int[][][] typeArray, int startScreen) {
+ LauncherSettings.Settings.call(mContext.getContentResolver(),
+ LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
+ int[][][] ids = new int[typeArray.length][][];
+
+ for (int i = 0; i < typeArray.length; i++) {
+ // Add screen to DB
+ int screenId = startScreen + i;
+
+ // Keep the screen id counter up to date
+ LauncherSettings.Settings.call(mContext.getContentResolver(),
+ LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
+
+ ids[i] = new int[typeArray[i].length][];
+ for (int y = 0; y < typeArray[i].length; y++) {
+ ids[i][y] = new int[typeArray[i][y].length];
+ for (int x = 0; x < typeArray[i][y].length; x++) {
+ if (typeArray[i][y][x] < 0) {
+ // Empty cell
+ ids[i][y][x] = -1;
+ } else {
+ ids[i][y][x] = addItem(typeArray[i][y][x], screenId, DESKTOP, x, y);
+ }
+ }
+ }
+ }
+
+ return ids;
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/model/GridBackupTableTest.java b/robolectric_tests/src/com/android/launcher3/model/GridBackupTableTest.java
new file mode 100644
index 0000000..53287a9
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/model/GridBackupTableTest.java
@@ -0,0 +1,115 @@
+package com.android.launcher3.model;
+
+
+import static android.database.DatabaseUtils.queryNumEntries;
+
+import static com.android.launcher3.LauncherSettings.Favorites.BACKUP_TABLE_NAME;
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ContentValues;
+import android.graphics.Point;
+
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.LauncherSettings.Settings;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/**
+ * Unit tests for {@link GridBackupTable}
+ */
+@RunWith(RobolectricTestRunner.class)
+public class GridBackupTableTest extends BaseGridChangesTestCase {
+
+ private static final int BACKUP_ITEM_COUNT = 12;
+
+ @Before
+ public void setupGridData() {
+ createGrid(new int[][][]{{
+ { APP_ICON, APP_ICON, SHORTCUT, SHORTCUT},
+ { SHORTCUT, SHORTCUT, NO__ICON, NO__ICON},
+ { NO__ICON, NO__ICON, SHORTCUT, SHORTCUT},
+ { APP_ICON, SHORTCUT, SHORTCUT, APP_ICON},
+ }});
+ assertEquals(BACKUP_ITEM_COUNT, queryNumEntries(mDb, TABLE_NAME));
+ }
+
+ @Test
+ public void backupTableCreated() {
+ GridBackupTable backupTable = new GridBackupTable(mContext, mDb, 4, 4, 4);
+ assertFalse(backupTable.backupOrRestoreAsNeeded());
+ Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
+
+ assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
+
+ // One extra entry for properties
+ assertEquals(BACKUP_ITEM_COUNT + 1, queryNumEntries(mDb, BACKUP_TABLE_NAME));
+ }
+
+ @Test
+ public void backupTableRestored() {
+ assertFalse(new GridBackupTable(mContext, mDb, 4, 4, 4).backupOrRestoreAsNeeded());
+ Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
+
+ // Delete entries
+ mDb.delete(TABLE_NAME, null, null);
+ assertEquals(0, queryNumEntries(mDb, TABLE_NAME));
+
+ GridBackupTable backupTable = new GridBackupTable(mContext, mDb, 3, 3, 3);
+ assertTrue(backupTable.backupOrRestoreAsNeeded());
+
+ // Items have been restored
+ assertEquals(BACKUP_ITEM_COUNT, queryNumEntries(mDb, TABLE_NAME));
+
+ Point outSize = new Point();
+ assertEquals(4, backupTable.getRestoreHotseatAndGridSize(outSize));
+ assertEquals(4, outSize.x);
+ assertEquals(4, outSize.y);
+ }
+
+ @Test
+ public void backupTableRemovedOnAdd() {
+ assertFalse(new GridBackupTable(mContext, mDb, 4, 4, 4).backupOrRestoreAsNeeded());
+ Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
+
+ assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
+
+ addItem(1, 2, DESKTOP, 1, 1);
+ assertFalse(tableExists(mDb, BACKUP_TABLE_NAME));
+ }
+
+ @Test
+ public void backupTableRemovedOnDelete() {
+ assertFalse(new GridBackupTable(mContext, mDb, 4, 4, 4).backupOrRestoreAsNeeded());
+ Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
+
+ assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
+
+ mContext.getContentResolver().delete(Favorites.CONTENT_URI, null, null);
+ assertFalse(tableExists(mDb, BACKUP_TABLE_NAME));
+ }
+
+ @Test
+ public void backupTableRetainedOnUpdate() {
+ assertFalse(new GridBackupTable(mContext, mDb, 4, 4, 4).backupOrRestoreAsNeeded());
+ Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
+
+ assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
+
+ ContentValues values = new ContentValues();
+ values.put(Favorites.RANK, 4);
+ // Something was updated
+ assertTrue(mContext.getContentResolver()
+ .update(Favorites.CONTENT_URI, values, null, null) > 0);
+
+ // Backup table remains
+ assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
index d4188aa..ce07a27 100644
--- a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
@@ -5,29 +5,21 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
import android.database.Cursor;
import android.graphics.Point;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.config.FlagOverrideRule;
import com.android.launcher3.config.FlagOverrideRule.FlagOverride;
import com.android.launcher3.model.GridSizeMigrationTask.MultiStepMigrationTask;
import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.TestLauncherProvider;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.shadows.ShadowContentResolver;
import java.util.HashSet;
import java.util.LinkedList;
@@ -36,47 +28,33 @@
* Unit tests for {@link GridSizeMigrationTask}
*/
@RunWith(RobolectricTestRunner.class)
-public class GridSizeMigrationTaskTest {
-
- private static final int DESKTOP = LauncherSettings.Favorites.CONTAINER_DESKTOP;
- private static final int HOTSEAT = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
-
- private static final int APPLICATION = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
- private static final int SHORTCUT = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
-
- private static final String TEST_PACKAGE = "com.android.launcher3.validpackage";
+public class GridSizeMigrationTaskTest extends BaseGridChangesTestCase {
@Rule
public final FlagOverrideRule flags = new FlagOverrideRule();
private HashSet<String> mValidPackages;
private InvariantDeviceProfile mIdp;
- private Context mContext;
- private TestLauncherProvider mProvider;
@Before
public void setUp() {
mValidPackages = new HashSet<>();
mValidPackages.add(TEST_PACKAGE);
mIdp = new InvariantDeviceProfile();
- mContext = RuntimeEnvironment.application;
-
- mProvider = Robolectric.setupContentProvider(TestLauncherProvider.class);
- ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, mProvider);
}
@Test
public void testHotseatMigration_apps_dropped() throws Exception {
int[] hotseatItems = {
- addItem(APPLICATION, 0, HOTSEAT, 0, 0),
+ addItem(APP_ICON, 0, HOTSEAT, 0, 0),
addItem(SHORTCUT, 1, HOTSEAT, 0, 0),
-1,
addItem(SHORTCUT, 3, HOTSEAT, 0, 0),
- addItem(APPLICATION, 4, HOTSEAT, 0, 0),
+ addItem(APP_ICON, 4, HOTSEAT, 0, 0),
};
mIdp.numHotseatIcons = 3;
- new GridSizeMigrationTask(mContext, mIdp, mValidPackages, 5, 3)
+ new GridSizeMigrationTask(mContext, mDb, mValidPackages, 5, 3)
.migrateHotseat();
// First item is dropped as it has the least weight.
verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
@@ -85,7 +63,7 @@
@Test
public void testHotseatMigration_shortcuts_dropped() throws Exception {
int[] hotseatItems = {
- addItem(APPLICATION, 0, HOTSEAT, 0, 0),
+ addItem(APP_ICON, 0, HOTSEAT, 0, 0),
addItem(30, 1, HOTSEAT, 0, 0),
-1,
addItem(SHORTCUT, 3, HOTSEAT, 0, 0),
@@ -93,7 +71,7 @@
};
mIdp.numHotseatIcons = 3;
- new GridSizeMigrationTask(mContext, mIdp, mValidPackages, 5, 3)
+ new GridSizeMigrationTask(mContext, mDb, mValidPackages, 5, 3)
.migrateHotseat();
// First item is dropped as it has the least weight.
verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
@@ -138,7 +116,7 @@
{ 5, 2, -1, 6},
}});
- new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
+ new GridSizeMigrationTask(mContext, mDb, mValidPackages,
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Column 2 and row 2 got removed.
@@ -158,7 +136,7 @@
{ 5, 2, -1, 6},
}});
- new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
+ new GridSizeMigrationTask(mContext, mDb, mValidPackages,
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Items in the second column get moved to new screen
@@ -183,7 +161,7 @@
{ 3, 1, -1, 4},
}});
- new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
+ new GridSizeMigrationTask(mContext, mDb, mValidPackages,
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Items in the second column of the first screen should get placed on the 3rd
@@ -215,7 +193,7 @@
{ 5, 2, -1, 6},
}});
- new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
+ new GridSizeMigrationTask(mContext, mDb, mValidPackages,
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Items in the second column of the first screen should get placed on a new screen.
@@ -244,7 +222,7 @@
{ 5, 2, 7, -1},
}}, 0);
- new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
+ new GridSizeMigrationTask(mContext, mDb, mValidPackages,
new Point(4, 4), new Point(3, 4)).migrateWorkspace();
// Items in the second column of the first screen should get placed on a new screen.
@@ -269,7 +247,7 @@
{ 5, 6, 7, -1},
}}, 0);
- new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
+ new GridSizeMigrationTask(mContext, mDb, mValidPackages,
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Items in the second column of the first screen should get placed on a new screen.
@@ -283,54 +261,13 @@
}});
}
- private int[][][] createGrid(int[][][] typeArray) throws Exception {
- return createGrid(typeArray, 1);
- }
-
- /**
- * Initializes the DB with dummy elements to represent the provided grid structure.
- * @param typeArray A 3d array of item types. {@see #addItem(int, long, long, int, int)} for
- * type definitions. The first dimension represents the screens and the next
- * two represent the workspace grid.
- * @return the same grid representation where each entry is the corresponding item id.
- */
- private int[][][] createGrid(int[][][] typeArray, int startScreen) throws Exception {
- LauncherSettings.Settings.call(mContext.getContentResolver(),
- LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
- int[][][] ids = new int[typeArray.length][][];
-
- for (int i = 0; i < typeArray.length; i++) {
- // Add screen to DB
- int screenId = startScreen + i;
-
- // Keep the screen id counter up to date
- LauncherSettings.Settings.call(mContext.getContentResolver(),
- LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
-
- ids[i] = new int[typeArray[i].length][];
- for (int y = 0; y < typeArray[i].length; y++) {
- ids[i][y] = new int[typeArray[i][y].length];
- for (int x = 0; x < typeArray[i][y].length; x++) {
- if (typeArray[i][y][x] < 0) {
- // Empty cell
- ids[i][y][x] = -1;
- } else {
- ids[i][y][x] = addItem(typeArray[i][y][x], screenId, DESKTOP, x, y);
- }
- }
- }
- }
-
- return ids;
- }
-
/**
* Verifies that the workspace items are arranged in the provided order.
* @param ids A 3d array where the first dimension represents the screen, and the rest two
* represent the workspace grid.
*/
private void verifyWorkspace(int[][][] ids) {
- IntArray allScreens = getWorkspaceScreenIds(mContext);
+ IntArray allScreens = getWorkspaceScreenIds(mDb);
assertEquals(ids.length, allScreens.size());
int total = 0;
@@ -367,42 +304,6 @@
c.close();
}
- /**
- * Adds a dummy item in the DB.
- * @param type {@link #APPLICATION} or {@link #SHORTCUT} or >= 2 for
- * folder (where the type represents the number of items in the folder).
- */
- private int addItem(int type, int screen, int container, int x, int y) throws Exception {
- int id = LauncherSettings.Settings.call(mContext.getContentResolver(),
- LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
- .getInt(LauncherSettings.Settings.EXTRA_VALUE);
-
- ContentValues values = new ContentValues();
- values.put(LauncherSettings.Favorites._ID, id);
- values.put(LauncherSettings.Favorites.CONTAINER, container);
- values.put(LauncherSettings.Favorites.SCREEN, screen);
- values.put(LauncherSettings.Favorites.CELLX, x);
- values.put(LauncherSettings.Favorites.CELLY, y);
- values.put(LauncherSettings.Favorites.SPANX, 1);
- values.put(LauncherSettings.Favorites.SPANY, 1);
-
- if (type == APPLICATION || type == SHORTCUT) {
- values.put(LauncherSettings.Favorites.ITEM_TYPE, type);
- values.put(LauncherSettings.Favorites.INTENT,
- new Intent(Intent.ACTION_MAIN).setPackage(TEST_PACKAGE).toUri(0));
- } else {
- values.put(LauncherSettings.Favorites.ITEM_TYPE,
- LauncherSettings.Favorites.ITEM_TYPE_FOLDER);
- // Add folder items.
- for (int i = 0; i < type; i++) {
- addItem(APPLICATION, 0, id, 0, 0);
- }
- }
-
- mContext.getContentResolver().insert(LauncherSettings.Favorites.CONTENT_URI, values);
- return id;
- }
-
@Test
public void testMultiStepMigration_small_to_large() throws Exception {
MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier();
@@ -435,7 +336,7 @@
private final LinkedList<Point> mPoints;
public MultiStepMigrationTaskVerifier(int... points) {
- super(null, null);
+ super(null, null, null);
mPoints = new LinkedList<>();
for (int i = 0; i < points.length; i += 2) {
diff --git a/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java b/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java
index 32808f5..31e303e 100644
--- a/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java
+++ b/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java
@@ -23,6 +23,11 @@
}
}
+ public SQLiteDatabase getDb() {
+ createDbIfNotExists();
+ return mOpenHelper.getWritableDatabase();
+ }
+
@Override
protected void notifyListeners() { }
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 257e46c..dbefa45 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -40,6 +40,7 @@
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collections;
import androidx.annotation.VisibleForTesting;
@@ -135,7 +136,7 @@
float minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), dm);
float minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), dm);
// Sort the profiles based on the closeness to the device size
- allOptions.sort((a, b) ->
+ Collections.sort(allOptions, (a, b) ->
Float.compare(dist(minWidthDps, minHeightDps, a.minWidthDps, a.minHeightDps),
dist(minWidthDps, minHeightDps, b.minWidthDps, b.minHeightDps)));
DisplayOption interpolatedDisplayOption =
@@ -314,18 +315,6 @@
return (float) Math.hypot(x1 - x0, y1 - y0);
}
- /**
- * Returns the closest device profiles ordered by closeness to the specified width and height
- */
- @VisibleForTesting
- static ArrayList<DisplayOption> sortByClosenessToSize(
- float width, float height, ArrayList<DisplayOption> points) {
- points.sort((a, b) ->
- Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps),
- dist(width, height, b.minWidthDps, b.minHeightDps)));
- return points;
- }
-
@VisibleForTesting
static DisplayOption invDistWeightedInterpolate(float width, float height,
ArrayList<DisplayOption> points) {
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 8ed3314..24dc17a 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -16,6 +16,9 @@
package com.android.launcher3;
+import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
+import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
+
import android.annotation.TargetApi;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
@@ -210,6 +213,7 @@
addModifiedTime(initialValues);
final int rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
if (rowId < 0) return null;
+ mOpenHelper.onAddOrDeleteOp(db);
uri = ContentUris.withAppendedId(uri, rowId);
notifyListeners();
@@ -282,6 +286,7 @@
return 0;
}
}
+ mOpenHelper.onAddOrDeleteOp(db);
t.commit();
}
@@ -290,15 +295,30 @@
return values.length;
}
+ @TargetApi(Build.VERSION_CODES.M)
@Override
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
createDbIfNotExists();
try (SQLiteTransaction t = new SQLiteTransaction(mOpenHelper.getWritableDatabase())) {
- ContentProviderResult[] result = super.applyBatch(operations);
+ boolean isAddOrDelete = !Utilities.ATLEAST_MARSHMALLOW;
+
+ final int numOperations = operations.size();
+ final ContentProviderResult[] results = new ContentProviderResult[numOperations];
+ for (int i = 0; i < numOperations; i++) {
+ ContentProviderOperation op = operations.get(i);
+ results[i] = op.apply(this, results, i);
+
+ isAddOrDelete |= (op.isInsert() || op.isDelete()) &&
+ results[i].count != null && results[i].count > 0;
+ }
+ if (isAddOrDelete) {
+ mOpenHelper.onAddOrDeleteOp(t.getDb());
+ }
+
t.commit();
reloadLauncherIfExternal();
- return result;
+ return results;
}
}
@@ -315,6 +335,7 @@
}
int count = db.delete(args.table, args.where, args.args);
if (count > 0) {
+ mOpenHelper.onAddOrDeleteOp(db);
notifyListeners();
reloadLauncherIfExternal();
}
@@ -381,6 +402,17 @@
mOpenHelper.removeGhostWidgets(mOpenHelper.getWritableDatabase());
return null;
}
+ case LauncherSettings.Settings.METHOD_NEW_TRANSACTION: {
+ Bundle result = new Bundle();
+ result.putBinder(LauncherSettings.Settings.EXTRA_VALUE,
+ new SQLiteTransaction(mOpenHelper.getWritableDatabase()));
+ return result;
+ }
+ case LauncherSettings.Settings.METHOD_REFRESH_BACKUP_TABLE: {
+ mOpenHelper.mBackupTableExists =
+ tableExists(mOpenHelper.getReadableDatabase(), Favorites.BACKUP_TABLE_NAME);
+ return null;
+ }
}
return null;
}
@@ -528,17 +560,19 @@
private final Context mContext;
private int mMaxItemId = -1;
private int mMaxScreenId = -1;
+ private boolean mBackupTableExists;
DatabaseHelper(Context context, Handler widgetHostResetHandler) {
this(context, widgetHostResetHandler, LauncherFiles.LAUNCHER_DB);
// Table creation sometimes fails silently, which leads to a crash loop.
// This way, we will try to create a table every time after crash, so the device
// would eventually be able to recover.
- if (!tableExists(Favorites.TABLE_NAME)) {
+ if (!tableExists(getReadableDatabase(), Favorites.TABLE_NAME)) {
Log.e(TAG, "Tables are missing after onCreate has been called. Trying to recreate");
// This operation is a no-op if the table already exists.
addFavoritesTable(getWritableDatabase(), true);
}
+ mBackupTableExists = tableExists(getReadableDatabase(), Favorites.BACKUP_TABLE_NAME);
initIds();
}
@@ -564,18 +598,6 @@
}
}
- private boolean tableExists(String tableName) {
- Cursor c = getReadableDatabase().query(
- true, "sqlite_master", new String[] {"tbl_name"},
- "tbl_name = ?", new String[] {tableName},
- null, null, null, null, null);
- try {
- return c.getCount() > 0;
- } finally {
- c.close();
- }
- }
-
@Override
public void onCreate(SQLiteDatabase db) {
if (LOGD) Log.d(TAG, "creating new launcher database");
@@ -590,6 +612,13 @@
onEmptyDbCreated();
}
+ protected void onAddOrDeleteOp(SQLiteDatabase db) {
+ if (mBackupTableExists) {
+ dropTable(db, Favorites.BACKUP_TABLE_NAME);
+ mBackupTableExists = false;
+ }
+ }
+
/**
* Overriden in tests.
*/
@@ -733,7 +762,7 @@
Favorites.CONTAINER, Favorites.CONTAINER_DESKTOP);
db.execSQL(query);
}
- db.execSQL("DROP TABLE IF EXISTS workspaceScreens");
+ dropTable(db, "workspaceScreens");
}
case 28:
// DB Upgraded successfully
@@ -762,8 +791,8 @@
*/
public void createEmptyDB(SQLiteDatabase db) {
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
- db.execSQL("DROP TABLE IF EXISTS " + Favorites.TABLE_NAME);
- db.execSQL("DROP TABLE IF EXISTS workspaceScreens");
+ dropTable(db, Favorites.TABLE_NAME);
+ dropTable(db, "workspaceScreens");
onCreate(db);
t.commit();
}
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 79c8208..e248ba0 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -89,6 +89,11 @@
public static final String TABLE_NAME = "favorites";
/**
+ * Backup table created when when the favorites table is modified during grid migration
+ */
+ public static final String BACKUP_TABLE_NAME = "favorites_bakup";
+
+ /**
* The content:// style URL for this table
*/
public static final Uri CONTENT_URI = Uri.parse("content://" +
@@ -231,8 +236,13 @@
public static final String OPTIONS = "options";
public static void addTableToDb(SQLiteDatabase db, long myProfileId, boolean optional) {
+ addTableToDb(db, myProfileId, optional, TABLE_NAME);
+ }
+
+ public static void addTableToDb(SQLiteDatabase db, long myProfileId, boolean optional,
+ String tableName) {
String ifNotExists = optional ? " IF NOT EXISTS " : "";
- db.execSQL("CREATE TABLE " + ifNotExists + TABLE_NAME + " (" +
+ db.execSQL("CREATE TABLE " + ifNotExists + tableName + " (" +
"_id INTEGER PRIMARY KEY," +
"title TEXT," +
"intent TEXT," +
@@ -279,6 +289,10 @@
public static final String METHOD_REMOVE_GHOST_WIDGETS = "remove_ghost_widgets";
+ public static final String METHOD_NEW_TRANSACTION = "new_db_transaction";
+
+ public static final String METHOD_REFRESH_BACKUP_TABLE = "refresh_backup_table";
+
public static final String EXTRA_VALUE = "value";
public static Bundle call(ContentResolver cr, String method) {
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 23ad912..3f420fa 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -95,6 +95,9 @@
public static final TogglableFlag APPLY_CONFIG_AT_RUNTIME = new TogglableFlag(
"APPLY_CONFIG_AT_RUNTIME", false, "Apply display changes dynamically");
+ public static final TogglableFlag ENABLE_TASK_STABILIZER = new TogglableFlag(
+ "ENABLE_TASK_STABILIZER", true, "Stable task list across fast task switches");
+
public static void initialize(Context context) {
// Avoid the disk read for user builds
if (Utilities.IS_DEBUG_DEVICE) {
diff --git a/src/com/android/launcher3/model/GridBackupTable.java b/src/com/android/launcher3/model/GridBackupTable.java
new file mode 100644
index 0000000..804a040
--- /dev/null
+++ b/src/com/android/launcher3/model/GridBackupTable.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.model;
+
+import static com.android.launcher3.LauncherSettings.Favorites.BACKUP_TABLE_NAME;
+import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
+import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.graphics.Point;
+import android.os.Process;
+import android.util.Log;
+
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.LauncherSettings.Settings;
+import com.android.launcher3.compat.UserManagerCompat;
+
+/**
+ * Helper class to backup and restore Favorites table into a separate table
+ * within the same data base.
+ */
+public class GridBackupTable {
+ private static final String TAG = "GridBackupTable";
+
+ private static final int ID_PROPERTY = -1;
+
+ private static final String KEY_HOTSEAT_SIZE = Favorites.SCREEN;
+ private static final String KEY_GRID_X_SIZE = Favorites.SPANX;
+ private static final String KEY_GRID_Y_SIZE = Favorites.SPANY;
+ private static final String KEY_DB_VERSION = Favorites.RANK;
+
+ private final Context mContext;
+ private final SQLiteDatabase mDb;
+
+ private final int mOldHotseatSize;
+ private final int mOldGridX;
+ private final int mOldGridY;
+
+ private int mRestoredHotseatSize;
+ private int mRestoredGridX;
+ private int mRestoredGridY;
+
+ public GridBackupTable(Context context, SQLiteDatabase db,
+ int hotseatSize, int gridX, int gridY) {
+ mContext = context;
+ mDb = db;
+
+ mOldHotseatSize = hotseatSize;
+ mOldGridX = gridX;
+ mOldGridY = gridY;
+ }
+
+ public boolean backupOrRestoreAsNeeded() {
+ // Check if backup table exists
+ if (!tableExists(mDb, BACKUP_TABLE_NAME)) {
+ if (Settings.call(mContext.getContentResolver(), Settings.METHOD_WAS_EMPTY_DB_CREATED)
+ .getBoolean(Settings.EXTRA_VALUE, false)) {
+ // No need to copy if empty DB was created.
+ return false;
+ }
+
+ copyTable(Favorites.TABLE_NAME, BACKUP_TABLE_NAME);
+ encodeDBProperties();
+ return false;
+ }
+
+ if (!loadDbProperties()) {
+ return false;
+ }
+ copyTable(BACKUP_TABLE_NAME, Favorites.TABLE_NAME);
+ Log.d(TAG, "Backup table found");
+ return true;
+ }
+
+ public int getRestoreHotseatAndGridSize(Point outGridSize) {
+ outGridSize.set(mRestoredGridX, mRestoredGridY);
+ return mRestoredHotseatSize;
+ }
+
+ private void copyTable(String from, String to) {
+ long userSerial = UserManagerCompat.getInstance(mContext).getSerialNumberForUser(
+ Process.myUserHandle());
+ dropTable(mDb, to);
+ Favorites.addTableToDb(mDb, userSerial, false, to);
+ mDb.execSQL("INSERT INTO " + to + " SELECT * FROM " + from + " where _id > " + ID_PROPERTY);
+ }
+
+ private void encodeDBProperties() {
+ ContentValues values = new ContentValues();
+ values.put(Favorites._ID, ID_PROPERTY);
+ values.put(KEY_DB_VERSION, mDb.getVersion());
+ values.put(KEY_GRID_X_SIZE, mOldGridX);
+ values.put(KEY_GRID_Y_SIZE, mOldGridY);
+ values.put(KEY_HOTSEAT_SIZE, mOldHotseatSize);
+ mDb.insert(BACKUP_TABLE_NAME, null, values);
+ }
+
+ private boolean loadDbProperties() {
+ try (Cursor c = mDb.query(BACKUP_TABLE_NAME, new String[] {
+ KEY_DB_VERSION, // 0
+ KEY_GRID_X_SIZE, // 1
+ KEY_GRID_Y_SIZE, // 2
+ KEY_HOTSEAT_SIZE}, // 3
+ "_id=" + ID_PROPERTY, null, null, null, null)) {
+ if (!c.moveToNext()) {
+ Log.e(TAG, "Meta data not found in backup table");
+ return false;
+ }
+ if (mDb.getVersion() != c.getInt(0)) {
+ return false;
+ }
+
+ mRestoredGridX = c.getInt(1);
+ mRestoredGridY = c.getInt(2);
+ mRestoredHotseatSize = c.getInt(3);
+ return true;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index a9ddccb..1c7bffa 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -1,10 +1,10 @@
package com.android.launcher3.model;
+import static com.android.launcher3.LauncherSettings.Settings.EXTRA_VALUE;
import static com.android.launcher3.Utilities.getPointString;
import static com.android.launcher3.Utilities.parsePoint;
import android.content.ComponentName;
-import android.content.ContentProviderOperation;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
@@ -12,24 +12,27 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
import android.graphics.Point;
import android.util.Log;
+import android.util.SparseArray;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.LauncherSettings.Settings;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.provider.LauncherDbUtils;
+import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.IntSparseArrayMap;
import java.util.ArrayList;
@@ -60,13 +63,13 @@
private static final float WT_WIDGET_FACTOR = 0.6f;
private static final float WT_FOLDER_FACTOR = 0.5f;
- private final Context mContext;
- private final InvariantDeviceProfile mIdp;
+ protected final SQLiteDatabase mDb;
+ protected final Context mContext;
- private final ContentValues mTempValues = new ContentValues();
protected final IntArray mEntryToRemove = new IntArray();
- private final ArrayList<ContentProviderOperation> mUpdateOperations = new ArrayList<>();
protected final ArrayList<DbEntry> mCarryOver = new ArrayList<>();
+
+ private final SparseArray<ContentValues> mUpdateOperations = new SparseArray<>();
private final HashSet<String> mValidPackages;
private final int mSrcX, mSrcY;
@@ -76,11 +79,11 @@
private final int mSrcHotseatSize;
private final int mDestHotseatSize;
- protected GridSizeMigrationTask(Context context, InvariantDeviceProfile idp,
+ protected GridSizeMigrationTask(Context context, SQLiteDatabase db,
HashSet<String> validPackages, Point sourceSize, Point targetSize) {
mContext = context;
+ mDb = db;
mValidPackages = validPackages;
- mIdp = idp;
mSrcX = sourceSize.x;
mSrcY = sourceSize.y;
@@ -95,11 +98,10 @@
mSrcHotseatSize = mDestHotseatSize = -1;
}
- protected GridSizeMigrationTask(Context context,
- InvariantDeviceProfile idp, HashSet<String> validPackages,
- int srcHotseatSize, int destHotseatSize) {
+ protected GridSizeMigrationTask(Context context, SQLiteDatabase db,
+ HashSet<String> validPackages, int srcHotseatSize, int destHotseatSize) {
mContext = context;
- mIdp = idp;
+ mDb = db;
mValidPackages = validPackages;
mSrcHotseatSize = srcHotseatSize;
@@ -118,20 +120,21 @@
*/
private boolean applyOperations() throws Exception {
// Update items
- if (!mUpdateOperations.isEmpty()) {
- mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, mUpdateOperations);
+ int updateCount = mUpdateOperations.size();
+ for (int i = 0; i < updateCount; i++) {
+ mDb.update(Favorites.TABLE_NAME, mUpdateOperations.valueAt(i),
+ "_id=" + mUpdateOperations.keyAt(i), null);
}
if (!mEntryToRemove.isEmpty()) {
if (DEBUG) {
Log.d(TAG, "Removing items: " + mEntryToRemove.toConcatString());
}
- mContext.getContentResolver().delete(LauncherSettings.Favorites.CONTENT_URI,
- Utilities.createDbSelectionQuery(
- LauncherSettings.Favorites._ID, mEntryToRemove), null);
+ mDb.delete(Favorites.TABLE_NAME, Utilities.createDbSelectionQuery(
+ Favorites._ID, mEntryToRemove), null);
}
- return !mUpdateOperations.isEmpty() || !mEntryToRemove.isEmpty();
+ return updateCount > 0 || !mEntryToRemove.isEmpty();
}
/**
@@ -181,24 +184,17 @@
}
@VisibleForTesting
- static IntArray getWorkspaceScreenIds(Context context) {
- IntSet set = new IntSet();
- try (Cursor c = context.getContentResolver().query(Favorites.CONTENT_URI,
- new String[] {Favorites.SCREEN},
+ static IntArray getWorkspaceScreenIds(SQLiteDatabase db) {
+ return LauncherDbUtils.queryIntArray(db, Favorites.TABLE_NAME, Favorites.SCREEN,
Favorites.CONTAINER + " = " + Favorites.CONTAINER_DESKTOP,
- null, Favorites.SCREEN)) {
- while (c.moveToNext()) {
- set.add(c.getInt(0));
- }
- }
- return set.getArray();
+ Favorites.SCREEN, Favorites.SCREEN);
}
/**
* @return true if any DB change was made
*/
protected boolean migrateWorkspace() throws Exception {
- IntArray allScreens = getWorkspaceScreenIds(mContext);
+ IntArray allScreens = getWorkspaceScreenIds(mDb);
if (allScreens.isEmpty()) {
throw new Exception("Unable to get workspace screens");
}
@@ -230,8 +226,7 @@
int newScreenId = LauncherSettings.Settings.call(
mContext.getContentResolver(),
LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
- .getInt(LauncherSettings.Settings.EXTRA_VALUE);
-
+ .getInt(EXTRA_VALUE);
for (DbEntry item : placement.finalPlacedItems) {
if (!mCarryOver.remove(itemMap.get(item.id))) {
throw new Exception("Unable to find matching items");
@@ -362,11 +357,9 @@
* Updates an item in the DB.
*/
protected void update(DbEntry item) {
- mTempValues.clear();
- item.addToContentValues(mTempValues);
- mUpdateOperations.add(ContentProviderOperation
- .newUpdate(LauncherSettings.Favorites.getContentUri(item.id))
- .withValues(mTempValues).build());
+ ContentValues values = new ContentValues();
+ item.addToContentValues(values);
+ mUpdateOperations.put(item.id, values);
}
/**
@@ -612,13 +605,13 @@
}
private ArrayList<DbEntry> loadHotseatEntries() {
- Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+ Cursor c = queryWorkspace(
new String[]{
Favorites._ID, // 0
Favorites.ITEM_TYPE, // 1
Favorites.INTENT, // 2
Favorites.SCREEN}, // 3
- Favorites.CONTAINER + " = " + Favorites.CONTAINER_HOTSEAT, null, null, null);
+ Favorites.CONTAINER + " = " + Favorites.CONTAINER_HOTSEAT);
final int indexId = c.getColumnIndexOrThrow(Favorites._ID);
final int indexItemType = c.getColumnIndexOrThrow(Favorites.ITEM_TYPE);
@@ -796,8 +789,7 @@
}
protected Cursor queryWorkspace(String[] columns, String where) {
- return mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
- columns, where, null, null, null);
+ return mDb.query(Favorites.TABLE_NAME, columns, where, null, null, null, null);
}
/**
@@ -864,11 +856,11 @@
}
public void addToContentValues(ContentValues values) {
- values.put(LauncherSettings.Favorites.SCREEN, screenId);
- values.put(LauncherSettings.Favorites.CELLX, cellX);
- values.put(LauncherSettings.Favorites.CELLY, cellY);
- values.put(LauncherSettings.Favorites.SPANX, spanX);
- values.put(LauncherSettings.Favorites.SPANY, spanY);
+ values.put(Favorites.SCREEN, screenId);
+ values.put(Favorites.CELLX, cellX);
+ values.put(Favorites.CELLY, cellY);
+ values.put(Favorites.SPANX, spanX);
+ values.put(Favorites.SPANY, spanY);
}
}
@@ -907,34 +899,43 @@
}
long migrationStartTime = System.currentTimeMillis();
- try {
+ try (SQLiteTransaction transaction = (SQLiteTransaction) Settings.call(
+ context.getContentResolver(), Settings.METHOD_NEW_TRANSACTION)
+ .getBinder(Settings.EXTRA_VALUE)) {
+
+ int srcHotseatCount = prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
+ idp.numHotseatIcons);
+ Point sourceSize = parsePoint(prefs.getString(
+ KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString));
+
boolean dbChanged = false;
+ GridBackupTable backupTable = new GridBackupTable(context, transaction.getDb(),
+ srcHotseatCount, sourceSize.x, sourceSize.y);
+ if (backupTable.backupOrRestoreAsNeeded()) {
+ dbChanged = true;
+ srcHotseatCount = backupTable.getRestoreHotseatAndGridSize(sourceSize);
+ }
+
HashSet<String> validPackages = getValidPackages(context);
// Hotseat
- int srcHotseatCount = prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
- idp.numHotseatIcons);
if (srcHotseatCount != idp.numHotseatIcons) {
// Migrate hotseat.
-
- dbChanged = new GridSizeMigrationTask(context, LauncherAppState.getIDP(context),
+ dbChanged = new GridSizeMigrationTask(context, transaction.getDb(),
validPackages, srcHotseatCount, idp.numHotseatIcons).migrateHotseat();
}
// Grid size
Point targetSize = new Point(idp.numColumns, idp.numRows);
- Point sourceSize = parsePoint(prefs.getString(
- KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString));
-
- if (new MultiStepMigrationTask(validPackages, context).migrate(sourceSize,
- targetSize)) {
+ if (new MultiStepMigrationTask(validPackages, context, transaction.getDb())
+ .migrate(sourceSize, targetSize)) {
dbChanged = true;
}
if (dbChanged) {
// Make sure we haven't removed everything.
final Cursor c = context.getContentResolver().query(
- LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
+ Favorites.CONTENT_URI, null, null, null, null);
boolean hasData = c.moveToNext();
c.close();
if (!hasData) {
@@ -942,6 +943,8 @@
}
}
+ transaction.commit();
+ Settings.call(context.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
return true;
} catch (Exception e) {
Log.e(TAG, "Error during grid migration", e);
@@ -982,19 +985,24 @@
*/
public static IntSparseArrayMap<Object> removeBrokenHotseatItems(Context context)
throws Exception {
- GridSizeMigrationTask task = new GridSizeMigrationTask(
- context, LauncherAppState.getIDP(context), getValidPackages(context),
- Integer.MAX_VALUE, Integer.MAX_VALUE);
+ try (SQLiteTransaction transaction = (SQLiteTransaction) Settings.call(
+ context.getContentResolver(), Settings.METHOD_NEW_TRANSACTION)
+ .getBinder(Settings.EXTRA_VALUE)) {
+ GridSizeMigrationTask task = new GridSizeMigrationTask(
+ context, transaction.getDb(), getValidPackages(context),
+ Integer.MAX_VALUE, Integer.MAX_VALUE);
- // Load all the valid entries
- ArrayList<DbEntry> items = task.loadHotseatEntries();
- // Delete any entry marked for deletion by above load.
- task.applyOperations();
- IntSparseArrayMap<Object> positions = new IntSparseArrayMap<>();
- for (DbEntry item : items) {
- positions.put(item.screenId, item);
+ // Load all the valid entries
+ ArrayList<DbEntry> items = task.loadHotseatEntries();
+ // Delete any entry marked for deletion by above load.
+ task.applyOperations();
+ IntSparseArrayMap<Object> positions = new IntSparseArrayMap<>();
+ for (DbEntry item : items) {
+ positions.put(item.screenId, item);
+ }
+ transaction.commit();
+ return positions;
}
- return positions;
}
/**
@@ -1003,10 +1011,13 @@
protected static class MultiStepMigrationTask {
private final HashSet<String> mValidPackages;
private final Context mContext;
+ private final SQLiteDatabase mDb;
- public MultiStepMigrationTask(HashSet<String> validPackages, Context context) {
+ public MultiStepMigrationTask(HashSet<String> validPackages, Context context,
+ SQLiteDatabase db) {
mValidPackages = validPackages;
mContext = context;
+ mDb = db;
}
public boolean migrate(Point sourceSize, Point targetSize) throws Exception {
@@ -1042,7 +1053,7 @@
}
protected boolean runStepTask(Point sourceSize, Point nextSize) throws Exception {
- return new GridSizeMigrationTask(mContext, LauncherAppState.getIDP(mContext),
+ return new GridSizeMigrationTask(mContext, mDb,
mValidPackages, sourceSize, nextSize).migrateWorkspace();
}
}
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index a4fe570..8a6aa1af 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -672,6 +672,11 @@
appWidgetInfo.spanY = c.getInt(spanYIndex);
appWidgetInfo.user = c.user;
+ if (appWidgetInfo.spanX <= 0 || appWidgetInfo.spanY <= 0) {
+ c.markDeleted("Widget has invalid size: "
+ + appWidgetInfo.spanX + "x" + appWidgetInfo.spanY);
+ continue;
+ }
if (!c.isOnWorkspaceOrHotseat()) {
c.markDeleted("Widget found where container != " +
"CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
index b79478a..2c843f9 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.java
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.java
@@ -21,6 +21,7 @@
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
+import android.os.Binder;
import android.util.Log;
import com.android.launcher3.LauncherAppState;
@@ -103,10 +104,22 @@
return out;
}
+ public static boolean tableExists(SQLiteDatabase db, String tableName) {
+ try (Cursor c = db.query(true, "sqlite_master", new String[] {"tbl_name"},
+ "tbl_name = ?", new String[] {tableName},
+ null, null, null, null, null)) {
+ return c.getCount() > 0;
+ }
+ }
+
+ public static void dropTable(SQLiteDatabase db, String tableName) {
+ db.execSQL("DROP TABLE IF EXISTS " + tableName);
+ }
+
/**
* Utility class to simplify managing sqlite transactions
*/
- public static class SQLiteTransaction implements AutoCloseable {
+ public static class SQLiteTransaction extends Binder implements AutoCloseable {
private final SQLiteDatabase mDb;
public SQLiteTransaction(SQLiteDatabase db) {
@@ -122,5 +135,9 @@
public void close() {
mDb.endTransaction();
}
+
+ public SQLiteDatabase getDb() {
+ return mDb;
+ }
}
}
diff --git a/src/com/android/launcher3/provider/LossyScreenMigrationTask.java b/src/com/android/launcher3/provider/LossyScreenMigrationTask.java
index 9166b83..6d839f3 100644
--- a/src/com/android/launcher3/provider/LossyScreenMigrationTask.java
+++ b/src/com/android/launcher3/provider/LossyScreenMigrationTask.java
@@ -37,29 +37,21 @@
*/
public class LossyScreenMigrationTask extends GridSizeMigrationTask {
- private final SQLiteDatabase mDb;
-
private final IntSparseArrayMap<DbEntry> mOriginalItems;
private final IntSparseArrayMap<DbEntry> mUpdates;
protected LossyScreenMigrationTask(
Context context, InvariantDeviceProfile idp, SQLiteDatabase db) {
// Decrease the rows count by 1
- super(context, idp, getValidPackages(context),
+ super(context, db, getValidPackages(context),
new Point(idp.numColumns, idp.numRows + 1),
new Point(idp.numColumns, idp.numRows));
- mDb = db;
mOriginalItems = new IntSparseArrayMap<>();
mUpdates = new IntSparseArrayMap<>();
}
@Override
- protected Cursor queryWorkspace(String[] columns, String where) {
- return mDb.query(Favorites.TABLE_NAME, columns, where, null, null, null, null);
- }
-
- @Override
protected void update(DbEntry item) {
mUpdates.put(item.id, item.copy());
}
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 17c66b4..bcca4d8 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -16,6 +16,8 @@
package com.android.launcher3.provider;
+import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
+
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@@ -111,7 +113,7 @@
db.execSQL("ALTER TABLE favorites RENAME TO favorites_old;");
Favorites.addTableToDb(db, newProfileId, false);
db.execSQL("INSERT INTO favorites SELECT * FROM favorites_old;");
- db.execSQL("DROP TABLE favorites_old;");
+ dropTable(db, "favorites_old");
}
/**
diff --git a/src/com/android/launcher3/util/IntArray.java b/src/com/android/launcher3/util/IntArray.java
index b2fb32a..d2a551f 100644
--- a/src/com/android/launcher3/util/IntArray.java
+++ b/src/com/android/launcher3/util/IntArray.java
@@ -100,6 +100,14 @@
}
/**
+ * Sets the array to be same as {@param other}
+ */
+ public void copyFrom(IntArray other) {
+ clear();
+ addAll(other);
+ }
+
+ /**
* Ensures capacity to append at least <code>count</code> values.
*/
private void ensureCapacity(int count) {
@@ -127,6 +135,25 @@
return wrap(toArray());
}
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj instanceof IntArray) {
+ IntArray arr = (IntArray) obj;
+ if (mSize == arr.mSize) {
+ for (int i = 0; i < mSize; i++) {
+ if (arr.mValues[i] != mValues[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Returns the value at the specified position in this array.
*/
diff --git a/src/com/android/launcher3/util/IntSet.java b/src/com/android/launcher3/util/IntSet.java
index 2459fad..851f129 100644
--- a/src/com/android/launcher3/util/IntSet.java
+++ b/src/com/android/launcher3/util/IntSet.java
@@ -49,10 +49,29 @@
return mArray.size();
}
+ public void clear() {
+ mArray.clear();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ return (obj instanceof IntSet) && ((IntSet) obj).mArray.equals(mArray);
+ }
+
public IntArray getArray() {
return mArray;
}
+ /**
+ * Sets this set to be same as {@param other}
+ */
+ public void copyFrom(IntSet other) {
+ mArray.copyFrom(other.mArray);
+ }
+
public static IntSet wrap(IntArray array) {
IntSet set = new IntSet();
set.mArray.addAll(array);
diff --git a/src/com/android/launcher3/util/MyActivity.java b/src/com/android/launcher3/util/MyActivity.java
deleted file mode 100644
index 379a8da..0000000
--- a/src/com/android/launcher3/util/MyActivity.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.util;
-
-import android.annotation.TargetApi;
-import android.app.Activity;
-import android.graphics.Bitmap;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.os.Bundle;
-import android.view.View;
-import android.widget.ImageView;
-
-import com.android.launcher3.LauncherAppState;
-
-import com.android.launcher3.graphics.LauncherPreviewRenderer;
-
-/**
- * TODO: Remove this class
- */
-@TargetApi(Build.VERSION_CODES.O)
-public class MyActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- new AsyncTask<Void, Void, Bitmap>() {
- @Override
- protected Bitmap doInBackground(Void... voids) {
- LauncherPreviewRenderer renderer = new LauncherPreviewRenderer(MyActivity.this,
- LauncherAppState.getIDP(MyActivity.this));
-
- Bitmap b = renderer.createScreenShot();
- return b;
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- ImageView view = new ImageView(MyActivity.this);
- view.setImageBitmap(bitmap);
- setContentView(view);
-
- view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
- | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
-
- }
- }.execute();
- }
-}