Merge "Import translations. DO NOT MERGE" into oc-dr1-dev
diff --git a/src/com/android/settings/RegulatoryInfoDisplayActivity.java b/src/com/android/settings/RegulatoryInfoDisplayActivity.java
index fd94b67..043071c 100644
--- a/src/com/android/settings/RegulatoryInfoDisplayActivity.java
+++ b/src/com/android/settings/RegulatoryInfoDisplayActivity.java
@@ -20,15 +20,20 @@
 import android.app.AlertDialog;
 import android.content.DialogInterface;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.SystemProperties;
+import android.support.annotation.VisibleForTesting;
 import android.text.TextUtils;
 import android.view.Gravity;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import java.util.Locale;
+
 /**
  * {@link Activity} that displays regulatory information for the "Regulatory information"
  * preference item, and when "*#07#" is dialed on the Phone keypad. To enable this feature,
@@ -41,7 +46,12 @@
  */
 public class RegulatoryInfoDisplayActivity extends Activity implements
         DialogInterface.OnDismissListener {
+
     private final String REGULATORY_INFO_RESOURCE = "regulatory_info";
+    private static final String DEFAULT_REGULATORY_INFO_FILEPATH =
+            "/data/misc/elabel/regulatory_info.png";
+    private static final String REGULATORY_INFO_FILEPATH_TEMPLATE =
+            "/data/misc/elabel/regulatory_info_%s.png";
 
     /**
      * Display the regulatory info graphic in a dialog window.
@@ -60,7 +70,18 @@
                 .setOnDismissListener(this);
 
         boolean regulatoryInfoDrawableExists = false;
-        int resId = getResourceId();
+
+        final String regulatoryInfoFile = getRegulatoryInfoImageFileName();
+        final Bitmap regulatoryInfoBitmap = BitmapFactory.decodeFile(regulatoryInfoFile);
+
+        if (regulatoryInfoBitmap != null) {
+            regulatoryInfoDrawableExists = true;
+        }
+
+        int resId = 0;
+        if (!regulatoryInfoDrawableExists) {
+            resId = getResourceId();
+        }
         if (resId != 0) {
             try {
                 Drawable d = getDrawable(resId);
@@ -77,8 +98,12 @@
 
         if (regulatoryInfoDrawableExists) {
             View view = getLayoutInflater().inflate(R.layout.regulatory_info, null);
-            ImageView image = (ImageView) view.findViewById(R.id.regulatoryInfo);
-            image.setImageResource(resId);
+            ImageView image = view.findViewById(R.id.regulatoryInfo);
+            if (regulatoryInfoBitmap != null) {
+                image.setImageBitmap(regulatoryInfoBitmap);
+            } else {
+                image.setImageResource(resId);
+            }
             builder.setView(view);
             builder.show();
         } else if (regulatoryText.length() > 0) {
@@ -99,7 +124,7 @@
                 REGULATORY_INFO_RESOURCE, "drawable", getPackageName());
 
         // When hardware sku property exists, use regulatory_info_<sku> resource if valid.
-        String sku = SystemProperties.get("ro.boot.hardware.sku", "");
+        final String sku = getSku();
         if (!TextUtils.isEmpty(sku)) {
             String regulatory_info_res = REGULATORY_INFO_RESOURCE + "_" + sku.toLowerCase();
             int id = getResources().getIdentifier(
@@ -115,4 +140,20 @@
     public void onDismiss(DialogInterface dialog) {
         finish();   // close the activity
     }
+
+    @VisibleForTesting
+    public static String getSku() {
+        return SystemProperties.get("ro.boot.hardware.sku", "");
+    }
+
+    @VisibleForTesting
+    public static String getRegulatoryInfoImageFileName() {
+        final String sku = getSku();
+        if (TextUtils.isEmpty(sku)) {
+            return DEFAULT_REGULATORY_INFO_FILEPATH;
+        } else {
+            return String.format(Locale.US, REGULATORY_INFO_FILEPATH_TEMPLATE,
+                    sku.toLowerCase());
+        }
+    }
 }
diff --git a/src/com/android/settings/bluetooth/BluetoothPairingService.java b/src/com/android/settings/bluetooth/BluetoothPairingService.java
index 58df919..4fedb6d 100644
--- a/src/com/android/settings/bluetooth/BluetoothPairingService.java
+++ b/src/com/android/settings/bluetooth/BluetoothPairingService.java
@@ -120,7 +120,8 @@
         Notification.Builder builder = new Notification.Builder(this,
             BLUETOOTH_NOTIFICATION_CHANNEL)
                 .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
-                .setTicker(res.getString(R.string.bluetooth_notif_ticker));
+                .setTicker(res.getString(R.string.bluetooth_notif_ticker))
+                .setLocalOnly(true);
 
         PendingIntent pairIntent = PendingIntent.getActivity(this, 0,
                 getPairingDialogIntent(this, intent), PendingIntent.FLAG_ONE_SHOT);
diff --git a/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java b/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java
index 59496dd..85c5e45 100644
--- a/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java
+++ b/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java
@@ -181,6 +181,7 @@
                         .setDeleteIntent(PendingIntent.getBroadcast(context, 0, deleteIntent, 0))
                         .setColor(context.getColor(
                                 com.android.internal.R.color.system_notification_accent_color))
+                        .setLocalOnly(true)
                         .build();
 
                 notification.flags |= Notification.FLAG_NO_CLEAR; // Cannot be set with the builder.
diff --git a/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java b/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java
index 0b685d0..630df85 100644
--- a/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java
+++ b/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java
@@ -105,7 +105,17 @@
                 continue;
             }
 
-            long blamedSize = stats.getDataBytes() - stats.getCacheBytes();
+            final long dataSize = stats.getDataBytes();
+            final long cacheQuota = mStatsManager.getCacheQuotaBytes(mUuid, app.uid);
+            final long cacheBytes = stats.getCacheBytes();
+            long blamedSize = dataSize;
+            // Technically, we could overages as freeable on the storage settings screen.
+            // If the app is using more cache than its quota, we would accidentally subtract the
+            // overage from the system size (because it shows up as unused) during our attribution.
+            // Thus, we cap the attribution at the quota size.
+            if (cacheQuota < cacheBytes) {
+                blamedSize = blamedSize - cacheBytes + cacheQuota;
+            }
 
             // This isn't quite right because it slams the first user by user id with the whole code
             // size, but this ensures that we count all apps seen once.
diff --git a/tests/unit/src/com/android/settings/RegulatoryInfoDisplayActivityTest.java b/tests/unit/src/com/android/settings/RegulatoryInfoDisplayActivityTest.java
index 70087ae..b1d94d4 100644
--- a/tests/unit/src/com/android/settings/RegulatoryInfoDisplayActivityTest.java
+++ b/tests/unit/src/com/android/settings/RegulatoryInfoDisplayActivityTest.java
@@ -16,6 +16,25 @@
 
 package com.android.settings;
 
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
 import static android.support.test.espresso.Espresso.onView;
 import static android.support.test.espresso.assertion.ViewAssertions.matches;
 import static android.support.test.espresso.matcher.RootMatchers.isDialog;
@@ -23,28 +42,19 @@
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
 import static junit.framework.Assert.fail;
 
-import android.app.Instrumentation;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class RegulatoryInfoDisplayActivityTest {
+    private static final String TAG = "RegulatoryInfoTest";
 
     private Instrumentation mInstrumentation;
     private Intent mRegulatoryInfoIntent;
+    private UiAutomation mUiAutomation;
 
     @Before
     public void setUp() {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         mRegulatoryInfoIntent = new Intent("android.settings.SHOW_REGULATORY_INFO")
                 .addCategory(Intent.CATEGORY_DEFAULT)
                 .setPackage(mInstrumentation.getTargetContext().getPackageName());
@@ -88,4 +98,55 @@
                 .check(matches(isDisplayed()));
     }
 
+    @Test
+    public void launchRegulatoryInfo_withInfoImage_shouldDisplay() throws IOException {
+        // TODO: Remove "setenforce 0" when selinux rules is updated to give read permission for
+        // regulatory info.
+        mUiAutomation.executeShellCommand("setenforce 0");
+
+        final boolean tempFileCreated = ensureRegulatoryInfoImageExists();
+        try {
+            final Context context = mInstrumentation.getTargetContext();
+            final boolean hasRegulatoryInfo = context.getResources()
+                    .getBoolean(R.bool.config_show_regulatory_info);
+
+            if (!hasRegulatoryInfo) {
+                return;
+            }
+            // Launch intent
+            mInstrumentation.startActivitySync(mRegulatoryInfoIntent);
+
+            onView(withId(R.id.regulatoryInfo))
+                    .inRoot(isDialog())
+                    .check(matches(isDisplayed()));
+        } finally {
+            if (tempFileCreated) {
+                final String filename =
+                        RegulatoryInfoDisplayActivity.getRegulatoryInfoImageFileName();
+                new File(filename).delete();
+                Log.d(TAG, "Deleting temp file " + filename);
+            }
+        }
+    }
+
+    /**
+     * Ensures regulatory label image exists on disk.
+     *
+     * @return true if a test image is created.
+     */
+    private boolean ensureRegulatoryInfoImageExists() throws IOException {
+        final String filename = RegulatoryInfoDisplayActivity.getRegulatoryInfoImageFileName();
+        if (new File(filename).exists()) {
+            return false;
+        }
+        Log.d(TAG, "Creating temp file " + filename);
+        final Bitmap bitmap = Bitmap.createBitmap(400 /* width */, 400 /* height */,
+                Bitmap.Config.ARGB_8888);
+        final FileOutputStream out = new FileOutputStream(filename);
+        bitmap.compress(Bitmap.CompressFormat.PNG, 100 /* quality */, out);
+        out.close();
+        return true;
+    }
+
+
 }
