diff options
-rw-r--r-- | api/current.xml | 219 | ||||
-rw-r--r-- | core/java/android/app/ContextImpl.java | 59 | ||||
-rw-r--r-- | core/java/android/app/IntentService.java | 2 | ||||
-rw-r--r-- | core/java/android/content/Context.java | 132 | ||||
-rw-r--r-- | core/java/android/content/ContextWrapper.java | 10 | ||||
-rw-r--r-- | core/java/android/content/pm/IPackageManager.aidl | 1 | ||||
-rw-r--r-- | core/java/android/content/pm/PackageManager.java | 7 | ||||
-rw-r--r-- | core/java/android/os/Environment.java | 182 | ||||
-rw-r--r-- | media/java/android/media/MediaScannerConnection.java | 86 | ||||
-rwxr-xr-x | packages/DefaultContainerService/AndroidManifest.xml | 4 | ||||
-rw-r--r-- | packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java | 41 | ||||
-rw-r--r-- | services/java/com/android/server/DevicePolicyManagerService.java | 20 | ||||
-rw-r--r-- | services/java/com/android/server/PackageManagerService.java | 115 | ||||
-rw-r--r-- | test-runner/android/test/mock/MockContext.java | 10 | ||||
-rw-r--r-- | tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java | 12 |
15 files changed, 855 insertions, 45 deletions
diff --git a/api/current.xml b/api/current.xml index 3da6b72..a8f7109 100644 --- a/api/current.xml +++ b/api/current.xml @@ -32785,6 +32785,30 @@ <parameter name="mode" type="int"> </parameter> </method> +<method name="getExternalCacheDir" + return="java.io.File" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getExternalFilesDir" + return="java.io.File" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="type" type="java.lang.String"> +</parameter> +</method> <method name="getFileStreamPath" return="java.io.File" abstract="true" @@ -34293,6 +34317,30 @@ <parameter name="mode" type="int"> </parameter> </method> +<method name="getExternalCacheDir" + return="java.io.File" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getExternalFilesDir" + return="java.io.File" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="type" type="java.lang.String"> +</parameter> +</method> <method name="getFileStreamPath" return="java.io.File" abstract="false" @@ -83028,6 +83076,25 @@ <parameter name="mimeType" type="java.lang.String"> </parameter> </method> +<method name="scanFile" + return="void" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +<parameter name="paths" type="java.lang.String[]"> +</parameter> +<parameter name="mimeTypes" type="java.lang.String[]"> +</parameter> +<parameter name="callback" type="android.media.MediaScannerConnection.ScanResultListener"> +</parameter> +</method> </class> <interface name="MediaScannerConnection.MediaScannerConnectionClient" abstract="true" @@ -83036,6 +83103,8 @@ deprecated="not deprecated" visibility="public" > +<implements name="android.media.MediaScannerConnection.ScanResultListener"> +</implements> <method name="onMediaScannerConnected" return="void" abstract="true" @@ -83063,6 +83132,29 @@ </parameter> </method> </interface> +<interface name="MediaScannerConnection.ScanResultListener" + abstract="true" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<method name="onScanCompleted" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="path" type="java.lang.String"> +</parameter> +<parameter name="uri" type="android.net.Uri"> +</parameter> +</method> +</interface> <class name="Ringtone" extends="java.lang.Object" abstract="false" @@ -112670,6 +112762,19 @@ visibility="public" > </method> +<method name="getExternalStoragePublicDirectory" + return="java.io.File" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="type" type="java.lang.String"> +</parameter> +</method> <method name="getExternalStorageState" return="java.lang.String" abstract="false" @@ -112692,6 +112797,96 @@ visibility="public" > </method> +<field name="DIRECTORY_ALARMS" + type="java.lang.String" + transient="false" + volatile="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="DIRECTORY_DCIM" + type="java.lang.String" + transient="false" + volatile="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="DIRECTORY_DOWNLOADS" + type="java.lang.String" + transient="false" + volatile="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="DIRECTORY_MOVIES" + type="java.lang.String" + transient="false" + volatile="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="DIRECTORY_MUSIC" + type="java.lang.String" + transient="false" + volatile="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="DIRECTORY_NOTIFICATIONS" + type="java.lang.String" + transient="false" + volatile="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="DIRECTORY_PICTURES" + type="java.lang.String" + transient="false" + volatile="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="DIRECTORY_PODCASTS" + type="java.lang.String" + transient="false" + volatile="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="DIRECTORY_RINGTONES" + type="java.lang.String" + transient="false" + volatile="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="MEDIA_BAD_REMOVAL" type="java.lang.String" transient="false" @@ -144243,6 +144438,30 @@ <parameter name="mode" type="int"> </parameter> </method> +<method name="getExternalCacheDir" + return="java.io.File" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getExternalFilesDir" + return="java.io.File" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="type" type="java.lang.String"> +</parameter> +</method> <method name="getFileStreamPath" return="java.io.File" abstract="false" diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 4923eee..9b9cbd5 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -197,9 +197,9 @@ class ContextImpl extends Context { private File mDatabasesDir; private File mPreferencesDir; private File mFilesDir; - - private File mCacheDir; + private File mExternalFilesDir; + private File mExternalCacheDir; private static long sInstanceCount = 0; @@ -438,6 +438,38 @@ class ContextImpl extends Context { } @Override + public File getExternalFilesDir(String type) { + synchronized (mSync) { + if (mExternalFilesDir == null) { + mExternalFilesDir = Environment.getExternalStorageAppFilesDirectory( + getPackageName()); + } + if (!mExternalFilesDir.exists()) { + try { + (new File(Environment.getExternalStorageAndroidDataDir(), + ".nomedia")).createNewFile(); + } catch (IOException e) { + } + if (!mExternalFilesDir.mkdirs()) { + Log.w(TAG, "Unable to create external files directory"); + return null; + } + } + if (type == null) { + return mExternalFilesDir; + } + File dir = new File(mExternalFilesDir, type); + if (!dir.exists()) { + if (!dir.mkdirs()) { + Log.w(TAG, "Unable to create external media directory " + dir); + return null; + } + } + return dir; + } + } + + @Override public File getCacheDir() { synchronized (mSync) { if (mCacheDir == null) { @@ -457,7 +489,28 @@ class ContextImpl extends Context { return mCacheDir; } - + @Override + public File getExternalCacheDir() { + synchronized (mSync) { + if (mExternalCacheDir == null) { + mExternalCacheDir = Environment.getExternalStorageAppCacheDirectory( + getPackageName()); + } + if (!mExternalCacheDir.exists()) { + try { + (new File(Environment.getExternalStorageAndroidDataDir(), + ".nomedia")).createNewFile(); + } catch (IOException e) { + } + if (!mExternalCacheDir.mkdirs()) { + Log.w(TAG, "Unable to create external cache directory"); + return null; + } + } + return mExternalCacheDir; + } + } + @Override public File getFileStreamPath(String name) { return makeFilename(getFilesDir(), name); diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java index 804c8eb..3fd36a3 100644 --- a/core/java/android/app/IntentService.java +++ b/core/java/android/app/IntentService.java @@ -42,7 +42,7 @@ public abstract class IntentService extends Service { * {@link #onStartCommand(Intent, int, int)} will return * {@link Service#START_REDELIVER_INTENT} instead of * {@link Service#START_NOT_STICKY}, so that if this service's process - * is called while it is executing the Intent in + * is killed while it is executing the Intent in * {@link #onHandleIntent(Intent)}, then when later restarted the same Intent * will be re-delivered to it, to retry its execution. */ diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index b4a0bf8..672e5f7 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -25,6 +25,7 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; +import android.media.MediaScannerConnection.ScanResultListener; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -137,7 +138,28 @@ public abstract class Context { /** * Return the context of the single, global Application object of the - * current process. + * current process. This generally should only be used if you need a + * Context whose lifecycle is separate from the current context, that is + * tied to the lifetime of the process rather than the current component. + * + * <p>Consider for example how this interacts with + * {@ #registerReceiver(BroadcastReceiver, IntentFilter)}: + * <ul> + * <li> <p>If used from an Activity context, the receiver is being registered + * within that activity. This means that you are expected to unregister + * before the activity is done being destroyed; in fact if you do not do + * so, the framework will clean up your leaked registration as it removes + * the activity and log an error. Thus, if you use the Activity context + * to register a receiver that is static (global to the process, not + * associated with an Activity instance) then that registration will be + * removed on you at whatever point the activity you used is destroyed. + * <li> <p>If used from the Context returned here, the receiver is being + * registered with the global state associated with your application. Thus + * it will never be unregistered for you. This is necessary if the receiver + * is associated with static data, not a particular component. However + * using the ApplicationContext elsewhere can easily lead to serious leaks + * if you forget to unregister, unbind, etc. + * </ul> */ public abstract Context getApplicationContext(); @@ -393,11 +415,84 @@ public abstract class Context { public abstract File getFilesDir(); /** + * Returns the absolute path to the directory on the external filesystem + * (that is somewhere on {@link android.os.Environment#getExternalStorageDirectory() + * Environment.getExternalStorageDirectory()} where the application can + * place persistent files it owns. These files are private to the + * applications, and not typically visible to the user as media. + * + * <p>This is like {@link #getFilesDir()} in that these + * files will be deleted when the application is uninstalled, however there + * are some important differences: + * + * <ul> + * <li>External files are not always available: they will disappear if the + * user mounts the external storage on a computer or removes it. See the + * APIs on {@link android.os.Environment} for information in the storage state. + * <li>There is no security enforced with these files. All applications + * can read and write files placed here. + * </ul> + * + * <p>Here is an example of typical code to manipulate a file in + * an application's private storage:</p> + * + * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java + * private_file} + * + * <p>If you install a non-null <var>type</var> to this function, the returned + * file will be a path to a sub-directory of the given type. Though these files + * are not automatically scanned by the media scanner, you can explicitly + * add them to the media database with + * {@link android.media.MediaScannerConnection#scanFile(Context, String[], String[], + * ScanResultListener) MediaScannerConnection.scanFile}. + * Note that this is not the same as + * {@link android.os.Environment#getExternalStoragePublicDirectory + * Environment.getExternalStoragePublicDirectory()}, which provides + * directories of media shared by all applications. The + * directories returned here are + * owned by the application, and its contents will be removed when the + * application is uninstalled. Unlike + * {@link android.os.Environment#getExternalStoragePublicDirectory + * Environment.getExternalStoragePublicDirectory()}, the directory + * returned here will be automatically created for you. + * + * <p>Here is an example of typical code to manipulate a picture in + * an application's private storage and add it to the media database:</p> + * + * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java + * private_picture} + * + * @param type The type of files directory to return. May be null for + * the root of the files directory or one of + * the following Environment constants for a subdirectory: + * {@link android.os.Environment#DIRECTORY_MUSIC}, + * {@link android.os.Environment#DIRECTORY_PODCASTS}, + * {@link android.os.Environment#DIRECTORY_RINGTONES}, + * {@link android.os.Environment#DIRECTORY_ALARMS}, + * {@link android.os.Environment#DIRECTORY_NOTIFICATIONS}, + * {@link android.os.Environment#DIRECTORY_PICTURES}, or + * {@link android.os.Environment#DIRECTORY_MOVIES}. + * + * @return Returns the path of the directory holding application files + * on external storage. Returns null if external storage is not currently + * mounted so it could not ensure the path exists; you will need to call + * this method again when it is available. + * + * @see #getFilesDir + */ + public abstract File getExternalFilesDir(String type); + + /** * Returns the absolute path to the application specific cache directory * on the filesystem. These files will be ones that get deleted first when the - * device runs low on storage + * device runs low on storage. * There is no guarantee when these files will be deleted. - * + * + * <strong>Note: you should not <em>rely</em> on the system deleting these + * files for you; you should always have a reasonable maximum, such as 1 MB, + * for the amount of space you consume with cache files, and prune those + * files when exceeding that space.</strong> + * * @return Returns the path of the directory holding application cache files. * * @see #openFileOutput @@ -407,6 +502,37 @@ public abstract class Context { public abstract File getCacheDir(); /** + * Returns the absolute path to the directory on the external filesystem + * (that is somewhere on {@link android.os.Environment#getExternalStorageDirectory() + * Environment.getExternalStorageDirectory()} where the application can + * place cache files it owns. + * + * <p>This is like {@link #getCacheDir()} in that these + * files will be deleted when the application is uninstalled, however there + * are some important differences: + * + * <ul> + * <li>The platform does not monitor the space available in external storage, + * and thus will not automatically delete these files. Note that you should + * be managing the maximum space you will use for these anyway, just like + * with {@link #getCacheDir()}. + * <li>External files are not always available: they will disappear if the + * user mounts the external storage on a computer or removes it. See the + * APIs on {@link android.os.Environment} for information in the storage state. + * <li>There is no security enforced with these files. All applications + * can read and write files placed here. + * </ul> + * + * @return Returns the path of the directory holding application cache files + * on external storage. Returns null if external storage is not currently + * mounted so it could not ensure the path exists; you will need to call + * this method again when it is available. + * + * @see #getCacheDir + */ + public abstract File getExternalCacheDir(); + + /** * Returns an array of strings naming the private files associated with * this Context's application package. * diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 1b34320..a447108 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -179,11 +179,21 @@ public class ContextWrapper extends Context { } @Override + public File getExternalFilesDir(String type) { + return mBase.getExternalFilesDir(type); + } + + @Override public File getCacheDir() { return mBase.getCacheDir(); } @Override + public File getExternalCacheDir() { + return mBase.getExternalCacheDir(); + } + + @Override public File getDir(String name, int mode) { return mBase.getDir(name, mode); } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 54db5e0..2c8c112 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -305,4 +305,5 @@ interface IPackageManager { */ void updateExternalMediaStatus(boolean mounted); + String nextPackageToClean(String lastPackage); } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index e3b1694..fca8588 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -622,6 +622,13 @@ public abstract class PackageManager { public static final String FEATURE_LIVE_WALLPAPER = "android.software.live_wallpaper"; /** + * Action to external storage service to clean out removed apps. + * @hide + */ + public static final String ACTION_CLEAN_EXTERNAL_STORAGE + = "android.content.pm.CLEAN_EXTERNAL_STORAGE"; + + /** * Determines best place to install an application: either SD or internal FLASH. * Tweak the algorithm for best results. * @param appInfo ApplicationInfo object of the package to install. diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index ef1f3be..a9831aa 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -91,6 +91,14 @@ public class Environment { private static final File EXTERNAL_STORAGE_DIRECTORY = getDirectory("EXTERNAL_STORAGE", "/sdcard"); + private static final File EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY + = new File (new File(getDirectory("EXTERNAL_STORAGE", "/sdcard"), + "Android"), "data"); + + private static final File EXTERNAL_STORAGE_ANDROID_MEDIA_DIRECTORY + = new File (new File(getDirectory("EXTERNAL_STORAGE", "/sdcard"), + "Android"), "media"); + private static final File DOWNLOAD_CACHE_DIRECTORY = getDirectory("DOWNLOAD_CACHE", "/cache"); @@ -102,13 +110,183 @@ public class Environment { } /** - * Gets the Android external storage directory. + * Gets the Android external storage directory. This directory may not + * currently be accessible if it has been mounted by the user on their + * computer, has been removed from the device, or some other problem has + * happened. You can determine its current state with + * {@link #getExternalStorageState()}. + * + * <p>Here is an example of typical code to monitor the state of + * external storage:</p> + * + * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java + * monitor_storage} */ public static File getExternalStorageDirectory() { return EXTERNAL_STORAGE_DIRECTORY; } /** + * Standard directory in which to place any audio files that should be + * in the regular list of music for the user. + * This may be combined with + * {@link #DIRECTORY_PODCASTS}, {@link #DIRECTORY_NOTIFICATIONS}, + * {@link #DIRECTORY_ALARMS}, and {@link #DIRECTORY_RINGTONES} as a series + * of directories to categories a particular audio file as more than one + * type. + */ + public static String DIRECTORY_MUSIC = "Music"; + + /** + * Standard directory in which to place any audio files that should be + * in the list of podcasts that the user can select (not as regular + * music). + * This may be combined with {@link #DIRECTORY_MUSIC}, + * {@link #DIRECTORY_NOTIFICATIONS}, + * {@link #DIRECTORY_ALARMS}, and {@link #DIRECTORY_RINGTONES} as a series + * of directories to categories a particular audio file as more than one + * type. + */ + public static String DIRECTORY_PODCASTS = "Podcasts"; + + /** + * Standard directory in which to place any audio files that should be + * in the list of ringtones that the user can select (not as regular + * music). + * This may be combined with {@link #DIRECTORY_MUSIC}, + * {@link #DIRECTORY_PODCASTS}, {@link #DIRECTORY_NOTIFICATIONS}, and + * {@link #DIRECTORY_ALARMS} as a series + * of directories to categories a particular audio file as more than one + * type. + */ + public static String DIRECTORY_RINGTONES = "Ringtones"; + + /** + * Standard directory in which to place any audio files that should be + * in the list of alarms that the user can select (not as regular + * music). + * This may be combined with {@link #DIRECTORY_MUSIC}, + * {@link #DIRECTORY_PODCASTS}, {@link #DIRECTORY_NOTIFICATIONS}, + * and {@link #DIRECTORY_RINGTONES} as a series + * of directories to categories a particular audio file as more than one + * type. + */ + public static String DIRECTORY_ALARMS = "Alarms"; + + /** + * Standard directory in which to place any audio files that should be + * in the list of notifications that the user can select (not as regular + * music). + * This may be combined with {@link #DIRECTORY_MUSIC}, + * {@link #DIRECTORY_PODCASTS}, + * {@link #DIRECTORY_ALARMS}, and {@link #DIRECTORY_RINGTONES} as a series + * of directories to categories a particular audio file as more than one + * type. + */ + public static String DIRECTORY_NOTIFICATIONS = "Notifications"; + + /** + * Standard directory in which to place pictures that are available to + * the user. Note that this is primarily a convention for the top-level + * public directory, as the media scanner will find and collect pictures + * in any directory. + */ + public static String DIRECTORY_PICTURES = "Pictures"; + + /** + * Standard directory in which to place movies that are available to + * the user. Note that this is primarily a convention for the top-level + * public directory, as the media scanner will find and collect movies + * in any directory. + */ + public static String DIRECTORY_MOVIES = "Movies"; + + /** + * Standard directory in which to place files that have been downloaded by + * the user. Note that this is primarily a convention for the top-level + * public directory, you are free to download files anywhere in your own + * private directories. + */ + public static String DIRECTORY_DOWNLOADS = "Downloads"; + + /** + * The traditional location for pictures and videos when mounting the + * device as a camera. Note that this is primarily a convention for the + * top-level public directory, as this convention makes no sense elsewhere. + */ + public static String DIRECTORY_DCIM = "DCIM"; + + /** + * Get a top-level public external storage directory for placing files of + * a particular type. This is where the user will typically place and + * manage their own files, so you should be careful about what you put here + * to ensure you don't erase their files or get in the way of their own + * organization. + * + * <p>Here is an example of typical code to manipulate a picture on + * the public external storage:</p> + * + * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java + * public_picture} + * + * @param type The type of storage directory to return. Should be one of + * {@link #DIRECTORY_MUSIC}, {@link #DIRECTORY_PODCASTS}, + * {@link #DIRECTORY_RINGTONES}, {@link #DIRECTORY_ALARMS}, + * {@link #DIRECTORY_NOTIFICATIONS}, {@link #DIRECTORY_PICTURES}, + * {@link #DIRECTORY_MOVIES}, {@link #DIRECTORY_DOWNLOADS}, or + * {@link #DIRECTORY_DCIM}. May not be null. + * + * @return Returns the File path for the directory. Note that this + * directory may not yet exist, so you must make sure it exists before + * using it such as with {@link File#mkdirs File.mkdirs()}. + */ + public static File getExternalStoragePublicDirectory(String type) { + return new File(getExternalStorageDirectory(), type); + } + + /** + * Returns the path for android-specific data on the SD card. + * @hide + */ + public static File getExternalStorageAndroidDataDir() { + return EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY; + } + + /** + * Generates the raw path to an application's data + * @hide + */ + public static File getExternalStorageAppDataDirectory(String packageName) { + return new File(EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY, packageName); + } + + /** + * Generates the raw path to an application's media + * @hide + */ + public static File getExternalStorageAppMediaDirectory(String packageName) { + return new File(EXTERNAL_STORAGE_ANDROID_MEDIA_DIRECTORY, packageName); + } + + /** + * Generates the path to an application's files. + * @hide + */ + public static File getExternalStorageAppFilesDirectory(String packageName) { + return new File(new File(EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY, + packageName), "files"); + } + + /** + * Generates the path to an application's cache. + * @hide + */ + public static File getExternalStorageAppCacheDirectory(String packageName) { + return new File(new File(EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY, + packageName), "cache"); + } + + /** * Gets the Android Download/Cache content directory. */ public static File getDownloadCacheDirectory() { @@ -173,6 +351,8 @@ public class Environment { * Gets the current state of the external storage device. * Note: This call should be deprecated as it doesn't support * multiple volumes. + * + * <p>See {@link #getExternalStorageDirectory()} for an example of its use. */ public static String getExternalStorageState() { try { diff --git a/media/java/android/media/MediaScannerConnection.java b/media/java/android/media/MediaScannerConnection.java index d2596b8..65b67a1 100644 --- a/media/java/android/media/MediaScannerConnection.java +++ b/media/java/android/media/MediaScannerConnection.java @@ -57,17 +57,32 @@ public class MediaScannerConnection implements ServiceConnection { }; /** + * Interface for notifying clients of the result of scanning a + * requested media file. + */ + public interface ScanResultListener { + /** + * Called to notify the client when the media scanner has finished + * scanning a file. + * @param path the path to the file that has been scanned. + * @param uri the Uri for the file if the scanning operation succeeded + * and the file was added to the media database, or null if scanning failed. + */ + public void onScanCompleted(String path, Uri uri); + } + + /** * An interface for notifying clients of MediaScannerConnection * when a connection to the MediaScanner service has been established * and when the scanning of a file has completed. */ - public interface MediaScannerConnectionClient { + public interface MediaScannerConnectionClient extends ScanResultListener { /** * Called to notify the client when a connection to the * MediaScanner service has been established. */ public void onMediaScannerConnected(); - + /** * Called to notify the client when the media scanner has finished * scanning a file. @@ -136,11 +151,12 @@ public class MediaScannerConnection implements ServiceConnection { /** * Requests the media scanner to scan a file. + * Success or failure of the scanning operation cannot be determined until + * {@link MediaScannerConnectionClient#onScanCompleted(String, Uri)} is called. + * * @param path the path to the file to be scanned. * @param mimeType an optional mimeType for the file. * If mimeType is null, then the mimeType will be inferred from the file extension. - * Success or failure of the scanning operation cannot be determined until - * {@link MediaScannerConnectionClient#onScanCompleted(String, Uri)} is called. */ public void scanFile(String path, String mimeType) { synchronized (this) { @@ -159,7 +175,67 @@ public class MediaScannerConnection implements ServiceConnection { } } } - + + static class ClientProxy implements MediaScannerConnectionClient { + final String[] mPaths; + final String[] mMimeTypes; + final ScanResultListener mClient; + MediaScannerConnection mConnection; + int mNextPath; + + ClientProxy(String[] paths, String[] mimeTypes, ScanResultListener client) { + mPaths = paths; + mMimeTypes = mimeTypes; + mClient = client; + } + + public void onMediaScannerConnected() { + scanNextPath(); + } + + public void onScanCompleted(String path, Uri uri) { + if (mClient != null) { + mClient.onScanCompleted(path, uri); + } + scanNextPath(); + } + + void scanNextPath() { + if (mNextPath >= mPaths.length) { + mConnection.disconnect(); + return; + } + String mimeType = mMimeTypes != null ? mMimeTypes[mNextPath] : null; + mConnection.scanFile(mPaths[mNextPath], mimeType); + mNextPath++; + } + } + + /** + * Convenience for constructing a {@link MediaScannerConnection}, calling + * {@link #connect} on it, and calling {@link #scanFile} with the given + * <var>path</var> and <var>mimeType</var> when the connection is + * established. + * @param context The caller's Context, required for establishing a connection to + * the media scanner service. + * Success or failure of the scanning operation cannot be determined until + * {@link MediaScannerConnectionClient#onScanCompleted(String, Uri)} is called. + * @param paths Array of paths to be scanned. + * @param mimeTypes Optional array of MIME types for each path. + * If mimeType is null, then the mimeType will be inferred from the file extension. + * @param callback Optional callback through which you can receive the + * scanned URI and MIME type; If null, the file will be scanned but + * you will not get a result back. + * @see scanFile(String, String) + */ + public static void scanFile(Context context, String[] paths, String[] mimeTypes, + ScanResultListener callback) { + ClientProxy client = new ClientProxy(paths, mimeTypes, callback); + MediaScannerConnection connection = new MediaScannerConnection(context, client); + client.mConnection = connection; + connection.connect(); + } + /** * Part of the ServiceConnection interface. Do not call. */ diff --git a/packages/DefaultContainerService/AndroidManifest.xml b/packages/DefaultContainerService/AndroidManifest.xml index 3d72017..5ec72df 100755 --- a/packages/DefaultContainerService/AndroidManifest.xml +++ b/packages/DefaultContainerService/AndroidManifest.xml @@ -5,9 +5,9 @@ <uses-permission android:name="android.permission.ASEC_CREATE"/> <uses-permission android:name="android.permission.ASEC_DESTROY"/> <uses-permission android:name="android.permission.ASEC_MOUNT_UNMOUNT"/> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> - <application android:process="def.container.service" - android:label="@string/service_name"> + <application android:label="@string/service_name"> <service android:name=".DefaultContainerService" android:enabled="true" diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java index fecd366..c418ccb 100644 --- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java +++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java @@ -3,8 +3,11 @@ package com.android.defcontainer; import com.android.internal.app.IMediaContainerService; import android.content.Intent; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; import android.net.Uri; import android.os.Debug; +import android.os.Environment; import android.os.IBinder; import android.os.storage.IMountService; import android.os.storage.StorageResultCode; @@ -12,6 +15,7 @@ import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.app.IntentService; import android.app.Service; import android.util.Log; @@ -25,7 +29,6 @@ import java.io.OutputStream; import android.os.FileUtils; - /* * This service copies a downloaded apk to a file passed in as * a ParcelFileDescriptor or to a newly created container specified @@ -33,7 +36,7 @@ import android.os.FileUtils; * based on its uid. This process also needs the ACCESS_DOWNLOAD_MANAGER * permission to access apks downloaded via the download manager. */ -public class DefaultContainerService extends Service { +public class DefaultContainerService extends IntentService { private static final String TAG = "DefContainer"; private static final boolean localLOGV = false; @@ -78,6 +81,40 @@ public class DefaultContainerService extends Service { } }; + public DefaultContainerService() { + super("DefaultContainerService"); + setIntentRedelivery(true); + } + + @Override + protected void onHandleIntent(Intent intent) { + if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) { + IPackageManager pm = IPackageManager.Stub.asInterface( + ServiceManager.getService("package")); + String pkg = null; + try { + while ((pkg=pm.nextPackageToClean(pkg)) != null) { + eraseFiles(Environment.getExternalStorageAppDataDirectory(pkg)); + eraseFiles(Environment.getExternalStorageAppMediaDirectory(pkg)); + } + } catch (RemoteException e) { + } + } + } + + void eraseFiles(File path) { + if (path.isDirectory()) { + String[] files = path.list(); + if (files != null) { + for (String file : files) { + eraseFiles(new File(path, file)); + } + } + } + //Log.i(TAG, "Deleting: " + path); + path.delete(); + } + public IBinder onBind(Intent intent) { return mBinder; } diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java index e4ee4ae..1625d9f 100644 --- a/services/java/com/android/server/DevicePolicyManagerService.java +++ b/services/java/com/android/server/DevicePolicyManagerService.java @@ -320,14 +320,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } tag = parser.getName(); if ("admin".equals(tag)) { - DeviceAdminInfo dai = findAdmin( - ComponentName.unflattenFromString( - parser.getAttributeValue(null, "name"))); - if (dai != null) { - ActiveAdmin ap = new ActiveAdmin(dai); - ap.readFromXml(parser); - mAdminMap.put(ap.info.getComponent(), ap); - mAdminList.add(ap); + String name = parser.getAttributeValue(null, "name"); + try { + DeviceAdminInfo dai = findAdmin( + ComponentName.unflattenFromString(name)); + if (dai != null) { + ActiveAdmin ap = new ActiveAdmin(dai); + ap.readFromXml(parser); + mAdminMap.put(ap.info.getComponent(), ap); + mAdminList.add(ap); + } + } catch (RuntimeException e) { + Log.w(TAG, "Failed loading admin " + name, e); } } else if ("failed-password-attempts".equals(tag)) { mFailedPasswordAttempts = Integer.parseInt( diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index c99480f..b1e5d32 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -148,6 +148,10 @@ class PackageManagerService extends IPackageManager.Stub { static final int SCAN_NEW_INSTALL = 1<<4; static final int SCAN_NO_PATHS = 1<<5; + static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName( + "com.android.defcontainer", + "com.android.defcontainer.DefaultContainerService"); + final HandlerThread mHandlerThread = new HandlerThread("PackageManager", Process.THREAD_PRIORITY_BACKGROUND); final PackageHandler mHandler; @@ -298,6 +302,7 @@ class PackageManagerService extends IPackageManager.Stub { static final int END_COPY = 4; static final int INIT_COPY = 5; static final int MCS_UNBIND = 6; + static final int START_CLEANING_PACKAGE = 7; // Delay time in millisecs static final int BROADCAST_DELAY = 10 * 1000; private ServiceConnection mDefContainerConn = new ServiceConnection() { @@ -328,9 +333,7 @@ class PackageManagerService extends IPackageManager.Stub { case INIT_COPY: { InstallArgs args = (InstallArgs) msg.obj; args.createCopyFile(); - Intent service = new Intent().setComponent(new ComponentName( - "com.android.defcontainer", - "com.android.defcontainer.DefaultContainerService")); + Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT); if (mContainerService != null) { // No need to add to pending list. Use remote stub directly handleStartCopy(args); @@ -405,6 +408,15 @@ class PackageManagerService extends IPackageManager.Stub { } break; } + case START_CLEANING_PACKAGE: { + String packageName = (String)msg.obj; + synchronized (mPackages) { + if (!mSettings.mPackagesToBeCleaned.contains(packageName)) { + mSettings.mPackagesToBeCleaned.add(packageName); + } + } + startCleaningPackages(); + } break; } } @@ -2823,6 +2835,9 @@ class PackageManagerService extends IPackageManager.Stub { mSettings.insertPackageSettingLP(pkgSetting, pkg); // Add the new setting to mPackages mPackages.put(pkg.applicationInfo.packageName, pkg); + // Make sure we don't accidentally delete its data. + mSettings.mPackagesToBeCleaned.remove(pkgName); + int N = pkg.providers.size(); StringBuilder r = null; int i; @@ -4018,7 +4033,47 @@ class PackageManagerService extends IPackageManager.Stub { } } } + + public String nextPackageToClean(String lastPackage) { + synchronized (mPackages) { + if (!mMediaMounted) { + // If the external storage is no longer mounted at this point, + // the caller may not have been able to delete all of this + // packages files and can not delete any more. Bail. + return null; + } + if (lastPackage != null) { + mSettings.mPackagesToBeCleaned.remove(lastPackage); + } + return mSettings.mPackagesToBeCleaned.size() > 0 + ? mSettings.mPackagesToBeCleaned.get(0) : null; + } + } + void schedulePackageCleaning(String packageName) { + mHandler.sendMessage(mHandler.obtainMessage(START_CLEANING_PACKAGE, packageName)); + } + + void startCleaningPackages() { + synchronized (mPackages) { + if (!mMediaMounted) { + return; + } + if (mSettings.mPackagesToBeCleaned.size() <= 0) { + return; + } + } + Intent intent = new Intent(PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE); + intent.setComponent(DEFAULT_CONTAINER_COMPONENT); + IActivityManager am = ActivityManagerNative.getDefault(); + if (am != null) { + try { + am.startService(null, intent, null); + } catch (RemoteException e) { + } + } + } + private final class AppDirObserver extends FileObserver { public AppDirObserver(String path, int mask, boolean isrom) { super(path, mask); @@ -5224,7 +5279,6 @@ class PackageManagerService extends IPackageManager.Stub { * persisting settings for later use * sending a broadcast if necessary */ - private boolean deletePackageX(String packageName, boolean sendBroadCast, boolean deleteCodeAndResources, int flags) { PackageRemovedInfo info = new PackageRemovedInfo(); @@ -5316,6 +5370,7 @@ class PackageManagerService extends IPackageManager.Stub { File dataDir = new File(pkg.applicationInfo.dataDir); dataDir.delete(); } + schedulePackageCleaning(packageName); synchronized (mPackages) { if (outInfo != null) { outInfo.removedUid = mSettings.removePackageLP(packageName); @@ -5328,7 +5383,7 @@ class PackageManagerService extends IPackageManager.Stub { mSettings.updateSharedUserPermsLP(deletedPs, mGlobalGids); } // Save settings now - mSettings.writeLP (); + mSettings.writeLP(); } } @@ -6817,6 +6872,10 @@ class PackageManagerService extends IPackageManager.Stub { final HashMap<String, BasePermission> mPermissionTrees = new HashMap<String, BasePermission>(); + // Packages that have been uninstalled and still need their external + // storage data deleted. + final ArrayList<String> mPackagesToBeCleaned = new ArrayList<String>(); + private final StringBuilder mReadMessages = new StringBuilder(); private static final class PendingPackage extends PackageSettingBase { @@ -7380,6 +7439,14 @@ class PackageManagerService extends IPackageManager.Stub { serializer.endTag(null, "shared-user"); } + if (mPackagesToBeCleaned.size() > 0) { + for (int i=0; i<mPackagesToBeCleaned.size(); i++) { + serializer.startTag(null, "cleaning-package"); + serializer.attribute(null, "name", mPackagesToBeCleaned.get(i)); + serializer.endTag(null, "cleaning-package"); + } + } + serializer.endTag(null, "packages"); serializer.endDocument(); @@ -7412,7 +7479,7 @@ class PackageManagerService extends IPackageManager.Stub { } void writeDisabledSysPackage(XmlSerializer serializer, final PackageSetting pkg) - throws java.io.IOException { + throws java.io.IOException { serializer.startTag(null, "updated-package"); serializer.attribute(null, "name", pkg.name); serializer.attribute(null, "codePath", pkg.codePathString); @@ -7450,7 +7517,7 @@ class PackageManagerService extends IPackageManager.Stub { } void writePackage(XmlSerializer serializer, final PackageSetting pkg) - throws java.io.IOException { + throws java.io.IOException { serializer.startTag(null, "package"); serializer.attribute(null, "name", pkg.name); serializer.attribute(null, "codePath", pkg.codePathString); @@ -7640,6 +7707,11 @@ class PackageManagerService extends IPackageManager.Stub { readPreferredActivitiesLP(parser); } else if(tagName.equals("updated-package")) { readDisabledSysPackageLP(parser); + } else if (tagName.equals("cleaning-package")) { + String name = parser.getAttributeValue(null, "name"); + if (name != null) { + mPackagesToBeCleaned.add(name); + } } else { Log.w(TAG, "Unknown element under <packages>: " + parser.getName()); @@ -7765,7 +7837,7 @@ class PackageManagerService extends IPackageManager.Stub { } private void readDisabledSysPackageLP(XmlPullParser parser) - throws XmlPullParserException, IOException { + throws XmlPullParserException, IOException { String name = parser.getAttributeValue(null, "name"); String codePathStr = parser.getAttributeValue(null, "codePath"); String resourcePathStr = parser.getAttributeValue(null, "resourcePath"); @@ -8435,19 +8507,21 @@ class PackageManagerService extends IPackageManager.Stub { } public void updateExternalMediaStatus(final boolean mediaStatus) { - if (DEBUG_SD_INSTALL) Log.i(TAG, "updateExternalMediaStatus:: mediaStatus=" + - mediaStatus+", mMediaMounted=" + mMediaMounted); - if (mediaStatus == mMediaMounted) { - return; - } - mMediaMounted = mediaStatus; - // Queue up an async operation since the package installation may take a little while. - mHandler.post(new Runnable() { - public void run() { - mHandler.removeCallbacks(this); - updateExternalMediaStatusInner(mediaStatus); + synchronized (mPackages) { + if (DEBUG_SD_INSTALL) Log.i(TAG, "updateExternalMediaStatus:: mediaStatus=" + + mediaStatus+", mMediaMounted=" + mMediaMounted); + if (mediaStatus == mMediaMounted) { + return; } - }); + mMediaMounted = mediaStatus; + // Queue up an async operation since the package installation may take a little while. + mHandler.post(new Runnable() { + public void run() { + mHandler.removeCallbacks(this); + updateExternalMediaStatusInner(mediaStatus); + } + }); + } } void updateExternalMediaStatusInner(boolean mediaStatus) { @@ -8505,6 +8579,7 @@ class PackageManagerService extends IPackageManager.Stub { if (mediaStatus) { if (DEBUG_SD_INSTALL) Log.i(TAG, "Loading packages"); loadMediaPackages(processCids, uidArr); + startCleaningPackages(); } else { if (DEBUG_SD_INSTALL) Log.i(TAG, "Unloading packages"); unloadMediaPackages(processCids, uidArr); diff --git a/test-runner/android/test/mock/MockContext.java b/test-runner/android/test/mock/MockContext.java index 57b22f8..ffd757c 100644 --- a/test-runner/android/test/mock/MockContext.java +++ b/test-runner/android/test/mock/MockContext.java @@ -158,11 +158,21 @@ public class MockContext extends Context { } @Override + public File getExternalFilesDir(String type) { + throw new UnsupportedOperationException(); + } + + @Override public File getCacheDir() { throw new UnsupportedOperationException(); } @Override + public File getExternalCacheDir() { + throw new UnsupportedOperationException(); + } + + @Override public File getDir(String name, int mode) { throw new UnsupportedOperationException(); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java index b670eee..57b5d4e 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java @@ -928,6 +928,12 @@ public final class BridgeContext extends Context { } @Override + public File getExternalCacheDir() { + // TODO Auto-generated method stub + return null; + } + + @Override public ContentResolver getContentResolver() { if (mContentResolver == null) { mContentResolver = new BridgeContentResolver(this); @@ -960,6 +966,12 @@ public final class BridgeContext extends Context { } @Override + public File getExternalFilesDir(String type) { + // TODO Auto-generated method stub + return null; + } + + @Override public String getPackageCodePath() { // TODO Auto-generated method stub return null; |