• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.AppWidgetHost;
27 import android.appwidget.AppWidgetManager;
28 import android.content.ClipData;
29 import android.content.ClipDescription;
30 import android.content.Intent;
31 import android.content.res.Configuration;
32 import android.graphics.Canvas;
33 import android.graphics.Point;
34 import android.graphics.PointF;
35 import android.graphics.Rect;
36 import android.os.Build;
37 import android.os.Bundle;
38 import android.view.MotionEvent;
39 import android.view.View;
40 import android.view.View.DragShadowBuilder;
41 import android.view.View.OnLongClickListener;
42 import android.view.View.OnTouchListener;
43 
44 import com.android.launcher3.BaseActivity;
45 import com.android.launcher3.InstallShortcutReceiver;
46 import com.android.launcher3.InvariantDeviceProfile;
47 import com.android.launcher3.Launcher;
48 import com.android.launcher3.LauncherAppState;
49 import com.android.launcher3.LauncherAppWidgetProviderInfo;
50 import com.android.launcher3.R;
51 import com.android.launcher3.Utilities;
52 import com.android.launcher3.compat.AppWidgetManagerCompat;
53 import com.android.launcher3.compat.PinItemRequestCompat;
54 import com.android.launcher3.model.WidgetItem;
55 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
56 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
57 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
58 import com.android.launcher3.widget.PendingAddShortcutInfo;
59 import com.android.launcher3.widget.PendingAddWidgetInfo;
60 import com.android.launcher3.widget.WidgetHostViewLoader;
61 import com.android.launcher3.widget.WidgetImageView;
62 
63 @TargetApi(Build.VERSION_CODES.N_MR1)
64 public 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
onCreate(Bundle savedInstanceState)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
onTouch(View view, MotionEvent motionEvent)126     public boolean onTouch(View view, MotionEvent motionEvent) {
127         mLastTouchPos.set(motionEvent.getX(), motionEvent.getY());
128         return false;
129     }
130 
131     @Override
onLongClick(View view)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 
setupShortcut()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 
setupWidget()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      */
onCancelClick(View v)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      */
onPlaceAutomaticallyClick(View v)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 
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     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
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()),
305                 newContainerTarget(ContainerType.PINITEM)), null);
306     }
307 }
308