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