blob: 51c47a468df1a2080e4a7e916a505d3810660e27 [file] [log] [blame]
Steve McKay1f199482015-05-20 15:58:42 -07001/*
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
17package com.android.documentsui;
18
Steve McKay1f199482015-05-20 15:58:42 -070019import android.content.ClipData;
20import android.content.ClipboardManager;
21import android.content.ContentProviderClient;
22import android.content.ContentResolver;
23import android.content.Context;
24import android.database.Cursor;
25import android.net.Uri;
Ben Linff4d5842016-04-18 14:35:28 -070026import android.os.PersistableBundle;
Steve McKay1f199482015-05-20 15:58:42 -070027import android.provider.DocumentsContract;
Steve McKayfefcd702015-08-20 16:19:38 +000028import android.support.annotation.Nullable;
Steve McKay1f199482015-05-20 15:58:42 -070029import android.util.Log;
30
31import com.android.documentsui.model.DocumentInfo;
Ben Linff4d5842016-04-18 14:35:28 -070032import com.android.documentsui.services.FileOperationService;
33import com.android.documentsui.services.FileOperationService.OpType;
Steve McKay1f199482015-05-20 15:58:42 -070034
Steve McKay1f199482015-05-20 15:58:42 -070035import libcore.io.IoUtils;
36
Steve McKayfefcd702015-08-20 16:19:38 +000037import java.util.ArrayList;
Vladislav Kaznacheev0859d302016-05-03 15:37:21 -070038import java.util.Arrays;
Steve McKay22b35a72016-04-13 14:03:24 -070039import java.util.Collections;
Vladislav Kaznacheev0859d302016-05-03 15:37:21 -070040import java.util.HashSet;
Steve McKay1f199482015-05-20 15:58:42 -070041import java.util.List;
42
43/**
44 * ClipboardManager wrapper class providing higher level logical
45 * support for dealing with Documents.
46 */
Steve McKayf68210e2015-11-03 15:23:16 -080047public final class DocumentClipper {
Steve McKay1f199482015-05-20 15:58:42 -070048
49 private static final String TAG = "DocumentClipper";
Ben Linff4d5842016-04-18 14:35:28 -070050 private static final String SRC_PARENT_KEY = "srcParent";
51 private static final String OP_TYPE_KEY = "opType";
Steve McKay1f199482015-05-20 15:58:42 -070052
53 private Context mContext;
54 private ClipboardManager mClipboard;
55
56 public DocumentClipper(Context context) {
57 mContext = context;
58 mClipboard = context.getSystemService(ClipboardManager.class);
59 }
60
61 public boolean hasItemsToPaste() {
62 if (mClipboard.hasPrimaryClip()) {
63 ClipData clipData = mClipboard.getPrimaryClip();
64 int count = clipData.getItemCount();
65 if (count > 0) {
66 for (int i = 0; i < count; ++i) {
67 ClipData.Item item = clipData.getItemAt(i);
68 Uri uri = item.getUri();
69 if (isDocumentUri(uri)) {
70 return true;
71 }
72 }
73 }
74 }
75 return false;
76 }
77
78 private boolean isDocumentUri(@Nullable Uri uri) {
79 return uri != null && DocumentsContract.isDocumentUri(mContext, uri);
80 }
81
82 /**
Ben Linff4d5842016-04-18 14:35:28 -070083 * Returns details regarding the documents on the primary clipboard
Steve McKay1f199482015-05-20 15:58:42 -070084 */
Ben Linff4d5842016-04-18 14:35:28 -070085 public ClipDetails getClipDetails() {
86 return getClipDetails(mClipboard.getPrimaryClip());
Steve McKay1f199482015-05-20 15:58:42 -070087 }
88
Ben Linff4d5842016-04-18 14:35:28 -070089 public ClipDetails getClipDetails(@Nullable ClipData clipData) {
90 if (clipData == null) {
91 return null;
92 }
93
94 String srcParent = clipData.getDescription().getExtras().getString(SRC_PARENT_KEY);
95
96 ClipDetails clipDetails = new ClipDetails(
97 clipData.getDescription().getExtras().getInt(OP_TYPE_KEY),
98 getDocumentsFromClipData(clipData),
99 createDocument((srcParent != null) ? Uri.parse(srcParent) : null));
100
101 return clipDetails;
102 }
103
104 private List<DocumentInfo> getDocumentsFromClipData(ClipData clipData) {
Steve McKay0af8afd2016-02-25 13:34:03 -0800105 assert(clipData != null);
Steve McKay1f199482015-05-20 15:58:42 -0700106
107 int count = clipData.getItemCount();
108 if (count == 0) {
Ben Linff4d5842016-04-18 14:35:28 -0700109 return Collections.EMPTY_LIST;
Steve McKay1f199482015-05-20 15:58:42 -0700110 }
111
Ben Linff4d5842016-04-18 14:35:28 -0700112 final List<DocumentInfo> srcDocs = new ArrayList<>();
113
Steve McKay1f199482015-05-20 15:58:42 -0700114 for (int i = 0; i < count; ++i) {
115 ClipData.Item item = clipData.getItemAt(i);
116 Uri itemUri = item.getUri();
Ben Linff4d5842016-04-18 14:35:28 -0700117 srcDocs.add(createDocument(itemUri));
Steve McKay1f199482015-05-20 15:58:42 -0700118 }
119
120 return srcDocs;
121 }
122
123 /**
124 * Returns ClipData representing the list of docs, or null if docs is empty,
125 * or docs cannot be converted.
126 */
Ben Linff4d5842016-04-18 14:35:28 -0700127 public @Nullable ClipData getClipDataForDocuments(List<DocumentInfo> docs, @OpType int opType) {
Steve McKay1f199482015-05-20 15:58:42 -0700128 final ContentResolver resolver = mContext.getContentResolver();
Vladislav Kaznacheev0859d302016-05-03 15:37:21 -0700129 final String[] mimeTypes = getMimeTypes(resolver, docs);
Steve McKay1f199482015-05-20 15:58:42 -0700130 ClipData clipData = null;
131 for (DocumentInfo doc : docs) {
Ben Linff4d5842016-04-18 14:35:28 -0700132 assert(doc != null);
133 assert(doc.derivedUri != null);
Steve McKay1f199482015-05-20 15:58:42 -0700134 if (clipData == null) {
135 // TODO: figure out what this string should be.
136 // Currently it is not displayed anywhere in the UI, but this might change.
Ben Linff4d5842016-04-18 14:35:28 -0700137 final String clipLabel = "";
Vladislav Kaznacheev9c628d02016-05-04 10:56:05 -0700138 clipData = new ClipData(clipLabel, mimeTypes, new ClipData.Item(doc.derivedUri));
Ben Linff4d5842016-04-18 14:35:28 -0700139 PersistableBundle bundle = new PersistableBundle();
140 bundle.putInt(OP_TYPE_KEY, opType);
141 clipData.getDescription().setExtras(bundle);
Steve McKay1f199482015-05-20 15:58:42 -0700142 } else {
143 // TODO: update list of mime types in ClipData.
Ben Linff4d5842016-04-18 14:35:28 -0700144 clipData.addItem(new ClipData.Item(doc.derivedUri));
Steve McKay1f199482015-05-20 15:58:42 -0700145 }
146 }
147 return clipData;
148 }
149
Vladislav Kaznacheev0859d302016-05-03 15:37:21 -0700150 private static String[] getMimeTypes(ContentResolver resolver, List<DocumentInfo> docs) {
151 final HashSet<String> mimeTypes = new HashSet<>();
152 for (DocumentInfo doc : docs) {
153 assert(doc != null);
154 assert(doc.derivedUri != null);
155 final Uri uri = doc.derivedUri;
156 if ("content".equals(uri.getScheme())) {
157 mimeTypes.add(resolver.getType(uri));
158 final String[] streamTypes = resolver.getStreamTypes(uri, "*/*");
159 if (streamTypes != null) {
160 mimeTypes.addAll(Arrays.asList(streamTypes));
161 }
162 }
163 }
164 return mimeTypes.toArray(new String[0]);
165 }
166
Ben Linff4d5842016-04-18 14:35:28 -0700167 /**
168 * Puts {@code ClipData} in a primary clipboard, describing a copy operation
169 */
170 public void clipDocumentsForCopy(List<DocumentInfo> docs) {
171 ClipData data = getClipDataForDocuments(docs, FileOperationService.OPERATION_COPY);
172 assert(data != null);
173
Steve McKay1f199482015-05-20 15:58:42 -0700174 mClipboard.setPrimaryClip(data);
175 }
Ben Linff4d5842016-04-18 14:35:28 -0700176
177 /**
178 * Puts {@Code ClipData} in a primary clipboard, describing a cut operation
179 */
180 public void clipDocumentsForCut(List<DocumentInfo> docs, DocumentInfo srcParent) {
181 assert(docs != null);
182 assert(!docs.isEmpty());
183 assert(srcParent != null);
184 assert(srcParent.derivedUri != null);
185
186 ClipData data = getClipDataForDocuments(docs, FileOperationService.OPERATION_MOVE);
187 assert(data != null);
188
189 PersistableBundle bundle = data.getDescription().getExtras();
190 bundle.putString(SRC_PARENT_KEY, srcParent.derivedUri.toString());
191
192 mClipboard.setPrimaryClip(data);
193 }
194
195 private DocumentInfo createDocument(Uri uri) {
196 DocumentInfo doc = null;
197 if (uri != null && DocumentsContract.isDocumentUri(mContext, uri)) {
198 ContentResolver resolver = mContext.getContentResolver();
199 ContentProviderClient client = null;
200 Cursor cursor = null;
201 try {
202 client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, uri.getAuthority());
203 cursor = client.query(uri, null, null, null, null);
204 cursor.moveToPosition(0);
205 doc = DocumentInfo.fromCursor(cursor, uri.getAuthority());
206 } catch (Exception e) {
207 Log.e(TAG, e.getMessage());
208 } finally {
209 IoUtils.closeQuietly(cursor);
210 ContentProviderClient.releaseQuietly(client);
211 }
212 }
213 return doc;
214 }
215
216 public static class ClipDetails {
217 public final @OpType int opType;
218 public final List<DocumentInfo> docs;
219 public final @Nullable DocumentInfo parent;
220
221 ClipDetails(@OpType int opType, List<DocumentInfo> docs, @Nullable DocumentInfo parent) {
222 this.opType = opType;
223 this.docs = docs;
224 this.parent = parent;
225 }
226 }
Steve McKay1f199482015-05-20 15:58:42 -0700227}