• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.android.documentsui.clipping;
18 
19 import static com.android.documentsui.clipping.DocumentClipper.OP_JUMBO_SELECTION_SIZE;
20 import static com.android.documentsui.clipping.DocumentClipper.OP_JUMBO_SELECTION_TAG;
21 
22 import android.content.ClipData;
23 import android.content.Context;
24 import android.net.Uri;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 import android.os.PersistableBundle;
28 import android.support.annotation.VisibleForTesting;
29 import android.util.Log;
30 
31 import com.android.documentsui.DocumentsApplication;
32 import com.android.documentsui.base.Shared;
33 import com.android.documentsui.selection.Selection;
34 import com.android.documentsui.services.FileOperation;
35 
36 import java.io.File;
37 import java.io.IOException;
38 import java.util.ArrayList;
39 import java.util.Collection;
40 import java.util.List;
41 import java.util.function.Function;
42 
43 /**
44  * UrisSupplier provides doc uri list to {@link FileOperation}.
45  *
46  * <p>Under the hood it provides cross-process synchronization support such that its consumer doesn't
47  * need to explicitly synchronize its access.
48  */
49 public abstract class UrisSupplier implements Parcelable {
50 
getItemCount()51     public abstract int getItemCount();
52 
53     /**
54      * Gets doc list.
55      *
56      * @param context We need context to obtain {@link ClipStorage}. It can't be sent in a parcel.
57      */
getUris(Context context)58     public Iterable<Uri> getUris(Context context) throws IOException {
59         return getUris(DocumentsApplication.getClipStore(context));
60     }
61 
62     @VisibleForTesting
getUris(ClipStore storage)63     abstract Iterable<Uri> getUris(ClipStore storage) throws IOException;
64 
dispose()65     public void dispose() {}
66 
67     @Override
describeContents()68     public int describeContents() {
69         return 0;
70     }
71 
create(ClipData clipData, ClipStore storage)72     public static UrisSupplier create(ClipData clipData, ClipStore storage) throws IOException {
73         UrisSupplier uris;
74         PersistableBundle bundle = clipData.getDescription().getExtras();
75         if (bundle.containsKey(OP_JUMBO_SELECTION_TAG)) {
76             uris = new JumboUrisSupplier(clipData, storage);
77         } else {
78             uris = new StandardUrisSupplier(clipData);
79         }
80 
81         return uris;
82     }
83 
create( Selection selection, Function<String, Uri> uriBuilder, ClipStore storage)84     public static UrisSupplier create(
85             Selection selection, Function<String, Uri> uriBuilder, ClipStore storage)
86             throws IOException {
87 
88         List<Uri> uris = new ArrayList<>(selection.size());
89         for (String id : selection) {
90             uris.add(uriBuilder.apply(id));
91         }
92 
93         return create(uris, storage);
94     }
95 
96     @VisibleForTesting
create(List<Uri> uris, ClipStore storage)97     static UrisSupplier create(List<Uri> uris, ClipStore storage) throws IOException {
98         UrisSupplier urisSupplier = (uris.size() > Shared.MAX_DOCS_IN_INTENT)
99                 ? new JumboUrisSupplier(uris, storage)
100                 : new StandardUrisSupplier(uris);
101 
102         return urisSupplier;
103     }
104 
105     private static class JumboUrisSupplier extends UrisSupplier {
106         private static final String TAG = "JumboUrisSupplier";
107 
108         private final File mFile;
109         private final int mSelectionSize;
110 
111         private final List<ClipStorageReader> mReaders = new ArrayList<>();
112 
JumboUrisSupplier(ClipData clipData, ClipStore storage)113         private JumboUrisSupplier(ClipData clipData, ClipStore storage) throws IOException {
114             PersistableBundle bundle = clipData.getDescription().getExtras();
115             final int tag = bundle.getInt(OP_JUMBO_SELECTION_TAG, ClipStorage.NO_SELECTION_TAG);
116             assert(tag != ClipStorage.NO_SELECTION_TAG);
117             mFile = storage.getFile(tag);
118             assert(mFile.exists());
119 
120             mSelectionSize = bundle.getInt(OP_JUMBO_SELECTION_SIZE);
121             assert(mSelectionSize > Shared.MAX_DOCS_IN_INTENT);
122         }
123 
JumboUrisSupplier(Collection<Uri> uris, ClipStore clipStore)124         private JumboUrisSupplier(Collection<Uri> uris, ClipStore clipStore) throws IOException {
125             final int tag = clipStore.persistUris(uris);
126 
127             // There is a tiny race condition here. A job may starts to read before persist task
128             // starts to write, but it has to beat an IPC and background task schedule, which is
129             // pretty rare. Creating a symlink doesn't need that file to exist, but we can't assert
130             // on its existence.
131             mFile = clipStore.getFile(tag);
132             mSelectionSize = uris.size();
133         }
134 
135         @Override
getItemCount()136         public int getItemCount() {
137             return mSelectionSize;
138         }
139 
140         @Override
getUris(ClipStore storage)141         Iterable<Uri> getUris(ClipStore storage) throws IOException {
142             ClipStorageReader reader = storage.createReader(mFile);
143             synchronized (mReaders) {
144                 mReaders.add(reader);
145             }
146 
147             return reader;
148         }
149 
150         @Override
dispose()151         public void dispose() {
152             synchronized (mReaders) {
153                 for (ClipStorageReader reader : mReaders) {
154                     try {
155                         reader.close();
156                     } catch (IOException e) {
157                         Log.w(TAG, "Failed to close a reader.", e);
158                     }
159                 }
160             }
161 
162             // mFile is a symlink to the actual data file. Delete the symlink here so that we know
163             // there is one fewer referrer that needs the data file. The actual data file will be
164             // cleaned up during file slot rotation. See ClipStorage for more details.
165             mFile.delete();
166         }
167 
168         @Override
toString()169         public String toString() {
170             StringBuilder builder = new StringBuilder();
171             builder.append("JumboUrisSupplier{");
172             builder.append("file=").append(mFile.getAbsolutePath());
173             builder.append(", selectionSize=").append(mSelectionSize);
174             builder.append("}");
175             return builder.toString();
176         }
177 
178         @Override
writeToParcel(Parcel dest, int flags)179         public void writeToParcel(Parcel dest, int flags) {
180             dest.writeString(mFile.getAbsolutePath());
181             dest.writeInt(mSelectionSize);
182         }
183 
JumboUrisSupplier(Parcel in)184         private JumboUrisSupplier(Parcel in) {
185             mFile = new File(in.readString());
186             mSelectionSize = in.readInt();
187         }
188 
189         public static final Parcelable.Creator<JumboUrisSupplier> CREATOR =
190                 new Parcelable.Creator<JumboUrisSupplier>() {
191 
192                     @Override
193                     public JumboUrisSupplier createFromParcel(Parcel source) {
194                         return new JumboUrisSupplier(source);
195                     }
196 
197                     @Override
198                     public JumboUrisSupplier[] newArray(int size) {
199                         return new JumboUrisSupplier[size];
200                     }
201                 };
202     }
203 
204     /**
205      * This class and its constructor is visible for testing to create test doubles of
206      * {@link UrisSupplier}.
207      */
208     @VisibleForTesting
209     public static class StandardUrisSupplier extends UrisSupplier {
210         private final List<Uri> mDocs;
211 
StandardUrisSupplier(ClipData clipData)212         private StandardUrisSupplier(ClipData clipData) {
213             mDocs = listDocs(clipData);
214         }
215 
216         @VisibleForTesting
StandardUrisSupplier(List<Uri> docs)217         public StandardUrisSupplier(List<Uri> docs) {
218             mDocs = docs;
219         }
220 
listDocs(ClipData clipData)221         private List<Uri> listDocs(ClipData clipData) {
222             ArrayList<Uri> docs = new ArrayList<>(clipData.getItemCount());
223 
224             for (int i = 0; i < clipData.getItemCount(); ++i) {
225                 Uri uri = clipData.getItemAt(i).getUri();
226                 assert(uri != null);
227                 docs.add(uri);
228             }
229 
230             return docs;
231         }
232 
233         @Override
getItemCount()234         public int getItemCount() {
235             return mDocs.size();
236         }
237 
238         @Override
getUris(ClipStore storage)239         Iterable<Uri> getUris(ClipStore storage) {
240             return mDocs;
241         }
242 
243         @Override
toString()244         public String toString() {
245             StringBuilder builder = new StringBuilder();
246             builder.append("StandardUrisSupplier{");
247             builder.append("docs=").append(mDocs.toString());
248             builder.append("}");
249             return builder.toString();
250         }
251 
252         @Override
writeToParcel(Parcel dest, int flags)253         public void writeToParcel(Parcel dest, int flags) {
254             dest.writeTypedList(mDocs);
255         }
256 
StandardUrisSupplier(Parcel in)257         private StandardUrisSupplier(Parcel in) {
258             mDocs = in.createTypedArrayList(Uri.CREATOR);
259         }
260 
261         public static final Parcelable.Creator<StandardUrisSupplier> CREATOR =
262                 new Parcelable.Creator<StandardUrisSupplier>() {
263 
264                     @Override
265                     public StandardUrisSupplier createFromParcel(Parcel source) {
266                         return new StandardUrisSupplier(source);
267                     }
268 
269                     @Override
270                     public StandardUrisSupplier[] newArray(int size) {
271                         return new StandardUrisSupplier[size];
272                     }
273                 };
274     }
275 }
276