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