Home | History | Annotate | Download | only in documentsui
      1 /*
      2  * Copyright (C) 2016 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 
     17 package com.android.documentsui;
     18 
     19 import android.content.ClipData;
     20 import android.graphics.drawable.Drawable;
     21 import android.util.Log;
     22 import android.view.DragEvent;
     23 import android.view.View;
     24 import android.view.View.OnDragListener;
     25 
     26 import com.android.documentsui.ItemDragListener.DragHost;
     27 import com.android.internal.annotations.VisibleForTesting;
     28 
     29 import java.util.Timer;
     30 import java.util.TimerTask;
     31 
     32 import javax.annotation.Nullable;
     33 
     34 /**
     35  * An {@link OnDragListener} that adds support for "spring loading views". Use this when you want
     36  * items to pop-open when user hovers on them during a drag n drop.
     37  */
     38 public class ItemDragListener<H extends DragHost> implements OnDragListener {
     39 
     40     private static final String TAG = "ItemDragListener";
     41 
     42     @VisibleForTesting
     43     static final int DEFAULT_SPRING_TIMEOUT = 1500;
     44 
     45     protected final H mDragHost;
     46     private final Timer mHoverTimer;
     47     private final int mSpringTimeout;
     48 
     49     public ItemDragListener(H dragHost) {
     50         this(dragHost, new Timer(), DEFAULT_SPRING_TIMEOUT);
     51     }
     52 
     53     public ItemDragListener(H dragHost, int springTimeout) {
     54         this(dragHost, new Timer(), springTimeout);
     55     }
     56 
     57     @VisibleForTesting
     58     protected ItemDragListener(H dragHost, Timer timer, int springTimeout) {
     59         mDragHost = dragHost;
     60         mHoverTimer = timer;
     61         mSpringTimeout = springTimeout;
     62     }
     63 
     64     @Override
     65     public boolean onDrag(final View v, DragEvent event) {
     66         switch (event.getAction()) {
     67             case DragEvent.ACTION_DRAG_STARTED:
     68                 return true;
     69             case DragEvent.ACTION_DRAG_ENTERED:
     70                 handleEnteredEvent(v, event);
     71                 return true;
     72             case DragEvent.ACTION_DRAG_LOCATION:
     73                 handleLocationEvent(v, event.getX(), event.getY());
     74                 return true;
     75             case DragEvent.ACTION_DRAG_EXITED:
     76                 mDragHost.onDragExited(v, event.getLocalState());
     77                 // fall through
     78             case DragEvent.ACTION_DRAG_ENDED:
     79                 handleExitedEndedEvent(v, event);
     80                 return true;
     81             case DragEvent.ACTION_DROP:
     82                 return handleDropEvent(v, event);
     83         }
     84 
     85         return false;
     86     }
     87 
     88     private void handleEnteredEvent(View v, DragEvent event) {
     89         mDragHost.onDragEntered(v, event.getLocalState());
     90         @Nullable TimerTask task = createOpenTask(v, event);
     91         mDragHost.setDropTargetHighlight(v, event.getLocalState(), true);
     92         if (task == null) {
     93             return;
     94         }
     95         v.setTag(R.id.drag_hovering_tag, task);
     96         mHoverTimer.schedule(task, mSpringTimeout);
     97     }
     98 
     99     private void handleLocationEvent(View v, float x, float y) {
    100         Drawable background = v.getBackground();
    101         if (background != null) {
    102             background.setHotspot(x, y);
    103         }
    104     }
    105 
    106     private void handleExitedEndedEvent(View v, DragEvent event) {
    107         mDragHost.setDropTargetHighlight(v, event.getLocalState(), false);
    108         TimerTask task = (TimerTask) v.getTag(R.id.drag_hovering_tag);
    109         if (task != null) {
    110             task.cancel();
    111         }
    112     }
    113 
    114     private boolean handleDropEvent(View v, DragEvent event) {
    115         ClipData clipData = event.getClipData();
    116         if (clipData == null) {
    117             Log.w(TAG, "Received invalid drop event with null clipdata. Ignoring.");
    118             return false;
    119         }
    120 
    121         return handleDropEventChecked(v, event);
    122     }
    123 
    124     /**
    125      * Sub-classes such as {@link DirectoryDragListener} can override this method and return null.
    126      */
    127     public @Nullable TimerTask createOpenTask(final View v, DragEvent event) {
    128         TimerTask task = new TimerTask() {
    129             @Override
    130             public void run() {
    131                 mDragHost.runOnUiThread(() -> {
    132                     mDragHost.onViewHovered(v);
    133                 });
    134             }
    135         };
    136         return task;
    137     }
    138 
    139     /**
    140      * Handles a drop event. Override it if you want to do something on drop event. It's called when
    141      * {@link DragEvent#ACTION_DROP} happens. ClipData in DragEvent is guaranteed not null.
    142      *
    143      * @param v The view where user drops.
    144      * @param event the drag event.
    145      * @return true if this event is consumed; false otherwise
    146      */
    147     public boolean handleDropEventChecked(View v, DragEvent event) {
    148         return false; // we didn't handle the drop
    149     }
    150 
    151     /**
    152      * An interface {@link ItemDragListener} uses to make some callbacks.
    153      */
    154     public interface DragHost {
    155 
    156         /**
    157          * Runs this runnable in main thread.
    158          */
    159         void runOnUiThread(Runnable runnable);
    160 
    161         /**
    162          * Highlights/unhighlights the view to visually indicate this view is being hovered.
    163          * @param v the view being hovered
    164          * @param localState the Local state object  given by DragEvent
    165          * @param highlight true if highlight the view; false if unhighlight it
    166          */
    167         void setDropTargetHighlight(View v, Object localState, boolean highlight);
    168 
    169         /**
    170          * Notifies hovering timeout has elapsed
    171          * @param v the view being hovered
    172          */
    173         void onViewHovered(View v);
    174 
    175         /**
    176          * Notifies right away when drag shadow enters the view
    177          * @param v the view which drop shadow just entered
    178          * @param localState the Local state object given by DragEvent
    179          */
    180         void onDragEntered(View v, Object localState);
    181 
    182         /**
    183          * Notifies right away when drag shadow exits the view
    184          * @param v the view which drop shadow just exited
    185          * @param localState the Local state object given by DragEvent
    186          */
    187         void onDragExited(View v, Object localState);
    188     }
    189 }
    190