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 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 */ 58 public Iterable<Uri> getUris(Context context) throws IOException { 59 return getUris(DocumentsApplication.getClipStore(context)); 60 } 61 62 @VisibleForTesting 63 abstract Iterable<Uri> getUris(ClipStore storage) throws IOException; 64 65 public void dispose() {} 66 67 @Override 68 public int describeContents() { 69 return 0; 70 } 71 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 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 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 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 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 136 public int getItemCount() { 137 return mSelectionSize; 138 } 139 140 @Override 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 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 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 179 public void writeToParcel(Parcel dest, int flags) { 180 dest.writeString(mFile.getAbsolutePath()); 181 dest.writeInt(mSelectionSize); 182 } 183 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 212 private StandardUrisSupplier(ClipData clipData) { 213 mDocs = listDocs(clipData); 214 } 215 216 @VisibleForTesting 217 public StandardUrisSupplier(List<Uri> docs) { 218 mDocs = docs; 219 } 220 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 234 public int getItemCount() { 235 return mDocs.size(); 236 } 237 238 @Override 239 Iterable<Uri> getUris(ClipStore storage) { 240 return mDocs; 241 } 242 243 @Override 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 253 public void writeToParcel(Parcel dest, int flags) { 254 dest.writeTypedList(mDocs); 255 } 256 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