Merge "Fix NPE" into jb-ub-now-indigo-rose
diff --git a/Android.mk b/Android.mk
index 016d151..10c9f24 100644
--- a/Android.mk
+++ b/Android.mk
@@ -21,7 +21,13 @@
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v13
-LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src) \
+ $(call all-proto-files-under, protos)
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := nano
+
+LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos
+
LOCAL_SDK_VERSION := 17
LOCAL_PACKAGE_NAME := Launcher3
diff --git a/protos/backup.proto b/protos/backup.proto
new file mode 100644
index 0000000..3780bc5
--- /dev/null
+++ b/protos/backup.proto
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package launcher_backup;
+
+option java_package = "com.android.launcher3.backup";
+option java_outer_classname = "BackupProtos";
+
+message Key {
+ enum Type {
+ FAVORITE = 1;
+ SCREEN = 2;
+ IMAGE = 3;
+ }
+ required Type type = 1;
+ optional string name = 2; // keep this short
+ optional int64 id = 3;
+ optional int64 checksum = 4;
+}
+
+message CheckedMessage {
+ required bytes payload = 1;
+ required int64 checksum = 2;
+}
+
+message Journal {
+ required int32 app_version = 1;
+ required int64 t = 2;
+ optional int64 bytes = 3;
+ optional int32 rows = 4;
+ repeated Key key = 5;
+}
+
+message Favorite {
+ required int64 id = 1;
+ required int32 itemType = 2;
+ optional string title = 3;
+ optional int32 container = 4;
+ optional int32 screen = 5;
+ optional int32 cellX = 6;
+ optional int32 cellY = 7;
+ optional int32 spanX = 8;
+ optional int32 spanY = 9;
+ optional int32 displayMode = 10;
+ optional int32 appWidgetId = 11;
+ optional string appWidgetProvider = 12;
+ optional string intent = 13;
+ optional string uri = 14;
+ optional int32 iconType = 15;
+ optional string iconPackage = 16;
+ optional string iconResource = 17;
+ optional bytes icon = 18;
+ }
+
+message Screen {
+ required int64 id = 1;
+ optional int32 rank = 2;
+ }
+
+message Resource {
+ required int32 dpi = 2;
+ required bytes data = 3;
+ }
diff --git a/res/layout-land/first_run_cling.xml b/res/layout-land/first_run_cling.xml
index f827380..3b21e14 100644
--- a/res/layout-land/first_run_cling.xml
+++ b/res/layout-land/first_run_cling.xml
@@ -34,9 +34,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="10dp"
android:text="@string/first_run_cling_title"
- android:textColor="#49C0EC"
- android:textSize="32sp" />
+ android:textColor="#FFFFFFFF"
+ android:textSize="30sp" />
<TextView
style="@style/ClingAltTitleText"
android:layout_width="wrap_content"
@@ -47,22 +48,28 @@
</LinearLayout>
<TextView
style="@style/ClingHintText"
+ android:id="@+id/search_bar_hint"
android:layout_width="160dp"
android:layout_height="wrap_content"
android:layout_gravity="top|end"
android:layout_marginEnd="10dp"
android:layout_marginTop="80dp"
- android:text="@string/first_run_cling_search_bar_hint"
- android:visibility="gone" />
+ android:visibility="gone"
+ android:drawableTop="@drawable/cling_arrow_up"
+ android:drawablePadding="5dp"
+ android:text="@string/first_run_cling_search_bar_hint" />
<TextView
style="@style/ClingHintText"
+ android:id="@+id/custom_content_hint"
android:layout_width="160dp"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_marginStart="10dp"
android:layout_marginTop="100dp"
- android:text="@string/first_run_cling_custom_content_hint"
- android:visibility="gone" />
+ android:visibility="gone"
+ android:drawableStart="@drawable/cling_arrow_left"
+ android:drawablePadding="10dp"
+ android:text="@string/first_run_cling_custom_content_hint" />
<TextView
style="@style/ClingHintText"
android:layout_width="160dp"
@@ -70,8 +77,9 @@
android:layout_gravity="bottom|end"
android:layout_marginEnd="10dp"
android:layout_marginBottom="100dp"
- android:text="@string/first_run_cling_create_screens_hint"
- android:visibility="gone" />
+ android:drawableEnd="@drawable/cling_arrow_right"
+ android:drawablePadding="5dp"
+ android:text="@string/first_run_cling_create_screens_hint" />
</FrameLayout>
<Button
style="@style/ClingButton"
diff --git a/res/layout-port/first_run_cling.xml b/res/layout-port/first_run_cling.xml
index cdc49b9..3b21e14 100644
--- a/res/layout-port/first_run_cling.xml
+++ b/res/layout-port/first_run_cling.xml
@@ -36,7 +36,7 @@
android:layout_gravity="center_horizontal"
android:layout_marginBottom="10dp"
android:text="@string/first_run_cling_title"
- android:textColor="#49C0EC"
+ android:textColor="#FFFFFFFF"
android:textSize="30sp" />
<TextView
style="@style/ClingAltTitleText"
@@ -48,19 +48,27 @@
</LinearLayout>
<TextView
style="@style/ClingHintText"
+ android:id="@+id/search_bar_hint"
android:layout_width="160dp"
android:layout_height="wrap_content"
android:layout_gravity="top|end"
android:layout_marginEnd="10dp"
android:layout_marginTop="80dp"
+ android:visibility="gone"
+ android:drawableTop="@drawable/cling_arrow_up"
+ android:drawablePadding="5dp"
android:text="@string/first_run_cling_search_bar_hint" />
<TextView
style="@style/ClingHintText"
+ android:id="@+id/custom_content_hint"
android:layout_width="160dp"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_marginStart="10dp"
android:layout_marginTop="100dp"
+ android:visibility="gone"
+ android:drawableStart="@drawable/cling_arrow_left"
+ android:drawablePadding="10dp"
android:text="@string/first_run_cling_custom_content_hint" />
<TextView
style="@style/ClingHintText"
@@ -70,6 +78,7 @@
android:layout_marginEnd="10dp"
android:layout_marginBottom="100dp"
android:drawableEnd="@drawable/cling_arrow_right"
+ android:drawablePadding="5dp"
android:text="@string/first_run_cling_create_screens_hint" />
</FrameLayout>
<Button
diff --git a/res/layout/apps_customize_pane.xml b/res/layout/apps_customize_pane.xml
index b01add9..e488601 100644
--- a/res/layout/apps_customize_pane.xml
+++ b/res/layout/apps_customize_pane.xml
@@ -16,7 +16,7 @@
<com.android.launcher3.AppsCustomizeTabHost
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
- android:background="#FF000000">
+ android:background="#80FFFFFF">
<LinearLayout
android:id="@+id/apps_customize_content"
android:orientation="vertical"
diff --git a/res/layout/apps_customize_widget.xml b/res/layout/apps_customize_widget.xml
index ad677e9..f2d2342 100644
--- a/res/layout/apps_customize_widget.xml
+++ b/res/layout/apps_customize_widget.xml
@@ -35,8 +35,7 @@
android:paddingStart="@dimen/app_widget_preview_padding_left"
android:paddingEnd="@dimen/app_widget_preview_padding_right"
android:scaleType="matrix"
- android:background="@drawable/widget_container_holo" />
-
+ android:background="@drawable/screenpanel" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -57,7 +56,10 @@
android:textColor="#FFFFFFFF"
android:textSize="13sp"
- android:textAlignment="viewStart" />
+ android:textAlignment="viewStart"
+ android:fontFamily="sans-serif-condensed"
+ android:shadowRadius="2.0"
+ android:shadowColor="#B0000000" />
<!-- The original dimensions of the widget (can't be the same text as above due to different
style. -->
@@ -70,8 +72,11 @@
android:layout_weight="0"
android:gravity="start"
- android:textColor="#FF555555"
- android:textSize="12sp" />
+ android:textColor="#FFAAAAAA"
+ android:textSize="12sp"
+ android:fontFamily="sans-serif-condensed"
+ android:shadowRadius="2.0"
+ android:shadowColor="#B0000000" />
</LinearLayout>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 48a06fc..43a856d 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -35,4 +35,6 @@
<color name="wallpaper_picker_translucent_gray">#66000000</color>
<color name="folder_items_text_color">#FF333333</color>
<color name="outline_color">#FFFFFFFF</color>
+
+ <color name="first_run_cling_circle_background_color">#FF83AEE8</color>
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index e6bb935..9f2a105 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -94,10 +94,11 @@
</style>
<style name="WorkspaceIcon.AppsCustomize">
- <item name="android:shadowRadius">0.0</item> <!-- Don't use text shadow -->
<item name="android:background">@null</item>
<item name="android:textColor">@color/apps_customize_icon_text_color</item>
<item name="android:drawablePadding">4dp</item>
+ <item name="android:shadowRadius">4.0</item>
+ <item name="android:shadowColor">#FF000000</item>
</style>
<style name="QSBBar">
diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java
index 213e50a..dd870e4 100644
--- a/src/com/android/launcher3/AppsCustomizePagedView.java
+++ b/src/com/android/launcher3/AppsCustomizePagedView.java
@@ -499,13 +499,7 @@
if (mPressedIcon != null) {
mPressedIcon.lockDrawableState();
}
-
- // NOTE: We want all transitions from launcher to act as if the wallpaper were enabled
- // to be consistent. So re-enable the flag here, and we will re-disable it as necessary
- // when Launcher resumes and we are still in AllApps.
- mLauncher.updateWallpaperVisibility(true);
mLauncher.startActivitySafely(v, appInfo.intent, appInfo);
-
} else if (v instanceof PagedViewWidget) {
// Let the user know that they have to long press to add a widget
if (mWidgetInstructionToast != null) {
diff --git a/src/com/android/launcher3/AppsCustomizeTabHost.java b/src/com/android/launcher3/AppsCustomizeTabHost.java
index 89e74b2..bfcf92a 100644
--- a/src/com/android/launcher3/AppsCustomizeTabHost.java
+++ b/src/com/android/launcher3/AppsCustomizeTabHost.java
@@ -22,6 +22,7 @@
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Color;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.LayoutInflater;
@@ -81,13 +82,6 @@
setOnTabChangedListener(this);
}
- void selectAppsTab() {
- setContentTypeImmediate(AppsCustomizePagedView.ContentType.Applications);
- }
- void selectWidgetsTab() {
- setContentTypeImmediate(AppsCustomizePagedView.ContentType.Widgets);
- }
-
@Override
public void setInsets(Rect insets) {
mInsets.set(insets);
@@ -203,6 +197,9 @@
}
private void onTabChangedEnd(AppsCustomizePagedView.ContentType type) {
+ int bgAlpha = (int) (255 * (getResources().getInteger(
+ R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f));
+ setBackgroundColor(Color.argb(bgAlpha, 0, 0, 0));
mAppsCustomizePane.setContentType(type);
}
@@ -439,6 +436,7 @@
ViewGroup parent = (ViewGroup) getParent();
if (parent == null) return;
+ View overviewPanel = ((Launcher) getContext()).getOverviewPanel();
final int count = parent.getChildCount();
if (!isChildrenDrawingOrderEnabled()) {
for (int i = 0; i < count; i++) {
@@ -446,7 +444,7 @@
if (child == this) {
break;
} else {
- if (child.getVisibility() == GONE) {
+ if (child.getVisibility() == GONE || child == overviewPanel) {
continue;
}
child.setVisibility(visibility);
diff --git a/src/com/android/launcher3/AutoScrollHelper.java b/src/com/android/launcher3/AutoScrollHelper.java
deleted file mode 100644
index 3efd807..0000000
--- a/src/com/android/launcher3/AutoScrollHelper.java
+++ /dev/null
@@ -1,867 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.content.res.Resources;
-import android.os.SystemClock;
-import android.support.v4.view.MotionEventCompat;
-import android.support.v4.view.ViewCompat;
-import android.util.DisplayMetrics;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-
-/**
- * AutoScrollHelper is a utility class for adding automatic edge-triggered
- * scrolling to Views.
- * <p>
- * <b>Note:</b> Implementing classes are responsible for overriding the
- * {@link #scrollTargetBy}, {@link #canTargetScrollHorizontally}, and
- * {@link #canTargetScrollVertically} methods. See
- * {@link ListViewAutoScrollHelper} for a {@link android.widget.ListView}
- * -specific implementation.
- * <p>
- * <h1>Activation</h1> Automatic scrolling starts when the user touches within
- * an activation area. By default, activation areas are defined as the top,
- * left, right, and bottom 20% of the host view's total area. Touching within
- * the top activation area scrolls up, left scrolls to the left, and so on.
- * <p>
- * As the user touches closer to the extreme edge of the activation area,
- * scrolling accelerates up to a maximum velocity. When using the default edge
- * type, {@link #EDGE_TYPE_INSIDE_EXTEND}, moving outside of the view bounds
- * will scroll at the maximum velocity.
- * <p>
- * The following activation properties may be configured:
- * <ul>
- * <li>Delay after entering activation area before auto-scrolling begins, see
- * {@link #setActivationDelay}. Default value is
- * {@link ViewConfiguration#getTapTimeout()} to avoid conflicting with taps.
- * <li>Location of activation areas, see {@link #setEdgeType}. Default value is
- * {@link #EDGE_TYPE_INSIDE_EXTEND}.
- * <li>Size of activation areas relative to view size, see
- * {@link #setRelativeEdges}. Default value is 20% for both vertical and
- * horizontal edges.
- * <li>Maximum size used to constrain relative size, see
- * {@link #setMaximumEdges}. Default value is {@link #NO_MAX}.
- * </ul>
- * <h1>Scrolling</h1> When automatic scrolling is active, the helper will
- * repeatedly call {@link #scrollTargetBy} to apply new scrolling offsets.
- * <p>
- * The following scrolling properties may be configured:
- * <ul>
- * <li>Acceleration ramp-up duration, see {@link #setRampUpDuration}. Default
- * value is 2500 milliseconds.
- * <li>Acceleration ramp-down duration, see {@link #setRampDownDuration}.
- * Default value is 500 milliseconds.
- * <li>Target velocity relative to view size, see {@link #setRelativeVelocity}.
- * Default value is 100% per second for both vertical and horizontal.
- * <li>Minimum velocity used to constrain relative velocity, see
- * {@link #setMinimumVelocity}. When set, scrolling will accelerate to the
- * larger of either this value or the relative target value. Default value is
- * approximately 5 centimeters or 315 dips per second.
- * <li>Maximum velocity used to constrain relative velocity, see
- * {@link #setMaximumVelocity}. Default value is approximately 25 centimeters or
- * 1575 dips per second.
- * </ul>
- */
-public abstract class AutoScrollHelper implements View.OnTouchListener {
- /**
- * Constant passed to {@link #setRelativeEdges} or
- * {@link #setRelativeVelocity}. Using this value ensures that the computed
- * relative value is ignored and the absolute maximum value is always used.
- */
- public static final float RELATIVE_UNSPECIFIED = 0;
-
- /**
- * Constant passed to {@link #setMaximumEdges}, {@link #setMaximumVelocity},
- * or {@link #setMinimumVelocity}. Using this value ensures that the
- * computed relative value is always used without constraining to a
- * particular minimum or maximum value.
- */
- public static final float NO_MAX = Float.MAX_VALUE;
-
- /**
- * Constant passed to {@link #setMaximumEdges}, or
- * {@link #setMaximumVelocity}, or {@link #setMinimumVelocity}. Using this
- * value ensures that the computed relative value is always used without
- * constraining to a particular minimum or maximum value.
- */
- public static final float NO_MIN = 0;
-
- /**
- * Edge type that specifies an activation area starting at the view bounds
- * and extending inward. Moving outside the view bounds will stop scrolling.
- *
- * @see #setEdgeType
- */
- public static final int EDGE_TYPE_INSIDE = 0;
-
- /**
- * Edge type that specifies an activation area starting at the view bounds
- * and extending inward. After activation begins, moving outside the view
- * bounds will continue scrolling.
- *
- * @see #setEdgeType
- */
- public static final int EDGE_TYPE_INSIDE_EXTEND = 1;
-
- /**
- * Edge type that specifies an activation area starting at the view bounds
- * and extending outward. Moving inside the view bounds will stop scrolling.
- *
- * @see #setEdgeType
- */
- public static final int EDGE_TYPE_OUTSIDE = 2;
-
- private static final int HORIZONTAL = 0;
- private static final int VERTICAL = 1;
-
- /** Scroller used to control acceleration toward maximum velocity. */
- private final ClampedScroller mScroller = new ClampedScroller();
-
- /** Interpolator used to scale velocity with touch position. */
- private final Interpolator mEdgeInterpolator = new AccelerateInterpolator();
-
- /** The view to auto-scroll. Might not be the source of touch events. */
- private final View mTarget;
-
- /** Runnable used to animate scrolling. */
- private Runnable mRunnable;
-
- /** Edge insets used to activate auto-scrolling. */
- private float[] mRelativeEdges = new float[] { RELATIVE_UNSPECIFIED, RELATIVE_UNSPECIFIED };
-
- /** Clamping values for edge insets used to activate auto-scrolling. */
- private float[] mMaximumEdges = new float[] { NO_MAX, NO_MAX };
-
- /** The type of edge being used. */
- private int mEdgeType;
-
- /** Delay after entering an activation edge before auto-scrolling begins. */
- private int mActivationDelay;
-
- /** Relative scrolling velocity at maximum edge distance. */
- private float[] mRelativeVelocity = new float[] { RELATIVE_UNSPECIFIED, RELATIVE_UNSPECIFIED };
-
- /** Clamping values used for scrolling velocity. */
- private float[] mMinimumVelocity = new float[] { NO_MIN, NO_MIN };
-
- /** Clamping values used for scrolling velocity. */
- private float[] mMaximumVelocity = new float[] { NO_MAX, NO_MAX };
-
- /** Whether to start activation immediately. */
- private boolean mAlreadyDelayed;
-
- /** Whether to reset the scroller start time on the next animation. */
- private boolean mNeedsReset;
-
- /** Whether to send a cancel motion event to the target view. */
- private boolean mNeedsCancel;
-
- /** Whether the auto-scroller is actively scrolling. */
- private boolean mAnimating;
-
- /** Whether the auto-scroller is enabled. */
- private boolean mEnabled;
-
- /** Whether the auto-scroller consumes events when scrolling. */
- private boolean mExclusive;
-
- // Default values.
- private static final int DEFAULT_EDGE_TYPE = EDGE_TYPE_INSIDE_EXTEND;
- private static final int DEFAULT_MINIMUM_VELOCITY_DIPS = 315;
- private static final int DEFAULT_MAXIMUM_VELOCITY_DIPS = 1575;
- private static final float DEFAULT_MAXIMUM_EDGE = NO_MAX;
- private static final float DEFAULT_RELATIVE_EDGE = 0.2f;
- private static final float DEFAULT_RELATIVE_VELOCITY = 1f;
- private static final int DEFAULT_ACTIVATION_DELAY = ViewConfiguration.getTapTimeout();
- private static final int DEFAULT_RAMP_UP_DURATION = 500;
- private static final int DEFAULT_RAMP_DOWN_DURATION = 500;
-
- /**
- * Creates a new helper for scrolling the specified target view.
- * <p>
- * The resulting helper may be configured by chaining setter calls and
- * should be set as a touch listener on the target view.
- * <p>
- * By default, the helper is disabled and will not respond to touch events
- * until it is enabled using {@link #setEnabled}.
- *
- * @param target The view to automatically scroll.
- */
- public AutoScrollHelper(View target) {
- mTarget = target;
-
- final DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
- final int maxVelocity = (int) (DEFAULT_MAXIMUM_VELOCITY_DIPS * metrics.density + 0.5f);
- final int minVelocity = (int) (DEFAULT_MINIMUM_VELOCITY_DIPS * metrics.density + 0.5f);
- setMaximumVelocity(maxVelocity, maxVelocity);
- setMinimumVelocity(minVelocity, minVelocity);
-
- setEdgeType(DEFAULT_EDGE_TYPE);
- setMaximumEdges(DEFAULT_MAXIMUM_EDGE, DEFAULT_MAXIMUM_EDGE);
- setRelativeEdges(DEFAULT_RELATIVE_EDGE, DEFAULT_RELATIVE_EDGE);
- setRelativeVelocity(DEFAULT_RELATIVE_VELOCITY, DEFAULT_RELATIVE_VELOCITY);
- setActivationDelay(DEFAULT_ACTIVATION_DELAY);
- setRampUpDuration(DEFAULT_RAMP_UP_DURATION);
- setRampDownDuration(DEFAULT_RAMP_DOWN_DURATION);
- }
-
- /**
- * Sets whether the scroll helper is enabled and should respond to touch
- * events.
- *
- * @param enabled Whether the scroll helper is enabled.
- * @return The scroll helper, which may used to chain setter calls.
- */
- public AutoScrollHelper setEnabled(boolean enabled) {
- if (mEnabled && !enabled) {
- requestStop();
- }
-
- mEnabled = enabled;
- return this;
- }
-
- /**
- * @return True if this helper is enabled and responding to touch events.
- */
- public boolean isEnabled() {
- return mEnabled;
- }
-
- /**
- * Enables or disables exclusive handling of touch events during scrolling.
- * By default, exclusive handling is disabled and the target view receives
- * all touch events.
- * <p>
- * When enabled, {@link #onTouch} will return true if the helper is
- * currently scrolling and false otherwise.
- *
- * @param exclusive True to exclusively handle touch events during scrolling,
- * false to allow the target view to receive all touch events.
- * @return The scroll helper, which may used to chain setter calls.
- */
- public AutoScrollHelper setExclusive(boolean exclusive) {
- mExclusive = exclusive;
- return this;
- }
-
- /**
- * Indicates whether the scroll helper handles touch events exclusively
- * during scrolling.
- *
- * @return True if exclusive handling of touch events during scrolling is
- * enabled, false otherwise.
- * @see #setExclusive(boolean)
- */
- public boolean isExclusive() {
- return mExclusive;
- }
-
- /**
- * Sets the absolute maximum scrolling velocity.
- * <p>
- * If relative velocity is not specified, scrolling will always reach the
- * same maximum velocity. If both relative and maximum velocities are
- * specified, the maximum velocity will be used to clamp the calculated
- * relative velocity.
- *
- * @param horizontalMax The maximum horizontal scrolling velocity, or
- * {@link #NO_MAX} to leave the relative value unconstrained.
- * @param verticalMax The maximum vertical scrolling velocity, or
- * {@link #NO_MAX} to leave the relative value unconstrained.
- * @return The scroll helper, which may used to chain setter calls.
- */
- public AutoScrollHelper setMaximumVelocity(float horizontalMax, float verticalMax) {
- mMaximumVelocity[HORIZONTAL] = horizontalMax / 1000f;
- mMaximumVelocity[VERTICAL] = verticalMax / 1000f;
- return this;
- }
-
- /**
- * Sets the absolute minimum scrolling velocity.
- * <p>
- * If both relative and minimum velocities are specified, the minimum
- * velocity will be used to clamp the calculated relative velocity.
- *
- * @param horizontalMin The minimum horizontal scrolling velocity, or
- * {@link #NO_MIN} to leave the relative value unconstrained.
- * @param verticalMin The minimum vertical scrolling velocity, or
- * {@link #NO_MIN} to leave the relative value unconstrained.
- * @return The scroll helper, which may used to chain setter calls.
- */
- public AutoScrollHelper setMinimumVelocity(float horizontalMin, float verticalMin) {
- mMinimumVelocity[HORIZONTAL] = horizontalMin / 1000f;
- mMinimumVelocity[VERTICAL] = verticalMin / 1000f;
- return this;
- }
-
- /**
- * Sets the target scrolling velocity relative to the host view's
- * dimensions.
- * <p>
- * If both relative and maximum velocities are specified, the maximum
- * velocity will be used to clamp the calculated relative velocity.
- *
- * @param horizontal The target horizontal velocity as a fraction of the
- * host view width per second, or {@link #RELATIVE_UNSPECIFIED}
- * to ignore.
- * @param vertical The target vertical velocity as a fraction of the host
- * view height per second, or {@link #RELATIVE_UNSPECIFIED} to
- * ignore.
- * @return The scroll helper, which may used to chain setter calls.
- */
- public AutoScrollHelper setRelativeVelocity(float horizontal, float vertical) {
- mRelativeVelocity[HORIZONTAL] = horizontal / 1000f;
- mRelativeVelocity[VERTICAL] = vertical / 1000f;
- return this;
- }
-
- /**
- * Sets the activation edge type, one of:
- * <ul>
- * <li>{@link #EDGE_TYPE_INSIDE} for edges that respond to touches inside
- * the bounds of the host view. If touch moves outside the bounds, scrolling
- * will stop.
- * <li>{@link #EDGE_TYPE_INSIDE_EXTEND} for inside edges that continued to
- * scroll when touch moves outside the bounds of the host view.
- * <li>{@link #EDGE_TYPE_OUTSIDE} for edges that only respond to touches
- * that move outside the bounds of the host view.
- * </ul>
- *
- * @param type The type of edge to use.
- * @return The scroll helper, which may used to chain setter calls.
- */
- public AutoScrollHelper setEdgeType(int type) {
- mEdgeType = type;
- return this;
- }
-
- /**
- * Sets the activation edge size relative to the host view's dimensions.
- * <p>
- * If both relative and maximum edges are specified, the maximum edge will
- * be used to constrain the calculated relative edge size.
- *
- * @param horizontal The horizontal edge size as a fraction of the host view
- * width, or {@link #RELATIVE_UNSPECIFIED} to always use the
- * maximum value.
- * @param vertical The vertical edge size as a fraction of the host view
- * height, or {@link #RELATIVE_UNSPECIFIED} to always use the
- * maximum value.
- * @return The scroll helper, which may used to chain setter calls.
- */
- public AutoScrollHelper setRelativeEdges(float horizontal, float vertical) {
- mRelativeEdges[HORIZONTAL] = horizontal;
- mRelativeEdges[VERTICAL] = vertical;
- return this;
- }
-
- /**
- * Sets the absolute maximum edge size.
- * <p>
- * If relative edge size is not specified, activation edges will always be
- * the maximum edge size. If both relative and maximum edges are specified,
- * the maximum edge will be used to constrain the calculated relative edge
- * size.
- *
- * @param horizontalMax The maximum horizontal edge size in pixels, or
- * {@link #NO_MAX} to use the unconstrained calculated relative
- * value.
- * @param verticalMax The maximum vertical edge size in pixels, or
- * {@link #NO_MAX} to use the unconstrained calculated relative
- * value.
- * @return The scroll helper, which may used to chain setter calls.
- */
- public AutoScrollHelper setMaximumEdges(float horizontalMax, float verticalMax) {
- mMaximumEdges[HORIZONTAL] = horizontalMax;
- mMaximumEdges[VERTICAL] = verticalMax;
- return this;
- }
-
- /**
- * Sets the delay after entering an activation edge before activation of
- * auto-scrolling. By default, the activation delay is set to
- * {@link ViewConfiguration#getTapTimeout()}.
- * <p>
- * Specifying a delay of zero will start auto-scrolling immediately after
- * the touch position enters an activation edge.
- *
- * @param delayMillis The activation delay in milliseconds.
- * @return The scroll helper, which may used to chain setter calls.
- */
- public AutoScrollHelper setActivationDelay(int delayMillis) {
- mActivationDelay = delayMillis;
- return this;
- }
-
- /**
- * Sets the amount of time after activation of auto-scrolling that is takes
- * to reach target velocity for the current touch position.
- * <p>
- * Specifying a duration greater than zero prevents sudden jumps in
- * velocity.
- *
- * @param durationMillis The ramp-up duration in milliseconds.
- * @return The scroll helper, which may used to chain setter calls.
- */
- public AutoScrollHelper setRampUpDuration(int durationMillis) {
- mScroller.setRampUpDuration(durationMillis);
- return this;
- }
-
- /**
- * Sets the amount of time after de-activation of auto-scrolling that is
- * takes to slow to a stop.
- * <p>
- * Specifying a duration greater than zero prevents sudden jumps in
- * velocity.
- *
- * @param durationMillis The ramp-down duration in milliseconds.
- * @return The scroll helper, which may used to chain setter calls.
- */
- public AutoScrollHelper setRampDownDuration(int durationMillis) {
- mScroller.setRampDownDuration(durationMillis);
- return this;
- }
-
- /**
- * Handles touch events by activating automatic scrolling, adjusting scroll
- * velocity, or stopping.
- * <p>
- * If {@link #isExclusive()} is false, always returns false so that
- * the host view may handle touch events. Otherwise, returns true when
- * automatic scrolling is active and false otherwise.
- */
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- if (!mEnabled) {
- return false;
- }
-
- final int action = MotionEventCompat.getActionMasked(event);
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- mNeedsCancel = true;
- mAlreadyDelayed = false;
- // $FALL-THROUGH$
- case MotionEvent.ACTION_MOVE:
- final float xTargetVelocity = computeTargetVelocity(
- HORIZONTAL, event.getX(), v.getWidth(), mTarget.getWidth());
- final float yTargetVelocity = computeTargetVelocity(
- VERTICAL, event.getY(), v.getHeight(), mTarget.getHeight());
- mScroller.setTargetVelocity(xTargetVelocity, yTargetVelocity);
-
- // If the auto scroller was not previously active, but it should
- // be, then update the state and start animations.
- if (!mAnimating && shouldAnimate()) {
- startAnimating();
- }
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- requestStop();
- break;
- }
-
- return mExclusive && mAnimating;
- }
-
- /**
- * @return whether the target is able to scroll in the requested direction
- */
- private boolean shouldAnimate() {
- final ClampedScroller scroller = mScroller;
- final int verticalDirection = scroller.getVerticalDirection();
- final int horizontalDirection = scroller.getHorizontalDirection();
-
- return verticalDirection != 0 && canTargetScrollVertically(verticalDirection)
- || horizontalDirection != 0 && canTargetScrollHorizontally(horizontalDirection);
- }
-
- /**
- * Starts the scroll animation.
- */
- private void startAnimating() {
- if (mRunnable == null) {
- mRunnable = new ScrollAnimationRunnable();
- }
-
- mAnimating = true;
- mNeedsReset = true;
-
- if (!mAlreadyDelayed && mActivationDelay > 0) {
- ViewCompat.postOnAnimationDelayed(mTarget, mRunnable, mActivationDelay);
- } else {
- mRunnable.run();
- }
-
- // If we start animating again before the user lifts their finger, we
- // already know it's not a tap and don't need an activation delay.
- mAlreadyDelayed = true;
- }
-
- /**
- * Requests that the scroll animation slow to a stop. If there is an
- * activation delay, this may occur between posting the animation and
- * actually running it.
- */
- private void requestStop() {
- if (mNeedsReset) {
- // The animation has been posted, but hasn't run yet. Manually
- // stopping animation will prevent it from running.
- mAnimating = false;
- } else {
- mScroller.requestStop();
- }
- }
-
- private float computeTargetVelocity(
- int direction, float coordinate, float srcSize, float dstSize) {
- final float relativeEdge = mRelativeEdges[direction];
- final float maximumEdge = mMaximumEdges[direction];
- final float value = getEdgeValue(relativeEdge, srcSize, maximumEdge, coordinate);
- if (value == 0) {
- // The edge in this direction is not activated.
- return 0;
- }
-
- final float relativeVelocity = mRelativeVelocity[direction];
- final float minimumVelocity = mMinimumVelocity[direction];
- final float maximumVelocity = mMaximumVelocity[direction];
- final float targetVelocity = relativeVelocity * dstSize;
-
- // Target velocity is adjusted for interpolated edge position, then
- // clamped to the minimum and maximum values. Later, this value will be
- // adjusted for time-based acceleration.
- if (value > 0) {
- return constrain(value * targetVelocity, minimumVelocity, maximumVelocity);
- } else {
- return -constrain(-value * targetVelocity, minimumVelocity, maximumVelocity);
- }
- }
-
- /**
- * Override this method to scroll the target view by the specified number of
- * pixels.
- *
- * @param deltaX The number of pixels to scroll by horizontally.
- * @param deltaY The number of pixels to scroll by vertically.
- */
- public abstract void scrollTargetBy(int deltaX, int deltaY);
-
- /**
- * Override this method to return whether the target view can be scrolled
- * horizontally in a certain direction.
- *
- * @param direction Negative to check scrolling left, positive to check
- * scrolling right.
- * @return true if the target view is able to horizontally scroll in the
- * specified direction.
- */
- public abstract boolean canTargetScrollHorizontally(int direction);
-
- /**
- * Override this method to return whether the target view can be scrolled
- * vertically in a certain direction.
- *
- * @param direction Negative to check scrolling up, positive to check
- * scrolling down.
- * @return true if the target view is able to vertically scroll in the
- * specified direction.
- */
- public abstract boolean canTargetScrollVertically(int direction);
-
- /**
- * Returns the interpolated position of a touch point relative to an edge
- * defined by its relative inset, its maximum absolute inset, and the edge
- * interpolator.
- *
- * @param relativeValue The size of the inset relative to the total size.
- * @param size Total size.
- * @param maxValue The maximum size of the inset, used to clamp (relative *
- * total).
- * @param current Touch position within within the total size.
- * @return Interpolated value of the touch position within the edge.
- */
- private float getEdgeValue(float relativeValue, float size, float maxValue, float current) {
- // For now, leading and trailing edges are always the same size.
- final float edgeSize = constrain(relativeValue * size, NO_MIN, maxValue);
- final float valueLeading = constrainEdgeValue(current, edgeSize);
- final float valueTrailing = constrainEdgeValue(size - current, edgeSize);
- final float value = (valueTrailing - valueLeading);
- final float interpolated;
- if (value < 0) {
- interpolated = -mEdgeInterpolator.getInterpolation(-value);
- } else if (value > 0) {
- interpolated = mEdgeInterpolator.getInterpolation(value);
- } else {
- return 0;
- }
-
- return constrain(interpolated, -1, 1);
- }
-
- private float constrainEdgeValue(float current, float leading) {
- if (leading == 0) {
- return 0;
- }
-
- switch (mEdgeType) {
- case EDGE_TYPE_INSIDE:
- case EDGE_TYPE_INSIDE_EXTEND:
- if (current < leading) {
- if (current > 0) {
- // Movement up to the edge is scaled.
- return 1f - current / leading;
- } else if (mAnimating && (mEdgeType == EDGE_TYPE_INSIDE_EXTEND)) {
- // Movement beyond the edge is always maximum.
- return 1f;
- }
- }
- break;
- case EDGE_TYPE_OUTSIDE:
- if (current < 0) {
- // Movement beyond the edge is scaled.
- return current / -leading;
- }
- break;
- }
-
- return 0;
- }
-
- private static int constrain(int value, int min, int max) {
- if (value > max) {
- return max;
- } else if (value < min) {
- return min;
- } else {
- return value;
- }
- }
-
- private static float constrain(float value, float min, float max) {
- if (value > max) {
- return max;
- } else if (value < min) {
- return min;
- } else {
- return value;
- }
- }
-
- /**
- * Sends a {@link MotionEvent#ACTION_CANCEL} event to the target view,
- * canceling any ongoing touch events.
- */
- private void cancelTargetTouch() {
- final long eventTime = SystemClock.uptimeMillis();
- final MotionEvent cancel = MotionEvent.obtain(
- eventTime, eventTime, MotionEvent.ACTION_CANCEL, 0, 0, 0);
- mTarget.onTouchEvent(cancel);
- cancel.recycle();
- }
-
- private class ScrollAnimationRunnable implements Runnable {
- @Override
- public void run() {
- if (!mAnimating) {
- return;
- }
-
- if (mNeedsReset) {
- mNeedsReset = false;
- mScroller.start();
- }
-
- final ClampedScroller scroller = mScroller;
- if (scroller.isFinished() || !shouldAnimate()) {
- mAnimating = false;
- return;
- }
-
- if (mNeedsCancel) {
- mNeedsCancel = false;
- cancelTargetTouch();
- }
-
- scroller.computeScrollDelta();
-
- final int deltaX = scroller.getDeltaX();
- final int deltaY = scroller.getDeltaY();
- scrollTargetBy(deltaX, deltaY);
-
- // Keep going until the scroller has permanently stopped.
- ViewCompat.postOnAnimation(mTarget, this);
- }
- }
-
- /**
- * Scroller whose velocity follows the curve of an {@link Interpolator} and
- * is clamped to the interpolated 0f value before starting and the
- * interpolated 1f value after a specified duration.
- */
- private static class ClampedScroller {
- private int mRampUpDuration;
- private int mRampDownDuration;
- private float mTargetVelocityX;
- private float mTargetVelocityY;
-
- private long mStartTime;
-
- private long mDeltaTime;
- private int mDeltaX;
- private int mDeltaY;
-
- private long mStopTime;
- private float mStopValue;
- private int mEffectiveRampDown;
-
- /**
- * Creates a new ramp-up scroller that reaches full velocity after a
- * specified duration.
- */
- public ClampedScroller() {
- mStartTime = Long.MIN_VALUE;
- mStopTime = -1;
- mDeltaTime = 0;
- mDeltaX = 0;
- mDeltaY = 0;
- }
-
- public void setRampUpDuration(int durationMillis) {
- mRampUpDuration = durationMillis;
- }
-
- public void setRampDownDuration(int durationMillis) {
- mRampDownDuration = durationMillis;
- }
-
- /**
- * Starts the scroller at the current animation time.
- */
- public void start() {
- mStartTime = AnimationUtils.currentAnimationTimeMillis();
- mStopTime = -1;
- mDeltaTime = mStartTime;
- mStopValue = 0.5f;
- mDeltaX = 0;
- mDeltaY = 0;
- }
-
- /**
- * Stops the scroller at the current animation time.
- */
- public void requestStop() {
- final long currentTime = AnimationUtils.currentAnimationTimeMillis();
- mEffectiveRampDown = constrain((int) (currentTime - mStartTime), 0, mRampDownDuration);
- mStopValue = getValueAt(currentTime);
- mStopTime = currentTime;
- }
-
- public boolean isFinished() {
- return mStopTime > 0
- && AnimationUtils.currentAnimationTimeMillis() > mStopTime + mEffectiveRampDown;
- }
-
- private float getValueAt(long currentTime) {
- if (currentTime < mStartTime) {
- return 0f;
- } else if (mStopTime < 0 || currentTime < mStopTime) {
- final long elapsedSinceStart = currentTime - mStartTime;
- return 0.5f * constrain(elapsedSinceStart / (float) mRampUpDuration, 0, 1);
- } else {
- final long elapsedSinceEnd = currentTime - mStopTime;
- return (1 - mStopValue) + mStopValue
- * constrain(elapsedSinceEnd / (float) mEffectiveRampDown, 0, 1);
- }
- }
-
- /**
- * Interpolates the value along a parabolic curve corresponding to the equation
- * <code>y = -4x * (x-1)</code>.
- *
- * @param value The value to interpolate, between 0 and 1.
- * @return the interpolated value, between 0 and 1.
- */
- private float interpolateValue(float value) {
- return -4 * value * value + 4 * value;
- }
-
- /**
- * Computes the current scroll deltas. This usually only be called after
- * starting the scroller with {@link #start()}.
- *
- * @see #getDeltaX()
- * @see #getDeltaY()
- */
- public void computeScrollDelta() {
- if (mDeltaTime == 0) {
- throw new RuntimeException("Cannot compute scroll delta before calling start()");
- }
-
- final long currentTime = AnimationUtils.currentAnimationTimeMillis();
- final float value = getValueAt(currentTime);
- final float scale = interpolateValue(value);
- final long elapsedSinceDelta = currentTime - mDeltaTime;
-
- mDeltaTime = currentTime;
- mDeltaX = (int) (elapsedSinceDelta * scale * mTargetVelocityX);
- mDeltaY = (int) (elapsedSinceDelta * scale * mTargetVelocityY);
- }
-
- /**
- * Sets the target velocity for this scroller.
- *
- * @param x The target X velocity in pixels per millisecond.
- * @param y The target Y velocity in pixels per millisecond.
- */
- public void setTargetVelocity(float x, float y) {
- mTargetVelocityX = x;
- mTargetVelocityY = y;
- }
-
- public int getHorizontalDirection() {
- return (int) (mTargetVelocityX / Math.abs(mTargetVelocityX));
- }
-
- public int getVerticalDirection() {
- return (int) (mTargetVelocityY / Math.abs(mTargetVelocityY));
- }
-
- /**
- * The distance traveled in the X-coordinate computed by the last call
- * to {@link #computeScrollDelta()}.
- */
- public int getDeltaX() {
- return mDeltaX;
- }
-
- /**
- * The distance traveled in the Y-coordinate computed by the last call
- * to {@link #computeScrollDelta()}.
- */
- public int getDeltaY() {
- return mDeltaY;
- }
- }
-}
\ No newline at end of file
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 30ca737..22492ac 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -949,9 +949,11 @@
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+ int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
+ int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
- int cw = grid.calculateCellWidth(widthSize, mCountX);
- int ch = grid.calculateCellHeight(heightSize, mCountY);
+ int cw = grid.calculateCellWidth(childWidthSize, mCountX);
+ int ch = grid.calculateCellHeight(childHeightSize, mCountY);
if (cw != mCellWidth || ch != mCellHeight) {
mCellWidth = cw;
mCellHeight = ch;
@@ -960,8 +962,8 @@
}
}
- int newWidth = widthSize;
- int newHeight = heightSize;
+ int newWidth = childWidthSize;
+ int newHeight = childHeightSize;
if (mFixedWidth > 0 && mFixedHeight > 0) {
newWidth = mFixedWidth;
newHeight = mFixedHeight;
@@ -973,8 +975,8 @@
int numHeightGaps = mCountY - 1;
if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
- int hSpace = widthSize - getPaddingLeft() - getPaddingRight();
- int vSpace = heightSize - getPaddingTop() - getPaddingBottom();
+ int hSpace = childWidthSize;
+ int vSpace = childHeightSize;
int hFreeSpace = hSpace - (mCountX * mCellWidth);
int vFreeSpace = vSpace - (mCountY * mCellHeight);
mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
@@ -990,15 +992,19 @@
int maxHeight = 0;
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
- int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth - getPaddingLeft() -
- getPaddingRight(), MeasureSpec.EXACTLY);
- int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() -
- getPaddingBottom(), MeasureSpec.EXACTLY);
+ int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth,
+ MeasureSpec.EXACTLY);
+ int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight,
+ MeasureSpec.EXACTLY);
child.measure(childWidthMeasureSpec, childheightMeasureSpec);
maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
}
- setMeasuredDimension(maxWidth, maxHeight);
+ if (mFixedWidth > 0 && mFixedHeight > 0) {
+ setMeasuredDimension(maxWidth, maxHeight);
+ } else {
+ setMeasuredDimension(widthSize, heightSize);
+ }
}
@Override
@@ -1006,8 +1012,11 @@
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
- child.layout(getPaddingLeft(), getPaddingTop(),
- r - l - getPaddingRight(), b - t - getPaddingBottom());
+ int left = getPaddingLeft();
+ int top = getPaddingTop();
+ child.layout(left, top,
+ left + r - l,
+ top + b - t);
}
}
diff --git a/src/com/android/launcher3/Cling.java b/src/com/android/launcher3/Cling.java
index 963702a..7ca6990 100644
--- a/src/com/android/launcher3/Cling.java
+++ b/src/com/android/launcher3/Cling.java
@@ -24,6 +24,7 @@
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
@@ -55,7 +56,7 @@
private static String FOLDER_LANDSCAPE = "folder_landscape";
private static String FOLDER_LARGE = "folder_large";
- private static float FIRST_RUN_CIRCLE_BUFFER_DPS = 40;
+ private static float FIRST_RUN_CIRCLE_BUFFER_DPS = 60;
private static float WORKSPACE_INNER_CIRCLE_RADIUS_DPS = 50;
private static float WORKSPACE_OUTER_CIRCLE_RADIUS_DPS = 60;
private static float WORKSPACE_CIRCLE_Y_OFFSET_DPS = 30;
@@ -101,8 +102,10 @@
mErasePaint.setAlpha(0);
mErasePaint.setAntiAlias(true);
+ int circleColor = getResources().getColor(
+ R.color.first_run_cling_circle_background_color);
mBubblePaint = new Paint();
- mBubblePaint.setColor(0xFFFFFF);
+ mBubblePaint.setColor(circleColor);
mBubblePaint.setAntiAlias(true);
mDotPaint = new Paint();
diff --git a/src/com/android/launcher3/DynamicGrid.java b/src/com/android/launcher3/DynamicGrid.java
index 664a99c..495e930 100644
--- a/src/com/android/launcher3/DynamicGrid.java
+++ b/src/com/android/launcher3/DynamicGrid.java
@@ -290,8 +290,7 @@
Rect getWorkspacePadding(int orientation) {
Rect padding = new Rect();
- if (orientation == CellLayout.LANDSCAPE &&
- transposeLayoutWithOrientation) {
+ if (isVerticalBarLayout()) {
// Pad the left and right of the workspace with search/hotseat bar sizes
padding.set(searchBarSpaceHeightPx, edgeMarginPx,
hotseatBarHeightPx, edgeMarginPx);
@@ -321,6 +320,17 @@
return padding;
}
+ // The rect returned will be extended to below the system ui that covers the workspace
+ Rect getHotseatRect() {
+ if (isVerticalBarLayout()) {
+ return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
+ Integer.MAX_VALUE, availableHeightPx);
+ } else {
+ return new Rect(0, availableHeightPx - hotseatBarHeightPx,
+ availableWidthPx, Integer.MAX_VALUE);
+ }
+ }
+
int calculateCellWidth(int width, int countX) {
return width / countX;
}
@@ -338,11 +348,14 @@
return isLargeTablet;
}
+ boolean isVerticalBarLayout() {
+ return isLandscape && transposeLayoutWithOrientation;
+ }
+
public void layout(Launcher launcher) {
FrameLayout.LayoutParams lp;
Resources res = launcher.getResources();
- boolean hasVerticalBarLayout = isLandscape &&
- res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
+ boolean hasVerticalBarLayout = isVerticalBarLayout();
// Layout the search bar space
View searchBarSpace = launcher.findViewById(R.id.qsb_bar);
@@ -430,7 +443,7 @@
lp.gravity = Gravity.BOTTOM;
lp.width = LayoutParams.MATCH_PARENT;
lp.height = hotseatBarHeightPx;
- hotseat.setPadding(2 * edgeMarginPx, 0,
+ hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
2 * edgeMarginPx, 0);
}
hotseat.setLayoutParams(lp);
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index 9ee3f64..a71d9b2 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -26,6 +26,7 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
+import android.support.v4.widget.AutoScrollHelper;
import android.text.InputType;
import android.text.Selection;
import android.text.Spannable;
diff --git a/src/com/android/launcher3/FolderAutoScrollHelper.java b/src/com/android/launcher3/FolderAutoScrollHelper.java
index 68edc60..40e8884 100644
--- a/src/com/android/launcher3/FolderAutoScrollHelper.java
+++ b/src/com/android/launcher3/FolderAutoScrollHelper.java
@@ -16,6 +16,7 @@
package com.android.launcher3;
+import android.support.v4.widget.AutoScrollHelper;
import android.widget.ScrollView;
/**
@@ -36,6 +37,8 @@
setEdgeType(EDGE_TYPE_INSIDE_EXTEND);
setExclusive(true);
setMaximumVelocity(MAX_SCROLL_VELOCITY, MAX_SCROLL_VELOCITY);
+ setRampDownDuration(0);
+ setRampUpDuration(0);
}
@Override
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 3bbb39e..84d5a09 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -274,6 +274,7 @@
private boolean mVisible = false;
private boolean mAttached = false;
private static final boolean DISABLE_CLINGS = true;
+ private static final boolean DISABLE_CUSTOM_CLINGS = true;
private static LocaleConfiguration sLocaleConfiguration = null;
@@ -895,11 +896,6 @@
@Override
protected void onPause() {
- // NOTE: We want all transitions from launcher to act as if the wallpaper were enabled
- // to be consistent. So re-enable the flag here, and we will re-disable it as necessary
- // when Launcher resumes and we are still in AllApps.
- updateWallpaperVisibility(true);
-
// Ensure that items added to Launcher are queued until Launcher returns
InstallShortcutReceiver.enableInstallQueue();
@@ -2646,16 +2642,6 @@
view.setPivotY(view.getHeight() / 2.0f);
}
- void disableWallpaperIfInAllApps() {
- // Only disable it if we are in all apps
- if (isAllAppsVisible()) {
- if (mAppsCustomizeTabHost != null &&
- !mAppsCustomizeTabHost.isTransitioning()) {
- updateWallpaperVisibility(false);
- }
- }
- }
-
private void setWorkspaceBackground(boolean workspace) {
mLauncherView.setBackground(workspace ?
mWorkspaceBackgroundDrawable : null);
@@ -2815,7 +2801,6 @@
@Override
public void onAnimationStart(Animator animation) {
- updateWallpaperVisibility(true);
// Prepare the position
toView.setTranslationX(0.0f);
toView.setTranslationY(0.0f);
@@ -2827,10 +2812,6 @@
dispatchOnLauncherTransitionEnd(fromView, animated, false);
dispatchOnLauncherTransitionEnd(toView, animated, false);
- if (!animationCancelled) {
- updateWallpaperVisibility(false);
- }
-
// Hide the search bar
if (mSearchDropTargetBar != null) {
mSearchDropTargetBar.hideSearchBar(false);
@@ -2904,7 +2885,6 @@
dispatchOnLauncherTransitionPrepare(toView, animated, false);
dispatchOnLauncherTransitionStart(toView, animated, false);
dispatchOnLauncherTransitionEnd(toView, animated, false);
- updateWallpaperVisibility(false);
}
}
@@ -2941,7 +2921,6 @@
}
setPivotsForZoom(fromView, scaleFactor);
- updateWallpaperVisibility(true);
showHotseat(animated);
if (animated) {
final LauncherViewPropertyAnimator scaleAnim =
@@ -2973,7 +2952,6 @@
mStateAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- updateWallpaperVisibility(true);
fromView.setVisibility(View.GONE);
dispatchOnLauncherTransitionEnd(fromView, animated, true);
dispatchOnLauncherTransitionEnd(toView, animated, true);
@@ -3011,30 +2989,13 @@
}
}
- @Override
- public void onWindowFocusChanged(boolean hasFocus) {
- if (!hasFocus) {
- // When another window occludes launcher (like the notification shade, or recents),
- // ensure that we enable the wallpaper flag so that transitions are done correctly.
- updateWallpaperVisibility(true);
- } else {
- // When launcher has focus again, disable the wallpaper if we are in AllApps
- mWorkspace.postDelayed(new Runnable() {
- @Override
- public void run() {
- disableWallpaperIfInAllApps();
- }
- }, 500);
- }
- }
-
void showWorkspace(boolean animated) {
showWorkspace(animated, null);
}
void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
if (mState != State.WORKSPACE) {
- boolean wasInSpringLoadedMode = (mState == State.APPS_CUSTOMIZE_SPRING_LOADED);
+ boolean wasInSpringLoadedMode = (mState != State.WORKSPACE);
mWorkspace.setVisibility(View.VISIBLE);
hideAppsCustomizeHelper(State.WORKSPACE, animated, false, onCompleteRunnable);
@@ -3863,6 +3824,13 @@
});
}
+ public boolean isAllAppsButtonRank(int rank) {
+ if (mHotseat != null) {
+ return mHotseat.isAllAppsButtonRank(rank);
+ }
+ return false;
+ }
+
private boolean canRunNewAppsAnimation() {
long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
@@ -4166,16 +4134,33 @@
// If we're not using the default workspace layout, replace workspace cling
// with a custom workspace cling (usually specified in an overlay)
// For now, only do this on tablets
- if (mSharedPrefs.getInt(LauncherProvider.DEFAULT_WORKSPACE_RESOURCE_ID, 0) != 0 &&
- getResources().getBoolean(R.bool.config_useCustomClings)) {
- // Use a custom cling
- View cling = findViewById(R.id.workspace_cling);
- ViewGroup clingParent = (ViewGroup) cling.getParent();
- int clingIndex = clingParent.indexOfChild(cling);
- clingParent.removeViewAt(clingIndex);
- View customCling = mInflater.inflate(R.layout.custom_workspace_cling, clingParent, false);
- clingParent.addView(customCling, clingIndex);
- customCling.setId(R.id.workspace_cling);
+ if (!DISABLE_CUSTOM_CLINGS) {
+ if (mSharedPrefs.getInt(LauncherProvider.DEFAULT_WORKSPACE_RESOURCE_ID, 0) != 0 &&
+ getResources().getBoolean(R.bool.config_useCustomClings)) {
+ // Use a custom cling
+ View cling = findViewById(R.id.workspace_cling);
+ ViewGroup clingParent = (ViewGroup) cling.getParent();
+ int clingIndex = clingParent.indexOfChild(cling);
+ clingParent.removeViewAt(clingIndex);
+ View customCling = mInflater.inflate(R.layout.custom_workspace_cling, clingParent, false);
+ clingParent.addView(customCling, clingIndex);
+ customCling.setId(R.id.workspace_cling);
+ }
+ }
+ Cling cling = (Cling) findViewById(R.id.first_run_cling);
+ if (cling != null) {
+ String sbHintStr = getFirstRunClingSearchBarHint();
+ String ccHintStr = getFirstRunCustomContentHint();
+ if (!sbHintStr.isEmpty()) {
+ TextView sbHint = (TextView) cling.findViewById(R.id.search_bar_hint);
+ sbHint.setText(sbHintStr);
+ sbHint.setVisibility(View.VISIBLE);
+ }
+ if (!ccHintStr.isEmpty()) {
+ TextView ccHint = (TextView) cling.findViewById(R.id.custom_content_hint);
+ ccHint.setText(ccHintStr);
+ ccHint.setVisibility(View.VISIBLE);
+ }
}
initCling(R.id.first_run_cling, null, false, true);
} else {
@@ -4183,6 +4168,13 @@
}
}
+ protected String getFirstRunClingSearchBarHint() {
+ return "";
+ }
+ protected String getFirstRunCustomContentHint() {
+ return "";
+ }
+
public void showFirstRunWorkspaceCling() {
// Enable the clings only if they have not been dismissed before
if (isClingsEnabled() &&
diff --git a/src/com/android/launcher3/LauncherBackupAgent.java b/src/com/android/launcher3/LauncherBackupAgent.java
new file mode 100644
index 0000000..bb15ca1
--- /dev/null
+++ b/src/com/android/launcher3/LauncherBackupAgent.java
@@ -0,0 +1,655 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
+import com.google.protobuf.nano.MessageNano;
+
+import com.android.launcher3.LauncherSettings.ChangeLogColumns;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.LauncherSettings.WorkspaceScreens;
+import com.android.launcher3.backup.BackupProtos;
+import com.android.launcher3.backup.BackupProtos.CheckedMessage;
+import com.android.launcher3.backup.BackupProtos.Favorite;
+import com.android.launcher3.backup.BackupProtos.Journal;
+import com.android.launcher3.backup.BackupProtos.Key;
+import com.android.launcher3.backup.BackupProtos.Screen;
+
+import android.app.backup.BackupAgent;
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.os.ParcelFileDescriptor;
+import android.provider.BaseColumns;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.zip.CRC32;
+
+/**
+ * Persist the launcher home state across calamities.
+ */
+public class LauncherBackupAgent extends BackupAgent {
+
+ private static final String TAG = "LauncherBackupAgent";
+ private static final boolean DEBUG = false;
+
+ private static final int MAX_JOURNAL_SIZE = 1000000;
+
+ private static BackupManager sBackupManager;
+
+ private static final String[] FAVORITE_PROJECTION = {
+ Favorites._ID, // 0
+ Favorites.APPWIDGET_ID, // 1
+ Favorites.APPWIDGET_PROVIDER, // 2
+ Favorites.CELLX, // 3
+ Favorites.CELLY, // 4
+ Favorites.CONTAINER, // 5
+ Favorites.ICON, // 6
+ Favorites.ICON_PACKAGE, // 7
+ Favorites.ICON_RESOURCE, // 8
+ Favorites.ICON_TYPE, // 9
+ Favorites.ITEM_TYPE, // 10
+ Favorites.INTENT, // 11
+ Favorites.SCREEN, // 12
+ Favorites.SPANX, // 13
+ Favorites.SPANY, // 14
+ Favorites.TITLE, // 15
+ };
+
+ private static final int ID_INDEX = 0;
+ private static final int APPWIDGET_ID_INDEX = 1;
+ private static final int APPWIDGET_PROVIDER_INDEX = 2;
+ private static final int CELLX_INDEX = 3;
+ private static final int CELLY_INDEX = 4;
+ private static final int CONTAINER_INDEX = 5;
+ private static final int ICON_INDEX = 6;
+ private static final int ICON_PACKAGE_INDEX = 7;
+ private static final int ICON_RESOURCE_INDEX = 8;
+ private static final int ICON_TYPE_INDEX = 9;
+ private static final int ITEM_TYPE_INDEX = 10;
+ private static final int INTENT_INDEX = 11;
+ private static final int SCREEN_INDEX = 12;
+ private static final int SPANX_INDEX = 13 ;
+ private static final int SPANY_INDEX = 14;
+ private static final int TITLE_INDEX = 15;
+
+ private static final String[] SCREEN_PROJECTION = {
+ WorkspaceScreens._ID, // 0
+ WorkspaceScreens.SCREEN_RANK // 1
+ };
+
+ private static final int SCREEN_RANK_INDEX = 1;
+
+ private static final String[] ID_ONLY_PROJECTION = {
+ BaseColumns._ID
+ };
+
+
+ /**
+ * Notify the backup manager that out database is dirty.
+ *
+ * <P>This does not force an immediate backup.
+ *
+ * @param context application context
+ */
+ public static void dataChanged(Context context) {
+ if (sBackupManager == null) {
+ sBackupManager = new BackupManager(context);
+ }
+ sBackupManager.dataChanged();
+ }
+
+ /**
+ * Back up launcher data so we can restore the user's state on a new device.
+ *
+ * <P>The journal is a timestamp and a list of keys that were saved as of that time.
+ *
+ * <P>Keys may come back in any order, so each key/value is one complete row of the database.
+ *
+ * @param oldState notes from the last backup
+ * @param data incremental key/value pairs to persist off-device
+ * @param newState notes for the next backup
+ * @throws IOException
+ */
+ @Override
+ public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState)
+ throws IOException {
+ Log.v(TAG, "onBackup");
+
+ Journal in = readJournal(oldState);
+ Journal out = new Journal();
+
+ long lastBackupTime = in.t;
+ out.t = System.currentTimeMillis();
+ out.rows = 0;
+ out.bytes = 0;
+
+ Log.v(TAG, "lastBackupTime=" + lastBackupTime);
+
+ ArrayList<Key> keys = new ArrayList<Key>();
+ backupFavorites(in, data, out, keys);
+ backupScreens(in, data, out, keys);
+
+ out.key = keys.toArray(BackupProtos.Key.EMPTY_ARRAY);
+ writeJournal(newState, out);
+ Log.v(TAG, "onBackup: wrote " + out.bytes + "b in " + out.rows + " rows.");
+
+ Log.v(TAG, "onBackup: finished");
+ }
+
+ /**
+ * Restore home screen from the restored data stream.
+ *
+ * <P>Keys may arrive in any order.
+ *
+ * @param data the key/value pairs from the server
+ * @param versionCode the version of the app that generated the data
+ * @param newState notes for the next backup
+ * @throws IOException
+ */
+ @Override
+ public void onRestore(BackupDataInput data, int versionCode, ParcelFileDescriptor newState)
+ throws IOException {
+ Log.v(TAG, "onRestore");
+ int numRows = 0;
+ Journal out = new Journal();
+
+ ArrayList<Key> keys = new ArrayList<Key>();
+ byte[] buffer = new byte[512];
+ while (data.readNextHeader()) {
+ numRows++;
+ String backupKey = data.getKey();
+ int dataSize = data.getDataSize();
+ if (buffer.length < dataSize) {
+ buffer = new byte[dataSize];
+ }
+ Key key = null;
+ int bytesRead = data.readEntityData(buffer, 0, dataSize);
+ if (DEBUG) {
+ Log.d(TAG, "read " + bytesRead + " of " + dataSize + " available");
+ }
+ try {
+ key = backupKeyToKey(backupKey);
+ switch (key.type) {
+ case Key.FAVORITE:
+ restoreFavorite(key, buffer, dataSize, keys);
+ break;
+
+ case Key.SCREEN:
+ restoreScreen(key, buffer, dataSize, keys);
+ break;
+
+ default:
+ Log.w(TAG, "unknown restore entity type: " + key.type);
+ break;
+ }
+ } catch (KeyParsingException e) {
+ Log.w(TAG, "ignoring unparsable backup key: " + backupKey);
+ }
+ }
+
+ // clear the output journal time, to force a full backup to
+ // will catch any changes the restore process might have made
+ out.t = 0;
+ out.key = keys.toArray(BackupProtos.Key.EMPTY_ARRAY);
+ writeJournal(newState, out);
+ Log.v(TAG, "onRestore: read " + numRows + " rows");
+ }
+
+ /**
+ * Write all modified favorites to the data stream.
+ *
+ *
+ * @param in notes from last backup
+ * @param data output stream for key/value pairs
+ * @param out notes about this backup
+ * @param keys keys to mark as clean in the notes for next backup
+ * @throws IOException
+ */
+ private void backupFavorites(Journal in, BackupDataOutput data, Journal out,
+ ArrayList<Key> keys)
+ throws IOException {
+ // read the old ID set
+ Set<String> savedIds = new HashSet<String>();
+ for(int i = 0; i < in.key.length; i++) {
+ Key key = in.key[i];
+ if (key.type == Key.FAVORITE) {
+ savedIds.add(keyToBackupKey(key));
+ }
+ }
+ if (DEBUG) Log.d(TAG, "favorite savedIds.size()=" + savedIds.size());
+
+ // persist things that have changed since the last backup
+ ContentResolver cr = getContentResolver();
+ String where = ChangeLogColumns.MODIFIED + " > ?";
+ String[] args = {Long.toString(in.t)};
+ String updateOrder = ChangeLogColumns.MODIFIED;
+ Cursor updated = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
+ where, args, updateOrder);
+ if (DEBUG) Log.d(TAG, "favorite updated.getCount()=" + updated.getCount());
+ try {
+ updated.moveToPosition(-1);
+ while(updated.moveToNext()) {
+ final long id = updated.getLong(ID_INDEX);
+ Key key = getKey(Key.FAVORITE, id);
+ byte[] blob = packFavorite(updated);
+ String backupKey = keyToBackupKey(key);
+ data.writeEntityHeader(backupKey, blob.length);
+ data.writeEntityData(blob, blob.length);
+ out.rows++;
+ out.bytes += blob.length;
+ Log.v(TAG, "saving favorite " + backupKey + ": " + id + "/" + blob.length);
+ if(DEBUG) Log.d(TAG, "wrote " +
+ Base64.encodeToString(blob, 0, blob.length, Base64.NO_WRAP));
+ // remember that is was a new column, so we don't delete it.
+ savedIds.add(backupKey);
+ }
+ } finally {
+ updated.close();
+ }
+ if (DEBUG) Log.d(TAG, "favorite savedIds.size()=" + savedIds.size());
+
+ // build the current ID set
+ String idOrder = BaseColumns._ID;
+ Cursor idCursor = cr.query(Favorites.CONTENT_URI, ID_ONLY_PROJECTION,
+ null, null, idOrder);
+ Set<String> currentIds = new HashSet<String>(idCursor.getCount());
+ try {
+ idCursor.moveToPosition(-1);
+ while(idCursor.moveToNext()) {
+ Key key = getKey(Key.FAVORITE, idCursor.getLong(ID_INDEX));
+ currentIds.add(keyToBackupKey(key));
+ // save the IDs for next time
+ keys.add(key);
+ }
+ } finally {
+ idCursor.close();
+ }
+ if (DEBUG) Log.d(TAG, "favorite currentIds.size()=" + currentIds.size());
+
+ // these IDs must have been deleted
+ savedIds.removeAll(currentIds);
+ for (String deleted : savedIds) {
+ Log.v(TAG, "dropping favorite " + deleted);
+ data.writeEntityHeader(deleted, -1);
+ out.rows++;
+ }
+ }
+
+ /**
+ * Read a favorite from the stream.
+ *
+ * <P>Keys arrive in any order, so screens and containers may not exist yet.
+ *
+ * @param key identifier for the row
+ * @param buffer the serialized proto from the stream, may be larger than dataSize
+ * @param dataSize the size of the proto from the stream
+ * @param keys keys to mark as clean in the notes for next backup
+ */
+ private void restoreFavorite(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
+ Log.v(TAG, "unpacking favorite " + key.id + " (" + dataSize + " bytes)");
+ if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
+ Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
+
+ try {
+ Favorite favorite = unpackFavorite(buffer, 0, dataSize);
+ if (DEBUG) Log.d(TAG, "unpacked " + favorite.itemType);
+ } catch (InvalidProtocolBufferNanoException e) {
+ Log.w(TAG, "failed to decode proto", e);
+ }
+ }
+
+ /**
+ * Write all modified screens to the data stream.
+ *
+ *
+ * @param in notes from last backup
+ * @param data output stream for key/value pairs
+ * @param out notes about this backup
+ * @param keys keys to mark as clean in the notes for next backup @throws IOException
+ */
+ private void backupScreens(Journal in, BackupDataOutput data, Journal out,
+ ArrayList<Key> keys)
+ throws IOException {
+ // read the old ID set
+ Set<String> savedIds = new HashSet<String>();
+ for(int i = 0; i < in.key.length; i++) {
+ Key key = in.key[i];
+ if (key.type == Key.SCREEN) {
+ savedIds.add(keyToBackupKey(key));
+ }
+ }
+ if (DEBUG) Log.d(TAG, "screens savedIds.size()=" + savedIds.size());
+
+ // persist things that have changed since the last backup
+ ContentResolver cr = getContentResolver();
+ String where = ChangeLogColumns.MODIFIED + " > ?";
+ String[] args = {Long.toString(in.t)};
+ String updateOrder = ChangeLogColumns.MODIFIED;
+ Cursor updated = cr.query(WorkspaceScreens.CONTENT_URI, SCREEN_PROJECTION,
+ where, args, updateOrder);
+ updated.moveToPosition(-1);
+ if (DEBUG) Log.d(TAG, "screens updated.getCount()=" + updated.getCount());
+ try {
+ while(updated.moveToNext()) {
+ final long id = updated.getLong(ID_INDEX);
+ Key key = getKey(Key.SCREEN, id);
+ byte[] blob = packScreen(updated);
+ String backupKey = keyToBackupKey(key);
+ data.writeEntityHeader(backupKey, blob.length);
+ data.writeEntityData(blob, blob.length);
+ out.rows++;
+ out.bytes += blob.length;
+ Log.v(TAG, "saving screen " + backupKey + ": " + id + "/" + blob.length);
+ if(DEBUG) Log.d(TAG, "wrote " +
+ Base64.encodeToString(blob, 0, blob.length, Base64.NO_WRAP));
+ // remember that is was a new column, so we don't delete it.
+ savedIds.add(backupKey);
+ }
+ } finally {
+ updated.close();
+ }
+ if (DEBUG) Log.d(TAG, "screen savedIds.size()=" + savedIds.size());
+
+ // build the current ID set
+ String idOrder = BaseColumns._ID;
+ Cursor idCursor = cr.query(WorkspaceScreens.CONTENT_URI, ID_ONLY_PROJECTION,
+ null, null, idOrder);
+ idCursor.moveToPosition(-1);
+ Set<String> currentIds = new HashSet<String>(idCursor.getCount());
+ try {
+ while(idCursor.moveToNext()) {
+ Key key = getKey(Key.SCREEN, idCursor.getLong(ID_INDEX));
+ currentIds.add(keyToBackupKey(key));
+ // save the IDs for next time
+ keys.add(key);
+ }
+ } finally {
+ idCursor.close();
+ }
+ if (DEBUG) Log.d(TAG, "screen currentIds.size()=" + currentIds.size());
+
+ // these IDs must have been deleted
+ savedIds.removeAll(currentIds);
+ for(String deleted: savedIds) {
+ Log.v(TAG, "dropping screen " + deleted);
+ data.writeEntityHeader(deleted, -1);
+ out.rows++;
+ }
+ }
+
+ /**
+ * Read a screen from the stream.
+ *
+ * <P>Keys arrive in any order, so children of this screen may already exist.
+ *
+ * @param key identifier for the row
+ * @param buffer the serialized proto from the stream, may be larger than dataSize
+ * @param dataSize the size of the proto from the stream
+ * @param keys keys to mark as clean in the notes for next backup
+ */
+ private void restoreScreen(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
+ Log.v(TAG, "unpacking screen " + key.id);
+ if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
+ Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
+ try {
+ Screen screen = unpackScreen(buffer, 0, dataSize);
+ if (DEBUG) Log.d(TAG, "unpacked " + screen.rank);
+ } catch (InvalidProtocolBufferNanoException e) {
+ Log.w(TAG, "failed to decode proto", e);
+ }
+ }
+
+ /** create a new key object.
+ *
+ * <P> Keys contain their own checksum instead of using
+ * the heavy-weight CheckedMessage wrapper.
+ */
+ private Key getKey(int type, long id) {
+ Key key = new Key();
+ key.type = type;
+ key.id = id;
+ key.checksum = checkKey(key);
+ return key;
+ }
+
+ /** keys need to be strings, serialize and encode. */
+ private String keyToBackupKey(Key key) {
+ return Base64.encodeToString(Key.toByteArray(key), Base64.NO_WRAP | Base64.NO_PADDING);
+ }
+
+ /** keys need to be strings, decode and parse. */
+ private Key backupKeyToKey(String backupKey) throws KeyParsingException {
+ try {
+ Key key = Key.parseFrom(Base64.decode(backupKey, Base64.DEFAULT));
+ if (key.checksum != checkKey(key)) {
+ key = null;
+ throw new KeyParsingException("invalid key read from stream" + backupKey);
+ }
+ return key;
+ } catch (InvalidProtocolBufferNanoException e) {
+ throw new KeyParsingException(e);
+ } catch (IllegalArgumentException e) {
+ throw new KeyParsingException(e);
+ }
+ }
+
+ /** Compute the checksum over the important bits of a key. */
+ private long checkKey(Key key) {
+ CRC32 checksum = new CRC32();
+ checksum.update(key.type);
+ checksum.update((int) (key.id & 0xffff));
+ checksum.update((int) ((key.id >> 32) & 0xffff));
+ if (!TextUtils.isEmpty(key.name)) {
+ checksum.update(key.name.getBytes());
+ }
+ return checksum.getValue();
+ }
+
+ /** Serialize a Favorite for persistence, including a checksum wrapper. */
+ private byte[] packFavorite(Cursor c) {
+ Favorite favorite = new Favorite();
+ favorite.id = c.getLong(ID_INDEX);
+ favorite.screen = c.getInt(SCREEN_INDEX);
+ favorite.container = c.getInt(CONTAINER_INDEX);
+ favorite.cellX = c.getInt(CELLX_INDEX);
+ favorite.cellY = c.getInt(CELLY_INDEX);
+ favorite.spanX = c.getInt(SPANX_INDEX);
+ favorite.spanY = c.getInt(SPANY_INDEX);
+ favorite.iconType = c.getInt(ICON_TYPE_INDEX);
+ if (favorite.iconType == Favorites.ICON_TYPE_RESOURCE) {
+ String iconPackage = c.getString(ICON_PACKAGE_INDEX);
+ if (!TextUtils.isEmpty(iconPackage)) {
+ favorite.iconPackage = iconPackage;
+ }
+ String iconResource = c.getString(ICON_RESOURCE_INDEX);
+ if (!TextUtils.isEmpty(iconResource)) {
+ favorite.iconResource = iconResource;
+ }
+ }
+ if (favorite.iconType == Favorites.ICON_TYPE_BITMAP) {
+ byte[] blob = c.getBlob(ICON_INDEX);
+ if (blob != null && blob.length > 0) {
+ favorite.icon = blob;
+ }
+ }
+ String title = c.getString(TITLE_INDEX);
+ if (!TextUtils.isEmpty(title)) {
+ favorite.title = title;
+ }
+ String intent = c.getString(INTENT_INDEX);
+ if (!TextUtils.isEmpty(intent)) {
+ favorite.intent = intent;
+ }
+ favorite.itemType = c.getInt(ITEM_TYPE_INDEX);
+ if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
+ favorite.appWidgetId = c.getInt(APPWIDGET_ID_INDEX);
+ String appWidgetProvider = c.getString(APPWIDGET_PROVIDER_INDEX);
+ if (!TextUtils.isEmpty(appWidgetProvider)) {
+ favorite.appWidgetProvider = appWidgetProvider;
+ }
+ }
+
+ return writeCheckedBytes(favorite);
+ }
+
+ /** Deserialize a Favorite from persistence, after verifying checksum wrapper. */
+ private Favorite unpackFavorite(byte[] buffer, int offset, int dataSize)
+ throws InvalidProtocolBufferNanoException {
+ Favorite favorite = new Favorite();
+ MessageNano.mergeFrom(favorite, readCheckedBytes(buffer, offset, dataSize));
+ return favorite;
+ }
+
+ /** Serialize a Screen for persistence, including a checksum wrapper. */
+ private byte[] packScreen(Cursor c) {
+ Screen screen = new Screen();
+ screen.id = c.getLong(ID_INDEX);
+ screen.rank = c.getInt(SCREEN_RANK_INDEX);
+
+ return writeCheckedBytes(screen);
+ }
+
+ /** Deserialize a Screen from persistence, after verifying checksum wrapper. */
+ private Screen unpackScreen(byte[] buffer, int offset, int dataSize)
+ throws InvalidProtocolBufferNanoException {
+ Screen screen = new Screen();
+ MessageNano.mergeFrom(screen, readCheckedBytes(buffer, offset, dataSize));
+ return screen;
+ }
+
+ /**
+ * Read the old journal from the input file.
+ *
+ * In the event of any error, just pretend we didn't have a journal,
+ * in that case, do a full backup.
+ *
+ * @param oldState the read-0only file descriptor pointing to the old journal
+ * @return a Journal protocol bugffer
+ */
+ private Journal readJournal(ParcelFileDescriptor oldState) {
+ int fileSize = (int) oldState.getStatSize();
+ int remaining = fileSize;
+ byte[] buffer = null;
+ Journal journal = new Journal();
+ if (remaining < MAX_JOURNAL_SIZE) {
+ FileInputStream inStream = new FileInputStream(oldState.getFileDescriptor());
+ int offset = 0;
+
+ buffer = new byte[remaining];
+ while (remaining > 0) {
+ int bytesRead = 0;
+ try {
+ bytesRead = inStream.read(buffer, offset, remaining);
+ } catch (IOException e) {
+ Log.w(TAG, "failed to read the journal", e);
+ buffer = null;
+ remaining = 0;
+ }
+ if (bytesRead > 0) {
+ remaining -= bytesRead;
+ } else {
+ // act like there is not journal
+ Log.w(TAG, "failed to read the journal");
+ buffer = null;
+ remaining = 0;
+ }
+ }
+
+ if (buffer != null) {
+ try {
+ MessageNano.mergeFrom(journal, readCheckedBytes(buffer, 0, fileSize));
+ } catch (InvalidProtocolBufferNanoException e) {
+ Log.d(TAG, "failed to read the journal", e);
+ journal.clear();
+ }
+ }
+
+ try {
+ inStream.close();
+ } catch (IOException e) {
+ Log.d(TAG, "failed to close the journal", e);
+ }
+ }
+ return journal;
+ }
+
+ /**
+ * Write the new journal to the output file.
+ *
+ * In the event of any error, just pretend we didn't have a journal,
+ * in that case, do a full backup.
+
+ * @param newState the write-only file descriptor pointing to the new journal
+ * @param journal a Journal protocol buffer
+ */
+ private void writeJournal(ParcelFileDescriptor newState, Journal journal) {
+ FileOutputStream outStream = null;
+ try {
+ outStream = new FileOutputStream(newState.getFileDescriptor());
+ outStream.write(writeCheckedBytes(journal));
+ outStream.close();
+ } catch (IOException e) {
+ Log.d(TAG, "failed to write backup journal", e);
+ }
+ }
+
+ /** Wrap a proto in a CheckedMessage and compute the checksum. */
+ private byte[] writeCheckedBytes(MessageNano proto) {
+ CheckedMessage wrapper = new CheckedMessage();
+ wrapper.payload = MessageNano.toByteArray(proto);
+ CRC32 checksum = new CRC32();
+ checksum.update(wrapper.payload);
+ wrapper.checksum = checksum.getValue();
+ return MessageNano.toByteArray(wrapper);
+ }
+
+ /** Unwrap a proto message from a CheckedMessage, verifying the checksum. */
+ private byte[] readCheckedBytes(byte[] buffer, int offset, int dataSize)
+ throws InvalidProtocolBufferNanoException {
+ CheckedMessage wrapper = new CheckedMessage();
+ MessageNano.mergeFrom(wrapper, buffer, offset, dataSize);
+ CRC32 checksum = new CRC32();
+ checksum.update(wrapper.payload);
+ if (wrapper.checksum != checksum.getValue()) {
+ throw new InvalidProtocolBufferNanoException("checksum does not match");
+ }
+ return wrapper.payload;
+ }
+
+ private class KeyParsingException extends Throwable {
+ private KeyParsingException(Throwable cause) {
+ super(cause);
+ }
+
+ public KeyParsingException(String reason) {
+ super(reason);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index bc0d1bc..1d264aa 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -58,6 +58,7 @@
import java.util.List;
import java.util.Set;
import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Maintains in-memory state of the Launcher. It is expected that there should be only one
@@ -169,6 +170,7 @@
boolean matchPackageNamesOnly);
public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts);
public void bindSearchablesChanged();
+ public boolean isAllAppsButtonRank(int rank);
public void onPageBoundSynchronously(int page);
public void dumpLogsToLocalData();
}
@@ -1513,7 +1515,8 @@
}
// check & update map of what's occupied; used to discard overlapping/invalid items
- private boolean checkItemPlacement(HashMap<Long, ItemInfo[][]> occupied, ItemInfo item) {
+ private boolean checkItemPlacement(HashMap<Long, ItemInfo[][]> occupied, ItemInfo item,
+ AtomicBoolean deleteOnItemOverlap) {
LauncherAppState app = LauncherAppState.getInstance();
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
int countX = (int) grid.numColumns;
@@ -1521,6 +1524,13 @@
long containerIndex = item.screenId;
if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ // Return early if we detect that an item is under the hotseat button
+ if (mCallbacks == null ||
+ mCallbacks.get().isAllAppsButtonRank((int) item.screenId)) {
+ deleteOnItemOverlap.set(true);
+ return false;
+ }
+
if (occupied.containsKey(LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
if (occupied.get(LauncherSettings.Favorites.CONTAINER_HOTSEAT)
[(int) item.screenId][0] != null) {
@@ -1658,6 +1668,7 @@
Intent intent;
while (!mStopped && c.moveToNext()) {
+ AtomicBoolean deleteOnItemOverlap = new AtomicBoolean(false);
try {
int itemType = c.getInt(itemTypeIndex);
@@ -1672,9 +1683,8 @@
if (cn != null && !isValidPackageComponent(manager, cn)) {
if (!mAppsCanBeOnRemoveableStorage) {
// Log the invalid package, and remove it from the db
- Uri uri = LauncherSettings.Favorites.getContentUri(id,
- false);
- contentResolver.delete(uri, null, null);
+ Launcher.addDumpLog(TAG, "Invalid package removed: " + cn, true);
+ itemsToRemove.add(id);
} else {
// If apps can be on external storage, then we just
// leave them for the user to remove (maybe add
@@ -1728,7 +1738,11 @@
}
}
// check & update map of what's occupied
- if (!checkItemPlacement(occupied, info)) {
+ deleteOnItemOverlap.set(false);
+ if (!checkItemPlacement(occupied, info, deleteOnItemOverlap)) {
+ if (deleteOnItemOverlap.get()) {
+ itemsToRemove.add(id);
+ }
break;
}
@@ -1776,7 +1790,12 @@
}
}
// check & update map of what's occupied
- if (!checkItemPlacement(occupied, folderInfo)) {
+ deleteOnItemOverlap.set(false);
+ if (!checkItemPlacement(occupied, folderInfo,
+ deleteOnItemOverlap)) {
+ if (deleteOnItemOverlap.get()) {
+ itemsToRemove.add(id);
+ }
break;
}
@@ -1838,7 +1857,12 @@
}
}
// check & update map of what's occupied
- if (!checkItemPlacement(occupied, appWidgetInfo)) {
+ deleteOnItemOverlap.set(false);
+ if (!checkItemPlacement(occupied, appWidgetInfo,
+ deleteOnItemOverlap)) {
+ if (deleteOnItemOverlap.get()) {
+ itemsToRemove.add(id);
+ }
break;
}
String providerName = provider.provider.flattenToString();
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index fa61b11..471d2d2 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -49,6 +49,7 @@
import android.util.Log;
import android.util.Xml;
+import com.android.launcher3.LauncherSettings.BaseLauncherColumns;
import com.android.launcher3.LauncherSettings.Favorites;
import org.xmlpull.v1.XmlPullParser;
@@ -65,7 +66,7 @@
private static final String DATABASE_NAME = "launcher.db";
- private static final int DATABASE_VERSION = 14;
+ private static final int DATABASE_VERSION = 15;
static final String OLD_AUTHORITY = "com.android.launcher2.settings";
static final String AUTHORITY = "com.android.launcher3.settings";
@@ -146,6 +147,7 @@
SqlArguments args = new SqlArguments(uri);
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ addModifiedTime(initialValues);
final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
if (rowId <= 0) return null;
@@ -164,6 +166,7 @@
try {
int numValues = values.length;
for (int i = 0; i < numValues; i++) {
+ addModifiedTime(values[i]);
if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) {
return 0;
}
@@ -192,6 +195,7 @@
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
+ addModifiedTime(values);
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int count = db.update(args.table, values, args.where, args.args);
if (count > 0) sendNotify(uri);
@@ -204,6 +208,13 @@
if (notify == null || "true".equals(notify)) {
getContext().getContentResolver().notifyChange(uri, null);
}
+
+ // always notify the backup agent
+ LauncherBackupAgent.dataChanged(getContext());
+ }
+
+ private void addModifiedTime(ContentValues values) {
+ values.put(LauncherSettings.ChangeLogColumns.MODIFIED, System.currentTimeMillis());
}
public long generateNewItemId() {
@@ -343,7 +354,8 @@
"icon BLOB," +
"uri TEXT," +
"displayMode INTEGER," +
- "appWidgetProvider TEXT" +
+ "appWidgetProvider TEXT," +
+ "modified INTEGER NOT NULL DEFAULT 0" +
");");
addWorkspacesTable(db);
@@ -384,7 +396,8 @@
private void addWorkspacesTable(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE_WORKSPACE_SCREENS + " (" +
LauncherSettings.WorkspaceScreens._ID + " INTEGER," +
- LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER" +
+ LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER," +
+ LauncherSettings.ChangeLogColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 0" +
");");
}
@@ -643,6 +656,25 @@
}
}
+
+ if (version < 15) {
+ db.beginTransaction();
+ try {
+ // Insert new column for holding update timestamp
+ db.execSQL("ALTER TABLE favorites " +
+ "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
+ db.execSQL("ALTER TABLE workspaceScreens " +
+ "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
+ db.setTransactionSuccessful();
+ version = 15;
+ } catch (SQLException ex) {
+ // Old version remains, which means we wipe old data
+ Log.e(TAG, ex.getMessage(), ex);
+ } finally {
+ db.endTransaction();
+ }
+ }
+
if (version != DATABASE_VERSION) {
Log.w(TAG, "Destroying all old data.");
db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index ada09e9..988e5ef 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -23,7 +23,16 @@
* Settings related utilities.
*/
class LauncherSettings {
- static interface BaseLauncherColumns extends BaseColumns {
+ /** Columns required on table staht will be subject to backup and restore. */
+ static interface ChangeLogColumns extends BaseColumns {
+ /**
+ * The time of the last update to this row.
+ * <P>Type: INTEGER</P>
+ */
+ static final String MODIFIED = "modified";
+ }
+
+ static interface BaseLauncherColumns extends ChangeLogColumns {
/**
* Descriptive name of the gesture that can be displayed to the user.
* <P>Type: TEXT</P>
@@ -95,7 +104,7 @@
*
* Tracks the order of workspace screens.
*/
- static final class WorkspaceScreens implements BaseColumns {
+ static final class WorkspaceScreens implements ChangeLogColumns {
/**
* The content:// style URL for this table
*/
diff --git a/src/com/android/launcher3/LiveWallpaperListAdapter.java b/src/com/android/launcher3/LiveWallpaperListAdapter.java
index 9d0f48b..e9e5e79 100644
--- a/src/com/android/launcher3/LiveWallpaperListAdapter.java
+++ b/src/com/android/launcher3/LiveWallpaperListAdapter.java
@@ -116,6 +116,7 @@
mThumbnail = thumbnail;
mInfo = info;
}
+ @Override
public void onClick(WallpaperPickerActivity a) {
Intent preview = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
preview.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,
diff --git a/src/com/android/launcher3/MarketUpdateReceiver.java b/src/com/android/launcher3/MarketUpdateReceiver.java
new file mode 100644
index 0000000..c41bf3e
--- /dev/null
+++ b/src/com/android/launcher3/MarketUpdateReceiver.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.util.Log;
+
+public class MarketUpdateReceiver extends BroadcastReceiver {
+ private static final String TAG = "MarketUpdateReceiver";
+
+ private static final String ACTION_PACKAGE_ENQUEUED =
+ "com.android.launcher.action.ACTION_PACKAGE_ENQUEUED";
+ private static final String ACTION_PACKAGE_DOWNLOADING =
+ "com.android.launcher.action.ACTION_PACKAGE_DOWNLOADING";
+ private static final String ACTION_PACKAGE_INSTALLING =
+ "com.android.launcher.action.ACTION_PACKAGE_INSTALLING";
+ private static final String ACTION_PACKAGE_DEQUEUED =
+ "com.android.launcher.action.ACTION_PACKAGE_DEQUEUED";
+
+ /** extra for {@link #ACTION_PACKAGE_ENQUEUED}, send on of the following values **/
+ private static final String EXTRA_KEY_REASON = "reason";
+ private static final String EXTRA_VALUE_REASON_INSTALL = "install";
+ private static final String EXTRA_VALUE_REASON_UPDATE = "update";
+ private static final String EXTRA_VALUE_REASON_RESTORE = "restore";
+
+ /** extra for {@link #ACTION_PACKAGE_DOWNLOADING}, send an int in the range [0-100]. **/
+ private static final String EXTRA_KEY_PROGRESS = "progress";
+
+ /**
+ * extra for {@link #ACTION_PACKAGE_DEQUEUED}
+ * send {@link android.app.Activity#RESULT_OK} on success.
+ * or {@link android.app.Activity#RESULT_CANCELED} if the package was abandoned.
+ * **/
+ private static final String EXTRA_KEY_STATUS =
+ "com.android.launcher.action.EXTRA_STATUS";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ String pkgName = "none";
+ Uri uri = intent.getData();
+ if (uri != null) {
+ pkgName = uri.getSchemeSpecificPart();;
+ }
+ if (ACTION_PACKAGE_ENQUEUED.equals(action)) {
+ String reason = "unknown";
+ if (intent.hasExtra(EXTRA_KEY_REASON)) {
+ reason = intent.getStringExtra(EXTRA_KEY_REASON);
+ }
+ Log.d(TAG, "market has promised to " + reason + ": " + pkgName);
+ } else if (ACTION_PACKAGE_DOWNLOADING.equals(action)) {
+ int progress = intent.getIntExtra(EXTRA_KEY_PROGRESS, 0);
+ Log.d(TAG, "market is downloading (" + progress + "%): " + pkgName);
+ } else if (ACTION_PACKAGE_INSTALLING.equals(action)) {
+ Log.d(TAG, "market is installing: " + pkgName);
+ } else if ( ACTION_PACKAGE_DEQUEUED.equals(action)) {
+ boolean success = Activity.RESULT_OK == intent.getIntExtra(EXTRA_KEY_STATUS,
+ Activity.RESULT_CANCELED);
+ if (success) {
+ Log.d(TAG, "market has installed: " + pkgName);
+ } else {
+ Log.d(TAG, "market has decided not to install: " + pkgName);
+ }
+ } else {
+ Log.d(TAG, "unknown message " + action);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 31a9797..1ae2943 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -911,8 +911,9 @@
mPageScrolls[i] = childLeft - scrollOffset - offsetX;
if (i != endIndex - delta) {
+ childLeft += childWidth + scrollOffset;
int nextScrollOffset = (getViewportWidth() - getChildWidth(i + delta)) / 2;
- childLeft += childWidth + nextScrollOffset;
+ childLeft += nextScrollOffset;
}
}
}
@@ -1039,9 +1040,6 @@
protected int getChildOffset(int index) {
if (index < 0 || index > getChildCount() - 1) return 0;
- final boolean isRtl = isLayoutRtl();
-
- if (isRtl) index = getChildCount() - index - 1;
int offset = getPageAt(index).getLeft() - getViewportOffsetX();
return offset;
@@ -1056,33 +1054,43 @@
final int pageCount = getChildCount();
mTmpIntPoint[0] = mTmpIntPoint[1] = 0;
+ range[0] = -1;
+ range[1] = -1;
+
if (pageCount > 0) {
int viewportWidth = getViewportWidth();
- int leftScreen = 0;
- int rightScreen = 0;
+ int curScreen = 0;
- for (leftScreen = getNextPage(); leftScreen >= 0; --leftScreen) {
- View currPage = getPageAt(leftScreen);
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View currPage = getPageAt(i);
- // Check if the right edge of the page is in the viewport
+ mTmpIntPoint[0] = 0;
+ Utilities.getDescendantCoordRelativeToParent(currPage, this, mTmpIntPoint, false);
+ if (mTmpIntPoint[0] > viewportWidth) {
+ if (range[0] == -1) {
+ continue;
+ } else {
+ break;
+ }
+ }
+
mTmpIntPoint[0] = currPage.getMeasuredWidth();
Utilities.getDescendantCoordRelativeToParent(currPage, this, mTmpIntPoint, false);
if (mTmpIntPoint[0] < 0) {
- break;
+ if (range[0] == -1) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ curScreen = i;
+ if (range[0] < 0) {
+ range[0] = curScreen;
}
}
- for (rightScreen = getNextPage(); rightScreen < getChildCount(); ++rightScreen) {
- View currPage = getPageAt(rightScreen);
- // Check if the left edge of the page is in the viewport
- mTmpIntPoint[0] = 0;
- Utilities.getDescendantCoordRelativeToParent(currPage, this, mTmpIntPoint, false);
- if (mTmpIntPoint[0] >= viewportWidth) {
- break;
- }
- }
- range[0] = Math.max(0, leftScreen);
- range[1] = Math.min(rightScreen, getChildCount() - 1);
+ range[1] = curScreen;
} else {
range[0] = -1;
range[1] = -1;
@@ -1558,8 +1566,13 @@
if (!mFreeScroll) {
snapToPage(snapPage);
} else {
- mFreeScrollMinScrollX = getScrollForPage(mTempVisiblePagesRange[0]);
- mFreeScrollMaxScrollX = getScrollForPage(mTempVisiblePagesRange[1]);
+ if (isLayoutRtl()) {
+ mFreeScrollMinScrollX = getScrollForPage(mTempVisiblePagesRange[1]);
+ mFreeScrollMaxScrollX = getScrollForPage(mTempVisiblePagesRange[0]);
+ } else {
+ mFreeScrollMinScrollX = getScrollForPage(mTempVisiblePagesRange[0]);
+ mFreeScrollMaxScrollX = getScrollForPage(mTempVisiblePagesRange[1]);
+ }
if (getCurrentPage() < mTempVisiblePagesRange[0]) {
setCurrentPage(mTempVisiblePagesRange[0]);
diff --git a/src/com/android/launcher3/PagedViewIcon.java b/src/com/android/launcher3/PagedViewIcon.java
index fa9ec5a..c6d5e49 100644
--- a/src/com/android/launcher3/PagedViewIcon.java
+++ b/src/com/android/launcher3/PagedViewIcon.java
@@ -18,6 +18,9 @@
import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Region;
+import android.graphics.Region.Op;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;
@@ -99,4 +102,27 @@
setAlpha(1f);
}
}
+
+ @Override
+ public void draw(Canvas canvas) {
+ // If text is transparent, don't draw any shadow
+ if (getCurrentTextColor() == getResources().getColor(android.R.color.transparent)) {
+ getPaint().clearShadowLayer();
+ super.draw(canvas);
+ return;
+ }
+
+ // We enhance the shadow by drawing the shadow twice
+ getPaint().setShadowLayer(BubbleTextView.SHADOW_LARGE_RADIUS, 0.0f,
+ BubbleTextView.SHADOW_Y_OFFSET, BubbleTextView.SHADOW_LARGE_COLOUR);
+ super.draw(canvas);
+ canvas.save(Canvas.CLIP_SAVE_FLAG);
+ canvas.clipRect(getScrollX(), getScrollY() + getExtendedPaddingTop(),
+ getScrollX() + getWidth(),
+ getScrollY() + getHeight(), Region.Op.INTERSECT);
+ getPaint().setShadowLayer(BubbleTextView.SHADOW_SMALL_RADIUS, 0.0f, 0.0f,
+ BubbleTextView.SHADOW_SMALL_COLOUR);
+ super.draw(canvas);
+ canvas.restore();
+ }
}
diff --git a/src/com/android/launcher3/SavedWallpaperImages.java b/src/com/android/launcher3/SavedWallpaperImages.java
index f00f62f..7cd82f0 100644
--- a/src/com/android/launcher3/SavedWallpaperImages.java
+++ b/src/com/android/launcher3/SavedWallpaperImages.java
@@ -57,6 +57,7 @@
mDbId = dbId;
mThumb = thumb;
}
+ @Override
public void onClick(WallpaperPickerActivity a) {
String imageFilename = a.getSavedImages().getImageFilename(mDbId);
File file = new File(a.getFilesDir(), imageFilename);
@@ -65,14 +66,17 @@
v.moveToLeft();
v.setTouchEnabled(false);
}
+ @Override
public void onSave(WallpaperPickerActivity a) {
boolean finishActivityWhenDone = true;
String imageFilename = a.getSavedImages().getImageFilename(mDbId);
a.setWallpaper(imageFilename, finishActivityWhenDone);
}
+ @Override
public void onDelete(WallpaperPickerActivity a) {
a.getSavedImages().deleteImage(mDbId);
}
+ @Override
public boolean isSelectable() {
return true;
}
diff --git a/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java b/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java
index ab2f5d7..7ed1c1b 100644
--- a/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java
+++ b/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java
@@ -46,6 +46,7 @@
public ThirdPartyWallpaperTile(ResolveInfo resolveInfo) {
mResolveInfo = resolveInfo;
}
+ @Override
public void onClick(WallpaperPickerActivity a) {
final ComponentName itemComponentName = new ComponentName(
mResolveInfo.activityInfo.packageName, mResolveInfo.activityInfo.name);
diff --git a/src/com/android/launcher3/WallpaperPickerActivity.java b/src/com/android/launcher3/WallpaperPickerActivity.java
index bc56b55..7f82a2f 100644
--- a/src/com/android/launcher3/WallpaperPickerActivity.java
+++ b/src/com/android/launcher3/WallpaperPickerActivity.java
@@ -87,11 +87,12 @@
public static abstract class WallpaperTileInfo {
public void onClick(WallpaperPickerActivity a) {}
public void onSave(WallpaperPickerActivity a) {}
- public void onDelete() {}
+ public void onDelete(WallpaperPickerActivity a) {}
public boolean isSelectable() { return false; }
}
public static class PickImageInfo extends WallpaperTileInfo {
+ @Override
public void onClick(WallpaperPickerActivity a) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
@@ -104,13 +105,14 @@
public UriWallpaperInfo(Uri uri) {
mUri = uri;
}
+ @Override
public void onClick(WallpaperPickerActivity a) {
CropView v = a.getCropView();
v.setTileSource(new BitmapRegionTileSource(
a, mUri, 1024, 0), null);
v.setTouchEnabled(true);
}
-
+ @Override
public void onSave(final WallpaperPickerActivity a) {
boolean finishActivityWhenDone = true;
OnBitmapCroppedHandler h = new OnBitmapCroppedHandler() {
@@ -123,6 +125,7 @@
};
a.cropImageAndSetWallpaper(mUri, h, finishActivityWhenDone);
}
+ @Override
public boolean isSelectable() {
return true;
}
@@ -138,6 +141,7 @@
mResId = resId;
mThumb = thumb;
}
+ @Override
public void onClick(WallpaperPickerActivity a) {
BitmapRegionTileSource source = new BitmapRegionTileSource(
mResources, a, mResId, 1024, 0);
@@ -151,10 +155,12 @@
v.setScale(wallpaperSize.x / crop.width());
v.setTouchEnabled(false);
}
+ @Override
public void onSave(WallpaperPickerActivity a) {
boolean finishActivityWhenDone = true;
a.cropImageAndSetWallpaper(mResources, mResId, finishActivityWhenDone);
}
+ @Override
public boolean isSelectable() {
return true;
}
@@ -363,7 +369,8 @@
CheckableFrameLayout c =
(CheckableFrameLayout) mWallpapersView.getChildAt(i);
if (c.isChecked()) {
- ((WallpaperTileInfo) c.getTag()).onDelete();
+ WallpaperTileInfo info = (WallpaperTileInfo) c.getTag();
+ info.onDelete(WallpaperPickerActivity.this);
viewsToRemove.add(c);
}
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index aab0a63..ada41a0 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -933,8 +933,9 @@
boolean passRightSwipesToCustomContent =
(mTouchDownTime - mCustomContentShowTime) > CUSTOM_CONTENT_GESTURE_DELAY;
- if (deltaX > 0 && getScreenIdForPageIndex(getCurrentPage()) == CUSTOM_CONTENT_SCREEN_ID
- && passRightSwipesToCustomContent) {
+ boolean swipeInIgnoreDirection = isLayoutRtl() ? deltaX < 0 : deltaX > 0;
+ if (swipeInIgnoreDirection && getScreenIdForPageIndex(getCurrentPage()) ==
+ CUSTOM_CONTENT_SCREEN_ID && passRightSwipesToCustomContent) {
// Pass swipes to the right to the custom content page.
return;
}
@@ -1363,9 +1364,15 @@
if (hasCustomContent()) {
int index = mScreenOrder.indexOf(CUSTOM_CONTENT_SCREEN_ID);
int scrollDelta = getScrollForPage(index + 1) - getScrollX();
- translationX = Math.max(scrollDelta, 0);
+ translationX = scrollDelta;
progress = (1.0f * scrollDelta) /
(getScrollForPage(index + 1) - getScrollForPage(index));
+
+ if (isLayoutRtl()) {
+ translationX = Math.min(0, translationX);
+ } else {
+ translationX = Math.max(0, translationX);
+ }
progress = Math.max(0, progress);
}
@@ -1396,7 +1403,10 @@
updateStateForCustomContent(screenCenter);
enableHwLayersOnVisiblePages();
- if ((mOverScrollX < 0 && !hasCustomContent()) || mOverScrollX > mMaxScrollX) {
+ boolean shouldOverScroll = (mOverScrollX < 0 && (!hasCustomContent() || isLayoutRtl())) ||
+ (mOverScrollX > mMaxScrollX && (!hasCustomContent() || !isLayoutRtl()));
+
+ if (shouldOverScroll) {
int index = 0;
float pivotX = 0f;
final float leftBiasedPivot = 0.25f;
@@ -1836,10 +1846,10 @@
final boolean stateIsSpringLoaded = (state == State.SPRING_LOADED);
final boolean stateIsSmall = (state == State.SMALL);
final boolean stateIsOverview = (state == State.OVERVIEW);
- float finalBackgroundAlpha = stateIsSpringLoaded ? 1.0f : 0f;
+ float finalBackgroundAlpha = (stateIsSpringLoaded || stateIsOverview) ? 1.0f : 0f;
float finalHotseatAndPageIndicatorAlpha = (stateIsOverview || stateIsSmall) ? 0f : 1f;
float finalOverviewPanelAlpha = stateIsOverview ? 1f : 0f;
- float finalSearchBarAlpha = stateIsOverview ? 0f : 1f;
+ float finalSearchBarAlpha = !stateIsNormal ? 0f : 1f;
float finalWorkspaceTranslationY = stateIsOverview ? getOverviewModeTranslationY() : 0;
boolean zoomIn = true;
@@ -1857,13 +1867,11 @@
} else if (stateIsOverview) {
mNewScale = mOverviewModeShrinkFactor;
} else if (stateIsSmall){
- mNewScale = mOverviewModeShrinkFactor - 0.1f;
+ mNewScale = mOverviewModeShrinkFactor - 0.3f;
}
if (oldStateIsNormal && stateIsSmall) {
zoomIn = false;
updateChildrenLayersEnabled(false);
- } else {
- finalBackgroundAlpha = 1.0f;
}
}
final int duration = zoomIn ?
@@ -1871,24 +1879,9 @@
getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
for (int i = 0; i < getChildCount(); i++) {
final CellLayout cl = (CellLayout) getChildAt(i);
- float finalAlpha = (!mWorkspaceFadeInAdjacentScreens || stateIsSpringLoaded ||
- (i == mCurrentPage)) ? 1f : 0f;
- float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
- float initialAlpha = currentAlpha;
-
- // Determine the pages alpha during the state transition
- if ((oldStateIsSmall && stateIsNormal) ||
- (oldStateIsNormal && stateIsSmall)) {
- // To/from workspace - only show the current page unless the transition is not
- // animated and the animation end callback below doesn't run;
- // or, if we're in spring-loaded mode
- if (i == mCurrentPage || !animated || oldStateIsSpringLoaded) {
- finalAlpha = 1f;
- } else {
- initialAlpha = 0f;
- finalAlpha = 0f;
- }
- }
+ float finalAlpha = (!mWorkspaceFadeInAdjacentScreens ||
+ (i == mCurrentPage)) && !stateIsSmall ? 1f : 0f;
+ float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
mOldAlphas[i] = initialAlpha;
mNewAlphas[i] = finalAlpha;
@@ -1901,7 +1894,9 @@
}
}
- View searchBar = mLauncher.getQsbBar();
+ final View searchBar = mLauncher.getQsbBar();
+ final View overviewPanel = mLauncher.getOverviewPanel();
+ final View hotseat = mLauncher.getHotseat();
if (animated) {
LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(this);
scale.scaleX(mNewScale)
@@ -1917,7 +1912,6 @@
cl.setBackgroundAlpha(mNewBackgroundAlphas[i]);
cl.setShortcutAndWidgetAlpha(mNewAlphas[i]);
} else {
-
if (mOldAlphas[i] != mNewAlphas[i] || currentAlpha != mNewAlphas[i]) {
LauncherViewPropertyAnimator alphaAnim =
new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
@@ -1947,29 +1941,32 @@
pageIndicatorAlpha = ObjectAnimator.ofFloat(getPageIndicator(), "alpha",
finalHotseatAndPageIndicatorAlpha);
}
- ObjectAnimator hotseatAlpha = ObjectAnimator.ofFloat(mLauncher.getHotseat(), "alpha",
+ ObjectAnimator hotseatAlpha = ObjectAnimator.ofFloat(hotseat, "alpha",
finalHotseatAndPageIndicatorAlpha);
ObjectAnimator searchBarAlpha = ObjectAnimator.ofFloat(searchBar,
"alpha", finalSearchBarAlpha);
- ObjectAnimator overviewPanelAlpha = ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(),
+ ObjectAnimator overviewPanelAlpha = ObjectAnimator.ofFloat(overviewPanel,
"alpha", finalOverviewPanelAlpha);
- overviewPanelAlpha.addUpdateListener(new AlphaUpdateListener(
- mLauncher.getOverviewPanel()));
- hotseatAlpha.addUpdateListener(new AlphaUpdateListener(mLauncher.getHotseat()));
+
+ overviewPanelAlpha.addUpdateListener(new AlphaUpdateListener(overviewPanel));
+ hotseatAlpha.addUpdateListener(new AlphaUpdateListener(hotseat));
searchBarAlpha.addUpdateListener(new AlphaUpdateListener(searchBar));
+
if (getPageIndicator() != null) {
pageIndicatorAlpha.addUpdateListener(new AlphaUpdateListener(getPageIndicator()));
}
+
+
anim.play(overviewPanelAlpha);
anim.play(hotseatAlpha);
anim.play(searchBarAlpha);
anim.play(pageIndicatorAlpha);
anim.setStartDelay(delay);
} else {
- mLauncher.getOverviewPanel().setAlpha(finalOverviewPanelAlpha);
- AlphaUpdateListener.updateVisibility(mLauncher.getOverviewPanel());
- mLauncher.getHotseat().setAlpha(finalHotseatAndPageIndicatorAlpha);
- AlphaUpdateListener.updateVisibility(mLauncher.getHotseat());
+ overviewPanel.setAlpha(finalOverviewPanelAlpha);
+ AlphaUpdateListener.updateVisibility(overviewPanel);
+ hotseat.setAlpha(finalHotseatAndPageIndicatorAlpha);
+ AlphaUpdateListener.updateVisibility(hotseat);
if (getPageIndicator() != null) {
getPageIndicator().setAlpha(finalHotseatAndPageIndicatorAlpha);
AlphaUpdateListener.updateVisibility(getPageIndicator());
@@ -2917,7 +2914,10 @@
mTempPt[0] = x;
mTempPt[1] = y;
mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
- mLauncher.getHotseat().getHitRect(r);
+
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ r = grid.getHotseatRect();
if (r.contains(mTempPt[0], mTempPt[1])) {
return true;
}