1 /*
2  * Copyright 2015, 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.example.android.dragsource;
18 
19 import com.example.android.common.logger.Log;
20 
21 import android.content.ClipData;
22 import android.content.ClipDescription;
23 import android.content.Context;
24 import android.graphics.Bitmap;
25 import android.graphics.BitmapFactory;
26 import android.graphics.Point;
27 import android.net.Uri;
28 import android.os.Bundle;
29 import android.os.PersistableBundle;
30 import android.support.annotation.Nullable;
31 import android.support.v13.view.DragStartHelper;
32 import android.support.v4.app.Fragment;
33 import android.support.v4.content.FileProvider;
34 import android.view.DragEvent;
35 import android.view.LayoutInflater;
36 import android.view.View;
37 import android.view.ViewGroup;
38 import android.widget.ImageView;
39 
40 import java.io.File;
41 import java.io.FileOutputStream;
42 import java.io.IOException;
43 import java.util.Date;
44 
45 
46 /**
47  * This sample demonstrates data can be moved between views within the app or between different
48  * apps via drag and drop.
49  * <p>This is the source app for the drag and drop sample. This app contains several
50  * {@link android.widget.ImageView} widgets which can be a drag source. Images can be dropped
51  * to a drop target area within the same app or in the DropTarget app (a separate app in this
52  * sample).
53  * <p>
54  * There is also one {@link android.widget.EditText} widget that can be a drag source (no extra
55  * setup is necessary).
56  * <p/>
57  * To enable cross application drag and drop, the {@link android.view.View#DRAG_FLAG_GLOBAL}
58  * permission needs to be passed to the {@link android.view.View#startDragAndDrop(ClipData,
59  * View.DragShadowBuilder, Object, int)} method. If a Uri
60  * requiring permission grants is being sent, then the
61  * {@link android.view.View#DRAG_FLAG_GLOBAL_URI_READ} and/or the
62  * {@link android.view.View#DRAG_FLAG_GLOBAL_URI_WRITE} flags must be used also.
63  */
64 public class DragSourceFragment extends Fragment {
65 
66     /**
67      * Name of saved data that stores the dropped image URI on the local ImageView when set.
68      */
69     private static final String IMAGE_URI = "IMAGE_URI";
70 
71     /**
72      * Name of the parameter for a {@link ClipData} extra that stores a text describing the dragged
73      * image.
74      */
75     public static final String EXTRA_IMAGE_INFO = "IMAGE_INFO";
76 
77     /**
78      * Uri of the ImageView source when set.
79      */
80     private Uri mLocalImageUri;
81 
82     private static final String TAG = "DragSourceFragment";
83 
84     private static final String CONTENT_AUTHORITY = "com.example.android.dragsource.fileprovider";
85 
86     @Nullable
87     @Override
onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)88     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
89             @Nullable Bundle savedInstanceState) {
90 
91         View view = inflater.inflate(R.layout.fragment_dragsource, null);
92 
93         // Set up two image views for global drag and drop with a permission grant.
94         Uri imageUri = getFileUri(R.drawable.image1, "image1.png");
95         ImageView imageView = (ImageView) view.findViewById(R.id.image_one);
96         setUpDraggableImage(imageView, imageUri);
97         imageView.setImageURI(imageUri);
98 
99         imageUri = getFileUri(R.drawable.image2, "image2.png");
100         imageView = (ImageView) view.findViewById(R.id.image_two);
101         setUpDraggableImage(imageView, imageUri);
102         imageView.setImageURI(imageUri);
103 
104         // Set up the local drop target area.
105         final ImageView localImageTarget = (ImageView) view.findViewById(R.id.local_target);
106         localImageTarget.setOnDragListener(new ImageDragListener() {
107             @Override
108             protected boolean setImageUri(View view, DragEvent event, Uri uri) {
109                 mLocalImageUri = uri;
110                 Log.d(TAG, "Setting local image to: " + uri);
111                 return super.setImageUri(view, event, uri);
112             }
113         });
114 
115         if (savedInstanceState != null) {
116             final String uriString = savedInstanceState.getString(IMAGE_URI);
117             if (uriString != null) {
118                 mLocalImageUri = Uri.parse(uriString);
119                 Log.d(TAG, "Restoring local image to: " + mLocalImageUri);
120                 localImageTarget.setImageURI(mLocalImageUri);
121             }
122         }
123         return view;
124     }
125 
126     @Override
onSaveInstanceState(Bundle savedInstanceState)127     public void onSaveInstanceState(Bundle savedInstanceState) {
128         if (mLocalImageUri != null) {
129             savedInstanceState.putString(IMAGE_URI, mLocalImageUri.toString());
130         }
131         super.onSaveInstanceState(savedInstanceState);
132     }
133 
setUpDraggableImage(ImageView imageView, final Uri imageUri)134     private void setUpDraggableImage(ImageView imageView, final Uri imageUri) {
135 
136         // Set up a listener that starts the drag and drop event with flags and extra data.
137         DragStartHelper.OnDragStartListener listener = new DragStartHelper.OnDragStartListener() {
138             @Override
139             public boolean onDragStart(View view, final DragStartHelper helper) {
140                 Log.d(TAG, "Drag start event received from helper.");
141 
142                 // Use a DragShadowBuilder
143                 View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view) {
144                     @Override
145                     public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
146                         super.onProvideShadowMetrics(shadowSize, shadowTouchPoint);
147                         // Notify the DragStartHelper of point where the view was touched.
148                         helper.getTouchPosition(shadowTouchPoint);
149                         Log.d(TAG, "View was touched at: " + shadowTouchPoint);
150                     }
151                 };
152 
153                 // Set up the flags for the drag event.
154                 // Enable drag and drop across apps (global)
155                 // and require read permissions for this URI.
156                 int flags = View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ;
157 
158                 // Add an optional clip description that that contains an extra String that is
159                 // read out by the target app.
160                 final ClipDescription clipDescription = new ClipDescription("", new String[]{
161                         getContext().getContentResolver().getType(imageUri)});
162                 // Extras are stored within a PersistableBundle.
163                 PersistableBundle extras = new PersistableBundle(1);
164                 // Add a String that the target app will display.
165                 extras.putString(EXTRA_IMAGE_INFO,
166                         "Drag Started at " + new Date());
167                 clipDescription.setExtras(extras);
168 
169                 // The ClipData object describes the object that is being dragged and dropped.
170                 final ClipData clipData =
171                         new ClipData(clipDescription, new ClipData.Item(imageUri));
172 
173                 Log.d(TAG, "Created ClipDescription. Starting drag and drop.");
174                 // Start the drag and drop event.
175                 return view.startDragAndDrop(clipData, shadowBuilder, null, flags);
176 
177             }
178 
179         };
180 
181         // Use the DragStartHelper to detect drag and drop events and use the OnDragStartListener
182         // defined above to start the event when it has been detected.
183         DragStartHelper helper = new DragStartHelper(imageView, listener);
184         helper.attach();
185         Log.d(TAG, "DragStartHelper attached to view.");
186     }
187 
188     /**
189      * Copy a drawable resource into local storage and makes it available via the
190      * {@link FileProvider}.
191      *
192      * @see Context#getFilesDir()
193      * @see FileProvider
194      * @see FileProvider#getUriForFile(Context, String, File)
195      */
getFileUri(int sourceResourceId, String targetName)196     private Uri getFileUri(int sourceResourceId, String targetName) {
197         // Create the images/ sub directory if it does not exist yet.
198         File filePath = new File(getContext().getFilesDir(), "images");
199         if (!filePath.exists() && !filePath.mkdir()) {
200             return null;
201         }
202 
203         // Copy a drawable from resources to the internal directory.
204         File newFile = new File(filePath, targetName);
205         if (!newFile.exists()) {
206             copyImageResourceToFile(sourceResourceId, newFile);
207         }
208 
209         // Make the file accessible via the FileProvider and retrieve its URI.
210         return FileProvider.getUriForFile(getContext(), CONTENT_AUTHORITY, newFile);
211     }
212 
213 
214     /**
215      * Copy a PNG resource drawable to a {@File}.
216      */
copyImageResourceToFile(int resourceId, File filePath)217     private void copyImageResourceToFile(int resourceId, File filePath) {
218         Bitmap image = BitmapFactory.decodeResource(getResources(), resourceId);
219 
220         FileOutputStream out = null;
221         try {
222             out = new FileOutputStream(filePath);
223             image.compress(Bitmap.CompressFormat.PNG, 100, out);
224         } catch (Exception e) {
225             e.printStackTrace();
226         } finally {
227             try {
228                 if (out != null) {
229                     out.close();
230                 }
231             } catch (IOException e) {
232                 e.printStackTrace();
233             }
234         }
235     }
236 
237 }
238