1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.launcher3.dragndrop; 18 19 import static com.android.launcher3.logging.LoggerUtils.newCommandAction; 20 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; 21 import static com.android.launcher3.logging.LoggerUtils.newItemTarget; 22 import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent; 23 24 import android.annotation.TargetApi; 25 import android.app.ActivityOptions; 26 import android.appwidget.AppWidgetManager; 27 import android.content.ClipData; 28 import android.content.ClipDescription; 29 import android.content.Intent; 30 import android.content.pm.LauncherApps.PinItemRequest; 31 import android.graphics.Canvas; 32 import android.graphics.Point; 33 import android.graphics.PointF; 34 import android.graphics.Rect; 35 import android.os.Build; 36 import android.os.Bundle; 37 import android.view.MotionEvent; 38 import android.view.View; 39 import android.view.View.DragShadowBuilder; 40 import android.view.View.OnLongClickListener; 41 import android.view.View.OnTouchListener; 42 43 import com.android.launcher3.BaseActivity; 44 import com.android.launcher3.InstallShortcutReceiver; 45 import com.android.launcher3.InvariantDeviceProfile; 46 import com.android.launcher3.LauncherAppState; 47 import com.android.launcher3.LauncherAppWidgetHost; 48 import com.android.launcher3.LauncherAppWidgetProviderInfo; 49 import com.android.launcher3.R; 50 import com.android.launcher3.compat.AppWidgetManagerCompat; 51 import com.android.launcher3.compat.LauncherAppsCompatVO; 52 import com.android.launcher3.model.WidgetItem; 53 import com.android.launcher3.shortcuts.ShortcutInfoCompat; 54 import com.android.launcher3.userevent.nano.LauncherLogProto.Action; 55 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; 56 import com.android.launcher3.util.InstantAppResolver; 57 import com.android.launcher3.widget.PendingAddShortcutInfo; 58 import com.android.launcher3.widget.PendingAddWidgetInfo; 59 import com.android.launcher3.widget.WidgetHostViewLoader; 60 import com.android.launcher3.widget.WidgetImageView; 61 62 @TargetApi(Build.VERSION_CODES.O) 63 public class AddItemActivity extends BaseActivity implements OnLongClickListener, OnTouchListener { 64 65 private static final int SHADOW_SIZE = 10; 66 67 private static final int REQUEST_BIND_APPWIDGET = 1; 68 private static final String STATE_EXTRA_WIDGET_ID = "state.widget.id"; 69 70 private final PointF mLastTouchPos = new PointF(); 71 72 private PinItemRequest mRequest; 73 private LauncherAppState mApp; 74 private InvariantDeviceProfile mIdp; 75 76 private LivePreviewWidgetCell mWidgetCell; 77 78 // Widget request specific options. 79 private LauncherAppWidgetHost mAppWidgetHost; 80 private AppWidgetManagerCompat mAppWidgetManager; 81 private PendingAddWidgetInfo mPendingWidgetInfo; 82 private int mPendingBindWidgetId; 83 private Bundle mWidgetOptions; 84 85 private boolean mFinishOnPause = false; 86 private InstantAppResolver mInstantAppResolver; 87 88 @Override onCreate(Bundle savedInstanceState)89 protected void onCreate(Bundle savedInstanceState) { 90 super.onCreate(savedInstanceState); 91 92 mRequest = LauncherAppsCompatVO.getPinItemRequest(getIntent()); 93 if (mRequest == null) { 94 finish(); 95 return; 96 } 97 98 mApp = LauncherAppState.getInstance(this); 99 mIdp = mApp.getInvariantDeviceProfile(); 100 mInstantAppResolver = InstantAppResolver.newInstance(this); 101 102 // Use the application context to get the device profile, as in multiwindow-mode, the 103 // confirmation activity might be rotated. 104 mDeviceProfile = mIdp.getDeviceProfile(getApplicationContext()); 105 106 setContentView(R.layout.add_item_confirmation_activity); 107 mWidgetCell = findViewById(R.id.widget_cell); 108 109 if (mRequest.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT) { 110 setupShortcut(); 111 } else { 112 if (!setupWidget()) { 113 // TODO: show error toast? 114 finish(); 115 } 116 } 117 118 mWidgetCell.setOnTouchListener(this); 119 mWidgetCell.setOnLongClickListener(this); 120 121 // savedInstanceState is null when the activity is created the first time (i.e., avoids 122 // duplicate logging during rotation) 123 if (savedInstanceState == null) { 124 logCommand(Action.Command.ENTRY); 125 } 126 } 127 128 @Override onTouch(View view, MotionEvent motionEvent)129 public boolean onTouch(View view, MotionEvent motionEvent) { 130 mLastTouchPos.set(motionEvent.getX(), motionEvent.getY()); 131 return false; 132 } 133 134 @Override onLongClick(View view)135 public boolean onLongClick(View view) { 136 // Find the position of the preview relative to the touch location. 137 WidgetImageView img = mWidgetCell.getWidgetView(); 138 139 // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and 140 // we abort the drag. 141 if (img.getBitmap() == null) { 142 return false; 143 } 144 145 Rect bounds = img.getBitmapBounds(); 146 bounds.offset(img.getLeft() - (int) mLastTouchPos.x, img.getTop() - (int) mLastTouchPos.y); 147 148 // Start home and pass the draw request params 149 PinItemDragListener listener = new PinItemDragListener(mRequest, bounds, 150 img.getBitmap().getWidth(), img.getWidth()); 151 152 Intent homeIntent = listener.addToIntent( 153 new Intent(Intent.ACTION_MAIN) 154 .addCategory(Intent.CATEGORY_HOME) 155 .setPackage(getPackageName()) 156 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 157 158 listener.initWhenReady(); 159 startActivity(homeIntent, 160 ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out).toBundle()); 161 mFinishOnPause = true; 162 163 // Start a system drag and drop. We use a transparent bitmap as preview for system drag 164 // as the preview is handled internally by launcher. 165 ClipDescription description = new ClipDescription("", new String[]{listener.getMimeType()}); 166 ClipData data = new ClipData(description, new ClipData.Item("")); 167 view.startDragAndDrop(data, new DragShadowBuilder(view) { 168 169 @Override 170 public void onDrawShadow(Canvas canvas) { } 171 172 @Override 173 public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) { 174 outShadowSize.set(SHADOW_SIZE, SHADOW_SIZE); 175 outShadowTouchPoint.set(SHADOW_SIZE / 2, SHADOW_SIZE / 2); 176 } 177 }, null, View.DRAG_FLAG_GLOBAL); 178 return false; 179 } 180 181 @Override onPause()182 protected void onPause() { 183 super.onPause(); 184 if (mFinishOnPause) { 185 finish(); 186 } 187 } 188 setupShortcut()189 private void setupShortcut() { 190 PinShortcutRequestActivityInfo shortcutInfo = 191 new PinShortcutRequestActivityInfo(mRequest, this); 192 WidgetItem item = new WidgetItem(shortcutInfo); 193 mWidgetCell.getWidgetView().setTag(new PendingAddShortcutInfo(shortcutInfo)); 194 mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache()); 195 mWidgetCell.ensurePreview(); 196 } 197 setupWidget()198 private boolean setupWidget() { 199 LauncherAppWidgetProviderInfo widgetInfo = LauncherAppWidgetProviderInfo 200 .fromProviderInfo(this, mRequest.getAppWidgetProviderInfo(this)); 201 if (widgetInfo.minSpanX > mIdp.numColumns || widgetInfo.minSpanY > mIdp.numRows) { 202 // Cannot add widget 203 return false; 204 } 205 mWidgetCell.setPreview(PinItemDragListener.getPreview(mRequest)); 206 207 mAppWidgetManager = AppWidgetManagerCompat.getInstance(this); 208 mAppWidgetHost = new LauncherAppWidgetHost(this); 209 210 mPendingWidgetInfo = new PendingAddWidgetInfo(widgetInfo); 211 mPendingWidgetInfo.spanX = Math.min(mIdp.numColumns, widgetInfo.spanX); 212 mPendingWidgetInfo.spanY = Math.min(mIdp.numRows, widgetInfo.spanY); 213 mWidgetOptions = WidgetHostViewLoader.getDefaultOptionsForWidget(this, mPendingWidgetInfo); 214 215 WidgetItem item = new WidgetItem(widgetInfo, getPackageManager(), mIdp); 216 mWidgetCell.getWidgetView().setTag(mPendingWidgetInfo); 217 mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache()); 218 mWidgetCell.ensurePreview(); 219 return true; 220 } 221 222 /** 223 * Called when the cancel button is clicked. 224 */ onCancelClick(View v)225 public void onCancelClick(View v) { 226 logCommand(Action.Command.CANCEL); 227 finish(); 228 } 229 230 /** 231 * Called when place-automatically button is clicked. 232 */ onPlaceAutomaticallyClick(View v)233 public void onPlaceAutomaticallyClick(View v) { 234 if (mRequest.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT) { 235 InstallShortcutReceiver.queueShortcut( 236 new ShortcutInfoCompat(mRequest.getShortcutInfo()), this); 237 logCommand(Action.Command.CONFIRM); 238 mRequest.accept(); 239 finish(); 240 return; 241 } 242 243 mPendingBindWidgetId = mAppWidgetHost.allocateAppWidgetId(); 244 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed( 245 mPendingBindWidgetId, mRequest.getAppWidgetProviderInfo(this), mWidgetOptions); 246 if (success) { 247 acceptWidget(mPendingBindWidgetId); 248 return; 249 } 250 251 // request bind widget 252 mAppWidgetHost.startBindFlow(this, mPendingBindWidgetId, 253 mRequest.getAppWidgetProviderInfo(this), REQUEST_BIND_APPWIDGET); 254 } 255 acceptWidget(int widgetId)256 private void acceptWidget(int widgetId) { 257 InstallShortcutReceiver.queueWidget(mRequest.getAppWidgetProviderInfo(this), widgetId, this); 258 mWidgetOptions.putInt(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId); 259 mRequest.accept(mWidgetOptions); 260 logCommand(Action.Command.CONFIRM); 261 finish(); 262 } 263 264 @Override onBackPressed()265 public void onBackPressed() { 266 logCommand(Action.Command.BACK); 267 super.onBackPressed(); 268 } 269 270 @Override onActivityResult(int requestCode, int resultCode, Intent data)271 public void onActivityResult(int requestCode, int resultCode, Intent data) { 272 if (requestCode == REQUEST_BIND_APPWIDGET) { 273 int widgetId = data != null 274 ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mPendingBindWidgetId) 275 : mPendingBindWidgetId; 276 if (resultCode == RESULT_OK) { 277 acceptWidget(widgetId); 278 } else { 279 // Simply wait it out. 280 mAppWidgetHost.deleteAppWidgetId(widgetId); 281 mPendingBindWidgetId = -1; 282 } 283 return; 284 } 285 super.onActivityResult(requestCode, resultCode, data); 286 } 287 288 @Override onSaveInstanceState(Bundle outState)289 protected void onSaveInstanceState(Bundle outState) { 290 super.onSaveInstanceState(outState); 291 outState.putInt(STATE_EXTRA_WIDGET_ID, mPendingBindWidgetId); 292 } 293 294 @Override onRestoreInstanceState(Bundle savedInstanceState)295 protected void onRestoreInstanceState(Bundle savedInstanceState) { 296 super.onRestoreInstanceState(savedInstanceState); 297 mPendingBindWidgetId = savedInstanceState 298 .getInt(STATE_EXTRA_WIDGET_ID, mPendingBindWidgetId); 299 } 300 logCommand(int command)301 private void logCommand(int command) { 302 getUserEventDispatcher().dispatchUserEvent(newLauncherEvent( 303 newCommandAction(command), 304 newItemTarget(mWidgetCell.getWidgetView(), mInstantAppResolver), 305 newContainerTarget(ContainerType.PINITEM)), null); 306 } 307 } 308