blob: 4939a9c22cf808addebb12b8db268cc9aef7d462 [file] [log] [blame]
/*
* Copyright (C) 2021 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.systemui.statusbar.notification.row;
import static android.widget.Toast.LENGTH_SHORT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.view.DragEvent;
import android.view.HapticFeedbackConstants;
import android.view.SurfaceControl;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.annotation.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import javax.inject.Inject;
/**
* Controller for Notification to window.
*/
public class ExpandableNotificationRowDragController {
private static final String TAG = ExpandableNotificationRowDragController.class.getSimpleName();
private int mIconSize;
private final Context mContext;
private final HeadsUpManager mHeadsUpManager;
private final ShadeController mShadeController;
@Inject
public ExpandableNotificationRowDragController(Context context,
HeadsUpManager headsUpManager,
ShadeController shadeController) {
mContext = context;
mHeadsUpManager = headsUpManager;
mShadeController = shadeController;
init();
}
private void init() {
mIconSize = mContext.getResources().getDimensionPixelSize(R.dimen.drag_and_drop_icon_size);
}
/**
* Called when drag event beyond the touchslop,
* and start drag and drop.
*
* @param view notification that was long pressed and started to drag and drop.
*/
@VisibleForTesting
public void startDragAndDrop(View view) {
ExpandableNotificationRow enr = null;
if (view instanceof ExpandableNotificationRow) {
enr = (ExpandableNotificationRow) view;
}
StatusBarNotification sn = enr.getEntry().getSbn();
Notification notification = sn.getNotification();
final PendingIntent contentIntent = notification.contentIntent != null
? notification.contentIntent
: notification.fullScreenIntent;
if (contentIntent == null) {
if (!enr.isPinned()) {
// We dismiss the shade for consistency, but also because toasts currently don't
// show above the shade
dismissShade();
}
Toast.makeText(mContext, R.string.drag_split_not_supported, LENGTH_SHORT)
.show();
return;
}
Bitmap iconBitmap = getBitmapFromDrawable(
getPkgIcon(enr.getEntry().getSbn().getPackageName()));
final ImageView snapshot = new ImageView(mContext);
snapshot.setImageBitmap(iconBitmap);
snapshot.layout(0, 0, mIconSize, mIconSize);
ClipDescription clipDescription = new ClipDescription("Drag And Drop",
new String[]{ClipDescription.MIMETYPE_APPLICATION_ACTIVITY});
Intent dragIntent = new Intent();
dragIntent.putExtra(ClipDescription.EXTRA_PENDING_INTENT, contentIntent);
dragIntent.putExtra(Intent.EXTRA_USER, android.os.Process.myUserHandle());
ClipData.Item item = new ClipData.Item(dragIntent);
ClipData dragData = new ClipData(clipDescription, item);
View.DragShadowBuilder myShadow = new View.DragShadowBuilder(snapshot);
view.setOnDragListener(getDraggedViewDragListener());
boolean result = view.startDragAndDrop(dragData, myShadow, null, View.DRAG_FLAG_GLOBAL
| View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION);
if (result) {
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
if (enr.isPinned()) {
mHeadsUpManager.releaseAllImmediately();
} else {
dismissShade();
}
}
}
private void dismissShade() {
// Speed up dismissing the shade since the drag needs to be handled by
// the shell layer underneath
mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */,
false /* delayed */, 1.1f /* speedUpFactor */);
}
private Drawable getPkgIcon(String pkgName) {
Drawable pkgicon = null;
PackageManager pm = mContext.getPackageManager();
ApplicationInfo info;
try {
info = pm.getApplicationInfo(
pkgName,
PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_DIRECT_BOOT_AWARE);
if (info != null) {
pkgicon = pm.getApplicationIcon(info);
} else {
Log.d(TAG, " application info is null ");
pkgicon = pm.getDefaultActivityIcon();
}
} catch (PackageManager.NameNotFoundException e) {
Log.d(TAG, "can not find package with : " + pkgName);
pkgicon = pm.getDefaultActivityIcon();
}
return pkgicon;
}
private Bitmap getBitmapFromDrawable(@NonNull Drawable drawable) {
final Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bmp);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bmp;
}
private View.OnDragListener getDraggedViewDragListener() {
return (view, dragEvent) -> {
switch (dragEvent.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
return true;
case DragEvent.ACTION_DRAG_ENDED:
if (dragEvent.getResult()) {
if (view instanceof ExpandableNotificationRow) {
ExpandableNotificationRow enr = (ExpandableNotificationRow) view;
enr.dragAndDropSuccess();
}
} else {
// Fade out the drag surface in place instead of animating back to the
// start position now that the shade is closed
fadeOutAndRemoveDragSurface(dragEvent);
}
// Clear the drag listener set above
view.setOnDragListener(null);
return true;
}
return false;
};
}
private void fadeOutAndRemoveDragSurface(DragEvent dragEvent) {
SurfaceControl dragSurface = dragEvent.getDragSurface();
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
ValueAnimator returnAnimator = ValueAnimator.ofFloat(0f, 1f);
returnAnimator.setDuration(200);
returnAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
returnAnimator.addUpdateListener(animation -> {
float t = animation.getAnimatedFraction();
float alpha = 1f - t;
tx.setAlpha(dragSurface, alpha);
tx.apply();
});
returnAnimator.addListener(new AnimatorListenerAdapter() {
private boolean mCanceled = false;
@Override
public void onAnimationCancel(Animator animation) {
cleanUpSurface();
mCanceled = true;
}
@Override
public void onAnimationEnd(Animator animation) {
if (mCanceled) {
// Already handled above
return;
}
cleanUpSurface();
}
private void cleanUpSurface() {
tx.remove(dragSurface);
tx.apply();
tx.close();
}
});
returnAnimator.start();
}
}