MTP: Add support for reserve storage setting to avoid low storage situations.

Set resource config_mtpReserveSpaceMegabytes to number of megabytes to reserve.
If MTP has dedicated storage this value should be zero, but if MTP is
sharing storage with the rest of the system, set this to a positive value
to ensure that MTP activity does not result in the storage being
too close to full.

BUG: 3250924

Change-Id: I881c87240da268bad1ea1b99ad03673ab85ffdbf
Signed-off-by: Mike Lockwood <lockwood@android.com>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index af0e866..5d03638 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -105,6 +105,16 @@
          removable. -->
     <bool name="config_externalStorageRemovable" product="default">true</bool>
 
+    <!-- Number of megabytes of space to leave unallocated by MTP.
+         MTP will subtract this value from the free space it reports back
+         to the host via GetStorageInfo, and will not allow new files to
+         be added via MTP if there is less than this amount left free in the storage.
+         If MTP has dedicated storage this value should be zero, but if MTP is
+         sharing storage with the rest of the system, set this to a positive value
+         to ensure that MTP activity does not result in the storage being
+         too close to full. -->
+    <integer name="config_mtpReserveSpaceMegabytes">0</integer>
+
     <!-- XXXXX NOTE THE FOLLOWING RESOURCES USE THE WRONG NAMING CONVENTION.
          Please don't copy them, copy anything else. -->
 
diff --git a/media/java/android/media/MtpServer.java b/media/java/android/media/MtpServer.java
index 7f15276..76e6f3f 100644
--- a/media/java/android/media/MtpServer.java
+++ b/media/java/android/media/MtpServer.java
@@ -30,8 +30,8 @@
         System.loadLibrary("media_jni");
     }
 
