mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-03-01 00:06:47 +00:00
Shortcut size should be the size of all apps icon + padding
Test: Check shortcuts and widgets are shown without crash in the
following surfaces with display & font set to largest and
display cutout enabled. Repeat the same test with normal
display setting and no cutout.
1. Full widgets picker
2. Bottom widgets picker
3. Pin widget dialog
Fix: 193422438
Change-Id: Ibfebf94e92eed5e9cd1dd4196d98823b0e4dda6b
384 lines
15 KiB
Java
384 lines
15 KiB
Java
/*
|
|
* Copyright (C) 2017 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.launcher3.dragndrop;
|
|
|
|
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PIN_WIDGETS;
|
|
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ADD_EXTERNAL_ITEM_BACK;
|
|
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ADD_EXTERNAL_ITEM_CANCELLED;
|
|
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ADD_EXTERNAL_ITEM_DRAGGED;
|
|
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ADD_EXTERNAL_ITEM_PLACED_AUTOMATICALLY;
|
|
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ADD_EXTERNAL_ITEM_START;
|
|
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
|
|
|
import android.annotation.TargetApi;
|
|
import android.app.ActivityOptions;
|
|
import android.appwidget.AppWidgetManager;
|
|
import android.content.ClipData;
|
|
import android.content.ClipDescription;
|
|
import android.content.Intent;
|
|
import android.content.pm.LauncherApps.PinItemRequest;
|
|
import android.content.res.Configuration;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Point;
|
|
import android.graphics.PointF;
|
|
import android.graphics.Rect;
|
|
import android.os.AsyncTask;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
import android.view.View.DragShadowBuilder;
|
|
import android.view.View.OnLongClickListener;
|
|
import android.view.View.OnTouchListener;
|
|
import android.view.WindowManager;
|
|
import android.widget.TextView;
|
|
|
|
import com.android.launcher3.BaseActivity;
|
|
import com.android.launcher3.InvariantDeviceProfile;
|
|
import com.android.launcher3.Launcher;
|
|
import com.android.launcher3.LauncherAppState;
|
|
import com.android.launcher3.R;
|
|
import com.android.launcher3.logging.StatsLogManager;
|
|
import com.android.launcher3.model.ItemInstallQueue;
|
|
import com.android.launcher3.model.WidgetItem;
|
|
import com.android.launcher3.model.data.ItemInfo;
|
|
import com.android.launcher3.pm.PinRequestHelper;
|
|
import com.android.launcher3.util.SystemUiController;
|
|
import com.android.launcher3.views.AbstractSlideInView;
|
|
import com.android.launcher3.views.BaseDragLayer;
|
|
import com.android.launcher3.widget.AddItemWidgetsBottomSheet;
|
|
import com.android.launcher3.widget.LauncherAppWidgetHost;
|
|
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
|
|
import com.android.launcher3.widget.NavigableAppWidgetHostView;
|
|
import com.android.launcher3.widget.PendingAddShortcutInfo;
|
|
import com.android.launcher3.widget.PendingAddWidgetInfo;
|
|
import com.android.launcher3.widget.WidgetCell;
|
|
import com.android.launcher3.widget.WidgetCellPreview;
|
|
import com.android.launcher3.widget.WidgetImageView;
|
|
import com.android.launcher3.widget.WidgetManagerHelper;
|
|
|
|
import java.util.function.Supplier;
|
|
|
|
/**
|
|
* Activity to show pin widget dialog.
|
|
*/
|
|
@TargetApi(Build.VERSION_CODES.O)
|
|
public class AddItemActivity extends BaseActivity
|
|
implements OnLongClickListener, OnTouchListener, AbstractSlideInView.OnCloseListener {
|
|
|
|
private static final int SHADOW_SIZE = 10;
|
|
|
|
private static final int REQUEST_BIND_APPWIDGET = 1;
|
|
private static final String STATE_EXTRA_WIDGET_ID = "state.widget.id";
|
|
|
|
private final PointF mLastTouchPos = new PointF();
|
|
|
|
private PinItemRequest mRequest;
|
|
private LauncherAppState mApp;
|
|
private InvariantDeviceProfile mIdp;
|
|
private BaseDragLayer<AddItemActivity> mDragLayer;
|
|
private AddItemWidgetsBottomSheet mSlideInView;
|
|
|
|
private WidgetCell mWidgetCell;
|
|
|
|
// Widget request specific options.
|
|
private LauncherAppWidgetHost mAppWidgetHost;
|
|
private WidgetManagerHelper mAppWidgetManager;
|
|
private int mPendingBindWidgetId;
|
|
private Bundle mWidgetOptions;
|
|
|
|
private boolean mFinishOnPause = false;
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
|
|
mRequest = PinRequestHelper.getPinItemRequest(getIntent());
|
|
if (mRequest == null) {
|
|
finish();
|
|
return;
|
|
}
|
|
|
|
mApp = LauncherAppState.getInstance(this);
|
|
mIdp = mApp.getInvariantDeviceProfile();
|
|
|
|
// Use the application context to get the device profile, as in multiwindow-mode, the
|
|
// confirmation activity might be rotated.
|
|
mDeviceProfile = mIdp.getDeviceProfile(getApplicationContext());
|
|
|
|
setContentView(R.layout.add_item_confirmation_activity);
|
|
// Set flag to allow activity to draw over navigation and status bar.
|
|
getWindow().setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
|
|
mDragLayer = findViewById(R.id.add_item_drag_layer);
|
|
mDragLayer.recreateControllers();
|
|
mWidgetCell = findViewById(R.id.widget_cell);
|
|
|
|
if (mRequest.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT) {
|
|
setupShortcut();
|
|
} else {
|
|
if (!setupWidget()) {
|
|
// TODO: show error toast?
|
|
finish();
|
|
}
|
|
}
|
|
|
|
WidgetCellPreview previewContainer = mWidgetCell.findViewById(
|
|
R.id.widget_preview_container);
|
|
previewContainer.setOnTouchListener(this);
|
|
previewContainer.setOnLongClickListener(this);
|
|
|
|
// savedInstanceState is null when the activity is created the first time (i.e., avoids
|
|
// duplicate logging during rotation)
|
|
if (savedInstanceState == null) {
|
|
logCommand(LAUNCHER_ADD_EXTERNAL_ITEM_START);
|
|
}
|
|
|
|
TextView widgetAppName = findViewById(R.id.widget_appName);
|
|
widgetAppName.setText(getApplicationInfo().labelRes);
|
|
|
|
mSlideInView = findViewById(R.id.add_item_bottom_sheet);
|
|
mSlideInView.addOnCloseListener(this);
|
|
mSlideInView.show();
|
|
setupNavBarColor();
|
|
}
|
|
|
|
@Override
|
|
public boolean onTouch(View view, MotionEvent motionEvent) {
|
|
mLastTouchPos.set(motionEvent.getX(), motionEvent.getY());
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean onLongClick(View view) {
|
|
// Find the position of the preview relative to the touch location.
|
|
WidgetImageView img = mWidgetCell.getWidgetView();
|
|
NavigableAppWidgetHostView appWidgetHostView = mWidgetCell.getAppWidgetHostViewPreview();
|
|
|
|
// If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
|
|
// we abort the drag.
|
|
if (img.getDrawable() == null && appWidgetHostView == null) {
|
|
return false;
|
|
}
|
|
|
|
final Rect bounds;
|
|
// Start home and pass the draw request params
|
|
final PinItemDragListener listener;
|
|
if (appWidgetHostView != null) {
|
|
bounds = new Rect();
|
|
appWidgetHostView.getSourceVisualDragBounds(bounds);
|
|
bounds.offset(appWidgetHostView.getLeft() - (int) mLastTouchPos.x,
|
|
appWidgetHostView.getTop() - (int) mLastTouchPos.y);
|
|
listener = new PinItemDragListener(mRequest, bounds,
|
|
appWidgetHostView.getMeasuredWidth(), appWidgetHostView.getMeasuredWidth());
|
|
} else {
|
|
bounds = img.getBitmapBounds();
|
|
bounds.offset(img.getLeft() - (int) mLastTouchPos.x,
|
|
img.getTop() - (int) mLastTouchPos.y);
|
|
listener = new PinItemDragListener(mRequest, bounds,
|
|
img.getDrawable().getIntrinsicWidth(), img.getWidth());
|
|
}
|
|
|
|
// Start a system drag and drop. We use a transparent bitmap as preview for system drag
|
|
// as the preview is handled internally by launcher.
|
|
ClipDescription description = new ClipDescription("", new String[]{listener.getMimeType()});
|
|
ClipData data = new ClipData(description, new ClipData.Item(""));
|
|
view.startDragAndDrop(data, new DragShadowBuilder(view) {
|
|
|
|
@Override
|
|
public void onDrawShadow(Canvas canvas) { }
|
|
|
|
@Override
|
|
public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) {
|
|
outShadowSize.set(SHADOW_SIZE, SHADOW_SIZE);
|
|
outShadowTouchPoint.set(SHADOW_SIZE / 2, SHADOW_SIZE / 2);
|
|
}
|
|
}, null, View.DRAG_FLAG_GLOBAL);
|
|
|
|
Intent homeIntent = new Intent(Intent.ACTION_MAIN)
|
|
.addCategory(Intent.CATEGORY_HOME)
|
|
.setPackage(getPackageName())
|
|
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
Launcher.ACTIVITY_TRACKER.registerCallback(listener);
|
|
startActivity(homeIntent,
|
|
ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out)
|
|
.toBundle());
|
|
logCommand(LAUNCHER_ADD_EXTERNAL_ITEM_DRAGGED);
|
|
mFinishOnPause = true;
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
protected void onPause() {
|
|
super.onPause();
|
|
if (mFinishOnPause) {
|
|
finish();
|
|
}
|
|
}
|
|
|
|
private void setupShortcut() {
|
|
PinShortcutRequestActivityInfo shortcutInfo =
|
|
new PinShortcutRequestActivityInfo(mRequest, this);
|
|
mWidgetCell.getWidgetView().setTag(new PendingAddShortcutInfo(shortcutInfo));
|
|
applyWidgetItemAsync(
|
|
() -> new WidgetItem(shortcutInfo, mApp.getIconCache(), getPackageManager()));
|
|
}
|
|
|
|
private boolean setupWidget() {
|
|
LauncherAppWidgetProviderInfo widgetInfo = LauncherAppWidgetProviderInfo
|
|
.fromProviderInfo(this, mRequest.getAppWidgetProviderInfo(this));
|
|
if (widgetInfo.minSpanX > mIdp.numColumns || widgetInfo.minSpanY > mIdp.numRows) {
|
|
// Cannot add widget
|
|
return false;
|
|
}
|
|
mWidgetCell.setRemoteViewsPreview(PinItemDragListener.getPreview(mRequest));
|
|
|
|
mAppWidgetManager = new WidgetManagerHelper(this);
|
|
mAppWidgetHost = new LauncherAppWidgetHost(this);
|
|
|
|
PendingAddWidgetInfo pendingInfo =
|
|
new PendingAddWidgetInfo(widgetInfo, CONTAINER_PIN_WIDGETS);
|
|
pendingInfo.spanX = Math.min(mIdp.numColumns, widgetInfo.spanX);
|
|
pendingInfo.spanY = Math.min(mIdp.numRows, widgetInfo.spanY);
|
|
mWidgetOptions = pendingInfo.getDefaultSizeOptions(this);
|
|
mWidgetCell.getWidgetView().setTag(pendingInfo);
|
|
|
|
applyWidgetItemAsync(() -> new WidgetItem(widgetInfo, mIdp, mApp.getIconCache()));
|
|
return true;
|
|
}
|
|
|
|
private void applyWidgetItemAsync(final Supplier<WidgetItem> itemProvider) {
|
|
new AsyncTask<Void, Void, WidgetItem>() {
|
|
@Override
|
|
protected WidgetItem doInBackground(Void... voids) {
|
|
return itemProvider.get();
|
|
}
|
|
|
|
@Override
|
|
protected void onPostExecute(WidgetItem item) {
|
|
mWidgetCell.setPreviewSize(item);
|
|
mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache());
|
|
mWidgetCell.ensurePreview();
|
|
}
|
|
}.executeOnExecutor(MODEL_EXECUTOR);
|
|
// TODO: Create a worker looper executor and reuse that everywhere.
|
|
}
|
|
|
|
/**
|
|
* Called when the cancel button is clicked.
|
|
*/
|
|
public void onCancelClick(View v) {
|
|
logCommand(LAUNCHER_ADD_EXTERNAL_ITEM_CANCELLED);
|
|
mSlideInView.close(/* animate= */ true);
|
|
}
|
|
|
|
/**
|
|
* Called when place-automatically button is clicked.
|
|
*/
|
|
public void onPlaceAutomaticallyClick(View v) {
|
|
if (mRequest.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT) {
|
|
ItemInstallQueue.INSTANCE.get(this).queueItem(mRequest.getShortcutInfo());
|
|
logCommand(LAUNCHER_ADD_EXTERNAL_ITEM_PLACED_AUTOMATICALLY);
|
|
mRequest.accept();
|
|
mSlideInView.close(/* animate= */ true);
|
|
return;
|
|
}
|
|
|
|
mPendingBindWidgetId = mAppWidgetHost.allocateAppWidgetId();
|
|
boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
|
|
mPendingBindWidgetId, mRequest.getAppWidgetProviderInfo(this), mWidgetOptions);
|
|
if (success) {
|
|
acceptWidget(mPendingBindWidgetId);
|
|
return;
|
|
}
|
|
|
|
// request bind widget
|
|
mAppWidgetHost.startBindFlow(this, mPendingBindWidgetId,
|
|
mRequest.getAppWidgetProviderInfo(this), REQUEST_BIND_APPWIDGET);
|
|
}
|
|
|
|
private void acceptWidget(int widgetId) {
|
|
ItemInstallQueue.INSTANCE.get(this)
|
|
.queueItem(mRequest.getAppWidgetProviderInfo(this), widgetId);
|
|
mWidgetOptions.putInt(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
|
|
mRequest.accept(mWidgetOptions);
|
|
logCommand(LAUNCHER_ADD_EXTERNAL_ITEM_PLACED_AUTOMATICALLY);
|
|
mSlideInView.close(/* animate= */ true);
|
|
}
|
|
|
|
@Override
|
|
public void onBackPressed() {
|
|
logCommand(LAUNCHER_ADD_EXTERNAL_ITEM_BACK);
|
|
mSlideInView.close(/* animate= */ true);
|
|
}
|
|
|
|
@Override
|
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
if (requestCode == REQUEST_BIND_APPWIDGET) {
|
|
int widgetId = data != null
|
|
? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mPendingBindWidgetId)
|
|
: mPendingBindWidgetId;
|
|
if (resultCode == RESULT_OK) {
|
|
acceptWidget(widgetId);
|
|
} else {
|
|
// Simply wait it out.
|
|
mAppWidgetHost.deleteAppWidgetId(widgetId);
|
|
mPendingBindWidgetId = -1;
|
|
}
|
|
return;
|
|
}
|
|
super.onActivityResult(requestCode, resultCode, data);
|
|
}
|
|
|
|
@Override
|
|
protected void onSaveInstanceState(Bundle outState) {
|
|
super.onSaveInstanceState(outState);
|
|
outState.putInt(STATE_EXTRA_WIDGET_ID, mPendingBindWidgetId);
|
|
}
|
|
|
|
@Override
|
|
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
|
super.onRestoreInstanceState(savedInstanceState);
|
|
mPendingBindWidgetId = savedInstanceState
|
|
.getInt(STATE_EXTRA_WIDGET_ID, mPendingBindWidgetId);
|
|
}
|
|
|
|
@Override
|
|
public BaseDragLayer getDragLayer() {
|
|
return mDragLayer;
|
|
}
|
|
|
|
@Override
|
|
public void onSlideInViewClosed() {
|
|
finish();
|
|
}
|
|
|
|
protected void setupNavBarColor() {
|
|
boolean isSheetDark = (getApplicationContext().getResources().getConfiguration().uiMode
|
|
& Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
|
|
getSystemUiController().updateUiState(
|
|
SystemUiController.UI_STATE_BASE_WINDOW,
|
|
isSheetDark ? SystemUiController.FLAG_DARK_NAV : SystemUiController.FLAG_LIGHT_NAV);
|
|
}
|
|
|
|
private void logCommand(StatsLogManager.EventEnum command) {
|
|
getStatsLogManager().logger()
|
|
.withItemInfo((ItemInfo) mWidgetCell.getWidgetView().getTag())
|
|
.log(command);
|
|
}
|
|
}
|