Snap for 9914775 from f6bf07a5fe to udc-release

Change-Id: I6f41cfe7cdb8085e9c5d124c5d510419cac13faa
This commit is contained in:
Android Build Coastguard Worker
2023-04-11 01:31:45 +00:00
29 changed files with 748 additions and 1281 deletions
-79
View File
@@ -1,79 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.launcher3.taskbar.TaskbarEduView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:gravity="bottom"
android:orientation="vertical"
android:layout_marginHorizontal="108dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/edu_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_rounded_corner_bottom_sheet"
android:gravity="center_horizontal"
android:paddingHorizontal="36dp"
android:paddingTop="64dp">
<com.android.launcher3.taskbar.TaskbarEduPagedView
android:id="@+id/content"
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="378dp"
app:layout_constraintTop_toTopOf="parent"
launcher:pageIndicator="@+id/content_page_indicator" />
<Button
android:id="@+id/edu_start_button"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:layout_marginBottom="18dp"
android:layout_marginTop="32dp"
app:layout_constraintTop_toBottomOf="@id/content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:text="@string/taskbar_edu_close"
style="@style/TaskbarEdu.Button.Close"
android:textColor="?android:attr/textColorPrimary"/>
<com.android.launcher3.pageindicators.PageIndicatorDots
android:id="@+id/content_page_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="@id/edu_start_button"
app:layout_constraintBottom_toBottomOf="@id/edu_start_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:elevation="1dp" />
<Button
android:id="@+id/edu_end_button"
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="@id/edu_start_button"
app:layout_constraintBottom_toBottomOf="@id/edu_start_button"
app:layout_constraintEnd_toEndOf="parent"
android:text="@string/taskbar_edu_next"
style="@style/TaskbarEdu.Button.Next"
android:textColor="?androidprv:attr/textColorOnAccent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.android.launcher3.taskbar.TaskbarEduView>
@@ -106,7 +106,7 @@
<Button
android:id="@+id/done_button"
style="@style/TaskbarEdu.Button.Next"
style="@style/TaskbarEdu.Button.Done"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:layout_marginTop="32dp"
@@ -1,58 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
style="@style/TextAppearance.TaskbarEdu.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/taskbar_edu_splitscreen" />
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/animation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:lottie_rawRes="@raw/taskbar_edu_splitscreen_persistent" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
style="@style/TextAppearance.TaskbarEdu.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/taskbar_edu_suggestions" />
<com.airbnb.lottie.LottieAnimationView
android:id="@id/animation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:lottie_rawRes="@raw/taskbar_edu_suggestions_persistent" />
</LinearLayout>
</merge>
@@ -1,78 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
style="@style/TextAppearance.TaskbarEdu.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/taskbar_edu_stashing" />
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/animation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:lottie_rawRes="@raw/taskbar_edu_stashing_transient" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
style="@style/TextAppearance.TaskbarEdu.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/taskbar_edu_splitscreen" />
<com.airbnb.lottie.LottieAnimationView
android:id="@id/animation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:lottie_rawRes="@raw/taskbar_edu_splitscreen_transient" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
style="@style/TextAppearance.TaskbarEdu.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/taskbar_edu_suggestions" />
<com.airbnb.lottie.LottieAnimationView
android:id="@id/animation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:lottie_rawRes="@raw/taskbar_edu_suggestions_transient" />
</LinearLayout>
</merge>
File diff suppressed because one or more lines are too long
-8
View File
@@ -241,10 +241,6 @@
<!-- ******* Taskbar Edu ******* -->
<!-- Accessibility title for the Taskbar education window. [CHAR_LIMIT=NONE] -->
<string name="taskbar_edu_a11y_title">Taskbar education</string>
<!-- Accessibility text spoken when the Taskbar education panel appears [CHAR_LIMIT=NONE] -->
<string name="taskbar_edu_opened">Taskbar education appeared</string>
<!-- Accessibility text spoken when the Taskbar education panel disappears [CHAR_LIMIT=NONE] -->
<string name="taskbar_edu_closed">Taskbar education closed</string>
<!-- Text in dialog that lets a user know how they can use the Taskbar to use multiple apps at once on their device. [CHAR_LIMIT=60] -->
<string name="taskbar_edu_splitscreen">Drag an app to the side to use 2 apps at once</string>
<!-- Text in dialog that lets a user know how they can show the Taskbar on their device. [CHAR_LIMIT=60] -->
@@ -255,10 +251,6 @@
<string name="taskbar_edu_settings_persistent">Turn on gesture navigation in Settings to auto-hide the Taskbar</string>
<!-- Title in dialog that shows a user what they can do with the Taskbar. [CHAR_LIMIT=60] -->
<string name="taskbar_edu_features">Do more with the Taskbar</string>
<!-- Text on button to go to the next screen of a tutorial [CHAR_LIMIT=16] -->
<string name="taskbar_edu_next">Next</string>
<!-- Text on button to go to the previous screen of a tutorial [CHAR_LIMIT=16] -->
<string name="taskbar_edu_previous">Back</string>
<!-- Text on button to exit a tutorial [CHAR_LIMIT=16] -->
<string name="taskbar_edu_close">Close</string>
<!-- Text on button to finish a tutorial [CHAR_LIMIT=16] -->
+1 -18
View File
@@ -195,30 +195,13 @@
<item name="iconDisplay">taskbar</item>
</style>
<style name="TaskbarEdu.Button.Close" parent="@android:style/Widget.Material.Button">
<item name="android:background">@drawable/button_taskbar_edu_bordered</item>
<item name="android:stateListAnimator">@null</item>
<item name="android:textSize">16sp</item>
<item name="android:padding">4dp</item>
</style>
<style name="TaskbarEdu.Button.Next" parent="@android:style/Widget.Material.Button">
<style name="TaskbarEdu.Button.Done" parent="@android:style/Widget.Material.Button">
<item name="android:background">@drawable/button_taskbar_edu_colored</item>
<item name="android:stateListAnimator">@null</item>
<item name="android:textSize">16sp</item>
<item name="android:padding">4dp</item>
</style>
<style name="TextAppearance.TaskbarEdu.Title"
parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle" >
<item name="android:layout_marginHorizontal">16dp</item>
<item name="android:gravity">center_horizontal</item>
<item name="android:fontFamily">google-sans</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">24sp</item>
<item name="android:lines">2</item>
</style>
<style name="TextAppearance.TaskbarEduTooltip.Title" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle">
<item name="android:gravity">center_horizontal</item>
<item name="android:fontFamily">google-sans</item>
@@ -48,11 +48,15 @@ import android.os.UserHandle;
import android.util.Log;
import android.util.StatsEvent;
import androidx.annotation.AnyThread;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.InstanceIdSequence;
@@ -62,6 +66,7 @@ import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.PersistedItemArray;
import com.android.quickstep.logging.SettingsChangeLogger;
@@ -111,45 +116,80 @@ public class QuickstepModelDelegate extends ModelDelegate {
mStatsManager = context.getSystemService(StatsManager.class);
}
@CallSuper
@Override
public void loadHotseatItems(UserManagerState ums,
Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) {
// TODO: Implement caching and preloading
super.loadHotseatItems(ums, pinnedShortcuts);
WorkspaceItemFactory hotseatFactory = new WorkspaceItemFactory(mApp, ums, pinnedShortcuts,
mIDP.numDatabaseHotseatIcons, mHotseatState.containerId);
FixedContainerItems hotseatItems = new FixedContainerItems(mHotseatState.containerId,
mHotseatState.storage.read(mApp.getContext(), hotseatFactory, ums.allUsers::get));
mDataModel.extraItems.put(mHotseatState.containerId, hotseatItems);
public void loadAndBindWorkspaceItems(@NonNull UserManagerState ums,
@NonNull BgDataModel.Callbacks[] callbacks,
@NonNull Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) {
loadAndBindItems(ums, pinnedShortcuts, callbacks, mIDP.numDatabaseHotseatIcons,
mHotseatState);
}
@CallSuper
@Override
public void loadAllAppsItems(UserManagerState ums,
Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) {
// TODO: Implement caching and preloading
super.loadAllAppsItems(ums, pinnedShortcuts);
WorkspaceItemFactory allAppsFactory = new WorkspaceItemFactory(mApp, ums, pinnedShortcuts,
mIDP.numDatabaseAllAppsColumns, mAllAppsState.containerId);
FixedContainerItems allAppsPredictionItems = new FixedContainerItems(
mAllAppsState.containerId, mAllAppsState.storage.read(mApp.getContext(),
allAppsFactory, ums.allUsers::get));
mDataModel.extraItems.put(mAllAppsState.containerId, allAppsPredictionItems);
public void loadAndBindAllAppsItems(@NonNull UserManagerState ums,
@NonNull BgDataModel.Callbacks[] callbacks,
@NonNull Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) {
loadAndBindItems(ums, pinnedShortcuts, callbacks, mIDP.numDatabaseAllAppsColumns,
mAllAppsState);
}
@Override
public void loadWidgetsRecommendationItems() {
@WorkerThread
private void loadAndBindItems(@NonNull UserManagerState ums,
@NonNull Map<ShortcutKey, ShortcutInfo> pinnedShortcuts,
@NonNull BgDataModel.Callbacks[] callbacks,
int numColumns, @NonNull PredictorState state) {
// TODO: Implement caching and preloading
super.loadWidgetsRecommendationItems();
WorkspaceItemFactory factory =
new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, numColumns, state.containerId);
FixedContainerItems fci = new FixedContainerItems(state.containerId,
state.storage.read(mApp.getContext(), factory, ums.allUsers::get));
if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
bindPredictionItems(callbacks, fci);
}
mDataModel.extraItems.put(state.containerId, fci);
}
@CallSuper
@Override
public void loadAndBindOtherItems(@NonNull BgDataModel.Callbacks[] callbacks) {
FixedContainerItems widgetPredictionFCI = new FixedContainerItems(
mWidgetsRecommendationState.containerId, new ArrayList<>());
// Widgets prediction isn't used frequently. And thus, it is not persisted on disk.
mDataModel.extraItems.put(mWidgetsRecommendationState.containerId,
new FixedContainerItems(mWidgetsRecommendationState.containerId,
new ArrayList<>()));
mDataModel.extraItems.put(mWidgetsRecommendationState.containerId, widgetPredictionFCI);
bindPredictionItems(callbacks, widgetPredictionFCI);
loadStringCache(mDataModel.stringCache);
}
@AnyThread
private void bindPredictionItems(@NonNull BgDataModel.Callbacks[] callbacks,
@NonNull FixedContainerItems fci) {
Executors.MAIN_EXECUTOR.execute(() -> {
for (BgDataModel.Callbacks c : callbacks) {
c.bindExtraContainerItems(fci);
}
});
}
@Override
@WorkerThread
public void bindAllModelExtras(@NonNull BgDataModel.Callbacks[] callbacks) {
Iterable<FixedContainerItems> containerItems;
synchronized (mDataModel.extraItems) {
containerItems = mDataModel.extraItems.clone();
}
Executors.MAIN_EXECUTOR.execute(() -> {
for (BgDataModel.Callbacks c : callbacks) {
for (FixedContainerItems fci : containerItems) {
c.bindExtraContainerItems(fci);
}
}
});
}
public void markActive() {
super.markActive();
mActive = true;
@@ -16,7 +16,6 @@
package com.android.launcher3.taskbar;
import static com.android.launcher3.QuickstepTransitionManager.TRANSIENT_TASKBAR_TRANSITION_DURATION;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_EDU_TOOLTIP;
import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE;
import static com.android.launcher3.taskbar.TaskbarEduTooltipControllerKt.TOOLTIP_STEP_FEATURES;
import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_RESUMED;
@@ -252,13 +251,6 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
return;
}
// Transient and persistent bottom sheet.
if (!ENABLE_TASKBAR_EDU_TOOLTIP.get()) {
mLauncher.getOnboardingPrefs().markChecked(OnboardingPrefs.TASKBAR_EDU_SEEN);
mControllers.taskbarEduController.showEdu();
return;
}
// Persistent features EDU tooltip.
if (!DisplayController.isTransientTaskbar(mLauncher)) {
mControllers.taskbarEduTooltipController.maybeShowFeaturesEdu();
@@ -277,11 +269,6 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
return false;
}
// Transient and persistent bottom sheet.
if (!ENABLE_TASKBAR_EDU_TOOLTIP.get()) {
return !mLauncher.getOnboardingPrefs().getBoolean(OnboardingPrefs.TASKBAR_EDU_SEEN);
}
// Persistent features EDU tooltip.
if (!DisplayController.isTransientTaskbar(mLauncher)) {
return !mLauncher.getOnboardingPrefs().hasReachedMaxCount(
@@ -224,7 +224,6 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
new TaskbarKeyguardController(this),
new StashedHandleViewController(this, stashedHandleView),
new TaskbarStashController(this),
new TaskbarEduController(this),
new TaskbarAutohideSuspendController(this),
new TaskbarPopupController(this),
new TaskbarForceVisibleImmersiveController(this),
@@ -48,7 +48,6 @@ public class TaskbarControllers {
public final TaskbarKeyguardController taskbarKeyguardController;
public final StashedHandleViewController stashedHandleViewController;
public final TaskbarStashController taskbarStashController;
public final TaskbarEduController taskbarEduController;
public final TaskbarAutohideSuspendController taskbarAutohideSuspendController;
public final TaskbarPopupController taskbarPopupController;
public final TaskbarForceVisibleImmersiveController taskbarForceVisibleImmersiveController;
@@ -95,7 +94,6 @@ public class TaskbarControllers {
TaskbarKeyguardController taskbarKeyguardController,
StashedHandleViewController stashedHandleViewController,
TaskbarStashController taskbarStashController,
TaskbarEduController taskbarEduController,
TaskbarAutohideSuspendController taskbarAutoHideSuspendController,
TaskbarPopupController taskbarPopupController,
TaskbarForceVisibleImmersiveController taskbarForceVisibleImmersiveController,
@@ -120,7 +118,6 @@ public class TaskbarControllers {
this.taskbarKeyguardController = taskbarKeyguardController;
this.stashedHandleViewController = stashedHandleViewController;
this.taskbarStashController = taskbarStashController;
this.taskbarEduController = taskbarEduController;
this.taskbarAutohideSuspendController = taskbarAutoHideSuspendController;
this.taskbarPopupController = taskbarPopupController;
this.taskbarForceVisibleImmersiveController = taskbarForceVisibleImmersiveController;
@@ -155,7 +152,6 @@ public class TaskbarControllers {
taskbarSpringOnStashController.init(this);
stashedHandleViewController.init(this);
taskbarStashController.init(this, sharedState.setupUIVisible, mSharedState);
taskbarEduController.init(this);
taskbarPopupController.init(this);
taskbarForceVisibleImmersiveController.init(this);
taskbarOverlayController.init(this);
@@ -172,7 +168,7 @@ public class TaskbarControllers {
taskbarDragController, navButtonController, navbarButtonsViewController,
taskbarDragLayerController, taskbarScrimViewController, taskbarViewController,
taskbarUnfoldAnimationController, taskbarKeyguardController,
stashedHandleViewController, taskbarStashController, taskbarEduController,
stashedHandleViewController, taskbarStashController,
taskbarAutohideSuspendController, taskbarPopupController, taskbarInsetsController,
voiceInteractionWindowController, taskbarTranslationController,
taskbarEduTooltipController, keyboardQuickSwitchController
@@ -1,131 +0,0 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.taskbar;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
import com.android.launcher3.util.DisplayController;
import com.airbnb.lottie.LottieAnimationView;
import java.io.PrintWriter;
/** Handles the Taskbar Education flow. */
public class TaskbarEduController implements TaskbarControllers.LoggableTaskbarController {
private final TaskbarActivityContext mActivity;
// Initialized in init.
TaskbarControllers mControllers;
private TaskbarEduView mTaskbarEduView;
private TaskbarEduPagedView mPagedView;
public TaskbarEduController(TaskbarActivityContext activity) {
mActivity = activity;
}
public void init(TaskbarControllers controllers) {
mControllers = controllers;
}
void showEdu() {
TaskbarOverlayController overlayController = mControllers.taskbarOverlayController;
TaskbarOverlayContext overlayContext = overlayController.requestWindow();
LayoutInflater layoutInflater = overlayContext.getLayoutInflater();
mTaskbarEduView = (TaskbarEduView) layoutInflater.inflate(
R.layout.taskbar_edu, overlayContext.getDragLayer(), false);
mPagedView = mTaskbarEduView.findViewById(R.id.content);
layoutInflater.inflate(
DisplayController.isTransientTaskbar(overlayContext)
? R.layout.taskbar_edu_pages_transient
: R.layout.taskbar_edu_pages_persistent,
mPagedView,
true);
// Provide enough room for taskbar.
View startButton = mTaskbarEduView.findViewById(R.id.edu_start_button);
ViewGroup.MarginLayoutParams layoutParams =
(ViewGroup.MarginLayoutParams) startButton.getLayoutParams();
DeviceProfile dp = overlayContext.getDeviceProfile();
layoutParams.bottomMargin += dp.taskbarHeight + dp.taskbarBottomMargin;
mTaskbarEduView.init(new TaskbarEduCallbacks());
mControllers.navbarButtonsViewController.setSlideInViewVisible(true);
mTaskbarEduView.setOnCloseBeginListener(
() -> mControllers.navbarButtonsViewController.setSlideInViewVisible(false));
mTaskbarEduView.addOnCloseListener(() -> mTaskbarEduView = null);
mTaskbarEduView.show();
}
@Override
public void dumpLogs(String prefix, PrintWriter pw) {
pw.println(prefix + "TaskbarEduController:");
pw.println(prefix + "\tisShowingEdu=" + (mTaskbarEduView != null));
}
/**
* Callbacks for {@link TaskbarEduView} to interact with its controller.
*/
class TaskbarEduCallbacks {
void onPageChanged(int prevPage, int currentPage, int pageCount) {
// Reset previous pages' animation.
LottieAnimationView prevAnimation = mPagedView.getChildAt(prevPage)
.findViewById(R.id.animation);
prevAnimation.cancelAnimation();
prevAnimation.setFrame(0);
mPagedView.getChildAt(currentPage)
.<LottieAnimationView>findViewById(R.id.animation)
.playAnimation();
if (currentPage == 0) {
mTaskbarEduView.updateStartButton(R.string.taskbar_edu_close,
v -> mTaskbarEduView.close(true /* animate */));
} else {
mTaskbarEduView.updateStartButton(R.string.taskbar_edu_previous,
v -> mTaskbarEduView.snapToPage(currentPage - 1));
}
if (currentPage == pageCount - 1) {
mTaskbarEduView.updateEndButton(R.string.taskbar_edu_done,
v -> mTaskbarEduView.close(true /* animate */));
} else {
mTaskbarEduView.updateEndButton(R.string.taskbar_edu_next,
v -> mTaskbarEduView.snapToPage(currentPage + 1));
}
}
int getIconLayoutBoundsWidth() {
return mControllers.taskbarViewController.getIconLayoutWidth();
}
int getOpenDuration() {
return mControllers.taskbarOverlayController.getOpenDuration();
}
int getCloseDuration() {
return mControllers.taskbarOverlayController.getCloseDuration();
}
}
}
@@ -1,79 +0,0 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.taskbar;
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_EDUCATION_DIALOG;
import android.content.Context;
import android.util.AttributeSet;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.PagedView;
import com.android.launcher3.R;
import com.android.launcher3.pageindicators.PageIndicatorDots;
import com.android.launcher3.taskbar.TaskbarEduController.TaskbarEduCallbacks;
import com.android.launcher3.views.ActivityContext;
/** Horizontal carousel of tutorial screens for Taskbar Edu. */
public class TaskbarEduPagedView extends PagedView<PageIndicatorDots> {
private TaskbarEduView mTaskbarEduView;
private TaskbarEduCallbacks mControllerCallbacks;
public TaskbarEduPagedView(Context context, AttributeSet attrs) {
super(context, attrs);
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
void setTaskbarEduView(TaskbarEduView taskbarEduView) {
mTaskbarEduView = taskbarEduView;
mPageIndicator = taskbarEduView.findViewById(R.id.content_page_indicator);
initParentViews(taskbarEduView);
}
void setControllerCallbacks(TaskbarEduCallbacks controllerCallbacks) {
mControllerCallbacks = controllerCallbacks;
mControllerCallbacks.onPageChanged(getCurrentPage(), getCurrentPage(), getPageCount());
}
@Override
protected int getChildGap(int fromIndex, int toIndex) {
return mTaskbarEduView.getPaddingLeft() + mTaskbarEduView.getPaddingRight();
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (mMaxScroll > 0) {
mPageIndicator.setScroll(l, mMaxScroll);
}
}
@Override
protected void notifyPageSwitchListener(int prevPage) {
super.notifyPageSwitchListener(prevPage);
mControllerCallbacks.onPageChanged(prevPage, getCurrentPage(), getPageCount());
}
@Override
protected boolean canScroll(float absVScroll, float absHScroll) {
return AbstractFloatingView.getTopOpenViewWithType(
ActivityContext.lookupContext(getContext()),
TYPE_ALL & ~TYPE_TASKBAR_EDUCATION_DIALOG) == null;
}
}
@@ -31,7 +31,6 @@ import com.airbnb.lottie.LottieProperty.COLOR_FILTER
import com.airbnb.lottie.model.KeyPath
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_EDU_TOOLTIP
import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_EDU_OPEN
import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
import com.android.launcher3.util.DisplayController
@@ -59,7 +58,7 @@ class TaskbarEduTooltipController(val activityContext: TaskbarActivityContext) :
LoggableTaskbarController {
private val isTooltipEnabled: Boolean
get() = !Utilities.isRunningInTestHarness() && ENABLE_TASKBAR_EDU_TOOLTIP.get()
get() = !Utilities.isRunningInTestHarness()
private val isOpen: Boolean
get() = tooltip?.isOpen ?: false
val isBeforeTooltipFeaturesStep: Boolean
@@ -1,183 +0,0 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.taskbar;
import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.graphics.Rect;
import android.provider.Settings;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.View;
import android.view.animation.Interpolator;
import android.widget.Button;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
import com.android.launcher3.views.AbstractSlideInView;
/** Education view about the Taskbar. */
public class TaskbarEduView extends AbstractSlideInView<TaskbarOverlayContext>
implements Insettable {
private final Rect mInsets = new Rect();
// Initialized in init.
private TaskbarEduController.TaskbarEduCallbacks mTaskbarEduCallbacks;
private Button mStartButton;
private Button mEndButton;
private TaskbarEduPagedView mPagedView;
public TaskbarEduView(Context context, AttributeSet attr) {
this(context, attr, 0);
}
public TaskbarEduView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
protected void init(TaskbarEduController.TaskbarEduCallbacks callbacks) {
if (mPagedView != null) {
mPagedView.setControllerCallbacks(callbacks);
}
mTaskbarEduCallbacks = callbacks;
}
@Override
protected void handleClose(boolean animate) {
handleClose(animate, mTaskbarEduCallbacks.getCloseDuration());
}
@Override
protected Interpolator getIdleInterpolator() {
return EMPHASIZED;
}
@Override
protected boolean isOfType(int type) {
return (type & TYPE_TASKBAR_EDUCATION_DIALOG) != 0;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mContent = findViewById(R.id.edu_view);
mStartButton = findViewById(R.id.edu_start_button);
mEndButton = findViewById(R.id.edu_end_button);
mPagedView = findViewById(R.id.content);
mPagedView.setTaskbarEduView(this);
}
@Override
public void setInsets(Rect insets) {
mInsets.set(insets);
mContent.setPadding(mContent.getPaddingStart(),
mContent.getPaddingTop(), mContent.getPaddingEnd(), insets.bottom);
}
@Override
protected void attachToContainer() {
if (mColorScrim != null) {
getPopupContainer().addView(mColorScrim, 0);
}
getPopupContainer().addView(this, 1);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING, 0);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int contentWidth = Math.min(getContentAreaWidth(), getMeasuredWidth());
contentWidth = Math.max(contentWidth, mTaskbarEduCallbacks.getIconLayoutBoundsWidth());
int contentAreaWidthSpec = MeasureSpec.makeMeasureSpec(contentWidth, MeasureSpec.EXACTLY);
mContent.measure(contentAreaWidthSpec, MeasureSpec.UNSPECIFIED);
}
private int getContentAreaWidth() {
return mTaskbarEduCallbacks.getIconLayoutBoundsWidth()
+ getResources().getDimensionPixelSize(R.dimen.taskbar_edu_horizontal_margin) * 2;
}
/** Show the Education flow. */
public void show() {
attachToContainer();
animateOpen();
}
@Override
protected Pair<View, String> getAccessibilityTarget() {
return Pair.create(mContent, mIsOpen ? getContext().getString(R.string.taskbar_edu_opened)
: getContext().getString(R.string.taskbar_edu_closed));
}
@Override
protected int getScrimColor(Context context) {
return context.getResources().getColor(R.color.widgets_picker_scrim);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int width = r - l;
int height = b - t;
// Lay out the content as center bottom aligned.
int contentWidth = mContent.getMeasuredWidth();
int contentLeft = (width - contentWidth - mInsets.left - mInsets.right) / 2 + mInsets.left;
mContent.layout(contentLeft, height - mContent.getMeasuredHeight(),
contentLeft + contentWidth, height);
setTranslationShift(mTranslationShift);
}
private void animateOpen() {
if (mIsOpen || mOpenCloseAnimator.isRunning()) {
return;
}
mIsOpen = true;
mOpenCloseAnimator.setValues(
PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
mOpenCloseAnimator.setInterpolator(EMPHASIZED);
mOpenCloseAnimator.setDuration(mTaskbarEduCallbacks.getOpenDuration()).start();
}
void snapToPage(int page) {
mPagedView.snapToPage(page);
}
void updateStartButton(int textResId, OnClickListener onClickListener) {
mStartButton.setText(textResId);
mStartButton.setOnClickListener(onClickListener);
}
void updateEndButton(int textResId, OnClickListener onClickListener) {
mEndButton.setText(textResId);
mEndButton.setOnClickListener(onClickListener);
}
}
@@ -40,7 +40,6 @@ abstract class TaskbarBaseTestCase {
@Mock lateinit var taskbarKeyguardController: TaskbarKeyguardController
@Mock lateinit var stashedHandleViewController: StashedHandleViewController
@Mock lateinit var taskbarStashController: TaskbarStashController
@Mock lateinit var taskbarEduController: TaskbarEduController
@Mock lateinit var taskbarAutohideSuspendController: TaskbarAutohideSuspendController
@Mock lateinit var taskbarPopupController: TaskbarPopupController
@Mock
@@ -81,7 +80,6 @@ abstract class TaskbarBaseTestCase {
taskbarKeyguardController,
stashedHandleViewController,
taskbarStashController,
taskbarEduController,
taskbarAutohideSuspendController,
taskbarPopupController,
taskbarForceVisibleImmersiveController,
@@ -421,6 +421,9 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
launcherBinder.bindAllApps();
launcherBinder.bindDeepShortcuts();
launcherBinder.bindWidgets();
if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
mModelDelegate.bindAllModelExtras(callbacksList);
}
return true;
} else {
stopLoader();
+4 -538
View File
@@ -18,7 +18,6 @@ package com.android.launcher3;
import static com.android.launcher3.DefaultLayoutParser.RES_PARTNER_DEFAULT_LAYOUT;
import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
import android.annotation.TargetApi;
@@ -30,83 +29,55 @@ import android.content.ContentProviderResult;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.OperationApplicationException;
import android.content.SharedPreferences;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.database.sqlite.SQLiteStatement;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.BaseColumns;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.Xml;
import androidx.annotation.NonNull;
import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.DbDowngradeHelper;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.model.DatabaseHelper;
import com.android.launcher3.provider.LauncherDbUtils;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
import com.android.launcher3.provider.RestoreDbTask;
import com.android.launcher3.util.IOUtils;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.NoLocaleSQLiteHelper;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Partner;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.widget.LauncherWidgetHolder;
import org.xmlpull.v1.XmlPullParser;
import java.io.File;
import java.io.FileDescriptor;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public class LauncherProvider extends ContentProvider {
private static final String TAG = "LauncherProvider";
private static final boolean LOGD = false;
private static final String DOWNGRADE_SCHEMA_FILE = "downgrade_schema.json";
/**
* Represents the schema of the database. Changes in scheme need not be backwards compatible.
* When increasing the scheme version, ensure that downgrade_schema.json is updated
*/
public static final int SCHEMA_VERSION = 31;
public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".settings";
public static final String KEY_LAYOUT_PROVIDER_AUTHORITY = "KEY_LAYOUT_PROVIDER_AUTHORITY";
private static final int TEST_WORKSPACE_LAYOUT_RES_XML = R.xml.default_test_workspace;
private static final int TEST2_WORKSPACE_LAYOUT_RES_XML = R.xml.default_test2_workspace;
private static final int TAPL_WORKSPACE_LAYOUT_RES_XML = R.xml.default_tapl_test_workspace;
static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
public static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
protected DatabaseHelper mOpenHelper;
protected String mProviderAuthority;
@@ -193,18 +164,6 @@ public class LauncherProvider extends ContentProvider {
return result;
}
@Thunk static int dbInsertAndCheck(DatabaseHelper helper,
SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
if (values == null) {
throw new RuntimeException("Error: attempting to insert null values");
}
if (!values.containsKey(LauncherSettings.Favorites._ID)) {
throw new RuntimeException("Error: attempting to add item without specifying an id");
}
helper.checkId(values);
return (int) db.insert(table, nullColumnHack, values);
}
private void reloadLauncherIfExternal() {
if (Binder.getCallingPid() != Process.myPid()) {
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
@@ -228,7 +187,7 @@ public class LauncherProvider extends ContentProvider {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
addModifiedTime(initialValues);
final int rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
final int rowId = mOpenHelper.dbInsertAndCheck(db, args.table, initialValues);
if (rowId < 0) return null;
onAddOrDeleteOp(db);
@@ -287,7 +246,7 @@ public class LauncherProvider extends ContentProvider {
int numValues = values.length;
for (int i = 0; i < numValues; i++) {
addModifiedTime(values[i]);
if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) {
if (mOpenHelper.dbInsertAndCheck(db, args.table, values[i]) < 0) {
return 0;
}
}
@@ -616,499 +575,6 @@ public class LauncherProvider extends ContentProvider {
mOpenHelper, getContext().getResources(), defaultLayout);
}
/**
* The class is subclassed in tests to create an in-memory db.
*/
public static class DatabaseHelper extends NoLocaleSQLiteHelper implements
LayoutParserCallback {
private final Context mContext;
private final boolean mForMigration;
private int mMaxItemId = -1;
private boolean mHotseatRestoreTableExists;
static DatabaseHelper createDatabaseHelper(Context context, boolean forMigration) {
return createDatabaseHelper(context, null, forMigration);
}
static DatabaseHelper createDatabaseHelper(Context context, String dbName,
boolean forMigration) {
if (dbName == null) {
dbName = InvariantDeviceProfile.INSTANCE.get(context).dbFile;
}
DatabaseHelper databaseHelper = new DatabaseHelper(context, dbName, forMigration);
// 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(databaseHelper.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.
databaseHelper.addFavoritesTable(databaseHelper.getWritableDatabase(), true);
}
databaseHelper.mHotseatRestoreTableExists = tableExists(
databaseHelper.getReadableDatabase(), Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
databaseHelper.initIds();
return databaseHelper;
}
/**
* Constructor used in tests and for restore.
*/
public DatabaseHelper(Context context, String dbName, boolean forMigration) {
super(context, dbName, SCHEMA_VERSION);
mContext = context;
mForMigration = forMigration;
}
protected void initIds() {
// In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
// the DB here
if (mMaxItemId == -1) {
mMaxItemId = initializeMaxItemId(getWritableDatabase());
}
}
@Override
public void onCreate(SQLiteDatabase db) {
if (LOGD) Log.d(TAG, "creating new launcher database");
mMaxItemId = 1;
addFavoritesTable(db, false);
// Fresh and clean launcher DB.
mMaxItemId = initializeMaxItemId(db);
if (!mForMigration) {
onEmptyDbCreated();
}
}
protected void onAddOrDeleteOp(SQLiteDatabase db) {
if (mHotseatRestoreTableExists) {
dropTable(db, Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
mHotseatRestoreTableExists = false;
}
}
/**
* Re-composite given key in respect to database. If the current db is
* {@link LauncherFiles#LAUNCHER_DB}, return the key as-is. Otherwise append the db name to
* given key. e.g. consider key="EMPTY_DATABASE_CREATED", dbName="minimal.db", the returning
* string will be "EMPTY_DATABASE_CREATED@minimal.db".
*/
String getKey(final String key) {
if (TextUtils.equals(getDatabaseName(), LauncherFiles.LAUNCHER_DB)) {
return key;
}
return key + "@" + getDatabaseName();
}
/**
* Overriden in tests.
*/
protected void onEmptyDbCreated() {
// Set the flag for empty DB
LauncherPrefs.getPrefs(mContext).edit().putBoolean(getKey(EMPTY_DATABASE_CREATED), true)
.commit();
}
public long getSerialNumberForUser(UserHandle user) {
return UserCache.INSTANCE.get(mContext).getSerialNumberForUser(user);
}
public long getDefaultUserSerial() {
return getSerialNumberForUser(Process.myUserHandle());
}
private void addFavoritesTable(SQLiteDatabase db, boolean optional) {
Favorites.addTableToDb(db, getDefaultUserSerial(), optional);
}
@Override
public void onOpen(SQLiteDatabase db) {
super.onOpen(db);
File schemaFile = mContext.getFileStreamPath(DOWNGRADE_SCHEMA_FILE);
if (!schemaFile.exists()) {
handleOneTimeDataUpgrade(db);
}
DbDowngradeHelper.updateSchemaFile(schemaFile, SCHEMA_VERSION, mContext);
}
/**
* One-time data updated before support of onDowngrade was added. This update is backwards
* compatible and can safely be run multiple times.
* Note: No new logic should be added here after release, as the new logic might not get
* executed on an existing device.
* TODO: Move this to db upgrade path, once the downgrade path is released.
*/
protected void handleOneTimeDataUpgrade(SQLiteDatabase db) {
// Remove "profile extra"
UserCache um = UserCache.INSTANCE.get(mContext);
for (UserHandle user : um.getUserProfiles()) {
long serial = um.getSerialNumberForUser(user);
String sql = "update favorites set intent = replace(intent, "
+ "';l.profile=" + serial + ";', ';') where itemType = 0;";
db.execSQL(sql);
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (LOGD) Log.d(TAG, "onUpgrade triggered: " + oldVersion);
switch (oldVersion) {
// The version cannot be lower that 12, as Launcher3 never supported a lower
// version of the DB.
case 12:
// No-op
case 13: {
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
// Insert new column for holding widget provider name
db.execSQL("ALTER TABLE favorites " +
"ADD COLUMN appWidgetProvider TEXT;");
t.commit();
} catch (SQLException ex) {
Log.e(TAG, ex.getMessage(), ex);
// Old version remains, which means we wipe old data
break;
}
}
case 14: {
if (!addIntegerColumn(db, Favorites.MODIFIED, 0)) {
// Old version remains, which means we wipe old data
break;
}
}
case 15: {
if (!addIntegerColumn(db, Favorites.RESTORED, 0)) {
// Old version remains, which means we wipe old data
break;
}
}
case 16:
// No-op
case 17:
// No-op
case 18:
// No-op
case 19: {
// Add userId column
if (!addIntegerColumn(db, Favorites.PROFILE_ID, getDefaultUserSerial())) {
// Old version remains, which means we wipe old data
break;
}
}
case 20:
if (!updateFolderItemsRank(db, true)) {
break;
}
case 21:
// No-op
case 22: {
if (!addIntegerColumn(db, Favorites.OPTIONS, 0)) {
// Old version remains, which means we wipe old data
break;
}
}
case 23:
// No-op
case 24:
// No-op
case 25:
convertShortcutsToLauncherActivities(db);
case 26:
// QSB was moved to the grid. Ignore overlapping items
case 27: {
// Update the favorites table so that the screen ids are ordered based on
// workspace page rank.
IntArray finalScreens = LauncherDbUtils.queryIntArray(false, db,
"workspaceScreens", BaseColumns._ID, null, null, "screenRank");
int[] original = finalScreens.toArray();
Arrays.sort(original);
String updatemap = "";
for (int i = 0; i < original.length; i++) {
if (finalScreens.get(i) != original[i]) {
updatemap += String.format(Locale.ENGLISH, " WHEN %1$s=%2$d THEN %3$d",
Favorites.SCREEN, finalScreens.get(i), original[i]);
}
}
if (!TextUtils.isEmpty(updatemap)) {
String query = String.format(Locale.ENGLISH,
"UPDATE %1$s SET %2$s=CASE %3$s ELSE %2$s END WHERE %4$s = %5$d",
Favorites.TABLE_NAME, Favorites.SCREEN, updatemap,
Favorites.CONTAINER, Favorites.CONTAINER_DESKTOP);
db.execSQL(query);
}
dropTable(db, "workspaceScreens");
}
case 28: {
boolean columnAdded = addIntegerColumn(
db, Favorites.APPWIDGET_SOURCE, Favorites.CONTAINER_UNKNOWN);
if (!columnAdded) {
// Old version remains, which means we wipe old data
break;
}
}
case 29: {
// Remove widget panel related leftover workspace items
db.delete(Favorites.TABLE_NAME, Utilities.createDbSelectionQuery(
Favorites.SCREEN, IntArray.wrap(-777, -778)), null);
}
case 30: {
if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
// Clean up first row in screen 0 as it might contain junk data.
Log.d(TAG, "Cleaning up first row");
db.delete(Favorites.TABLE_NAME,
String.format(Locale.ENGLISH,
"%1$s = %2$d AND %3$s = %4$d AND %5$s = %6$d",
Favorites.SCREEN, 0,
Favorites.CONTAINER, Favorites.CONTAINER_DESKTOP,
Favorites.CELLY, 0), null);
}
return;
}
case 31: {
// DB Upgraded successfully
return;
}
}
// DB was not upgraded
Log.w(TAG, "Destroying all old data.");
createEmptyDB(db);
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
try {
DbDowngradeHelper.parse(mContext.getFileStreamPath(DOWNGRADE_SCHEMA_FILE))
.onDowngrade(db, oldVersion, newVersion);
} catch (Exception e) {
Log.d(TAG, "Unable to downgrade from: " + oldVersion + " to " + newVersion +
". Wiping databse.", e);
createEmptyDB(db);
}
}
/**
* Clears all the data for a fresh start.
*/
public void createEmptyDB(SQLiteDatabase db) {
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
dropTable(db, Favorites.TABLE_NAME);
dropTable(db, "workspaceScreens");
onCreate(db);
t.commit();
}
}
/**
* Removes widgets which are registered to the Launcher's host, but are not present
* in our model.
*/
public void removeGhostWidgets(SQLiteDatabase db) {
// Get all existing widget ids.
final LauncherWidgetHolder holder = newLauncherWidgetHolder();
try {
final int[] allWidgets;
try {
// Although the method was defined in O, it has existed since the beginning of
// time, so it might work on older platforms as well.
allWidgets = holder.getAppWidgetIds();
} catch (IncompatibleClassChangeError e) {
Log.e(TAG, "getAppWidgetIds not supported", e);
return;
}
final IntSet validWidgets = IntSet.wrap(LauncherDbUtils.queryIntArray(false, db,
Favorites.TABLE_NAME, Favorites.APPWIDGET_ID,
"itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null));
boolean isAnyWidgetRemoved = false;
for (int widgetId : allWidgets) {
if (!validWidgets.contains(widgetId)) {
try {
FileLog.d(TAG, "Deleting invalid widget " + widgetId);
holder.deleteAppWidgetId(widgetId);
isAnyWidgetRemoved = true;
} catch (RuntimeException e) {
// Ignore
}
}
}
if (isAnyWidgetRemoved) {
final String allWidgetsIds = Arrays.stream(allWidgets).mapToObj(String::valueOf)
.collect(Collectors.joining(",", "[", "]"));
final String validWidgetsIds = Arrays.stream(
validWidgets.getArray().toArray()).mapToObj(String::valueOf)
.collect(Collectors.joining(",", "[", "]"));
FileLog.d(TAG, "One or more widgets was removed. db_path=" + db.getPath()
+ " allWidgetsIds=" + allWidgetsIds
+ ", validWidgetsIds=" + validWidgetsIds);
}
} finally {
holder.destroy();
}
}
/**
* Replaces all shortcuts of type {@link Favorites#ITEM_TYPE_SHORTCUT} which have a valid
* launcher activity target with {@link Favorites#ITEM_TYPE_APPLICATION}.
*/
@Thunk void convertShortcutsToLauncherActivities(SQLiteDatabase db) {
try (SQLiteTransaction t = new SQLiteTransaction(db);
// Only consider the primary user as other users can't have a shortcut.
Cursor c = db.query(Favorites.TABLE_NAME,
new String[] { Favorites._ID, Favorites.INTENT},
"itemType=" + Favorites.ITEM_TYPE_SHORTCUT +
" AND profileId=" + getDefaultUserSerial(),
null, null, null, null);
SQLiteStatement updateStmt = db.compileStatement("UPDATE favorites SET itemType="
+ Favorites.ITEM_TYPE_APPLICATION + " WHERE _id=?")
) {
final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
final int intentIndex = c.getColumnIndexOrThrow(Favorites.INTENT);
while (c.moveToNext()) {
String intentDescription = c.getString(intentIndex);
Intent intent;
try {
intent = Intent.parseUri(intentDescription, 0);
} catch (URISyntaxException e) {
Log.e(TAG, "Unable to parse intent", e);
continue;
}
if (!PackageManagerHelper.isLauncherAppTarget(intent)) {
continue;
}
int id = c.getInt(idIndex);
updateStmt.bindLong(1, id);
updateStmt.executeUpdateDelete();
}
t.commit();
} catch (SQLException ex) {
Log.w(TAG, "Error deduping shortcuts", ex);
}
}
@Thunk boolean updateFolderItemsRank(SQLiteDatabase db, boolean addRankColumn) {
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
if (addRankColumn) {
// Insert new column for holding rank
db.execSQL("ALTER TABLE favorites ADD COLUMN rank INTEGER NOT NULL DEFAULT 0;");
}
// Get a map for folder ID to folder width
Cursor c = db.rawQuery("SELECT container, MAX(cellX) FROM favorites"
+ " WHERE container IN (SELECT _id FROM favorites WHERE itemType = ?)"
+ " GROUP BY container;",
new String[] {Integer.toString(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)});
while (c.moveToNext()) {
db.execSQL("UPDATE favorites SET rank=cellX+(cellY*?) WHERE "
+ "container=? AND cellX IS NOT NULL AND cellY IS NOT NULL;",
new Object[] {c.getLong(1) + 1, c.getLong(0)});
}
c.close();
t.commit();
} catch (SQLException ex) {
// Old version remains, which means we wipe old data
Log.e(TAG, ex.getMessage(), ex);
return false;
}
return true;
}
private boolean addIntegerColumn(SQLiteDatabase db, String columnName, long defaultValue) {
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
db.execSQL("ALTER TABLE favorites ADD COLUMN "
+ columnName + " INTEGER NOT NULL DEFAULT " + defaultValue + ";");
t.commit();
} catch (SQLException ex) {
Log.e(TAG, ex.getMessage(), ex);
return false;
}
return true;
}
// Generates a new ID to use for an object in your database. This method should be only
// called from the main UI thread. As an exception, we do call it when we call the
// constructor from the worker thread; however, this doesn't extend until after the
// constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
// after that point
@Override
public int generateNewItemId() {
if (mMaxItemId < 0) {
throw new RuntimeException("Error: max item id was not initialized");
}
mMaxItemId += 1;
return mMaxItemId;
}
/**
* @return A new {@link LauncherWidgetHolder} based on the current context
*/
@NonNull
public LauncherWidgetHolder newLauncherWidgetHolder() {
return LauncherWidgetHolder.newInstance(mContext);
}
@Override
public int insertAndCheck(SQLiteDatabase db, ContentValues values) {
return dbInsertAndCheck(this, db, Favorites.TABLE_NAME, null, values);
}
public void checkId(ContentValues values) {
int id = values.getAsInteger(Favorites._ID);
mMaxItemId = Math.max(id, mMaxItemId);
}
private int initializeMaxItemId(SQLiteDatabase db) {
return getMaxId(db, "SELECT MAX(%1$s) FROM %2$s", Favorites._ID, Favorites.TABLE_NAME);
}
// Returns a new ID to use for an workspace screen in your database that is greater than all
// existing screen IDs.
private int getNewScreenId() {
return getMaxId(getWritableDatabase(),
"SELECT MAX(%1$s) FROM %2$s WHERE %3$s = %4$d AND %1$s >= 0",
Favorites.SCREEN, Favorites.TABLE_NAME, Favorites.CONTAINER,
Favorites.CONTAINER_DESKTOP) + 1;
}
@Thunk int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) {
// TODO: Use multiple loaders with fall-back and transaction.
int count = loader.loadLayout(db, new IntArray());
// Ensure that the max ids are initialized
mMaxItemId = initializeMaxItemId(db);
return count;
}
}
/**
* @return the max _id in the provided table.
*/
@Thunk static int getMaxId(SQLiteDatabase db, String query, Object... args) {
int max = 0;
try (SQLiteStatement prog = db.compileStatement(
String.format(Locale.ENGLISH, query, args))) {
max = (int) DatabaseUtils.longForQuery(prog, null);
if (max < 0) {
throw new RuntimeException("Error: could not query max id");
}
} catch (IllegalArgumentException exception) {
String message = exception.getMessage();
if (message.contains("re-open") && message.contains("already-closed")) {
// Don't crash trying to end a transaction an an already closed DB. See b/173162852.
} else {
throw exception;
}
}
return max;
}
static class SqlArguments {
public final String table;
public final String where;
@@ -331,10 +331,6 @@ public final class FeatureFlags {
"ENABLE_FORCED_MONO_ICON", DISABLED,
"Enable the ability to generate monochromatic icons, if it is not provided by the app");
public static final BooleanFlag ENABLE_TASKBAR_EDU_TOOLTIP = getDebugFlag(270396268,
"ENABLE_TASKBAR_EDU_TOOLTIP", ENABLED,
"Enable the tooltip version of the Taskbar education flow.");
public static final BooleanFlag ENABLE_MULTI_INSTANCE = getDebugFlag(270396680,
"ENABLE_MULTI_INSTANCE", DISABLED,
"Enables creation and filtering of multiple task instances in overview");
@@ -349,6 +345,11 @@ public final class FeatureFlags {
"load the current workspace screen visible to the user before the rest rather than "
+ "loading all of them at once.");
public static final BooleanFlag CHANGE_MODEL_DELEGATE_LOADING_ORDER = getDebugFlag(251502424,
"CHANGE_MODEL_DELEGATE_LOADING_ORDER", DISABLED,
"changes the timing of the loading and binding of delegate items during "
+ "data preparation for loading the home screen");
public static final BooleanFlag ENABLE_GRID_ONLY_OVERVIEW = getDebugFlag(270397206,
"ENABLE_GRID_ONLY_OVERVIEW", DISABLED,
"Enable a grid-only overview without a focused task.");
@@ -229,8 +229,8 @@ public class PreviewSurfaceRenderer {
query += " or " + LauncherSettings.Favorites.SCREEN + " = "
+ Workspace.SECOND_SCREEN_ID;
}
loadWorkspace(new ArrayList<>(), LauncherSettings.Favorites.PREVIEW_CONTENT_URI,
query);
loadWorkspaceForPreviewSurfaceRenderer(new ArrayList<>(),
LauncherSettings.Favorites.PREVIEW_CONTENT_URI, query);
final SparseArray<Size> spanInfo =
getLoadedLauncherWidgetInfo(previewContext.getBaseContext());
@@ -63,7 +63,7 @@ public abstract class BaseLauncherBinder {
protected final BgDataModel mBgDataModel;
private final AllAppsList mBgAllAppsList;
private final Callbacks[] mCallbacksList;
final Callbacks[] mCallbacksList;
private int mMyBindingId;
@@ -293,8 +293,10 @@ public abstract class BaseLauncherBinder {
// Load items on the current page.
bindWorkspaceItems(currentWorkspaceItems, mUiExecutor);
bindAppWidgets(currentAppWidgets, mUiExecutor);
mExtraItems.forEach(item ->
executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor));
if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
mExtraItems.forEach(item ->
executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor));
}
RunnableList pendingTasks = new RunnableList();
Executor pendingExecutor = pendingTasks::add;
@@ -382,14 +384,22 @@ public abstract class BaseLauncherBinder {
// Save a copy of all the bg-thread collections
ArrayList<ItemInfo> workspaceItems;
ArrayList<LauncherAppWidgetInfo> appWidgets;
ArrayList<FixedContainerItems> fciList = new ArrayList<>();
synchronized (mBgDataModel) {
workspaceItems = new ArrayList<>(mBgDataModel.workspaceItems);
appWidgets = new ArrayList<>(mBgDataModel.appWidgets);
if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
mBgDataModel.extraItems.forEach(fciList::add);
}
}
workspaceItems.forEach(it -> mBoundItemIds.add(it.id));
appWidgets.forEach(it -> mBoundItemIds.add(it.id));
if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
fciList.forEach(item ->
executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor));
}
sortWorkspaceItemsSpatially(mApp.getInvariantDeviceProfile(), workspaceItems);
@@ -0,0 +1,586 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.provider.LauncherDbUtils.dropTable;
import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import android.os.Process;
import android.os.UserHandle;
import android.provider.BaseColumns;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import com.android.launcher3.AutoInstallsLayout;
import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherFiles;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.provider.LauncherDbUtils;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.NoLocaleSQLiteHelper;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.widget.LauncherWidgetHolder;
import java.io.File;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Locale;
import java.util.stream.Collectors;
/**
* SqLite database for launcher home-screen model
* The class is subclassed in tests to create an in-memory db.
*/
public class DatabaseHelper extends NoLocaleSQLiteHelper implements
LayoutParserCallback {
/**
* Represents the schema of the database. Changes in scheme need not be backwards compatible.
* When increasing the scheme version, ensure that downgrade_schema.json is updated
*/
public static final int SCHEMA_VERSION = 31;
private static final String TAG = "DatabaseHelper";
private static final boolean LOGD = false;
private static final String DOWNGRADE_SCHEMA_FILE = "downgrade_schema.json";
private final Context mContext;
private final boolean mForMigration;
private int mMaxItemId = -1;
public boolean mHotseatRestoreTableExists;
public static DatabaseHelper createDatabaseHelper(Context context, boolean forMigration) {
return createDatabaseHelper(context, null, forMigration);
}
public static DatabaseHelper createDatabaseHelper(Context context, String dbName,
boolean forMigration) {
if (dbName == null) {
dbName = InvariantDeviceProfile.INSTANCE.get(context).dbFile;
}
DatabaseHelper databaseHelper = new DatabaseHelper(context, dbName, forMigration);
// 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(databaseHelper.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.
databaseHelper.addFavoritesTable(databaseHelper.getWritableDatabase(), true);
}
databaseHelper.mHotseatRestoreTableExists = tableExists(
databaseHelper.getReadableDatabase(), Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
databaseHelper.initIds();
return databaseHelper;
}
/**
* Constructor used in tests and for restore.
*/
public DatabaseHelper(Context context, String dbName, boolean forMigration) {
super(context, dbName, SCHEMA_VERSION);
mContext = context;
mForMigration = forMigration;
}
protected void initIds() {
// In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
// the DB here
if (mMaxItemId == -1) {
mMaxItemId = initializeMaxItemId(getWritableDatabase());
}
}
@Override
public void onCreate(SQLiteDatabase db) {
if (LOGD) Log.d(TAG, "creating new launcher database");
mMaxItemId = 1;
addFavoritesTable(db, false);
// Fresh and clean launcher DB.
mMaxItemId = initializeMaxItemId(db);
if (!mForMigration) {
onEmptyDbCreated();
}
}
public void onAddOrDeleteOp(SQLiteDatabase db) {
if (mHotseatRestoreTableExists) {
dropTable(db, Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
mHotseatRestoreTableExists = false;
}
}
/**
* Re-composite given key in respect to database. If the current db is
* {@link LauncherFiles#LAUNCHER_DB}, return the key as-is. Otherwise append the db name to
* given key. e.g. consider key="EMPTY_DATABASE_CREATED", dbName="minimal.db", the returning
* string will be "EMPTY_DATABASE_CREATED@minimal.db".
*/
public String getKey(final String key) {
if (TextUtils.equals(getDatabaseName(), LauncherFiles.LAUNCHER_DB)) {
return key;
}
return key + "@" + getDatabaseName();
}
/**
* Overridden in tests.
*/
protected void onEmptyDbCreated() {
// Set the flag for empty DB
LauncherPrefs.getPrefs(mContext).edit().putBoolean(getKey(
LauncherProvider.EMPTY_DATABASE_CREATED), true)
.commit();
}
public long getSerialNumberForUser(UserHandle user) {
return UserCache.INSTANCE.get(mContext).getSerialNumberForUser(user);
}
public long getDefaultUserSerial() {
return getSerialNumberForUser(Process.myUserHandle());
}
private void addFavoritesTable(SQLiteDatabase db, boolean optional) {
Favorites.addTableToDb(db, getDefaultUserSerial(), optional);
}
@Override
public void onOpen(SQLiteDatabase db) {
super.onOpen(db);
File schemaFile = mContext.getFileStreamPath(DOWNGRADE_SCHEMA_FILE);
if (!schemaFile.exists()) {
handleOneTimeDataUpgrade(db);
}
DbDowngradeHelper.updateSchemaFile(schemaFile, SCHEMA_VERSION, mContext);
}
/**
* One-time data updated before support of onDowngrade was added. This update is backwards
* compatible and can safely be run multiple times.
* Note: No new logic should be added here after release, as the new logic might not get
* executed on an existing device.
* TODO: Move this to db upgrade path, once the downgrade path is released.
*/
protected void handleOneTimeDataUpgrade(SQLiteDatabase db) {
// Remove "profile extra"
UserCache um = UserCache.INSTANCE.get(mContext);
for (UserHandle user : um.getUserProfiles()) {
long serial = um.getSerialNumberForUser(user);
String sql = "update favorites set intent = replace(intent, "
+ "';l.profile=" + serial + ";', ';') where itemType = 0;";
db.execSQL(sql);
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (LOGD) {
Log.d(TAG, "onUpgrade triggered: " + oldVersion);
}
switch (oldVersion) {
// The version cannot be lower that 12, as Launcher3 never supported a lower
// version of the DB.
case 12:
// No-op
case 13: {
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
// Insert new column for holding widget provider name
db.execSQL("ALTER TABLE favorites ADD COLUMN appWidgetProvider TEXT;");
t.commit();
} catch (SQLException ex) {
Log.e(TAG, ex.getMessage(), ex);
// Old version remains, which means we wipe old data
break;
}
}
case 14: {
if (!addIntegerColumn(db, Favorites.MODIFIED, 0)) {
// Old version remains, which means we wipe old data
break;
}
}
case 15: {
if (!addIntegerColumn(db, Favorites.RESTORED, 0)) {
// Old version remains, which means we wipe old data
break;
}
}
case 16:
// No-op
case 17:
// No-op
case 18:
// No-op
case 19: {
// Add userId column
if (!addIntegerColumn(db, Favorites.PROFILE_ID, getDefaultUserSerial())) {
// Old version remains, which means we wipe old data
break;
}
}
case 20:
if (!updateFolderItemsRank(db, true)) {
break;
}
case 21:
// No-op
case 22: {
if (!addIntegerColumn(db, Favorites.OPTIONS, 0)) {
// Old version remains, which means we wipe old data
break;
}
}
case 23:
// No-op
case 24:
// No-op
case 25:
convertShortcutsToLauncherActivities(db);
case 26:
// QSB was moved to the grid. Ignore overlapping items
case 27: {
// Update the favorites table so that the screen ids are ordered based on
// workspace page rank.
IntArray finalScreens = LauncherDbUtils.queryIntArray(false, db,
"workspaceScreens", BaseColumns._ID, null, null, "screenRank");
int[] original = finalScreens.toArray();
Arrays.sort(original);
String updatemap = "";
for (int i = 0; i < original.length; i++) {
if (finalScreens.get(i) != original[i]) {
updatemap += String.format(Locale.ENGLISH, " WHEN %1$s=%2$d THEN %3$d",
Favorites.SCREEN, finalScreens.get(i), original[i]);
}
}
if (!TextUtils.isEmpty(updatemap)) {
String query = String.format(Locale.ENGLISH,
"UPDATE %1$s SET %2$s=CASE %3$s ELSE %2$s END WHERE %4$s = %5$d",
Favorites.TABLE_NAME, Favorites.SCREEN, updatemap,
Favorites.CONTAINER, Favorites.CONTAINER_DESKTOP);
db.execSQL(query);
}
dropTable(db, "workspaceScreens");
}
case 28: {
boolean columnAdded = addIntegerColumn(
db, Favorites.APPWIDGET_SOURCE, Favorites.CONTAINER_UNKNOWN);
if (!columnAdded) {
// Old version remains, which means we wipe old data
break;
}
}
case 29: {
// Remove widget panel related leftover workspace items
db.delete(Favorites.TABLE_NAME, Utilities.createDbSelectionQuery(
Favorites.SCREEN, IntArray.wrap(-777, -778)), null);
}
case 30: {
if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
// Clean up first row in screen 0 as it might contain junk data.
Log.d(TAG, "Cleaning up first row");
db.delete(Favorites.TABLE_NAME,
String.format(Locale.ENGLISH,
"%1$s = %2$d AND %3$s = %4$d AND %5$s = %6$d",
Favorites.SCREEN, 0,
Favorites.CONTAINER, Favorites.CONTAINER_DESKTOP,
Favorites.CELLY, 0), null);
}
return;
}
case 31: {
// DB Upgraded successfully
return;
}
}
// DB was not upgraded
Log.w(TAG, "Destroying all old data.");
createEmptyDB(db);
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
try {
DbDowngradeHelper.parse(mContext.getFileStreamPath(DOWNGRADE_SCHEMA_FILE))
.onDowngrade(db, oldVersion, newVersion);
} catch (Exception e) {
Log.d(TAG, "Unable to downgrade from: " + oldVersion + " to " + newVersion
+ ". Wiping database.", e);
createEmptyDB(db);
}
}
/**
* Clears all the data for a fresh start.
*/
public void createEmptyDB(SQLiteDatabase db) {
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
dropTable(db, Favorites.TABLE_NAME);
dropTable(db, "workspaceScreens");
onCreate(db);
t.commit();
}
}
/**
* Removes widgets which are registered to the Launcher's host, but are not present
* in our model.
*/
public void removeGhostWidgets(SQLiteDatabase db) {
// Get all existing widget ids.
final LauncherWidgetHolder holder = newLauncherWidgetHolder();
try {
final int[] allWidgets;
try {
// Although the method was defined in O, it has existed since the beginning of
// time, so it might work on older platforms as well.
allWidgets = holder.getAppWidgetIds();
} catch (IncompatibleClassChangeError e) {
Log.e(TAG, "getAppWidgetIds not supported", e);
return;
}
final IntSet validWidgets = IntSet.wrap(LauncherDbUtils.queryIntArray(false, db,
Favorites.TABLE_NAME, Favorites.APPWIDGET_ID,
"itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null));
boolean isAnyWidgetRemoved = false;
for (int widgetId : allWidgets) {
if (!validWidgets.contains(widgetId)) {
try {
FileLog.d(TAG, "Deleting invalid widget " + widgetId);
holder.deleteAppWidgetId(widgetId);
isAnyWidgetRemoved = true;
} catch (RuntimeException e) {
// Ignore
}
}
}
if (isAnyWidgetRemoved) {
final String allWidgetsIds = Arrays.stream(allWidgets).mapToObj(String::valueOf)
.collect(Collectors.joining(",", "[", "]"));
final String validWidgetsIds = Arrays.stream(
validWidgets.getArray().toArray()).mapToObj(String::valueOf)
.collect(Collectors.joining(",", "[", "]"));
FileLog.d(TAG,
"One or more widgets was removed. db_path=" + db.getPath()
+ " allWidgetsIds=" + allWidgetsIds
+ ", validWidgetsIds=" + validWidgetsIds);
}
} finally {
holder.destroy();
}
}
/**
* Replaces all shortcuts of type {@link Favorites#ITEM_TYPE_SHORTCUT} which have a valid
* launcher activity target with {@link Favorites#ITEM_TYPE_APPLICATION}.
*/
@Thunk
void convertShortcutsToLauncherActivities(SQLiteDatabase db) {
try (SQLiteTransaction t = new SQLiteTransaction(db);
// Only consider the primary user as other users can't have a shortcut.
Cursor c = db.query(Favorites.TABLE_NAME,
new String[]{Favorites._ID, Favorites.INTENT},
"itemType=" + Favorites.ITEM_TYPE_SHORTCUT
+ " AND profileId=" + getDefaultUserSerial(),
null, null, null, null);
SQLiteStatement updateStmt = db.compileStatement("UPDATE favorites SET itemType="
+ Favorites.ITEM_TYPE_APPLICATION + " WHERE _id=?")
) {
final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
final int intentIndex = c.getColumnIndexOrThrow(Favorites.INTENT);
while (c.moveToNext()) {
String intentDescription = c.getString(intentIndex);
Intent intent;
try {
intent = Intent.parseUri(intentDescription, 0);
} catch (URISyntaxException e) {
Log.e(TAG, "Unable to parse intent", e);
continue;
}
if (!PackageManagerHelper.isLauncherAppTarget(intent)) {
continue;
}
int id = c.getInt(idIndex);
updateStmt.bindLong(1, id);
updateStmt.executeUpdateDelete();
}
t.commit();
} catch (SQLException ex) {
Log.w(TAG, "Error deduping shortcuts", ex);
}
}
@Thunk
boolean updateFolderItemsRank(SQLiteDatabase db, boolean addRankColumn) {
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
if (addRankColumn) {
// Insert new column for holding rank
db.execSQL("ALTER TABLE favorites ADD COLUMN rank INTEGER NOT NULL DEFAULT 0;");
}
// Get a map for folder ID to folder width
Cursor c = db.rawQuery("SELECT container, MAX(cellX) FROM favorites"
+ " WHERE container IN (SELECT _id FROM favorites WHERE itemType = ?)"
+ " GROUP BY container;",
new String[]{Integer.toString(Favorites.ITEM_TYPE_FOLDER)});
while (c.moveToNext()) {
db.execSQL("UPDATE favorites SET rank=cellX+(cellY*?) WHERE "
+ "container=? AND cellX IS NOT NULL AND cellY IS NOT NULL;",
new Object[]{c.getLong(1) + 1, c.getLong(0)});
}
c.close();
t.commit();
} catch (SQLException ex) {
// Old version remains, which means we wipe old data
Log.e(TAG, ex.getMessage(), ex);
return false;
}
return true;
}
private boolean addIntegerColumn(SQLiteDatabase db, String columnName, long defaultValue) {
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
db.execSQL("ALTER TABLE favorites ADD COLUMN "
+ columnName + " INTEGER NOT NULL DEFAULT " + defaultValue + ";");
t.commit();
} catch (SQLException ex) {
Log.e(TAG, ex.getMessage(), ex);
return false;
}
return true;
}
// Generates a new ID to use for an object in your database. This method should be only
// called from the main UI thread. As an exception, we do call it when we call the
// constructor from the worker thread; however, this doesn't extend until after the
// constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
// after that point
@Override
public int generateNewItemId() {
if (mMaxItemId < 0) {
throw new RuntimeException("Error: max item id was not initialized");
}
mMaxItemId += 1;
return mMaxItemId;
}
/**
* @return A new {@link LauncherWidgetHolder} based on the current context
*/
@NonNull
public LauncherWidgetHolder newLauncherWidgetHolder() {
return LauncherWidgetHolder.newInstance(mContext);
}
@Override
public int insertAndCheck(SQLiteDatabase db, ContentValues values) {
return dbInsertAndCheck(db, Favorites.TABLE_NAME, values);
}
public int dbInsertAndCheck(SQLiteDatabase db, String table, ContentValues values) {
if (values == null) {
throw new RuntimeException("Error: attempting to insert null values");
}
if (!values.containsKey(LauncherSettings.Favorites._ID)) {
throw new RuntimeException("Error: attempting to add item without specifying an id");
}
checkId(values);
return (int) db.insert(table, null, values);
}
public void checkId(ContentValues values) {
int id = values.getAsInteger(Favorites._ID);
mMaxItemId = Math.max(id, mMaxItemId);
}
private int initializeMaxItemId(SQLiteDatabase db) {
return getMaxId(db, "SELECT MAX(%1$s) FROM %2$s", Favorites._ID,
Favorites.TABLE_NAME);
}
/**
* Returns a new ID to use for a workspace screen in your database that is greater than all
* existing screen IDs
*/
public int getNewScreenId() {
return getMaxId(getWritableDatabase(),
"SELECT MAX(%1$s) FROM %2$s WHERE %3$s = %4$d AND %1$s >= 0",
Favorites.SCREEN, Favorites.TABLE_NAME, Favorites.CONTAINER,
Favorites.CONTAINER_DESKTOP) + 1;
}
public int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) {
// TODO: Use multiple loaders with fall-back and transaction.
int count = loader.loadLayout(db, new IntArray());
// Ensure that the max ids are initialized
mMaxItemId = initializeMaxItemId(db);
return count;
}
/**
* @return the max _id in the provided table.
*/
private static int getMaxId(SQLiteDatabase db, String query, Object... args) {
int max = 0;
try (SQLiteStatement prog = db.compileStatement(
String.format(Locale.ENGLISH, query, args))) {
max = (int) DatabaseUtils.longForQuery(prog, null);
if (max < 0) {
throw new RuntimeException("Error: could not query max id");
}
} catch (IllegalArgumentException exception) {
String message = exception.getMessage();
if (message.contains("re-open") && message.contains("already-closed")) {
// Don't crash trying to end a transaction an an already closed DB. See b/173162852.
} else {
throw exception;
}
}
return max;
}
}
+38 -14
View File
@@ -138,6 +138,7 @@ public class LoaderTask implements Runnable {
private final UserManagerState mUserManagerState = new UserManagerState();
protected final Map<ComponentKey, AppWidgetProviderInfo> mWidgetProvidersMap = new ArrayMap<>();
private Map<ShortcutKey, ShortcutInfo> mShortcutKeyToPinnedShortcuts;
private boolean mStopped;
@@ -211,6 +212,14 @@ public class LoaderTask implements Runnable {
}
logASplit(timingLogger, "loadWorkspace");
if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
verifyNotStopped();
mModelDelegate.loadAndBindWorkspaceItems(mUserManagerState,
mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
mModelDelegate.markActive();
logASplit(timingLogger, "workspaceDelegateItems");
}
// Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from db.
// sanitizeData should not be invoked if the workspace is loaded from a db different
// from the main db as defined in the invariant device profile.
@@ -246,6 +255,11 @@ public class LoaderTask implements Runnable {
}
logASplit(timingLogger, "loadAllApps");
if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
mModelDelegate.loadAndBindAllAppsItems(mUserManagerState,
mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
logASplit(timingLogger, "allAppsDelegateItems");
}
verifyNotStopped();
mLauncherBinder.bindAllApps();
logASplit(timingLogger, "bindAllApps");
@@ -296,6 +310,12 @@ public class LoaderTask implements Runnable {
logASplit(timingLogger, "bindWidgets");
verifyNotStopped();
if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
mModelDelegate.loadAndBindOtherItems(mLauncherBinder.mCallbacksList);
logASplit(timingLogger, "otherDelegateItems");
verifyNotStopped();
}
updateHandler.updateIcons(allWidgetsList,
new ComponentWithIconCachingLogic(mApp.getContext(), true),
mApp.getModel()::onWidgetLabelsUpdated);
@@ -334,9 +354,14 @@ public class LoaderTask implements Runnable {
null /* selection */, memoryLogger);
}
protected void loadWorkspace(
protected void loadWorkspaceForPreviewSurfaceRenderer(
List<ShortcutInfo> allDeepShortcuts, Uri contentUri, String selection) {
loadWorkspace(allDeepShortcuts, contentUri, selection, null);
if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
mModelDelegate.loadAndBindWorkspaceItems(mUserManagerState,
mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
mModelDelegate.markActive();
}
}
protected void loadWorkspace(
@@ -376,7 +401,7 @@ public class LoaderTask implements Runnable {
final PackageUserKey tempPackageKey = new PackageUserKey(null, null);
mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs);
Map<ShortcutKey, ShortcutInfo> shortcutKeyToPinnedShortcuts = new HashMap<>();
mShortcutKeyToPinnedShortcuts = new HashMap<>();
final LoaderCursor c = new LoaderCursor(
contentResolver.query(contentUri, null, selection, null, null), contentUri,
mApp, mUserManagerState);
@@ -397,7 +422,7 @@ public class LoaderTask implements Runnable {
.query(ShortcutRequest.PINNED);
if (pinnedShortcuts.wasSuccess()) {
for (ShortcutInfo shortcut : pinnedShortcuts) {
shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
mShortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
shortcut);
}
} else {
@@ -414,7 +439,7 @@ public class LoaderTask implements Runnable {
while (!mStopped && c.moveToNext()) {
processWorkspaceItem(c, memoryLogger, installingPkgs, isSdCardReady,
tempPackageKey, widgetHelper, pmHelper, shortcutKeyToPinnedShortcuts,
tempPackageKey, widgetHelper, pmHelper,
iconRequestInfos, unlockedUsers, isSafeMode, allDeepShortcuts);
}
tryLoadWorkspaceIconsInBulk(iconRequestInfos);
@@ -422,14 +447,14 @@ public class LoaderTask implements Runnable {
IOUtils.closeSilently(c);
}
// Load delegate items
mModelDelegate.loadHotseatItems(mUserManagerState, shortcutKeyToPinnedShortcuts);
mModelDelegate.loadAllAppsItems(mUserManagerState, shortcutKeyToPinnedShortcuts);
mModelDelegate.loadWidgetsRecommendationItems();
mModelDelegate.markActive();
// Load string cache
mModelDelegate.loadStringCache(mBgDataModel.stringCache);
if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
mModelDelegate.loadAndBindWorkspaceItems(mUserManagerState,
mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
mModelDelegate.loadAndBindAllAppsItems(mUserManagerState,
mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
mModelDelegate.loadAndBindOtherItems(mLauncherBinder.mCallbacksList);
mModelDelegate.markActive();
}
// Break early if we've stopped loading
if (mStopped) {
@@ -474,7 +499,6 @@ public class LoaderTask implements Runnable {
PackageUserKey tempPackageKey,
WidgetManagerHelper widgetHelper,
PackageManagerHelper pmHelper,
Map<ShortcutKey, ShortcutInfo> shortcutKeyToPinnedShortcuts,
List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos,
LongSparseArray<Boolean> unlockedUsers,
boolean isSafeMode,
@@ -603,7 +627,7 @@ public class LoaderTask implements Runnable {
} else if (c.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
ShortcutKey key = ShortcutKey.fromIntent(intent, c.user);
if (unlockedUsers.get(c.serialNumber)) {
ShortcutInfo pinnedShortcut = shortcutKeyToPinnedShortcuts.get(key);
ShortcutInfo pinnedShortcut = mShortcutKeyToPinnedShortcuts.get(key);
if (pinnedShortcut == null) {
// The shortcut is no longer valid.
c.markDeleted("Pinned shortcut not found");
@@ -20,6 +20,7 @@ import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermis
import android.content.Context;
import android.content.pm.ShortcutInfo;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherAppState;
@@ -68,9 +69,7 @@ public class ModelDelegate implements ResourceBasedOverride {
this.mContext = context;
}
/**
* Called periodically to validate and update any data
*/
/** Called periodically to validate and update any data */
@WorkerThread
public void validateData() {
if (hasShortcutsPermission(mApp.getContext())
@@ -79,36 +78,32 @@ public class ModelDelegate implements ResourceBasedOverride {
}
}
/**
* Load hot seat items if any in the data model
*/
/** Load workspace items (for example, those in the hot seat) if any in the data model */
@WorkerThread
public void loadHotseatItems(UserManagerState ums,
Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) { }
public void loadAndBindWorkspaceItems(@NonNull UserManagerState ums,
@NonNull BgDataModel.Callbacks[] callbacks,
@NonNull Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) { }
/**
* Load all apps items if any in the data model
*/
/** Load all apps items if any in the data model */
@WorkerThread
public void loadAllAppsItems(UserManagerState ums,
Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) { }
public void loadAndBindAllAppsItems(@NonNull UserManagerState ums,
@NonNull BgDataModel.Callbacks[] callbacks,
@NonNull Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) { }
/**
* Load widget recommendation items if any in the data model
*/
/** Load other items like widget recommendations if any in the data model */
@WorkerThread
public void loadWidgetsRecommendationItems() { }
public void loadAndBindOtherItems(@NonNull BgDataModel.Callbacks[] callbacks) { }
/**
* Marks the ModelDelegate as active
*/
/** binds everything not bound by launcherBinder */
@WorkerThread
public void bindAllModelExtras(@NonNull BgDataModel.Callbacks[] callbacks) { }
/** Marks the ModelDelegate as active */
public void markActive() { }
/**
* Load String cache
*/
/** Load String cache */
@WorkerThread
public void loadStringCache(StringCache cache) {
public void loadStringCache(@NonNull StringCache cache) {
cache.loadStrings(mContext);
}
@@ -44,10 +44,10 @@ import com.android.launcher3.AppWidgetsRestoredReceiver;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherProvider.DatabaseHelper;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.Utilities;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.DatabaseHelper;
import com.android.launcher3.model.DeviceGridState;
import com.android.launcher3.model.GridBackupTable;
import com.android.launcher3.model.data.AppInfo;
@@ -41,7 +41,6 @@ public class OnboardingPrefs<T extends ActivityContext> {
public static final String SEARCH_KEYBOARD_EDU_SEEN = "launcher.search_edu_seen";
public static final String SEARCH_SNACKBAR_COUNT = "launcher.keyboard_snackbar_count";
public static final String SEARCH_ONBOARDING_COUNT = "launcher.search_onboarding_count";
public static final String TASKBAR_EDU_SEEN = "launcher.taskbar_edu_seen2";
public static final String ALL_APPS_VISITED_COUNT = "launcher.all_apps_visited_count";
public static final String QSB_SEARCH_ONBOARDING_CARD_DISMISSED = "launcher.qsb_edu_dismiss";
public static final String TASKBAR_EDU_TOOLTIP_STEP = "launcher.taskbar_edu_tooltip_step";
@@ -52,7 +51,7 @@ public class OnboardingPrefs<T extends ActivityContext> {
HOTSEAT_LONGPRESS_TIP_SEEN },
"Search Education", new String[] { SEARCH_KEYBOARD_EDU_SEEN, SEARCH_SNACKBAR_COUNT,
SEARCH_ONBOARDING_COUNT, QSB_SEARCH_ONBOARDING_CARD_DISMISSED},
"Taskbar Education", new String[] { TASKBAR_EDU_SEEN, TASKBAR_EDU_TOOLTIP_STEP },
"Taskbar Education", new String[] { TASKBAR_EDU_TOOLTIP_STEP },
"All Apps Visited Count", new String[] {ALL_APPS_VISITED_COUNT}
);
@@ -63,7 +62,6 @@ public class OnboardingPrefs<T extends ActivityContext> {
HOME_BOUNCE_SEEN,
HOTSEAT_LONGPRESS_TIP_SEEN,
SEARCH_KEYBOARD_EDU_SEEN,
TASKBAR_EDU_SEEN,
QSB_SEARCH_ONBOARDING_CARD_DISMISSED
})
@Retention(RetentionPolicy.SOURCE)
@@ -37,8 +37,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherProvider.DatabaseHelper;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.R;
@@ -74,7 +72,7 @@ public class DbDowngradeHelperTest {
mSchemaFile.delete();
assertFalse(mSchemaFile.exists());
DbDowngradeHelper.updateSchemaFile(mSchemaFile, 0, mContext);
assertEquals(LauncherProvider.SCHEMA_VERSION, DbDowngradeHelper.parse(mSchemaFile).version);
assertEquals(DatabaseHelper.SCHEMA_VERSION, DbDowngradeHelper.parse(mSchemaFile).version);
}
@Test
@@ -140,7 +138,7 @@ public class DbDowngradeHelperTest {
@Override
public void onOpen(SQLiteDatabase db) { }
};
assertEquals(LauncherProvider.SCHEMA_VERSION, helper.getWritableDatabase().getVersion());
assertEquals(DatabaseHelper.SCHEMA_VERSION, helper.getWritableDatabase().getVersion());
try (Cursor c = helper.getWritableDatabase().query(Favorites.TABLE_NAME,
null, null, null, null, null, null)) {
@@ -165,7 +163,7 @@ public class DbDowngradeHelperTest {
mSchemaFile.delete();
mDbFile.delete();
DbDowngradeHelper.updateSchemaFile(mSchemaFile, LauncherProvider.SCHEMA_VERSION, mContext);
DbDowngradeHelper.updateSchemaFile(mSchemaFile, DatabaseHelper.SCHEMA_VERSION, mContext);
DatabaseHelper dbHelper = new DatabaseHelper(mContext, DB_FILE, false) {
@Override
@@ -26,8 +26,8 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.LauncherProvider.DatabaseHelper;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.model.DatabaseHelper;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -61,6 +61,7 @@ import com.android.launcher3.LauncherSettings;
import com.android.launcher3.model.AllAppsList;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.DatabaseHelper;
import com.android.launcher3.model.ItemInstallQueue;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;