AddItemActivity.java revision 80fa41f00f0c468b4ca5bfed801164ec654459cd
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.pm.LauncherApps.PinItemRequest; 32import android.content.res.Configuration; 33import android.graphics.Canvas; 34import android.graphics.Point; 35import android.graphics.PointF; 36import android.graphics.Rect; 37import android.os.Build; 38import android.os.Bundle; 39import android.view.MotionEvent; 40import android.view.View; 41import android.view.View.DragShadowBuilder; 42import android.view.View.OnLongClickListener; 43import android.view.View.OnTouchListener; 44 45import com.android.launcher3.BaseActivity; 46import com.android.launcher3.InstallShortcutReceiver; 47import com.android.launcher3.InvariantDeviceProfile; 48import com.android.launcher3.Launcher; 49import com.android.launcher3.LauncherAppState; 50import com.android.launcher3.LauncherAppWidgetProviderInfo; 51import com.android.launcher3.R; 52import com.android.launcher3.Utilities; 53import com.android.launcher3.compat.AppWidgetManagerCompat; 54import com.android.launcher3.compat.LauncherAppsCompatVO; 55import com.android.launcher3.model.WidgetItem; 56import com.android.launcher3.shortcuts.ShortcutInfoCompat; 57import com.android.launcher3.userevent.nano.LauncherLogProto.Action; 58import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; 59import com.android.launcher3.widget.PendingAddShortcutInfo; 60import com.android.launcher3.widget.PendingAddWidgetInfo; 61import com.android.launcher3.widget.WidgetHostViewLoader; 62import com.android.launcher3.widget.WidgetImageView; 63 64@TargetApi(Build.VERSION_CODES.O) 65public class AddItemActivity extends BaseActivity implements OnLongClickListener, OnTouchListener { 66 67 private static final int SHADOW_SIZE = 10; 68 69 private static final int REQUEST_BIND_APPWIDGET = 1; 70 private static final String STATE_EXTRA_WIDGET_ID = "state.widget.id"; 71 72 private final PointF mLastTouchPos = new PointF(); 73 74 private PinItemRequest mRequest; 75 private LauncherAppState mApp; 76 private InvariantDeviceProfile mIdp; 77 78 private LivePreviewWidgetCell mWidgetCell; 79 80 // Widget request specific options. 81 private AppWidgetHost mAppWidgetHost; 82 private AppWidgetManagerCompat mAppWidgetManager; 83 private PendingAddWidgetInfo mPendingWidgetInfo; 84 private int mPendingBindWidgetId; 85 private Bundle mWidgetOptions; 86 87 private boolean mFinishOnPause = false; 88 89 @Override 90 protected void onCreate(Bundle savedInstanceState) { 91 super.onCreate(savedInstanceState); 92 93 mRequest = LauncherAppsCompatVO.getPinItemRequest(getIntent()); 94 if (mRequest == null) { 95 finish(); 96 return; 97 } 98 99 mApp = LauncherAppState.getInstance(this); 100 mIdp = mApp.getInvariantDeviceProfile(); 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 129 public boolean onTouch(View view, MotionEvent motionEvent) { 130 mLastTouchPos.set(motionEvent.getX(), motionEvent.getY()); 131 return false; 132 } 133 134 @Override 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 Intent homeIntent = new Intent(Intent.ACTION_MAIN) 152 .addCategory(Intent.CATEGORY_HOME) 153 .setPackage(getPackageName()) 154 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 155 .putExtra(PinItemDragListener.EXTRA_PIN_ITEM_DRAG_LISTENER, listener); 156 157 if (!getResources().getBoolean(R.bool.allow_rotation) && 158 !Utilities.isAllowRotationPrefEnabled(this) && 159 (getResources().getConfiguration().orientation == 160 Configuration.ORIENTATION_LANDSCAPE && !isInMultiWindowMode())) { 161 // If we are starting the drag in landscape even though home is locked in portrait, 162 // restart the home activity to temporarily allow rotation. 163 homeIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); 164 } 165 166 startActivity(homeIntent, 167 ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out).toBundle()); 168 mFinishOnPause = true; 169 170 // Start a system drag and drop. We use a transparent bitmap as preview for system drag 171 // as the preview is handled internally by launcher. 172 ClipDescription description = new ClipDescription("", new String[]{listener.getMimeType()}); 173 ClipData data = new ClipData(description, new ClipData.Item("")); 174 view.startDragAndDrop(data, new DragShadowBuilder(view) { 175 176 @Override 177 public void onDrawShadow(Canvas canvas) { } 178 179 @Override 180 public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) { 181 outShadowSize.set(SHADOW_SIZE, SHADOW_SIZE); 182 outShadowTouchPoint.set(SHADOW_SIZE / 2, SHADOW_SIZE / 2); 183 } 184 }, null, View.DRAG_FLAG_GLOBAL); 185 return false; 186 } 187 188 @Override 189 protected void onPause() { 190 super.onPause(); 191 if (mFinishOnPause) { 192 finish(); 193 } 194 } 195 196 private void setupShortcut() { 197 PinShortcutRequestActivityInfo shortcutInfo = 198 new PinShortcutRequestActivityInfo(mRequest, this); 199 WidgetItem item = new WidgetItem(shortcutInfo); 200 mWidgetCell.getWidgetView().setTag(new PendingAddShortcutInfo(shortcutInfo)); 201 mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache()); 202 mWidgetCell.ensurePreview(); 203 } 204 205 private boolean setupWidget() { 206 LauncherAppWidgetProviderInfo widgetInfo = LauncherAppWidgetProviderInfo 207 .fromProviderInfo(this, mRequest.getAppWidgetProviderInfo(this)); 208 if (widgetInfo.minSpanX > mIdp.numColumns || widgetInfo.minSpanY > mIdp.numRows) { 209 // Cannot add widget 210 return false; 211 } 212 mWidgetCell.setPreview(PinItemDragListener.getPreview(mRequest)); 213 214 mAppWidgetManager = AppWidgetManagerCompat.getInstance(this); 215 mAppWidgetHost = new AppWidgetHost(this, Launcher.APPWIDGET_HOST_ID); 216 217 mPendingWidgetInfo = new PendingAddWidgetInfo(widgetInfo); 218 mPendingWidgetInfo.spanX = Math.min(mIdp.numColumns, widgetInfo.spanX); 219 mPendingWidgetInfo.spanY = Math.min(mIdp.numRows, widgetInfo.spanY); 220 mWidgetOptions = WidgetHostViewLoader.getDefaultOptionsForWidget(this, mPendingWidgetInfo); 221 222 WidgetItem item = new WidgetItem(widgetInfo, getPackageManager(), mIdp); 223 mWidgetCell.getWidgetView().setTag(mPendingWidgetInfo); 224 mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache()); 225 mWidgetCell.ensurePreview(); 226 return true; 227 } 228 229 /** 230 * Called when the cancel button is clicked. 231 */ 232 public void onCancelClick(View v) { 233 logCommand(Action.Command.CANCEL); 234 finish(); 235 } 236 237 /** 238 * Called when place-automatically button is clicked. 239 */ 240 public void onPlaceAutomaticallyClick(View v) { 241 if (mRequest.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT) { 242 InstallShortcutReceiver.queueShortcut( 243 new ShortcutInfoCompat(mRequest.getShortcutInfo()), this); 244 logCommand(Action.Command.CONFIRM); 245 mRequest.accept(); 246 finish(); 247 return; 248 } 249 250 mPendingBindWidgetId = mAppWidgetHost.allocateAppWidgetId(); 251 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed( 252 mPendingBindWidgetId, mRequest.getAppWidgetProviderInfo(this), mWidgetOptions); 253 if (success) { 254 acceptWidget(mPendingBindWidgetId); 255 return; 256 } 257 258 // request bind widget 259 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND); 260 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mPendingBindWidgetId); 261 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, 262 mPendingWidgetInfo.componentName); 263 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE, 264 mRequest.getAppWidgetProviderInfo(this).getProfile()); 265 startActivityForResult(intent, REQUEST_BIND_APPWIDGET); 266 } 267 268 private void acceptWidget(int widgetId) { 269 InstallShortcutReceiver.queueWidget(mRequest.getAppWidgetProviderInfo(this), widgetId, this); 270 mWidgetOptions.putInt(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId); 271 mRequest.accept(mWidgetOptions); 272 logCommand(Action.Command.CONFIRM); 273 finish(); 274 } 275 276 @Override 277 public void onBackPressed() { 278 logCommand(Action.Command.BACK); 279 super.onBackPressed(); 280 } 281 282 @Override 283 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 284 if (requestCode == REQUEST_BIND_APPWIDGET) { 285 int widgetId = data != null 286 ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mPendingBindWidgetId) 287 : mPendingBindWidgetId; 288 if (resultCode == RESULT_OK) { 289 acceptWidget(widgetId); 290 } else { 291 // Simply wait it out. 292 mAppWidgetHost.deleteAppWidgetId(widgetId); 293 mPendingBindWidgetId = -1; 294 } 295 return; 296 } 297 super.onActivityResult(requestCode, resultCode, data); 298 } 299 300 @Override 301 protected void onSaveInstanceState(Bundle outState) { 302 super.onSaveInstanceState(outState); 303 outState.putInt(STATE_EXTRA_WIDGET_ID, mPendingBindWidgetId); 304 } 305 306 @Override 307 protected void onRestoreInstanceState(Bundle savedInstanceState) { 308 super.onRestoreInstanceState(savedInstanceState); 309 mPendingBindWidgetId = savedInstanceState 310 .getInt(STATE_EXTRA_WIDGET_ID, mPendingBindWidgetId); 311 } 312 313 private void logCommand(int command) { 314 getUserEventDispatcher().dispatchUserEvent(newLauncherEvent( 315 newCommandAction(command), 316 newItemTarget(mWidgetCell.getWidgetView()), 317 newContainerTarget(ContainerType.PINITEM)), null); 318 } 319} 320