184769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay/* 284769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay * Copyright (C) 2016 The Android Open Source Project 384769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay * 484769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay * Licensed under the Apache License, Version 2.0 (the "License"); 584769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay * you may not use this file except in compliance with the License. 684769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay * You may obtain a copy of the License at 784769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay * 884769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay * http://www.apache.org/licenses/LICENSE-2.0 984769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay * 1084769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay * Unless required by applicable law or agreed to in writing, software 1184769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay * distributed under the License is distributed on an "AS IS" BASIS, 1284769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1384769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay * See the License for the specific language governing permissions and 1484769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay * limitations under the License. 1584769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay */ 1684769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay 179666ce691a4d45344460f17cec3577dbe075235aGarfield, Tanpackage com.android.documentsui.clipping; 1884769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay 19b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tanimport android.content.SharedPreferences; 2084769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKayimport android.net.Uri; 21edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tanimport android.os.AsyncTask; 2284769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKayimport android.support.annotation.VisibleForTesting; 23b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tanimport android.system.ErrnoException; 24b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tanimport android.system.Os; 25edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tanimport android.util.Log; 2684769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay 27d9caa6ab53aa784acaf241c0ded3c4ae2d342bf8Steve McKayimport com.android.documentsui.base.Files; 289666ce691a4d45344460f17cec3577dbe075235aGarfield, Tan 2984769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKayimport java.io.Closeable; 3084769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKayimport java.io.File; 3184769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKayimport java.io.FileOutputStream; 3284769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKayimport java.io.IOException; 33edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tanimport java.nio.channels.FileLock; 34b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tanimport java.util.concurrent.TimeUnit; 3584769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay 3684769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay/** 3784769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay * Provides support for storing lists of documents identified by Uri. 3884769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay * 39b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan * This class uses a ring buffer to recycle clip file slots, to mitigate the issue of clip file 4064936a7f5027a8592d227a3d058fd97b4d134dc1Garfield, Tan * deletions. Below is the directory layout: 4164936a7f5027a8592d227a3d058fd97b4d134dc1Garfield, Tan * [cache dir] 4264936a7f5027a8592d227a3d058fd97b4d134dc1Garfield, Tan * - [dir] 1 4364936a7f5027a8592d227a3d058fd97b4d134dc1Garfield, Tan * - [dir] 2 4464936a7f5027a8592d227a3d058fd97b4d134dc1Garfield, Tan * - ... to {@link #NUM_OF_SLOTS} 4564936a7f5027a8592d227a3d058fd97b4d134dc1Garfield, Tan * When a clip data is actively being used: 4664936a7f5027a8592d227a3d058fd97b4d134dc1Garfield, Tan * [cache dir] 4764936a7f5027a8592d227a3d058fd97b4d134dc1Garfield, Tan * - [dir] 1 4864936a7f5027a8592d227a3d058fd97b4d134dc1Garfield, Tan * - [file] primary 4964936a7f5027a8592d227a3d058fd97b4d134dc1Garfield, Tan * - [symlink] 1 > primary # copying to location X 5064936a7f5027a8592d227a3d058fd97b4d134dc1Garfield, Tan * - [symlink] 2 > primary # copying to location Y 5184769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay */ 52c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKaypublic final class ClipStorage implements ClipStore { 5384769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay 54b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan public static final int NO_SELECTION_TAG = -1; 55b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan 569666ce691a4d45344460f17cec3577dbe075235aGarfield, Tan public static final String PREF_NAME = "ClipStoragePref"; 57b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan 58b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan @VisibleForTesting 59b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan static final int NUM_OF_SLOTS = 20; 60b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan 61edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan private static final String TAG = "ClipStorage"; 62edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan 63b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan private static final long STALENESS_THRESHOLD = TimeUnit.DAYS.toMillis(2); 64b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan 65c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay private static final String NEXT_AVAIL_SLOT = "NextAvailableSlot"; 66b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan private static final String PRIMARY_DATA_FILE_NAME = "primary"; 67b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan 6884769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay private static final byte[] LINE_SEPARATOR = System.lineSeparator().getBytes(); 6984769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay 7084769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay private final File mOutDir; 71b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan private final SharedPreferences mPref; 72b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan 73b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan private final File[] mSlots = new File[NUM_OF_SLOTS]; 74c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay private int mNextSlot; 7584769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay 7684769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay /** 7784769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay * @param outDir see {@link #prepareStorage(File)}. 7884769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay */ 79b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan public ClipStorage(File outDir, SharedPreferences pref) { 8084769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay assert(outDir.isDirectory()); 8184769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay mOutDir = outDir; 82b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan mPref = pref; 83b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan 84c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay mNextSlot = mPref.getInt(NEXT_AVAIL_SLOT, 0); 8584769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay } 8684769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay 8784769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay /** 88b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan * Tries to get the next available clip slot. It's guaranteed to return one. If none of 89b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan * slots is available, it returns the next slot of the most recently returned slot by this 90b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan * method. 9184769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay * 92b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan * <p>This is not a perfect solution, but should be enough for most regular use. There are 93b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan * several situations this method may not work: 94b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan * <ul> 95b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan * <li>Making {@link #NUM_OF_SLOTS} - 1 times of large drag and drop or moveTo/copyTo/delete 96b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan * operations after cutting a primary clip, then the primary clip is overwritten.</li> 97b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan * <li>Having more than {@link #NUM_OF_SLOTS} queued jumbo file operations, one or more clip 98b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan * file may be overwritten.</li> 99b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan * </ul> 100c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay * 101c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay * Implementations should take caution to serialize access. 10284769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay */ 103c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay @VisibleForTesting 1049666ce691a4d45344460f17cec3577dbe075235aGarfield, Tan synchronized int claimStorageSlot() { 105c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay int curSlot = mNextSlot; 106c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay for (int i = 0; i < NUM_OF_SLOTS; ++i, curSlot = (curSlot + 1) % NUM_OF_SLOTS) { 107c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay createSlotFileObject(curSlot); 108b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan 109c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay if (!mSlots[curSlot].exists()) { 110b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan break; 111b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan } 112b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan 113b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan // No file or only primary file exists, we deem it available. 114c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay if (mSlots[curSlot].list().length <= 1) { 115b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan break; 116b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan } 117b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan // This slot doesn't seem available, but still need to check if it's a legacy of 118b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan // service being killed or a service crash etc. If it's stale, it's available. 119c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay else if (checkStaleFiles(curSlot)) { 120b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan break; 121b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan } 122b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan } 123b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan 124c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay prepareSlot(curSlot); 125b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan 126c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay mNextSlot = (curSlot + 1) % NUM_OF_SLOTS; 127c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay mPref.edit().putInt(NEXT_AVAIL_SLOT, mNextSlot).commit(); 128c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay return curSlot; 129b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan } 130b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan 131b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan private boolean checkStaleFiles(int pos) { 132b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan File slotData = toSlotDataFile(pos); 133b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan 134b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan // No need to check if the file exists. File.lastModified() returns 0L if the file doesn't 135b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan // exist. 136b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan return slotData.lastModified() + STALENESS_THRESHOLD <= System.currentTimeMillis(); 137b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan } 138b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan 139b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan private void prepareSlot(int pos) { 140b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan assert(mSlots[pos] != null); 141b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan 142b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan Files.deleteRecursively(mSlots[pos]); 143b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan mSlots[pos].mkdir(); 144b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan assert(mSlots[pos].isDirectory()); 14584769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay } 14684769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay 14784769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay /** 148edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan * Returns a writer. Callers must close the writer when finished. 14984769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay */ 150c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay private Writer createWriter(int slot) throws IOException { 151c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay File file = toSlotDataFile(slot); 152edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan return new Writer(file); 15384769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay } 15484769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay 155c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay @Override 156c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay public synchronized File getFile(int slot) throws IOException { 157c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay createSlotFileObject(slot); 158b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan 159c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay File primary = toSlotDataFile(slot); 160b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan 161c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay String linkFileName = Integer.toString(mSlots[slot].list().length); 162c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay File link = new File(mSlots[slot], linkFileName); 163b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan 164b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan try { 165b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan Os.symlink(primary.getAbsolutePath(), link.getAbsolutePath()); 166b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan } catch (ErrnoException e) { 167b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan e.rethrowAsIOException(); 168b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan } 169b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan return link; 170b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan } 171b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan 172c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay @Override 173c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay public ClipStorageReader createReader(File file) throws IOException { 174b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan assert(file.getParentFile().getParentFile().equals(mOutDir)); 1759666ce691a4d45344460f17cec3577dbe075235aGarfield, Tan return new ClipStorageReader(file); 17684769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay } 17784769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay 178b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan private File toSlotDataFile(int pos) { 179b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan assert(mSlots[pos] != null); 180b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan return new File(mSlots[pos], PRIMARY_DATA_FILE_NAME); 18184769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay } 18284769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay 18364936a7f5027a8592d227a3d058fd97b4d134dc1Garfield, Tan private void createSlotFileObject(int pos) { 184b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan if (mSlots[pos] == null) { 185b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan mSlots[pos] = new File(mOutDir, Integer.toString(pos)); 186b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan } 18784769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay } 18884769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay 189edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan /** 190edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan * Provides initialization of the clip data storage directory. 191edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan */ 1929666ce691a4d45344460f17cec3577dbe075235aGarfield, Tan public static File prepareStorage(File cacheDir) { 193edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan File clipDir = getClipDir(cacheDir); 194edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan clipDir.mkdir(); 195edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan 196edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan assert(clipDir.isDirectory()); 197edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan return clipDir; 198edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan } 199edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan 200edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan private static File getClipDir(File cacheDir) { 201edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan return new File(cacheDir, "clippings"); 202edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan } 203edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan 204c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay public static final class Writer implements Closeable { 20584769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay 20684769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay private final FileOutputStream mOut; 207edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan private final FileLock mLock; 208edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan 209edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan private Writer(File file) throws IOException { 210b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan assert(!file.exists()); 211b7e5f6b55f5075e4752bd94586211fae9af2bae0Garfield, Tan 212edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan mOut = new FileOutputStream(file); 21384769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay 214edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan // Lock the file here so copy tasks would wait until everything is flushed to disk 215edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan // before start to run. 216edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan mLock = mOut.getChannel().lock(); 21784769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay } 21884769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay 21984769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay public void write(Uri uri) throws IOException { 22084769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay mOut.write(uri.toString().getBytes()); 22184769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay mOut.write(LINE_SEPARATOR); 22284769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay } 22384769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay 22484769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay @Override 22584769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay public void close() throws IOException { 226edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan if (mLock != null) { 227edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan mLock.release(); 228edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan } 229edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan 230edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan if (mOut != null) { 231edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan mOut.close(); 232edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan } 23384769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay } 23484769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay } 23584769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay 236c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay @Override 237c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay public int persistUris(Iterable<Uri> uris) { 238c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay int slot = claimStorageSlot(); 239c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay persistUris(uris, slot); 240c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay return slot; 241c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay } 242c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay 243c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay @VisibleForTesting 244c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay void persistUris(Iterable<Uri> uris, int slot) { 245c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay new PersistTask(this, uris, slot).execute(); 246c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay } 247c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay 24884769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay /** 249edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan * An {@link AsyncTask} that persists doc uris in {@link ClipStorage}. 25084769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay */ 251c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay private static final class PersistTask extends AsyncTask<Void, Void, Void> { 252edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan 253c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay private final ClipStorage mClipStore; 254edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan private final Iterable<Uri> mUris; 255c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay private final int mSlot; 256edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan 257c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay PersistTask(ClipStorage clipStore, Iterable<Uri> uris, int slot) { 258c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay mClipStore = clipStore; 259edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan mUris = uris; 260c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay mSlot = slot; 261edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan } 262edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan 263edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan @Override 264edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan protected Void doInBackground(Void... params) { 265c8889af6757ecb348fb8fd6dadf84d67d3478cbfSteve McKay try(Writer writer = mClipStore.createWriter(mSlot)){ 266edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan for (Uri uri: mUris) { 267edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan assert(uri != null); 268edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan writer.write(uri); 269edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan } 270edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan } catch (IOException e) { 271edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan Log.e(TAG, "Caught exception trying to write jumbo clip to disk.", e); 272edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan } 273edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan 274edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan return null; 27584769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay } 27684769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay } 27784769b8205cf4a100c9d4f65c41cf72ba7a3b40fSteve McKay} 278