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