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.example.android.droptarget;
18 
19 import com.example.android.common.logger.Log;
20 
21 import android.app.Activity;
22 import android.content.ClipDescription;
23 import android.content.ContentResolver;
24 import android.net.Uri;
25 import android.os.Bundle;
26 import android.os.PersistableBundle;
27 import android.support.annotation.Nullable;
28 import android.support.v13.app.ActivityCompat;
29 import android.support.v13.view.DragAndDropPermissionsCompat;
30 import android.support.v4.app.Fragment;
31 import android.view.DragEvent;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.view.ViewGroup;
35 import android.widget.CheckBox;
36 import android.widget.ImageView;
37 
38 /**
39  * This sample demonstrates data can be moved between views in different applications via
40  * drag and drop.
41  * <p>This is the Target app for the drag and drop process. This app uses a
42  * {@link android.widget.ImageView} as the drop target. Images onto this
43  * view from the DragSource app that is also part of this sample.
44  * <p>
45  * There is also a {@link android.widget.EditText} widget that can accept dropped text (no
46  * extra setup is necessary).
47  * To access content URIs requiring permissions, the target app needs to request the
48  * {@link android.view.DragAndDropPermissions} from the Activity via
49  * {@link ActivityCompat#requestDragAndDropPermissions(Activity, DragEvent)}. This permission will
50  * stay either as long as the activity is alive, or until the release() method is called on the
51  * {@link android.view.DragAndDropPermissions} object.
52  */
53 public class DropTargetFragment extends Fragment {
54 
55     private static final String IMAGE_URI = "IMAGE_URI";
56 
57     public static final String EXTRA_IMAGE_INFO = "IMAGE_INFO";
58 
59     private static final String TAG = "DropTargetFragment";
60 
61     private Uri mImageUri;
62 
63     private CheckBox mReleasePermissionCheckBox;
64 
65     @Nullable
66     @Override
onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)67     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
68             @Nullable Bundle savedInstanceState) {
69 
70         View rootView = inflater.inflate(R.layout.fragment_droptarget, container, false);
71         final ImageView imageView = (ImageView) rootView.findViewById(R.id.image_target);
72 
73         ImageDragListener imageDragListener = new PermissionAwareImageDragListener();
74 
75         imageView.setOnDragListener(imageDragListener);
76 
77         // Restore the application state if an image was being displayed.
78         if (savedInstanceState != null) {
79             final String uriString = savedInstanceState.getString(IMAGE_URI);
80             if (uriString != null) {
81                 mImageUri = Uri.parse(uriString);
82                 imageView.setImageURI(mImageUri);
83             }
84         }
85 
86         mReleasePermissionCheckBox = (CheckBox) rootView.findViewById(R.id.release_checkbox);
87 
88         return rootView;
89     }
90 
91     @Override
onSaveInstanceState(Bundle savedInstanceState)92     public void onSaveInstanceState(Bundle savedInstanceState) {
93         if (mImageUri != null) {
94             savedInstanceState.putString(IMAGE_URI, mImageUri.toString());
95         }
96         super.onSaveInstanceState(savedInstanceState);
97     }
98 
99     private class PermissionAwareImageDragListener extends ImageDragListener {
100 
101         @Override
processLocation(float x, float y)102         protected void processLocation(float x, float y) {
103             // Callback is received when the dragged image enters the drop area.
104         }
105 
106         @Override
setImageUri(View view, DragEvent event, Uri uri)107         protected boolean setImageUri(View view, DragEvent event, Uri uri) {
108             // Read the string from the clip description extras.
109             Log.d(TAG, "ClipDescription extra: " + getExtra(event));
110 
111             Log.d(TAG, "Setting image source to: " + uri.toString());
112             mImageUri = uri;
113 
114             if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
115                 // Accessing a "content" scheme Uri requires a permission grant.
116                 DragAndDropPermissionsCompat dropPermissions = ActivityCompat
117                         .requestDragAndDropPermissions(getActivity(), event);
118                 Log.d(TAG, "Requesting permissions.");
119 
120                 if (dropPermissions == null) {
121                     // Permission could not be obtained.
122                     Log.d(TAG, "Drop permission request failed.");
123                     return false;
124                 }
125 
126                 final boolean result = super.setImageUri(view, event, uri);
127 
128                 if (mReleasePermissionCheckBox.isChecked()) {
129                     /* Release the permissions if you are done with the URI.
130                      Note that you may need to hold onto the permission until later if other
131                      operations are performed on the content. For instance, releasing the
132                      permissions here will prevent onCreateView from properly restoring the
133                      ImageView state.
134                      If permissions are not explicitly released, the permission grant will be
135                      revoked when the activity is destroyed.
136                      */
137                     dropPermissions.release();
138                     Log.d(TAG, "Permissions released.");
139                 }
140 
141                 return result;
142             } else {
143                 // Other schemes (such as "android.resource") do not require a permission grant.
144                 return super.setImageUri(view, event, uri);
145             }
146         }
147 
148         @Override
onDrag(View view, DragEvent event)149         public boolean onDrag(View view, DragEvent event) {
150             // DragTarget is peeking into the MIME types of the dragged event in order to ignore
151             // non-image drags completely.
152             // DragSource does not do that but rejects non-image content once a drop has happened.
153             ClipDescription clipDescription = event.getClipDescription();
154             if (clipDescription != null && !clipDescription.hasMimeType("image/*")) {
155                 return false;
156             }
157             // Callback received when image is being dragged.
158             return super.onDrag(view, event);
159         }
160     }
161 
162     /**
163      * DragEvents can contain additional data packaged in a {@link PersistableBundle}.
164      * Extract the extras from the event and return the String stored for the
165      * {@link #EXTRA_IMAGE_INFO} entry.
166      */
getExtra(DragEvent event)167     private String getExtra(DragEvent event) {
168         // The extras are contained in the ClipDescription in the DragEvent.
169         ClipDescription clipDescription = event.getClipDescription();
170         if (clipDescription != null) {
171             PersistableBundle extras = clipDescription.getExtras();
172             if (extras != null) {
173                 return extras.getString(EXTRA_IMAGE_INFO);
174             }
175         }
176         return null;
177     }
178 }
179