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