blob: 910b0293ec2066a71f59d1b70f400facfb91d4ec [file] [log] [blame]
/*
* Copyright (C) 2020 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.secondarydisplay;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Intent;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewAnimationUtils;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.UiThread;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.graphics.DragPreviewProvider;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.StringCache;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.touch.ItemClickHandler.ItemClickProxy;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer;
import java.util.HashMap;
import java.util.Map;
/**
* Launcher activity for secondary displays
*/
public class SecondaryDisplayLauncher extends BaseDraggingActivity
implements BgDataModel.Callbacks, DragController.DragListener {
private LauncherModel mModel;
private SecondaryDragLayer mDragLayer;
private SecondaryDragController mDragController;
private ActivityAllAppsContainerView<SecondaryDisplayLauncher> mAppsView;
private View mAppsButton;
private PopupDataProvider mPopupDataProvider;
private boolean mAppDrawerShown = false;
private StringCache mStringCache;
private boolean mBindingItems = false;
private SecondaryDisplayPredictions mSecondaryDisplayPredictions;
private final int[] mTempXY = new int[2];
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mModel = LauncherAppState.getInstance(this).getModel();
mDragController = new SecondaryDragController(this);
mSecondaryDisplayPredictions = SecondaryDisplayPredictions.newInstance(this);
if (getWindow().getDecorView().isAttachedToWindow()) {
initUi();
}
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
initUi();
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
this.getDragController().removeDragListener(this);
}
private void initUi() {
if (mDragLayer != null) {
return;
}
InvariantDeviceProfile currentDisplayIdp = new InvariantDeviceProfile(
this, getWindow().getDecorView().getDisplay());
// Disable transpose layout and use multi-window mode so that the icons are scaled properly
mDeviceProfile = currentDisplayIdp.getDeviceProfile(this)
.toBuilder(this)
.setMultiWindowMode(true)
.setTransposeLayoutWithOrientation(false)
.build();
mDeviceProfile.autoResizeAllAppsCells();
setContentView(R.layout.secondary_launcher);
mDragLayer = findViewById(R.id.drag_layer);
mAppsView = findViewById(R.id.apps_view);
mAppsButton = findViewById(R.id.all_apps_button);
mDragController.addDragListener(this);
mPopupDataProvider = new PopupDataProvider(
mAppsView.getAppsStore()::updateNotificationDots);
mModel.addCallbacksAndLoad(this);
}
@Override
protected void onPause() {
super.onPause();
mDragController.cancelDrag();
}
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (Intent.ACTION_MAIN.equals(intent.getAction())) {
// Hide keyboard.
final View v = getWindow().peekDecorView();
if (v != null && v.getWindowToken() != null) {
getSystemService(InputMethodManager.class).hideSoftInputFromWindow(
v.getWindowToken(), 0);
}
}
// A new intent will bring the launcher to top. Hide the app drawer to reset the state.
showAppDrawer(false);
}
public DragController getDragController() {
return mDragController;
}
@Override
public void onBackPressed() {
if (finishAutoCancelActionMode()) {
return;
}
if (mDragController.isDragging()) {
mDragController.cancelDrag();
return;
}
// Note: There should be at most one log per method call. This is enforced implicitly
// by using if-else statements.
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
if (topView != null && topView.canHandleBack()) {
// Handled by the floating view.
topView.onBackInvoked();
} else {
showAppDrawer(false);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mModel.removeCallbacks(this);
}
public boolean isAppDrawerShown() {
return mAppDrawerShown;
}
@Override
public ActivityAllAppsContainerView<SecondaryDisplayLauncher> getAppsView() {
return mAppsView;
}
@Override
public <T extends View> T getOverviewPanel() {
return null;
}
@Override
public View getRootView() {
return mDragLayer;
}
@Override
protected void reapplyUi() { }
@Override
public BaseDragLayer getDragLayer() {
return mDragLayer;
}
@Override
public void bindIncrementalDownloadProgressUpdated(AppInfo app) {
mAppsView.getAppsStore().updateProgressBar(app);
}
/**
* Called when apps-button is clicked
*/
public void onAppsButtonClicked(View v) {
showAppDrawer(true);
}
/**
* Show/hide app drawer card with animation.
*/
public void showAppDrawer(boolean show) {
if (show == mAppDrawerShown) {
return;
}
float openR = (float) Math.hypot(mAppsView.getWidth(), mAppsView.getHeight());
float closeR = Themes.getDialogCornerRadius(this);
float startR = mAppsButton.getWidth() / 2f;
float[] buttonPos = new float[]{startR, startR};
mDragLayer.getDescendantCoordRelativeToSelf(mAppsButton, buttonPos);
mDragLayer.mapCoordInSelfToDescendant(mAppsView, buttonPos);
final Animator animator = ViewAnimationUtils.createCircularReveal(mAppsView,
(int) buttonPos[0], (int) buttonPos[1],
show ? closeR : openR, show ? openR : closeR);
if (show) {
mAppDrawerShown = true;
mAppsView.setVisibility(View.VISIBLE);
mAppsButton.setVisibility(View.INVISIBLE);
mSecondaryDisplayPredictions.updateAppDivider();
} else {
mAppDrawerShown = false;
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mAppsView.setVisibility(View.INVISIBLE);
mAppsButton.setVisibility(View.VISIBLE);
mAppsView.getSearchUiManager().resetSearch();
}
});
}
animator.start();
}
@Override
public void startBinding() {
mBindingItems = true;
mDragController.cancelDrag();
}
@Override
public boolean isBindingItems() {
return mBindingItems;
}
@Override
public void finishBindingItems(IntSet pagesBoundFirst) {
mBindingItems = false;
}
@Override
public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap) {
mPopupDataProvider.setDeepShortcutMap(deepShortcutMap);
}
@UiThread
@Override
public void bindAllApplications(AppInfo[] apps, int flags,
Map<PackageUserKey, Integer> packageUserKeytoUidMap) {
Preconditions.assertUIThread();
AllAppsStore<SecondaryDisplayLauncher> appsStore = mAppsView.getAppsStore();
appsStore.setApps(apps, flags, packageUserKeytoUidMap);
PopupContainerWithArrow.dismissInvalidPopup(this);
}
@Override
public void bindExtraContainerItems(BgDataModel.FixedContainerItems item) {
if (item.containerId == LauncherSettings.Favorites.CONTAINER_PREDICTION) {
mSecondaryDisplayPredictions.setPredictedApps(item);
}
}
@Override
public StringCache getStringCache() {
return mStringCache;
}
@Override
public void bindStringCache(StringCache cache) {
mStringCache = cache;
}
public PopupDataProvider getPopupDataProvider() {
return mPopupDataProvider;
}
@Override
public OnClickListener getItemOnClickListener() {
return this::onIconClicked;
}
@Override
public View.OnLongClickListener getAllAppsItemLongClickListener() {
return v -> mDragLayer.onIconLongClicked(v);
}
private void onIconClicked(View v) {
// Make sure that rogue clicks don't get through while allapps is launching, or after the
// view has detached (it's possible for this to happen if the view is removed mid touch).
if (v.getWindowToken() == null) return;
Object tag = v.getTag();
if (tag instanceof ItemClickProxy) {
((ItemClickProxy) tag).onItemClicked(v);
} else if (tag instanceof ItemInfo) {
ItemInfo item = (ItemInfo) tag;
Intent intent;
if (item instanceof ItemInfoWithIcon
&& (((ItemInfoWithIcon) item).runtimeStatusFlags
& ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) {
ItemInfoWithIcon appInfo = (ItemInfoWithIcon) item;
intent = appInfo.getMarketIntent(this);
} else {
intent = item.getIntent();
}
if (intent == null) {
throw new IllegalArgumentException("Input must have a valid intent");
}
startActivitySafely(v, intent, item);
}
}
/**
* Core functionality for beginning a drag operation for an item that will be dropped within
* the secondary display grid home screen
*/
public void beginDragShared(View child, DragSource source, DragOptions options) {
Object dragObject = child.getTag();
if (!(dragObject instanceof ItemInfo)) {
String msg = "Drag started with a view that has no tag set. This "
+ "will cause a crash (issue 11627249) down the line. "
+ "View: " + child + " tag: " + child.getTag();
throw new IllegalStateException(msg);
}
beginDragShared(child, source, (ItemInfo) dragObject,
new DragPreviewProvider(child), options);
}
private void beginDragShared(View child, DragSource source,
ItemInfo dragObject, DragPreviewProvider previewProvider, DragOptions options) {
float iconScale = 1f;
if (child instanceof BubbleTextView) {
FastBitmapDrawable icon = ((BubbleTextView) child).getIcon();
if (icon != null) {
iconScale = icon.getAnimatedScale();
}
}
// clear pressed state if necessary
child.clearFocus();
child.setPressed(false);
if (child instanceof BubbleTextView) {
BubbleTextView icon = (BubbleTextView) child;
icon.clearPressedBackground();
}
DraggableView draggableView = null;
if (child instanceof DraggableView) {
draggableView = (DraggableView) child;
}
final View contentView = previewProvider.getContentView();
final float scale;
// The draggable drawable follows the touch point around on the screen
final Drawable drawable;
if (contentView == null) {
drawable = previewProvider.createDrawable();
scale = previewProvider.getScaleAndPosition(drawable, mTempXY);
} else {
drawable = null;
scale = previewProvider.getScaleAndPosition(contentView, mTempXY);
}
int dragLayerX = mTempXY[0];
int dragLayerY = mTempXY[1];
Rect dragRect = new Rect();
if (draggableView != null) {
draggableView.getSourceVisualDragBounds(dragRect);
dragLayerY += dragRect.top;
}
if (options.preDragCondition != null) {
int xOffSet = options.preDragCondition.getDragOffset().x;
int yOffSet = options.preDragCondition.getDragOffset().y;
if (xOffSet != 0 && yOffSet != 0) {
dragLayerX += xOffSet;
dragLayerY += yOffSet;
}
}
if (contentView != null) {
mDragController.startDrag(
contentView,
draggableView,
dragLayerX,
dragLayerY,
source,
dragObject,
dragRect,
scale * iconScale,
scale,
options);
} else {
mDragController.startDrag(
drawable,
draggableView,
dragLayerX,
dragLayerY,
source,
dragObject,
dragRect,
scale * iconScale,
scale,
options);
}
}
@Override
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { }
@Override
public void onDragEnd() { }
}