1 /*
2  * Copyright (C) 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.android.documentsui;
18 
19 import android.content.ClipData;
20 import android.content.ClipboardManager;
21 import android.content.ContentProviderClient;
22 import android.content.ContentResolver;
23 import android.content.Context;
24 import android.database.Cursor;
25 import android.net.Uri;
26 import android.provider.DocumentsContract;
27 import android.support.annotation.Nullable;
28 import android.util.Log;
29 
30 import com.android.documentsui.model.DocumentInfo;
31 
32 import libcore.io.IoUtils;
33 
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.Collections;
37 import java.util.HashSet;
38 import java.util.List;
39 
40 /**
41  * ClipboardManager wrapper class providing higher level logical
42  * support for dealing with Documents.
43  */
44 public final class DocumentClipper {
45 
46     private static final String TAG = "DocumentClipper";
47 
48     private Context mContext;
49     private ClipboardManager mClipboard;
50 
DocumentClipper(Context context)51     public DocumentClipper(Context context) {
52         mContext = context;
53         mClipboard = context.getSystemService(ClipboardManager.class);
54     }
55 
hasItemsToPaste()56     public boolean hasItemsToPaste() {
57         if (mClipboard.hasPrimaryClip()) {
58             ClipData clipData = mClipboard.getPrimaryClip();
59             int count = clipData.getItemCount();
60             if (count > 0) {
61                 for (int i = 0; i < count; ++i) {
62                     ClipData.Item item = clipData.getItemAt(i);
63                     Uri uri = item.getUri();
64                     if (isDocumentUri(uri)) {
65                         return true;
66                     }
67                 }
68             }
69         }
70         return false;
71     }
72 
isDocumentUri(@ullable Uri uri)73     private boolean isDocumentUri(@Nullable Uri uri) {
74         return uri != null && DocumentsContract.isDocumentUri(mContext, uri);
75     }
76 
77     /**
78      * Returns a list of Documents as decoded from Clipboard primary clipdata.
79      * This should be run from inside an AsyncTask.
80      */
getClippedDocuments()81     public List<DocumentInfo> getClippedDocuments() {
82         ClipData data = mClipboard.getPrimaryClip();
83         return data == null ? Collections.EMPTY_LIST : getDocumentsFromClipData(data);
84     }
85 
86     /**
87      * Returns a list of Documents as decoded in clipData.
88      * This should be run from inside an AsyncTask.
89      */
getDocumentsFromClipData(ClipData clipData)90     public List<DocumentInfo> getDocumentsFromClipData(ClipData clipData) {
91         assert(clipData != null);
92         final List<DocumentInfo> srcDocs = new ArrayList<>();
93 
94         int count = clipData.getItemCount();
95         if (count == 0) {
96             return srcDocs;
97         }
98 
99         ContentResolver resolver = mContext.getContentResolver();
100         for (int i = 0; i < count; ++i) {
101             ClipData.Item item = clipData.getItemAt(i);
102             Uri itemUri = item.getUri();
103             if (itemUri != null && DocumentsContract.isDocumentUri(mContext, itemUri)) {
104                 ContentProviderClient client = null;
105                 Cursor cursor = null;
106                 try {
107                     client = DocumentsApplication.acquireUnstableProviderOrThrow(
108                             resolver, itemUri.getAuthority());
109                     cursor = client.query(itemUri, null, null, null, null);
110                     cursor.moveToPosition(0);
111                     srcDocs.add(DocumentInfo.fromCursor(cursor, itemUri.getAuthority()));
112                 } catch (Exception e) {
113                     Log.e(TAG, e.getMessage());
114                 } finally {
115                     IoUtils.closeQuietly(cursor);
116                     ContentProviderClient.releaseQuietly(client);
117                 }
118             }
119         }
120 
121         return srcDocs;
122     }
123 
124     /**
125      * Returns ClipData representing the list of docs, or null if docs is empty,
126      * or docs cannot be converted.
127      */
getClipDataForDocuments(List<DocumentInfo> docs)128     public @Nullable ClipData getClipDataForDocuments(List<DocumentInfo> docs) {
129         final ContentResolver resolver = mContext.getContentResolver();
130         final String[] mimeTypes = getMimeTypes(resolver, docs);
131         ClipData clipData = null;
132         for (DocumentInfo doc : docs) {
133             if (clipData == null) {
134                 // TODO: figure out what this string should be.
135                 // Currently it is not displayed anywhere in the UI, but this might change.
136                 final String label = "";
137                 clipData = new ClipData(label, mimeTypes, new ClipData.Item(doc.derivedUri));
138             } else {
139                 // TODO: update list of mime types in ClipData.
140                 clipData.addItem(new ClipData.Item(doc.derivedUri));
141             }
142         }
143         return clipData;
144     }
145 
getMimeTypes(ContentResolver resolver, List<DocumentInfo> docs)146     private static String[] getMimeTypes(ContentResolver resolver, List<DocumentInfo> docs) {
147         final HashSet<String> mimeTypes = new HashSet<>();
148         for (DocumentInfo doc : docs) {
149             assert(doc != null);
150             assert(doc.derivedUri != null);
151             final Uri uri = doc.derivedUri;
152             if ("content".equals(uri.getScheme())) {
153                 mimeTypes.add(resolver.getType(uri));
154                 final String[] streamTypes = resolver.getStreamTypes(uri, "*/*");
155                 if (streamTypes != null) {
156                     mimeTypes.addAll(Arrays.asList(streamTypes));
157                 }
158             }
159         }
160         return mimeTypes.toArray(new String[0]);
161     }
162 
clipDocuments(List<DocumentInfo> docs)163     public void clipDocuments(List<DocumentInfo> docs) {
164         ClipData data = getClipDataForDocuments(docs);
165         mClipboard.setPrimaryClip(data);
166     }
167 }
168