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