Merge "Add NotificationListener to launcher." into ub-launcher3-master
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index b6e5bb0..5588289 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -88,6 +88,8 @@
<activity android:name="com.android.launcher3.dragndrop.AddItemActivity"
android:theme="@android:style/Theme.DeviceDefault.Light.Dialog.Alert"
+ android:excludeFromRecents="true"
+ android:autoRemoveFromRecents="true"
android:label="@string/action_add_to_workspace" >
<intent-filter>
<action android:name="android.content.pm.action.CONFIRM_PIN_ITEM" />
diff --git a/build.gradle b/build.gradle
index 3a812a9..51ac5a1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -46,12 +46,16 @@
androidTest {
java.srcDirs = ['tests/src']
res.srcDirs = ['tests/res']
- manifest.srcFile "tests/AndroidManifest.xml"
+ manifest.srcFile "tests/AndroidManifest-common.xml"
}
aosp {
manifest.srcFile "AndroidManifest.xml"
}
+
+ aospAndroidTest {
+ manifest.srcFile "tests/AndroidManifest.xml"
+ }
}
}
diff --git a/res/layout/widgets_list_row_view.xml b/res/layout/widgets_list_row_view.xml
index 30a34d4..530e856 100644
--- a/res/layout/widgets_list_row_view.xml
+++ b/res/layout/widgets_list_row_view.xml
@@ -41,7 +41,7 @@
android:paddingRight="@dimen/widget_section_horizontal_padding"
android:paddingTop="@dimen/widget_section_vertical_padding"
android:singleLine="true"
- android:textColor="@color/widgets_view_section_text_color"
+ android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
launcher:customShadows="false"
launcher:deferShadowGeneration="true"
diff --git a/res/values/colors.xml b/res/values/colors.xml
index ccbae58..809fc6d 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -46,8 +46,7 @@
<color name="spring_loaded_highlighted_panel_border_color">#FFF</color>
<!-- Widgets view -->
- <color name="widgets_view_section_text_color">#FFFFFF</color>
- <color name="widgets_view_item_text_color">#C4C4C4</color>
+ <color name="widgets_view_item_text_color">#3B3B3B</color>
<!-- Used as a fallback since colorSecondary doesn't exist pre-API 25 -->
<color name="fallback_secondary_color">#FF37474F</color>
diff --git a/src/com/android/launcher3/AnotherWindowDropTarget.java b/src/com/android/launcher3/AnotherWindowDropTarget.java
deleted file mode 100644
index 052e5d0..0000000
--- a/src/com/android/launcher3/AnotherWindowDropTarget.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2015 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.Context;
-import android.graphics.PointF;
-import android.graphics.Rect;
-
-/**
- * Drop target used when another window (i.e. another process) has accepted a global system drag.
- * If the accepted item was a shortcut, we delete it from Launcher.
- */
-public class AnotherWindowDropTarget implements DropTarget {
- final Launcher mLauncher;
-
- public AnotherWindowDropTarget (Context context) { mLauncher = Launcher.getLauncher(context); }
-
- @Override
- public boolean isDropEnabled() { return true; }
-
- @Override
- public void onDrop(DragObject dragObject) {
- dragObject.deferDragViewCleanupPostAnimation = false;
- LauncherModel.deleteItemFromDatabase(mLauncher, (ShortcutInfo) dragObject.dragInfo);
- }
-
- @Override
- public void onDragEnter(DragObject dragObject) {}
-
- @Override
- public void onDragOver(DragObject dragObject) {}
-
- @Override
- public void onDragExit(DragObject dragObject) {}
-
- @Override
- public boolean acceptDrop(DragObject dragObject) {
- return dragObject.dragInfo instanceof ShortcutInfo;
- }
-
- @Override
- public void prepareAccessibilityDrop() {}
-
- // These methods are implemented in Views
- @Override
- public void getHitRectRelativeToDragLayer(Rect outRect) {}
-}
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 4c88e7e..5fff2e7 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -93,7 +93,7 @@
}
@Override
- void onAddToDatabase(ContentWriter writer) {
+ public void onAddToDatabase(ContentWriter writer) {
super.onAddToDatabase(writer);
writer.put(LauncherSettings.Favorites.TITLE, title)
.put(LauncherSettings.Favorites.OPTIONS, options);
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 82c7ab8..aec6c7d 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -166,7 +166,7 @@
/**
* Write the fields of this item to the DB
*/
- void onAddToDatabase(ContentWriter writer) {
+ public void onAddToDatabase(ContentWriter writer) {
if (screenId == Workspace.EXTRA_EMPTY_SCREEN_ID) {
// We should never persist an item on the extra empty screen.
throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index aa5b8c8..26e388d 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -49,6 +49,7 @@
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Parcelable;
import android.os.Process;
import android.os.StrictMode;
import android.os.SystemClock;
@@ -96,6 +97,7 @@
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dragndrop.PinItemDragListener;
import com.android.launcher3.dynamicui.ExtractedColors;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
@@ -126,6 +128,7 @@
import com.android.launcher3.util.ViewOnDrawExecutor;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
+import com.android.launcher3.widget.WidgetAddFlowHandler;
import com.android.launcher3.widget.WidgetHostViewLoader;
import com.android.launcher3.widget.WidgetsContainerView;
@@ -691,8 +694,9 @@
// Since the view was just bound, also launch the configure activity if needed
LauncherAppWidgetProviderInfo provider = mAppWidgetManager
.getLauncherAppWidgetInfo(widgetId);
- if (provider != null && provider.configure != null) {
- startRestoredWidgetReconfigActivity(provider, widgetInfo);
+ if (provider != null) {
+ new WidgetAddFlowHandler(provider)
+ .startConfigActivity(this, widgetInfo, REQUEST_RECONFIGURE_APPWIDGET);
}
}
break;
@@ -739,7 +743,7 @@
} else if (resultCode == RESULT_OK) {
addAppWidgetImpl(
appWidgetId, requestArgs, null,
- requestArgs.getWidgetProvider(this),
+ requestArgs.getWidgetHandler(),
ON_ACTIVITY_RESULT_ANIMATION_DELAY);
}
return;
@@ -832,7 +836,7 @@
}
@Override
- protected void onActivityResult(
+ public void onActivityResult(
final int requestCode, final int resultCode, final Intent data) {
handleActivityResult(requestCode, resultCode, data);
if (mLauncherCallbacks != null) {
@@ -898,7 +902,7 @@
if (resultCode == RESULT_OK) {
animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId,
- requestArgs.getWidgetProvider(this));
+ requestArgs.getWidgetHandler().getProviderInfo(this));
boundWidget = layout;
onCompleteRunnable = new Runnable() {
@Override
@@ -1777,6 +1781,14 @@
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onHomeIntent();
}
+
+ Parcelable dragExtra = intent
+ .getParcelableExtra(PinItemDragListener.EXTRA_PIN_ITEM_DRAG_LISTENER);
+ if (dragExtra instanceof PinItemDragListener) {
+ PinItemDragListener dragListener = (PinItemDragListener) dragExtra;
+ dragListener.setLauncher(this);
+ mDragLayer.setOnDragListener(dragListener);
+ }
}
if (mLauncherCallbacks != null) {
@@ -2010,7 +2022,7 @@
}
}
- private void setWaitingForResult(PendingRequestArgs args) {
+ public void setWaitingForResult(PendingRequestArgs args) {
boolean isLocked = isWorkspaceLocked();
mPendingRequestArgs = args;
if (isLocked != isWorkspaceLocked()) {
@@ -2025,24 +2037,18 @@
}
void addAppWidgetFromDropImpl(int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget,
- LauncherAppWidgetProviderInfo appWidgetInfo) {
+ WidgetAddFlowHandler addFlowHandler) {
if (LOGD) {
Log.d(TAG, "Adding widget from drop");
}
- addAppWidgetImpl(appWidgetId, info, boundWidget, appWidgetInfo, 0);
+ addAppWidgetImpl(appWidgetId, info, boundWidget, addFlowHandler, 0);
}
void addAppWidgetImpl(int appWidgetId, ItemInfo info,
- AppWidgetHostView boundWidget, LauncherAppWidgetProviderInfo appWidgetInfo,
- int delay) {
- if (appWidgetInfo.configure != null) {
- setWaitingForResult(PendingRequestArgs.forWidgetInfo(appWidgetId, appWidgetInfo, info));
+ AppWidgetHostView boundWidget, WidgetAddFlowHandler addFlowHandler, int delay) {
+ if (!addFlowHandler.startConfigActivity(this, appWidgetId, info, REQUEST_CREATE_APPWIDGET)) {
+ // If the configuration flow was not started, add the widget
- // Launch over to configure widget, if needed
- mAppWidgetManager.startConfigActivity(appWidgetInfo, appWidgetId, this,
- mAppWidgetHost, REQUEST_CREATE_APPWIDGET);
- } else {
- // Otherwise just add it
Runnable onComplete = new Runnable() {
@Override
public void run() {
@@ -2051,7 +2057,7 @@
null);
}
};
- completeAddAppWidget(appWidgetId, info, boundWidget, appWidgetInfo);
+ completeAddAppWidget(appWidgetId, info, boundWidget, addFlowHandler.getProviderInfo(this));
mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
}
}
@@ -2103,6 +2109,7 @@
private void addAppWidgetFromDrop(PendingAddWidgetInfo info) {
AppWidgetHostView hostView = info.boundWidget;
int appWidgetId;
+ WidgetAddFlowHandler addFlowHandler = info.getHander();
if (hostView != null) {
// In the case where we've prebound the widget, we remove it from the DragLayer
if (LOGD) {
@@ -2111,7 +2118,7 @@
getDragLayer().removeView(hostView);
appWidgetId = hostView.getAppWidgetId();
- addAppWidgetFromDropImpl(appWidgetId, info, hostView, info.info);
+ addAppWidgetFromDropImpl(appWidgetId, info, hostView, addFlowHandler);
// Clear the boundWidget so that it doesn't get destroyed.
info.boundWidget = null;
@@ -2124,17 +2131,9 @@
boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
appWidgetId, info.info, options);
if (success) {
- addAppWidgetFromDropImpl(appWidgetId, info, null, info.info);
+ addAppWidgetFromDropImpl(appWidgetId, info, null, addFlowHandler);
} else {
- setWaitingForResult(PendingRequestArgs.forWidgetInfo(appWidgetId, info.info, info));
- Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE,
- info.info.getUser());
- // TODO: we need to make sure that this accounts for the options bundle.
- // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
- startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
+ addFlowHandler.startBindFlow(this, appWidgetId, info, REQUEST_BIND_APPWIDGET);
}
}
}
@@ -2350,30 +2349,22 @@
final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
if (v.isReadyForClickSetup()) {
+ LauncherAppWidgetProviderInfo appWidgetInfo =
+ mAppWidgetManager.findProvider(info.providerName, info.user);
+ if (appWidgetInfo == null) {
+ return;
+ }
+ WidgetAddFlowHandler addFlowHandler = new WidgetAddFlowHandler(appWidgetInfo);
+
if (info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
if (!info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
// This should not happen, as we make sure that an Id is allocated during bind.
return;
}
- LauncherAppWidgetProviderInfo appWidgetInfo =
- mAppWidgetManager.findProvider(info.providerName, info.user);
- if (appWidgetInfo != null) {
- setWaitingForResult(PendingRequestArgs
- .forWidgetInfo(info.appWidgetId, appWidgetInfo, info));
-
- Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, info.appWidgetId);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, appWidgetInfo.provider);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE,
- appWidgetInfo.getUser());
- startActivityForResult(intent, REQUEST_BIND_PENDING_APPWIDGET);
- }
+ addFlowHandler.startBindFlow(this, info.appWidgetId, info,
+ REQUEST_BIND_PENDING_APPWIDGET);
} else {
- LauncherAppWidgetProviderInfo appWidgetInfo =
- mAppWidgetManager.getLauncherAppWidgetInfo(info.appWidgetId);
- if (appWidgetInfo != null) {
- startRestoredWidgetReconfigActivity(appWidgetInfo, info);
- }
+ addFlowHandler.startConfigActivity(this, info, REQUEST_RECONFIGURE_APPWIDGET);
}
} else {
final String packageName = info.providerName.getPackageName();
@@ -2381,13 +2372,6 @@
}
}
- private void startRestoredWidgetReconfigActivity(
- LauncherAppWidgetProviderInfo provider, LauncherAppWidgetInfo info) {
- setWaitingForResult(PendingRequestArgs.forWidgetInfo(info.appWidgetId, provider, info));
- mAppWidgetManager.startConfigActivity(provider,
- info.appWidgetId, this, mAppWidgetHost, REQUEST_RECONFIGURE_APPWIDGET);
- }
-
/**
* Event handler for the "grid" button that appears on the home screen, which
* enters all apps mode.
diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java
index 1429df5..61b8a7a 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java
@@ -135,8 +135,6 @@
// The provider info or the views might have changed.
checkIfAutoAdvance();
-
- mIsScrollable = checkScrollableRecursively(this);
}
private boolean checkScrollableRecursively(ViewGroup viewGroup) {
@@ -383,6 +381,8 @@
}
});
}
+
+ mIsScrollable = checkScrollableRecursively(this);
}
@Override
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
index b68a64b..1e0f285 100644
--- a/src/com/android/launcher3/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -65,7 +65,7 @@
/**
* Indicates that the widget hasn't been instantiated yet.
*/
- static final int NO_ID = -1;
+ public static final int NO_ID = -1;
/**
* Indicates that this is a locally defined widget and hence has no system allocated id.
@@ -76,7 +76,7 @@
* Identifier for this widget when talking with
* {@link android.appwidget.AppWidgetManager} for updates.
*/
- int appWidgetId = NO_ID;
+ public int appWidgetId = NO_ID;
public ComponentName providerName;
@@ -126,7 +126,7 @@
}
@Override
- void onAddToDatabase(ContentWriter writer) {
+ public void onAddToDatabase(ContentWriter writer) {
super.onAddToDatabase(writer);
writer.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId)
.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, providerName.flattenToString())
diff --git a/src/com/android/launcher3/MainThreadExecutor.java b/src/com/android/launcher3/MainThreadExecutor.java
index 866b17c..4ca0a59 100644
--- a/src/com/android/launcher3/MainThreadExecutor.java
+++ b/src/com/android/launcher3/MainThreadExecutor.java
@@ -16,65 +16,18 @@
package com.android.launcher3;
-import android.os.Handler;
import android.os.Looper;
-import java.util.List;
-import java.util.concurrent.AbstractExecutorService;
-import java.util.concurrent.TimeUnit;
+import com.android.launcher3.util.LooperExecuter;
/**
* An executor service that executes its tasks on the main thread.
*
* Shutting down this executor is not supported.
*/
-public class MainThreadExecutor extends AbstractExecutorService {
+public class MainThreadExecutor extends LooperExecuter {
- private Handler mHandler = new Handler(Looper.getMainLooper());
-
- @Override
- public void execute(Runnable runnable) {
- if (Looper.getMainLooper() == Looper.myLooper()) {
- runnable.run();
- } else {
- mHandler.post(runnable);
- }
- }
-
- /**
- * Not supported and throws an exception when used.
- */
- @Override
- @Deprecated
- public void shutdown() {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Not supported and throws an exception when used.
- */
- @Override
- @Deprecated
- public List<Runnable> shutdownNow() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isShutdown() {
- return false;
- }
-
- @Override
- public boolean isTerminated() {
- return false;
- }
-
- /**
- * Not supported and throws an exception when used.
- */
- @Override
- @Deprecated
- public boolean awaitTermination(long l, TimeUnit timeUnit) throws InterruptedException {
- throw new UnsupportedOperationException();
+ public MainThreadExecutor() {
+ super(Looper.getMainLooper());
}
}
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index 1a42395..b35dcb7 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -161,7 +161,7 @@
}
@Override
- void onAddToDatabase(ContentWriter writer) {
+ public void onAddToDatabase(ContentWriter writer) {
super.onAddToDatabase(writer);
writer.put(LauncherSettings.BaseLauncherColumns.TITLE, title)
.put(LauncherSettings.BaseLauncherColumns.INTENT, getIntent())
diff --git a/src/com/android/launcher3/compat/PinItemRequestCompat.java b/src/com/android/launcher3/compat/PinItemRequestCompat.java
index 74d7765..481310a 100644
--- a/src/com/android/launcher3/compat/PinItemRequestCompat.java
+++ b/src/com/android/launcher3/compat/PinItemRequestCompat.java
@@ -21,13 +21,14 @@
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.os.Bundle;
+import android.os.Parcel;
import android.os.Parcelable;
/**
* A wrapper around platform implementation of PinItemRequestCompat until the
* updated SDK is available.
*/
-public class PinItemRequestCompat {
+public class PinItemRequestCompat implements Parcelable {
public static final String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
@@ -83,6 +84,32 @@
}
}
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int i) {
+ parcel.writeParcelable(mObject, i);
+ }
+
+ public Intent toIntent() {
+ return new Intent().putExtra(EXTRA_PIN_ITEM_REQUEST, mObject);
+ }
+
+ public static final Parcelable.Creator<PinItemRequestCompat> CREATOR =
+ new Parcelable.Creator<PinItemRequestCompat>() {
+ public PinItemRequestCompat createFromParcel(Parcel source) {
+ Parcelable object = source.readParcelable(null);
+ return new PinItemRequestCompat(object);
+ }
+
+ public PinItemRequestCompat[] newArray(int size) {
+ return new PinItemRequestCompat[size];
+ }
+ };
+
public static PinItemRequestCompat getPinItemRequest(Intent intent) {
Parcelable extra = intent.getParcelableExtra(EXTRA_PIN_ITEM_REQUEST);
return extra == null ? null : new PinItemRequestCompat(extra);
diff --git a/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java b/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java
index ece7759..1cfbd20 100644
--- a/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java
+++ b/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java
@@ -35,6 +35,7 @@
import android.widget.Toast;
import com.android.launcher3.IconCache;
+import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -67,7 +68,7 @@
public abstract Drawable getFullResIcon(IconCache cache);
- public boolean startConfigActivity(Activity activity, int requestCode) {
+ public boolean startConfigActivity(Launcher activity, int requestCode) {
Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT)
.setComponent(getComponent());
try {
@@ -136,7 +137,7 @@
}
@Override
- public boolean startConfigActivity(Activity activity, int requestCode) {
+ public boolean startConfigActivity(Launcher activity, int requestCode) {
if (getUser().equals(Process.myUserHandle())) {
return super.startConfigActivity(activity, requestCode);
}
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 6c6f141..423fdab 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -17,12 +17,23 @@
package com.android.launcher3.dragndrop;
import android.annotation.TargetApi;
+import android.app.ActivityOptions;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
+import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.Intent;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.Build;
import android.os.Bundle;
+import android.util.Log;
+import android.view.MotionEvent;
import android.view.View;
+import android.view.View.*;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.InstallShortcutReceiver;
@@ -38,13 +49,18 @@
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.WidgetHostViewLoader;
+import com.android.launcher3.widget.WidgetImageView;
@TargetApi(Build.VERSION_CODES.N_MR1)
-public class AddItemActivity extends BaseActivity {
+public class AddItemActivity extends BaseActivity implements OnLongClickListener, OnTouchListener {
+
+ private static final int SHADOW_SIZE = 10;
private static final int REQUEST_BIND_APPWIDGET = 1;
private static final String STATE_EXTRA_WIDGET_ID = "state.widget.id";
+ private final PointF mLastTouchPos = new PointF();
+
private PinItemRequestCompat mRequest;
private LauncherAppState mApp;
private InvariantDeviceProfile mIdp;
@@ -86,11 +102,54 @@
finish();
}
}
+
+ mWidgetCell.setOnTouchListener(this);
+ mWidgetCell.setOnLongClickListener(this);
+ }
+
+ @Override
+ public boolean onTouch(View view, MotionEvent motionEvent) {
+ mLastTouchPos.set(motionEvent.getX(), motionEvent.getY());
+ return false;
+ }
+
+ @Override
+ public boolean onLongClick(View view) {
+ // Find the position of the preview relative to the touch location.
+ WidgetImageView img = mWidgetCell.getWidgetView();
+ Rect bounds = img.getBitmapBounds();
+ bounds.offset(img.getLeft() - (int) mLastTouchPos.x, img.getTop() - (int) mLastTouchPos.y);
+
+ // Start home and pass the draw request params
+ PinItemDragListener listener = new PinItemDragListener(mRequest, bounds);
+ Intent homeIntent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .setPackage(getPackageName())
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .putExtra(PinItemDragListener.EXTRA_PIN_ITEM_DRAG_LISTENER, listener);
+ startActivity(homeIntent,
+ ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out).toBundle());
+
+ // Start a system drag and drop. We use a transparent bitmap as preview for system drag
+ // as the preview is handled internally by launcher.
+ ClipDescription description = new ClipDescription("", new String[]{listener.getMimeType()});
+ ClipData data = new ClipData(description, new ClipData.Item(""));
+ view.startDragAndDrop(data, new DragShadowBuilder(view) {
+
+ @Override
+ public void onDrawShadow(Canvas canvas) { }
+
+ @Override
+ public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) {
+ outShadowSize.set(SHADOW_SIZE, SHADOW_SIZE);
+ outShadowTouchPoint.set(SHADOW_SIZE / 2, SHADOW_SIZE / 2);
+ }
+ }, null, View.DRAG_FLAG_GLOBAL);
+ return false;
}
private void setupShortcut() {
- WidgetItem item = new WidgetItem(new PinShortcutRequestActivityInfo(
- mRequest.getShortcutInfo(), this));
+ WidgetItem item = new WidgetItem(new PinShortcutRequestActivityInfo(mRequest, this));
mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache());
mWidgetCell.ensurePreview();
}
diff --git a/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java b/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java
deleted file mode 100644
index 1623010..0000000
--- a/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2016 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.dragndrop;
-
-import android.content.Context;
-import android.view.View;
-
-import com.android.launcher3.DragSource;
-import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-
-/**
- * DragSource used when the drag started at another window.
- */
-public class AnotherWindowDragSource implements DragSource {
-
- private final Context mContext;
-
- AnotherWindowDragSource(Context context) {
- mContext = context;
- }
-
- @Override
- public boolean supportsAppInfoDropTarget() {
- return false;
- }
-
- @Override
- public boolean supportsDeleteDropTarget() {
- return false;
- }
-
- @Override
- public float getIntrinsicIconScaleFactor() {
- return 1;
- }
-
- @Override
- public void onDropCompleted(View target, DragObject d,
- boolean isFlingToDelete, boolean success) {
- if (!success) {
- Launcher.getLauncher(mContext).exitSpringLoadedDragModeDelayed(false, 0, null);
- }
-
- }
-
- @Override
- public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
- // TODO: Probably log something
- }
-}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 745776d..80c2860 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -36,7 +36,6 @@
import com.android.launcher3.R;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.TouchController;
@@ -447,7 +446,8 @@
/**
* Call this from a drag source view.
*/
- public boolean onDragEvent(DragEvent event) {
+ public boolean onDragEvent(long dragStartTime, DragEvent event) {
+ mFlingToDeleteHelper.recordDragEvent(dragStartTime, event);
return mDragDriver != null && mDragDriver.onDragEvent(event);
}
diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
index a90cfff..65c0f29 100644
--- a/src/com/android/launcher3/dragndrop/DragDriver.java
+++ b/src/com/android/launcher3/dragndrop/DragDriver.java
@@ -16,20 +16,13 @@
package com.android.launcher3.dragndrop;
-import android.content.ClipData;
-import android.content.ClipDescription;
import android.content.Context;
-import android.content.Intent;
import android.view.DragEvent;
import android.view.MotionEvent;
import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.InstallShortcutReceiver;
-import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
-import java.util.ArrayList;
-
/**
* Base class for driving a drag/drop operation.
*/
@@ -107,7 +100,6 @@
private final DragObject mDragObject;
private final Context mContext;
- boolean mReceivedDropEvent = false;
float mLastX = 0;
float mLastY = 0;
@@ -149,65 +141,21 @@
case DragEvent.ACTION_DROP:
mLastX = event.getX();
mLastY = event.getY();
- mReceivedDropEvent =
- updateInfoFromClipData(event.getClipData(), event.getClipDescription());
- return mReceivedDropEvent;
-
+ mEventListener.onDriverDragMove(event.getX(), event.getY());
+ mEventListener.onDriverDragEnd(mLastX, mLastY);
+ return true;
case DragEvent.ACTION_DRAG_EXITED:
mEventListener.onDriverDragExitWindow();
return true;
case DragEvent.ACTION_DRAG_ENDED:
- if (mReceivedDropEvent) {
- mEventListener.onDriverDragEnd(mLastX, mLastY);
- } else {
- mEventListener.onDriverDragCancel();
- }
+ mEventListener.onDriverDragCancel();
return true;
default:
return false;
}
}
-
- private boolean updateInfoFromClipData(ClipData data, ClipDescription desc) {
- if (data == null) {
- return false;
- }
- ArrayList<Intent> intents = new ArrayList<>();
- int itemCount = data.getItemCount();
- for (int i = 0; i < itemCount; i++) {
- Intent intent = data.getItemAt(i).getIntent();
- if (intent == null) {
- continue;
- }
-
- // Give preference to shortcut intents.
- if (!Intent.ACTION_CREATE_SHORTCUT.equals(intent.getAction())) {
- intents.add(intent);
- continue;
- }
- ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(mContext, intent);
- if (info != null) {
- mDragObject.dragInfo = info;
- return true;
- }
- return true;
- }
-
- // Try creating shortcuts just using the intent and label
- Intent fullIntent = new Intent().putExtra(Intent.EXTRA_SHORTCUT_NAME, desc.getLabel());
- for (Intent intent : intents) {
- fullIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent);
- ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(mContext, fullIntent);
- if (info != null) {
- mDragObject.dragInfo = info;
- return true;
- }
- }
-
- return false;
- }
}
/**
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 4279cc3..de416e3 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -21,21 +21,13 @@
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.TargetApi;
-import android.content.ClipDescription;
import android.content.Context;
-import android.content.Intent;
import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
-import android.os.Build;
import android.util.AttributeSet;
-import android.view.DragEvent;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -59,9 +51,7 @@
import com.android.launcher3.PinchToOverviewListener;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
-import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.Folder;
@@ -373,49 +363,6 @@
return false;
}
- @TargetApi(Build.VERSION_CODES.N)
- private void handleSystemDragStart(DragEvent event) {
- if (!FeatureFlags.LAUNCHER3_USE_SYSTEM_DRAG_DRIVER || !Utilities.ATLEAST_NOUGAT) {
- return;
- }
- if (mLauncher.isWorkspaceLocked()) {
- return;
- }
-
- ClipDescription description = event.getClipDescription();
- if (!description.hasMimeType(ClipDescription.MIMETYPE_TEXT_INTENT)) {
- return;
- }
- ShortcutInfo info = new ShortcutInfo();
- // Set a dummy intent until we get the final value
- info.intent = new Intent();
-
- // Since we are not going through the workspace for starting the drag, set drag related
- // information on the workspace before starting the drag.
- ExternalDragPreviewProvider previewProvider =
- new ExternalDragPreviewProvider(mLauncher, info);
- mLauncher.getWorkspace().prepareDragWithProvider(previewProvider);
-
- DragOptions options = new DragOptions();
- options.systemDndStartPoint = new Point((int) event.getX(), (int) event.getY());
-
- int halfPadding = previewProvider.previewPadding / 2;
- mDragController.startDrag(
- Bitmap.createBitmap(1, 1, Config.ARGB_8888),
- 0, 0,
- new AnotherWindowDragSource(mLauncher), info,
- new Point(- halfPadding, halfPadding),
- previewProvider.getPreviewBounds(), 1f, options);
- }
-
- @Override
- public boolean onDragEvent (DragEvent event) {
- if (event.getAction() == DragEvent.ACTION_DRAG_STARTED) {
- handleSystemDragStart(event);
- }
- return mDragController.onDragEvent(event);
- }
-
/**
* Determine the rect of the descendant in this DragLayer's coordinates
*
diff --git a/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java b/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java
deleted file mode 100644
index e558487..0000000
--- a/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2016 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.dragndrop;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Rect;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.graphics.DragPreviewProvider;
-import com.android.launcher3.graphics.HolographicOutlineHelper;
-
-/**
- * Extension of {@link DragPreviewProvider} which provides a dummy outline when drag starts from
- * a different window.
- * It just draws an empty circle to a placeholder outline.
- */
-public class ExternalDragPreviewProvider extends DragPreviewProvider {
-
- private final Launcher mLauncher;
- private final ItemInfo mAddInfo;
-
- private final int[] mOutlineSize;
-
- public ExternalDragPreviewProvider(Launcher launcher, ItemInfo addInfo) {
- super(null, launcher);
- mLauncher = launcher;
- mAddInfo = addInfo;
-
- mOutlineSize = mLauncher.getWorkspace().estimateItemSize(mAddInfo, false, false);
- }
-
- public Rect getPreviewBounds() {
- Rect rect = new Rect();
- DeviceProfile dp = mLauncher.getDeviceProfile();
- rect.left = blurSizeOutline / 2;
- rect.top = (mOutlineSize[1] - dp.cellHeightPx) / 2;
- rect.right = rect.left + dp.iconSizePx;
- rect.bottom = rect.top + dp.iconSizePx;
- return rect;
- }
-
- @Override
- public Bitmap createDragOutline(Canvas canvas) {
- final Bitmap b = Bitmap.createBitmap(mOutlineSize[0], mOutlineSize[1], Bitmap.Config.ALPHA_8);
- canvas.setBitmap(b);
-
- Paint paint = new Paint();
- paint.setColor(Color.WHITE);
- paint.setStyle(Paint.Style.FILL);
-
- // Use 0.9f times the radius for the actual circle to account for icon normalization.
- float radius = getPreviewBounds().width() * 0.5f;
- canvas.drawCircle(blurSizeOutline / 2 + radius,
- blurSizeOutline / 2 + radius, radius * 0.9f, paint);
-
- HolographicOutlineHelper.getInstance(mLauncher).applyExpensiveOutlineWithBlur(b, canvas);
- canvas.setBitmap(null);
- return b;
- }
-}
diff --git a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
index a2aa27d..e794744 100644
--- a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
+++ b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
@@ -17,6 +17,8 @@
package com.android.launcher3.dragndrop;
import android.graphics.PointF;
+import android.os.SystemClock;
+import android.view.DragEvent;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
@@ -53,6 +55,31 @@
mVelocityTracker.addMovement(ev);
}
+ /**
+ * Same as {@link #recordMotionEvent}. It creates a temporary {@link MotionEvent} object
+ * using {@param event} for tracking velocity.
+ */
+ public void recordDragEvent(long dragStartTime, DragEvent event) {
+ final int motionAction;
+ switch (event.getAction()) {
+ case DragEvent.ACTION_DRAG_STARTED:
+ motionAction = MotionEvent.ACTION_DOWN;
+ break;
+ case DragEvent.ACTION_DRAG_LOCATION:
+ motionAction = MotionEvent.ACTION_MOVE;
+ break;
+ case DragEvent.ACTION_DRAG_ENDED:
+ motionAction = MotionEvent.ACTION_UP;
+ break;
+ default:
+ return;
+ }
+ MotionEvent emulatedEvent = MotionEvent.obtain(dragStartTime, SystemClock.uptimeMillis(),
+ motionAction, event.getX(), event.getY(), 0);
+ recordMotionEvent(emulatedEvent);
+ emulatedEvent.recycle();
+ }
+
public void releaseVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
new file mode 100644
index 0000000..1a99cc8
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2017 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.dragndrop;
+
+import android.content.ClipDescription;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.DragEvent;
+import android.view.View;
+
+import com.android.launcher3.DeleteDropTarget;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.PendingAddItemInfo;
+import com.android.launcher3.compat.PinItemRequestCompat;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.widget.PendingAddShortcutInfo;
+import com.android.launcher3.widget.PendingAddWidgetInfo;
+import com.android.launcher3.widget.PendingItemPreviewProvider;
+
+import java.util.UUID;
+
+/**
+ * {@link DragSource} for handling drop from from a different window. This object is initialized
+ * in the source window and is passed on to the Launcher activity as an Intent extra.
+ */
+public class PinItemDragListener implements Parcelable, View.OnDragListener, DragSource {
+
+ private static final String TAG = "PinItemDragListener";
+
+ private static final String MIME_TYPE_PREFIX = "com.android.launcher3.drag_and_drop/";
+ public static final String EXTRA_PIN_ITEM_DRAG_LISTENER = "pin_item_drag_listener";
+
+ private final PinItemRequestCompat mRequest;
+
+ // Position of preview relative to the touch location
+ private final Rect mPreviewRect;
+
+ // Randomly generated id used to verify the drag event.
+ private final String mId;
+
+ private Launcher mLauncher;
+ private DragController mDragController;
+ private long mDragStartTime;
+
+ public PinItemDragListener(PinItemRequestCompat request, Rect previewRect) {
+ mRequest = request;
+ mPreviewRect = previewRect;
+ mId = UUID.randomUUID().toString();
+ }
+
+ private PinItemDragListener(Parcel parcel) {
+ mRequest = PinItemRequestCompat.CREATOR.createFromParcel(parcel);
+ mPreviewRect = Rect.CREATOR.createFromParcel(parcel);
+ mId = parcel.readString();
+ }
+
+ public String getMimeType() {
+ return MIME_TYPE_PREFIX + mId;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int i) {
+ mRequest.writeToParcel(parcel, i);
+ mPreviewRect.writeToParcel(parcel, i);
+ parcel.writeString(mId);
+ }
+
+ public void setLauncher(Launcher launcher) {
+ mLauncher = launcher;
+ mDragController = launcher.getDragController();
+ }
+
+ @Override
+ public boolean onDrag(View view, DragEvent event) {
+ if (mLauncher == null || mDragController == null) {
+ postCleanup();
+ return false;
+ }
+ if (event.getAction() == DragEvent.ACTION_DRAG_STARTED) {
+ if (onDragStart(event)) {
+ return true;
+ } else {
+ postCleanup();
+ return false;
+ }
+ }
+ return mDragController.onDragEvent(mDragStartTime, event);
+ }
+
+ private boolean onDragStart(DragEvent event) {
+ if (!mRequest.isValid()) {
+ return false;
+ }
+ ClipDescription desc = event.getClipDescription();
+ if (desc == null || !desc.hasMimeType(getMimeType())) {
+ Log.e(TAG, "Someone started a dragAndDrop before us.");
+ return false;
+ }
+
+ if (mLauncher.isWorkspaceLocked()) {
+ // TODO: implement wait
+ return false;
+ }
+
+ final PendingAddItemInfo item;
+ final Bitmap preview;
+
+ Point dragShift = new Point(mPreviewRect.left, mPreviewRect.top);
+ if (mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_SHORTCUT) {
+ item = new PendingAddShortcutInfo(
+ new PinShortcutRequestActivityInfo(mRequest, mLauncher));
+
+ ShortcutInfoCompat compat = new ShortcutInfoCompat(mRequest.getShortcutInfo());
+ Bitmap icon = LauncherIcons.createShortcutIcon(compat, mLauncher, false /* badged */);
+
+ // Create a preview same as the workspace cell size and draw the icon at the
+ // appropriate position.
+ int[] size = mLauncher.getWorkspace().estimateItemSize(item, true, false);
+ preview = Bitmap.createBitmap(size[0], size[1], Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(preview);
+ DeviceProfile dp = mLauncher.getDeviceProfile();
+ c.drawBitmap(icon, (size[0] - icon.getWidth()) / 2,
+ (size[1] - icon.getHeight() - dp.iconTextSizePx - dp.iconDrawablePaddingPx) / 2,
+ new Paint(Paint.FILTER_BITMAP_FLAG));
+ } else {
+ PendingAddWidgetInfo info = new PendingAddWidgetInfo(
+ LauncherAppWidgetProviderInfo.fromProviderInfo(
+ mLauncher, mRequest.getAppWidgetProviderInfo(mLauncher)));
+ int[] size = mLauncher.getWorkspace().estimateItemSize(info, true, false);
+
+ float minScale = 1.25f;
+ int maxWidth = Math.min((int) (mPreviewRect.width() * minScale), size[0]);
+ int[] previewSizeBeforeScale = new int[1];
+ preview = LauncherAppState.getInstance(mLauncher).getWidgetCache()
+ .generateWidgetPreview(mLauncher, info.info, maxWidth, null,
+ previewSizeBeforeScale);
+
+ dragShift.offset(
+ (mPreviewRect.width() - preview.getWidth()) / 2,
+ (mPreviewRect.height() - preview.getHeight()) / 2);
+ item = info;
+ }
+
+ PendingItemPreviewProvider previewProvider =
+ new PendingItemPreviewProvider(new View(mLauncher), item, preview);
+
+ // Since we are not going through the workspace for starting the drag, set drag related
+ // information on the workspace before starting the drag.
+ mLauncher.getWorkspace().prepareDragWithProvider(previewProvider);
+
+ Point downPos = new Point((int) event.getX(), (int) event.getY());
+ DragOptions options = new DragOptions();
+ options.systemDndStartPoint = downPos;
+
+ int x = downPos.x + dragShift.x;
+ int y = downPos.y + dragShift.y;
+ mDragController.startDrag(
+ preview, x, y, this, item, null, null, 1f, options);
+ mDragStartTime = SystemClock.uptimeMillis();
+ return true;
+ }
+
+ @Override
+ public boolean supportsAppInfoDropTarget() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsDeleteDropTarget() {
+ return false;
+ }
+
+ @Override
+ public float getIntrinsicIconScaleFactor() {
+ return 1f;
+ }
+
+ @Override
+ public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
+ boolean success) {
+ if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
+ !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
+ // Exit spring loaded mode if we have not successfully dropped or have not handled the
+ // drop in Workspace
+ mLauncher.exitSpringLoadedDragModeDelayed(true,
+ Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
+ }
+ postCleanup();
+ }
+
+ @Override
+ public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
+ LauncherLogProto.Target targetParent) {
+ // TODO: We should probably log something
+ }
+
+ private void postCleanup() {
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ removeListener();
+ }
+ });
+ }
+
+ public void removeListener() {
+ if (mLauncher != null) {
+ mLauncher.getDragLayer().setOnDragListener(null);
+ }
+ }
+
+ public static final Parcelable.Creator<PinItemDragListener> CREATOR =
+ new Parcelable.Creator<PinItemDragListener>() {
+ public PinItemDragListener createFromParcel(Parcel source) {
+ return new PinItemDragListener(source);
+ }
+
+ public PinItemDragListener[] newArray(int size) {
+ return new PinItemDragListener[size];
+ }
+ };
+}
diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
index d1f878a..2121b43 100644
--- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -26,7 +26,9 @@
import android.os.Build;
import com.android.launcher3.IconCache;
+import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.compat.PinItemRequestCompat;
import com.android.launcher3.compat.ShortcutConfigActivityInfo;
/**
@@ -40,12 +42,15 @@
// actual existing class.
private static final String DUMMY_COMPONENT_CLASS = "pinned-shortcut";
+ private final PinItemRequestCompat mRequest;
private final ShortcutInfo mInfo;
private final Context mContext;
- public PinShortcutRequestActivityInfo(ShortcutInfo info, Context context) {
- super(new ComponentName(info.getPackage(), DUMMY_COMPONENT_CLASS), info.getUserHandle());
- mInfo = info;
+ public PinShortcutRequestActivityInfo(PinItemRequestCompat request, Context context) {
+ super(new ComponentName(request.getShortcutInfo().getPackage(), DUMMY_COMPONENT_CLASS),
+ request.getShortcutInfo().getUserHandle());
+ mRequest = request;
+ mInfo = request.getShortcutInfo();
mContext = context;
}
@@ -61,8 +66,9 @@
}
@Override
- public boolean startConfigActivity(Activity activity, int requestCode) {
- throw new RuntimeException("Not supported");
+ public boolean startConfigActivity(Launcher activity, int requestCode) {
+ activity.onActivityResult(requestCode, Activity.RESULT_OK, mRequest.toIntent());
+ return true;
}
@Override
diff --git a/src/com/android/launcher3/util/LooperExecuter.java b/src/com/android/launcher3/util/LooperExecuter.java
new file mode 100644
index 0000000..4db999b
--- /dev/null
+++ b/src/com/android/launcher3/util/LooperExecuter.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.launcher3.util;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.List;
+import java.util.concurrent.AbstractExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Extension of {@link AbstractExecutorService} which executed on a provided looper.
+ */
+public class LooperExecuter extends AbstractExecutorService {
+
+ private final Handler mHandler;
+
+ public LooperExecuter(Looper looper) {
+ mHandler = new Handler(looper);
+ }
+
+ @Override
+ public void execute(Runnable runnable) {
+ if (mHandler.getLooper() == Looper.myLooper()) {
+ runnable.run();
+ } else {
+ mHandler.post(runnable);
+ }
+ }
+
+ /**
+ * Not supported and throws an exception when used.
+ */
+ @Override
+ @Deprecated
+ public void shutdown() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Not supported and throws an exception when used.
+ */
+ @Override
+ @Deprecated
+ public List<Runnable> shutdownNow() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return false;
+ }
+
+ @Override
+ public boolean isTerminated() {
+ return false;
+ }
+
+ /**
+ * Not supported and throws an exception when used.
+ */
+ @Override
+ @Deprecated
+ public boolean awaitTermination(long l, TimeUnit timeUnit) throws InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/src/com/android/launcher3/util/PendingRequestArgs.java b/src/com/android/launcher3/util/PendingRequestArgs.java
index 9452fbd..538e1df 100644
--- a/src/com/android/launcher3/util/PendingRequestArgs.java
+++ b/src/com/android/launcher3/util/PendingRequestArgs.java
@@ -15,15 +15,13 @@
*/
package com.android.launcher3.util;
-import android.appwidget.AppWidgetProviderInfo;
import android.content.ContentValues;
-import android.content.Context;
import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.WidgetAddFlowHandler;
/**
* Utility class to store information regarding a pending request made by launcher. This information
@@ -58,12 +56,7 @@
mArg1 = parcel.readInt();
mObjectType = parcel.readInt();
- if (parcel.readInt() != 0) {
- mObject = (mObjectType == TYPE_INTENT ? Intent.CREATOR : AppWidgetProviderInfo.CREATOR)
- .createFromParcel(parcel);
- } else {
- mObject = null;
- }
+ mObject = parcel.readParcelable(null);
}
@Override
@@ -79,18 +72,11 @@
dest.writeInt(mArg1);
dest.writeInt(mObjectType);
- if (mObject != null) {
- dest.writeInt(1);
- mObject.writeToParcel(dest, flags);
- } else {
- dest.writeInt(0);
- }
+ dest.writeParcelable(mObject, flags);
}
- public LauncherAppWidgetProviderInfo getWidgetProvider(Context context) {
- return mObjectType == TYPE_APP_WIDGET ?
- LauncherAppWidgetProviderInfo.fromProviderInfo(
- context, (AppWidgetProviderInfo) mObject) : null;
+ public WidgetAddFlowHandler getWidgetHandler() {
+ return mObjectType == TYPE_APP_WIDGET ? (WidgetAddFlowHandler) mObject : null;
}
public int getWidgetId() {
@@ -106,8 +92,9 @@
}
public static PendingRequestArgs forWidgetInfo(
- int appWidgetId, AppWidgetProviderInfo widgetInfo, ItemInfo info) {
- PendingRequestArgs args = new PendingRequestArgs(appWidgetId, TYPE_APP_WIDGET, widgetInfo);
+ int appWidgetId, WidgetAddFlowHandler widgetHandler, ItemInfo info) {
+ PendingRequestArgs args =
+ new PendingRequestArgs(appWidgetId, TYPE_APP_WIDGET, widgetHandler);
args.copyFrom(info);
return args;
}
diff --git a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
index 7968684..23e2f92 100644
--- a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
@@ -51,4 +51,8 @@
minSpanX = i.minSpanX;
minSpanY = i.minSpanY;
}
+
+ public WidgetAddFlowHandler getHander() {
+ return new WidgetAddFlowHandler(info);
+ }
}
diff --git a/src/com/android/launcher3/widget/WidgetAddFlowHandler.java b/src/com/android/launcher3/widget/WidgetAddFlowHandler.java
new file mode 100644
index 0000000..f44e56c
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetAddFlowHandler.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017 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.widget;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.compat.AppWidgetManagerCompat;
+import com.android.launcher3.util.PendingRequestArgs;
+
+/**
+ * Utility class to handle app widget add flow.
+ */
+public class WidgetAddFlowHandler implements Parcelable {
+
+ private final AppWidgetProviderInfo mProviderInfo;
+
+ public WidgetAddFlowHandler(AppWidgetProviderInfo providerInfo) {
+ mProviderInfo = providerInfo;
+ }
+
+ private WidgetAddFlowHandler(Parcel parcel) {
+ mProviderInfo = AppWidgetProviderInfo.CREATOR.createFromParcel(parcel);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int i) {
+ mProviderInfo.writeToParcel(parcel, i);
+ }
+
+ public void startBindFlow(Launcher launcher, int appWidgetId, ItemInfo info, int requestCode) {
+ launcher.setWaitingForResult(PendingRequestArgs.forWidgetInfo(appWidgetId, this, info));
+
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, mProviderInfo.provider);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE,
+ mProviderInfo.getProfile());
+ // TODO: we need to make sure that this accounts for the options bundle.
+ // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
+ launcher.startActivityForResult(intent, requestCode);
+ }
+
+ /**
+ * @see #startConfigActivity(Launcher, int, ItemInfo, int)
+ */
+ public boolean startConfigActivity(Launcher launcher, LauncherAppWidgetInfo info,
+ int requestCode) {
+ return startConfigActivity(launcher, info.appWidgetId, info, requestCode);
+ }
+
+ /**
+ * Starts the widget configuration flow if needed.
+ * @return true if the configuration flow was started, false otherwise.
+ */
+ public boolean startConfigActivity(Launcher launcher, int appWidgetId, ItemInfo info,
+ int requestCode) {
+ if (mProviderInfo.configure == null) {
+ return false;
+ }
+ launcher.setWaitingForResult(PendingRequestArgs.forWidgetInfo(appWidgetId, this, info));
+
+ AppWidgetManagerCompat.getInstance(launcher).startConfigActivity(
+ mProviderInfo, appWidgetId, launcher, launcher.getAppWidgetHost(), requestCode);
+ return true;
+ }
+
+ public LauncherAppWidgetProviderInfo getProviderInfo(Context context) {
+ return LauncherAppWidgetProviderInfo.fromProviderInfo(context, mProviderInfo);
+ }
+
+ public static final Parcelable.Creator<WidgetAddFlowHandler> CREATOR =
+ new Parcelable.Creator<WidgetAddFlowHandler>() {
+ public WidgetAddFlowHandler createFromParcel(Parcel source) {
+ return new WidgetAddFlowHandler(source);
+ }
+
+ public WidgetAddFlowHandler[] newArray(int size) {
+ return new WidgetAddFlowHandler[size];
+ }
+ };
+}
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 87247f4..455ec4e 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -144,12 +144,8 @@
}
}
- public int[] getPreviewSize() {
- int[] maxSize = new int[2];
-
- maxSize[0] = mPresetPreviewSize;
- maxSize[1] = mPresetPreviewSize;
- return maxSize;
+ public WidgetImageView getWidgetView() {
+ return mWidgetImage;
}
public void applyPreview(Bitmap bitmap) {
@@ -166,12 +162,8 @@
if (mActiveRequest != null) {
return;
}
- int[] size = getPreviewSize();
- if (DEBUG) {
- Log.d(TAG, String.format("[tag=%s] ensurePreview (%d, %d):",
- getTagToString(), size[0], size[1]));
- }
- mActiveRequest = mWidgetPreviewLoader.getPreview(mItem, size[0], size[1], this);
+ mActiveRequest = mWidgetPreviewLoader.getPreview(
+ mItem, mPresetPreviewSize, mPresetPreviewSize, this);
}
@Override
diff --git a/tests/Android.mk b/tests/Android.mk
index 5103ced..e8797a7 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -20,6 +20,7 @@
LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator mockito-target-minus-junit4
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/AndroidManifest-common.xml
LOCAL_SDK_VERSION := current
LOCAL_MIN_SDK_VERSION := 21
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
new file mode 100644
index 0000000..763481a
--- /dev/null
+++ b/tests/AndroidManifest-common.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.launcher3.tests">
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+
+ <receiver android:name="com.android.launcher3.testcomponent.AppWidgetNoConfig">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data android:name="android.appwidget.provider"
+ android:resource="@xml/appwidget_no_config" />
+ </receiver>
+
+ <receiver android:name="com.android.launcher3.testcomponent.AppWidgetWithConfig">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data android:name="android.appwidget.provider"
+ android:resource="@xml/appwidget_with_config" />
+ </receiver>
+
+ <activity
+ android:name="com.android.launcher3.testcomponent.WidgetConfigActivity">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/res/layout/test_layout_appwidget_blue.xml b/tests/res/layout/test_layout_appwidget_blue.xml
new file mode 100644
index 0000000..8111978
--- /dev/null
+++ b/tests/res/layout/test_layout_appwidget_blue.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:background="#FF0000FF"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
\ No newline at end of file
diff --git a/tests/res/layout/test_layout_appwidget_red.xml b/tests/res/layout/test_layout_appwidget_red.xml
new file mode 100644
index 0000000..48d3e81
--- /dev/null
+++ b/tests/res/layout/test_layout_appwidget_red.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:background="#FFFF0000"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
\ No newline at end of file
diff --git a/tests/res/xml/appwidget_no_config.xml b/tests/res/xml/appwidget_no_config.xml
new file mode 100644
index 0000000..d24dfe3
--- /dev/null
+++ b/tests/res/xml/appwidget_no_config.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<appwidget-provider
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:minWidth="180dp"
+ android:minHeight="110dp"
+ android:updatePeriodMillis="86400000"
+ android:initialLayout="@layout/test_layout_appwidget_red"
+ android:resizeMode="horizontal|vertical"
+ android:widgetCategory="home_screen">
+</appwidget-provider>
\ No newline at end of file
diff --git a/tests/res/xml/appwidget_with_config.xml b/tests/res/xml/appwidget_with_config.xml
new file mode 100644
index 0000000..3e96c6f
--- /dev/null
+++ b/tests/res/xml/appwidget_with_config.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<appwidget-provider
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:minWidth="180dp"
+ android:minHeight="110dp"
+ android:updatePeriodMillis="86400000"
+ android:initialLayout="@layout/test_layout_appwidget_blue"
+ android:configure="com.android.launcher3.testcomponent.WidgetConfigActivity"
+ android:resizeMode="horizontal|vertical"
+ android:widgetCategory="home_screen">
+</appwidget-provider>
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/testcomponent/AppWidgetNoConfig.java b/tests/src/com/android/launcher3/testcomponent/AppWidgetNoConfig.java
new file mode 100644
index 0000000..9b320d8
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/AppWidgetNoConfig.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 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.testcomponent;
+
+import android.appwidget.AppWidgetProvider;
+
+/**
+ * A simple app widget without any configuration screen.
+ */
+public class AppWidgetNoConfig extends AppWidgetProvider {
+
+
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/AppWidgetWithConfig.java b/tests/src/com/android/launcher3/testcomponent/AppWidgetWithConfig.java
new file mode 100644
index 0000000..033e6e6
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/AppWidgetWithConfig.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 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.testcomponent;
+
+/**
+ * A simple app widget with configuration sceen.
+ */
+public class AppWidgetWithConfig extends AppWidgetNoConfig {
+
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/WidgetConfigActivity.java b/tests/src/com/android/launcher3/testcomponent/WidgetConfigActivity.java
new file mode 100644
index 0000000..c0509bc
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/WidgetConfigActivity.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 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.testcomponent;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+
+/**
+ * Simple activity for widget configuration
+ */
+public class WidgetConfigActivity extends Activity {
+
+ public static final String SUFFIX_FINISH = "-finish";
+ public static final String EXTRA_CODE = "code";
+ public static final String EXTRA_INTENT = "intent";
+
+ private final BroadcastReceiver mFinishReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ WidgetConfigActivity.this.setResult(
+ intent.getIntExtra(EXTRA_CODE, RESULT_CANCELED),
+ (Intent) intent.getParcelableExtra(EXTRA_INTENT));
+ WidgetConfigActivity.this.finish();
+ }
+ };
+
+ private final String mAction = this.getClass().getName();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ registerReceiver(mFinishReceiver, new IntentFilter(mAction + SUFFIX_FINISH));
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ sendBroadcast(new Intent(mAction).putExtra(Intent.EXTRA_INTENT, getIntent()));
+ }
+
+ @Override
+ protected void onDestroy() {
+ unregisterReceiver(mFinishReceiver);
+ super.onDestroy();
+ }
+}
diff --git a/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java b/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java
index 42c6cd7..4bc40c6 100644
--- a/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java
+++ b/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java
@@ -1,13 +1,27 @@
+/*
+ * Copyright (C) 2017 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.ui;
-import android.app.SearchManager;
-import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Point;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.support.test.uiautomator.By;
@@ -19,22 +33,23 @@
import android.test.InstrumentationTestCase;
import android.view.MotionEvent;
-import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.testcomponent.AppWidgetNoConfig;
+import com.android.launcher3.testcomponent.AppWidgetWithConfig;
import com.android.launcher3.util.ManagedProfileHeuristic;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Locale;
import java.util.concurrent.Callable;
-import java.util.concurrent.atomic.AtomicReference;
/**
* Base class for all instrumentation tests providing various utility methods.
@@ -42,6 +57,7 @@
public class LauncherInstrumentationTestCase extends InstrumentationTestCase {
public static final long DEFAULT_UI_TIMEOUT = 3000;
+ public static final long DEFAULT_WORKER_TIMEOUT_SECS = 5;
protected UiDevice mDevice;
protected Context mTargetContext;
@@ -233,18 +249,11 @@
* Runs the callback on the UI thread and returns the result.
*/
protected <T> T getOnUiThread(final Callable<T> callback) {
- final AtomicReference<T> result = new AtomicReference<>(null);
try {
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- try {
- result.set(callback.call());
- } catch (Exception e) { }
- }
- });
- } catch (Throwable t) { }
- return result.get();
+ return new MainThreadExecutor().submit(callback).get();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
}
/**
@@ -252,35 +261,14 @@
* @param hasConfigureScreen if true, a provider with a config screen is returned.
*/
protected LauncherAppWidgetProviderInfo findWidgetProvider(final boolean hasConfigureScreen) {
- LauncherAppWidgetProviderInfo info = getOnUiThread(new Callable<LauncherAppWidgetProviderInfo>() {
+ LauncherAppWidgetProviderInfo info =
+ getOnUiThread(new Callable<LauncherAppWidgetProviderInfo>() {
@Override
public LauncherAppWidgetProviderInfo call() throws Exception {
- InvariantDeviceProfile idv = LauncherAppState.getIDP(mTargetContext);
-
- ComponentName searchComponent = ((SearchManager) mTargetContext
- .getSystemService(Context.SEARCH_SERVICE)).getGlobalSearchActivity();
- String searchPackage = searchComponent == null
- ? null : searchComponent.getPackageName();
-
- for (AppWidgetProviderInfo info :
- AppWidgetManagerCompat.getInstance(mTargetContext).getAllProviders()) {
- if ((info.configure != null) ^ hasConfigureScreen) {
- continue;
- }
- // Exclude the widgets in search package, as Launcher already binds them in
- // QSB, so they can cause conflicts.
- if (info.provider.getPackageName().equals(searchPackage)) {
- continue;
- }
- LauncherAppWidgetProviderInfo widgetInfo = LauncherAppWidgetProviderInfo
- .fromProviderInfo(mTargetContext, info);
- if (widgetInfo.minSpanX >= idv.numColumns
- || widgetInfo.minSpanY >= idv.numRows) {
- continue;
- }
- return widgetInfo;
- }
- return null;
+ ComponentName cn = new ComponentName(getInstrumentation().getContext(),
+ hasConfigureScreen ? AppWidgetWithConfig.class : AppWidgetNoConfig.class);
+ return AppWidgetManagerCompat.getInstance(mTargetContext)
+ .findProvider(cn, Process.myUserHandle());
}
});
if (info == null) {
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
new file mode 100644
index 0000000..7cbd292
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2017 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.ui.widget;
+
+import android.app.Activity;
+import android.app.Application;
+import android.appwidget.AppWidgetManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.View;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.testcomponent.WidgetConfigActivity;
+import com.android.launcher3.ui.LauncherInstrumentationTestCase;
+import com.android.launcher3.util.Condition;
+import com.android.launcher3.util.SimpleActivityMonitor;
+import com.android.launcher3.util.Wait;
+import com.android.launcher3.widget.WidgetCell;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test to verify widget configuration is properly shown.
+ */
+@LargeTest
+public class AddConfigWidgetTest extends LauncherInstrumentationTestCase {
+
+ public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
+ public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5;
+
+ private LauncherAppWidgetProviderInfo mWidgetInfo;
+ private SimpleActivityMonitor mActivityMonitor;
+ private MainThreadExecutor mMainThreadExecutor;
+ private AppWidgetManager mAppWidgetManager;
+
+ private int mWidgetId;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mWidgetInfo = findWidgetProvider(true /* hasConfigureScreen */);
+ mActivityMonitor = new SimpleActivityMonitor();
+ ((Application) getInstrumentation().getTargetContext().getApplicationContext())
+ .registerActivityLifecycleCallbacks(mActivityMonitor);
+ mMainThreadExecutor = new MainThreadExecutor();
+ mAppWidgetManager = AppWidgetManager.getInstance(mTargetContext);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ ((Application) getInstrumentation().getTargetContext().getApplicationContext())
+ .unregisterActivityLifecycleCallbacks(mActivityMonitor);
+ super.tearDown();
+ }
+
+ public void testWidgetConfig() throws Throwable {
+ runTest(false, true);
+ }
+
+ public void testWidgetConfig_rotate() throws Throwable {
+ runTest(true, true);
+ }
+
+ public void testConfigCancelled() throws Throwable {
+ runTest(false, false);
+ }
+
+ public void testConfigCancelled_rotate() throws Throwable {
+ runTest(true, false);
+ }
+
+ /**
+ * @param rotateConfig should the config screen be rotated
+ * @param acceptConfig accept the config activity
+ */
+ private void runTest(boolean rotateConfig, boolean acceptConfig) throws Throwable {
+ lockRotation(true);
+
+ clearHomescreen();
+ startLauncher();
+
+ // Open widget tray and wait for load complete.
+ final UiObject2 widgetContainer = openWidgetsTray();
+ assertTrue(Wait.atMost(Condition.minChildCount(widgetContainer, 2), DEFAULT_UI_TIMEOUT));
+
+ // Drag widget to homescreen
+ WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor();
+ UiObject2 widget = scrollAndFind(widgetContainer, By.clazz(WidgetCell.class)
+ .hasDescendant(By.text(mWidgetInfo.getLabel(mTargetContext.getPackageManager()))));
+ dragToWorkspace(widget, false);
+ // Widget id for which the config activity was opened
+ mWidgetId = monitor.getWidgetId();
+
+ if (rotateConfig) {
+ // Rotate the screen and verify that the config activity is recreated
+ monitor = new WidgetConfigStartupMonitor();
+ lockRotation(false);
+ assertEquals(mWidgetId, monitor.getWidgetId());
+ }
+
+ // Verify that the widget id is valid and bound
+ assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId));
+
+ if (acceptConfig) {
+ setResult(Activity.RESULT_OK);
+ assertTrue(Wait.atMost(new WidgetSearchCondition(), DEFAULT_ACTIVITY_TIMEOUT));
+ assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId));
+ } else {
+ setResult(Activity.RESULT_CANCELED);
+ // Verify that the widget id is deleted.
+ assertTrue(Wait.atMost(new Condition() {
+ @Override
+ public boolean isTrue() throws Throwable {
+ return mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null;
+ }
+ }, DEFAULT_ACTIVITY_TIMEOUT));
+ }
+ }
+
+ private void setResult(int resultCode) {
+ String action = WidgetConfigActivity.class.getName() + WidgetConfigActivity.SUFFIX_FINISH;
+ getInstrumentation().getTargetContext().sendBroadcast(
+ new Intent(action).putExtra(WidgetConfigActivity.EXTRA_CODE, resultCode));
+ }
+
+ /**
+ * Condition for searching widget id
+ */
+ private class WidgetSearchCondition extends Condition
+ implements Callable<Boolean>, Workspace.ItemOperator {
+
+ @Override
+ public boolean isTrue() throws Throwable {
+ return mMainThreadExecutor.submit(this).get();
+ }
+
+ @Override
+ public boolean evaluate(ItemInfo info, View view) {
+ return info instanceof LauncherAppWidgetInfo &&
+ ((LauncherAppWidgetInfo) info).providerName.equals(mWidgetInfo.provider) &&
+ ((LauncherAppWidgetInfo) info).appWidgetId == mWidgetId;
+ }
+
+ @Override
+ public Boolean call() throws Exception {
+ // Find the resumed launcher
+ Launcher launcher = null;
+ for (Activity a : mActivityMonitor.resumed) {
+ if (a instanceof Launcher) {
+ launcher = (Launcher) a;
+ }
+ }
+ if (launcher == null) {
+ return false;
+ }
+ return launcher.getWorkspace().getFirstMatch(this) != null;
+ }
+ }
+
+ /**
+ * Broadcast receiver for receiving widget config activity status.
+ */
+ private class WidgetConfigStartupMonitor extends BroadcastReceiver {
+
+ private final CountDownLatch latch = new CountDownLatch(1);
+ private Intent mIntent;
+
+ WidgetConfigStartupMonitor() {
+ getInstrumentation().getTargetContext().registerReceiver(this,
+ new IntentFilter(WidgetConfigActivity.class.getName()));
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT);
+ latch.countDown();
+ }
+
+ public int getWidgetId() throws InterruptedException {
+ latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS);
+ getInstrumentation().getTargetContext().unregisterReceiver(this);
+ assertNotNull(mIntent);
+ assertEquals(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE, mIntent.getAction());
+ int widgetId = mIntent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
+ LauncherAppWidgetInfo.NO_ID);
+ assertNotSame(widgetId, LauncherAppWidgetInfo.NO_ID);
+ return widgetId;
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/ui/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
similarity index 74%
rename from tests/src/com/android/launcher3/ui/AddWidgetTest.java
rename to tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index d536af2..b7e1ca9 100644
--- a/tests/src/com/android/launcher3/ui/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -1,4 +1,19 @@
-package com.android.launcher3.ui;
+/*
+ * Copyright (C) 2017 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.ui.widget;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiObject2;
@@ -10,6 +25,7 @@
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.Workspace.ItemOperator;
+import com.android.launcher3.ui.LauncherInstrumentationTestCase;
import com.android.launcher3.util.Condition;
import com.android.launcher3.util.Wait;
import com.android.launcher3.widget.WidgetCell;
diff --git a/tests/src/com/android/launcher3/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
similarity index 90%
rename from tests/src/com/android/launcher3/BindWidgetTest.java
rename to tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index 575b42b..df2b662 100644
--- a/tests/src/com/android/launcher3/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -1,4 +1,19 @@
-package com.android.launcher3;
+/*
+ * Copyright (C) 2017 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.ui.widget;
import android.appwidget.AppWidgetHost;
import android.content.ComponentName;
@@ -12,10 +27,19 @@
import android.support.test.uiautomator.UiSelector;
import android.test.suitebuilder.annotation.LargeTest;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppWidgetHostView;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.PendingAppWidgetHostView;
+import com.android.launcher3.Workspace;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.ui.LauncherInstrumentationTestCase;
import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.util.LooperExecuter;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.WidgetHostViewLoader;
@@ -315,14 +339,11 @@
/**
* Blocks the current thread until all the jobs in the main worker thread are complete.
*/
- private void waitUntilLoaderIdle() throws InterruptedException {
- final CountDownLatch latch = new CountDownLatch(1);
- LauncherModel.sWorker.post(new Runnable() {
- @Override
- public void run() {
- latch.countDown();
- }
- });
- assertTrue(latch.await(5, TimeUnit.SECONDS));
+ private void waitUntilLoaderIdle() throws Exception {
+ new LooperExecuter(LauncherModel.getWorkerLooper())
+ .submit(new Runnable() {
+ @Override
+ public void run() { }
+ }).get(DEFAULT_WORKER_TIMEOUT_SECS, TimeUnit.SECONDS);
}
}
diff --git a/tests/src/com/android/launcher3/util/SimpleActivityMonitor.java b/tests/src/com/android/launcher3/util/SimpleActivityMonitor.java
new file mode 100644
index 0000000..6154ab6
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/SimpleActivityMonitor.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.launcher3.util;
+
+import android.app.Activity;
+import android.app.Application.*;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+
+/**
+ * Simple monitor to keep a list of active activities.
+ */
+public class SimpleActivityMonitor implements ActivityLifecycleCallbacks {
+
+ public final ArrayList<Activity> created = new ArrayList<>();
+ public final ArrayList<Activity> started = new ArrayList<>();
+ public final ArrayList<Activity> resumed = new ArrayList<>();
+
+ @Override
+ public void onActivityCreated(Activity activity, Bundle bundle) {
+ created.add(activity);
+ }
+
+ @Override
+ public void onActivityStarted(Activity activity) {
+ started.add(activity);
+ }
+
+ @Override
+ public void onActivityResumed(Activity activity) {
+ resumed.add(activity);
+ }
+
+ @Override
+ public void onActivityPaused(Activity activity) {
+ resumed.remove(activity);
+ }
+
+ @Override
+ public void onActivityStopped(Activity activity) {
+ started.remove(activity);
+ }
+
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { }
+
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+ created.remove(activity);
+ }
+}