| /* |
| * 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() { } |
| } |