Snap for 10071751 from 93e0b08dba to udc-qpr1-release
Change-Id: Ia2ec821012383986a0fd93c8add9fa5b47e28dd2
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
|
||||
<item android:state_hovered="false" android:color="?androidprv:attr/colorSurface" />
|
||||
<item android:state_hovered="true" android:color="?androidprv:attr/colorSurfaceVariant" />
|
||||
</selector>
|
||||
@@ -15,8 +15,7 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
|
||||
<solid android:color="?androidprv:attr/colorSurface" />
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/menu_item_hover_state_color" />
|
||||
<corners android:radius="@dimen/task_menu_item_corner_radius" />
|
||||
</shape>
|
||||
|
||||
@@ -13,29 +13,36 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout 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:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
style="@style/TextAppearance.TaskbarEduTooltip.Title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="@dimen/taskbar_edu_tooltip_vertical_margin"
|
||||
android:text="@string/taskbar_edu_features"
|
||||
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/splitscreen_animation"/>
|
||||
|
||||
<com.airbnb.lottie.LottieAnimationView
|
||||
android:id="@+id/splitscreen_animation"
|
||||
android:layout_width="@dimen/taskbar_edu_features_lottie_width"
|
||||
android:layout_height="@dimen/taskbar_edu_features_lottie_height"
|
||||
android:layout_marginTop="@dimen/taskbar_edu_tooltip_vertical_margin"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"
|
||||
|
||||
app:lottie_autoPlay="true"
|
||||
app:lottie_loop="true" />
|
||||
app:lottie_loop="true"
|
||||
|
||||
app:layout_constraintEnd_toEndOf="@id/splitscreen_text"
|
||||
app:layout_constraintStart_toStartOf="@id/splitscreen_text"
|
||||
app:layout_constraintTop_toBottomOf="@id/title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/splitscreen_text"
|
||||
@@ -43,8 +50,9 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/taskbar_edu_splitscreen"
|
||||
app:layout_constraintEnd_toEndOf="@id/splitscreen_animation"
|
||||
app:layout_constraintStart_toStartOf="@id/splitscreen_animation"
|
||||
|
||||
app:layout_constraintEnd_toStartOf="@id/settings_text"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/splitscreen_animation" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
@@ -57,13 +65,14 @@
|
||||
android:id="@+id/settings_animation"
|
||||
android:layout_width="@dimen/taskbar_edu_features_lottie_width"
|
||||
android:layout_height="@dimen/taskbar_edu_features_lottie_height"
|
||||
android:layout_marginStart="@dimen/taskbar_edu_features_horizontal_spacing"
|
||||
android:layout_marginTop="@dimen/taskbar_edu_tooltip_vertical_margin"
|
||||
app:layout_constraintStart_toEndOf="@id/splitscreen_animation"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"
|
||||
|
||||
app:lottie_autoPlay="true"
|
||||
app:lottie_loop="true"
|
||||
app:lottie_rawRes="@raw/taskbar_edu_settings" />
|
||||
app:lottie_rawRes="@raw/taskbar_edu_settings"
|
||||
|
||||
app:layout_constraintEnd_toEndOf="@id/settings_text"
|
||||
app:layout_constraintStart_toStartOf="@id/settings_text"
|
||||
app:layout_constraintTop_toBottomOf="@id/title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/settings_text"
|
||||
@@ -71,21 +80,23 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/taskbar_edu_settings_persistent"
|
||||
app:layout_constraintEnd_toEndOf="@id/settings_animation"
|
||||
app:layout_constraintStart_toStartOf="@id/settings_animation"
|
||||
android:layout_marginStart="@dimen/taskbar_edu_features_horizontal_spacing"
|
||||
|
||||
app:layout_constraintEnd_toStartOf="@id/suggestions_text"
|
||||
app:layout_constraintStart_toEndOf="@id/splitscreen_text"
|
||||
app:layout_constraintTop_toBottomOf="@id/settings_animation" />
|
||||
|
||||
<com.airbnb.lottie.LottieAnimationView
|
||||
android:id="@+id/suggestions_animation"
|
||||
android:layout_width="@dimen/taskbar_edu_features_lottie_width"
|
||||
android:layout_height="@dimen/taskbar_edu_features_lottie_height"
|
||||
android:layout_marginStart="@dimen/taskbar_edu_features_horizontal_spacing"
|
||||
android:layout_marginTop="@dimen/taskbar_edu_tooltip_vertical_margin"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/settings_animation"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"
|
||||
|
||||
app:lottie_autoPlay="true"
|
||||
app:lottie_loop="true" />
|
||||
app:lottie_loop="true"
|
||||
|
||||
app:layout_constraintEnd_toEndOf="@id/suggestions_text"
|
||||
app:layout_constraintStart_toStartOf="@id/suggestions_text"
|
||||
app:layout_constraintTop_toBottomOf="@id/title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/suggestions_text"
|
||||
@@ -93,8 +104,10 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/taskbar_edu_suggestions"
|
||||
app:layout_constraintEnd_toEndOf="@id/suggestions_animation"
|
||||
app:layout_constraintStart_toStartOf="@id/suggestions_animation"
|
||||
android:layout_marginStart="@dimen/taskbar_edu_features_horizontal_spacing"
|
||||
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/settings_text"
|
||||
app:layout_constraintTop_toBottomOf="@id/suggestions_animation" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
@@ -108,11 +121,12 @@
|
||||
android:id="@+id/done_button"
|
||||
style="@style/TaskbarEdu.Button.Done"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:text="@string/taskbar_edu_done"
|
||||
android:textColor="?androidprv:attr/textColorOnAccent"
|
||||
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/edu_barrier_bottom" />
|
||||
</merge>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -13,8 +13,10 @@
|
||||
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">
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
@@ -39,4 +41,4 @@
|
||||
app:lottie_loop="true"
|
||||
app:lottie_rawRes="@raw/taskbar_edu_stashing" />
|
||||
|
||||
</merge>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -17,7 +17,8 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|center"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:focusable="true"
|
||||
@@ -25,7 +26,7 @@
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<FrameLayout
|
||||
android:id="@+id/content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -340,6 +340,8 @@
|
||||
<dimen name="taskbar_edu_features_lottie_width">170dp</dimen>
|
||||
<dimen name="taskbar_edu_features_lottie_height">106dp</dimen>
|
||||
<dimen name="taskbar_edu_features_horizontal_spacing">24dp</dimen>
|
||||
<dimen name="taskbar_edu_features_tooltip_width_persistent">624dp</dimen>
|
||||
<dimen name="taskbar_edu_features_tooltip_width_transient">428dp</dimen>
|
||||
|
||||
<!--- Taskbar Pinning -->
|
||||
<dimen name="taskbar_pinning_popup_menu_width">300dp</dimen>
|
||||
|
||||
@@ -200,7 +200,7 @@
|
||||
<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>
|
||||
<item name="android:minHeight">36dp</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.TaskbarEduTooltip.Title" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle">
|
||||
|
||||
@@ -25,7 +25,9 @@ import android.view.MotionEvent
|
||||
import android.view.MotionEvent.ACTION_DOWN
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.view.animation.Interpolator
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
|
||||
import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE
|
||||
import com.android.app.animation.Interpolators.STANDARD
|
||||
@@ -77,6 +79,18 @@ constructor(
|
||||
}
|
||||
mIsOpen = true
|
||||
activityContext.dragLayer.addView(this)
|
||||
|
||||
// Make sure we have enough height to display all of the content, which can be an issue on
|
||||
// large text and display scaling configurations. If we run out of height, remove the width
|
||||
// constraint to reduce the number of lines of text and hopefully free up some height.
|
||||
activityContext.dragLayer.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
|
||||
if (
|
||||
measuredHeight + activityContext.deviceProfile.taskbarHeight >=
|
||||
activityContext.deviceProfile.availableHeightPx
|
||||
) {
|
||||
updateLayoutParams { width = MATCH_PARENT }
|
||||
}
|
||||
|
||||
openCloseAnimator = createOpenCloseAnimator(isOpening = true).apply { start() }
|
||||
}
|
||||
|
||||
|
||||
@@ -19,11 +19,13 @@ import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
import android.view.accessibility.AccessibilityEvent
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import androidx.annotation.IntDef
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import com.airbnb.lottie.LottieAnimationView
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.Utilities
|
||||
@@ -127,11 +129,24 @@ class TaskbarEduTooltipController(val activityContext: TaskbarActivityContext) :
|
||||
settingsEdu.visibility = VISIBLE
|
||||
}
|
||||
|
||||
findViewById<View>(R.id.done_button)?.setOnClickListener { hide() }
|
||||
if (DisplayController.isTransientTaskbar(activityContext)) {
|
||||
(layoutParams as ViewGroup.MarginLayoutParams).bottomMargin +=
|
||||
activityContext.deviceProfile.taskbarHeight
|
||||
// Set up layout parameters.
|
||||
content.updateLayoutParams { width = MATCH_PARENT }
|
||||
updateLayoutParams<MarginLayoutParams> {
|
||||
if (DisplayController.isTransientTaskbar(activityContext)) {
|
||||
width =
|
||||
resources.getDimensionPixelSize(
|
||||
R.dimen.taskbar_edu_features_tooltip_width_transient
|
||||
)
|
||||
bottomMargin += activityContext.deviceProfile.taskbarHeight
|
||||
} else {
|
||||
width =
|
||||
resources.getDimensionPixelSize(
|
||||
R.dimen.taskbar_edu_features_tooltip_width_persistent
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
findViewById<View>(R.id.done_button)?.setOnClickListener { hide() }
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext;
|
||||
import com.android.launcher3.taskbar.TaskbarControllers;
|
||||
import com.android.quickstep.views.DesktopTaskView;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListener;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListeners;
|
||||
|
||||
@@ -65,7 +66,9 @@ public final class TaskbarOverlayController {
|
||||
|
||||
@Override
|
||||
public void onTaskMovedToFront(int taskId) {
|
||||
mProxyView.close(false);
|
||||
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
|
||||
mProxyView.close(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
|
||||
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.CANCEL_RECENTS_ANIMATION;
|
||||
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION;
|
||||
@@ -34,6 +36,7 @@ import com.android.quickstep.util.ActiveGestureLog;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -105,8 +108,16 @@ public class RecentsAnimationCallbacks implements
|
||||
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(),
|
||||
mController::finishAnimationToApp);
|
||||
} else {
|
||||
RemoteAnimationTarget[] nonAppTargets =
|
||||
mSystemUiProxy.onGoingToRecentsLegacy(appTargets);
|
||||
RemoteAnimationTarget[] nonAppTargets;
|
||||
if (!TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
|
||||
nonAppTargets = mSystemUiProxy.onGoingToRecentsLegacy(appTargets);
|
||||
} else {
|
||||
final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
|
||||
final ArrayList<RemoteAnimationTarget> nonApps = new ArrayList<>();
|
||||
classifyTargets(appTargets, apps, nonApps);
|
||||
appTargets = apps.toArray(new RemoteAnimationTarget[apps.size()]);
|
||||
nonAppTargets = nonApps.toArray(new RemoteAnimationTarget[nonApps.size()]);
|
||||
}
|
||||
if (nonAppTargets == null) {
|
||||
nonAppTargets = new RemoteAnimationTarget[0];
|
||||
}
|
||||
@@ -176,6 +187,18 @@ public class RecentsAnimationCallbacks implements
|
||||
return mListeners.toArray(new RecentsAnimationListener[mListeners.size()]);
|
||||
}
|
||||
|
||||
private void classifyTargets(RemoteAnimationTarget[] appTargets,
|
||||
ArrayList<RemoteAnimationTarget> apps, ArrayList<RemoteAnimationTarget> nonApps) {
|
||||
for (int i = 0; i < appTargets.length; i++) {
|
||||
RemoteAnimationTarget target = appTargets[i];
|
||||
if (target.windowType == TYPE_DOCK_DIVIDER) {
|
||||
nonApps.add(target);
|
||||
} else {
|
||||
apps.add(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for the recents animation callbacks.
|
||||
*/
|
||||
|
||||
@@ -75,7 +75,7 @@ public class BaseDepthController {
|
||||
// marking the launcher surface as opaque. Only used in certain Launcher states.
|
||||
private boolean mHasContentBehindLauncher;
|
||||
|
||||
/** Pause applying depth and blur, can be used when something behind the Launcher. */
|
||||
/** Pause blur but allow transparent, can be used when launch something behind the Launcher. */
|
||||
protected boolean mPauseBlurs;
|
||||
|
||||
/**
|
||||
@@ -132,7 +132,7 @@ public class BaseDepthController {
|
||||
return;
|
||||
}
|
||||
boolean hasOpaqueBg = mLauncher.getScrimView().isFullyOpaque();
|
||||
boolean isSurfaceOpaque = mPauseBlurs || (!mHasContentBehindLauncher && hasOpaqueBg);
|
||||
boolean isSurfaceOpaque = !mHasContentBehindLauncher && hasOpaqueBg && !mPauseBlurs;
|
||||
|
||||
mCurrentBlur = !mCrossWindowBlursEnabled || hasOpaqueBg || mPauseBlurs
|
||||
? 0 : (int) (depth * mMaxBlurRadius);
|
||||
|
||||
@@ -43,6 +43,10 @@ public abstract class BaseUnfoldMoveFromCenterAnimator implements TransitionProg
|
||||
new UnfoldMoveFromCenterRotationListener();
|
||||
private boolean mAnimationInProgress = false;
|
||||
|
||||
// Save the last transition progress so we can re-apply it in case we re-register the view for
|
||||
// the animation (by calling onPrepareViewsForAnimation)
|
||||
private Float mLastTransitionProgress = null;
|
||||
|
||||
public BaseUnfoldMoveFromCenterAnimator(WindowManager windowManager,
|
||||
RotationChangeProvider rotationChangeProvider) {
|
||||
mMoveFromCenterAnimation = new UnfoldMoveFromCenterAnimator(windowManager,
|
||||
@@ -63,11 +67,13 @@ public abstract class BaseUnfoldMoveFromCenterAnimator implements TransitionProg
|
||||
@Override
|
||||
public void onTransitionProgress(float progress) {
|
||||
mMoveFromCenterAnimation.onTransitionProgress(progress);
|
||||
mLastTransitionProgress = progress;
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
public void onTransitionFinished() {
|
||||
mLastTransitionProgress = null;
|
||||
mAnimationInProgress = false;
|
||||
mRotationChangeProvider.removeCallback(mRotationListener);
|
||||
mMoveFromCenterAnimation.onTransitionFinished();
|
||||
@@ -93,8 +99,11 @@ public abstract class BaseUnfoldMoveFromCenterAnimator implements TransitionProg
|
||||
mOriginalClipToPadding.clear();
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
protected void onPrepareViewsForAnimation() {
|
||||
|
||||
if (mLastTransitionProgress != null) {
|
||||
mMoveFromCenterAnimation.onTransitionProgress(mLastTransitionProgress);
|
||||
}
|
||||
}
|
||||
|
||||
protected void registerViewForAnimation(View view) {
|
||||
|
||||
@@ -27,10 +27,15 @@ import android.view.WindowManager;
|
||||
|
||||
import androidx.core.view.OneShotPreDrawListener;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
|
||||
import com.android.launcher3.Hotseat;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.Workspace;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.util.HorizontalInsettableView;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.util.unfold.PreemptiveUnfoldTransitionProgressProvider;
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
|
||||
import com.android.systemui.unfold.updates.RotationChangeProvider;
|
||||
@@ -40,7 +45,7 @@ import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
|
||||
/**
|
||||
* Controls animations that are happening during unfolding foldable devices
|
||||
*/
|
||||
public class LauncherUnfoldAnimationController {
|
||||
public class LauncherUnfoldAnimationController implements OnDeviceProfileChangeListener {
|
||||
|
||||
// Percentage of the width of the quick search bar that will be reduced
|
||||
// from the both sides of the bar when progress is 0
|
||||
@@ -55,9 +60,11 @@ public class LauncherUnfoldAnimationController {
|
||||
private final NaturalRotationUnfoldProgressProvider mNaturalOrientationProgressProvider;
|
||||
private final UnfoldMoveFromCenterHotseatAnimator mUnfoldMoveFromCenterHotseatAnimator;
|
||||
private final UnfoldMoveFromCenterWorkspaceAnimator mUnfoldMoveFromCenterWorkspaceAnimator;
|
||||
private PreemptiveUnfoldTransitionProgressProvider mPreemptiveProgressProvider = null;
|
||||
private Boolean mIsTablet = null;
|
||||
|
||||
private static final String TRACE_WAIT_TO_HANDLE_UNFOLD_TRANSITION =
|
||||
"waitingOneFrameBeforeHandlingUnfoldAnimation";
|
||||
"LauncherUnfoldAnimationController#waitingForTheNextFrame";
|
||||
|
||||
@Nullable
|
||||
private HorizontalInsettableView mQsbInsettable;
|
||||
@@ -68,8 +75,19 @@ public class LauncherUnfoldAnimationController {
|
||||
UnfoldTransitionProgressProvider unfoldTransitionProgressProvider,
|
||||
RotationChangeProvider rotationChangeProvider) {
|
||||
mLauncher = launcher;
|
||||
mProgressProvider = new ScopedUnfoldTransitionProgressProvider(
|
||||
unfoldTransitionProgressProvider);
|
||||
|
||||
if (FeatureFlags.PREEMPTIVE_UNFOLD_ANIMATION_START.get()) {
|
||||
mPreemptiveProgressProvider = new PreemptiveUnfoldTransitionProgressProvider(
|
||||
unfoldTransitionProgressProvider, launcher.getMainThreadHandler());
|
||||
mPreemptiveProgressProvider.init();
|
||||
|
||||
mProgressProvider = new ScopedUnfoldTransitionProgressProvider(
|
||||
mPreemptiveProgressProvider);
|
||||
} else {
|
||||
mProgressProvider = new ScopedUnfoldTransitionProgressProvider(
|
||||
unfoldTransitionProgressProvider);
|
||||
}
|
||||
|
||||
mUnfoldMoveFromCenterHotseatAnimator = new UnfoldMoveFromCenterHotseatAnimator(launcher,
|
||||
windowManager, rotationChangeProvider);
|
||||
mUnfoldMoveFromCenterWorkspaceAnimator = new UnfoldMoveFromCenterWorkspaceAnimator(launcher,
|
||||
@@ -85,6 +103,8 @@ public class LauncherUnfoldAnimationController {
|
||||
// Animated only in natural orientation
|
||||
mNaturalOrientationProgressProvider.addCallback(new QsbAnimationListener());
|
||||
mNaturalOrientationProgressProvider.addCallback(mUnfoldMoveFromCenterHotseatAnimator);
|
||||
|
||||
mLauncher.addOnDeviceProfileChangeListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,17 +116,21 @@ public class LauncherUnfoldAnimationController {
|
||||
mQsbInsettable = (HorizontalInsettableView) hotseat.getQsb();
|
||||
}
|
||||
|
||||
handleTransitionOnNextFrame();
|
||||
mProgressProvider.setReadyToHandleTransition(true);
|
||||
}
|
||||
|
||||
private void handleTransitionOnNextFrame() {
|
||||
private void preemptivelyStartAnimationOnNextFrame() {
|
||||
Trace.asyncTraceBegin(Trace.TRACE_TAG_APP,
|
||||
TRACE_WAIT_TO_HANDLE_UNFOLD_TRANSITION, /* cookie= */ 0);
|
||||
|
||||
// Start the animation (and apply the transformations) in pre-draw listener to make sure
|
||||
// that the views are laid out as some transformations depend on the view sizes and position
|
||||
OneShotPreDrawListener.add(mLauncher.getWorkspace(),
|
||||
() -> {
|
||||
Trace.asyncTraceEnd(Trace.TRACE_TAG_APP,
|
||||
TRACE_WAIT_TO_HANDLE_UNFOLD_TRANSITION, /* cookie= */ 0);
|
||||
mProgressProvider.setReadyToHandleTransition(true);
|
||||
mPreemptiveProgressProvider.preemptivelyStartTransition(
|
||||
/* initialProgress= */ 0f);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -124,14 +148,34 @@ public class LauncherUnfoldAnimationController {
|
||||
public void onDestroy() {
|
||||
mProgressProvider.destroy();
|
||||
mNaturalOrientationProgressProvider.destroy();
|
||||
mLauncher.removeOnDeviceProfileChangeListener(this);
|
||||
}
|
||||
|
||||
/** Called when launcher finished binding its items. */
|
||||
/**
|
||||
* Called when launcher has finished binding its items
|
||||
*/
|
||||
public void updateRegisteredViewsIfNeeded() {
|
||||
mUnfoldMoveFromCenterHotseatAnimator.updateRegisteredViewsIfNeeded();
|
||||
mUnfoldMoveFromCenterWorkspaceAnimator.updateRegisteredViewsIfNeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceProfileChanged(DeviceProfile dp) {
|
||||
if (!FeatureFlags.PREEMPTIVE_UNFOLD_ANIMATION_START.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mIsTablet != null && dp.isTablet != mIsTablet) {
|
||||
if (dp.isTablet && SystemUiProxy.INSTANCE.get(mLauncher).isActive()) {
|
||||
// Preemptively start the unfold animation to make sure that we have drawn
|
||||
// the first frame of the animation before the screen gets unblocked
|
||||
preemptivelyStartAnimationOnNextFrame();
|
||||
}
|
||||
}
|
||||
|
||||
mIsTablet = dp.isTablet;
|
||||
}
|
||||
|
||||
private class QsbAnimationListener implements TransitionProgressListener {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -48,6 +48,8 @@ public class UnfoldMoveFromCenterHotseatAnimator extends BaseUnfoldMoveFromCente
|
||||
View child = hotseatIcons.getChildAt(i);
|
||||
registerViewForAnimation(child);
|
||||
}
|
||||
|
||||
super.onPrepareViewsForAnimation();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -58,6 +58,8 @@ public class UnfoldMoveFromCenterWorkspaceAnimator extends BaseUnfoldMoveFromCen
|
||||
|
||||
setClipChildren(workspace, false);
|
||||
setClipToPadding(workspace, true);
|
||||
|
||||
super.onPrepareViewsForAnimation();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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.quickstep.util.unfold
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Trace
|
||||
import android.util.Log
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
|
||||
|
||||
/**
|
||||
* Transition progress provider wrapper that can preemptively start the transition on demand
|
||||
* without relying on the source provider. When the source provider has started the animation
|
||||
* it switches to it.
|
||||
*
|
||||
* This might be useful when we want to synchronously start the unfold animation and render
|
||||
* the first frame during turning on the screen. For example, this is used in Launcher where
|
||||
* we need to render the first frame of the animation immediately after receiving a configuration
|
||||
* change event so Window Manager will wait for this frame to be rendered before unblocking
|
||||
* the screen. We can't rely on the original transition progress as it starts the animation
|
||||
* after the screen fully turned on (and unblocked), at this moment it is already too late to
|
||||
* start the animation.
|
||||
*
|
||||
* Using this provider we could render the first frame preemptively by sending 'transition started'
|
||||
* and '0' transition progress before the original progress provider sends these events.
|
||||
*/
|
||||
class PreemptiveUnfoldTransitionProgressProvider(
|
||||
private val source: UnfoldTransitionProgressProvider,
|
||||
private val handler: Handler
|
||||
) : UnfoldTransitionProgressProvider, TransitionProgressListener {
|
||||
|
||||
private val timeoutRunnable = Runnable {
|
||||
if (isRunning) {
|
||||
listeners.forEach { it.onTransitionFinished() }
|
||||
onPreemptiveStartFinished()
|
||||
Log.wtf(TAG, "Timeout occurred when waiting for the source transition to start")
|
||||
}
|
||||
}
|
||||
|
||||
private val listeners = arrayListOf<TransitionProgressListener>()
|
||||
private var isPreemptivelyRunning = false
|
||||
private var isSourceRunning = false
|
||||
|
||||
private val isRunning: Boolean
|
||||
get() = isPreemptivelyRunning || isSourceRunning
|
||||
|
||||
private val sourceListener =
|
||||
object : TransitionProgressListener {
|
||||
override fun onTransitionStarted() {
|
||||
handler.removeCallbacks(timeoutRunnable)
|
||||
|
||||
if (!isRunning) {
|
||||
listeners.forEach { it.onTransitionStarted() }
|
||||
}
|
||||
|
||||
onPreemptiveStartFinished()
|
||||
isSourceRunning = true
|
||||
}
|
||||
|
||||
override fun onTransitionProgress(progress: Float) {
|
||||
if (isRunning) {
|
||||
listeners.forEach { it.onTransitionProgress(progress) }
|
||||
isSourceRunning = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTransitionFinishing() {
|
||||
if (isRunning) {
|
||||
listeners.forEach { it.onTransitionFinishing() }
|
||||
isSourceRunning = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTransitionFinished() {
|
||||
if (isRunning) {
|
||||
listeners.forEach { it.onTransitionFinished() }
|
||||
}
|
||||
|
||||
isSourceRunning = false
|
||||
onPreemptiveStartFinished()
|
||||
handler.removeCallbacks(timeoutRunnable)
|
||||
}
|
||||
}
|
||||
|
||||
fun init() {
|
||||
source.addCallback(sourceListener)
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the animation preemptively.
|
||||
*
|
||||
* - If the source provider is already running, this method won't change any behavior
|
||||
* - If the source provider has not started running yet, it will call onTransitionStarted
|
||||
* for all listeners and optionally onTransitionProgress(initialProgress) if supplied.
|
||||
* When the source provider starts the animation it will switch to send progress and finished
|
||||
* events from it.
|
||||
* If the source provider won't start the animation within a timeout, the animation will be
|
||||
* cancelled and onTransitionFinished will be delivered to the current listeners.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun preemptivelyStartTransition(initialProgress: Float? = null) {
|
||||
if (!isRunning) {
|
||||
Trace.beginAsyncSection("$TAG#startedPreemptively", 0)
|
||||
|
||||
listeners.forEach { it.onTransitionStarted() }
|
||||
initialProgress?.let { progress ->
|
||||
listeners.forEach { it.onTransitionProgress(progress) }
|
||||
}
|
||||
|
||||
handler.removeCallbacks(timeoutRunnable)
|
||||
handler.postDelayed(timeoutRunnable, PREEMPTIVE_UNFOLD_TIMEOUT_MS)
|
||||
}
|
||||
|
||||
isPreemptivelyRunning = true
|
||||
}
|
||||
|
||||
fun cancelPreemptiveStart() {
|
||||
handler.removeCallbacks(timeoutRunnable)
|
||||
if (isRunning) {
|
||||
listeners.forEach { it.onTransitionFinished() }
|
||||
}
|
||||
onPreemptiveStartFinished()
|
||||
}
|
||||
|
||||
private fun onPreemptiveStartFinished() {
|
||||
if (isPreemptivelyRunning) {
|
||||
Trace.endAsyncSection("$TAG#startedPreemptively", 0)
|
||||
isPreemptivelyRunning = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun destroy() {
|
||||
handler.removeCallbacks(timeoutRunnable)
|
||||
source.removeCallback(sourceListener)
|
||||
source.destroy()
|
||||
}
|
||||
|
||||
override fun addCallback(listener: TransitionProgressListener) {
|
||||
listeners += listener
|
||||
}
|
||||
|
||||
override fun removeCallback(listener: TransitionProgressListener) {
|
||||
listeners -= listener
|
||||
}
|
||||
}
|
||||
|
||||
const val TAG = "PreemptiveUnfoldTransitionProgressProvider"
|
||||
const val PREEMPTIVE_UNFOLD_TIMEOUT_MS = 1700L
|
||||
@@ -27,7 +27,6 @@ import android.content.Intent;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.launcher3.tapl.Overview;
|
||||
import com.android.launcher3.tapl.Taskbar;
|
||||
import com.android.launcher3.ui.TaplTestsLauncher3;
|
||||
import com.android.launcher3.util.LauncherLayoutBuilder;
|
||||
@@ -64,10 +63,6 @@ public class TaplTestsTaskbar extends AbstractQuickStepTest {
|
||||
"com.android.launcher3.testcomponent.BaseTestingActivity");
|
||||
mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, layoutBuilder);
|
||||
TaplTestsLauncher3.initialize(this);
|
||||
Overview overview = mLauncher.getWorkspace().switchToOverview();
|
||||
if (overview.hasTasks()) {
|
||||
overview.dismissAllTasks();
|
||||
}
|
||||
|
||||
startAppFast(CALCULATOR_APP_PACKAGE);
|
||||
mLauncher.enableBlockTimeout(true);
|
||||
|
||||
+261
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* 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.quickstep.util.unfold
|
||||
|
||||
import android.os.Handler
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import android.testing.TestableLooper.RunWithLooper
|
||||
import android.util.Log
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.launcher3.util.any
|
||||
import com.android.launcher3.util.mock
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mockito.anyBoolean
|
||||
import org.mockito.Mockito.anyFloat
|
||||
import org.mockito.Mockito.inOrder
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.verify
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@RunWithLooper
|
||||
class PreemptiveUnfoldTransitionProgressProviderTest {
|
||||
|
||||
private lateinit var testableLooper: TestableLooper
|
||||
private lateinit var source: TransitionProgressListener
|
||||
private lateinit var handler: Handler
|
||||
private lateinit var oldWtfHandler: Log.TerribleFailureHandler
|
||||
private val listener: TransitionProgressListener = mock()
|
||||
private val testWtfHandler: Log.TerribleFailureHandler = mock()
|
||||
|
||||
private lateinit var provider: PreemptiveUnfoldTransitionProgressProvider
|
||||
|
||||
@Before
|
||||
fun before() {
|
||||
testableLooper = TestableLooper.get(this)
|
||||
handler = Handler(testableLooper.looper)
|
||||
|
||||
val testSource = createSource()
|
||||
source = testSource as TransitionProgressListener
|
||||
|
||||
oldWtfHandler = Log.setWtfHandler(testWtfHandler)
|
||||
|
||||
provider = PreemptiveUnfoldTransitionProgressProvider(testSource, handler)
|
||||
provider.init()
|
||||
provider.addCallback(listener)
|
||||
}
|
||||
|
||||
@After
|
||||
fun after() {
|
||||
Log.setWtfHandler(oldWtfHandler)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preemptiveStartInitialProgressNull_transitionStarts() {
|
||||
provider.preemptivelyStartTransition(initialProgress = null)
|
||||
|
||||
verify(listener).onTransitionStarted()
|
||||
verify(listener, never()).onTransitionProgress(anyFloat())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preemptiveStartWithInitialProgress_startsAnimationAndSendsProgress() {
|
||||
provider.preemptivelyStartTransition(initialProgress = 0.5f)
|
||||
|
||||
verify(listener).onTransitionStarted()
|
||||
verify(listener).onTransitionProgress(0.5f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preemptiveStartAndCancel_finishesAnimation() {
|
||||
provider.preemptivelyStartTransition()
|
||||
provider.cancelPreemptiveStart()
|
||||
|
||||
with(inOrder(listener)) {
|
||||
verify(listener).onTransitionStarted()
|
||||
verify(listener).onTransitionFinished()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preemptiveStartAndThenSourceStartsTransition_transitionStarts() {
|
||||
provider.preemptivelyStartTransition()
|
||||
source.onTransitionStarted()
|
||||
|
||||
verify(listener).onTransitionStarted()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preemptiveStartAndThenSourceStartsAndFinishesTransition_transitionFinishes() {
|
||||
provider.preemptivelyStartTransition()
|
||||
|
||||
source.onTransitionStarted()
|
||||
source.onTransitionFinished()
|
||||
|
||||
with(inOrder(listener)) {
|
||||
verify(listener).onTransitionStarted()
|
||||
verify(listener).onTransitionFinished()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preemptiveStartAndThenSourceStartsAnimationAndSendsProgress_sendsProgress() {
|
||||
provider.preemptivelyStartTransition()
|
||||
|
||||
source.onTransitionStarted()
|
||||
source.onTransitionProgress(0.4f)
|
||||
|
||||
verify(listener).onTransitionProgress(0.4f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preemptiveStartAndThenSourceSendsProgress_sendsProgress() {
|
||||
provider.preemptivelyStartTransition()
|
||||
|
||||
source.onTransitionProgress(0.4f)
|
||||
|
||||
verify(listener).onTransitionProgress(0.4f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preemptiveStartAfterTransitionRunning_transitionStarted() {
|
||||
source.onTransitionStarted()
|
||||
|
||||
provider.preemptivelyStartTransition()
|
||||
|
||||
verify(listener).onTransitionStarted()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preemptiveStartAfterTransitionRunningAndThenFinished_transitionFinishes() {
|
||||
source.onTransitionStarted()
|
||||
|
||||
provider.preemptivelyStartTransition()
|
||||
source.onTransitionFinished()
|
||||
|
||||
with(inOrder(listener)) {
|
||||
verify(listener).onTransitionStarted()
|
||||
verify(listener).onTransitionFinished()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preemptiveStart_transitionDoesNotFinishAfterTimeout_finishesTransition() {
|
||||
provider.preemptivelyStartTransition()
|
||||
|
||||
testableLooper.moveTimeForward(PREEMPTIVE_UNFOLD_TIMEOUT_MS + 1)
|
||||
testableLooper.processAllMessages()
|
||||
|
||||
with(inOrder(listener)) {
|
||||
verify(listener).onTransitionStarted()
|
||||
verify(listener).onTransitionFinished()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preemptiveStart_transitionFinishAfterTimeout_logsWtf() {
|
||||
provider.preemptivelyStartTransition()
|
||||
|
||||
testableLooper.moveTimeForward(PREEMPTIVE_UNFOLD_TIMEOUT_MS + 1)
|
||||
testableLooper.processAllMessages()
|
||||
|
||||
verify(testWtfHandler).onTerribleFailure(any(), any(), anyBoolean())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preemptiveStart_transitionDoesNotFinishBeforeTimeout_doesNotFinishTransition() {
|
||||
provider.preemptivelyStartTransition()
|
||||
|
||||
testableLooper.moveTimeForward(PREEMPTIVE_UNFOLD_TIMEOUT_MS - 1)
|
||||
testableLooper.processAllMessages()
|
||||
|
||||
verify(listener).onTransitionStarted()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preemptiveStart_transitionStarted_timeoutHappened_doesNotFinishTransition() {
|
||||
provider.preemptivelyStartTransition()
|
||||
|
||||
source.onTransitionStarted()
|
||||
testableLooper.moveTimeForward(PREEMPTIVE_UNFOLD_TIMEOUT_MS + 1)
|
||||
testableLooper.processAllMessages()
|
||||
|
||||
verify(listener).onTransitionStarted()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noPreemptiveStart_transitionStarted_startsTransition() {
|
||||
source.onTransitionStarted()
|
||||
|
||||
verify(listener).onTransitionStarted()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noPreemptiveStart_transitionProgress_sendsProgress() {
|
||||
source.onTransitionStarted()
|
||||
|
||||
source.onTransitionProgress(0.5f)
|
||||
|
||||
verify(listener).onTransitionProgress(0.5f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noPreemptiveStart_transitionFinishes_finishesTransition() {
|
||||
source.onTransitionStarted()
|
||||
source.onTransitionProgress(0.5f)
|
||||
|
||||
source.onTransitionFinished()
|
||||
|
||||
with(inOrder(listener)) {
|
||||
verify(listener).onTransitionStarted()
|
||||
verify(listener).onTransitionFinished()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSource(): UnfoldTransitionProgressProvider =
|
||||
object : TransitionProgressListener, UnfoldTransitionProgressProvider {
|
||||
|
||||
private val listeners = arrayListOf<TransitionProgressListener>()
|
||||
|
||||
override fun addCallback(listener: TransitionProgressListener) {
|
||||
listeners += listener
|
||||
}
|
||||
|
||||
override fun removeCallback(listener: TransitionProgressListener) {
|
||||
listeners -= listener
|
||||
}
|
||||
|
||||
override fun destroy() {}
|
||||
|
||||
override fun onTransitionStarted() =
|
||||
listeners.forEach(TransitionProgressListener::onTransitionStarted)
|
||||
|
||||
override fun onTransitionFinishing() =
|
||||
listeners.forEach(TransitionProgressListener::onTransitionFinishing)
|
||||
|
||||
override fun onTransitionFinished() =
|
||||
listeners.forEach(TransitionProgressListener::onTransitionFinished)
|
||||
|
||||
override fun onTransitionProgress(progress: Float) =
|
||||
listeners.forEach { it.onTransitionProgress(progress) }
|
||||
}
|
||||
}
|
||||
@@ -32,12 +32,6 @@
|
||||
<dimen name="dynamic_grid_hotseat_bottom_tall_padding">0dp</dimen>
|
||||
<dimen name="spring_loaded_hotseat_top_margin">76dp</dimen>
|
||||
|
||||
<!-- Qsb -->
|
||||
<!-- Used for adjusting the position of QSB when placed in hotseat. This is a ratio and a higher
|
||||
number signifies that the QSB is close to the hotseat icons and a lower number signifies that
|
||||
it is close to the bottom of the screen -->
|
||||
<item name="qsb_center_factor" format="float" type="dimen">0.325</item>
|
||||
|
||||
<dimen name="dynamic_grid_hotseat_side_padding">0dp</dimen>
|
||||
|
||||
<!-- Scalable Grid -->
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.android.launcher3;
|
||||
|
||||
import static android.appwidget.AppWidgetHostView.getDefaultPaddingForWidget;
|
||||
|
||||
import static com.android.launcher3.CellLayout.SPRING_LOADED_PROGRESS;
|
||||
import static com.android.launcher3.LauncherAnimUtils.LAYOUT_HEIGHT;
|
||||
import static com.android.launcher3.LauncherAnimUtils.LAYOUT_WIDTH;
|
||||
@@ -77,8 +75,6 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O
|
||||
private DragLayer mDragLayer;
|
||||
private ImageButton mReconfigureButton;
|
||||
|
||||
private Rect mWidgetPadding;
|
||||
|
||||
private final int mBackgroundPadding;
|
||||
private final int mTouchTargetWidth;
|
||||
|
||||
@@ -218,9 +214,6 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O
|
||||
mMaxHSpan = info.maxSpanX;
|
||||
mMaxVSpan = info.maxSpanY;
|
||||
|
||||
mWidgetPadding = getDefaultPaddingForWidget(getContext(),
|
||||
widgetView.getAppWidgetInfo().provider, null);
|
||||
|
||||
// Only show resize handles for the directions in which resizing is possible.
|
||||
InvariantDeviceProfile idp = LauncherAppState.getIDP(cellLayout.getContext());
|
||||
mVerticalResizeActive = (info.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0
|
||||
@@ -517,16 +510,12 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O
|
||||
*/
|
||||
private void getSnappedRectRelativeToDragLayer(Rect out) {
|
||||
float scale = mWidgetView.getScaleToFit();
|
||||
|
||||
mDragLayer.getViewRectRelativeToSelf(mWidgetView, out);
|
||||
|
||||
int width = 2 * mBackgroundPadding
|
||||
+ (int) (scale * (out.width() - mWidgetPadding.left - mWidgetPadding.right));
|
||||
int height = 2 * mBackgroundPadding
|
||||
+ (int) (scale * (out.height() - mWidgetPadding.top - mWidgetPadding.bottom));
|
||||
|
||||
int x = (int) (out.left - mBackgroundPadding + scale * mWidgetPadding.left);
|
||||
int y = (int) (out.top - mBackgroundPadding + scale * mWidgetPadding.top);
|
||||
int width = 2 * mBackgroundPadding + Math.round(scale * out.width());
|
||||
int height = 2 * mBackgroundPadding + Math.round(scale * out.height());
|
||||
int x = out.left - mBackgroundPadding;
|
||||
int y = out.top - mBackgroundPadding;
|
||||
|
||||
out.left = x;
|
||||
out.top = y;
|
||||
|
||||
@@ -16,8 +16,8 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.model.DatabaseHelper;
|
||||
import com.android.launcher3.model.LoaderTask;
|
||||
import com.android.launcher3.model.ModelDbController;
|
||||
import com.android.launcher3.model.WidgetsModel;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.pm.UserCache;
|
||||
@@ -52,7 +52,7 @@ public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
|
||||
* Updates the app widgets whose id has changed during the restore process.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static void restoreAppWidgetIds(Context context, ModelDbController controller,
|
||||
public static void restoreAppWidgetIds(Context context, DatabaseHelper helper,
|
||||
int[] oldWidgetIds, int[] newWidgetIds, @NonNull AppWidgetHost host) {
|
||||
if (WidgetsModel.GO_DISABLE_WIDGETS) {
|
||||
Log.e(TAG, "Skipping widget ID remap as widgets not supported");
|
||||
@@ -92,12 +92,12 @@ public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
|
||||
final String where = "appWidgetId=? and (restored & 1) = 1 and profileId=?";
|
||||
final String[] args = new String[] { oldWidgetId, Long.toString(mainProfileId) };
|
||||
int result = new ContentWriter(context,
|
||||
new ContentWriter.CommitParams(controller, where, args))
|
||||
new ContentWriter.CommitParams(helper, where, args))
|
||||
.put(LauncherSettings.Favorites.APPWIDGET_ID, newWidgetIds[i])
|
||||
.put(LauncherSettings.Favorites.RESTORED, state)
|
||||
.commit();
|
||||
if (result == 0) {
|
||||
Cursor cursor = controller.getDb().query(
|
||||
Cursor cursor = helper.getWritableDatabase().query(
|
||||
Favorites.TABLE_NAME,
|
||||
new String[] {Favorites.APPWIDGET_ID},
|
||||
"appWidgetId=?", new String[] { oldWidgetId }, null, null, null);
|
||||
|
||||
@@ -67,14 +67,12 @@ public class DeviceProfile {
|
||||
private static final int DEFAULT_DOT_SIZE = 100;
|
||||
private static final float ALL_APPS_TABLET_MAX_ROWS = 5.5f;
|
||||
private static final float MIN_FOLDER_TEXT_SIZE_SP = 16f;
|
||||
private static final float MIN_WIDGET_PADDING_DP = 6f;
|
||||
|
||||
public static final PointF DEFAULT_SCALE = new PointF(1.0f, 1.0f);
|
||||
public static final ViewScaleProvider DEFAULT_PROVIDER = itemInfo -> DEFAULT_SCALE;
|
||||
public static final Consumer<DeviceProfile> DEFAULT_DIMENSION_PROVIDER = dp -> {};
|
||||
|
||||
// Ratio of empty space, qsb should take up to appear visually centered.
|
||||
private final float mQsbCenterFactor;
|
||||
|
||||
public final InvariantDeviceProfile inv;
|
||||
private final Info mInfo;
|
||||
private final DisplayMetrics mMetrics;
|
||||
@@ -252,6 +250,10 @@ public class DeviceProfile {
|
||||
// Insets
|
||||
private final Rect mInsets = new Rect();
|
||||
public final Rect workspacePadding = new Rect();
|
||||
// Additional padding added to the widget inside its cellSpace. It is applied outside
|
||||
// the widgetView, such that the actual view size is same as the widget size.
|
||||
public final Rect widgetPadding = new Rect();
|
||||
|
||||
// When true, nav bar is on the left side of the screen.
|
||||
private boolean mIsSeascape;
|
||||
|
||||
@@ -314,9 +316,6 @@ public class DeviceProfile {
|
||||
availableHeightPx = windowBounds.availableSize.y;
|
||||
|
||||
aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
|
||||
boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
|
||||
mQsbCenterFactor = res.getFloat(R.dimen.qsb_center_factor);
|
||||
|
||||
if (isTwoPanels) {
|
||||
if (isLandscape) {
|
||||
mTypeIndex = INDEX_TWO_PANEL_LANDSCAPE;
|
||||
@@ -730,22 +729,6 @@ public class DeviceProfile {
|
||||
return mInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* We inset the widget padding added by the system and instead rely on the border spacing
|
||||
* between cells to create reliable consistency between widgets
|
||||
*/
|
||||
public boolean shouldInsetWidgets() {
|
||||
Rect widgetPadding = inv.defaultWidgetPadding;
|
||||
|
||||
// Check all sides to ensure that the widget won't overlap into another cell, or into
|
||||
// status bar.
|
||||
return workspaceTopPadding > widgetPadding.top
|
||||
&& cellLayoutBorderSpacePx.x > widgetPadding.left
|
||||
&& cellLayoutBorderSpacePx.y > widgetPadding.top
|
||||
&& cellLayoutBorderSpacePx.x > widgetPadding.right
|
||||
&& cellLayoutBorderSpacePx.y > widgetPadding.bottom;
|
||||
}
|
||||
|
||||
public Builder toBuilder(Context context) {
|
||||
WindowBounds bounds = new WindowBounds(
|
||||
widthPx, heightPx, availableWidthPx, availableHeightPx, rotationHint);
|
||||
@@ -999,6 +982,18 @@ public class DeviceProfile {
|
||||
// Folder icon
|
||||
folderIconSizePx = IconNormalizer.getNormalizedCircleSize(iconSizePx);
|
||||
folderIconOffsetYPx = (iconSizePx - folderIconSizePx) / 2;
|
||||
|
||||
// Update widget padding:
|
||||
float minSpacing = pxFromDp(MIN_WIDGET_PADDING_DP, mMetrics);
|
||||
if (cellLayoutBorderSpacePx.x < minSpacing
|
||||
|| cellLayoutBorderSpacePx.y < minSpacing) {
|
||||
widgetPadding.left = widgetPadding.right =
|
||||
Math.round(Math.max(0, minSpacing - cellLayoutBorderSpacePx.x));
|
||||
widgetPadding.top = widgetPadding.bottom =
|
||||
Math.round(Math.max(0, minSpacing - cellLayoutBorderSpacePx.y));
|
||||
} else {
|
||||
widgetPadding.setEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,8 +26,6 @@ import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUN
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.appwidget.AppWidgetHostView;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
@@ -35,7 +33,6 @@ import android.content.res.TypedArray;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
@@ -192,7 +189,6 @@ public class InvariantDeviceProfile {
|
||||
public List<DeviceProfile> supportedProfiles = Collections.EMPTY_LIST;
|
||||
|
||||
public Point defaultWallpaperSize;
|
||||
public Rect defaultWidgetPadding;
|
||||
|
||||
private final ArrayList<OnIDPChangeListener> mChangeListeners = new ArrayList<>();
|
||||
|
||||
@@ -443,9 +439,6 @@ public class InvariantDeviceProfile {
|
||||
deviceProfile.numShownHotseatIcons = numMinShownHotseatIconsForTablet;
|
||||
deviceProfile.recalculateHotseatWidthAndBorderSpace();
|
||||
});
|
||||
|
||||
ComponentName cn = new ComponentName(context.getPackageName(), getClass().getName());
|
||||
defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
|
||||
}
|
||||
|
||||
public void addOnChangeListener(OnIDPChangeListener listener) {
|
||||
|
||||
@@ -263,6 +263,18 @@ public class LauncherProvider extends ContentProvider {
|
||||
getModelDbController().refreshHotseatRestoreTable();
|
||||
return null;
|
||||
}
|
||||
case LauncherSettings.Settings.METHOD_UPDATE_CURRENT_OPEN_HELPER: {
|
||||
Bundle result = new Bundle();
|
||||
result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
|
||||
getModelDbController().updateCurrentOpenHelper(arg /* dbFile */));
|
||||
return result;
|
||||
}
|
||||
case LauncherSettings.Settings.METHOD_PREP_FOR_PREVIEW: {
|
||||
Bundle result = new Bundle();
|
||||
result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
|
||||
getModelDbController().prepareForPreview(arg /* dbFile */));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -147,6 +147,11 @@ public class LauncherSettings {
|
||||
*/
|
||||
public static final String HYBRID_HOTSEAT_BACKUP_TABLE = "hotseat_restore_backup";
|
||||
|
||||
/**
|
||||
* Temporary table used specifically for grid migrations during wallpaper preview
|
||||
*/
|
||||
public static final String PREVIEW_TABLE_NAME = "favorites_preview";
|
||||
|
||||
/**
|
||||
* Temporary table used specifically for multi-db grid migrations
|
||||
*/
|
||||
@@ -158,6 +163,18 @@ public class LauncherSettings {
|
||||
public static final Uri CONTENT_URI = Uri.parse("content://"
|
||||
+ LauncherProvider.AUTHORITY + "/" + TABLE_NAME);
|
||||
|
||||
/**
|
||||
* The content:// style URL for "favorites_preview" table
|
||||
*/
|
||||
public static final Uri PREVIEW_CONTENT_URI = Uri.parse("content://"
|
||||
+ LauncherProvider.AUTHORITY + "/" + PREVIEW_TABLE_NAME);
|
||||
|
||||
/**
|
||||
* The content:// style URL for "favorites_tmp" table
|
||||
*/
|
||||
public static final Uri TMP_CONTENT_URI = Uri.parse("content://"
|
||||
+ LauncherProvider.AUTHORITY + "/" + TMP_TABLE);
|
||||
|
||||
/**
|
||||
* The content:// style URL for a given row, identified by its id.
|
||||
*
|
||||
@@ -359,6 +376,10 @@ public class LauncherSettings {
|
||||
|
||||
public static final String METHOD_REFRESH_HOTSEAT_RESTORE_TABLE = "restore_hotseat_table";
|
||||
|
||||
public static final String METHOD_UPDATE_CURRENT_OPEN_HELPER = "update_current_open_helper";
|
||||
|
||||
public static final String METHOD_PREP_FOR_PREVIEW = "prep_for_preview";
|
||||
|
||||
public static final String EXTRA_VALUE = "value";
|
||||
|
||||
public static final String EXTRA_DB_NAME = "db_name";
|
||||
@@ -372,8 +393,11 @@ public class LauncherSettings {
|
||||
}
|
||||
|
||||
public static Bundle call(ContentResolver cr, String method, String arg) {
|
||||
return cr.call(CONTENT_URI, method, arg, null);
|
||||
return call(cr, method, arg, null /* extras */);
|
||||
}
|
||||
|
||||
public static Bundle call(ContentResolver cr, String method, String arg, Bundle extras) {
|
||||
return cr.call(CONTENT_URI, method, arg, extras);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,8 +46,6 @@ public class ShortcutAndWidgetContainer extends ViewGroup implements FolderIcon.
|
||||
// return an (x, y) value from helper functions. Do NOT use them to maintain other state.
|
||||
private final int[] mTmpCellXY = new int[2];
|
||||
|
||||
private final Rect mTempRect = new Rect();
|
||||
|
||||
@ContainerType
|
||||
private final int mContainerType;
|
||||
private final WallpaperManager mWallpaperManager;
|
||||
@@ -124,13 +122,12 @@ public class ShortcutAndWidgetContainer extends ViewGroup implements FolderIcon.
|
||||
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
|
||||
if (child instanceof NavigableAppWidgetHostView) {
|
||||
DeviceProfile profile = mActivity.getDeviceProfile();
|
||||
((NavigableAppWidgetHostView) child).getWidgetInset(profile, mTempRect);
|
||||
final PointF appWidgetScale = profile.getAppWidgetScale((ItemInfo) child.getTag());
|
||||
lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
|
||||
appWidgetScale.x, appWidgetScale.y, mBorderSpace, mTempRect);
|
||||
appWidgetScale.x, appWidgetScale.y, mBorderSpace, profile.widgetPadding);
|
||||
} else {
|
||||
lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
|
||||
mBorderSpace, null);
|
||||
mBorderSpace);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,13 +146,12 @@ public class ShortcutAndWidgetContainer extends ViewGroup implements FolderIcon.
|
||||
final DeviceProfile dp = mActivity.getDeviceProfile();
|
||||
|
||||
if (child instanceof NavigableAppWidgetHostView) {
|
||||
((NavigableAppWidgetHostView) child).getWidgetInset(dp, mTempRect);
|
||||
final PointF appWidgetScale = dp.getAppWidgetScale((ItemInfo) child.getTag());
|
||||
lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
|
||||
appWidgetScale.x, appWidgetScale.y, mBorderSpace, mTempRect);
|
||||
appWidgetScale.x, appWidgetScale.y, mBorderSpace, dp.widgetPadding);
|
||||
} else {
|
||||
lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
|
||||
mBorderSpace, null);
|
||||
mBorderSpace);
|
||||
// Center the icon/folder
|
||||
int cHeight = getCellContentHeight();
|
||||
int cellPaddingY = dp.isScalableGrid && mContainerType == WORKSPACE
|
||||
|
||||
@@ -2912,9 +2912,8 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
|
||||
Rect r = estimateItemPosition(layout, targetCell[0], targetCell[1], spanX, spanY);
|
||||
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
|
||||
DeviceProfile profile = mLauncher.getDeviceProfile();
|
||||
if (profile.shouldInsetWidgets() && finalView instanceof NavigableAppWidgetHostView) {
|
||||
Rect widgetPadding = new Rect();
|
||||
((NavigableAppWidgetHostView) finalView).getWidgetInset(profile, widgetPadding);
|
||||
if (finalView instanceof NavigableAppWidgetHostView) {
|
||||
Rect widgetPadding = profile.widgetPadding;
|
||||
r.left -= widgetPadding.left;
|
||||
r.right += widgetPadding.right;
|
||||
r.top -= widgetPadding.top;
|
||||
|
||||
@@ -113,13 +113,13 @@ public class CellLayoutLayoutParams extends ViewGroup.MarginLayoutParams {
|
||||
* full/invariant device profile sizes.
|
||||
*/
|
||||
public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
|
||||
int rowCount, Point borderSpace, @Nullable Rect inset) {
|
||||
int rowCount, Point borderSpace) {
|
||||
setup(cellWidth, cellHeight, invertHorizontally, colCount, rowCount, 1.0f, 1.0f,
|
||||
borderSpace, inset);
|
||||
borderSpace, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method, as opposed to {@link #setup(int, int, boolean, int, int, Point, Rect)},
|
||||
* Use this method, as opposed to {@link #setup(int, int, boolean, int, int, Point)},
|
||||
* if the view needs to be scaled.
|
||||
*
|
||||
* ie. In multi-window mode, we setup widgets so that they are measured and laid out
|
||||
@@ -150,10 +150,10 @@ public class CellLayoutLayoutParams extends ViewGroup.MarginLayoutParams {
|
||||
y = topMargin + (myCellY * cellHeight) + (myCellY * borderSpace.y);
|
||||
|
||||
if (inset != null) {
|
||||
x -= inset.left;
|
||||
y -= inset.top;
|
||||
width += inset.left + inset.right;
|
||||
height += inset.top + inset.bottom;
|
||||
x += inset.left;
|
||||
y += inset.top;
|
||||
width -= inset.left + inset.right;
|
||||
height -= inset.top + inset.bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,6 +314,12 @@ public final class FeatureFlags {
|
||||
"Enables receiving unfold animation events from sysui instead of calculating "
|
||||
+ "them in launcher process using hinge sensor values.");
|
||||
|
||||
public static final BooleanFlag PREEMPTIVE_UNFOLD_ANIMATION_START = getDebugFlag(270397209,
|
||||
"PREEMPTIVE_UNFOLD_ANIMATION_START", ENABLED,
|
||||
"Enables starting the unfold animation preemptively when unfolding, without"
|
||||
+ "waiting for SystemUI and then merging the SystemUI progress whenever we "
|
||||
+ "start receiving the events");
|
||||
|
||||
// TODO(Block 23): Clean up flags
|
||||
public static final BooleanFlag ENABLE_GRID_ONLY_OVERVIEW = getDebugFlag(270397206,
|
||||
"ENABLE_GRID_ONLY_OVERVIEW", DISABLED,
|
||||
|
||||
@@ -451,26 +451,8 @@ public class LauncherPreviewRenderer extends ContextWrapper
|
||||
final Size origSize = WidgetSizes.getWidgetSizePx(mDpOrig,
|
||||
launcherWidgetSize.getWidth(), launcherWidgetSize.getHeight());
|
||||
final Size newSize = WidgetSizes.getWidgetSizePx(mDp, info.spanX, info.spanY);
|
||||
final Rect previewInset = new Rect();
|
||||
final Rect origInset = new Rect();
|
||||
// When the setup() is called for the LayoutParams, insets are added to the width
|
||||
// and height of the view. This is not accounted for in WidgetSizes and is handled
|
||||
// here.
|
||||
if (mDp.shouldInsetWidgets()) {
|
||||
previewInset.set(mDp.inv.defaultWidgetPadding);
|
||||
} else {
|
||||
previewInset.setEmpty();
|
||||
}
|
||||
if (mDpOrig.shouldInsetWidgets()) {
|
||||
origInset.set(mDpOrig.inv.defaultWidgetPadding);
|
||||
} else {
|
||||
origInset.setEmpty();
|
||||
}
|
||||
|
||||
return new PointF((float) newSize.getWidth() / (origSize.getWidth()
|
||||
+ origInset.left + origInset.right),
|
||||
(float) newSize.getHeight() / (origSize.getHeight()
|
||||
+ origInset.top + origInset.bottom));
|
||||
return new PointF((float) newSize.getWidth() / origSize.getWidth(),
|
||||
(float) newSize.getHeight() / origSize.getHeight());
|
||||
}
|
||||
|
||||
private void inflateAndAddPredictedIcon(WorkspaceItemInfo info) {
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.launcher3.graphics;
|
||||
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
|
||||
@@ -53,8 +52,6 @@ import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext;
|
||||
import com.android.launcher3.model.BgDataModel;
|
||||
import com.android.launcher3.model.GridSizeMigrationUtil;
|
||||
import com.android.launcher3.model.LoaderTask;
|
||||
import com.android.launcher3.model.ModelDbController;
|
||||
import com.android.launcher3.provider.LauncherDbUtils;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.RunnableList;
|
||||
import com.android.launcher3.util.Themes;
|
||||
@@ -148,9 +145,7 @@ public class PreviewSurfaceRenderer {
|
||||
final String query = LauncherSettings.Favorites.ITEM_TYPE + " = "
|
||||
+ LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
|
||||
|
||||
ModelDbController mainController =
|
||||
LauncherAppState.getInstance(mContext).getModel().getModelDbController();
|
||||
try (Cursor c = mainController.query(TABLE_NAME,
|
||||
try (Cursor c = context.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
|
||||
new String[] {
|
||||
LauncherSettings.Favorites.APPWIDGET_ID,
|
||||
LauncherSettings.Favorites.SPANX,
|
||||
@@ -195,6 +190,8 @@ public class PreviewSurfaceRenderer {
|
||||
|
||||
@WorkerThread
|
||||
private void loadModelData() {
|
||||
final boolean migrated = doGridMigrationIfNecessary();
|
||||
|
||||
final Context inflationContext;
|
||||
if (mWallpaperColors != null) {
|
||||
// Create a themed context, without affecting the main application context
|
||||
@@ -212,20 +209,8 @@ public class PreviewSurfaceRenderer {
|
||||
Themes.getActivityThemeRes(mContext));
|
||||
}
|
||||
|
||||
if (GridSizeMigrationUtil.needsToMigrate(inflationContext, mIdp)) {
|
||||
// Start the migration
|
||||
if (migrated) {
|
||||
PreviewContext previewContext = new PreviewContext(inflationContext, mIdp);
|
||||
// Copy existing data to preview DB
|
||||
LauncherDbUtils.copyTable(LauncherAppState.getInstance(mContext)
|
||||
.getModel().getModelDbController().getDb(),
|
||||
TABLE_NAME,
|
||||
LauncherAppState.getInstance(previewContext)
|
||||
.getModel().getModelDbController().getDb(),
|
||||
TABLE_NAME,
|
||||
mContext);
|
||||
LauncherAppState.getInstance(previewContext)
|
||||
.getModel().getModelDbController().clearEmptyDbFlag();
|
||||
|
||||
new LoaderTask(
|
||||
LauncherAppState.getInstance(previewContext),
|
||||
/* bgAllAppsList= */ null,
|
||||
@@ -244,7 +229,8 @@ public class PreviewSurfaceRenderer {
|
||||
query += " or " + LauncherSettings.Favorites.SCREEN + " = "
|
||||
+ Workspace.SECOND_SCREEN_ID;
|
||||
}
|
||||
loadWorkspace(new ArrayList<>(), query, null);
|
||||
loadWorkspaceForPreviewSurfaceRenderer(new ArrayList<>(),
|
||||
LauncherSettings.Favorites.PREVIEW_CONTENT_URI, query);
|
||||
|
||||
final SparseArray<Size> spanInfo =
|
||||
getLoadedLauncherWidgetInfo(previewContext.getBaseContext());
|
||||
@@ -267,6 +253,14 @@ public class PreviewSurfaceRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private boolean doGridMigrationIfNecessary() {
|
||||
if (!GridSizeMigrationUtil.needsToMigrate(mContext, mIdp)) {
|
||||
return false;
|
||||
}
|
||||
return GridSizeMigrationUtil.migrateGridIfNeeded(mContext, mIdp);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void renderView(Context inflationContext, BgDataModel dataModel,
|
||||
Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap,
|
||||
|
||||
@@ -21,11 +21,15 @@ open class StartupLatencyLogger(val latencyType: LatencyType) {
|
||||
const val UNSET_LONG = -1L
|
||||
}
|
||||
|
||||
@VisibleForTesting val startTimeByEvent = SparseLongArray()
|
||||
@VisibleForTesting val endTimeByEvent = SparseLongArray()
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
val startTimeByEvent = SparseLongArray()
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
val endTimeByEvent = SparseLongArray()
|
||||
|
||||
@VisibleForTesting var cardinality: Int = UNSET_INT
|
||||
@VisibleForTesting var workspaceLoadStartTime: Long = UNSET_LONG
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
var cardinality: Int = UNSET_INT
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
var workspaceLoadStartTime: Long = UNSET_LONG
|
||||
|
||||
private var isInTest = false
|
||||
|
||||
@@ -116,7 +120,7 @@ open class StartupLatencyLogger(val latencyType: LatencyType) {
|
||||
return true
|
||||
}
|
||||
if (startTimeByEvent.contains(event.id)) {
|
||||
Log.e(TAG, String.format("Cannot restart same %s event", event.name))
|
||||
Log.e(TAG, "Cannot restart same ${event.name} event")
|
||||
return false
|
||||
} else if (
|
||||
startTimeByEvent.isEmpty() &&
|
||||
@@ -124,10 +128,8 @@ open class StartupLatencyLogger(val latencyType: LatencyType) {
|
||||
) {
|
||||
Log.e(
|
||||
TAG,
|
||||
String.format(
|
||||
"The first log start event must be %s.",
|
||||
LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION.name
|
||||
)
|
||||
"The first log start event must be " +
|
||||
"${LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION.name}.",
|
||||
)
|
||||
return false
|
||||
} else if (
|
||||
@@ -138,11 +140,7 @@ open class StartupLatencyLogger(val latencyType: LatencyType) {
|
||||
) {
|
||||
Log.e(
|
||||
TAG,
|
||||
String.format(
|
||||
"Cannot start %s event after %s starts",
|
||||
LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC.name,
|
||||
LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC.name
|
||||
)
|
||||
"Cannot start ${LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC.name} event after ${LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC.name} starts",
|
||||
)
|
||||
return false
|
||||
} else if (
|
||||
@@ -153,11 +151,7 @@ open class StartupLatencyLogger(val latencyType: LatencyType) {
|
||||
) {
|
||||
Log.e(
|
||||
TAG,
|
||||
String.format(
|
||||
"Cannot start %s event after %s starts",
|
||||
LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC.name,
|
||||
LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC.name
|
||||
)
|
||||
"Cannot start ${LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC.name} event after ${LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC.name} starts",
|
||||
)
|
||||
return false
|
||||
}
|
||||
@@ -172,10 +166,10 @@ open class StartupLatencyLogger(val latencyType: LatencyType) {
|
||||
return true
|
||||
}
|
||||
if (!startTimeByEvent.contains(event.id)) {
|
||||
Log.e(TAG, String.format("Cannot end %s event before starting it", event.name))
|
||||
Log.e(TAG, "Cannot end ${event.name} event before starting it")
|
||||
return false
|
||||
} else if (endTimeByEvent.contains(event.id)) {
|
||||
Log.e(TAG, String.format("Cannot end same %s event again", event.name))
|
||||
Log.e(TAG, "Cannot end same ${event.name} event again")
|
||||
return false
|
||||
} else if (
|
||||
event != LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION &&
|
||||
@@ -185,11 +179,7 @@ open class StartupLatencyLogger(val latencyType: LatencyType) {
|
||||
) {
|
||||
Log.e(
|
||||
TAG,
|
||||
String.format(
|
||||
"Cannot end %s event after %s",
|
||||
event.name,
|
||||
LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION.name
|
||||
)
|
||||
"Cannot end ${event.name} event after ${LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION.name}",
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
*/
|
||||
package com.android.launcher3.model;
|
||||
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.addTableToDb;
|
||||
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
|
||||
import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
@@ -36,6 +36,9 @@ 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.LauncherSettings;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.Utilities;
|
||||
@@ -55,7 +58,6 @@ import java.io.File;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.function.ToLongFunction;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -74,23 +76,45 @@ public class DatabaseHelper extends NoLocaleSQLiteHelper implements
|
||||
private static final boolean LOGD = false;
|
||||
|
||||
private static final String DOWNGRADE_SCHEMA_FILE = "downgrade_schema.json";
|
||||
public static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
|
||||
|
||||
private final Context mContext;
|
||||
private final ToLongFunction<UserHandle> mUserSerialProvider;
|
||||
private final Runnable mOnEmptyDbCreateCallback;
|
||||
|
||||
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,
|
||||
ToLongFunction<UserHandle> userSerialProvider, Runnable onEmptyDbCreateCallback) {
|
||||
public DatabaseHelper(Context context, String dbName, boolean forMigration) {
|
||||
super(context, dbName, SCHEMA_VERSION);
|
||||
mContext = context;
|
||||
mUserSerialProvider = userSerialProvider;
|
||||
mOnEmptyDbCreateCallback = onEmptyDbCreateCallback;
|
||||
mForMigration = forMigration;
|
||||
}
|
||||
|
||||
protected void initIds() {
|
||||
@@ -107,11 +131,13 @@ public class DatabaseHelper extends NoLocaleSQLiteHelper implements
|
||||
|
||||
mMaxItemId = 1;
|
||||
|
||||
addTableToDb(db, getDefaultUserSerial(), false /* optional */);
|
||||
addFavoritesTable(db, false);
|
||||
|
||||
// Fresh and clean launcher DB.
|
||||
mMaxItemId = initializeMaxItemId(db);
|
||||
mOnEmptyDbCreateCallback.run();
|
||||
if (!mForMigration) {
|
||||
onEmptyDbCreated();
|
||||
}
|
||||
}
|
||||
|
||||
public void onAddOrDeleteOp(SQLiteDatabase db) {
|
||||
@@ -121,8 +147,38 @@ public class DatabaseHelper extends NoLocaleSQLiteHelper implements
|
||||
}
|
||||
}
|
||||
|
||||
private long getDefaultUserSerial() {
|
||||
return mUserSerialProvider.applyAsLong(Process.myUserHandle());
|
||||
/**
|
||||
* 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(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
|
||||
|
||||
@@ -16,9 +16,6 @@
|
||||
|
||||
package com.android.launcher3.model;
|
||||
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.TMP_TABLE;
|
||||
import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
|
||||
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
|
||||
|
||||
import android.content.ComponentName;
|
||||
@@ -37,15 +34,16 @@ import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.graphics.LauncherPreviewRenderer;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.pm.InstallSessionHelper;
|
||||
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
|
||||
import com.android.launcher3.util.GridOccupancy;
|
||||
import com.android.launcher3.util.IntArray;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
|
||||
import com.android.launcher3.widget.WidgetManagerHelper;
|
||||
|
||||
@@ -91,38 +89,81 @@ public class GridSizeMigrationUtil {
|
||||
return needsToMigrate;
|
||||
}
|
||||
|
||||
/** See {@link #migrateGridIfNeeded(Context, InvariantDeviceProfile)} */
|
||||
public static boolean migrateGridIfNeeded(Context context) {
|
||||
if (context instanceof LauncherPreviewRenderer.PreviewContext) {
|
||||
return true;
|
||||
}
|
||||
return migrateGridIfNeeded(context, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* When migrating the grid, we copy the table
|
||||
* {@link LauncherSettings.Favorites#TABLE_NAME} from {@code source} into
|
||||
* {@link LauncherSettings.Favorites#TMP_TABLE}, run the grid size migration algorithm
|
||||
* When migrating the grid for preview, we copy the table
|
||||
* {@link LauncherSettings.Favorites#TABLE_NAME} into
|
||||
* {@link LauncherSettings.Favorites#PREVIEW_TABLE_NAME}, run grid size migration from the
|
||||
* former to the later, then use the later table for preview.
|
||||
*
|
||||
* Similarly when doing the actual grid migration, the former grid option's table
|
||||
* {@link LauncherSettings.Favorites#TABLE_NAME} is copied into the new grid option's
|
||||
* {@link LauncherSettings.Favorites#TMP_TABLE}, we then run the grid size migration algorithm
|
||||
* to migrate the later to the former, and load the workspace from the default
|
||||
* {@link LauncherSettings.Favorites#TABLE_NAME}.
|
||||
*
|
||||
* @return false if the migration failed.
|
||||
*/
|
||||
public static boolean migrateGridIfNeeded(
|
||||
@NonNull Context context,
|
||||
@NonNull InvariantDeviceProfile idp,
|
||||
@NonNull DatabaseHelper target,
|
||||
@NonNull SQLiteDatabase source) {
|
||||
public static boolean migrateGridIfNeeded(Context context, InvariantDeviceProfile idp) {
|
||||
boolean migrateForPreview = idp != null;
|
||||
if (!migrateForPreview) {
|
||||
idp = LauncherAppState.getIDP(context);
|
||||
}
|
||||
|
||||
DeviceGridState srcDeviceState = new DeviceGridState(context);
|
||||
DeviceGridState destDeviceState = new DeviceGridState(idp);
|
||||
if (!needsToMigrate(srcDeviceState, destDeviceState)) {
|
||||
return true;
|
||||
}
|
||||
copyTable(source, TABLE_NAME, target.getWritableDatabase(), TMP_TABLE, context);
|
||||
|
||||
HashSet<String> validPackages = getValidPackages(context);
|
||||
|
||||
if (migrateForPreview) {
|
||||
if (!LauncherSettings.Settings.call(
|
||||
context.getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_PREP_FOR_PREVIEW,
|
||||
destDeviceState.getDbFile()).getBoolean(
|
||||
LauncherSettings.Settings.EXTRA_VALUE)) {
|
||||
return false;
|
||||
}
|
||||
} else if (!LauncherSettings.Settings.call(
|
||||
context.getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_UPDATE_CURRENT_OPEN_HELPER,
|
||||
destDeviceState.getDbFile()).getBoolean(
|
||||
LauncherSettings.Settings.EXTRA_VALUE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
long migrationStartTime = System.currentTimeMillis();
|
||||
try (SQLiteTransaction t = new SQLiteTransaction(target.getWritableDatabase())) {
|
||||
DbReader srcReader = new DbReader(t.getDb(), TMP_TABLE, context, validPackages);
|
||||
DbReader destReader = new DbReader(t.getDb(), TABLE_NAME, context, validPackages);
|
||||
try (SQLiteTransaction t = (SQLiteTransaction) LauncherSettings.Settings.call(
|
||||
context.getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_NEW_TRANSACTION).getBinder(
|
||||
LauncherSettings.Settings.EXTRA_VALUE)) {
|
||||
|
||||
DbReader srcReader = new DbReader(t.getDb(),
|
||||
migrateForPreview ? LauncherSettings.Favorites.TABLE_NAME
|
||||
: LauncherSettings.Favorites.TMP_TABLE,
|
||||
context, validPackages);
|
||||
DbReader destReader = new DbReader(t.getDb(),
|
||||
migrateForPreview ? LauncherSettings.Favorites.PREVIEW_TABLE_NAME
|
||||
: LauncherSettings.Favorites.TABLE_NAME,
|
||||
context, validPackages);
|
||||
|
||||
Point targetSize = new Point(destDeviceState.getColumns(), destDeviceState.getRows());
|
||||
migrate(target, srcReader, destReader, destDeviceState.getNumHotseat(),
|
||||
migrate(context, t.getDb(), srcReader, destReader, destDeviceState.getNumHotseat(),
|
||||
targetSize, srcDeviceState, destDeviceState);
|
||||
dropTable(t.getDb(), TMP_TABLE);
|
||||
|
||||
if (!migrateForPreview) {
|
||||
dropTable(t.getDb(), LauncherSettings.Favorites.TMP_TABLE);
|
||||
}
|
||||
|
||||
t.commit();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
@@ -133,7 +174,7 @@ public class GridSizeMigrationUtil {
|
||||
Log.v(TAG, "Workspace migration completed in "
|
||||
+ (System.currentTimeMillis() - migrationStartTime));
|
||||
|
||||
if (!(context instanceof SandboxContext)) {
|
||||
if (!migrateForPreview) {
|
||||
// Save current configuration, so that the migration does not run again.
|
||||
destDeviceState.writeToPrefs(context);
|
||||
}
|
||||
@@ -141,7 +182,7 @@ public class GridSizeMigrationUtil {
|
||||
}
|
||||
|
||||
public static boolean migrate(
|
||||
@NonNull DatabaseHelper helper,
|
||||
@NonNull final Context context, @NonNull final SQLiteDatabase db,
|
||||
@NonNull final DbReader srcReader, @NonNull final DbReader destReader,
|
||||
final int destHotseatSize, @NonNull final Point targetSize,
|
||||
@NonNull final DeviceGridState srcDeviceState,
|
||||
@@ -193,8 +234,8 @@ public class GridSizeMigrationUtil {
|
||||
Collections.sort(workspaceToBeAdded);
|
||||
|
||||
// Migrate hotseat
|
||||
solveHotseatPlacement(helper, destHotseatSize,
|
||||
srcReader, destReader, dstHotseatItems, hotseatToBeAdded);
|
||||
solveHotseatPlacement(db, srcReader,
|
||||
destReader, context, destHotseatSize, dstHotseatItems, hotseatToBeAdded);
|
||||
|
||||
// Migrate workspace.
|
||||
// First we create a collection of the screens
|
||||
@@ -214,8 +255,8 @@ public class GridSizeMigrationUtil {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Migrating " + screenId);
|
||||
}
|
||||
solveGridPlacement(helper, srcReader,
|
||||
destReader, screenId, trgX, trgY, workspaceToBeAdded, false);
|
||||
solveGridPlacement(db, srcReader,
|
||||
destReader, context, screenId, trgX, trgY, workspaceToBeAdded, false);
|
||||
if (workspaceToBeAdded.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
@@ -225,8 +266,8 @@ public class GridSizeMigrationUtil {
|
||||
// any of the screens, in this case we add them to new screens until all of them are placed.
|
||||
int screenId = destReader.mLastScreenId + 1;
|
||||
while (!workspaceToBeAdded.isEmpty()) {
|
||||
solveGridPlacement(helper, srcReader,
|
||||
destReader, screenId, trgX, trgY, workspaceToBeAdded, preservePages);
|
||||
solveGridPlacement(db, srcReader,
|
||||
destReader, context, screenId, trgX, trgY, workspaceToBeAdded, preservePages);
|
||||
screenId++;
|
||||
}
|
||||
|
||||
@@ -257,33 +298,33 @@ public class GridSizeMigrationUtil {
|
||||
});
|
||||
}
|
||||
|
||||
private static void insertEntryInDb(DatabaseHelper helper, DbEntry entry,
|
||||
private static void insertEntryInDb(SQLiteDatabase db, Context context, DbEntry entry,
|
||||
String srcTableName, String destTableName) {
|
||||
int id = copyEntryAndUpdate(helper, entry, srcTableName, destTableName);
|
||||
int id = copyEntryAndUpdate(db, context, entry, srcTableName, destTableName);
|
||||
|
||||
if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
|
||||
for (Set<Integer> itemIds : entry.mFolderItems.values()) {
|
||||
for (int itemId : itemIds) {
|
||||
copyEntryAndUpdate(helper, itemId, id, srcTableName, destTableName);
|
||||
copyEntryAndUpdate(db, context, itemId, id, srcTableName, destTableName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int copyEntryAndUpdate(DatabaseHelper helper,
|
||||
private static int copyEntryAndUpdate(SQLiteDatabase db, Context context,
|
||||
DbEntry entry, String srcTableName, String destTableName) {
|
||||
return copyEntryAndUpdate(helper, entry, -1, -1, srcTableName, destTableName);
|
||||
return copyEntryAndUpdate(db, context, entry, -1, -1, srcTableName, destTableName);
|
||||
}
|
||||
|
||||
private static int copyEntryAndUpdate(DatabaseHelper helper,
|
||||
private static int copyEntryAndUpdate(SQLiteDatabase db, Context context,
|
||||
int id, int folderId, String srcTableName, String destTableName) {
|
||||
return copyEntryAndUpdate(helper, null, id, folderId, srcTableName, destTableName);
|
||||
return copyEntryAndUpdate(db, context, null, id, folderId, srcTableName, destTableName);
|
||||
}
|
||||
|
||||
private static int copyEntryAndUpdate(DatabaseHelper helper, DbEntry entry,
|
||||
int id, int folderId, String srcTableName, String destTableName) {
|
||||
private static int copyEntryAndUpdate(SQLiteDatabase db, Context context,
|
||||
DbEntry entry, int id, int folderId, String srcTableName, String destTableName) {
|
||||
int newId = -1;
|
||||
Cursor c = helper.getWritableDatabase().query(srcTableName, null,
|
||||
Cursor c = db.query(srcTableName, null,
|
||||
LauncherSettings.Favorites._ID + " = '" + (entry != null ? entry.id : id) + "'",
|
||||
null, null, null, null);
|
||||
while (c.moveToNext()) {
|
||||
@@ -294,9 +335,11 @@ public class GridSizeMigrationUtil {
|
||||
} else {
|
||||
values.put(LauncherSettings.Favorites.CONTAINER, folderId);
|
||||
}
|
||||
newId = helper.generateNewItemId();
|
||||
newId = LauncherSettings.Settings.call(context.getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_NEW_ITEM_ID).getInt(
|
||||
LauncherSettings.Settings.EXTRA_VALUE);
|
||||
values.put(LauncherSettings.Favorites._ID, newId);
|
||||
helper.getWritableDatabase().insert(destTableName, null, values);
|
||||
db.insert(destTableName, null, values);
|
||||
}
|
||||
c.close();
|
||||
return newId;
|
||||
@@ -324,9 +367,9 @@ public class GridSizeMigrationUtil {
|
||||
return validPackages;
|
||||
}
|
||||
|
||||
private static void solveGridPlacement(@NonNull final DatabaseHelper helper,
|
||||
private static void solveGridPlacement(@NonNull final SQLiteDatabase db,
|
||||
@NonNull final DbReader srcReader, @NonNull final DbReader destReader,
|
||||
final int screenId, final int trgX, final int trgY,
|
||||
@NonNull final Context context, final int screenId, final int trgX, final int trgY,
|
||||
@NonNull final List<DbEntry> sortedItemsToPlace, final boolean matchingScreenIdOnly) {
|
||||
final GridOccupancy occupied = new GridOccupancy(trgX, trgY);
|
||||
final Point trg = new Point(trgX, trgY);
|
||||
@@ -348,7 +391,7 @@ public class GridSizeMigrationUtil {
|
||||
continue;
|
||||
}
|
||||
if (findPlacementForEntry(entry, next, trg, occupied, screenId)) {
|
||||
insertEntryInDb(helper, entry, srcReader.mTableName, destReader.mTableName);
|
||||
insertEntryInDb(db, context, entry, srcReader.mTableName, destReader.mTableName);
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
@@ -385,9 +428,9 @@ public class GridSizeMigrationUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void solveHotseatPlacement(
|
||||
@NonNull final DatabaseHelper helper, final int hotseatSize,
|
||||
private static void solveHotseatPlacement(@NonNull final SQLiteDatabase db,
|
||||
@NonNull final DbReader srcReader, @NonNull final DbReader destReader,
|
||||
@NonNull final Context context, final int hotseatSize,
|
||||
@NonNull final List<DbEntry> placedHotseatItems,
|
||||
@NonNull final List<DbEntry> itemsToPlace) {
|
||||
|
||||
@@ -404,7 +447,7 @@ public class GridSizeMigrationUtil {
|
||||
// to something other than -1.
|
||||
entry.cellX = i;
|
||||
entry.cellY = 0;
|
||||
insertEntryInDb(helper, entry, srcReader.mTableName, destReader.mTableName);
|
||||
insertEntryInDb(db, context, entry, srcReader.mTableName, destReader.mTableName);
|
||||
occupied[entry.screenId] = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,16 +16,16 @@
|
||||
|
||||
package com.android.launcher3.model;
|
||||
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.LauncherActivityInfo;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.database.CursorWrapper;
|
||||
import android.net.Uri;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.BaseColumns;
|
||||
import android.text.TextUtils;
|
||||
@@ -66,7 +66,9 @@ public class LoaderCursor extends CursorWrapper {
|
||||
private final LongSparseArray<UserHandle> allUsers;
|
||||
|
||||
private final LauncherAppState mApp;
|
||||
private final Uri mContentUri;
|
||||
private final Context mContext;
|
||||
private final PackageManager mPM;
|
||||
private final IconCache mIconCache;
|
||||
private final InvariantDeviceProfile mIDP;
|
||||
|
||||
@@ -106,14 +108,17 @@ public class LoaderCursor extends CursorWrapper {
|
||||
public int itemType;
|
||||
public int restoreFlag;
|
||||
|
||||
public LoaderCursor(Cursor cursor, LauncherAppState app, UserManagerState userManagerState) {
|
||||
public LoaderCursor(Cursor cursor, Uri contentUri, LauncherAppState app,
|
||||
UserManagerState userManagerState) {
|
||||
super(cursor);
|
||||
|
||||
mApp = app;
|
||||
allUsers = userManagerState.allUsers;
|
||||
mContentUri = contentUri;
|
||||
mContext = app.getContext();
|
||||
mIconCache = app.getIconCache();
|
||||
mIDP = app.getInvariantDeviceProfile();
|
||||
mPM = mContext.getPackageManager();
|
||||
|
||||
// Init column indices
|
||||
mIconIndex = getColumnIndexOrThrow(Favorites.ICON);
|
||||
@@ -385,7 +390,7 @@ public class LoaderCursor extends CursorWrapper {
|
||||
*/
|
||||
public ContentWriter updater() {
|
||||
return new ContentWriter(mContext, new ContentWriter.CommitParams(
|
||||
mApp.getModel().getModelDbController(),
|
||||
mApp.getModel().getModelDbController().getDatabaseHelper(),
|
||||
BaseColumns._ID + "= ?", new String[]{Integer.toString(id)}));
|
||||
}
|
||||
|
||||
@@ -404,8 +409,8 @@ public class LoaderCursor extends CursorWrapper {
|
||||
public boolean commitDeleted() {
|
||||
if (mItemsToRemove.size() > 0) {
|
||||
// Remove dead items
|
||||
mApp.getModel().getModelDbController().delete(TABLE_NAME,
|
||||
Utilities.createDbSelectionQuery(Favorites._ID, mItemsToRemove), null);
|
||||
mContext.getContentResolver().delete(mContentUri, Utilities.createDbSelectionQuery(
|
||||
Favorites._ID, mItemsToRemove), null);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -430,8 +435,9 @@ public class LoaderCursor extends CursorWrapper {
|
||||
// Update restored items that no longer require special handling
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Favorites.RESTORED, 0);
|
||||
mApp.getModel().getModelDbController().update(TABLE_NAME, values,
|
||||
Utilities.createDbSelectionQuery(Favorites._ID, mRestoredRows), null);
|
||||
mContext.getContentResolver().update(mContentUri, values,
|
||||
Utilities.createDbSelectionQuery(
|
||||
Favorites._ID, mRestoredRows), null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.launcher3.model;
|
||||
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
|
||||
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
|
||||
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
|
||||
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
|
||||
@@ -42,6 +41,7 @@ import android.content.pm.PackageInstaller.SessionInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.graphics.Point;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Trace;
|
||||
import android.os.UserHandle;
|
||||
@@ -50,6 +50,7 @@ import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.util.LongSparseArray;
|
||||
import android.util.TimingLogger;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@@ -199,10 +200,25 @@ public class LoaderTask implements Runnable {
|
||||
}
|
||||
|
||||
Object traceToken = TraceHelper.INSTANCE.beginSection(TAG);
|
||||
TimingLogger timingLogger = new TimingLogger(TAG, "run");
|
||||
LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger();
|
||||
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
|
||||
List<ShortcutInfo> allShortcuts = new ArrayList<>();
|
||||
loadWorkspace(allShortcuts, "", memoryLogger);
|
||||
Trace.beginSection("LoadWorkspace");
|
||||
try {
|
||||
loadWorkspace(allShortcuts, memoryLogger);
|
||||
} finally {
|
||||
Trace.endSection();
|
||||
}
|
||||
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
|
||||
@@ -212,21 +228,21 @@ public class LoaderTask implements Runnable {
|
||||
verifyNotStopped();
|
||||
sanitizeFolders(mItemsDeleted);
|
||||
sanitizeWidgetsShortcutsAndPackages();
|
||||
logASplit("sanitizeData");
|
||||
logASplit(timingLogger, "sanitizeData");
|
||||
}
|
||||
|
||||
verifyNotStopped();
|
||||
mLauncherBinder.bindWorkspace(true /* incrementBindId */, /* isBindSync= */ false);
|
||||
logASplit("bindWorkspace");
|
||||
logASplit(timingLogger, "bindWorkspace");
|
||||
|
||||
mModelDelegate.workspaceLoadComplete();
|
||||
// Notify the installer packages of packages with active installs on the first screen.
|
||||
sendFirstScreenActiveInstallsBroadcast();
|
||||
logASplit("sendFirstScreenActiveInstallsBroadcast");
|
||||
logASplit(timingLogger, "sendFirstScreenActiveInstallsBroadcast");
|
||||
|
||||
// Take a break
|
||||
waitForIdle();
|
||||
logASplit("step 1 complete");
|
||||
logASplit(timingLogger, "step 1 complete");
|
||||
verifyNotStopped();
|
||||
|
||||
// second step
|
||||
@@ -237,16 +253,16 @@ public class LoaderTask implements Runnable {
|
||||
} finally {
|
||||
Trace.endSection();
|
||||
}
|
||||
logASplit("loadAllApps");
|
||||
logASplit(timingLogger, "loadAllApps");
|
||||
|
||||
if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
|
||||
mModelDelegate.loadAndBindAllAppsItems(mUserManagerState,
|
||||
mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
|
||||
logASplit("allAppsDelegateItems");
|
||||
logASplit(timingLogger, "allAppsDelegateItems");
|
||||
}
|
||||
verifyNotStopped();
|
||||
mLauncherBinder.bindAllApps();
|
||||
logASplit("bindAllApps");
|
||||
logASplit(timingLogger, "bindAllApps");
|
||||
|
||||
verifyNotStopped();
|
||||
IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler();
|
||||
@@ -254,73 +270,75 @@ public class LoaderTask implements Runnable {
|
||||
updateHandler.updateIcons(allActivityList,
|
||||
LauncherActivityCachingLogic.newInstance(mApp.getContext()),
|
||||
mApp.getModel()::onPackageIconsUpdated);
|
||||
logASplit("update icon cache");
|
||||
logASplit(timingLogger, "update icon cache");
|
||||
|
||||
verifyNotStopped();
|
||||
logASplit("save shortcuts in icon cache");
|
||||
logASplit(timingLogger, "save shortcuts in icon cache");
|
||||
updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(),
|
||||
mApp.getModel()::onPackageIconsUpdated);
|
||||
|
||||
// Take a break
|
||||
waitForIdle();
|
||||
logASplit("step 2 complete");
|
||||
logASplit(timingLogger, "step 2 complete");
|
||||
verifyNotStopped();
|
||||
|
||||
// third step
|
||||
List<ShortcutInfo> allDeepShortcuts = loadDeepShortcuts();
|
||||
logASplit("loadDeepShortcuts");
|
||||
logASplit(timingLogger, "loadDeepShortcuts");
|
||||
|
||||
verifyNotStopped();
|
||||
mLauncherBinder.bindDeepShortcuts();
|
||||
logASplit("bindDeepShortcuts");
|
||||
logASplit(timingLogger, "bindDeepShortcuts");
|
||||
|
||||
verifyNotStopped();
|
||||
logASplit("save deep shortcuts in icon cache");
|
||||
logASplit(timingLogger, "save deep shortcuts in icon cache");
|
||||
updateHandler.updateIcons(allDeepShortcuts,
|
||||
new ShortcutCachingLogic(), (pkgs, user) -> { });
|
||||
|
||||
// Take a break
|
||||
waitForIdle();
|
||||
logASplit("step 3 complete");
|
||||
logASplit(timingLogger, "step 3 complete");
|
||||
verifyNotStopped();
|
||||
|
||||
// fourth step
|
||||
List<ComponentWithLabelAndIcon> allWidgetsList =
|
||||
mBgDataModel.widgetsModel.update(mApp, null);
|
||||
logASplit("load widgets");
|
||||
logASplit(timingLogger, "load widgets");
|
||||
|
||||
verifyNotStopped();
|
||||
mLauncherBinder.bindWidgets();
|
||||
logASplit("bindWidgets");
|
||||
logASplit(timingLogger, "bindWidgets");
|
||||
verifyNotStopped();
|
||||
|
||||
if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
|
||||
mModelDelegate.loadAndBindOtherItems(mLauncherBinder.mCallbacksList);
|
||||
logASplit("otherDelegateItems");
|
||||
logASplit(timingLogger, "otherDelegateItems");
|
||||
verifyNotStopped();
|
||||
}
|
||||
|
||||
updateHandler.updateIcons(allWidgetsList,
|
||||
new ComponentWithIconCachingLogic(mApp.getContext(), true),
|
||||
mApp.getModel()::onWidgetLabelsUpdated);
|
||||
logASplit("save widgets in icon cache");
|
||||
logASplit(timingLogger, "save widgets in icon cache");
|
||||
|
||||
// fifth step
|
||||
loadFolderNames();
|
||||
|
||||
verifyNotStopped();
|
||||
updateHandler.finish();
|
||||
logASplit("finish icon update");
|
||||
logASplit(timingLogger, "finish icon update");
|
||||
|
||||
mModelDelegate.modelLoadComplete();
|
||||
transaction.commit();
|
||||
memoryLogger.clearLogs();
|
||||
} catch (CancellationException e) {
|
||||
// Loader stopped, ignore
|
||||
logASplit("Cancelled");
|
||||
logASplit(timingLogger, "Cancelled");
|
||||
} catch (Exception e) {
|
||||
memoryLogger.printLogs();
|
||||
throw e;
|
||||
} finally {
|
||||
timingLogger.dumpToLog();
|
||||
}
|
||||
TraceHelper.INSTANCE.endSection(traceToken);
|
||||
}
|
||||
@@ -330,29 +348,25 @@ public class LoaderTask implements Runnable {
|
||||
this.notify();
|
||||
}
|
||||
|
||||
protected void loadWorkspace(
|
||||
List<ShortcutInfo> allDeepShortcuts,
|
||||
String selection,
|
||||
LoaderMemoryLogger memoryLogger) {
|
||||
Trace.beginSection("LoadWorkspace");
|
||||
try {
|
||||
loadWorkspaceImpl(allDeepShortcuts, selection, memoryLogger);
|
||||
} finally {
|
||||
Trace.endSection();
|
||||
}
|
||||
logASplit("loadWorkspace");
|
||||
private void loadWorkspace(
|
||||
List<ShortcutInfo> allDeepShortcuts, LoaderMemoryLogger memoryLogger) {
|
||||
loadWorkspace(allDeepShortcuts, Favorites.CONTENT_URI,
|
||||
null /* selection */, memoryLogger);
|
||||
}
|
||||
|
||||
protected void loadWorkspaceForPreviewSurfaceRenderer(
|
||||
List<ShortcutInfo> allDeepShortcuts, Uri contentUri, String selection) {
|
||||
loadWorkspace(allDeepShortcuts, contentUri, selection, null);
|
||||
if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
|
||||
verifyNotStopped();
|
||||
mModelDelegate.loadAndBindWorkspaceItems(mUserManagerState,
|
||||
mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
|
||||
mModelDelegate.markActive();
|
||||
logASplit("workspaceDelegateItems");
|
||||
}
|
||||
}
|
||||
|
||||
private void loadWorkspaceImpl(
|
||||
protected void loadWorkspace(
|
||||
List<ShortcutInfo> allDeepShortcuts,
|
||||
Uri contentUri,
|
||||
String selection,
|
||||
@Nullable LoaderMemoryLogger memoryLogger) {
|
||||
final Context context = mApp.getContext();
|
||||
@@ -363,7 +377,7 @@ public class LoaderTask implements Runnable {
|
||||
final WidgetManagerHelper widgetHelper = new WidgetManagerHelper(context);
|
||||
|
||||
boolean clearDb = false;
|
||||
if (!mApp.getModel().getModelDbController().migrateGridIfNeeded()) {
|
||||
if (!GridSizeMigrationUtil.migrateGridIfNeeded(context)) {
|
||||
// Migration failed. Clear workspace.
|
||||
clearDb = true;
|
||||
}
|
||||
@@ -388,9 +402,8 @@ public class LoaderTask implements Runnable {
|
||||
mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs);
|
||||
|
||||
mShortcutKeyToPinnedShortcuts = new HashMap<>();
|
||||
ModelDbController dbController = mApp.getModel().getModelDbController();
|
||||
final LoaderCursor c = new LoaderCursor(
|
||||
dbController.query(TABLE_NAME, null, selection, null, null),
|
||||
contentResolver.query(contentUri, null, selection, null, null), contentUri,
|
||||
mApp, mUserManagerState);
|
||||
final Bundle extras = c.getExtras();
|
||||
mDbName = extras == null ? null : extras.getString(Settings.EXTRA_DB_NAME);
|
||||
@@ -1099,9 +1112,12 @@ public class LoaderTask implements Runnable {
|
||||
FileLog.d(TAG, widgetDimension.toString());
|
||||
}
|
||||
|
||||
private static void logASplit(String label) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, label);
|
||||
private static void logASplit(@Nullable TimingLogger timingLogger, String label) {
|
||||
if (timingLogger != null) {
|
||||
timingLogger.addSplit(label);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, label);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,11 @@ import static android.util.Base64.NO_PADDING;
|
||||
import static android.util.Base64.NO_WRAP;
|
||||
|
||||
import static com.android.launcher3.DefaultLayoutParser.RES_PARTNER_DEFAULT_LAYOUT;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.addTableToDb;
|
||||
import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_KEY;
|
||||
import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_LABEL;
|
||||
import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_TAG;
|
||||
import static com.android.launcher3.model.DatabaseHelper.EMPTY_DATABASE_CREATED;
|
||||
import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
|
||||
import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
|
||||
|
||||
import android.app.blob.BlobHandle;
|
||||
@@ -30,6 +31,7 @@ import android.app.blob.BlobStoreManager;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ProviderInfo;
|
||||
import android.content.res.Resources;
|
||||
@@ -41,7 +43,6 @@ import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Process;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
@@ -53,22 +54,18 @@ import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.android.launcher3.AutoInstallsLayout;
|
||||
import com.android.launcher3.AutoInstallsLayout.SourceResources;
|
||||
import com.android.launcher3.ConstantItem;
|
||||
import com.android.launcher3.DefaultLayoutParser;
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherFiles;
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.pm.UserCache;
|
||||
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.MainThreadInitializedObject.SandboxContext;
|
||||
import com.android.launcher3.util.Partner;
|
||||
import com.android.launcher3.widget.LauncherWidgetHolder;
|
||||
|
||||
@@ -76,6 +73,7 @@ import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.StringReader;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Utility class which maintains an instance of Launcher database and provides utility methods
|
||||
@@ -84,8 +82,6 @@ import java.io.StringReader;
|
||||
public class ModelDbController {
|
||||
private static final String TAG = "LauncherProvider";
|
||||
|
||||
private static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
|
||||
|
||||
protected DatabaseHelper mOpenHelper;
|
||||
|
||||
private final Context mContext;
|
||||
@@ -96,36 +92,26 @@ public class ModelDbController {
|
||||
|
||||
private synchronized void createDbIfNotExists() {
|
||||
if (mOpenHelper == null) {
|
||||
mOpenHelper = createDatabaseHelper(false /* forMigration */);
|
||||
RestoreDbTask.restoreIfNeeded(mContext, this);
|
||||
mOpenHelper = DatabaseHelper.createDatabaseHelper(
|
||||
mContext, false /* forMigration */);
|
||||
|
||||
RestoreDbTask.restoreIfNeeded(mContext, mOpenHelper);
|
||||
}
|
||||
}
|
||||
|
||||
protected DatabaseHelper createDatabaseHelper(boolean forMigration) {
|
||||
boolean isSandbox = mContext instanceof SandboxContext;
|
||||
String dbName = isSandbox ? null : InvariantDeviceProfile.INSTANCE.get(mContext).dbFile;
|
||||
|
||||
// Set the flag for empty DB
|
||||
Runnable onEmptyDbCreateCallback = forMigration ? () -> { }
|
||||
: () -> LauncherPrefs.get(mContext).putSync(getEmptyDbCreatedKey(dbName).to(true));
|
||||
|
||||
DatabaseHelper databaseHelper = new DatabaseHelper(mContext, dbName,
|
||||
this::getSerialNumberForUser, onEmptyDbCreateCallback);
|
||||
// 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.
|
||||
addTableToDb(databaseHelper.getWritableDatabase(),
|
||||
getSerialNumberForUser(Process.myUserHandle()),
|
||||
true /* optional */);
|
||||
private synchronized boolean prepForMigration(String dbFile, String targetTableName,
|
||||
Supplier<DatabaseHelper> src, Supplier<DatabaseHelper> dst) {
|
||||
if (TextUtils.equals(dbFile, mOpenHelper.getDatabaseName())) {
|
||||
Log.e(TAG, "prepForMigration - target db is same as current: " + dbFile);
|
||||
return false;
|
||||
}
|
||||
databaseHelper.mHotseatRestoreTableExists = tableExists(
|
||||
databaseHelper.getReadableDatabase(), Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
|
||||
|
||||
databaseHelper.initIds();
|
||||
return databaseHelper;
|
||||
final DatabaseHelper helper = src.get();
|
||||
mOpenHelper = dst.get();
|
||||
copyTable(helper.getReadableDatabase(), Favorites.TABLE_NAME,
|
||||
mOpenHelper.getWritableDatabase(), targetTableName, mContext);
|
||||
helper.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -281,38 +267,42 @@ public class ModelDbController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates the DB if needed, and returns false if the migration failed
|
||||
* and DB needs to be cleared.
|
||||
* Updates the current DB and copies all the existing data to the temp table
|
||||
* @param dbFile name of the target db file name
|
||||
*/
|
||||
public boolean migrateGridIfNeeded() {
|
||||
InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
|
||||
if (!GridSizeMigrationUtil.needsToMigrate(mContext, idp)) {
|
||||
return true;
|
||||
}
|
||||
String targetDbName = new DeviceGridState(idp).getDbFile();
|
||||
if (TextUtils.equals(targetDbName, mOpenHelper.getDatabaseName())) {
|
||||
// DB is same, ignore
|
||||
return true;
|
||||
}
|
||||
DatabaseHelper oldHelper = mOpenHelper;
|
||||
mOpenHelper = (mContext instanceof SandboxContext) ? oldHelper
|
||||
: createDatabaseHelper(true /* forMigration */);
|
||||
try {
|
||||
return GridSizeMigrationUtil.migrateGridIfNeeded(mContext, idp, mOpenHelper,
|
||||
oldHelper.getWritableDatabase());
|
||||
} finally {
|
||||
if (mOpenHelper != oldHelper) {
|
||||
oldHelper.close();
|
||||
}
|
||||
}
|
||||
@WorkerThread
|
||||
public boolean updateCurrentOpenHelper(String dbFile) {
|
||||
createDbIfNotExists();
|
||||
return prepForMigration(
|
||||
dbFile,
|
||||
Favorites.TMP_TABLE,
|
||||
() -> mOpenHelper,
|
||||
() -> DatabaseHelper.createDatabaseHelper(
|
||||
mContext, true /* forMigration */));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying model database
|
||||
* Returns the current DatabaseHelper.
|
||||
* Only for tests
|
||||
*/
|
||||
public SQLiteDatabase getDb() {
|
||||
@WorkerThread
|
||||
public DatabaseHelper getDatabaseHelper() {
|
||||
createDbIfNotExists();
|
||||
return mOpenHelper.getWritableDatabase();
|
||||
return mOpenHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the DB for preview by copying all existing data to preview table
|
||||
*/
|
||||
@WorkerThread
|
||||
public boolean prepareForPreview(String dbFile) {
|
||||
createDbIfNotExists();
|
||||
return prepForMigration(
|
||||
dbFile,
|
||||
Favorites.PREVIEW_TABLE_NAME,
|
||||
() -> DatabaseHelper.createDatabaseHelper(
|
||||
mContext, dbFile, true /* forMigration */),
|
||||
() -> mOpenHelper);
|
||||
}
|
||||
|
||||
private void onAddOrDeleteOp(SQLiteDatabase db) {
|
||||
@@ -355,7 +345,8 @@ public class ModelDbController {
|
||||
}
|
||||
|
||||
private void clearFlagEmptyDbCreated() {
|
||||
LauncherPrefs.get(mContext).removeSync(getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()));
|
||||
LauncherPrefs.getPrefs(mContext).edit()
|
||||
.remove(mOpenHelper.getKey(EMPTY_DATABASE_CREATED)).commit();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -368,8 +359,9 @@ public class ModelDbController {
|
||||
@WorkerThread
|
||||
public synchronized void loadDefaultFavoritesIfNecessary() {
|
||||
createDbIfNotExists();
|
||||
SharedPreferences sp = LauncherPrefs.getPrefs(mContext);
|
||||
|
||||
if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()))) {
|
||||
if (sp.getBoolean(mOpenHelper.getKey(EMPTY_DATABASE_CREATED), false)) {
|
||||
Log.d(TAG, "loading default workspace");
|
||||
|
||||
LauncherWidgetHolder widgetHolder = mOpenHelper.newLauncherWidgetHolder();
|
||||
@@ -487,27 +479,4 @@ public class ModelDbController {
|
||||
return new DefaultLayoutParser(mContext, widgetHolder,
|
||||
mOpenHelper, mContext.getResources(), defaultLayout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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".
|
||||
*/
|
||||
private ConstantItem<Boolean> getEmptyDbCreatedKey(String dbName) {
|
||||
if (mContext instanceof SandboxContext) {
|
||||
return LauncherPrefs.nonRestorableItem(EMPTY_DATABASE_CREATED,
|
||||
false /* default value */, false /* boot aware */);
|
||||
}
|
||||
String key = TextUtils.equals(dbName, LauncherFiles.LAUNCHER_DB)
|
||||
? EMPTY_DATABASE_CREATED : EMPTY_DATABASE_CREATED + "@" + dbName;
|
||||
return LauncherPrefs.backedUpItem(key, false /* default value */, false /* boot aware */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the serial number for the provided user
|
||||
*/
|
||||
public long getSerialNumberForUser(UserHandle user) {
|
||||
return UserCache.INSTANCE.get(mContext).getSerialNumberForUser(user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ public class LauncherDbUtils {
|
||||
UserManagerState ums = new UserManagerState();
|
||||
ums.init(UserCache.INSTANCE.get(context),
|
||||
context.getSystemService(UserManager.class));
|
||||
LoaderCursor lc = new LoaderCursor(c, LauncherAppState.getInstance(context), ums);
|
||||
LoaderCursor lc = new LoaderCursor(c, null, LauncherAppState.getInstance(context), ums);
|
||||
IntSet deletedShortcuts = new IntSet();
|
||||
|
||||
while (lc.moveToNext()) {
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package com.android.launcher3.provider;
|
||||
|
||||
import static android.os.Process.myUserHandle;
|
||||
|
||||
import static com.android.launcher3.InvariantDeviceProfile.TYPE_MULTI_DISPLAY;
|
||||
import static com.android.launcher3.LauncherPrefs.APP_WIDGET_IDS;
|
||||
import static com.android.launcher3.LauncherPrefs.OLD_APP_WIDGET_IDS;
|
||||
@@ -49,8 +47,8 @@ import com.android.launcher3.LauncherPrefs;
|
||||
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.ModelDbController;
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
@@ -85,12 +83,12 @@ public class RestoreDbTask {
|
||||
/**
|
||||
* Tries to restore the backup DB if needed
|
||||
*/
|
||||
public static void restoreIfNeeded(Context context, ModelDbController dbController) {
|
||||
public static void restoreIfNeeded(Context context, DatabaseHelper helper) {
|
||||
if (!isPending(context)) {
|
||||
return;
|
||||
}
|
||||
if (!performRestore(context, dbController)) {
|
||||
dbController.createEmptyDB();
|
||||
if (!performRestore(context, helper)) {
|
||||
helper.createEmptyDB(helper.getWritableDatabase());
|
||||
}
|
||||
|
||||
// Obtain InvariantDeviceProfile first before setting pending to false, so
|
||||
@@ -104,12 +102,12 @@ public class RestoreDbTask {
|
||||
idp.reinitializeAfterRestore(context);
|
||||
}
|
||||
|
||||
private static boolean performRestore(Context context, ModelDbController controller) {
|
||||
SQLiteDatabase db = controller.getDb();
|
||||
private static boolean performRestore(Context context, DatabaseHelper helper) {
|
||||
SQLiteDatabase db = helper.getWritableDatabase();
|
||||
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
|
||||
RestoreDbTask task = new RestoreDbTask();
|
||||
task.sanitizeDB(context, controller, db, new BackupManager(context));
|
||||
task.restoreAppWidgetIdsIfExists(context, controller);
|
||||
task.sanitizeDB(context, helper, db, new BackupManager(context));
|
||||
task.restoreAppWidgetIdsIfExists(context, helper);
|
||||
t.commit();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
@@ -131,10 +129,10 @@ public class RestoreDbTask {
|
||||
* @return number of items deleted.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
protected int sanitizeDB(Context context, ModelDbController controller, SQLiteDatabase db,
|
||||
protected int sanitizeDB(Context context, DatabaseHelper helper, SQLiteDatabase db,
|
||||
BackupManager backupManager) throws Exception {
|
||||
// Primary user ids
|
||||
long myProfileId = controller.getSerialNumberForUser(myUserHandle());
|
||||
long myProfileId = helper.getDefaultUserSerial();
|
||||
long oldProfileId = getDefaultProfileId(db);
|
||||
LongSparseArray<Long> oldManagedProfileIds = getManagedProfileIds(db, oldProfileId);
|
||||
LongSparseArray<Long> profileMapping = new LongSparseArray<>(oldManagedProfileIds.size()
|
||||
@@ -146,7 +144,7 @@ public class RestoreDbTask {
|
||||
long oldManagedProfileId = oldManagedProfileIds.keyAt(i);
|
||||
UserHandle user = getUserForAncestralSerialNumber(backupManager, oldManagedProfileId);
|
||||
if (user != null) {
|
||||
long newManagedProfileId = controller.getSerialNumberForUser(user);
|
||||
long newManagedProfileId = helper.getSerialNumberForUser(user);
|
||||
profileMapping.put(oldManagedProfileId, newManagedProfileId);
|
||||
}
|
||||
}
|
||||
@@ -215,7 +213,7 @@ public class RestoreDbTask {
|
||||
}
|
||||
|
||||
// Override shortcuts
|
||||
maybeOverrideShortcuts(context, controller, db, myProfileId);
|
||||
maybeOverrideShortcuts(context, helper, db, myProfileId);
|
||||
|
||||
return itemsDeleted;
|
||||
}
|
||||
@@ -323,11 +321,11 @@ public class RestoreDbTask {
|
||||
.putSync(RESTORE_DEVICE.to(new DeviceGridState(context).getDeviceType()));
|
||||
}
|
||||
|
||||
private void restoreAppWidgetIdsIfExists(Context context, ModelDbController controller) {
|
||||
private void restoreAppWidgetIdsIfExists(Context context, DatabaseHelper helper) {
|
||||
LauncherPrefs lp = LauncherPrefs.get(context);
|
||||
if (lp.has(APP_WIDGET_IDS, OLD_APP_WIDGET_IDS)) {
|
||||
AppWidgetHost host = new AppWidgetHost(context, APPWIDGET_HOST_ID);
|
||||
AppWidgetsRestoredReceiver.restoreAppWidgetIds(context, controller,
|
||||
AppWidgetsRestoredReceiver.restoreAppWidgetIds(context, helper,
|
||||
IntArray.fromConcatString(lp.get(OLD_APP_WIDGET_IDS)).toArray(),
|
||||
IntArray.fromConcatString(lp.get(APP_WIDGET_IDS)).toArray(),
|
||||
host);
|
||||
@@ -345,7 +343,7 @@ public class RestoreDbTask {
|
||||
APP_WIDGET_IDS.to(IntArray.wrap(newIds).toConcatString()));
|
||||
}
|
||||
|
||||
protected static void maybeOverrideShortcuts(Context context, ModelDbController controller,
|
||||
protected static void maybeOverrideShortcuts(Context context, DatabaseHelper helper,
|
||||
SQLiteDatabase db, long currentUser) {
|
||||
Map<String, LauncherActivityInfo> activityOverrides = ApiWrapper.getActivityOverrides(
|
||||
context);
|
||||
@@ -369,7 +367,7 @@ public class RestoreDbTask {
|
||||
if (override != null) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Favorites.PROFILE_ID,
|
||||
controller.getSerialNumberForUser(override.getUser()));
|
||||
helper.getSerialNumberForUser(override.getUser()));
|
||||
values.put(Favorites.INTENT, AppInfo.makeLaunchIntent(override).toUri(0));
|
||||
db.update(Favorites.TABLE_NAME, values, String.format("%s=?", Favorites._ID),
|
||||
new String[]{String.valueOf(c.getInt(idIndex))});
|
||||
|
||||
@@ -26,7 +26,7 @@ import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.icons.BitmapInfo;
|
||||
import com.android.launcher3.icons.GraphicsUtils;
|
||||
import com.android.launcher3.model.ModelDbController;
|
||||
import com.android.launcher3.model.DatabaseHelper;
|
||||
import com.android.launcher3.pm.UserCache;
|
||||
|
||||
/**
|
||||
@@ -106,7 +106,7 @@ public class ContentWriter {
|
||||
|
||||
public int commit() {
|
||||
if (mCommitParams != null) {
|
||||
mCommitParams.mDbController.update(
|
||||
mCommitParams.mDatabaseHelper.getWritableDatabase().update(
|
||||
Favorites.TABLE_NAME, getValues(mContext),
|
||||
mCommitParams.mWhere, mCommitParams.mSelectionArgs);
|
||||
}
|
||||
@@ -115,12 +115,12 @@ public class ContentWriter {
|
||||
|
||||
public static final class CommitParams {
|
||||
|
||||
final ModelDbController mDbController;
|
||||
final DatabaseHelper mDatabaseHelper;
|
||||
final String mWhere;
|
||||
final String[] mSelectionArgs;
|
||||
|
||||
public CommitParams(ModelDbController controller, String where, String[] selectionArgs) {
|
||||
mDbController = controller;
|
||||
public CommitParams(DatabaseHelper helper, String where, String[] selectionArgs) {
|
||||
mDatabaseHelper = helper;
|
||||
mWhere = where;
|
||||
mSelectionArgs = selectionArgs;
|
||||
}
|
||||
|
||||
@@ -146,8 +146,7 @@ public class DatabaseWidgetPreviewLoader {
|
||||
previewWidth = drawable.getIntrinsicWidth();
|
||||
previewHeight = drawable.getIntrinsicHeight();
|
||||
} else {
|
||||
Size widgetSize = WidgetSizes.getWidgetPaddedSizePx(mContext, info.provider, dp, spanX,
|
||||
spanY);
|
||||
Size widgetSize = WidgetSizes.getWidgetSizePx(dp, spanX, spanY);
|
||||
previewWidth = widgetSize.getWidth();
|
||||
previewHeight = widgetSize.getHeight();
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.android.launcher3.widget;
|
||||
|
||||
import static com.android.launcher3.Utilities.ATLEAST_S;
|
||||
|
||||
import android.appwidget.AppWidgetHostView;
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
@@ -105,44 +104,35 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo
|
||||
int spanX = 0;
|
||||
int spanY = 0;
|
||||
|
||||
Rect widgetPadding = new Rect();
|
||||
Rect localPadding = new Rect();
|
||||
AppWidgetHostView.getDefaultPaddingForWidget(context, provider, widgetPadding);
|
||||
|
||||
Point cellSize = new Point();
|
||||
for (DeviceProfile dp : idp.supportedProfiles) {
|
||||
dp.getCellSize(cellSize);
|
||||
// We want to account for the extra amount of padding that we are adding to the widget
|
||||
// to ensure that it gets the full amount of space that it has requested.
|
||||
// If grids supports insetting widgets, we do not account for widget padding.
|
||||
if (dp.shouldInsetWidgets()) {
|
||||
localPadding.setEmpty();
|
||||
} else {
|
||||
localPadding.set(widgetPadding);
|
||||
}
|
||||
Rect widgetPadding = dp.widgetPadding;
|
||||
|
||||
minSpanX = Math.max(minSpanX,
|
||||
getSpanX(localPadding, minResizeWidth, dp.cellLayoutBorderSpacePx.x,
|
||||
getSpanX(widgetPadding, minResizeWidth, dp.cellLayoutBorderSpacePx.x,
|
||||
cellSize.x));
|
||||
minSpanY = Math.max(minSpanY,
|
||||
getSpanY(localPadding, minResizeHeight, dp.cellLayoutBorderSpacePx.y,
|
||||
getSpanY(widgetPadding, minResizeHeight, dp.cellLayoutBorderSpacePx.y,
|
||||
cellSize.y));
|
||||
|
||||
if (ATLEAST_S) {
|
||||
if (maxResizeWidth > 0) {
|
||||
maxSpanX = Math.min(maxSpanX, getSpanX(localPadding, maxResizeWidth,
|
||||
maxSpanX = Math.min(maxSpanX, getSpanX(widgetPadding, maxResizeWidth,
|
||||
dp.cellLayoutBorderSpacePx.x, cellSize.x));
|
||||
}
|
||||
if (maxResizeHeight > 0) {
|
||||
maxSpanY = Math.min(maxSpanY, getSpanY(localPadding, maxResizeHeight,
|
||||
maxSpanY = Math.min(maxSpanY, getSpanY(widgetPadding, maxResizeHeight,
|
||||
dp.cellLayoutBorderSpacePx.y, cellSize.y));
|
||||
}
|
||||
}
|
||||
|
||||
spanX = Math.max(spanX,
|
||||
getSpanX(localPadding, minWidth, dp.cellLayoutBorderSpacePx.x,
|
||||
getSpanX(widgetPadding, minWidth, dp.cellLayoutBorderSpacePx.x,
|
||||
cellSize.x));
|
||||
spanY = Math.max(spanY,
|
||||
getSpanY(localPadding, minHeight, dp.cellLayoutBorderSpacePx.y,
|
||||
getSpanY(widgetPadding, minHeight, dp.cellLayoutBorderSpacePx.y,
|
||||
cellSize.y));
|
||||
}
|
||||
|
||||
@@ -184,15 +174,22 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo
|
||||
}
|
||||
|
||||
private int getSpanX(Rect widgetPadding, int widgetWidth, int cellSpacing, float cellWidth) {
|
||||
return Math.max(1, (int) Math.ceil(
|
||||
(widgetWidth + widgetPadding.left + widgetPadding.right + cellSpacing) / (cellWidth
|
||||
+ cellSpacing)));
|
||||
return getSpan(widgetPadding.left + widgetPadding.right,
|
||||
widgetWidth, cellSpacing, cellWidth);
|
||||
}
|
||||
|
||||
private int getSpanY(Rect widgetPadding, int widgetHeight, int cellSpacing, float cellHeight) {
|
||||
return getSpan(widgetPadding.top + widgetPadding.bottom, widgetHeight,
|
||||
cellSpacing, cellHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Solving the equation:
|
||||
* n * cellSize + (n - 1) * cellSpacing - widgetPadding = widgetSize
|
||||
*/
|
||||
private int getSpan(int widgetPadding, int widgetSize, int cellSpacing, float cellSize) {
|
||||
return Math.max(1, (int) Math.ceil(
|
||||
(widgetHeight + widgetPadding.top + widgetPadding.bottom + cellSpacing) / (
|
||||
cellHeight + cellSpacing)));
|
||||
(widgetSize + widgetPadding + cellSpacing) / (cellSize + cellSpacing)));
|
||||
}
|
||||
|
||||
public String getLabel(PackageManager packageManager) {
|
||||
|
||||
@@ -25,7 +25,6 @@ import android.view.View;
|
||||
import android.view.ViewDebug;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Reorderable;
|
||||
import com.android.launcher3.dragndrop.DraggableView;
|
||||
import com.android.launcher3.util.MultiTranslateDelegate;
|
||||
@@ -48,13 +47,13 @@ public abstract class NavigableAppWidgetHostView extends AppWidgetHostView
|
||||
|
||||
private float mScaleForReorderBounce = 1f;
|
||||
|
||||
private final Rect mTempRect = new Rect();
|
||||
|
||||
@ViewDebug.ExportedProperty(category = "launcher")
|
||||
private boolean mChildrenFocused;
|
||||
|
||||
protected final ActivityContext mActivity;
|
||||
|
||||
private boolean mDisableSetPadding = false;
|
||||
|
||||
public NavigableAppWidgetHostView(Context context) {
|
||||
super(context);
|
||||
mActivity = ActivityContext.lookupContext(context);
|
||||
@@ -146,6 +145,22 @@ public abstract class NavigableAppWidgetHostView extends AppWidgetHostView
|
||||
dispatchChildFocus(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) {
|
||||
// Prevent default padding being set on the view based on provider info. Launcher manages
|
||||
// its own widget spacing
|
||||
mDisableSetPadding = true;
|
||||
super.setAppWidget(appWidgetId, info);
|
||||
mDisableSetPadding = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPadding(int left, int top, int right, int bottom) {
|
||||
if (!mDisableSetPadding) {
|
||||
super.setPadding(left, top, right, bottom);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchUnhandledMove(View focused, int direction) {
|
||||
return mChildrenFocused;
|
||||
@@ -195,26 +210,6 @@ public abstract class NavigableAppWidgetHostView extends AppWidgetHostView
|
||||
public void getWorkspaceVisualDragBounds(Rect bounds) {
|
||||
int width = (int) (getMeasuredWidth() * mScaleToFit);
|
||||
int height = (int) (getMeasuredHeight() * mScaleToFit);
|
||||
|
||||
getWidgetInset(mActivity.getDeviceProfile(), mTempRect);
|
||||
bounds.set(mTempRect.left, mTempRect.top, width - mTempRect.right,
|
||||
height - mTempRect.bottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Widgets have padding added by the system. We may choose to inset this padding if the grid
|
||||
* supports it.
|
||||
*/
|
||||
public void getWidgetInset(DeviceProfile grid, Rect out) {
|
||||
if (!grid.shouldInsetWidgets()) {
|
||||
out.setEmpty();
|
||||
return;
|
||||
}
|
||||
AppWidgetProviderInfo info = getAppWidgetInfo();
|
||||
if (info == null) {
|
||||
out.set(grid.inv.defaultWidgetPadding);
|
||||
} else {
|
||||
AppWidgetHostView.getDefaultPaddingForWidget(getContext(), info.provider, out);
|
||||
}
|
||||
bounds.set(0, 0, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.launcher3.widget;
|
||||
|
||||
import static com.android.launcher3.widget.util.WidgetSizes.getWidgetSizePx;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
@@ -43,7 +45,6 @@ import com.android.launcher3.icons.FastBitmapDrawable;
|
||||
import com.android.launcher3.icons.LauncherIcons;
|
||||
import com.android.launcher3.icons.RoundDrawableWrapper;
|
||||
import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener;
|
||||
import com.android.launcher3.widget.util.WidgetSizes;
|
||||
|
||||
/**
|
||||
* Extension of {@link DragPreviewProvider} with logic specific to pending widgets/shortcuts
|
||||
@@ -121,13 +122,8 @@ public class PendingItemDragHelper extends DragPreviewProvider {
|
||||
mAppWidgetHostViewPreview.setAppWidget(/* appWidgetId= */ -1,
|
||||
((PendingAddWidgetInfo) mAddInfo).info);
|
||||
DeviceProfile deviceProfile = launcher.getDeviceProfile();
|
||||
Rect padding = new Rect();
|
||||
mAppWidgetHostViewPreview.getWidgetInset(deviceProfile, padding);
|
||||
mAppWidgetHostViewPreview.setPadding(padding.left, padding.top, padding.right,
|
||||
padding.bottom);
|
||||
mAppWidgetHostViewPreview.updateAppWidget(/* remoteViews= */ mRemoteViewsPreview);
|
||||
Size widgetSizes = WidgetSizes.getWidgetPaddedSizePx(launcher,
|
||||
mAddInfo.componentName, deviceProfile, mAddInfo.spanX, mAddInfo.spanY);
|
||||
Size widgetSizes = getWidgetSizePx(deviceProfile, mAddInfo.spanX, mAddInfo.spanY);
|
||||
mAppWidgetHostViewPreview.measure(
|
||||
MeasureSpec.makeMeasureSpec(widgetSizes.getWidth(), MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(widgetSizes.getHeight(), MeasureSpec.EXACTLY));
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package com.android.launcher3.widget.util;
|
||||
|
||||
import static android.appwidget.AppWidgetHostView.getDefaultPaddingForWidget;
|
||||
|
||||
import android.appwidget.AppWidgetHostView;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.ComponentName;
|
||||
@@ -28,8 +26,6 @@ import android.util.Log;
|
||||
import android.util.Size;
|
||||
import android.util.SizeF;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.R;
|
||||
@@ -43,24 +39,13 @@ public final class WidgetSizes {
|
||||
|
||||
/**
|
||||
* Returns the list of all possible sizes, in dp, for a widget of given spans on this device.
|
||||
*
|
||||
* <p>The returned sizes already take into account the system padding, and whether it is applied
|
||||
* or not in that specific configuration.
|
||||
*/
|
||||
public static ArrayList<SizeF> getWidgetPaddedSizes(Context context, ComponentName provider,
|
||||
int spanX, int spanY) {
|
||||
Rect padding = getDefaultPaddingForWidget(context, provider, /* padding= */ null);
|
||||
|
||||
public static ArrayList<SizeF> getWidgetSizesDp(Context context, int spanX, int spanY) {
|
||||
ArrayList<SizeF> sizes = new ArrayList<>(2);
|
||||
final float density = context.getResources().getDisplayMetrics().density;
|
||||
final Point cellSize = new Point();
|
||||
|
||||
for (DeviceProfile profile : LauncherAppState.getIDP(context).supportedProfiles) {
|
||||
Size widgetSizePx = getWidgetSizePx(profile, spanX, spanY, cellSize);
|
||||
if (!profile.shouldInsetWidgets()) {
|
||||
widgetSizePx = new Size(widgetSizePx.getWidth() - padding.left - padding.right,
|
||||
widgetSizePx.getHeight() - padding.top - padding.bottom);
|
||||
}
|
||||
Size widgetSizePx = getWidgetSizePx(profile, spanX, spanY);
|
||||
sizes.add(new SizeF(widgetSizePx.getWidth() / density,
|
||||
widgetSizePx.getHeight() / density));
|
||||
}
|
||||
@@ -69,21 +54,15 @@ public final class WidgetSizes {
|
||||
|
||||
/** Returns the size, in pixels, a widget of given spans & {@code profile}. */
|
||||
public static Size getWidgetSizePx(DeviceProfile profile, int spanX, int spanY) {
|
||||
return getWidgetSizePx(profile, spanX, spanY, /* recycledCellSize= */ null);
|
||||
}
|
||||
final int hBorderSpacing = (spanX - 1) * profile.cellLayoutBorderSpacePx.x;
|
||||
final int vBorderSpacing = (spanY - 1) * profile.cellLayoutBorderSpacePx.y;
|
||||
|
||||
/**
|
||||
* Returns the size, in pixels and removing padding, a widget of given spans & {@code profile}.
|
||||
*/
|
||||
public static Size getWidgetPaddedSizePx(Context context, ComponentName component,
|
||||
DeviceProfile profile, int spanX, int spanY) {
|
||||
Size size = getWidgetSizePx(profile, spanX, spanY);
|
||||
if (profile.shouldInsetWidgets()) {
|
||||
return size;
|
||||
}
|
||||
Rect padding = getDefaultPaddingForWidget(context, component, /* padding= */ null);
|
||||
return new Size(size.getWidth() - padding.left - padding.right,
|
||||
size.getHeight() - padding.top - padding.bottom);
|
||||
Point cellSize = profile.getCellSize();
|
||||
Rect padding = profile.widgetPadding;
|
||||
|
||||
return new Size(
|
||||
(spanX * cellSize.x) + hBorderSpacing - padding.left - padding.right,
|
||||
(spanY * cellSize.y) + vBorderSpacing - padding.top - padding.bottom);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,8 +71,7 @@ public final class WidgetSizes {
|
||||
* <p>This size is used by the widget picker. It should NEVER be shared with app widgets.
|
||||
*
|
||||
* <p>For sizes shared with app widgets, please refer to
|
||||
* {@link #getWidgetPaddedSizes(Context, ComponentName, int, int)} &
|
||||
* {@link #getWidgetPaddedSizePx(Context, ComponentName, DeviceProfile, int, int)}.
|
||||
* {@link #getWidgetSizesDp(Context, int, int)} &
|
||||
*/
|
||||
public static Size getWidgetItemSizePx(Context context, DeviceProfile profile,
|
||||
WidgetItem widgetItem) {
|
||||
@@ -102,27 +80,7 @@ public final class WidgetSizes {
|
||||
.getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
|
||||
return new Size(dimension, dimension);
|
||||
}
|
||||
Size widgetItemSize = getWidgetSizePx(profile, widgetItem.spanX,
|
||||
widgetItem.spanY, /* recycledCellSize= */ null);
|
||||
if (profile.shouldInsetWidgets()) {
|
||||
Rect inset = new Rect();
|
||||
AppWidgetHostView.getDefaultPaddingForWidget(context, widgetItem.componentName, inset);
|
||||
return new Size(widgetItemSize.getWidth() + inset.left + inset.right,
|
||||
widgetItemSize.getHeight() + inset.top + inset.bottom);
|
||||
}
|
||||
return widgetItemSize;
|
||||
}
|
||||
|
||||
private static Size getWidgetSizePx(DeviceProfile profile, int spanX, int spanY,
|
||||
@Nullable Point recycledCellSize) {
|
||||
final int hBorderSpacing = (spanX - 1) * profile.cellLayoutBorderSpacePx.x;
|
||||
final int vBorderSpacing = (spanY - 1) * profile.cellLayoutBorderSpacePx.y;
|
||||
if (recycledCellSize == null) {
|
||||
recycledCellSize = new Point();
|
||||
}
|
||||
profile.getCellSize(recycledCellSize);
|
||||
return new Size(((spanX * recycledCellSize.x) + hBorderSpacing),
|
||||
((spanY * recycledCellSize.y) + vBorderSpacing));
|
||||
return getWidgetSizePx(profile, widgetItem.spanX, widgetItem.spanY);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,7 +112,7 @@ public final class WidgetSizes {
|
||||
*/
|
||||
public static Bundle getWidgetSizeOptions(Context context, ComponentName provider, int spanX,
|
||||
int spanY) {
|
||||
ArrayList<SizeF> paddedSizes = getWidgetPaddedSizes(context, provider, spanX, spanY);
|
||||
ArrayList<SizeF> paddedSizes = getWidgetSizesDp(context, spanX, spanY);
|
||||
|
||||
Rect rect = getMinMaxSizes(paddedSizes);
|
||||
Bundle options = new Bundle();
|
||||
|
||||
@@ -39,7 +39,6 @@ import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.pm.UserCache;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -223,9 +222,7 @@ public class DbDowngradeHelperTest {
|
||||
private class MyDatabaseHelper extends DatabaseHelper {
|
||||
|
||||
MyDatabaseHelper() {
|
||||
super(mContext, DB_FILE,
|
||||
UserCache.INSTANCE.get(mContext)::getSerialNumberForUser,
|
||||
() -> { });
|
||||
super(mContext, DB_FILE, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package com.android.launcher3.model
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.database.Cursor
|
||||
@@ -24,7 +23,6 @@ import android.graphics.Point
|
||||
import android.os.Process
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.android.launcher3.InvariantDeviceProfile
|
||||
import com.android.launcher3.LauncherPrefs
|
||||
import com.android.launcher3.LauncherPrefs.Companion.WORKSPACE_SIZE
|
||||
@@ -33,7 +31,10 @@ import com.android.launcher3.config.FeatureFlags
|
||||
import com.android.launcher3.model.GridSizeMigrationUtil.DbReader
|
||||
import com.android.launcher3.pm.UserCache
|
||||
import com.android.launcher3.provider.LauncherDbUtils
|
||||
import com.android.launcher3.util.LauncherModelHelper
|
||||
import com.android.launcher3.util.LauncherModelHelper.*
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -42,12 +43,11 @@ import org.junit.runner.RunWith
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class GridSizeMigrationUtilTest {
|
||||
|
||||
private lateinit var modelHelper: LauncherModelHelper
|
||||
private lateinit var context: Context
|
||||
private lateinit var db: SQLiteDatabase
|
||||
private lateinit var validPackages: Set<String>
|
||||
private lateinit var idp: InvariantDeviceProfile
|
||||
private lateinit var dbHelper: DatabaseHelper
|
||||
private lateinit var db: SQLiteDatabase
|
||||
private val testPackage1 = "com.android.launcher3.validpackage1"
|
||||
private val testPackage2 = "com.android.launcher3.validpackage2"
|
||||
private val testPackage3 = "com.android.launcher3.validpackage3"
|
||||
@@ -61,17 +61,13 @@ class GridSizeMigrationUtilTest {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
dbHelper =
|
||||
DatabaseHelper(
|
||||
context,
|
||||
null,
|
||||
UserCache.INSTANCE.get(context)::getSerialNumberForUser
|
||||
) {}
|
||||
db = dbHelper.writableDatabase
|
||||
modelHelper = LauncherModelHelper()
|
||||
context = modelHelper.sandboxContext
|
||||
db = modelHelper.provider.db
|
||||
|
||||
validPackages =
|
||||
setOf(
|
||||
TEST_PACKAGE,
|
||||
testPackage1,
|
||||
testPackage2,
|
||||
testPackage3,
|
||||
@@ -90,6 +86,11 @@ class GridSizeMigrationUtilTest {
|
||||
addTableToDb(db, userSerial, false, TMP_TABLE)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
modelHelper.destroy()
|
||||
}
|
||||
|
||||
/**
|
||||
* Old migration logic, should be modified once [FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC] is not
|
||||
* needed anymore
|
||||
@@ -98,26 +99,26 @@ class GridSizeMigrationUtilTest {
|
||||
@Throws(Exception::class)
|
||||
fun testMigration() {
|
||||
// Src Hotseat icons
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_SHORTCUT, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
|
||||
modelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0, testPackage1, 1, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0, testPackage2, 2, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(SHORTCUT, 3, HOTSEAT, 0, 0, testPackage3, 3, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(APP_ICON, 4, HOTSEAT, 0, 0, testPackage4, 4, TMP_CONTENT_URI)
|
||||
// Src grid icons
|
||||
// _ _ _ _ _
|
||||
// _ _ _ _ 5
|
||||
// _ _ 6 _ 7
|
||||
// _ _ 8 _ 9
|
||||
// _ _ _ _ _
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 4, 1, testPackage5, 5, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 2, testPackage6, 6, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 4, 2, testPackage7, 7, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 3, testPackage8, 8, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 4, 3, testPackage9, 9, TMP_TABLE)
|
||||
modelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 1, testPackage5, 5, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 2, testPackage6, 6, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 2, testPackage7, 7, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 3, testPackage8, 8, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 3, testPackage9, 9, TMP_CONTENT_URI)
|
||||
|
||||
// Dest hotseat icons
|
||||
addItem(ITEM_TYPE_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2)
|
||||
modelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0, testPackage2)
|
||||
// Dest grid icons
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 2, testPackage10)
|
||||
modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 2, testPackage10)
|
||||
|
||||
idp.numDatabaseHotseatIcons = 4
|
||||
idp.numColumns = 4
|
||||
@@ -125,7 +126,8 @@ class GridSizeMigrationUtilTest {
|
||||
val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
|
||||
val destReader = DbReader(db, TABLE_NAME, context, validPackages)
|
||||
GridSizeMigrationUtil.migrate(
|
||||
dbHelper,
|
||||
context,
|
||||
db,
|
||||
srcReader,
|
||||
destReader,
|
||||
idp.numDatabaseHotseatIcons,
|
||||
@@ -136,13 +138,12 @@ class GridSizeMigrationUtilTest {
|
||||
|
||||
// Check hotseat items
|
||||
var c =
|
||||
db.query(
|
||||
TABLE_NAME,
|
||||
context.contentResolver.query(
|
||||
CONTENT_URI,
|
||||
arrayOf(SCREEN, INTENT),
|
||||
"container=$CONTAINER_HOTSEAT",
|
||||
null,
|
||||
SCREEN,
|
||||
null,
|
||||
null
|
||||
)
|
||||
?: throw IllegalStateException()
|
||||
@@ -167,13 +168,12 @@ class GridSizeMigrationUtilTest {
|
||||
|
||||
// Check workspace items
|
||||
c =
|
||||
db.query(
|
||||
TABLE_NAME,
|
||||
context.contentResolver.query(
|
||||
CONTENT_URI,
|
||||
arrayOf(CELLX, CELLY, INTENT),
|
||||
"container=$CONTAINER_DESKTOP",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
?: throw IllegalStateException()
|
||||
@@ -209,30 +209,30 @@ class GridSizeMigrationUtilTest {
|
||||
fun testMigrationBackAndForth() {
|
||||
// Hotseat items in grid A
|
||||
// 1 2 _ 3 4
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_SHORTCUT, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
|
||||
modelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0, testPackage1, 1, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0, testPackage2, 2, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(SHORTCUT, 3, HOTSEAT, 0, 0, testPackage3, 3, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(APP_ICON, 4, HOTSEAT, 0, 0, testPackage4, 4, TMP_CONTENT_URI)
|
||||
// Workspace items in grid A
|
||||
// _ _ _ _ _
|
||||
// _ _ _ _ 5
|
||||
// _ _ 6 _ 7
|
||||
// _ _ 8 _ _
|
||||
// _ _ _ _ _
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 4, 1, testPackage5, 5, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 2, testPackage6, 6, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 4, 2, testPackage7, 7, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 3, testPackage8, 8, TMP_TABLE)
|
||||
modelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 1, testPackage5, 5, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 2, testPackage6, 6, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 2, testPackage7, 7, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 3, testPackage8, 8, TMP_CONTENT_URI)
|
||||
|
||||
// Hotseat items in grid B
|
||||
// 2 _ _ _
|
||||
addItem(ITEM_TYPE_SHORTCUT, 0, CONTAINER_HOTSEAT, 0, 0, testPackage2)
|
||||
modelHelper.addItem(SHORTCUT, 0, HOTSEAT, 0, 0, testPackage2)
|
||||
// Workspace items in grid B
|
||||
// _ _ _ _
|
||||
// _ _ _ 10
|
||||
// _ _ _ _
|
||||
// _ _ _ _
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 1, 3, testPackage10)
|
||||
modelHelper.addItem(APP_ICON, 0, DESKTOP, 1, 3, testPackage10)
|
||||
|
||||
idp.numDatabaseHotseatIcons = 4
|
||||
idp.numColumns = 4
|
||||
@@ -241,7 +241,8 @@ class GridSizeMigrationUtilTest {
|
||||
val readerGridB = DbReader(db, TABLE_NAME, context, validPackages)
|
||||
// migrate from A -> B
|
||||
GridSizeMigrationUtil.migrate(
|
||||
dbHelper,
|
||||
context,
|
||||
db,
|
||||
readerGridA,
|
||||
readerGridB,
|
||||
idp.numDatabaseHotseatIcons,
|
||||
@@ -252,13 +253,12 @@ class GridSizeMigrationUtilTest {
|
||||
|
||||
// Check hotseat items in grid B
|
||||
var c =
|
||||
db.query(
|
||||
TABLE_NAME,
|
||||
context.contentResolver.query(
|
||||
CONTENT_URI,
|
||||
arrayOf(SCREEN, INTENT),
|
||||
"container=$CONTAINER_HOTSEAT",
|
||||
null,
|
||||
SCREEN,
|
||||
null,
|
||||
null
|
||||
)
|
||||
?: throw IllegalStateException()
|
||||
@@ -272,13 +272,12 @@ class GridSizeMigrationUtilTest {
|
||||
|
||||
// Check workspace items in grid B
|
||||
c =
|
||||
db.query(
|
||||
TABLE_NAME,
|
||||
context.contentResolver.query(
|
||||
CONTENT_URI,
|
||||
arrayOf(SCREEN, CELLX, CELLY, INTENT),
|
||||
"container=$CONTAINER_DESKTOP",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
?: throw IllegalStateException()
|
||||
@@ -295,11 +294,12 @@ class GridSizeMigrationUtilTest {
|
||||
assertThat(locMap[testPackage8]).isEqualTo(Triple(0, 3, 1))
|
||||
|
||||
// add item in B
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 0, 2, testPackage9)
|
||||
modelHelper.addItem(APP_ICON, 0, DESKTOP, 0, 2, testPackage9)
|
||||
|
||||
// migrate from B -> A
|
||||
GridSizeMigrationUtil.migrate(
|
||||
dbHelper,
|
||||
context,
|
||||
db,
|
||||
readerGridB,
|
||||
readerGridA,
|
||||
5,
|
||||
@@ -309,13 +309,12 @@ class GridSizeMigrationUtilTest {
|
||||
)
|
||||
// Check hotseat items in grid A
|
||||
c =
|
||||
db.query(
|
||||
TMP_TABLE,
|
||||
context.contentResolver.query(
|
||||
TMP_CONTENT_URI,
|
||||
arrayOf(SCREEN, INTENT),
|
||||
"container=$CONTAINER_HOTSEAT",
|
||||
null,
|
||||
SCREEN,
|
||||
null,
|
||||
null
|
||||
)
|
||||
?: throw IllegalStateException()
|
||||
@@ -329,13 +328,12 @@ class GridSizeMigrationUtilTest {
|
||||
|
||||
// Check workspace items in grid A
|
||||
c =
|
||||
db.query(
|
||||
TMP_TABLE,
|
||||
context.contentResolver.query(
|
||||
TMP_CONTENT_URI,
|
||||
arrayOf(SCREEN, CELLX, CELLY, INTENT),
|
||||
"container=$CONTAINER_DESKTOP",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
?: throw IllegalStateException()
|
||||
@@ -356,11 +354,12 @@ class GridSizeMigrationUtilTest {
|
||||
assertThat(locMap[testPackage9]).isEqualTo(Triple(0, 0, 2))
|
||||
|
||||
// remove item from B
|
||||
db.delete(TMP_TABLE, "$_ID=7", null)
|
||||
modelHelper.deleteItem(7, TMP_TABLE)
|
||||
|
||||
// migrate from A -> B
|
||||
GridSizeMigrationUtil.migrate(
|
||||
dbHelper,
|
||||
context,
|
||||
db,
|
||||
readerGridA,
|
||||
readerGridB,
|
||||
idp.numDatabaseHotseatIcons,
|
||||
@@ -371,13 +370,12 @@ class GridSizeMigrationUtilTest {
|
||||
|
||||
// Check hotseat items in grid B
|
||||
c =
|
||||
db.query(
|
||||
TABLE_NAME,
|
||||
context.contentResolver.query(
|
||||
CONTENT_URI,
|
||||
arrayOf(SCREEN, INTENT),
|
||||
"container=$CONTAINER_HOTSEAT",
|
||||
null,
|
||||
SCREEN,
|
||||
null,
|
||||
null
|
||||
)
|
||||
?: throw IllegalStateException()
|
||||
@@ -391,13 +389,12 @@ class GridSizeMigrationUtilTest {
|
||||
|
||||
// Check workspace items in grid B
|
||||
c =
|
||||
db.query(
|
||||
TABLE_NAME,
|
||||
context.contentResolver.query(
|
||||
CONTENT_URI,
|
||||
arrayOf(SCREEN, CELLX, CELLY, INTENT),
|
||||
"container=$CONTAINER_DESKTOP",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
?: throw IllegalStateException()
|
||||
@@ -446,28 +443,10 @@ class GridSizeMigrationUtilTest {
|
||||
fun migrateToLargerHotseat() {
|
||||
val srcHotseatItems =
|
||||
intArrayOf(
|
||||
addItem(
|
||||
ITEM_TYPE_APPLICATION,
|
||||
0,
|
||||
CONTAINER_HOTSEAT,
|
||||
0,
|
||||
0,
|
||||
testPackage1,
|
||||
1,
|
||||
TMP_TABLE
|
||||
),
|
||||
addItem(ITEM_TYPE_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE),
|
||||
addItem(
|
||||
ITEM_TYPE_APPLICATION,
|
||||
2,
|
||||
CONTAINER_HOTSEAT,
|
||||
0,
|
||||
0,
|
||||
testPackage3,
|
||||
3,
|
||||
TMP_TABLE
|
||||
),
|
||||
addItem(ITEM_TYPE_SHORTCUT, 3, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
|
||||
modelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0, testPackage1, 1, TMP_CONTENT_URI),
|
||||
modelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0, testPackage2, 2, TMP_CONTENT_URI),
|
||||
modelHelper.addItem(APP_ICON, 2, HOTSEAT, 0, 0, testPackage3, 3, TMP_CONTENT_URI),
|
||||
modelHelper.addItem(SHORTCUT, 3, HOTSEAT, 0, 0, testPackage4, 4, TMP_CONTENT_URI)
|
||||
)
|
||||
val numSrcDatabaseHotseatIcons = srcHotseatItems.size
|
||||
idp.numDatabaseHotseatIcons = 6
|
||||
@@ -476,7 +455,8 @@ class GridSizeMigrationUtilTest {
|
||||
val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
|
||||
val destReader = DbReader(db, TABLE_NAME, context, validPackages)
|
||||
GridSizeMigrationUtil.migrate(
|
||||
dbHelper,
|
||||
context,
|
||||
db,
|
||||
srcReader,
|
||||
destReader,
|
||||
idp.numDatabaseHotseatIcons,
|
||||
@@ -487,13 +467,12 @@ class GridSizeMigrationUtilTest {
|
||||
|
||||
// Check hotseat items
|
||||
val c =
|
||||
db.query(
|
||||
TABLE_NAME,
|
||||
context.contentResolver.query(
|
||||
CONTENT_URI,
|
||||
arrayOf(SCREEN, INTENT),
|
||||
"container=$CONTAINER_HOTSEAT",
|
||||
null,
|
||||
SCREEN,
|
||||
null,
|
||||
null
|
||||
)
|
||||
?: throw IllegalStateException()
|
||||
@@ -522,11 +501,11 @@ class GridSizeMigrationUtilTest {
|
||||
|
||||
@Test
|
||||
fun migrateFromLargerHotseat() {
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_SHORTCUT, 2, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_SHORTCUT, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 5, CONTAINER_HOTSEAT, 0, 0, testPackage5, 5, TMP_TABLE)
|
||||
modelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0, testPackage1, 1, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(SHORTCUT, 2, HOTSEAT, 0, 0, testPackage2, 2, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(APP_ICON, 3, HOTSEAT, 0, 0, testPackage3, 3, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(SHORTCUT, 4, HOTSEAT, 0, 0, testPackage4, 4, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(APP_ICON, 5, HOTSEAT, 0, 0, testPackage5, 5, TMP_CONTENT_URI)
|
||||
|
||||
idp.numDatabaseHotseatIcons = 4
|
||||
idp.numColumns = 4
|
||||
@@ -534,7 +513,8 @@ class GridSizeMigrationUtilTest {
|
||||
val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
|
||||
val destReader = DbReader(db, TABLE_NAME, context, validPackages)
|
||||
GridSizeMigrationUtil.migrate(
|
||||
dbHelper,
|
||||
context,
|
||||
db,
|
||||
srcReader,
|
||||
destReader,
|
||||
idp.numDatabaseHotseatIcons,
|
||||
@@ -545,13 +525,12 @@ class GridSizeMigrationUtilTest {
|
||||
|
||||
// Check hotseat items
|
||||
val c =
|
||||
db.query(
|
||||
TABLE_NAME,
|
||||
context.contentResolver.query(
|
||||
CONTENT_URI,
|
||||
arrayOf(SCREEN, INTENT),
|
||||
"container=$CONTAINER_HOTSEAT",
|
||||
null,
|
||||
SCREEN,
|
||||
null,
|
||||
null
|
||||
)
|
||||
?: throw IllegalStateException()
|
||||
@@ -589,11 +568,11 @@ class GridSizeMigrationUtilTest {
|
||||
enableNewMigrationLogic("4,4")
|
||||
|
||||
// Setup src grid
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 2, testPackage1, 5, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 3, testPackage2, 6, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 1, CONTAINER_DESKTOP, 3, 1, testPackage3, 7, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 1, CONTAINER_DESKTOP, 3, 2, testPackage4, 8, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 2, CONTAINER_DESKTOP, 3, 3, testPackage5, 9, TMP_TABLE)
|
||||
modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 2, testPackage1, 5, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 3, testPackage2, 6, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(APP_ICON, 1, DESKTOP, 3, 1, testPackage3, 7, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(APP_ICON, 1, DESKTOP, 3, 2, testPackage4, 8, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(APP_ICON, 2, DESKTOP, 3, 3, testPackage5, 9, TMP_CONTENT_URI)
|
||||
|
||||
idp.numDatabaseHotseatIcons = 4
|
||||
idp.numColumns = 6
|
||||
@@ -602,7 +581,8 @@ class GridSizeMigrationUtilTest {
|
||||
val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
|
||||
val destReader = DbReader(db, TABLE_NAME, context, validPackages)
|
||||
GridSizeMigrationUtil.migrate(
|
||||
dbHelper,
|
||||
context,
|
||||
db,
|
||||
srcReader,
|
||||
destReader,
|
||||
idp.numDatabaseHotseatIcons,
|
||||
@@ -613,13 +593,12 @@ class GridSizeMigrationUtilTest {
|
||||
|
||||
// Get workspace items
|
||||
val c =
|
||||
db.query(
|
||||
TABLE_NAME,
|
||||
context.contentResolver.query(
|
||||
CONTENT_URI,
|
||||
arrayOf(INTENT, SCREEN),
|
||||
"container=$CONTAINER_DESKTOP",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
?: throw IllegalStateException()
|
||||
@@ -651,11 +630,11 @@ class GridSizeMigrationUtilTest {
|
||||
enableNewMigrationLogic("2,2")
|
||||
|
||||
// Setup src grid
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 0, 1, testPackage1, 5, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 1, 1, testPackage2, 6, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 1, CONTAINER_DESKTOP, 0, 0, testPackage3, 7, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 1, CONTAINER_DESKTOP, 1, 0, testPackage4, 8, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 2, CONTAINER_DESKTOP, 0, 0, testPackage5, 9, TMP_TABLE)
|
||||
modelHelper.addItem(APP_ICON, 0, DESKTOP, 0, 1, testPackage1, 5, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(APP_ICON, 0, DESKTOP, 1, 1, testPackage2, 6, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(APP_ICON, 1, DESKTOP, 0, 0, testPackage3, 7, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(APP_ICON, 1, DESKTOP, 1, 0, testPackage4, 8, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(APP_ICON, 2, DESKTOP, 0, 0, testPackage5, 9, TMP_CONTENT_URI)
|
||||
|
||||
idp.numDatabaseHotseatIcons = 4
|
||||
idp.numColumns = 5
|
||||
@@ -663,7 +642,8 @@ class GridSizeMigrationUtilTest {
|
||||
val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
|
||||
val destReader = DbReader(db, TABLE_NAME, context, validPackages)
|
||||
GridSizeMigrationUtil.migrate(
|
||||
dbHelper,
|
||||
context,
|
||||
db,
|
||||
srcReader,
|
||||
destReader,
|
||||
idp.numDatabaseHotseatIcons,
|
||||
@@ -674,13 +654,12 @@ class GridSizeMigrationUtilTest {
|
||||
|
||||
// Get workspace items
|
||||
val c =
|
||||
db.query(
|
||||
TABLE_NAME,
|
||||
context.contentResolver.query(
|
||||
CONTENT_URI,
|
||||
arrayOf(INTENT, SCREEN),
|
||||
"container=$CONTAINER_DESKTOP",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
?: throw IllegalStateException()
|
||||
@@ -712,11 +691,11 @@ class GridSizeMigrationUtilTest {
|
||||
enableNewMigrationLogic("5,5")
|
||||
|
||||
// Setup src grid
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 0, 1, testPackage1, 5, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 1, 1, testPackage2, 6, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 1, CONTAINER_DESKTOP, 0, 0, testPackage3, 7, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 1, CONTAINER_DESKTOP, 1, 0, testPackage4, 8, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 2, CONTAINER_DESKTOP, 0, 0, testPackage5, 9, TMP_TABLE)
|
||||
modelHelper.addItem(APP_ICON, 0, DESKTOP, 0, 1, testPackage1, 5, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(APP_ICON, 0, DESKTOP, 1, 1, testPackage2, 6, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(APP_ICON, 1, DESKTOP, 0, 0, testPackage3, 7, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(APP_ICON, 1, DESKTOP, 1, 0, testPackage4, 8, TMP_CONTENT_URI)
|
||||
modelHelper.addItem(APP_ICON, 2, DESKTOP, 0, 0, testPackage5, 9, TMP_CONTENT_URI)
|
||||
|
||||
idp.numDatabaseHotseatIcons = 4
|
||||
idp.numColumns = 4
|
||||
@@ -724,7 +703,8 @@ class GridSizeMigrationUtilTest {
|
||||
val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
|
||||
val destReader = DbReader(db, TABLE_NAME, context, validPackages)
|
||||
GridSizeMigrationUtil.migrate(
|
||||
dbHelper,
|
||||
context,
|
||||
db,
|
||||
srcReader,
|
||||
destReader,
|
||||
idp.numDatabaseHotseatIcons,
|
||||
@@ -735,13 +715,12 @@ class GridSizeMigrationUtilTest {
|
||||
|
||||
// Get workspace items
|
||||
val c =
|
||||
db.query(
|
||||
TABLE_NAME,
|
||||
context.contentResolver.query(
|
||||
CONTENT_URI,
|
||||
arrayOf(INTENT, SCREEN),
|
||||
"container=$CONTAINER_DESKTOP",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
?: throw IllegalStateException()
|
||||
@@ -768,48 +747,4 @@ class GridSizeMigrationUtilTest {
|
||||
private fun enableNewMigrationLogic(srcGridSize: String) {
|
||||
LauncherPrefs.get(context).putSync(WORKSPACE_SIZE.to(srcGridSize))
|
||||
}
|
||||
|
||||
private fun addItem(
|
||||
type: Int,
|
||||
screen: Int,
|
||||
container: Int,
|
||||
x: Int,
|
||||
y: Int,
|
||||
packageName: String?
|
||||
): Int {
|
||||
return addItem(
|
||||
type,
|
||||
screen,
|
||||
container,
|
||||
x,
|
||||
y,
|
||||
packageName,
|
||||
dbHelper.generateNewItemId(),
|
||||
TABLE_NAME
|
||||
)
|
||||
}
|
||||
|
||||
private fun addItem(
|
||||
type: Int,
|
||||
screen: Int,
|
||||
container: Int,
|
||||
x: Int,
|
||||
y: Int,
|
||||
packageName: String?,
|
||||
id: Int,
|
||||
tableName: String
|
||||
): Int {
|
||||
val values = ContentValues()
|
||||
values.put(_ID, id)
|
||||
values.put(CONTAINER, container)
|
||||
values.put(SCREEN, screen)
|
||||
values.put(CELLX, x)
|
||||
values.put(CELLY, y)
|
||||
values.put(SPANX, 1)
|
||||
values.put(SPANY, 1)
|
||||
values.put(ITEM_TYPE, type)
|
||||
values.put(INTENT, Intent(Intent.ACTION_MAIN).setPackage(packageName).toUri(0))
|
||||
db.insert(tableName, null, values)
|
||||
return id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.util.Executors;
|
||||
@@ -101,7 +102,7 @@ public class LoaderCursorTest {
|
||||
});
|
||||
|
||||
UserManagerState ums = new UserManagerState();
|
||||
mLoaderCursor = new LoaderCursor(mCursor, mApp, ums);
|
||||
mLoaderCursor = new LoaderCursor(mCursor, Favorites.CONTENT_URI, mApp, ums);
|
||||
ums.allUsers.put(0, Process.myUserHandle());
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,6 @@ import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.model.DatabaseHelper;
|
||||
import com.android.launcher3.model.DbDowngradeHelper;
|
||||
import com.android.launcher3.pm.UserCache;
|
||||
import com.android.launcher3.settings.SettingsActivity;
|
||||
import com.android.launcher3.shortcuts.ShortcutKey;
|
||||
import com.android.launcher3.util.IOUtils;
|
||||
@@ -166,11 +165,12 @@ public class LauncherDbUtilsTest {
|
||||
private class MyDatabaseHelper extends DatabaseHelper {
|
||||
|
||||
MyDatabaseHelper() {
|
||||
super(mContext, null, UserCache.INSTANCE.get(mContext)::getSerialNumberForUser,
|
||||
() -> { });
|
||||
super(mContext, null, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleOneTimeDataUpgrade(SQLiteDatabase db) { }
|
||||
|
||||
protected void onEmptyDbCreated() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.model.DatabaseHelper;
|
||||
import com.android.launcher3.model.ModelDbController;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -64,13 +63,13 @@ public class RestoreDbTaskTest {
|
||||
|
||||
@Test
|
||||
public void testGetProfileId() throws Exception {
|
||||
SQLiteDatabase db = new MyModelDbController(23).getDb();
|
||||
SQLiteDatabase db = new MyDatabaseHelper(23).getWritableDatabase();
|
||||
assertEquals(23, new RestoreDbTask().getDefaultProfileId(db));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMigrateProfileId() throws Exception {
|
||||
SQLiteDatabase db = new MyModelDbController(42).getDb();
|
||||
SQLiteDatabase db = new MyDatabaseHelper(42).getWritableDatabase();
|
||||
// Add some mock data
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ContentValues values = new ContentValues();
|
||||
@@ -90,7 +89,7 @@ public class RestoreDbTaskTest {
|
||||
|
||||
@Test
|
||||
public void testChangeDefaultColumn() throws Exception {
|
||||
SQLiteDatabase db = new MyModelDbController(42).getDb();
|
||||
SQLiteDatabase db = new MyDatabaseHelper(42).getWritableDatabase();
|
||||
// Add some mock data
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ContentValues values = new ContentValues();
|
||||
@@ -121,20 +120,20 @@ public class RestoreDbTaskTest {
|
||||
long workProfileId = myProfileId + 2;
|
||||
long workProfileId_old = myProfileId + 3;
|
||||
|
||||
MyModelDbController controller = new MyModelDbController(myProfileId);
|
||||
SQLiteDatabase db = controller.getDb();
|
||||
MyDatabaseHelper helper = new MyDatabaseHelper(myProfileId);
|
||||
SQLiteDatabase db = helper.getWritableDatabase();
|
||||
BackupManager bm = spy(new BackupManager(context));
|
||||
doReturn(myUserHandle()).when(bm).getUserForAncestralSerialNumber(eq(myProfileId_old));
|
||||
doReturn(mWorkUser).when(bm).getUserForAncestralSerialNumber(eq(workProfileId_old));
|
||||
controller.users.put(workProfileId, mWorkUser);
|
||||
helper.users.put(workProfileId, mWorkUser);
|
||||
|
||||
addIconsBulk(controller, 10, 1, myProfileId_old);
|
||||
addIconsBulk(controller, 6, 2, workProfileId_old);
|
||||
addIconsBulk(helper, 10, 1, myProfileId_old);
|
||||
addIconsBulk(helper, 6, 2, workProfileId_old);
|
||||
assertEquals(10, getItemCountForProfile(db, myProfileId_old));
|
||||
assertEquals(6, getItemCountForProfile(db, workProfileId_old));
|
||||
|
||||
RestoreDbTask task = new RestoreDbTask();
|
||||
task.sanitizeDB(context, controller, controller.getDb(), bm);
|
||||
task.sanitizeDB(context, helper, helper.getWritableDatabase(), bm);
|
||||
|
||||
// All the data has been migrated to the new user ids
|
||||
assertEquals(0, getItemCountForProfile(db, myProfileId_old));
|
||||
@@ -152,20 +151,20 @@ public class RestoreDbTaskTest {
|
||||
long myProfileId_old = myProfileId + 1;
|
||||
long workProfileId_old = myProfileId + 3;
|
||||
|
||||
MyModelDbController controller = new MyModelDbController(myProfileId);
|
||||
SQLiteDatabase db = controller.getDb();
|
||||
MyDatabaseHelper helper = new MyDatabaseHelper(myProfileId);
|
||||
SQLiteDatabase db = helper.getWritableDatabase();
|
||||
BackupManager bm = spy(new BackupManager(context));
|
||||
doReturn(myUserHandle()).when(bm).getUserForAncestralSerialNumber(eq(myProfileId_old));
|
||||
// Work profile is not migrated
|
||||
doReturn(null).when(bm).getUserForAncestralSerialNumber(eq(workProfileId_old));
|
||||
|
||||
addIconsBulk(controller, 10, 1, myProfileId_old);
|
||||
addIconsBulk(controller, 6, 2, workProfileId_old);
|
||||
addIconsBulk(helper, 10, 1, myProfileId_old);
|
||||
addIconsBulk(helper, 6, 2, workProfileId_old);
|
||||
assertEquals(10, getItemCountForProfile(db, myProfileId_old));
|
||||
assertEquals(6, getItemCountForProfile(db, workProfileId_old));
|
||||
|
||||
RestoreDbTask task = new RestoreDbTask();
|
||||
task.sanitizeDB(context, controller, controller.getDb(), bm);
|
||||
task.sanitizeDB(context, helper, helper.getWritableDatabase(), bm);
|
||||
|
||||
// All the data has been migrated to the new user ids
|
||||
assertEquals(0, getItemCountForProfile(db, myProfileId_old));
|
||||
@@ -174,13 +173,12 @@ public class RestoreDbTaskTest {
|
||||
assertEquals(10, getCount(db, "select * from favorites"));
|
||||
}
|
||||
|
||||
private void addIconsBulk(MyModelDbController controller,
|
||||
int count, int screen, long profileId) {
|
||||
private void addIconsBulk(DatabaseHelper helper, int count, int screen, long profileId) {
|
||||
int columns = LauncherAppState.getIDP(getInstrumentation().getTargetContext()).numColumns;
|
||||
String packageName = getInstrumentation().getContext().getPackageName();
|
||||
for (int i = 0; i < count; i++) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(LauncherSettings.Favorites._ID, controller.generateNewItemId());
|
||||
values.put(LauncherSettings.Favorites._ID, helper.generateNewItemId());
|
||||
values.put(LauncherSettings.Favorites.CONTAINER, CONTAINER_DESKTOP);
|
||||
values.put(LauncherSettings.Favorites.SCREEN, screen);
|
||||
values.put(LauncherSettings.Favorites.CELLX, i % columns);
|
||||
@@ -191,11 +189,11 @@ public class RestoreDbTaskTest {
|
||||
values.put(LauncherSettings.Favorites.ITEM_TYPE, ITEM_TYPE_APPLICATION);
|
||||
values.put(LauncherSettings.Favorites.INTENT,
|
||||
new Intent(Intent.ACTION_MAIN).setPackage(packageName).toUri(0));
|
||||
|
||||
controller.insert(TABLE_NAME, values);
|
||||
helper.getWritableDatabase().insert(TABLE_NAME, null, values);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRemoveScreenIdGaps_firstScreenEmpty() {
|
||||
runRemoveScreenIdGapsTest(
|
||||
@@ -218,7 +216,7 @@ public class RestoreDbTaskTest {
|
||||
}
|
||||
|
||||
private void runRemoveScreenIdGapsTest(int[] screenIds, int[] expectedScreenIds) {
|
||||
SQLiteDatabase db = new MyModelDbController(42).getDb();
|
||||
SQLiteDatabase db = new MyDatabaseHelper(42).getWritableDatabase();
|
||||
// Add some mock data
|
||||
for (int i = 0; i < screenIds.length; i++) {
|
||||
ContentValues values = new ContentValues();
|
||||
@@ -256,15 +254,14 @@ public class RestoreDbTaskTest {
|
||||
}
|
||||
}
|
||||
|
||||
private class MyModelDbController extends ModelDbController {
|
||||
private class MyDatabaseHelper extends DatabaseHelper {
|
||||
|
||||
public final LongSparseArray<UserHandle> users = new LongSparseArray<>();
|
||||
public final LongSparseArray<UserHandle> users;
|
||||
|
||||
MyModelDbController(long profileId) {
|
||||
super(getInstrumentation().getTargetContext());
|
||||
MyDatabaseHelper(long profileId) {
|
||||
super(getInstrumentation().getTargetContext(), null, false);
|
||||
users = new LongSparseArray<>();
|
||||
users.put(profileId, myUserHandle());
|
||||
mOpenHelper = new DatabaseHelper(getInstrumentation().getTargetContext(), null,
|
||||
this::getSerialNumberForUser, () -> { });
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -272,5 +269,10 @@ public class RestoreDbTaskTest {
|
||||
int index = users.indexOfValue(user);
|
||||
return index >= 0 ? users.keyAt(index) : -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleOneTimeDataUpgrade(SQLiteDatabase db) { }
|
||||
|
||||
protected void onEmptyDbCreated() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,25 +15,27 @@
|
||||
*/
|
||||
package com.android.launcher3.ui.widget;
|
||||
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
import static com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.tapl.Widget;
|
||||
import com.android.launcher3.tapl.WidgetResizeFrame;
|
||||
import com.android.launcher3.tapl.Widgets;
|
||||
import com.android.launcher3.testcomponent.WidgetConfigActivity;
|
||||
import com.android.launcher3.ui.AbstractLauncherUiTest;
|
||||
import com.android.launcher3.ui.TestViewHelpers;
|
||||
import com.android.launcher3.util.Wait;
|
||||
import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
|
||||
import com.android.launcher3.util.rule.ShellCommandRule;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
|
||||
@@ -50,7 +52,6 @@ import org.junit.runner.RunWith;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class AddConfigWidgetTest extends AbstractLauncherUiTest {
|
||||
|
||||
private static final String WIDGET_PROVIDER_INFO = "b/276794291";
|
||||
@Rule
|
||||
public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
|
||||
|
||||
@@ -88,33 +89,51 @@ public class AddConfigWidgetTest extends AbstractLauncherUiTest {
|
||||
clearHomescreen();
|
||||
mDevice.pressHome();
|
||||
|
||||
final Widgets widgets = mLauncher.getWorkspace().openAllWidgets();
|
||||
|
||||
// Drag widget to homescreen
|
||||
WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor();
|
||||
WidgetResizeFrame resizeFrame =
|
||||
widgets.getWidget(mWidgetInfo.getLabel(mTargetContext.getPackageManager()))
|
||||
.dragConfigWidgetToWorkspace(acceptConfig);
|
||||
mLauncher.getWorkspace()
|
||||
.openAllWidgets()
|
||||
.getWidget(mWidgetInfo.getLabel(mTargetContext.getPackageManager()))
|
||||
.dragToWorkspace(true, false);
|
||||
// Widget id for which the config activity was opened
|
||||
mWidgetId = monitor.getWidgetId();
|
||||
|
||||
Log.e(WIDGET_PROVIDER_INFO,
|
||||
"InstalledProviders count: " + mAppWidgetManager.getInstalledProviders().size());
|
||||
|
||||
// Verify that the widget id is valid and bound
|
||||
assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId));
|
||||
|
||||
setResult(acceptConfig);
|
||||
if (acceptConfig) {
|
||||
assertNotNull("Widget resize frame not shown after widget added", resizeFrame);
|
||||
resizeFrame.dismiss();
|
||||
|
||||
final Widget widget =
|
||||
mLauncher.getWorkspace().tryGetWidget(mWidgetInfo.label, DEFAULT_UI_TIMEOUT);
|
||||
assertNotNull("Widget not found on the workspace", widget);
|
||||
Wait.atMost("", new WidgetSearchCondition(), DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
|
||||
assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId));
|
||||
} else {
|
||||
final Widget widget =
|
||||
mLauncher.getWorkspace().tryGetWidget(mWidgetInfo.label, DEFAULT_UI_TIMEOUT);
|
||||
assertNull("Widget unexpectedly found on the workspace", widget);
|
||||
// Verify that the widget id is deleted.
|
||||
Wait.atMost("", () -> mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null,
|
||||
DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
|
||||
}
|
||||
}
|
||||
|
||||
private void setResult(boolean success) {
|
||||
getInstrumentation().getTargetContext().sendBroadcast(
|
||||
WidgetConfigActivity.getCommandIntent(WidgetConfigActivity.class,
|
||||
success ? "clickOK" : "clickCancel"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Condition for searching widget id
|
||||
*/
|
||||
private class WidgetSearchCondition implements Wait.Condition, ItemOperator {
|
||||
|
||||
@Override
|
||||
public boolean isTrue() throws Throwable {
|
||||
return mMainThreadExecutor.submit(mActivityMonitor.itemExists(this)).get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean evaluate(ItemInfo info, View view) {
|
||||
return info instanceof LauncherAppWidgetInfo
|
||||
&& ((LauncherAppWidgetInfo) info).providerName.getClassName().equals(
|
||||
mWidgetInfo.provider.getClassName())
|
||||
&& ((LauncherAppWidgetInfo) info).appWidgetId == mWidgetId;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -363,6 +363,12 @@ public class LauncherModelHelper {
|
||||
sandboxContext.getContentResolver().insert(contentUri, values);
|
||||
}
|
||||
|
||||
public void deleteItem(int itemId, @NonNull final String tableName) {
|
||||
final Uri uri = Uri.parse("content://"
|
||||
+ LauncherProvider.AUTHORITY + "/" + tableName + "/" + itemId);
|
||||
sandboxContext.getContentResolver().delete(uri, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a mock provider to load the provided layout by default, next time the layout loads
|
||||
*/
|
||||
@@ -420,7 +426,7 @@ public class LauncherModelHelper {
|
||||
}
|
||||
|
||||
public SQLiteDatabase getDb() {
|
||||
return getModelDbController().getDb();
|
||||
return getModelDbController().getDatabaseHelper().getWritableDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -161,7 +162,6 @@ public final class LauncherAppWidgetProviderInfoTest {
|
||||
int maxPadding = Math.max(Math.max(padding.left, padding.right),
|
||||
Math.max(padding.top, padding.bottom));
|
||||
dp.cellLayoutBorderSpacePx.x = dp.cellLayoutBorderSpacePx.y = maxPadding + 1;
|
||||
Mockito.when(dp.shouldInsetWidgets()).thenReturn(true);
|
||||
|
||||
LauncherAppWidgetProviderInfo info = new LauncherAppWidgetProviderInfo();
|
||||
info.minWidth = CELL_SIZE * 3;
|
||||
@@ -184,7 +184,6 @@ public final class LauncherAppWidgetProviderInfoTest {
|
||||
int maxPadding = Math.max(Math.max(padding.left, padding.right),
|
||||
Math.max(padding.top, padding.bottom));
|
||||
dp.cellLayoutBorderSpacePx.x = dp.cellLayoutBorderSpacePx.y = maxPadding - 1;
|
||||
Mockito.when(dp.shouldInsetWidgets()).thenReturn(false);
|
||||
LauncherAppWidgetProviderInfo info = new LauncherAppWidgetProviderInfo();
|
||||
info.minWidth = CELL_SIZE * 3;
|
||||
info.minHeight = CELL_SIZE * 3;
|
||||
@@ -257,14 +256,16 @@ public final class LauncherAppWidgetProviderInfoTest {
|
||||
}
|
||||
|
||||
private InvariantDeviceProfile createIDP() {
|
||||
DeviceProfile profile = Mockito.mock(DeviceProfile.class);
|
||||
DeviceProfile dp = LauncherAppState.getIDP(mContext)
|
||||
.getDeviceProfile(mContext).copy(mContext);
|
||||
DeviceProfile profile = Mockito.spy(dp);
|
||||
doAnswer(i -> {
|
||||
((Point) i.getArgument(0)).set(CELL_SIZE, CELL_SIZE);
|
||||
return null;
|
||||
}).when(profile).getCellSize(any(Point.class));
|
||||
Mockito.when(profile.getCellSize()).thenReturn(new Point(CELL_SIZE, CELL_SIZE));
|
||||
profile.cellLayoutBorderSpacePx = new Point(SPACE_SIZE, SPACE_SIZE);
|
||||
Mockito.when(profile.shouldInsetWidgets()).thenReturn(true);
|
||||
profile.widgetPadding.setEmpty();
|
||||
|
||||
InvariantDeviceProfile idp = new InvariantDeviceProfile();
|
||||
List<DeviceProfile> supportedProfiles = new ArrayList<>(idp.supportedProfiles);
|
||||
|
||||
@@ -38,6 +38,7 @@ import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.icons.ComponentWithLabel;
|
||||
import com.android.launcher3.icons.IconCache;
|
||||
import com.android.launcher3.model.WidgetItem;
|
||||
@@ -50,6 +51,7 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -68,7 +70,6 @@ public final class WidgetsTableUtilsTest {
|
||||
@Mock
|
||||
private IconCache mIconCache;
|
||||
|
||||
@Mock
|
||||
private DeviceProfile mTestDeviceProfile;
|
||||
|
||||
private Context mContext;
|
||||
@@ -198,13 +199,18 @@ public final class WidgetsTableUtilsTest {
|
||||
}
|
||||
|
||||
private void initDP() {
|
||||
DeviceProfile dp = LauncherAppState.getIDP(mContext)
|
||||
.getDeviceProfile(mContext).copy(mContext);
|
||||
mTestDeviceProfile = Mockito.spy(dp);
|
||||
|
||||
doAnswer(i -> {
|
||||
((Point) i.getArgument(0)).set(CELL_SIZE, CELL_SIZE);
|
||||
return null;
|
||||
}).when(mTestDeviceProfile).getCellSize(any(Point.class));
|
||||
when(mTestDeviceProfile.getCellSize()).thenReturn(new Point(CELL_SIZE, CELL_SIZE));
|
||||
mTestDeviceProfile.cellLayoutBorderSpacePx = new Point(SPACE_SIZE, SPACE_SIZE);
|
||||
when(mTestDeviceProfile.shouldInsetWidgets()).thenReturn(false);
|
||||
mTestDeviceProfile.widgetPadding.setEmpty();
|
||||
mTestDeviceProfile.allAppsIconSizePx = 0;
|
||||
}
|
||||
|
||||
private void initTestWidgets() {
|
||||
|
||||
@@ -107,6 +107,8 @@ public final class LauncherInstrumentation {
|
||||
static final Pattern EVENT_TOUCH_DOWN_TIS = getTouchEventPatternTIS("ACTION_DOWN");
|
||||
static final Pattern EVENT_TOUCH_UP_TIS = getTouchEventPatternTIS("ACTION_UP");
|
||||
static final Pattern EVENT_TOUCH_CANCEL_TIS = getTouchEventPatternTIS("ACTION_CANCEL");
|
||||
static final Pattern EVENT_HOVER_ENTER_TIS = getTouchEventPatternTIS("ACTION_HOVER_ENTER");
|
||||
static final Pattern EVENT_HOVER_EXIT_TIS = getTouchEventPatternTIS("ACTION_HOVER_EXIT");
|
||||
|
||||
private static final Pattern EVENT_KEY_BACK_DOWN =
|
||||
getKeyEventPattern("ACTION_DOWN", "KEYCODE_BACK");
|
||||
@@ -140,7 +142,9 @@ public final class LauncherInstrumentation {
|
||||
* Represents a point in the code at which a callback can run.
|
||||
*/
|
||||
public enum CALLBACK_RUN_POINT {
|
||||
CALLBACK_HOLD_BEFORE_DROP
|
||||
CALLBACK_HOLD_BEFORE_DROP,
|
||||
CALLBACK_HOVER_ENTER,
|
||||
CALLBACK_HOVER_EXIT,
|
||||
}
|
||||
|
||||
private Consumer<CALLBACK_RUN_POINT> mCallbackAtRunPoint = null;
|
||||
@@ -1682,6 +1686,12 @@ public final class LauncherInstrumentation {
|
||||
? EVENT_TOUCH_CANCEL_TIS : EVENT_TOUCH_UP_TIS);
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_HOVER_ENTER:
|
||||
expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_HOVER_ENTER_TIS);
|
||||
break;
|
||||
case MotionEvent.ACTION_HOVER_EXIT:
|
||||
expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_HOVER_EXIT_TIS);
|
||||
break;
|
||||
}
|
||||
|
||||
final MotionEvent event = getMotionEvent(downTime, currentTime, action, point.x, point.y);
|
||||
|
||||
@@ -55,4 +55,12 @@ public class OverviewTaskMenu {
|
||||
UiObject2 menuItem = mLauncher.findObjectInContainer(mMenu, By.text(expectedMenuItemText));
|
||||
return menuItem != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the menu item specified by name if present.
|
||||
*/
|
||||
public OverviewTaskMenuItem getMenuItemByName(String menuItemName) {
|
||||
return new OverviewTaskMenuItem(mLauncher,
|
||||
mLauncher.waitForObjectInContainer(mMenu, By.text(menuItemName)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.tapl;
|
||||
|
||||
import static com.android.launcher3.tapl.LauncherInstrumentation.CALLBACK_RUN_POINT.CALLBACK_HOVER_ENTER;
|
||||
import static com.android.launcher3.tapl.LauncherInstrumentation.CALLBACK_RUN_POINT.CALLBACK_HOVER_EXIT;
|
||||
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.os.SystemClock;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import androidx.test.uiautomator.UiObject2;
|
||||
|
||||
/** Represents an item in the overview task menu. */
|
||||
public class OverviewTaskMenuItem {
|
||||
|
||||
private final LauncherInstrumentation mLauncher;
|
||||
private final UiObject2 mMenuItem;
|
||||
|
||||
OverviewTaskMenuItem(LauncherInstrumentation launcher, UiObject2 menuItem) {
|
||||
mLauncher = launcher;
|
||||
mMenuItem = menuItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this menu item's visible bounds.
|
||||
*/
|
||||
public Rect getVisibleBounds() {
|
||||
return mMenuItem.getVisibleBounds();
|
||||
}
|
||||
|
||||
/**
|
||||
* Emulate the cursor entering and exiting a hover over this menu item.
|
||||
*/
|
||||
public void hoverCursor() {
|
||||
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
|
||||
LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
|
||||
"cursor hover entering menu item")) {
|
||||
long downTime = SystemClock.uptimeMillis();
|
||||
mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_ENTER,
|
||||
new Point(mMenuItem.getVisibleCenter().x, mMenuItem.getVisibleCenter().y),
|
||||
null);
|
||||
mLauncher.runCallbackIfActive(CALLBACK_HOVER_ENTER);
|
||||
|
||||
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
|
||||
"cursor hover exiting menu item")) {
|
||||
downTime = SystemClock.uptimeMillis();
|
||||
mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_EXIT,
|
||||
new Point(mMenuItem.getVisibleCenter().x, mMenuItem.getVisibleCenter().y),
|
||||
null);
|
||||
mLauncher.runCallbackIfActive(CALLBACK_HOVER_EXIT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,7 @@
|
||||
package com.android.launcher3.tapl;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.test.uiautomator.By;
|
||||
import androidx.test.uiautomator.BySelector;
|
||||
import androidx.test.uiautomator.UiObject2;
|
||||
import androidx.test.uiautomator.Until;
|
||||
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
|
||||
@@ -69,10 +65,7 @@ public final class Widget extends Launchable implements WorkspaceDragSource {
|
||||
*/
|
||||
@NonNull
|
||||
public WidgetResizeFrame dragWidgetToWorkspace() {
|
||||
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
|
||||
return dragWidgetToWorkspace(/* configurable= */ false, /* acceptsConfig= */ false, -1,
|
||||
-1, 1, 1);
|
||||
}
|
||||
return dragWidgetToWorkspace(-1, -1, 1, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,22 +77,19 @@ public final class Widget extends Launchable implements WorkspaceDragSource {
|
||||
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
|
||||
LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
|
||||
"Dragging widget to workspace cell " + cellX + "," + cellY)) {
|
||||
return dragWidgetToWorkspace(/* configurable= */ false, /* acceptsConfig= */ false,
|
||||
cellX, cellY, spanX, spanY);
|
||||
}
|
||||
}
|
||||
if (cellX == -1 || cellY == -1) {
|
||||
internalDragToWorkspace(/* startsActivity= */ false, /* isWidgetShortcut= */
|
||||
false);
|
||||
} else {
|
||||
dragToWorkspaceCellPosition(/* startsActivity= */ false, /* isWidgetShortcut= */
|
||||
false, cellX, cellY, spanX, spanY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drags a configurable widget from the widgets container to the workspace, either accepts or
|
||||
* cancels the configuration based on {@code acceptsConfig}, and returns the resize frame that
|
||||
* is shown if the widget is added.
|
||||
*/
|
||||
@Nullable
|
||||
public WidgetResizeFrame dragConfigWidgetToWorkspace(boolean acceptsConfig) {
|
||||
// TODO(b/239438337, fransebas) add correct event checking for this case
|
||||
//try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
|
||||
return dragWidgetToWorkspace(/* configurable= */ true, acceptsConfig, -1, -1, 1, 1);
|
||||
//}
|
||||
try (LauncherInstrumentation.Closable closable = mLauncher.addContextLayer(
|
||||
"want to get widget resize frame")) {
|
||||
return new WidgetResizeFrame(mLauncher);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,53 +112,4 @@ public final class Widget extends Launchable implements WorkspaceDragSource {
|
||||
isWidgetShortcut,
|
||||
launchable::addExpectedEventsForLongClick);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drags a widget from the widgets container to the workspace and returns the resize frame that
|
||||
* is shown after the widget is added.
|
||||
*
|
||||
* <p> If {@code configurable} is true, then either accepts or cancels the configuration based
|
||||
* on {@code acceptsConfig}.
|
||||
* <p> If either {@code cellX} or {@code cellY} are negative, then a default location would be
|
||||
* chosen
|
||||
*
|
||||
* @param configurable if the widget has a configuration activity.
|
||||
* @param acceptsConfig if the widget has a configuration, then if we should accept it or
|
||||
* cancel it
|
||||
* @param cellX X position to drop the widget in the workspace
|
||||
* @param cellY Y position to drop the widget in the workspace
|
||||
* @return returns the given resize frame of the widget after being dropped, if
|
||||
* configurable is true and acceptsConfig is false then the widget would not be places and will
|
||||
* be cancel and it returns null.
|
||||
*/
|
||||
@Nullable
|
||||
private WidgetResizeFrame dragWidgetToWorkspace(boolean configurable, boolean acceptsConfig,
|
||||
int cellX, int cellY, int spanX, int spanY) {
|
||||
if (cellX == -1 || cellY == -1) {
|
||||
internalDragToWorkspace(/* startsActivity= */ configurable, /* isWidgetShortcut= */
|
||||
false);
|
||||
} else {
|
||||
dragToWorkspaceCellPosition(/* startsActivity= */ configurable, /* isWidgetShortcut= */
|
||||
false, cellX, cellY, spanX, spanY);
|
||||
}
|
||||
|
||||
if (configurable) {
|
||||
// Configure the widget.
|
||||
BySelector selector = By.text(acceptsConfig ? "OK" : "Cancel");
|
||||
mLauncher.getDevice()
|
||||
.wait(Until.findObject(selector), LauncherInstrumentation.WAIT_TIME_MS)
|
||||
.click();
|
||||
|
||||
// If the widget configuration was cancelled, then the widget wasn't added to the home
|
||||
// screen. In that case, we cannot return a resize frame.
|
||||
if (!acceptsConfig) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
|
||||
"want to get widget resize frame")) {
|
||||
return new WidgetResizeFrame(mLauncher);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user