diff --git a/tests/unit/src/com/android/settings/deviceinfo/storage/StorageAsyncLoaderTest.java b/tests/unit/src/com/android/settings/deviceinfo/storage/StorageAsyncLoaderTest.java
index 28bd861..5a3569c 100644
--- a/tests/unit/src/com/android/settings/deviceinfo/storage/StorageAsyncLoaderTest.java
+++ b/tests/unit/src/com/android/settings/deviceinfo/storage/StorageAsyncLoaderTest.java
@@ -29,6 +29,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
+import android.net.TrafficStats;
 import android.os.UserHandle;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -55,6 +56,7 @@
     private static final int SECONDARY_USER_ID = 10;
     private static final String PACKAGE_NAME_1 = "com.blah.test";
     private static final String PACKAGE_NAME_2 = "com.blah.test2";
+    private static final long DEFAULT_QUOTA = 64 * TrafficStats.MB_IN_BYTES;
 
     @Mock
     private StorageStatsSource mSource;
@@ -81,6 +83,7 @@
         mUsers = new ArrayList<>();
         mUsers.add(info);
         when(mUserManager.getUsers()).thenReturn(mUsers);
+        when(mSource.getCacheQuotaBytes(anyString(), anyInt())).thenReturn(DEFAULT_QUOTA);
     }
 
     @Test