-    public MtpServer(MtpDatabase database, String storagePath) {
-        native_setup(database, storagePath);
+    public MtpServer(MtpDatabase database, String storagePath, long reserveSpace) {
+        native_setup(database, storagePath, reserveSpace);
     }
 
     @Override
@@ -66,7 +66,8 @@
     // used by the JNI code
     private int mNativeContext;
 
-    private native final void native_setup(MtpDatabase database, String storagePath);
+    private native final void native_setup(MtpDatabase database, String storagePath,
+            long reserveSpace);
     private native final void native_finalize();
     private native final void native_start();
     private native final void native_stop();
diff --git a/media/jni/android_media_MtpServer.cpp b/media/jni/android_media_MtpServer.cpp
index f16cdd9..28a80cb 100644
--- a/media/jni/android_media_MtpServer.cpp
+++ b/media/jni/android_media_MtpServer.cpp
@@ -60,14 +60,17 @@
     MtpDatabase*    mDatabase;
     MtpServer*      mServer;
     String8         mStoragePath;
+    uint64_t        mReserveSpace;
     jobject         mJavaServer;
     int             mFd;
 
 public:
-    MtpThread(MtpDatabase* database, const char* storagePath, jobject javaServer)
+    MtpThread(MtpDatabase* database, const char* storagePath, uint64_t reserveSpace,
+                jobject javaServer)
         :   mDatabase(database),
             mServer(NULL),
             mStoragePath(storagePath),
+            mReserveSpace(reserveSpace),
             mJavaServer(javaServer),
             mFd(-1)
     {
@@ -100,7 +103,7 @@
         }
 
         mServer = new MtpServer(mFd, mDatabase, AID_SDCARD_RW, 0664, 0775);
-        mServer->addStorage(mStoragePath);
+        mServer->addStorage(mStoragePath, mReserveSpace);
         sMutex.unlock();
 
         LOGD("MtpThread mServer->run");
@@ -139,7 +142,8 @@
 #endif // HAVE_ANDROID_OS
 
 static void
-android_media_MtpServer_setup(JNIEnv *env, jobject thiz, jobject javaDatabase, jstring storagePath)
+android_media_MtpServer_setup(JNIEnv *env, jobject thiz, jobject javaDatabase,
+        jstring storagePath, jlong reserveSpace)
 {
 #ifdef HAVE_ANDROID_OS
     LOGD("setup\n");
@@ -147,7 +151,8 @@
     MtpDatabase* database = getMtpDatabase(env, javaDatabase);
     const char *storagePathStr = env->GetStringUTFChars(storagePath, NULL);
 
-    MtpThread* thread = new MtpThread(database, storagePathStr, env->NewGlobalRef(thiz));
+    MtpThread* thread = new MtpThread(database, storagePathStr,
+            reserveSpace, env->NewGlobalRef(thiz));
     env->SetIntField(thiz, field_context, (int)thread);
 
     env->ReleaseStringUTFChars(storagePath, storagePathStr);
@@ -213,7 +218,7 @@
 // ----------------------------------------------------------------------------
 
 static JNINativeMethod gMethods[] = {
-    {"native_setup",                "(Landroid/media/MtpDatabase;Ljava/lang/String;)V",
+    {"native_setup",                "(Landroid/media/MtpDatabase;Ljava/lang/String;J)V",
                                             (void *)android_media_MtpServer_setup},
     {"native_finalize",             "()V",  (void *)android_media_MtpServer_finalize},
     {"native_start",                "()V",  (void *)android_media_MtpServer_start},
diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp
index d65845d..b371e41 100644
--- a/media/mtp/MtpServer.cpp
+++ b/media/mtp/MtpServer.cpp
@@ -104,10 +104,10 @@
 MtpServer::~MtpServer() {
 }
 
-void MtpServer::addStorage(const char* filePath) {
+void MtpServer::addStorage(const char* filePath, uint64_t reserveSpace) {
     int index = mStorages.size() + 1;
     index |= index << 16;   // set high and low part to our index
-    MtpStorage* storage = new MtpStorage(index, filePath, mDatabase);
+    MtpStorage* storage = new MtpStorage(index, filePath, reserveSpace);
     addStorage(storage);
 }
 
@@ -687,6 +687,10 @@
     if (access(path, R_OK) == 0)
         return MTP_RESPONSE_GENERAL_ERROR;
 
+    // check space first
+    if (mSendObjectFileSize > storage->getFreeSpace())
+        return MTP_RESPONSE_STORAGE_FULL;
+
     MtpObjectHandle handle = mDatabase->beginSendObject((const char*)path,
             format, parent, storageID, mSendObjectFileSize, modifiedTime);
     if (handle == kInvalidObjectHandle) {
diff --git a/media/mtp/MtpServer.h b/media/mtp/MtpServer.h
index 5aee4ea..605d5a2 100644
--- a/media/mtp/MtpServer.h
+++ b/media/mtp/MtpServer.h
@@ -67,7 +67,7 @@
                                     int fileGroup, int filePerm, int directoryPerm);
     virtual             ~MtpServer();
 
-    void                addStorage(const char* filePath);
+    void                addStorage(const char* filePath, uint64_t reserveSpace);
     inline void         addStorage(MtpStorage* storage) { mStorages.push(storage); }
     MtpStorage*         getStorage(MtpStorageID id);
     void                run();
diff --git a/media/mtp/MtpStorage.cpp b/media/mtp/MtpStorage.cpp
index eccf186..abc23de 100644
--- a/media/mtp/MtpStorage.cpp
+++ b/media/mtp/MtpStorage.cpp
@@ -32,11 +32,11 @@
 
 namespace android {
 
-MtpStorage::MtpStorage(MtpStorageID id, const char* filePath, MtpDatabase* db)
+MtpStorage::MtpStorage(MtpStorageID id, const char* filePath, uint64_t reserveSpace)
     :   mStorageID(id),
         mFilePath(filePath),
-        mDatabase(db),
-        mMaxCapacity(0)
+        mMaxCapacity(0),
+        mReserveSpace(reserveSpace)
 {
     LOGD("MtpStorage id: %d path: %s\n", id, filePath);
 }
@@ -70,7 +70,8 @@
     struct statfs   stat;
     if (statfs(mFilePath, &stat))
         return -1;
-    return (uint64_t)stat.f_bavail * (uint64_t)stat.f_bsize;
+    uint64_t freeSpace = (uint64_t)stat.f_bavail * (uint64_t)stat.f_bsize;
+    return (freeSpace > mReserveSpace ? freeSpace - mReserveSpace : 0);
 }
 
 const char* MtpStorage::getDescription() const {
diff --git a/media/mtp/MtpStorage.h b/media/mtp/MtpStorage.h
index b13b926..ace720b 100644
--- a/media/mtp/MtpStorage.h
+++ b/media/mtp/MtpStorage.h
@@ -28,11 +28,13 @@
 private:
     MtpStorageID            mStorageID;
     const char*             mFilePath;
-    MtpDatabase*            mDatabase;
     uint64_t                mMaxCapacity;
+    // amount of free space to leave unallocated
+    uint64_t                mReserveSpace;
 
 public:
-                            MtpStorage(MtpStorageID id, const char* filePath, MtpDatabase* db);
+                            MtpStorage(MtpStorageID id, const char* filePath,
+                                    uint64_t reserveSpace);
     virtual                 ~MtpStorage();
 
     inline MtpStorageID     getStorageID() const { return mStorageID; }