1 /* 2 * Copyright (C) 2020 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 package android.scopedstorage.cts; 17 18 import static android.scopedstorage.cts.lib.RedactionTestHelper.EXIF_METADATA_QUERY; 19 import static android.scopedstorage.cts.lib.RedactionTestHelper.getExifMetadataFromFile; 20 import static android.scopedstorage.cts.lib.TestUtils.CAN_OPEN_FILE_FOR_READ_QUERY; 21 import static android.scopedstorage.cts.lib.TestUtils.CAN_OPEN_FILE_FOR_WRITE_QUERY; 22 import static android.scopedstorage.cts.lib.TestUtils.CAN_READ_WRITE_QUERY; 23 import static android.scopedstorage.cts.lib.TestUtils.CHECK_DATABASE_ROW_EXISTS_QUERY; 24 import static android.scopedstorage.cts.lib.TestUtils.CREATE_FILE_QUERY; 25 import static android.scopedstorage.cts.lib.TestUtils.CREATE_IMAGE_ENTRY_QUERY; 26 import static android.scopedstorage.cts.lib.TestUtils.DELETE_FILE_QUERY; 27 import static android.scopedstorage.cts.lib.TestUtils.DELETE_MEDIA_BY_URI_QUERY; 28 import static android.scopedstorage.cts.lib.TestUtils.DELETE_RECURSIVE_QUERY; 29 import static android.scopedstorage.cts.lib.TestUtils.FILE_EXISTS_QUERY; 30 import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXCEPTION; 31 import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_ARGS; 32 import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_CALLING_PKG; 33 import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_CONTENT; 34 import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_PATH; 35 import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_URI; 36 import static android.scopedstorage.cts.lib.TestUtils.IS_URI_REDACTED_VIA_FILEPATH; 37 import static android.scopedstorage.cts.lib.TestUtils.IS_URI_REDACTED_VIA_FILE_DESCRIPTOR_FOR_READ; 38 import static android.scopedstorage.cts.lib.TestUtils.IS_URI_REDACTED_VIA_FILE_DESCRIPTOR_FOR_WRITE; 39 import static android.scopedstorage.cts.lib.TestUtils.OPEN_FILE_FOR_READ_QUERY; 40 import static android.scopedstorage.cts.lib.TestUtils.OPEN_FILE_FOR_WRITE_QUERY; 41 import static android.scopedstorage.cts.lib.TestUtils.QUERY_MAX_ROW_ID; 42 import static android.scopedstorage.cts.lib.TestUtils.QUERY_MEDIA_BY_URI_QUERY; 43 import static android.scopedstorage.cts.lib.TestUtils.QUERY_MIN_ROW_ID; 44 import static android.scopedstorage.cts.lib.TestUtils.QUERY_OWNER_PACKAGE_NAMES; 45 import static android.scopedstorage.cts.lib.TestUtils.QUERY_TYPE; 46 import static android.scopedstorage.cts.lib.TestUtils.QUERY_URI; 47 import static android.scopedstorage.cts.lib.TestUtils.QUERY_WITH_ARGS; 48 import static android.scopedstorage.cts.lib.TestUtils.READDIR_QUERY; 49 import static android.scopedstorage.cts.lib.TestUtils.RENAME_FILE_PARAMS_SEPARATOR; 50 import static android.scopedstorage.cts.lib.TestUtils.RENAME_FILE_QUERY; 51 import static android.scopedstorage.cts.lib.TestUtils.SETATTR_QUERY; 52 import static android.scopedstorage.cts.lib.TestUtils.UPDATE_MEDIA_BY_URI_QUERY; 53 import static android.scopedstorage.cts.lib.TestUtils.canOpen; 54 import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively; 55 import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase; 56 import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri; 57 58 import static com.google.common.truth.Truth.assertThat; 59 60 import android.app.Activity; 61 import android.content.ContentResolver; 62 import android.content.ContentValues; 63 import android.content.Intent; 64 import android.database.Cursor; 65 import android.media.ExifInterface; 66 import android.net.Uri; 67 import android.os.Bundle; 68 import android.os.Environment; 69 import android.os.FileUtils; 70 import android.os.IBinder; 71 import android.os.Parcel; 72 import android.os.ParcelFileDescriptor; 73 import android.os.RemoteException; 74 import android.provider.MediaStore; 75 import android.util.Log; 76 77 import androidx.annotation.Nullable; 78 import androidx.core.content.FileProvider; 79 80 import com.google.common.base.Strings; 81 82 import java.io.File; 83 import java.io.FileDescriptor; 84 import java.io.FileInputStream; 85 import java.io.FileOutputStream; 86 import java.io.IOException; 87 import java.util.ArrayList; 88 import java.util.Collections; 89 import java.util.HashSet; 90 import java.util.Set; 91 import java.util.regex.Matcher; 92 import java.util.regex.Pattern; 93 94 /** 95 * Helper app for ScopedStorageTest. 96 * 97 * <p>Used to perform ScopedStorageTest functions as a different app. Based on the Query type 98 * app can perform different functions and send the result back to host app. 99 */ 100 public class ScopedStorageTestHelper extends Activity { 101 private static final String TAG = "ScopedStorageTestHelper"; 102 /** 103 * Regex that matches paths in all well-known package-specific directories, 104 * and which captures the directory type as the first group (data|media|obb) and the 105 * package name as the 2nd group. 106 */ 107 private static final Pattern PATTERN_OWNED_PATH = Pattern.compile( 108 "(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(data|media|obb)/([^/]+)(/?.*)?"); 109 110 @Override onCreate(Bundle savedInstanceState)111 public void onCreate(Bundle savedInstanceState) { 112 super.onCreate(savedInstanceState); 113 String queryType = getIntent().getStringExtra(QUERY_TYPE); 114 queryType = queryType == null ? "null" : queryType; 115 Intent returnIntent; 116 try { 117 switch (queryType) { 118 case READDIR_QUERY: 119 returnIntent = sendDirectoryEntries(queryType); 120 break; 121 case FILE_EXISTS_QUERY: 122 case CAN_READ_WRITE_QUERY: 123 case CREATE_FILE_QUERY: 124 case DELETE_FILE_QUERY: 125 case DELETE_RECURSIVE_QUERY: 126 case CAN_OPEN_FILE_FOR_READ_QUERY: 127 case CAN_OPEN_FILE_FOR_WRITE_QUERY: 128 case OPEN_FILE_FOR_READ_QUERY: 129 case OPEN_FILE_FOR_WRITE_QUERY: 130 case SETATTR_QUERY: 131 returnIntent = accessFile(queryType); 132 break; 133 case DELETE_MEDIA_BY_URI_QUERY: 134 returnIntent = deleteMediaByUri(queryType); 135 break; 136 case UPDATE_MEDIA_BY_URI_QUERY: 137 returnIntent = updateMediaByUri(queryType); 138 break; 139 case QUERY_MEDIA_BY_URI_QUERY: 140 returnIntent = queryMediaByUri(queryType); 141 break; 142 case EXIF_METADATA_QUERY: 143 returnIntent = sendMetadata(queryType); 144 break; 145 case CREATE_IMAGE_ENTRY_QUERY: 146 returnIntent = createImageEntry(queryType); 147 break; 148 case RENAME_FILE_QUERY: 149 returnIntent = renameFile(queryType); 150 break; 151 case CHECK_DATABASE_ROW_EXISTS_QUERY: 152 returnIntent = checkDatabaseRowExists(queryType); 153 break; 154 case IS_URI_REDACTED_VIA_FILE_DESCRIPTOR_FOR_WRITE: 155 case IS_URI_REDACTED_VIA_FILE_DESCRIPTOR_FOR_READ: 156 returnIntent = isFileDescriptorRedactedForUri(queryType); 157 break; 158 case IS_URI_REDACTED_VIA_FILEPATH: 159 returnIntent = isFilePathForUriRedacted(queryType); 160 break; 161 case QUERY_URI: 162 returnIntent = queryForUri(queryType); 163 break; 164 case QUERY_MAX_ROW_ID: 165 case QUERY_MIN_ROW_ID: 166 returnIntent = queryRowId(queryType); 167 break; 168 case QUERY_OWNER_PACKAGE_NAMES: 169 returnIntent = queryOwnerPackageNames(queryType); 170 break; 171 case QUERY_WITH_ARGS: 172 returnIntent = queryWithArgs(queryType); 173 break; 174 case "null": 175 default: 176 throw new IllegalStateException( 177 "Unknown query received from launcher app: " + queryType); 178 } 179 } catch (Exception e) { 180 returnIntent = new Intent(queryType); 181 returnIntent.putExtra(INTENT_EXCEPTION, e); 182 } 183 sendBroadcast(returnIntent); 184 } 185 queryForUri(String queryType)186 private Intent queryForUri(String queryType) { 187 final Intent intent = new Intent(queryType); 188 final Uri uri = getIntent().getParcelableExtra(INTENT_EXTRA_URI); 189 190 try { 191 final Cursor c = getContentResolver().query(uri, null, null, null); 192 intent.putExtra(queryType, c != null && c.moveToFirst()); 193 } catch (Exception e) { 194 intent.putExtra(INTENT_EXCEPTION, e); 195 } 196 197 return intent; 198 } 199 queryRowId(String queryType)200 private Intent queryRowId(String queryType) { 201 // Ensure M_E_S permission has been granted. 202 assertThat(Environment.isExternalStorageManager()).isTrue(); 203 final Intent intent = new Intent(queryType); 204 final Uri uri = getIntent().getParcelableExtra(INTENT_EXTRA_URI); 205 final Bundle bundle = createQueryArgs(queryType); 206 try { 207 final Cursor c = getContentResolver().query(uri, 208 new String[]{MediaStore.Files.FileColumns._ID}, bundle, null); 209 if (c != null && c.moveToFirst()) { 210 intent.putExtra(queryType, c.getLong(0)); 211 } 212 } catch (Exception e) { 213 intent.putExtra(INTENT_EXCEPTION, e); 214 } 215 216 return intent; 217 } 218 queryOwnerPackageNames(String queryType)219 private Intent queryOwnerPackageNames(String queryType) { 220 final Intent intent = new Intent(queryType); 221 final Uri uri = getIntent().getParcelableExtra(INTENT_EXTRA_URI); 222 223 try { 224 final Cursor c = getContentResolver().query(uri, 225 new String[]{MediaStore.MediaColumns.OWNER_PACKAGE_NAME}, null, null); 226 final Set<String> ownerPackageNames = new HashSet<>(); 227 while (c.moveToNext()) { 228 final String ownerPackageName = c.getString(0); 229 if (!Strings.isNullOrEmpty(ownerPackageName)) { 230 ownerPackageNames.add(ownerPackageName); 231 } 232 } 233 intent.putExtra(queryType, ownerPackageNames.toArray(new String[0])); 234 } catch (Exception e) { 235 intent.putExtra(INTENT_EXCEPTION, e); 236 } 237 238 return intent; 239 } 240 deleteMediaByUri(String queryType)241 private Intent deleteMediaByUri(String queryType) { 242 final Intent intent = new Intent(queryType); 243 final Uri uri = getIntent().getParcelableExtra(INTENT_EXTRA_URI); 244 245 try { 246 int rowsDeleted = getContentResolver().delete(uri, null); 247 intent.putExtra(queryType, rowsDeleted); 248 } catch (Exception e) { 249 intent.putExtra(INTENT_EXCEPTION, e); 250 } 251 252 return intent; 253 } 254 updateMediaByUri(String queryType)255 private Intent updateMediaByUri(String queryType) { 256 final Intent intent = new Intent(queryType); 257 final Uri uri = getIntent().getParcelableExtra(INTENT_EXTRA_URI); 258 final Bundle attributes = getIntent().getBundleExtra(INTENT_EXTRA_ARGS); 259 260 final ContentValues values = new ContentValues(); 261 for (String key : attributes.keySet()) { 262 values.put(key, attributes.getString(key)); 263 } 264 265 try { 266 getContentResolver().update(uri, values, null, null); 267 intent.putExtra(queryType, true); 268 } catch (Exception e) { 269 intent.putExtra(INTENT_EXCEPTION, e); 270 } 271 272 return intent; 273 } 274 queryMediaByUri(String queryType)275 private Intent queryMediaByUri(String queryType) { 276 final Intent intent = new Intent(queryType); 277 final Uri uri = getIntent().getParcelableExtra(INTENT_EXTRA_URI); 278 final Bundle projectionBundle = getIntent().getBundleExtra(INTENT_EXTRA_ARGS); 279 280 String[] projection = null; 281 if (projectionBundle != null && !projectionBundle.isEmpty()) { 282 projection = projectionBundle.keySet().toArray(new String[0]); 283 } 284 285 try (Cursor c = getContentResolver() 286 .query(uri, projection, null, null)) { 287 final Bundle result = new Bundle(); 288 if (c.getCount() == 1) { 289 c.moveToFirst(); 290 for (String column : c.getColumnNames()) { 291 result.putString(column, c.getString(c.getColumnIndex(column))); 292 } 293 } else { 294 Log.d(TAG, String.format("Uri in QUERY_MEDIA_BY_URI_QUERY query points " 295 + "to %d media files", c.getCount())); 296 } 297 intent.putExtra(queryType, result); 298 } catch (Exception e) { 299 intent.putExtra(INTENT_EXCEPTION, e); 300 } 301 302 return intent; 303 } 304 queryWithArgs(String queryType)305 private Intent queryWithArgs(String queryType) { 306 final Intent intent = new Intent(queryType); 307 final Uri uri = getIntent().getParcelableExtra(INTENT_EXTRA_URI); 308 final Bundle args = getIntent().getBundleExtra(INTENT_EXTRA_ARGS); 309 try { 310 final Cursor c = getContentResolver().query(uri, 311 new String[]{MediaStore.MediaColumns.DISPLAY_NAME}, args, null); 312 intent.putExtra(queryType, c.getCount()); 313 } catch (Exception e) { 314 intent.putExtra(INTENT_EXCEPTION, e); 315 } 316 317 return intent; 318 } 319 createQueryArgs(String queryType)320 private Bundle createQueryArgs(String queryType) { 321 switch (queryType){ 322 case QUERY_MAX_ROW_ID: 323 return createQueryArgToRetrieveMaximumRowId(); 324 case QUERY_MIN_ROW_ID: 325 return createQueryArgToRetrieveMinimumRowId(); 326 default: 327 throw new IllegalStateException( 328 "Unknown query type received from launcher app: " + queryType); 329 } 330 } 331 createQueryArgToRetrieveMinimumRowId()332 private Bundle createQueryArgToRetrieveMinimumRowId() { 333 final Bundle queryArgs = new Bundle(); 334 queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER, 335 MediaStore.Files.FileColumns._ID + " ASC"); 336 queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 1); 337 return queryArgs; 338 } 339 createQueryArgToRetrieveMaximumRowId()340 private Bundle createQueryArgToRetrieveMaximumRowId() { 341 final Bundle queryArgs = new Bundle(); 342 queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER, 343 MediaStore.Files.FileColumns._ID + " DESC"); 344 queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 1); 345 return queryArgs; 346 } 347 isFileDescriptorRedactedForUri(String queryType)348 private Intent isFileDescriptorRedactedForUri(String queryType) { 349 final Intent intent = new Intent(queryType); 350 final Uri uri = getIntent().getParcelableExtra(INTENT_EXTRA_URI); 351 352 try { 353 final String mode = queryType.equals(IS_URI_REDACTED_VIA_FILE_DESCRIPTOR_FOR_WRITE) 354 ? "w" : "r"; 355 try (ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, mode)) { 356 FileDescriptor fd = pfd.getFileDescriptor(); 357 ExifInterface exifInterface = new ExifInterface(fd); 358 intent.putExtra(queryType, exifInterface.getGpsDateTime() == -1); 359 } 360 } catch (Exception e) { 361 intent.putExtra(INTENT_EXCEPTION, e); 362 } 363 364 return intent; 365 } 366 isFilePathForUriRedacted(String queryType)367 private Intent isFilePathForUriRedacted(String queryType) { 368 final Intent intent = new Intent(queryType); 369 final Uri uri = getIntent().getParcelableExtra(INTENT_EXTRA_URI); 370 371 try { 372 final Cursor c = getContentResolver().query(uri, null, null, null); 373 if (!c.moveToFirst()) { 374 intent.putExtra(INTENT_EXCEPTION, new IOException("")); 375 return intent; 376 } 377 final String path = c.getString(c.getColumnIndex(MediaStore.MediaColumns.DATA)); 378 ExifInterface redactedExifInf = new ExifInterface(path); 379 intent.putExtra(queryType, redactedExifInf.getGpsDateTime() == -1); 380 } catch (Exception e) { 381 intent.putExtra(INTENT_EXCEPTION, e); 382 } 383 384 return intent; 385 } 386 sendMetadata(String queryType)387 private Intent sendMetadata(String queryType) throws IOException { 388 final Intent intent = new Intent(queryType); 389 if (getIntent().hasExtra(INTENT_EXTRA_PATH)) { 390 final String filePath = getIntent().getStringExtra(INTENT_EXTRA_PATH); 391 if (EXIF_METADATA_QUERY.equals(queryType)) { 392 intent.putExtra(queryType, getExifMetadataFromFile(new File(filePath))); 393 } 394 } else { 395 throw new IllegalStateException( 396 EXIF_METADATA_QUERY + ": File path not set from launcher app"); 397 } 398 return intent; 399 } 400 sendDirectoryEntries(String queryType)401 private Intent sendDirectoryEntries(String queryType) throws IOException { 402 if (getIntent().hasExtra(INTENT_EXTRA_PATH)) { 403 final String directoryPath = getIntent().getStringExtra(INTENT_EXTRA_PATH); 404 ArrayList<String> directoryEntriesList = new ArrayList<>(); 405 if (queryType.equals(READDIR_QUERY)) { 406 final String[] directoryEntries = new File(directoryPath).list(); 407 if (directoryEntries == null) { 408 throw new IOException( 409 "I/O exception while listing entries for " + directoryPath); 410 } 411 Collections.addAll(directoryEntriesList, directoryEntries); 412 } 413 final Intent intent = new Intent(queryType); 414 intent.putStringArrayListExtra(queryType, directoryEntriesList); 415 return intent; 416 } else { 417 throw new IllegalStateException( 418 READDIR_QUERY + ": Directory path not set from launcher app"); 419 } 420 } 421 createImageEntry(String queryType)422 private Intent createImageEntry(String queryType) throws Exception { 423 if (getIntent().hasExtra(INTENT_EXTRA_PATH)) { 424 final String path = getIntent().getStringExtra(INTENT_EXTRA_PATH); 425 final String relativePath = path.substring(0, path.lastIndexOf('/')); 426 final String name = path.substring(path.lastIndexOf('/') + 1); 427 428 final ContentValues values = new ContentValues(); 429 values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); 430 values.put(MediaStore.Images.Media.RELATIVE_PATH, relativePath); 431 values.put(MediaStore.Images.Media.DISPLAY_NAME, name); 432 433 final Uri imageUri = getContentResolver().insert(getImageContentUri(), values); 434 435 final Intent intent = new Intent(queryType); 436 intent.putExtra(queryType, imageUri.toString()); 437 return intent; 438 } else { 439 throw new IllegalStateException( 440 CREATE_IMAGE_ENTRY_QUERY + ": File path not set from launcher app"); 441 } 442 } 443 accessFile(String queryType)444 private Intent accessFile(String queryType) throws IOException, RemoteException { 445 if (getIntent().hasExtra(INTENT_EXTRA_PATH)) { 446 final String packageName = getIntent().getStringExtra(INTENT_EXTRA_CALLING_PKG); 447 final String filePath = getIntent().getStringExtra(INTENT_EXTRA_PATH); 448 final File file = new File(filePath); 449 final Intent intent = new Intent(queryType); 450 switch (queryType) { 451 case FILE_EXISTS_QUERY: 452 intent.putExtra(queryType, file.exists()); 453 return intent; 454 case CAN_READ_WRITE_QUERY: 455 intent.putExtra(queryType, file.exists() && file.canRead() && file.canWrite()); 456 return intent; 457 case CREATE_FILE_QUERY: 458 maybeCreateParentDirInAndroid(file); 459 if (!file.getParentFile().exists()) { 460 file.getParentFile().mkdirs(); 461 } 462 boolean success = file.createNewFile(); 463 if (success && getIntent().hasExtra(INTENT_EXTRA_CONTENT)) { 464 success = createFileContent(file); 465 } 466 intent.putExtra(queryType, success); 467 return intent; 468 case DELETE_FILE_QUERY: 469 intent.putExtra(queryType, file.delete()); 470 return intent; 471 case DELETE_RECURSIVE_QUERY: 472 intent.putExtra(queryType, deleteRecursively(file)); 473 return intent; 474 case SETATTR_QUERY: 475 int newTimeMillis = 12345000; 476 intent.putExtra(queryType, file.setLastModified(newTimeMillis)); 477 return intent; 478 case CAN_OPEN_FILE_FOR_READ_QUERY: 479 intent.putExtra(queryType, canOpen(file, false)); 480 return intent; 481 case CAN_OPEN_FILE_FOR_WRITE_QUERY: 482 intent.putExtra(queryType, canOpen(file, true)); 483 return intent; 484 case OPEN_FILE_FOR_READ_QUERY: 485 case OPEN_FILE_FOR_WRITE_QUERY: 486 Uri contentUri = FileProvider.getUriForFile(getApplicationContext(), 487 getApplicationContext().getPackageName(), file); 488 intent.putExtra(queryType, contentUri); 489 // Grant permission to the possible instrumenting test apps 490 if (packageName != null) { 491 getApplicationContext().grantUriPermission(packageName, 492 contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION 493 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 494 } 495 return intent; 496 default: 497 throw new IllegalStateException( 498 "Unknown query received from launcher app: " + queryType); 499 } 500 } else { 501 throw new IllegalStateException(queryType + ": File path not set from launcher app"); 502 } 503 } 504 createFileContent(File file)505 private boolean createFileContent(File file) throws RemoteException { 506 final Bundle content = getIntent().getBundleExtra( 507 INTENT_EXTRA_CONTENT); 508 IBinder binder = content.getBinder(INTENT_EXTRA_CONTENT); 509 Parcel reply = Parcel.obtain(); 510 binder.transact(IBinder.FIRST_CALL_TRANSACTION, Parcel.obtain(), reply, 0); 511 try (ParcelFileDescriptor inputPFD = reply.readFileDescriptor(); 512 FileInputStream fileInputStream = new FileInputStream( 513 inputPFD.getFileDescriptor()); 514 FileOutputStream outputStream = new FileOutputStream(file)) { 515 long copied = FileUtils.copy(fileInputStream, outputStream); 516 outputStream.getFD().sync(); 517 return copied > 0; 518 } catch (Exception e) { 519 Log.e(TAG, e.getMessage(), e); 520 return false; 521 } 522 } 523 renameFile(String queryType)524 private Intent renameFile(String queryType) { 525 if (getIntent().hasExtra(INTENT_EXTRA_PATH)) { 526 String[] paths = getIntent().getStringExtra(INTENT_EXTRA_PATH) 527 .split(RENAME_FILE_PARAMS_SEPARATOR); 528 File src = new File(paths[0]); 529 File dst = new File(paths[1]); 530 boolean result = src.renameTo(dst); 531 final Intent intent = new Intent(queryType); 532 intent.putExtra(queryType, result); 533 return intent; 534 } else { 535 throw new IllegalStateException( 536 queryType + ": File paths not set from launcher app"); 537 } 538 } 539 checkDatabaseRowExists(String queryType)540 private Intent checkDatabaseRowExists(String queryType) { 541 if (getIntent().hasExtra(INTENT_EXTRA_PATH)) { 542 final String filePath = getIntent().getStringExtra(INTENT_EXTRA_PATH); 543 boolean result = 544 getFileRowIdFromDatabase(getContentResolver(), new File(filePath)) != -1; 545 final Intent intent = new Intent(queryType); 546 intent.putExtra(queryType, result); 547 return intent; 548 } else { 549 throw new IllegalStateException( 550 queryType + ": File path not set from launcher app"); 551 } 552 } 553 maybeCreateParentDirInAndroid(File file)554 private void maybeCreateParentDirInAndroid(File file) { 555 final String ownedPathType = getOwnedDirectoryType(file); 556 if (ownedPathType == null) { 557 return; 558 } 559 // Create the external app dir first. 560 if (createExternalAppDir(ownedPathType)) { 561 // Then create everything along the path. 562 file.getParentFile().mkdirs(); 563 } 564 } 565 createExternalAppDir(String name)566 private boolean createExternalAppDir(String name) { 567 // Apps are not allowed to create data/cache/obb etc under Android directly and are 568 // expected to call one of the following methods. 569 switch (name) { 570 case "data": 571 getApplicationContext().getExternalFilesDirs(null); 572 getApplicationContext().getExternalCacheDirs(); 573 return true; 574 case "obb": 575 getApplicationContext().getObbDirs(); 576 return true; 577 case "media": 578 getApplicationContext().getExternalMediaDirs(); 579 return true; 580 default: 581 return false; 582 } 583 } 584 585 /** 586 * Returns null if given path is not an owned path. 587 */ 588 @Nullable getOwnedDirectoryType(File path)589 private static String getOwnedDirectoryType(File path) { 590 final Matcher m = PATTERN_OWNED_PATH.matcher(path.getAbsolutePath()); 591 if (m.matches()) { 592 return m.group(1); 593 } 594 return null; 595 } 596 } 597