@@ -120,13 +123,13 @@
     }
 
     @Test
-    public void testCacheIsIgnored() throws Exception {
+    public void testCacheIsNotIgnored() throws Exception {
         addPackage(PACKAGE_NAME_1, 100, 1, 10, ApplicationInfo.CATEGORY_UNDEFINED);
 
         SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
 
         assertThat(result.size()).isEqualTo(1);
-        assertThat(result.get(PRIMARY_USER_ID).otherAppsSize).isEqualTo(11L);
+        assertThat(result.get(PRIMARY_USER_ID).otherAppsSize).isEqualTo(111L);
     }
 
     @Test
@@ -155,7 +158,7 @@
         SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
 
         assertThat(result.size()).isEqualTo(1);
-        assertThat(result.get(PRIMARY_USER_ID).otherAppsSize).isEqualTo(11L);
+        assertThat(result.get(PRIMARY_USER_ID).otherAppsSize).isEqualTo(111L);
     }
 
     @Test
@@ -207,12 +210,23 @@
         assertThat(result.get(SECONDARY_USER_ID).videoAppsSize).isEqualTo(10L);
     }
 
+    @Test
+    public void testCacheOveragesAreCountedAsFree() throws Exception {
+        addPackage(PACKAGE_NAME_1, DEFAULT_QUOTA + 100, 1, 10, ApplicationInfo.CATEGORY_UNDEFINED);
+
+        SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
+
+        assertThat(result.size()).isEqualTo(1);
+        assertThat(result.get(PRIMARY_USER_ID).otherAppsSize).isEqualTo(DEFAULT_QUOTA + 11);
+    }
+
     private ApplicationInfo addPackage(String packageName, long cacheSize, long codeSize,
             long dataSize, int category) throws Exception {
         StorageStatsSource.AppStorageStats storageStats =
                 mock(StorageStatsSource.AppStorageStats.class);
         when(storageStats.getCodeBytes()).thenReturn(codeSize);
-        when(storageStats.getDataBytes()).thenReturn(dataSize);
+        when(storageStats.getDataBytes()).thenReturn(dataSize + cacheSize);
+        when(storageStats.getCacheBytes()).thenReturn(cacheSize);
         when(mSource.getStatsForPackage(anyString(), eq(packageName), any(UserHandle.class)))
                 .thenReturn(storageStats);