From ca7ec6b79c722ca0848536bd1b16e22e61cb6255 Mon Sep 17 00:00:00 2001 From: eggfly Date: Fri, 17 Dec 2021 14:09:09 +0800 Subject: [PATCH] [android] Provide a path instead of throwing NPE when getDir() suites returns null (#30367) --- .../android/io/flutter/util/PathUtils.java | 39 +++++++- .../test/io/flutter/util/PathUtilsTest.java | 89 +++++++++++++++++++ tools/android_lint/project.xml | 4 + 3 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 shell/platform/android/test/io/flutter/util/PathUtilsTest.java diff --git a/shell/platform/android/io/flutter/util/PathUtils.java b/shell/platform/android/io/flutter/util/PathUtils.java index 60addd2e272b9..1b4d36eb96529 100644 --- a/shell/platform/android/io/flutter/util/PathUtils.java +++ b/shell/platform/android/io/flutter/util/PathUtils.java @@ -6,21 +6,52 @@ import android.content.Context; import android.os.Build; +import java.io.File; public final class PathUtils { public static String getFilesDir(Context applicationContext) { - return applicationContext.getFilesDir().getPath(); + File filesDir = applicationContext.getFilesDir(); + if (filesDir == null) { + filesDir = new File(getDataDirPath(applicationContext), "files"); + } + return filesDir.getPath(); } public static String getDataDirectory(Context applicationContext) { - return applicationContext.getDir("flutter", Context.MODE_PRIVATE).getPath(); + final String name = "flutter"; + File flutterDir = applicationContext.getDir(name, Context.MODE_PRIVATE); + if (flutterDir == null) { + flutterDir = new File(getDataDirPath(applicationContext), "app_" + name); + } + return flutterDir.getPath(); } public static String getCacheDirectory(Context applicationContext) { + File cacheDir; if (Build.VERSION.SDK_INT >= 21) { - return applicationContext.getCodeCacheDir().getPath(); + cacheDir = applicationContext.getCodeCacheDir(); + if (cacheDir == null) { + cacheDir = applicationContext.getCacheDir(); + } + } else { + cacheDir = applicationContext.getCacheDir(); + } + if (cacheDir == null) { + // This can happen if the disk is full. This code path is used to set up dart:io's + // `Directory.systemTemp`. It's unknown if the application will ever try to + // use that or not, so do not throw here. In this case, this directory does + // not exist because the disk is full, and the application will later get an + // exception when it tries to actually write. + cacheDir = new File(getDataDirPath(applicationContext), "cache"); + } + return cacheDir.getPath(); + } + + private static String getDataDirPath(Context applicationContext) { + if (Build.VERSION.SDK_INT >= 24) { + return applicationContext.getDataDir().getPath(); } else { - return applicationContext.getCacheDir().getPath(); + return applicationContext.getApplicationInfo().dataDir; } } } diff --git a/shell/platform/android/test/io/flutter/util/PathUtilsTest.java b/shell/platform/android/test/io/flutter/util/PathUtilsTest.java new file mode 100644 index 0000000000000..47288f973a9c6 --- /dev/null +++ b/shell/platform/android/test/io/flutter/util/PathUtilsTest.java @@ -0,0 +1,89 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.Build; +import java.io.File; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +@Config(manifest = Config.NONE) +@RunWith(RobolectricTestRunner.class) +public class PathUtilsTest { + + private static final String APP_DATA_PATH = "/data/data/package_name"; + + @Test + public void canGetFilesDir() { + Context context = mock(Context.class); + when(context.getFilesDir()).thenReturn(new File(APP_DATA_PATH + "/files")); + assertEquals(PathUtils.getFilesDir(context), APP_DATA_PATH + "/files"); + } + + @Test + public void canOnlyGetFilesPathWhenDiskFullAndFilesDirNotCreated() { + Context context = mock(Context.class); + when(context.getFilesDir()).thenReturn(null); + if (Build.VERSION.SDK_INT >= 24) { + when(context.getDataDir()).thenReturn(new File(APP_DATA_PATH)); + } else { + when(context.getApplicationInfo().dataDir).thenReturn(APP_DATA_PATH); + } + assertEquals(PathUtils.getFilesDir(context), APP_DATA_PATH + "/files"); + } + + @Test + public void canGetFlutterDataDir() { + Context context = mock(Context.class); + when(context.getDir("flutter", Context.MODE_PRIVATE)) + .thenReturn(new File(APP_DATA_PATH + "/app_flutter")); + assertEquals(PathUtils.getDataDirectory(context), APP_DATA_PATH + "/app_flutter"); + } + + @Test + public void canOnlyGetFlutterDataPathWhenDiskFullAndFlutterDataDirNotCreated() { + Context context = mock(Context.class); + when(context.getDir("flutter", Context.MODE_PRIVATE)).thenReturn(null); + if (Build.VERSION.SDK_INT >= 24) { + when(context.getDataDir()).thenReturn(new File(APP_DATA_PATH)); + } else { + when(context.getApplicationInfo().dataDir).thenReturn(APP_DATA_PATH); + } + assertEquals(PathUtils.getDataDirectory(context), APP_DATA_PATH + "/app_flutter"); + } + + @Test + public void canGetCacheDir() { + Context context = mock(Context.class); + when(context.getCacheDir()).thenReturn(new File(APP_DATA_PATH + "/cache")); + if (Build.VERSION.SDK_INT >= 21) { + when(context.getCodeCacheDir()).thenReturn(new File(APP_DATA_PATH + "/code_cache")); + } + assertTrue(PathUtils.getCacheDirectory(context).startsWith(APP_DATA_PATH)); + } + + @Test + public void canOnlyGetCachePathWhenDiskFullAndCacheDirNotCreated() { + Context context = mock(Context.class); + when(context.getCacheDir()).thenReturn(null); + if (Build.VERSION.SDK_INT >= 21) { + when(context.getCodeCacheDir()).thenReturn(null); + } + if (Build.VERSION.SDK_INT >= 24) { + when(context.getDataDir()).thenReturn(new File(APP_DATA_PATH)); + } else { + when(context.getApplicationInfo().dataDir).thenReturn(APP_DATA_PATH); + } + assertEquals(PathUtils.getCacheDirectory(context), APP_DATA_PATH + "/cache"); + } +} diff --git a/tools/android_lint/project.xml b/tools/android_lint/project.xml index 916da6b37a313..b37e7f0029a4c 100644 --- a/tools/android_lint/project.xml +++ b/tools/android_lint/project.xml @@ -6,6 +6,7 @@ + @@ -72,6 +73,7 @@ + @@ -83,6 +85,7 @@ + @@ -116,6 +119,7 @@ +