Merge "Support RRO for ringtones, notifications and alarms"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 88cae0f..b9cccb0 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -5,6 +5,7 @@
         android:versionCode="1011">
 
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@@ -59,6 +60,7 @@
         <receiver android:name=".MediaReceiver">
             <intent-filter>
                 <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/>
                 <action android:name="android.intent.action.LOCALE_CHANGED" />
             </intent-filter>
             <intent-filter>
@@ -102,6 +104,8 @@
 
         <service android:name="MtpService" />
 
+        <service android:name="RingtoneOverlayService" />
+
         <activity android:name="RingtonePickerActivity"
                 android:theme="@style/PickerDialogTheme"
                 android:enabled="@*android:bool/config_defaultRingtonePickerEnabled"
diff --git a/res/raw/default_alarm_alert.ogg b/res/raw/default_alarm_alert.ogg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/res/raw/default_alarm_alert.ogg
diff --git a/res/raw/default_notification_sound.ogg b/res/raw/default_notification_sound.ogg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/res/raw/default_notification_sound.ogg
diff --git a/res/raw/default_ringtone.ogg b/res/raw/default_ringtone.ogg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/res/raw/default_ringtone.ogg
diff --git a/src/com/android/providers/media/MediaReceiver.java b/src/com/android/providers/media/MediaReceiver.java
index 12ab2c6..7f100e0 100644
--- a/src/com/android/providers/media/MediaReceiver.java
+++ b/src/com/android/providers/media/MediaReceiver.java
@@ -44,6 +44,8 @@
             // Scan internal only.
             scan(context, MediaProvider.INTERNAL_VOLUME);
 
+        } else if (Intent.ACTION_DEVICE_CUSTOMIZATION_READY.equals(action)) {
+            initResourceRingtones(context);
         } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
             scanTranslatable(context);
 
@@ -85,6 +87,11 @@
         }
     }
 
+    private void initResourceRingtones(Context context) {
+        context.startService(
+                new Intent(context, RingtoneOverlayService.class));
+    }
+
     private void scan(Context context, String volume) {
         Bundle args = new Bundle();
         args.putString("volume", volume);
diff --git a/src/com/android/providers/media/RingtoneOverlayService.java b/src/com/android/providers/media/RingtoneOverlayService.java
new file mode 100644
index 0000000..8a35a68
--- /dev/null
+++ b/src/com/android/providers/media/RingtoneOverlayService.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.media;
+
+import android.annotation.IdRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Service;
+import android.content.Intent;
+import android.media.MediaScannerConnection;
+import android.media.MediaScannerConnection.OnScanCompletedListener;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.IBinder;
+import android.provider.Settings.System;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.SynchronousQueue;
+
+/**
+ * Service to copy and set customization of default sounds
+ */
+public class RingtoneOverlayService extends Service {
+    private static final String TAG = "RingtoneOverlayService";
+    private static final boolean DEBUG = false;
+
+    @Override
+    public int onStartCommand(@Nullable final Intent intent, final int flags, final int startId) {
+        AsyncTask.execute(() -> {
+            updateRingtones();
+            stopSelf();
+        });
+
+        // Try again later if we are killed before we finish.
+        return Service.START_REDELIVER_INTENT;
+    }
+
+    @Override
+    public IBinder onBind(@Nullable final Intent intent) {
+        return null;
+    }
+
+    private void updateRingtones() {
+        copyResourceAndSetAsSound(R.raw.default_ringtone,
+                System.RINGTONE, Environment.DIRECTORY_RINGTONES);
+        copyResourceAndSetAsSound(R.raw.default_notification_sound,
+                System.NOTIFICATION_SOUND, Environment.DIRECTORY_NOTIFICATIONS);
+        copyResourceAndSetAsSound(R.raw.default_alarm_alert,
+                System.ALARM_ALERT, Environment.DIRECTORY_ALARMS);
+    }
+
+    /* If the resource contains any data, copy a resource to the file system, scan it, and set the
+     * file URI as the default for a sound. */
+    private void copyResourceAndSetAsSound(@IdRes final int id, @NonNull final String name,
+            @NonNull final String subPath) {
+        final File destDir = Environment.getExternalStoragePublicDirectory(subPath);
+        if (!destDir.exists() && !destDir.mkdirs()) {
+            Slog.e(TAG, "can't create " + destDir.getAbsolutePath());
+            return;
+        }
+
+        final File dest = new File(destDir, "default_" + name + ".ogg");
+        try (
+            InputStream is = getResources().openRawResource(id);
+            FileOutputStream os = new FileOutputStream(dest);
+        ) {
+            if (is.available() > 0) {
+                FileUtils.copy(is, os);
+                final Uri uri = scanFile(dest);
+                if (uri != null) {
+                    set(name, uri);
+                }
+            } else {
+                // TODO Shall we remove any former copied resource in this case and unset
+                // the defaults if we use this event a second time to clear the data?
+                if (DEBUG) Slog.d(TAG, "Resource for " + name + " has no overlay");
+            }
+        } catch (IOException e) {
+            Slog.e(TAG, "Unable to open resource for " + name + ": " + e);
+        }
+    }
+
+    private Uri scanFile(@NonNull final File file) {
+        SynchronousQueue<Uri> queue = new SynchronousQueue<>();
+
+        if (DEBUG) Slog.d(TAG, "Scanning " + file.getAbsolutePath());
+        MediaScannerConnection.scanFile(this, new String[] { file.getAbsolutePath() }, null,
+                new OnScanCompletedListener() {
+                    @Override
+                    public void onScanCompleted(String path, Uri uri) {
+                        if (uri == null) {
+                            file.delete();
+                            return;
+                        }
+                        try {
+                            queue.put(uri);
+                        } catch (InterruptedException e) {
+                            Slog.w(TAG, "Unable to put new Uri in queue", e);
+                        }
+                    }
+                });
+
+        try {
+            return queue.take();
+        } catch (InterruptedException e) {
+            Slog.w(TAG, "Unable to take new Uri from queue", e);
+        }
+
+        return null;
+    }
+
+    private void set(@NonNull final String name, @NonNull final Uri uri) {
+        final Uri settingUri = System.getUriFor(name);
+        RingtoneManager.setActualDefaultRingtoneUri(this,
+                RingtoneManager.getDefaultType(settingUri), uri);
+        System.putInt(getContentResolver(), name + "_set", 1);
+    }
+}