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