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