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