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