Merge "Fix an issue where running multiple test cases would cause a NPE in CarStorageMonitoringService"
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index bd46857..568d611 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -32,6 +32,7 @@
     field public static final java.lang.String PERMISSION_MILEAGE = "android.car.permission.CAR_MILEAGE";
     field public static final deprecated java.lang.String PERMISSION_MOCK_VEHICLE_HAL = "android.car.permission.CAR_MOCK_VEHICLE_HAL";
     field public static final java.lang.String PERMISSION_SPEED = "android.car.permission.CAR_SPEED";
+    field public static final java.lang.String PERMISSION_STORAGE_MONITORING = "android.car.permission.STORAGE_MONITORING";
     field public static final java.lang.String PERMISSION_VEHICLE_DYNAMICS_STATE = "android.car.permission.VEHICLE_DYNAMICS_STATE";
     field public static final java.lang.String PERMISSION_VENDOR_EXTENSION = "android.car.permission.CAR_VENDOR_EXTENSION";
     field public static final java.lang.String PROJECTION_SERVICE = "projection";
@@ -1004,6 +1005,7 @@
 
   public final class CarStorageMonitoringManager {
     method public int getPreEolIndicatorStatus() throws android.car.CarNotConnectedException;
+    method public android.car.storagemonitoring.WearEstimate getWearEstimate() throws android.car.CarNotConnectedException;
     field public static final int PRE_EOL_INFO_NORMAL = 1; // 0x1
     field public static final int PRE_EOL_INFO_UNKNOWN = 0; // 0x0
     field public static final int PRE_EOL_INFO_URGENT = 3; // 0x3
diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java
index c894504..6101797 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -300,6 +300,14 @@
     @SystemApi
     public static final String PERMISSION_CAR_DIAGNOSTIC_CLEAR = "android.car.permission.DIAGNOSTIC_CLEAR";
 
+    /**
+     * Permissions necessary to clear diagnostic information.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String PERMISSION_STORAGE_MONITORING = "android.car.permission.STORAGE_MONITORING";
+
     /** Type of car connection: platform runs directly in car. */
     public static final int CONNECTION_TYPE_EMBEDDED = 5;
 
diff --git a/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java b/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java
index 6d7aede..aa2ad84 100644
--- a/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java
+++ b/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java
@@ -67,5 +67,28 @@
         }
         return PRE_EOL_INFO_UNKNOWN;
     }
+
+    /**
+     * This method returns the value of the wear estimate indicators for the flash storage
+     * as retrieved during the current boot cycle.
+     *
+     * The indicators are guaranteed to be a lower-bound on the actual wear of the storage.
+     * Current technology in common automotive usage offers estimates in 10% increments.
+     *
+     * If either or both indicators are not available, they will be reported as UNKNOWN.
+     * @return
+     * @throws CarNotConnectedException
+     */
+    public WearEstimate getWearEstimate() throws CarNotConnectedException {
+        try {
+            return mService.getWearEstimate();
+        } catch (IllegalStateException e) {
+            CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
+        } catch (RemoteException e) {
+            throw new CarNotConnectedException();
+        }
+        return new WearEstimate(WearEstimate.UNKNOWN, WearEstimate.UNKNOWN);
+    }
+
 }
 
diff --git a/car-lib/src/android/car/storagemonitoring/ICarStorageMonitoring.aidl b/car-lib/src/android/car/storagemonitoring/ICarStorageMonitoring.aidl
index 8d05f79..61910c5 100644
--- a/car-lib/src/android/car/storagemonitoring/ICarStorageMonitoring.aidl
+++ b/car-lib/src/android/car/storagemonitoring/ICarStorageMonitoring.aidl
@@ -16,10 +16,17 @@
 
 package android.car.storagemonitoring;
 
+import android.car.storagemonitoring.WearEstimate;
+
 /** @hide */
 interface ICarStorageMonitoring {
   /**
    * Returns the value of the PRE_EOL register.
    */
   int getPreEolIndicatorStatus() = 1;
+
+  /**
+   * Returns the current wear estimate indicators.
+   */
+  WearEstimate getWearEstimate() = 2;
 }
diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml
index e39fb9e..05073e3 100644
--- a/service/AndroidManifest.xml
+++ b/service/AndroidManifest.xml
@@ -154,6 +154,11 @@
                 android:label="@string/car_permission_car_cluster_control"
                 android:description="@string/car_permission_desc_car_cluster_control" />
 
