Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 1 | /* |
| 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 | |
| 17 | package com.android.documentsui.archives; |
| 18 | |
| 19 | import android.content.Context; |
| 20 | import android.content.res.AssetFileDescriptor; |
| 21 | import android.database.Cursor; |
| 22 | import android.database.MatrixCursor; |
| 23 | import android.graphics.Point; |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 24 | import android.net.Uri; |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 25 | import android.os.CancellationSignal; |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 26 | import android.os.ParcelFileDescriptor; |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 27 | import android.provider.DocumentsContract.Document; |
felkachang | 1556c36 | 2018-08-30 16:21:59 +0800 | [diff] [blame^] | 28 | import android.provider.MetadataReader; |
Steve McKay | ccc18de | 2016-10-19 11:12:42 -0700 | [diff] [blame] | 29 | import android.system.ErrnoException; |
| 30 | import android.system.Os; |
| 31 | import android.system.OsConstants; |
| 32 | import android.text.TextUtils; |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 33 | import android.webkit.MimeTypeMap; |
| 34 | |
Jeff Sharkey | a4ff00f | 2018-07-09 14:57:51 -0600 | [diff] [blame] | 35 | import androidx.annotation.GuardedBy; |
felkachang | 1556c36 | 2018-08-30 16:21:59 +0800 | [diff] [blame^] | 36 | import androidx.annotation.Nullable; |
Jeff Sharkey | 00a12bf | 2018-07-09 16:48:45 -0600 | [diff] [blame] | 37 | import androidx.core.util.Preconditions; |
Tomasz Mikolajewski | 977cf48 | 2016-10-12 14:51:39 +0900 | [diff] [blame] | 38 | |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 39 | import java.io.Closeable; |
| 40 | import java.io.File; |
| 41 | import java.io.FileNotFoundException; |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 42 | import java.util.HashMap; |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 43 | import java.util.List; |
| 44 | import java.util.Locale; |
| 45 | import java.util.Map; |
felkachang | 1556c36 | 2018-08-30 16:21:59 +0800 | [diff] [blame^] | 46 | |
| 47 | import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 48 | |
| 49 | /** |
| 50 | * Provides basic implementation for creating, extracting and accessing |
| 51 | * files within archives exposed by a document provider. |
| 52 | * |
| 53 | * <p>This class is thread safe. |
| 54 | */ |
Tomasz Mikolajewski | d683f97 | 2017-01-24 18:54:42 +0900 | [diff] [blame] | 55 | public abstract class Archive implements Closeable { |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 56 | private static final String TAG = "Archive"; |
| 57 | |
| 58 | public static final String[] DEFAULT_PROJECTION = new String[] { |
| 59 | Document.COLUMN_DOCUMENT_ID, |
| 60 | Document.COLUMN_DISPLAY_NAME, |
| 61 | Document.COLUMN_MIME_TYPE, |
| 62 | Document.COLUMN_SIZE, |
| 63 | Document.COLUMN_FLAGS |
| 64 | }; |
| 65 | |
Tomasz Mikolajewski | d683f97 | 2017-01-24 18:54:42 +0900 | [diff] [blame] | 66 | final Context mContext; |
| 67 | final Uri mArchiveUri; |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 68 | final int mAccessMode; |
Tomasz Mikolajewski | d683f97 | 2017-01-24 18:54:42 +0900 | [diff] [blame] | 69 | final Uri mNotificationUri; |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 70 | |
| 71 | // The container as well as values are guarded by mEntries. |
| 72 | @GuardedBy("mEntries") |
felkachang | 1556c36 | 2018-08-30 16:21:59 +0800 | [diff] [blame^] | 73 | final Map<String, ZipArchiveEntry> mEntries; |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 74 | |
| 75 | // The container as well as values and elements of values are guarded by mEntries. |
| 76 | @GuardedBy("mEntries") |
felkachang | 1556c36 | 2018-08-30 16:21:59 +0800 | [diff] [blame^] | 77 | final Map<String, List<ZipArchiveEntry>> mTree; |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 78 | |
Tomasz Mikolajewski | d683f97 | 2017-01-24 18:54:42 +0900 | [diff] [blame] | 79 | Archive( |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 80 | Context context, |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 81 | Uri archiveUri, |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 82 | int accessMode, |
Tomasz Mikolajewski | d683f97 | 2017-01-24 18:54:42 +0900 | [diff] [blame] | 83 | @Nullable Uri notificationUri) { |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 84 | mContext = context; |
| 85 | mArchiveUri = archiveUri; |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 86 | mAccessMode = accessMode; |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 87 | mNotificationUri = notificationUri; |
Tomasz Mikolajewski | b33955b | 2016-11-30 16:08:04 +0900 | [diff] [blame] | 88 | |
Steve McKay | ccc18de | 2016-10-19 11:12:42 -0700 | [diff] [blame] | 89 | mTree = new HashMap<>(); |
Steve McKay | ccc18de | 2016-10-19 11:12:42 -0700 | [diff] [blame] | 90 | mEntries = new HashMap<>(); |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 91 | } |
| 92 | |
| 93 | /** |
Tomasz Mikolajewski | 24c29fc | 2016-10-17 10:34:53 +0900 | [diff] [blame] | 94 | * Returns a valid, normalized path for an entry. |
| 95 | */ |
felkachang | 1556c36 | 2018-08-30 16:21:59 +0800 | [diff] [blame^] | 96 | public static String getEntryPath(ZipArchiveEntry entry) { |
Tomasz Mikolajewski | 24c29fc | 2016-10-17 10:34:53 +0900 | [diff] [blame] | 97 | Preconditions.checkArgument(entry.isDirectory() == entry.getName().endsWith("/"), |
| 98 | "Ill-formated ZIP-file."); |
| 99 | if (entry.getName().startsWith("/")) { |
| 100 | return entry.getName(); |
| 101 | } else { |
| 102 | return "/" + entry.getName(); |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | /** |
Tomasz Mikolajewski | 977cf48 | 2016-10-12 14:51:39 +0900 | [diff] [blame] | 107 | * Returns true if the file descriptor is seekable. |
| 108 | * @param descriptor File descriptor to check. |
| 109 | */ |
| 110 | public static boolean canSeek(ParcelFileDescriptor descriptor) { |
| 111 | try { |
| 112 | return Os.lseek(descriptor.getFileDescriptor(), 0, |
Daichi Hirono | fefc600 | 2016-12-15 12:36:00 +0900 | [diff] [blame] | 113 | OsConstants.SEEK_CUR) == 0; |
Tomasz Mikolajewski | 977cf48 | 2016-10-12 14:51:39 +0900 | [diff] [blame] | 114 | } catch (ErrnoException e) { |
| 115 | return false; |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | /** |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 120 | * Lists child documents of an archive or a directory within an |
| 121 | * archive. Must be called only for archives with supported mime type, |
| 122 | * or for documents within archives. |
| 123 | * |
| 124 | * @see DocumentsProvider.queryChildDocuments(String, String[], String) |
| 125 | */ |
| 126 | public Cursor queryChildDocuments(String documentId, @Nullable String[] projection, |
| 127 | @Nullable String sortOrder) throws FileNotFoundException { |
| 128 | final ArchiveId parsedParentId = ArchiveId.fromDocumentId(documentId); |
| 129 | MorePreconditions.checkArgumentEquals(mArchiveUri, parsedParentId.mArchiveUri, |
| 130 | "Mismatching archive Uri. Expected: %s, actual: %s."); |
| 131 | |
| 132 | final MatrixCursor result = new MatrixCursor( |
| 133 | projection != null ? projection : DEFAULT_PROJECTION); |
| 134 | if (mNotificationUri != null) { |
| 135 | result.setNotificationUri(mContext.getContentResolver(), mNotificationUri); |
| 136 | } |
| 137 | |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 138 | synchronized (mEntries) { |
felkachang | 1556c36 | 2018-08-30 16:21:59 +0800 | [diff] [blame^] | 139 | final List<ZipArchiveEntry> parentList = mTree.get(parsedParentId.mPath); |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 140 | if (parentList == null) { |
| 141 | throw new FileNotFoundException(); |
| 142 | } |
felkachang | 1556c36 | 2018-08-30 16:21:59 +0800 | [diff] [blame^] | 143 | for (final ZipArchiveEntry entry : parentList) { |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 144 | addCursorRow(result, entry); |
| 145 | } |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 146 | } |
| 147 | return result; |
| 148 | } |
| 149 | |
| 150 | /** |
| 151 | * Returns a MIME type of a document within an archive. |
| 152 | * |
| 153 | * @see DocumentsProvider.getDocumentType(String) |
| 154 | */ |
| 155 | public String getDocumentType(String documentId) throws FileNotFoundException { |
| 156 | final ArchiveId parsedId = ArchiveId.fromDocumentId(documentId); |
| 157 | MorePreconditions.checkArgumentEquals(mArchiveUri, parsedId.mArchiveUri, |
| 158 | "Mismatching archive Uri. Expected: %s, actual: %s."); |
| 159 | |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 160 | synchronized (mEntries) { |
felkachang | 1556c36 | 2018-08-30 16:21:59 +0800 | [diff] [blame^] | 161 | final ZipArchiveEntry entry = mEntries.get(parsedId.mPath); |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 162 | if (entry == null) { |
| 163 | throw new FileNotFoundException(); |
| 164 | } |
| 165 | return getMimeTypeForEntry(entry); |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 166 | } |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 167 | } |
| 168 | |
| 169 | /** |
| 170 | * Returns true if a document within an archive is a child or any descendant of the archive |
| 171 | * document or another document within the archive. |
| 172 | * |
| 173 | * @see DocumentsProvider.isChildDocument(String, String) |
| 174 | */ |
| 175 | public boolean isChildDocument(String parentDocumentId, String documentId) { |
| 176 | final ArchiveId parsedParentId = ArchiveId.fromDocumentId(parentDocumentId); |
| 177 | final ArchiveId parsedId = ArchiveId.fromDocumentId(documentId); |
| 178 | MorePreconditions.checkArgumentEquals(mArchiveUri, parsedParentId.mArchiveUri, |
| 179 | "Mismatching archive Uri. Expected: %s, actual: %s."); |
| 180 | |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 181 | synchronized (mEntries) { |
felkachang | 1556c36 | 2018-08-30 16:21:59 +0800 | [diff] [blame^] | 182 | final ZipArchiveEntry entry = mEntries.get(parsedId.mPath); |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 183 | if (entry == null) { |
| 184 | return false; |
| 185 | } |
| 186 | |
felkachang | 1556c36 | 2018-08-30 16:21:59 +0800 | [diff] [blame^] | 187 | final ZipArchiveEntry parentEntry = mEntries.get(parsedParentId.mPath); |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 188 | if (parentEntry == null || !parentEntry.isDirectory()) { |
| 189 | return false; |
| 190 | } |
| 191 | |
| 192 | // Add a trailing slash even if it's not a directory, so it's easy to check if the |
| 193 | // entry is a descendant. |
| 194 | String pathWithSlash = entry.isDirectory() ? getEntryPath(entry) |
| 195 | : getEntryPath(entry) + "/"; |
| 196 | |
| 197 | return pathWithSlash.startsWith(parsedParentId.mPath) && |
| 198 | !parsedParentId.mPath.equals(pathWithSlash); |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 199 | } |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 200 | } |
| 201 | |
| 202 | /** |
| 203 | * Returns metadata of a document within an archive. |
| 204 | * |
| 205 | * @see DocumentsProvider.queryDocument(String, String[]) |
| 206 | */ |
| 207 | public Cursor queryDocument(String documentId, @Nullable String[] projection) |
| 208 | throws FileNotFoundException { |
| 209 | final ArchiveId parsedId = ArchiveId.fromDocumentId(documentId); |
| 210 | MorePreconditions.checkArgumentEquals(mArchiveUri, parsedId.mArchiveUri, |
| 211 | "Mismatching archive Uri. Expected: %s, actual: %s."); |
| 212 | |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 213 | synchronized (mEntries) { |
felkachang | 1556c36 | 2018-08-30 16:21:59 +0800 | [diff] [blame^] | 214 | final ZipArchiveEntry entry = mEntries.get(parsedId.mPath); |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 215 | if (entry == null) { |
| 216 | throw new FileNotFoundException(); |
| 217 | } |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 218 | |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 219 | final MatrixCursor result = new MatrixCursor( |
| 220 | projection != null ? projection : DEFAULT_PROJECTION); |
| 221 | if (mNotificationUri != null) { |
| 222 | result.setNotificationUri(mContext.getContentResolver(), mNotificationUri); |
| 223 | } |
| 224 | addCursorRow(result, entry); |
| 225 | return result; |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 226 | } |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 227 | } |
| 228 | |
| 229 | /** |
| 230 | * Creates a file within an archive. |
| 231 | * |
| 232 | * @see DocumentsProvider.createDocument(String, String, String)) |
| 233 | */ |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 234 | public String createDocument(String parentDocumentId, String mimeType, String displayName) |
| 235 | throws FileNotFoundException { |
| 236 | throw new UnsupportedOperationException("Creating documents not supported."); |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 237 | } |
| 238 | |
| 239 | /** |
| 240 | * Opens a file within an archive. |
| 241 | * |
| 242 | * @see DocumentsProvider.openDocument(String, String, CancellationSignal)) |
| 243 | */ |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 244 | public ParcelFileDescriptor openDocument( |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 245 | String documentId, String mode, @Nullable final CancellationSignal signal) |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 246 | throws FileNotFoundException { |
Tomasz Mikolajewski | 5ed6983 | 2016-11-30 17:43:57 +0900 | [diff] [blame] | 247 | throw new UnsupportedOperationException("Opening not supported."); |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 248 | } |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 249 | |
| 250 | /** |
| 251 | * Opens a thumbnail of a file within an archive. |
| 252 | * |
| 253 | * @see DocumentsProvider.openDocumentThumbnail(String, Point, CancellationSignal)) |
| 254 | */ |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 255 | public AssetFileDescriptor openDocumentThumbnail( |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 256 | String documentId, Point sizeHint, final CancellationSignal signal) |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 257 | throws FileNotFoundException { |
| 258 | throw new UnsupportedOperationException("Thumbnails not supported."); |
| 259 | } |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 260 | |
Tomasz Mikolajewski | d683f97 | 2017-01-24 18:54:42 +0900 | [diff] [blame] | 261 | /** |
| 262 | * Creates an archive id for the passed path. |
| 263 | */ |
| 264 | public ArchiveId createArchiveId(String path) { |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 265 | return new ArchiveId(mArchiveUri, mAccessMode, path); |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 266 | } |
| 267 | |
Tomasz Mikolajewski | a903c2c | 2017-01-25 15:07:31 +0900 | [diff] [blame] | 268 | /** |
| 269 | * Not thread safe. |
| 270 | */ |
felkachang | 1556c36 | 2018-08-30 16:21:59 +0800 | [diff] [blame^] | 271 | void addCursorRow(MatrixCursor cursor, ZipArchiveEntry entry) { |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 272 | final MatrixCursor.RowBuilder row = cursor.newRow(); |
Tomasz Mikolajewski | d683f97 | 2017-01-24 18:54:42 +0900 | [diff] [blame] | 273 | final ArchiveId parsedId = createArchiveId(getEntryPath(entry)); |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 274 | row.add(Document.COLUMN_DOCUMENT_ID, parsedId.toDocumentId()); |
| 275 | |
| 276 | final File file = new File(entry.getName()); |
| 277 | row.add(Document.COLUMN_DISPLAY_NAME, file.getName()); |
| 278 | row.add(Document.COLUMN_SIZE, entry.getSize()); |
| 279 | |
| 280 | final String mimeType = getMimeTypeForEntry(entry); |
| 281 | row.add(Document.COLUMN_MIME_TYPE, mimeType); |
| 282 | |
Steve McKay | 21b4a27 | 2017-07-20 11:40:24 -0700 | [diff] [blame] | 283 | int flags = mimeType.startsWith("image/") ? Document.FLAG_SUPPORTS_THUMBNAIL : 0; |
| 284 | if (MetadataReader.isSupportedMimeType(mimeType)) { |
| 285 | flags |= Document.FLAG_SUPPORTS_METADATA; |
| 286 | } |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 287 | row.add(Document.COLUMN_FLAGS, flags); |
| 288 | } |
| 289 | |
felkachang | 1556c36 | 2018-08-30 16:21:59 +0800 | [diff] [blame^] | 290 | static String getMimeTypeForEntry(ZipArchiveEntry entry) { |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 291 | if (entry.isDirectory()) { |
| 292 | return Document.MIME_TYPE_DIR; |
| 293 | } |
| 294 | |
| 295 | final int lastDot = entry.getName().lastIndexOf('.'); |
| 296 | if (lastDot >= 0) { |
| 297 | final String extension = entry.getName().substring(lastDot + 1).toLowerCase(Locale.US); |
| 298 | final String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); |
| 299 | if (mimeType != null) { |
| 300 | return mimeType; |
| 301 | } |
| 302 | } |
| 303 | |
| 304 | return "application/octet-stream"; |
| 305 | } |
| 306 | |
| 307 | // TODO: Upstream to the Preconditions class. |
Tomasz Mikolajewski | e731da6 | 2017-01-30 11:07:04 +0900 | [diff] [blame] | 308 | // TODO: Move to a separate file. |
| 309 | public static class MorePreconditions { |
Tomasz Mikolajewski | dbd6b8b | 2016-09-29 15:27:37 +0900 | [diff] [blame] | 310 | static void checkArgumentEquals(String expected, @Nullable String actual, |
| 311 | String message) { |
| 312 | if (!TextUtils.equals(expected, actual)) { |
| 313 | throw new IllegalArgumentException(String.format(message, |
| 314 | String.valueOf(expected), String.valueOf(actual))); |
| 315 | } |
| 316 | } |
| 317 | |
| 318 | static void checkArgumentEquals(Uri expected, @Nullable Uri actual, |
| 319 | String message) { |
| 320 | checkArgumentEquals(expected.toString(), actual.toString(), message); |
| 321 | } |
| 322 | } |
| 323 | }; |