1dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey/*
2dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey * Copyright (C) 2014 The Android Open Source Project
3dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey *
4dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey * Licensed under the Apache License, Version 2.0 (the "License");
5dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey * you may not use this file except in compliance with the License.
6dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey * You may obtain a copy of the License at
7dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey *
8dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey *      http://www.apache.org/licenses/LICENSE-2.0
9dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey *
10dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey * Unless required by applicable law or agreed to in writing, software
11dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey * distributed under the License is distributed on an "AS IS" BASIS,
12dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey * See the License for the specific language governing permissions and
14dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey * limitations under the License.
15dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey */
16dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
17dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeypackage com.android.providers.downloads;
18dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
19dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport static android.net.TrafficStats.MB_IN_BYTES;
20dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport static android.provider.Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR;
21dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport static android.text.format.DateUtils.DAY_IN_MILLIS;
22dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport static com.android.providers.downloads.Constants.TAG;
23dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
24dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport android.app.DownloadManager;
25dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport android.content.ContentResolver;
26dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport android.content.ContentUris;
27dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport android.content.Context;
28dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport android.content.pm.IPackageDataObserver;
29dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport android.content.pm.PackageManager;
30dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport android.database.Cursor;
31dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport android.os.Environment;
32dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport android.provider.Downloads;
335dcbf701c9077e62ca8c1ee0079b4cbffaf57d14Elliott Hughesimport android.system.ErrnoException;
345dcbf701c9077e62ca8c1ee0079b4cbffaf57d14Elliott Hughesimport android.system.Os;
355dcbf701c9077e62ca8c1ee0079b4cbffaf57d14Elliott Hughesimport android.system.StructStat;
365dcbf701c9077e62ca8c1ee0079b4cbffaf57d14Elliott Hughesimport android.system.StructStatVfs;
37dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport android.text.TextUtils;
38dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport android.util.Slog;
39dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
40dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport com.android.internal.annotations.VisibleForTesting;
41dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport com.google.android.collect.Lists;
42dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport com.google.android.collect.Sets;
43dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
44dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport libcore.io.IoUtils;
45dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
46dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport java.io.File;
47dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport java.io.FileDescriptor;
48dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport java.io.IOException;
49dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport java.util.ArrayList;
50dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport java.util.Collections;
51dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport java.util.Comparator;
52dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport java.util.HashSet;
53dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport java.util.LinkedList;
54dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport java.util.List;
55dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport java.util.Objects;
56dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport java.util.concurrent.CountDownLatch;
57dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeyimport java.util.concurrent.TimeUnit;
58dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
59dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey/**
60dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey * Utility methods for managing storage space related to
61dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey * {@link DownloadManager}.
62dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey */
63dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkeypublic class StorageUtils {
64dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
65dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey    /**
66dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     * Minimum age for a file to be considered for deletion.
67dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     */
68dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey    static final long MIN_DELETE_AGE = DAY_IN_MILLIS;
69dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
70dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey    /**
71dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     * Reserved disk space to avoid filling disk.
72dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     */
73dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey    static final long RESERVED_BYTES = 32 * MB_IN_BYTES;
74dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
75dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey    @VisibleForTesting
76dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey    static boolean sForceFullEviction = false;
77dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
78dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey    /**
79dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     * Ensure that requested free space exists on the partition backing the
80dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     * given {@link FileDescriptor}. If not enough space is available, it tries
81dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     * freeing up space as follows:
82dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     * <ul>
83dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     * <li>If backed by the data partition (including emulated external
84dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     * storage), then ask {@link PackageManager} to free space from cache
85dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     * directories.
86dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     * <li>If backed by the cache partition, then try deleting older downloads
87dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     * to free space.
88dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     * </ul>
89dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     */
90dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey    public static void ensureAvailableSpace(Context context, FileDescriptor fd, long bytes)
91dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            throws IOException, StopRequestException {
92dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
93dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        long availBytes = getAvailableBytes(fd);
94dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        if (availBytes >= bytes) {
95dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            // Underlying partition has enough space; go ahead
96dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            return;
97dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        }
98dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
99dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        // Not enough space, let's try freeing some up. Start by tracking down
100dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        // the backing partition.
101dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        final long dev;
102dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        try {
1035dcbf701c9077e62ca8c1ee0079b4cbffaf57d14Elliott Hughes            dev = Os.fstat(fd).st_dev;
104dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        } catch (ErrnoException e) {
105dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            throw e.rethrowAsIOException();
106dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        }
107dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
108dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        final long dataDev = getDeviceId(Environment.getDataDirectory());
109dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        final long cacheDev = getDeviceId(Environment.getDownloadCacheDirectory());
110dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        final long externalDev = getDeviceId(Environment.getExternalStorageDirectory());
111dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
112dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        if (dev == dataDev || (dev == externalDev && Environment.isExternalStorageEmulated())) {
113dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            // File lives on internal storage; ask PackageManager to try freeing
114dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            // up space from cache directories.
115dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            final PackageManager pm = context.getPackageManager();
116dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            final ObserverLatch observer = new ObserverLatch();
117dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            pm.freeStorageAndNotify(sForceFullEviction ? Long.MAX_VALUE : bytes, observer);
118dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
119dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            try {
120dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                if (!observer.latch.await(30, TimeUnit.SECONDS)) {
121dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                    throw new IOException("Timeout while freeing disk space");
122dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                }
123dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            } catch (InterruptedException e) {
124dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                Thread.currentThread().interrupt();
125dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            }
126dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
127dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        } else if (dev == cacheDev) {
128dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            // Try removing old files on cache partition
129dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            freeCacheStorage(bytes);
130dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        }
131dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
132dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        // Did we free enough space?
133dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        availBytes = getAvailableBytes(fd);
134dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        if (availBytes < bytes) {
135dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            throw new StopRequestException(STATUS_INSUFFICIENT_SPACE_ERROR,
136dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                    "Not enough free space; " + bytes + " requested, " + availBytes + " available");
137dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        }
138dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey    }
139dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
140dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey    /**
141dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     * Free requested space on cache partition, deleting oldest files first.
142dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     * We're only focused on freeing up disk space, and rely on the next orphan
143dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     * pass to clean up database entries.
144dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     */
145dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey    private static void freeCacheStorage(long bytes) {
146dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        // Only consider finished downloads
147dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        final List<ConcreteFile> files = listFilesRecursive(
148dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                Environment.getDownloadCacheDirectory(), Constants.DIRECTORY_CACHE_RUNNING,
149dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                android.os.Process.myUid());
150dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
151dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        Slog.d(TAG, "Found " + files.size() + " downloads on cache");
152dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
153dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        Collections.sort(files, new Comparator<ConcreteFile>() {
154dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            @Override
155dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            public int compare(ConcreteFile lhs, ConcreteFile rhs) {
156dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                return (int) (lhs.file.lastModified() - rhs.file.lastModified());
157dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            }
158dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        });
159dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
160dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        final long now = System.currentTimeMillis();
161dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        for (ConcreteFile file : files) {
162dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            if (bytes <= 0) break;
163dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
164dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            if (now - file.file.lastModified() < MIN_DELETE_AGE) {
165dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                Slog.d(TAG, "Skipping recently modified " + file.file);
166dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            } else {
167dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                final long len = file.file.length();
168dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                Slog.d(TAG, "Deleting " + file.file + " to reclaim " + len);
169dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                bytes -= len;
170dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                file.file.delete();
171dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            }
172dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        }
173dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey    }
174dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
175dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey    /**
176dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     * Return number of available bytes on the filesystem backing the given
177dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     * {@link FileDescriptor}, minus any {@link #RESERVED_BYTES} buffer.
178dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     */
179dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey    private static long getAvailableBytes(FileDescriptor fd) throws IOException {
180dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        try {
1815dcbf701c9077e62ca8c1ee0079b4cbffaf57d14Elliott Hughes            final StructStatVfs stat = Os.fstatvfs(fd);
182dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            return (stat.f_bavail * stat.f_bsize) - RESERVED_BYTES;
183dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        } catch (ErrnoException e) {
184dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            throw e.rethrowAsIOException();
185dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        }
186dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey    }
187dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
188dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey    private static long getDeviceId(File file) {
189dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        try {
1905dcbf701c9077e62ca8c1ee0079b4cbffaf57d14Elliott Hughes            return Os.stat(file.getAbsolutePath()).st_dev;
191dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        } catch (ErrnoException e) {
192dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            // Safe since dev_t is uint
193dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            return -1;
194dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        }
195dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey    }
196dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
197dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey    /**
198dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     * Return list of all normal files under the given directory, traversing
199dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     * directories recursively.
200dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     *
201dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     * @param exclude ignore dirs with this name, or {@code null} to ignore.
202dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     * @param uid only return files owned by this UID, or {@code -1} to ignore.
203dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     */
2041f2c2c560400ba60c5b9dfd6fd4f5e73b232803aJeff Sharkey    static List<ConcreteFile> listFilesRecursive(File startDir, String exclude, int uid) {
205dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        final ArrayList<ConcreteFile> files = Lists.newArrayList();
206dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        final LinkedList<File> dirs = new LinkedList<File>();
207dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        dirs.add(startDir);
208dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        while (!dirs.isEmpty()) {
209dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            final File dir = dirs.removeFirst();
210dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            if (Objects.equals(dir.getName(), exclude)) continue;
211dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
212dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            final File[] children = dir.listFiles();
213dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            if (children == null) continue;
214dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
215dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            for (File child : children) {
216dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                if (child.isDirectory()) {
217dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                    dirs.add(child);
218dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                } else if (child.isFile()) {
219dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                    try {
220dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                        final ConcreteFile file = new ConcreteFile(child);
221dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                        if (uid == -1 || file.stat.st_uid == uid) {
222dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                            files.add(file);
223dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                        }
224dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                    } catch (ErrnoException ignored) {
225dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                    }
226dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                }
227dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            }
228dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        }
229dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        return files;
230dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey    }
231dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
232dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey    /**
233dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     * Concrete file on disk that has a backing device and inode. Faster than
234dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     * {@code realpath()} when looking for identical files.
235dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey     */
2361f2c2c560400ba60c5b9dfd6fd4f5e73b232803aJeff Sharkey    static class ConcreteFile {
237dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        public final File file;
238dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        public final StructStat stat;
239dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
240dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        public ConcreteFile(File file) throws ErrnoException {
241dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            this.file = file;
2425dcbf701c9077e62ca8c1ee0079b4cbffaf57d14Elliott Hughes            this.stat = Os.lstat(file.getAbsolutePath());
243dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        }
244dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
245dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        @Override
246dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        public int hashCode() {
247dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            int result = 1;
248dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            result = 31 * result + (int) (stat.st_dev ^ (stat.st_dev >>> 32));
249dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            result = 31 * result + (int) (stat.st_ino ^ (stat.st_ino >>> 32));
250dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            return result;
251dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        }
252dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
253dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        @Override
254dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        public boolean equals(Object o) {
255dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            if (o instanceof ConcreteFile) {
256dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                final ConcreteFile f = (ConcreteFile) o;
257dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey                return (f.stat.st_dev == stat.st_dev) && (f.stat.st_ino == stat.st_ino);
258dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            }
259dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            return false;
260dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        }
261dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey    }
262dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
263dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey    static class ObserverLatch extends IPackageDataObserver.Stub {
264dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        public final CountDownLatch latch = new CountDownLatch(1);
265dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey
266dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        @Override
267dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        public void onRemoveCompleted(String packageName, boolean succeeded) {
268dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey            latch.countDown();
269dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey        }
270dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey    }
271dffbb9c4567e9d29d19964a83129e38dceab7055Jeff Sharkey}
272