+    <permission android:name="android.car.permission.STORAGE_MONITORING"
+        android:protectionLevel="system|signature"
+        android:label="@string/car_permission_label_storage_monitoring"
+        android:description="@string/car_permission_desc_storage_monitoring" />
+
     <uses-permission android:name="android.permission.CALL_PHONE" />
     <uses-permission android:name="android.permission.DEVICE_POWER" />
     <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS" />
diff --git a/service/res/values/strings.xml b/service/res/values/strings.xml
index 6cfff9a..37503ab 100644
--- a/service/res/values/strings.xml
+++ b/service/res/values/strings.xml
@@ -125,4 +125,9 @@
     <string name="car_permission_label_vms_subscriber">VMS subscriber</string>
     <!-- Permission text: apps can receive VMS messages from the car [CHAR LIMIT=NONE] -->
     <string name="car_permission_desc_vms_subscriber">Subscribe to vms messages</string>
+
+    <!-- Permission text: apps can monitor flash storage usage [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_storage_monitoring">Flash storage monitoring</string>
+    <!-- Permission text: apps can monitor flash storage usage [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_storage_monitoring">Monitor flash storage usage</string>
 </resources>
diff --git a/service/src/com/android/car/CarStorageMonitoringService.java b/service/src/com/android/car/CarStorageMonitoringService.java
index 8182e2e..113adf9 100644
--- a/service/src/com/android/car/CarStorageMonitoringService.java
+++ b/service/src/com/android/car/CarStorageMonitoringService.java
@@ -16,12 +16,13 @@
 
 package com.android.car;
 
+import android.car.Car;
 import android.car.storagemonitoring.ICarStorageMonitoring;
+import android.car.storagemonitoring.WearEstimate;
 import android.content.Context;
 import android.content.Intent;
 import android.util.Log;
-import com.android.car.storagemonitoring.EMmcWearInformationProvider;
-import com.android.car.storagemonitoring.UfsWearInformationProvider;
+import com.android.car.internal.CarPermission;
 import com.android.car.storagemonitoring.WearInformation;
 import com.android.car.storagemonitoring.WearInformationProvider;
 import java.io.File;
@@ -40,6 +41,8 @@
     private final File mWearInfoFile;
     private final OnShutdownReboot mOnShutdownReboot;
 
+    private final CarPermission mStorageMonitoringPermission;
+
     private UptimeTracker mUptimeTracker;
     private Optional<WearInformation> mWearInformation = Optional.empty();
 
@@ -49,6 +52,8 @@
         mWearInfoFile = new File(mContext.getFilesDir(), WEAR_INFO_FILENAME);
         mOnShutdownReboot = new OnShutdownReboot(mContext);
         mWearInformationProviders = systemInterface.getFlashWearInformationProviders();
+        mStorageMonitoringPermission =
+                new CarPermission(mContext, Car.PERMISSION_STORAGE_MONITORING);
     }
 
     /**
@@ -98,7 +103,18 @@
 
     @Override
     public int getPreEolIndicatorStatus() {
+        mStorageMonitoringPermission.assertGranted();
+
         return mWearInformation.map(wi -> wi.preEolInfo)
                 .orElse(WearInformation.UNKNOWN_PRE_EOL_INFO);
     }
+
+    @Override
+    public WearEstimate getWearEstimate() {
+        mStorageMonitoringPermission.assertGranted();
+
+        return mWearInformation.map(wi ->
+                new WearEstimate(wi.lifetimeEstimateA,wi.lifetimeEstimateB)).orElse(
+                    new WearEstimate(WearEstimate.UNKNOWN, WearEstimate.UNKNOWN));
+    }
 }
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index e7c53eb..c308d81 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -248,7 +248,7 @@
             case Car.BLUETOOTH_SERVICE:
                 return mCarBluetoothService;
             case Car.STORAGE_MONITORING_SERVICE:
-                // TODO(egranata): enforce permission here
+                assertPermission(mContext, Car.PERMISSION_STORAGE_MONITORING);
                 return mCarStorageMonitoringService;
             default:
                 Log.w(CarLog.TAG_SERVICE, "getCarService for unknown service:" + serviceName);
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
index 2a96898..db09cba 100644
--- a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -33,6 +33,7 @@
     <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME"/>
     <uses-permission android:name="android.car.permission.VEHICLE_DYNAMICS_STATE"/>
     <uses-permission android:name="android.car.permission.CAR_DISPLAY_IN_CLUSTER"/>
+    <uses-permission android:name="android.car.permission.STORAGE_MONITORING" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <uses-permission android:name="android.permission.MANAGE_USB" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/storagelifetime/StorageLifetimeFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/storagelifetime/StorageLifetimeFragment.java
index 51150ac..b5165a8 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/storagelifetime/StorageLifetimeFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/storagelifetime/StorageLifetimeFragment.java
@@ -64,8 +64,10 @@
             mStorageManager =
                 (CarStorageMonitoringManager) mActivity.getCar().getCarManager(
                         Car.STORAGE_MONITORING_SERVICE);
-            mStorageWearInfo.setText("Pre EOL indicator: " + preEolToString(
-                mStorageManager.getPreEolIndicatorStatus()));
+
+            mStorageWearInfo.setText("Wear estimate: " +
+                mStorageManager.getWearEstimate() + "\nPre EOL indicator: " +
+                preEolToString(mStorageManager.getPreEolIndicatorStatus()));
         } catch (android.car.CarNotConnectedException|
                  android.support.car.CarNotConnectedException e) {
             Log.e(TAG, "Car not connected or not supported", e);
diff --git a/tests/carservice_test/AndroidManifest.xml b/tests/carservice_test/AndroidManifest.xml
index b610e78..7330aaa 100644
--- a/tests/carservice_test/AndroidManifest.xml
+++ b/tests/carservice_test/AndroidManifest.xml
@@ -24,6 +24,7 @@
     <uses-permission android:name="android.car.permission.CONTROL_APP_BLOCKING" />
     <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME" />
     <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_SETTINGS" />
+    <uses-permission android:name="android.car.permission.STORAGE_MONITORING" />
     <instrumentation android:name="android.test.InstrumentationTestRunner"
             android:targetPackage="com.android.car.test"
             android:label="Tests for Car APIs"/>
diff --git a/tests/carservice_test/src/com/android/car/CarStorageMonitoringTest.java b/tests/carservice_test/src/com/android/car/CarStorageMonitoringTest.java
index 532bc1c..cd104f8 100644
--- a/tests/carservice_test/src/com/android/car/CarStorageMonitoringTest.java
+++ b/tests/carservice_test/src/com/android/car/CarStorageMonitoringTest.java
@@ -18,6 +18,7 @@
 
 import android.car.Car;
 import android.car.storagemonitoring.CarStorageMonitoringManager;
+import android.car.storagemonitoring.WearEstimate;
 import android.test.suitebuilder.annotation.MediumTest;
 import com.android.car.storagemonitoring.WearInformation;
 
@@ -26,14 +27,14 @@
 public class CarStorageMonitoringTest extends MockedCarTestBase {
     private static final String TAG = CarStorageMonitoringTest.class.getSimpleName();
 
+    private static final WearInformation DEFAULT_WEAR_INFORMATION =
+        new WearInformation(10, 0, WearInformation.PRE_EOL_INFO_NORMAL);
+
     private CarStorageMonitoringManager mCarStorageMonitoringManager;
 
     @Override
     protected synchronized void configureFakeSystemInterface() {
-        setFlashWearInformation(new WearInformation(
-            WearInformation.UNKNOWN_LIFETIME_ESTIMATE,
-            WearInformation.UNKNOWN_LIFETIME_ESTIMATE,
-            WearInformation.PRE_EOL_INFO_NORMAL));
+        setFlashWearInformation(DEFAULT_WEAR_INFORMATION);
     }
 
     @Override
@@ -45,7 +46,15 @@
     }
 
     public void testReadPreEolInformation() throws Exception {
-        assertEquals(WearInformation.PRE_EOL_INFO_NORMAL,
+        assertEquals(DEFAULT_WEAR_INFORMATION.preEolInfo,
                 mCarStorageMonitoringManager.getPreEolIndicatorStatus());
     }
+
+    public void testReadWearEstimate() throws Exception {
+        final WearEstimate wearEstimate = mCarStorageMonitoringManager.getWearEstimate();
+
+        assertNotNull(wearEstimate);
+        assertEquals(DEFAULT_WEAR_INFORMATION.lifetimeEstimateA, wearEstimate.typeA);
+        assertEquals(DEFAULT_WEAR_INFORMATION.lifetimeEstimateB, wearEstimate.typeB);
+    }
 }