Add back the legacy SoundPicker and create a separate directory for the new picker.

We're bringing back the legacy SoundPicker and moving the new picker implementation into a separate directory (SoundPicker2). This way the currently released picker won't be using any unreleased (flagged) apis, and we won't leak the new features until they are un-flagged.

Bug: 293846645
Test: N/A
Change-Id: Iaf5780bc0efcb2095c9eba4129a75d06982bd140
diff --git a/packages/SoundPicker/Android.bp b/packages/SoundPicker/Android.bp
index 235e672..2c89d6d 100644
--- a/packages/SoundPicker/Android.bp
+++ b/packages/SoundPicker/Android.bp
@@ -7,40 +7,22 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-android_library {
-    name: "SoundPickerLib",
-    srcs: [
-        "src/**/*.java",
-    ],
-    resource_dirs: [
-        "res",
-    ],
-    static_libs: [
-        "androidx.appcompat_appcompat",
-        "hilt_android",
-        "guava",
-        "androidx.recyclerview_recyclerview",
-        "androidx-constraintlayout_constraintlayout",
-        "androidx.viewpager2_viewpager2",
-        "com.google.android.material_material",
-    ],
-}
-
 android_app {
     name: "SoundPicker",
     defaults: ["platform_app_defaults"],
     manifest: "AndroidManifest.xml",
-    static_libs: ["SoundPickerLib"],
+
+    static_libs: [
+        "androidx.appcompat_appcompat",
+    ],
+    resource_dirs: [
+        "res",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+
     platform_apis: true,
     certificate: "media",
     privileged: true,
-
-    optimize: {
-        enabled: true,
-        optimize: true,
-        shrink: true,
-        shrink_resources: true,
-        obfuscate: false,
-        proguard_compatibility: false,
-    },
 }
diff --git a/packages/SoundPicker/AndroidManifest.xml b/packages/SoundPicker/AndroidManifest.xml
index 934b003..44295a5 100644
--- a/packages/SoundPicker/AndroidManifest.xml
+++ b/packages/SoundPicker/AndroidManifest.xml
@@ -1,6 +1,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.soundpicker"
-        android:sharedUserId="android.media">
+          package="com.android.soundpicker"
+          android:sharedUserId="android.media">
 
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@@ -9,16 +9,12 @@
     <uses-permission android:name="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
 
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
-
     <application
-            android:name=".RingtonePickerApplication"
-            android:allowBackup="false"
-            android:label="@string/app_label"
-            android:theme="@style/Theme.AppCompat"
-            android:supportsRtl="true">
+        android:allowBackup="false"
+        android:label="@string/app_label"
+        android:supportsRtl="true">
         <receiver android:name="RingtoneReceiver"
-                android:exported="true">
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/>
             </intent-filter>
@@ -27,17 +23,14 @@
         <service android:name="RingtoneOverlayService" />
 
         <activity android:name="RingtonePickerActivity"
-                android:theme="@style/Theme.AppCompat.Dialog"
-                android:enabled="@*android:bool/config_defaultRingtonePickerEnabled"
-                android:excludeFromRecents="true"
-                android:exported="true">
+                  android:theme="@style/PickerDialogTheme"
+                  android:enabled="@*android:bool/config_defaultRingtonePickerEnabled"
+                  android:excludeFromRecents="true"
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.RINGTONE_PICKER" />
                 <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.RINGTONE_PICKER_SOUND" />
-                <category android:name="android.intent.category.RINGTONE_PICKER_VIBRATION" />
-                <category android:name="android.intent.category.RINGTONE_PICKER_RINGTONE" />
             </intent-filter>
         </activity>
     </application>
-</manifest>
+</manifest>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/layout/add_new_sound_item.xml b/packages/SoundPicker/res/layout/add_new_sound_item.xml
index 024b97e..57b70d7 100644
--- a/packages/SoundPicker/res/layout/add_new_sound_item.xml
+++ b/packages/SoundPicker/res/layout/add_new_sound_item.xml
@@ -19,9 +19,7 @@
               android:layout_width="fill_parent"
               android:layout_height="wrap_content"
               android:gravity="center_vertical"
-              android:background="?android:attr/selectableItemBackground"
-              android:focusable="true"
-              android:clickable="true">
+              android:background="?android:attr/selectableItemBackground">
 
     <ImageView
         android:layout_width="24dp"
@@ -31,19 +29,19 @@
         android:scaleType="centerCrop"
         android:layout_marginRight="24dp"
         android:layout_marginLeft="24dp"
-        android:src="@drawable/ic_add"/>
+        android:src="@drawable/ic_add" />
 
-    <TextView
-        android:id="@+id/add_new_sound_text"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:minHeight="?android:attr/listPreferredItemHeightSmall"
-        android:text="@null"
-        android:textColor="?android:attr/colorAccent"
-        android:textAppearance="?android:attr/textAppearanceMedium"
-        android:maxLines="3"
-        android:gravity="center_vertical"
-        android:paddingEnd="?android:attr/dialogPreferredPadding"
-        android:drawablePadding="20dp"
-        android:ellipsize="marquee"/>
+    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/add_new_sound_text"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:minHeight="?android:attr/listPreferredItemHeightSmall"
+              android:text="@null"
+              android:textColor="?android:attr/colorAccent"
+              android:textAppearance="?android:attr/textAppearanceMedium"
+              android:maxLines="3"
+              android:gravity="center_vertical"
+              android:paddingEnd="?android:attr/dialogPreferredPadding"
+              android:drawablePadding="20dp"
+              android:ellipsize="marquee" />
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/layout/radio_with_work_badge.xml b/packages/SoundPicker/res/layout/radio_with_work_badge.xml
index 36ac93e..2e44b6f 100644
--- a/packages/SoundPicker/res/layout/radio_with_work_badge.xml
+++ b/packages/SoundPicker/res/layout/radio_with_work_badge.xml
@@ -14,14 +14,12 @@
      limitations under the License.
 -->
 
-<com.android.soundpicker.CheckedListItem
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="wrap_content"
-    android:gravity="center_vertical"
-    android:background="?android:attr/selectableItemBackground"
-    android:focusable="true"
-    android:clickable="true">
+<com.android.soundpicker.CheckedListItem xmlns:android="http://schemas.android.com/apk/res/android"
+     android:layout_width="fill_parent"
+     android:layout_height="wrap_content"
+     android:gravity="center_vertical"
+     android:background="?android:attr/selectableItemBackground"
+    >
 
     <CheckedTextView
         android:id="@+id/checked_text_view"
@@ -37,7 +35,7 @@
         android:drawablePadding="20dp"
         android:ellipsize="marquee"
         android:layout_toLeftOf="@+id/work_icon"
-        android:maxLines="3"/>
+        android:maxLines="3" />
 
     <ImageView
         android:id="@id/work_icon"
@@ -46,5 +44,5 @@
         android:layout_alignParentRight="true"
         android:layout_centerVertical="true"
         android:scaleType="centerCrop"
-        android:layout_marginRight="20dp"/>
-</com.android.soundpicker.CheckedListItem>
+        android:layout_marginRight="20dp" />
+</com.android.soundpicker.CheckedListItem>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/values/strings.xml b/packages/SoundPicker/res/values/strings.xml
index ab7b95a..04a2c2b 100644
--- a/packages/SoundPicker/res/values/strings.xml
+++ b/packages/SoundPicker/res/values/strings.xml
@@ -40,8 +40,4 @@
 
     <!-- Text for the name of the app. [CHAR LIMIT=12] -->
     <string name="app_label">Sounds</string>
-
-    <string name="empty_list">The list is empty</string>
-    <string name="sound_page_title">Sound</string>
-    <string name="vibration_page_title">Vibration</string>
 </resources>
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
index 90a14f9..ea46c0c 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
@@ -16,19 +16,43 @@
 
 package com.android.soundpicker;
 
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.database.Cursor;
+import android.database.CursorWrapper;
+import android.media.AudioAttributes;
+import android.media.Ringtone;
 import android.media.RingtoneManager;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
 import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.MediaStore;
+import android.provider.Settings;
 import android.util.Log;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.CursorAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
 
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentTransaction;
-import androidx.lifecycle.ViewModelProvider;
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
 
-import dagger.hilt.android.AndroidEntryPoint;
+import java.io.IOException;
+import java.util.regex.Pattern;
 
 /**
  * The {@link RingtonePickerActivity} allows the user to choose one from all of the
@@ -36,183 +60,727 @@
  *
  * @see RingtoneManager#ACTION_RINGTONE_PICKER
  */
-@AndroidEntryPoint(AppCompatActivity.class)
-public final class RingtonePickerActivity extends Hilt_RingtonePickerActivity {
+public final class RingtonePickerActivity extends AlertActivity implements
+        AdapterView.OnItemSelectedListener, Runnable, DialogInterface.OnClickListener,
+        AlertController.AlertParams.OnPrepareListViewListener {
+
+    private static final int POS_UNKNOWN = -1;
 
     private static final String TAG = "RingtonePickerActivity";
-    // TODO: Use the extra keys from RingtoneManager once they're added.
-    private static final String EXTRA_RINGTONE_PICKER_CATEGORY = "EXTRA_RINGTONE_PICKER_CATEGORY";
-    private static final String EXTRA_VIBRATION_SHOW_DEFAULT = "EXTRA_VIBRATION_SHOW_DEFAULT";
-    private static final String EXTRA_VIBRATION_DEFAULT_URI = "EXTRA_VIBRATION_DEFAULT_URI";
-    private static final String EXTRA_VIBRATION_SHOW_SILENT = "EXTRA_VIBRATION_SHOW_SILENT";
-    private static final String EXTRA_VIBRATION_EXISTING_URI = "EXTRA_VIBRATION_EXISTING_URI";
-    private static final boolean RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED = false;
 
-    private RingtonePickerViewModel mRingtonePickerViewModel;
+    private static final int DELAY_MS_SELECTION_PLAYED = 300;
+
+    private static final String COLUMN_LABEL = MediaStore.Audio.Media.TITLE;
+
+    private static final String SAVE_CLICKED_POS = "clicked_pos";
+
+    private static final String SOUND_NAME_RES_PREFIX = "sound_name_";
+
+    private static final int ADD_FILE_REQUEST_CODE = 300;
+
+    private RingtoneManager mRingtoneManager;
+    private int mType;
+
+    private Cursor mCursor;
+    private Handler mHandler;
+    private BadgedRingtoneAdapter mAdapter;
+
+    /** The position in the list of the 'Silent' item. */
+    private int mSilentPos = POS_UNKNOWN;
+
+    /** The position in the list of the 'Default' item. */
+    private int mDefaultRingtonePos = POS_UNKNOWN;
+
+    /** The position in the list of the ringtone to sample. */
+    private int mSampleRingtonePos = POS_UNKNOWN;
+
+    /** Whether this list has the 'Silent' item. */
+    private boolean mHasSilentItem;
+
+    /** The Uri to place a checkmark next to. */
+    private Uri mExistingUri;
+
+    /** The number of static items in the list. */
+    private int mStaticItemCount;
+
+    /** Whether this list has the 'Default' item. */
+    private boolean mHasDefaultItem;
+
+    /** The Uri to play when the 'Default' item is clicked. */
+    private Uri mUriForDefaultItem;
+
+    /** Id of the user to which the ringtone picker should list the ringtones */
+    private int mPickerUserId;
+
+    /** Context of the user specified by mPickerUserId */
+    private Context mTargetContext;
+
+    /**
+     * A Ringtone for the default ringtone. In most cases, the RingtoneManager
+     * will stop the previous ringtone. However, the RingtoneManager doesn't
+     * manage the default ringtone for us, so we should stop this one manually.
+     */
+    private Ringtone mDefaultRingtone;
+
+    /**
+     * The ringtone that's currently playing, unless the currently playing one is the default
+     * ringtone.
+     */
+    private Ringtone mCurrentRingtone;
+
+    /**
+     * Stable ID for the ringtone that is currently checked (may be -1 if no ringtone is checked).
+     */
+    private long mCheckedItemId = -1;
+
     private int mAttributesFlags;
 
+    private boolean mShowOkCancelButtons;
+
+    /**
+     * Keep the currently playing ringtone around when changing orientation, so that it
+     * can be stopped later, after the activity is recreated.
+     */
+    private static Ringtone sPlayingRingtone;
+
+    private DialogInterface.OnClickListener mRingtoneClickListener =
+            new DialogInterface.OnClickListener() {
+
+                /*
+                 * On item clicked
+                 */
+                public void onClick(DialogInterface dialog, int which) {
+                    if (which == mCursor.getCount() + mStaticItemCount) {
+                        // The "Add new ringtone" item was clicked. Start a file picker intent to select
+                        // only audio files (MIME type "audio/*")
+                        final Intent chooseFile = getMediaFilePickerIntent();
+                        startActivityForResult(chooseFile, ADD_FILE_REQUEST_CODE);
+                        return;
+                    }
+
+                    // Save the position of most recently clicked item
+                    setCheckedItem(which);
+
+                    // In the buttonless (watch-only) version, preemptively set our result since we won't
+                    // have another chance to do so before the activity closes.
+                    if (!mShowOkCancelButtons) {
+                        setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+                    }
+
+                    // Play clip
+                    playRingtone(which, 0);
+                }
+
+            };
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_ringtone_picker);
 
-        mRingtonePickerViewModel = new ViewModelProvider(this).get(RingtonePickerViewModel.class);
+        mHandler = new Handler();
 
         Intent intent = getIntent();
-        /**
-         * Id of the user to which the ringtone picker should list the ringtones
-         */
-        int pickerUserId = UserHandle.myUserId();
+        mPickerUserId = UserHandle.myUserId();
+        mTargetContext = this;
 
         // Get the types of ringtones to show
-        int ringtoneType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE,
-                RingtonePickerViewModel.RINGTONE_TYPE_UNKNOWN);
+        mType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, -1);
+        initRingtoneManager();
 
+        /*
+         * Get whether to show the 'Default' item, and the URI to play when the
+         * default is clicked
+         */
+        mHasDefaultItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
+        mUriForDefaultItem = intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
+        if (mUriForDefaultItem == null) {
+            if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+                mUriForDefaultItem = Settings.System.DEFAULT_NOTIFICATION_URI;
+            } else if (mType == RingtoneManager.TYPE_ALARM) {
+                mUriForDefaultItem = Settings.System.DEFAULT_ALARM_ALERT_URI;
+            } else if (mType == RingtoneManager.TYPE_RINGTONE) {
+                mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI;
+            } else {
+                // or leave it null for silence.
+                mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI;
+            }
+        }
+
+        // Get whether to show the 'Silent' item
+        mHasSilentItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
         // AudioAttributes flags
         mAttributesFlags |= intent.getIntExtra(
                 RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
                 0 /*defaultValue == no flags*/);
 
-        boolean showOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons);
-
-        String title = intent.getStringExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
-        if (title == null) {
-            title = getString(RingtonePickerViewModel.getTitleByType(ringtoneType));
-        }
-        String ringtonePickerCategory = intent.getStringExtra(EXTRA_RINGTONE_PICKER_CATEGORY);
-        RingtonePickerViewModel.PickerType pickerType = mapCategoryToPickerType(
-                ringtonePickerCategory);
-
-        RingtoneListHandler.Config soundListConfig = getSoundListConfig(pickerType, intent,
-                ringtoneType);
-        RingtoneListHandler.Config vibrationListConfig = getVibrationListConfig(pickerType, intent);
-
-        RingtonePickerViewModel.Config pickerConfig =
-                new RingtonePickerViewModel.Config(title, pickerUserId, ringtoneType,
-                        showOkCancelButtons, mAttributesFlags, pickerType);
-
-        mRingtonePickerViewModel.init(pickerConfig, soundListConfig, vibrationListConfig);
-
-        if (savedInstanceState == null) {
-            TabbedDialogFragment dialogFragment = new TabbedDialogFragment();
-
-            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
-            Fragment prev = getSupportFragmentManager().findFragmentByTag(TabbedDialogFragment.TAG);
-            if (prev != null) {
-                ft.remove(prev);
-            }
-            ft.addToBackStack(null);
-            dialogFragment.show(ft, TabbedDialogFragment.TAG);
-        }
+        mShowOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons);
 
         // The volume keys will control the stream that we are choosing a ringtone for
-        setVolumeControlStream(mRingtonePickerViewModel.getRingtoneStreamType());
-    }
+        setVolumeControlStream(mRingtoneManager.inferStreamType());
 
-    private RingtoneListHandler.Config getSoundListConfig(
-            RingtonePickerViewModel.PickerType pickerType, Intent intent, int ringtoneType) {
-        if (pickerType != RingtonePickerViewModel.PickerType.SOUND_PICKER
-                && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
-            // This ringtone picker does not require a sound picker.
-            return null;
-        }
-
-        // Get whether to show the 'Default' sound item, and the URI to play when it's clicked
-        boolean hasDefaultSoundItem =
-                intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
-
-        // The Uri to play when the 'Default' sound item is clicked.
-        Uri uriForDefaultSoundItem =
-                intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
-        if (uriForDefaultSoundItem == null) {
-            uriForDefaultSoundItem = RingtonePickerViewModel.getDefaultItemUriByType(ringtoneType);
-        }
-
-        // Get whether this list has the 'Silent' sound item.
-        boolean hasSilentSoundItem =
-                intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
-
-        // AudioAttributes flags
-        mAttributesFlags |= intent.getIntExtra(
-                RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
-                0 /*defaultValue == no flags*/);
-
-        // Get the sound URI whose list item should have a checkmark
-        Uri existingSoundUri = intent
+        // Get the URI whose list item should have a checkmark
+        mExistingUri = intent
                 .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
 
-        return new RingtoneListHandler.Config(hasDefaultSoundItem,
-                uriForDefaultSoundItem, hasSilentSoundItem, existingSoundUri);
-    }
-
-    private RingtoneListHandler.Config getVibrationListConfig(
-            RingtonePickerViewModel.PickerType pickerType, Intent intent) {
-        if (pickerType != RingtonePickerViewModel.PickerType.VIBRATION_PICKER
-                && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
-            // This ringtone picker does not require a vibration picker.
-            return null;
+        // Create the list of ringtones and hold on to it so we can update later.
+        mAdapter = new BadgedRingtoneAdapter(this, mCursor,
+                /* isManagedProfile = */ UserManager.get(this).isManagedProfile(mPickerUserId));
+        if (savedInstanceState != null) {
+            setCheckedItem(savedInstanceState.getInt(SAVE_CLICKED_POS, POS_UNKNOWN));
         }
 
-        // Get whether to show the 'Default' vibration item, and the URI to play when it's clicked
-        boolean hasDefaultVibrationItem =
-                intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_DEFAULT, false);
+        final AlertController.AlertParams p = mAlertParams;
+        p.mAdapter = mAdapter;
+        p.mOnClickListener = mRingtoneClickListener;
+        p.mLabelColumn = COLUMN_LABEL;
+        p.mIsSingleChoice = true;
+        p.mOnItemSelectedListener = this;
+        if (mShowOkCancelButtons) {
+            p.mPositiveButtonText = getString(com.android.internal.R.string.ok);
+            p.mPositiveButtonListener = this;
+            p.mNegativeButtonText = getString(com.android.internal.R.string.cancel);
+            p.mPositiveButtonListener = this;
+        }
+        p.mOnPrepareListViewListener = this;
 
-        // The Uri to play when the 'Default' vibration item is clicked.
-        Uri uriForDefaultVibrationItem = intent.getParcelableExtra(EXTRA_VIBRATION_DEFAULT_URI);
+        p.mTitle = intent.getCharSequenceExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
+        if (p.mTitle == null) {
+            if (mType == RingtoneManager.TYPE_ALARM) {
+                p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title_alarm);
+            } else if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+                p.mTitle =
+                        getString(com.android.internal.R.string.ringtone_picker_title_notification);
+            } else {
+                p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title);
+            }
+        }
 
-        // Get whether this list has the 'Silent' vibration item.
-        boolean hasSilentVibrationItem =
-                intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_SILENT, true);
+        setupAlert();
 
-        // Get the vibration URI whose list item should have a checkmark
-        Uri existingVibrationUri = intent.getParcelableExtra(EXTRA_VIBRATION_EXISTING_URI);
-
-        return new RingtoneListHandler.Config(
-                hasDefaultVibrationItem, uriForDefaultVibrationItem, hasSilentVibrationItem,
-                existingVibrationUri);
+        ListView listView = mAlert.getListView();
+        if (listView != null) {
+            // List view needs to gain focus in order for RSB to work.
+            if (!listView.requestFocus()) {
+                Log.e(TAG, "Unable to gain focus! RSB may not work properly.");
+            }
+        }
+    }
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(SAVE_CLICKED_POS, getCheckedItem());
     }
 
     @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+
+        if (requestCode == ADD_FILE_REQUEST_CODE && resultCode == RESULT_OK) {
+            // Add the custom ringtone in a separate thread
+            final AsyncTask<Uri, Void, Uri> installTask = new AsyncTask<Uri, Void, Uri>() {
+                @Override
+                protected Uri doInBackground(Uri... params) {
+                    try {
+                        return mRingtoneManager.addCustomExternalRingtone(params[0], mType);
+                    } catch (IOException | IllegalArgumentException e) {
+                        Log.e(TAG, "Unable to add new ringtone", e);
+                    }
+                    return null;
+                }
+
+                @Override
+                protected void onPostExecute(Uri ringtoneUri) {
+                    if (ringtoneUri != null) {
+                        requeryForAdapter();
+                    } else {
+                        // Ringtone was not added, display error Toast
+                        Toast.makeText(RingtonePickerActivity.this, R.string.unable_to_add_ringtone,
+                                Toast.LENGTH_SHORT).show();
+                    }
+                }
+            };
+            installTask.execute(data.getData());
+        }
+    }
+
+    // Disabled because context menus aren't Material Design :(
+    /*
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
+        int position = ((AdapterContextMenuInfo) menuInfo).position;
+
+        Ringtone ringtone = getRingtone(getRingtoneManagerPosition(position));
+        if (ringtone != null && mRingtoneManager.isCustomRingtone(ringtone.getUri())) {
+            // It's a custom ringtone so we display the context menu
+            menu.setHeaderTitle(ringtone.getTitle(this));
+            menu.add(Menu.NONE, Menu.FIRST, Menu.NONE, R.string.delete_ringtone_text);
+        }
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case Menu.FIRST: {
+                int deletedRingtonePos = ((AdapterContextMenuInfo) item.getMenuInfo()).position;
+                Uri deletedRingtoneUri = getRingtone(
+                        getRingtoneManagerPosition(deletedRingtonePos)).getUri();
+                if(mRingtoneManager.deleteExternalRingtone(deletedRingtoneUri)) {
+                    requeryForAdapter();
+                } else {
+                    Toast.makeText(this, R.string.unable_to_delete_ringtone, Toast.LENGTH_SHORT)
+                            .show();
+                }
+                return true;
+            }
+            default: {
+                return false;
+            }
+        }
+    }
+    */
+
+    @Override
     public void onDestroy() {
-        mRingtonePickerViewModel.cancelPendingAsyncTasks();
+        if (mHandler != null) {
+            mHandler.removeCallbacksAndMessages(null);
+        }
+        if (mCursor != null) {
+            mCursor.close();
+            mCursor = null;
+        }
         super.onDestroy();
     }
 
+    public void onPrepareListView(ListView listView) {
+        // Reset the static item count, as this method can be called multiple times
+        mStaticItemCount = 0;
+
+        if (mHasDefaultItem) {
+            mDefaultRingtonePos = addDefaultRingtoneItem(listView);
+
+            if (getCheckedItem() == POS_UNKNOWN && RingtoneManager.isDefault(mExistingUri)) {
+                setCheckedItem(mDefaultRingtonePos);
+            }
+        }
+
+        if (mHasSilentItem) {
+            mSilentPos = addSilentItem(listView);
+
+            // The 'Silent' item should use a null Uri
+            if (getCheckedItem() == POS_UNKNOWN && mExistingUri == null) {
+                setCheckedItem(mSilentPos);
+            }
+        }
+
+        if (getCheckedItem() == POS_UNKNOWN) {
+            setCheckedItem(getListPosition(mRingtoneManager.getRingtonePosition(mExistingUri)));
+        }
+
+        // In the buttonless (watch-only) version, preemptively set our result since we won't
+        // have another chance to do so before the activity closes.
+        if (!mShowOkCancelButtons) {
+            setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+        }
+        // If external storage is available, add a button to install sounds from storage.
+        if (resolvesMediaFilePicker()
+                && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+            addNewSoundItem(listView);
+        }
+
+        // Enable context menu in ringtone items
+        registerForContextMenu(listView);
+    }
+
+    /**
+     * Re-query RingtoneManager for the most recent set of installed ringtones. May move the
+     * selected item position to match the new position of the chosen sound.
+     *
+     * This should only need to happen after adding or removing a ringtone.
+     */
+    private void requeryForAdapter() {
+        // Refresh and set a new cursor, closing the old one.
+        initRingtoneManager();
+        mAdapter.changeCursor(mCursor);
+
+        // Update checked item location.
+        int checkedPosition = POS_UNKNOWN;
+        for (int i = 0; i < mAdapter.getCount(); i++) {
+            if (mAdapter.getItemId(i) == mCheckedItemId) {
+                checkedPosition = getListPosition(i);
+                break;
+            }
+        }
+        if (mHasSilentItem && checkedPosition == POS_UNKNOWN) {
+            checkedPosition = mSilentPos;
+        }
+        setCheckedItem(checkedPosition);
+        setupAlert();
+    }
+
+    /**
+     * Adds a static item to the top of the list. A static item is one that is not from the
+     * RingtoneManager.
+     *
+     * @param listView The ListView to add to.
+     * @param textResId The resource ID of the text for the item.
+     * @return The position of the inserted item.
+     */
+    private int addStaticItem(ListView listView, int textResId) {
+        TextView textView = (TextView) getLayoutInflater().inflate(
+                com.android.internal.R.layout.select_dialog_singlechoice_material, listView, false);
+        textView.setText(textResId);
+        listView.addHeaderView(textView);
+        mStaticItemCount++;
+        return listView.getHeaderViewsCount() - 1;
+    }
+
+    private int addDefaultRingtoneItem(ListView listView) {
+        if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+            return addStaticItem(listView, R.string.notification_sound_default);
+        } else if (mType == RingtoneManager.TYPE_ALARM) {
+            return addStaticItem(listView, R.string.alarm_sound_default);
+        }
+
+        return addStaticItem(listView, R.string.ringtone_default);
+    }
+
+    private int addSilentItem(ListView listView) {
+        return addStaticItem(listView, com.android.internal.R.string.ringtone_silent);
+    }
+
+    private void addNewSoundItem(ListView listView) {
+        View view = getLayoutInflater().inflate(R.layout.add_new_sound_item, listView,
+                false /* attachToRoot */);
+        TextView text = (TextView)view.findViewById(R.id.add_new_sound_text);
+
+        if (mType == RingtoneManager.TYPE_ALARM) {
+            text.setText(R.string.add_alarm_text);
+        } else if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+            text.setText(R.string.add_notification_text);
+        } else {
+            text.setText(R.string.add_ringtone_text);
+        }
+        listView.addFooterView(view);
+    }
+
+    private void initRingtoneManager() {
+        // Reinstantiate the RingtoneManager. Cursor.requery() was deprecated and calling it
+        // causes unexpected behavior.
+        mRingtoneManager = new RingtoneManager(mTargetContext, /* includeParentRingtones */ true);
+        if (mType != -1) {
+            mRingtoneManager.setType(mType);
+        }
+        mCursor = new LocalizedCursor(mRingtoneManager.getCursor(), getResources(), COLUMN_LABEL);
+    }
+
+    private Ringtone getRingtone(int ringtoneManagerPosition) {
+        if (ringtoneManagerPosition < 0) {
+            return null;
+        }
+        return mRingtoneManager.getRingtone(ringtoneManagerPosition);
+    }
+
+    private int getCheckedItem() {
+        return mAlertParams.mCheckedItem;
+    }
+
+    private void setCheckedItem(int pos) {
+        mAlertParams.mCheckedItem = pos;
+        mCheckedItemId = mAdapter.getItemId(getRingtoneManagerPosition(pos));
+    }
+
+    /*
+     * On click of Ok/Cancel buttons
+     */
+    public void onClick(DialogInterface dialog, int which) {
+        boolean positiveResult = which == DialogInterface.BUTTON_POSITIVE;
+
+        // Stop playing the previous ringtone
+        mRingtoneManager.stopPreviousRingtone();
+
+        if (positiveResult) {
+            setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+        } else {
+            setResult(RESULT_CANCELED);
+        }
+
+        finish();
+    }
+
+    /*
+     * On item selected via keys
+     */
+    public void onItemSelected(AdapterView parent, View view, int position, long id) {
+        // footer view
+        if (position >= mCursor.getCount() + mStaticItemCount) {
+            return;
+        }
+
+        playRingtone(position, DELAY_MS_SELECTION_PLAYED);
+
+        // In the buttonless (watch-only) version, preemptively set our result since we won't
+        // have another chance to do so before the activity closes.
+        if (!mShowOkCancelButtons) {
+            setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+        }
+    }
+
+    public void onNothingSelected(AdapterView parent) {
+    }
+
+    private void playRingtone(int position, int delayMs) {
+        mHandler.removeCallbacks(this);
+        mSampleRingtonePos = position;
+        mHandler.postDelayed(this, delayMs);
+    }
+
+    public void run() {
+        stopAnyPlayingRingtone();
+        if (mSampleRingtonePos == mSilentPos) {
+            return;
+        }
+
+        Ringtone ringtone;
+        if (mSampleRingtonePos == mDefaultRingtonePos) {
+            if (mDefaultRingtone == null) {
+                mDefaultRingtone = RingtoneManager.getRingtone(this, mUriForDefaultItem);
+            }
+            /*
+             * Stream type of mDefaultRingtone is not set explicitly here.
+             * It should be set in accordance with mRingtoneManager of this Activity.
+             */
+            if (mDefaultRingtone != null) {
+                mDefaultRingtone.setStreamType(mRingtoneManager.inferStreamType());
+            }
+            ringtone = mDefaultRingtone;
+            mCurrentRingtone = null;
+        } else {
+            ringtone = mRingtoneManager.getRingtone(getRingtoneManagerPosition(mSampleRingtonePos));
+            mCurrentRingtone = ringtone;
+        }
+
+        if (ringtone != null) {
+            if (mAttributesFlags != 0) {
+                ringtone.setAudioAttributes(
+                        new AudioAttributes.Builder(ringtone.getAudioAttributes())
+                                .setFlags(mAttributesFlags)
+                                .build());
+            }
+            ringtone.play();
+        }
+    }
+
     @Override
     protected void onStop() {
         super.onStop();
-        mRingtonePickerViewModel.onStop(isChangingConfigurations());
+
+        if (!isChangingConfigurations()) {
+            stopAnyPlayingRingtone();
+        } else {
+            saveAnyPlayingRingtone();
+        }
     }
 
     @Override
     protected void onPause() {
         super.onPause();
-        mRingtonePickerViewModel.onPause(isChangingConfigurations());
-    }
-
-    /**
-     * Maps the ringtone picker category to the appropriate PickerType.
-     * If the category is null or the feature is still not released, then it defaults to sound
-     * picker.
-     *
-     * @param category the ringtone picker category.
-     * @return the corresponding picker type.
-     */
-    private static RingtonePickerViewModel.PickerType mapCategoryToPickerType(String category) {
-        if (category == null || !RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED) {
-            return RingtonePickerViewModel.PickerType.SOUND_PICKER;
-        }
-
-        switch (category) {
-            case "android.intent.category.RINGTONE_PICKER_RINGTONE":
-                return RingtonePickerViewModel.PickerType.RINGTONE_PICKER;
-            case "android.intent.category.RINGTONE_PICKER_SOUND":
-                return RingtonePickerViewModel.PickerType.SOUND_PICKER;
-            case "android.intent.category.RINGTONE_PICKER_VIBRATION":
-                return RingtonePickerViewModel.PickerType.VIBRATION_PICKER;
-            default:
-                Log.w(TAG, "Unrecognized category: " + category + ". Defaulting to sound picker.");
-                return RingtonePickerViewModel.PickerType.SOUND_PICKER;
+        if (!isChangingConfigurations()) {
+            stopAnyPlayingRingtone();
         }
     }
-}
+
+    private void setSuccessResultWithRingtone(Uri ringtoneUri) {
+        setResult(RESULT_OK,
+                new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, ringtoneUri));
+    }
+
+    private Uri getCurrentlySelectedRingtoneUri() {
+        if (getCheckedItem() == POS_UNKNOWN) {
+            // When the getCheckItem is POS_UNKNOWN, it is not the case we expected.
+            // We return null for this case.
+            return null;
+        } else if (getCheckedItem() == mDefaultRingtonePos) {
+            // Use the default Uri that they originally gave us.
+            return mUriForDefaultItem;
+        } else if (getCheckedItem() == mSilentPos) {
+            // Use a null Uri for the 'Silent' item.
+            return null;
+        } else {
+            return mRingtoneManager.getRingtoneUri(getRingtoneManagerPosition(getCheckedItem()));
+        }
+    }
+
+    private void saveAnyPlayingRingtone() {
+        if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) {
+            sPlayingRingtone = mDefaultRingtone;
+        } else if (mCurrentRingtone != null && mCurrentRingtone.isPlaying()) {
+            sPlayingRingtone = mCurrentRingtone;
+        }
+    }
+
+    private void stopAnyPlayingRingtone() {
+        if (sPlayingRingtone != null && sPlayingRingtone.isPlaying()) {
+            sPlayingRingtone.stop();
+        }
+        sPlayingRingtone = null;
+
+        if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) {
+            mDefaultRingtone.stop();
+        }
+
+        if (mRingtoneManager != null) {
+            mRingtoneManager.stopPreviousRingtone();
+        }
+    }
+
+    private int getRingtoneManagerPosition(int listPos) {
+        return listPos - mStaticItemCount;
+    }
+
+    private int getListPosition(int ringtoneManagerPos) {
+
+        // If the manager position is -1 (for not found), return that
+        if (ringtoneManagerPos < 0) return ringtoneManagerPos;
+
+        return ringtoneManagerPos + mStaticItemCount;
+    }
+
+    private Intent getMediaFilePickerIntent() {
+        final Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
+        chooseFile.setType("audio/*");
+        chooseFile.putExtra(Intent.EXTRA_MIME_TYPES,
+                new String[] { "audio/*", "application/ogg" });
+        return chooseFile;
+    }
+
+    private boolean resolvesMediaFilePicker() {
+        return getMediaFilePickerIntent().resolveActivity(getPackageManager()) != null;
+    }
+
+    private static class LocalizedCursor extends CursorWrapper {
+
+        final int mTitleIndex;
+        final Resources mResources;
+        String mNamePrefix;
+        final Pattern mSanitizePattern;
+
+        LocalizedCursor(Cursor cursor, Resources resources, String columnLabel) {
+            super(cursor);
+            mTitleIndex = mCursor.getColumnIndex(columnLabel);
+            mResources = resources;
+            mSanitizePattern = Pattern.compile("[^a-zA-Z0-9]");
+            if (mTitleIndex == -1) {
+                Log.e(TAG, "No index for column " + columnLabel);
+                mNamePrefix = null;
+            } else {
+                try {
+                    // Build the prefix for the name of the resource to look up
+                    // format is: "ResourcePackageName::ResourceTypeName/"
+                    // (the type name is expected to be "string" but let's not hardcode it).
+                    // Here we use an existing resource "notification_sound_default" which is
+                    // always expected to be found.
+                    mNamePrefix = String.format("%s:%s/%s",
+                            mResources.getResourcePackageName(R.string.notification_sound_default),
+                            mResources.getResourceTypeName(R.string.notification_sound_default),
+                            SOUND_NAME_RES_PREFIX);
+                } catch (NotFoundException e) {
+                    mNamePrefix = null;
+                }
+            }
+        }
+
+        /**
+         * Process resource name to generate a valid resource name.
+         * @param input
+         * @return a non-null String
+         */
+        private String sanitize(String input) {
+            if (input == null) {
+                return "";
+            }
+            return mSanitizePattern.matcher(input).replaceAll("_").toLowerCase();
+        }
+
+        @Override
+        public String getString(int columnIndex) {
+            final String defaultName = mCursor.getString(columnIndex);
+            if ((columnIndex != mTitleIndex) || (mNamePrefix == null)) {
+                return defaultName;
+            }
+            TypedValue value = new TypedValue();
+            try {
+                // the name currently in the database is used to derive a name to match
+                // against resource names in this package
+                mResources.getValue(mNamePrefix + sanitize(defaultName), value, false);
+            } catch (NotFoundException e) {
+                // no localized string, use the default string
+                return defaultName;
+            }
+            if ((value != null) && (value.type == TypedValue.TYPE_STRING)) {
+                Log.d(TAG, String.format("Replacing name %s with %s",
+                        defaultName, value.string.toString()));
+                return value.string.toString();
+            } else {
+                Log.e(TAG, "Invalid value when looking up localized name, using " + defaultName);
+                return defaultName;
+            }
+        }
+    }
+
+    private class BadgedRingtoneAdapter extends CursorAdapter {
+        private final boolean mIsManagedProfile;
+
+        public BadgedRingtoneAdapter(Context context, Cursor cursor, boolean isManagedProfile) {
+            super(context, cursor);
+            mIsManagedProfile = isManagedProfile;
+        }
+
+        @Override
+        public long getItemId(int position) {
+            if (position < 0) {
+                return position;
+            }
+            return super.getItemId(position);
+        }
+
+        @Override
+        public View newView(Context context, Cursor cursor, ViewGroup parent) {
+            LayoutInflater inflater = LayoutInflater.from(context);
+            return inflater.inflate(R.layout.radio_with_work_badge, parent, false);
+        }
+
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+            // Set text as the title of the ringtone
+            ((TextView) view.findViewById(R.id.checked_text_view))
+                    .setText(cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX));
+
+            boolean isWorkRingtone = false;
+            if (mIsManagedProfile) {
+                /*
+                 * Display the work icon if the ringtone belongs to a work profile. We can tell that
+                 * a ringtone belongs to a work profile if the picker user is a managed profile, the
+                 * ringtone Uri is in external storage, and either the uri has no user id or has the
+                 * id of the picker user
+                 */
+                Uri currentUri = mRingtoneManager.getRingtoneUri(cursor.getPosition());
+                int uriUserId = ContentProvider.getUserIdFromUri(currentUri, mPickerUserId);
+                Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(currentUri);
+
+                if (uriUserId == mPickerUserId && uriWithoutUserId.toString()
+                        .startsWith(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString())) {
+                    isWorkRingtone = true;
+                }
+            }
+
+            ImageView workIcon = (ImageView) view.findViewById(R.id.work_icon);
+            if(isWorkRingtone) {
+                workIcon.setImageDrawable(getPackageManager().getUserBadgeForDensityNoBackground(
+                        UserHandle.of(mPickerUserId), -1 /* density */));
+                workIcon.setVisibility(View.VISIBLE);
+            } else {
+                workIcon.setVisibility(View.GONE);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SoundPicker2/Android.bp b/packages/SoundPicker2/Android.bp
new file mode 100644
index 0000000..f4d8bf2
--- /dev/null
+++ b/packages/SoundPicker2/Android.bp
@@ -0,0 +1,46 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+    name: "SoundPicker2Lib",
+    srcs: [
+        "src/**/*.java",
+    ],
+    resource_dirs: [
+        "res",
+    ],
+    static_libs: [
+        "androidx.appcompat_appcompat",
+        "hilt_android",
+        "guava",
+        "androidx.recyclerview_recyclerview",
+        "androidx-constraintlayout_constraintlayout",
+        "androidx.viewpager2_viewpager2",
+        "com.google.android.material_material",
+    ],
+}
+
+android_app {
+    name: "SoundPicker2",
+    defaults: ["platform_app_defaults"],
+    manifest: "AndroidManifest.xml",
+    static_libs: ["SoundPicker2Lib"],
+    platform_apis: true,
+    certificate: "media",
+    privileged: true,
+
+    optimize: {
+        enabled: true,
+        optimize: true,
+        shrink: true,
+        shrink_resources: true,
+        obfuscate: false,
+        proguard_compatibility: false,
+    },
+}
diff --git a/packages/SoundPicker2/AndroidManifest.xml b/packages/SoundPicker2/AndroidManifest.xml
new file mode 100644
index 0000000..934b003
--- /dev/null
+++ b/packages/SoundPicker2/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.soundpicker"
+        android:sharedUserId="android.media">
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <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.INTERACT_ACROSS_USERS" />
+
+    <application
+            android:name=".RingtonePickerApplication"
+            android:allowBackup="false"
+            android:label="@string/app_label"
+            android:theme="@style/Theme.AppCompat"
+            android:supportsRtl="true">
+        <receiver android:name="RingtoneReceiver"
+                android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/>
+            </intent-filter>
+        </receiver>
+
+        <service android:name="RingtoneOverlayService" />
+
+        <activity android:name="RingtonePickerActivity"
+                android:theme="@style/Theme.AppCompat.Dialog"
+                android:enabled="@*android:bool/config_defaultRingtonePickerEnabled"
+                android:excludeFromRecents="true"
+                android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.RINGTONE_PICKER" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.RINGTONE_PICKER_SOUND" />
+                <category android:name="android.intent.category.RINGTONE_PICKER_VIBRATION" />
+                <category android:name="android.intent.category.RINGTONE_PICKER_RINGTONE" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/packages/SoundPicker2/OWNERS b/packages/SoundPicker2/OWNERS
new file mode 100644
index 0000000..5bf46e0
--- /dev/null
+++ b/packages/SoundPicker2/OWNERS
@@ -0,0 +1,2 @@
+# Haptics team works on the SoundPicker
+include platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/packages/SoundPicker2/res/drawable/ic_add.xml b/packages/SoundPicker2/res/drawable/ic_add.xml
new file mode 100644
index 0000000..22b3fe9
--- /dev/null
+++ b/packages/SoundPicker2/res/drawable/ic_add.xml
@@ -0,0 +1,24 @@
+<!--
+    Copyright (C) 2016 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillColor="?android:attr/colorAccent"
+        android:pathData="M38.0,26.0L26.0,26.0l0.0,12.0l-4.0,0.0L22.0,26.0L10.0,26.0l0.0,-4.0l12.0,0.0L22.0,10.0l4.0,0.0l0.0,12.0l12.0,0.0l0.0,4.0z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SoundPicker2/res/drawable/ic_add_padded.xml b/packages/SoundPicker2/res/drawable/ic_add_padded.xml
new file mode 100644
index 0000000..c376867
--- /dev/null
+++ b/packages/SoundPicker2/res/drawable/ic_add_padded.xml
@@ -0,0 +1,22 @@
+<!--
+    Copyright (C) 2017 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.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+        android:drawable="@drawable/ic_add"
+        android:insetTop="4dp"
+        android:insetRight="4dp"
+        android:insetBottom="4dp"
+        android:insetLeft="4dp"/>
diff --git a/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml b/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml
new file mode 100644
index 0000000..edfc0ab
--- /dev/null
+++ b/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2017 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.
+-->
+
+<!--
+     Currently, no file manager app on watch could handle ACTION_GET_CONTENT intent.
+     Make the visibility to "gone" to prevent failures.
+ -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/add_new_sound_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="?android:attr/listPreferredItemHeightSmall"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:text="@null"
+        android:textColor="?android:attr/colorAccent"
+        android:gravity="center_vertical"
+        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+        android:drawableStart="@drawable/ic_add_padded"
+        android:drawablePadding="8dp"
+        android:ellipsize="marquee"
+        android:visibility="gone" />
diff --git a/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml b/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml
new file mode 100644
index 0000000..ee29a37
--- /dev/null
+++ b/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<com.android.soundpicker.CheckedListItem xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical"
+    android:background="?android:attr/selectableItemBackground"
+    >
+
+    <CheckedTextView
+        android:id="@+id/checked_text_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="?android:attr/listPreferredItemHeightSmall"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textColor="?android:attr/textColorAlertDialogListItem"
+        android:gravity="center_vertical"
+        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+        android:drawableStart="?android:attr/listChoiceIndicatorSingle"
+        android:drawablePadding="8dp"
+        android:ellipsize="marquee"
+        android:layout_toLeftOf="@+id/work_icon"
+        android:maxLines="3" />
+
+    <ImageView
+        android:id="@id/work_icon"
+        android:layout_width="18dp"
+        android:layout_height="18dp"
+        android:layout_alignParentRight="true"
+        android:layout_centerVertical="true"
+        android:scaleType="centerCrop"
+        android:layout_marginRight="20dp" />
+</com.android.soundpicker.CheckedListItem>
diff --git a/packages/SoundPicker/res/layout/activity_ringtone_picker.xml b/packages/SoundPicker2/res/layout/activity_ringtone_picker.xml
similarity index 100%
rename from packages/SoundPicker/res/layout/activity_ringtone_picker.xml
rename to packages/SoundPicker2/res/layout/activity_ringtone_picker.xml
diff --git a/packages/SoundPicker2/res/layout/add_new_sound_item.xml b/packages/SoundPicker2/res/layout/add_new_sound_item.xml
new file mode 100644
index 0000000..024b97e
--- /dev/null
+++ b/packages/SoundPicker2/res/layout/add_new_sound_item.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2016 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent"
+              android:layout_height="wrap_content"
+              android:gravity="center_vertical"
+              android:background="?android:attr/selectableItemBackground"
+              android:focusable="true"
+              android:clickable="true">
+
+    <ImageView
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_alignParentRight="true"
+        android:layout_centerVertical="true"
+        android:scaleType="centerCrop"
+        android:layout_marginRight="24dp"
+        android:layout_marginLeft="24dp"
+        android:src="@drawable/ic_add"/>
+
+    <TextView
+        android:id="@+id/add_new_sound_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="?android:attr/listPreferredItemHeightSmall"
+        android:text="@null"
+        android:textColor="?android:attr/colorAccent"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:maxLines="3"
+        android:gravity="center_vertical"
+        android:paddingEnd="?android:attr/dialogPreferredPadding"
+        android:drawablePadding="20dp"
+        android:ellipsize="marquee"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/layout/fragment_ringtone_picker.xml b/packages/SoundPicker2/res/layout/fragment_ringtone_picker.xml
similarity index 100%
rename from packages/SoundPicker/res/layout/fragment_ringtone_picker.xml
rename to packages/SoundPicker2/res/layout/fragment_ringtone_picker.xml
diff --git a/packages/SoundPicker/res/layout/fragment_tabbed_dialog.xml b/packages/SoundPicker2/res/layout/fragment_tabbed_dialog.xml
similarity index 100%
rename from packages/SoundPicker/res/layout/fragment_tabbed_dialog.xml
rename to packages/SoundPicker2/res/layout/fragment_tabbed_dialog.xml
diff --git a/packages/SoundPicker2/res/layout/radio_with_work_badge.xml b/packages/SoundPicker2/res/layout/radio_with_work_badge.xml
new file mode 100644
index 0000000..36ac93e
--- /dev/null
+++ b/packages/SoundPicker2/res/layout/radio_with_work_badge.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<com.android.soundpicker.CheckedListItem
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical"
+    android:background="?android:attr/selectableItemBackground"
+    android:focusable="true"
+    android:clickable="true">
+
+    <CheckedTextView
+        android:id="@+id/checked_text_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="?android:attr/listPreferredItemHeightSmall"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textColor="?android:attr/textColorAlertDialogListItem"
+        android:gravity="center_vertical"
+        android:paddingStart="20dp"
+        android:paddingEnd="?android:attr/dialogPreferredPadding"
+        android:drawableStart="?android:attr/listChoiceIndicatorSingle"
+        android:drawablePadding="20dp"
+        android:ellipsize="marquee"
+        android:layout_toLeftOf="@+id/work_icon"
+        android:maxLines="3"/>
+
+    <ImageView
+        android:id="@id/work_icon"
+        android:layout_width="18dp"
+        android:layout_height="18dp"
+        android:layout_alignParentRight="true"
+        android:layout_centerVertical="true"
+        android:scaleType="centerCrop"
+        android:layout_marginRight="20dp"/>
+</com.android.soundpicker.CheckedListItem>
diff --git a/packages/SoundPicker2/res/raw/default_alarm_alert.ogg b/packages/SoundPicker2/res/raw/default_alarm_alert.ogg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/SoundPicker2/res/raw/default_alarm_alert.ogg
diff --git a/packages/SoundPicker2/res/raw/default_notification_sound.ogg b/packages/SoundPicker2/res/raw/default_notification_sound.ogg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/SoundPicker2/res/raw/default_notification_sound.ogg
diff --git a/packages/SoundPicker2/res/raw/default_ringtone.ogg b/packages/SoundPicker2/res/raw/default_ringtone.ogg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/SoundPicker2/res/raw/default_ringtone.ogg
diff --git a/packages/SoundPicker2/res/values/config.xml b/packages/SoundPicker2/res/values/config.xml
new file mode 100644
index 0000000..4e237a2
--- /dev/null
+++ b/packages/SoundPicker2/res/values/config.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds.  Do not translate.
+
+     NOTE: The naming convention is "config_camelCaseValue".  -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- True if the ringtone picker should show the ok/cancel buttons. If it is not shown, the
+    ringtone will be automatically selected when the picker is closed. -->
+    <bool name="config_showOkCancelButtons">true</bool>
+</resources>
diff --git a/packages/SoundPicker2/res/values/strings.xml b/packages/SoundPicker2/res/values/strings.xml
new file mode 100644
index 0000000..ab7b95a
--- /dev/null
+++ b/packages/SoundPicker2/res/values/strings.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Choice in the ringtone picker.  If chosen, the default ringtone will be used. -->
+    <string name="ringtone_default">Default ringtone</string>
+
+    <!-- Choice in the notification sound picker.  If chosen, the default notification sound will be
+         used. -->
+    <string name="notification_sound_default">Default notification sound</string>
+
+    <!-- Choice in the alarm sound picker.  If chosen, the default alarm sound will be used. -->
+    <string name="alarm_sound_default">Default alarm sound</string>
+
+    <!-- Text for the RingtonePicker item that allows adding a new ringtone. -->
+    <string name="add_ringtone_text">Add ringtone</string>
+    <!-- Text for the RingtonePicker item that allows adding a new alarm. -->
+    <string name="add_alarm_text">Add alarm</string>
+    <!-- Text for the RingtonePicker item that allows adding a new notification. -->
+    <string name="add_notification_text">Add notification</string>
+    <!-- Text for the RingtonePicker item ContextMenu that allows deleting a custom ringtone. -->
+    <string name="delete_ringtone_text">Delete</string>
+    <!-- Text for the Toast displayed when adding a custom ringtone fails. -->
+    <string name="unable_to_add_ringtone">Unable to add custom ringtone</string>
+    <!-- Text for the Toast displayed when deleting a custom ringtone fails. -->
+    <string name="unable_to_delete_ringtone">Unable to delete custom ringtone</string>
+
+    <!-- Text for the name of the app. [CHAR LIMIT=12] -->
+    <string name="app_label">Sounds</string>
+
+    <string name="empty_list">The list is empty</string>
+    <string name="sound_page_title">Sound</string>
+    <string name="vibration_page_title">Vibration</string>
+</resources>
diff --git a/packages/SoundPicker2/res/values/styles.xml b/packages/SoundPicker2/res/values/styles.xml
new file mode 100644
index 0000000..d22d9c4
--- /dev/null
+++ b/packages/SoundPicker2/res/values/styles.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <style name="PickerDialogTheme" parent="@*android:style/Theme.DeviceDefault.Settings.Dialog">
+    </style>
+
+</resources>
diff --git a/packages/SoundPicker/src/com/android/soundpicker/BasePickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/BasePickerFragment.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/BasePickerFragment.java
rename to packages/SoundPicker2/src/com/android/soundpicker/BasePickerFragment.java
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java b/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java
new file mode 100644
index 0000000..819ae98
--- /dev/null
+++ b/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2016 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.soundpicker;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Checkable;
+import android.widget.CheckedTextView;
+import android.widget.RelativeLayout;
+
+/**
+ * The {@link CheckedListItem} is a layout item that represents a ringtone, and is used in
+ * {@link RingtonePickerActivity}. It contains the ringtone's name, and a work badge to right of the
+ * name if the ringtone belongs to a work profile.
+ */
+public class CheckedListItem extends RelativeLayout implements Checkable {
+
+    public CheckedListItem(Context context) {
+        super(context);
+    }
+
+    public CheckedListItem(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public CheckedListItem(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public CheckedListItem(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    public void setChecked(boolean checked) {
+        getCheckedTextView().setChecked(checked);
+    }
+
+    @Override
+    public boolean isChecked() {
+        return getCheckedTextView().isChecked();
+    }
+
+    @Override
+    public void toggle() {
+        getCheckedTextView().toggle();
+    }
+
+    private CheckedTextView getCheckedTextView() {
+        return (CheckedTextView) findViewById(R.id.checked_text_view);
+    }
+
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/ListeningExecutorServiceFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/ListeningExecutorServiceFactory.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/ListeningExecutorServiceFactory.java
rename to packages/SoundPicker2/src/com/android/soundpicker/ListeningExecutorServiceFactory.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/LocalizedCursor.java b/packages/SoundPicker2/src/com/android/soundpicker/LocalizedCursor.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/LocalizedCursor.java
rename to packages/SoundPicker2/src/com/android/soundpicker/LocalizedCursor.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneFactory.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtoneFactory.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneListHandler.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListHandler.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtoneListHandler.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtoneListHandler.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneListViewAdapter.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListViewAdapter.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtoneListViewAdapter.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtoneListViewAdapter.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneManagerFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneManagerFactory.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtoneManagerFactory.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtoneManagerFactory.java
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java
new file mode 100644
index 0000000..b94ebeb
--- /dev/null
+++ b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java
@@ -0,0 +1,113 @@
+/*
+ * 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.soundpicker;
+
+import android.app.Service;
+import android.content.Intent;
+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.MediaStore;
+import android.provider.Settings.System;
+import android.util.Log;
+
+import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * 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()) {
+            Log.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) Log.d(TAG, "Resource for " + name + " has no overlay");
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Unable to open resource for " + name + ": " + e);
+        }
+    }
+
+    private Uri scanFile(@NonNull final File file) {
+        return MediaStore.scanFile(getContentResolver(), file);
+    }
+
+    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);
+    }
+}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java
new file mode 100644
index 0000000..90a14f9
--- /dev/null
+++ b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2007 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.soundpicker;
+
+import android.content.Intent;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentTransaction;
+import androidx.lifecycle.ViewModelProvider;
+
+import dagger.hilt.android.AndroidEntryPoint;
+
+/**
+ * The {@link RingtonePickerActivity} allows the user to choose one from all of the
+ * available ringtones. The chosen ringtone's URI will be persisted as a string.
+ *
+ * @see RingtoneManager#ACTION_RINGTONE_PICKER
+ */
+@AndroidEntryPoint(AppCompatActivity.class)
+public final class RingtonePickerActivity extends Hilt_RingtonePickerActivity {
+
+    private static final String TAG = "RingtonePickerActivity";
+    // TODO: Use the extra keys from RingtoneManager once they're added.
+    private static final String EXTRA_RINGTONE_PICKER_CATEGORY = "EXTRA_RINGTONE_PICKER_CATEGORY";
+    private static final String EXTRA_VIBRATION_SHOW_DEFAULT = "EXTRA_VIBRATION_SHOW_DEFAULT";
+    private static final String EXTRA_VIBRATION_DEFAULT_URI = "EXTRA_VIBRATION_DEFAULT_URI";
+    private static final String EXTRA_VIBRATION_SHOW_SILENT = "EXTRA_VIBRATION_SHOW_SILENT";
+    private static final String EXTRA_VIBRATION_EXISTING_URI = "EXTRA_VIBRATION_EXISTING_URI";
+    private static final boolean RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED = false;
+
+    private RingtonePickerViewModel mRingtonePickerViewModel;
+    private int mAttributesFlags;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_ringtone_picker);
+
+        mRingtonePickerViewModel = new ViewModelProvider(this).get(RingtonePickerViewModel.class);
+
+        Intent intent = getIntent();
+        /**
+         * Id of the user to which the ringtone picker should list the ringtones
+         */
+        int pickerUserId = UserHandle.myUserId();
+
+        // Get the types of ringtones to show
+        int ringtoneType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE,
+                RingtonePickerViewModel.RINGTONE_TYPE_UNKNOWN);
+
+        // AudioAttributes flags
+        mAttributesFlags |= intent.getIntExtra(
+                RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
+                0 /*defaultValue == no flags*/);
+
+        boolean showOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons);
+
+        String title = intent.getStringExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
+        if (title == null) {
+            title = getString(RingtonePickerViewModel.getTitleByType(ringtoneType));
+        }
+        String ringtonePickerCategory = intent.getStringExtra(EXTRA_RINGTONE_PICKER_CATEGORY);
+        RingtonePickerViewModel.PickerType pickerType = mapCategoryToPickerType(
+                ringtonePickerCategory);
+
+        RingtoneListHandler.Config soundListConfig = getSoundListConfig(pickerType, intent,
+                ringtoneType);
+        RingtoneListHandler.Config vibrationListConfig = getVibrationListConfig(pickerType, intent);
+
+        RingtonePickerViewModel.Config pickerConfig =
+                new RingtonePickerViewModel.Config(title, pickerUserId, ringtoneType,
+                        showOkCancelButtons, mAttributesFlags, pickerType);
+
+        mRingtonePickerViewModel.init(pickerConfig, soundListConfig, vibrationListConfig);
+
+        if (savedInstanceState == null) {
+            TabbedDialogFragment dialogFragment = new TabbedDialogFragment();
+
+            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+            Fragment prev = getSupportFragmentManager().findFragmentByTag(TabbedDialogFragment.TAG);
+            if (prev != null) {
+                ft.remove(prev);
+            }
+            ft.addToBackStack(null);
+            dialogFragment.show(ft, TabbedDialogFragment.TAG);
+        }
+
+        // The volume keys will control the stream that we are choosing a ringtone for
+        setVolumeControlStream(mRingtonePickerViewModel.getRingtoneStreamType());
+    }
+
+    private RingtoneListHandler.Config getSoundListConfig(
+            RingtonePickerViewModel.PickerType pickerType, Intent intent, int ringtoneType) {
+        if (pickerType != RingtonePickerViewModel.PickerType.SOUND_PICKER
+                && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
+            // This ringtone picker does not require a sound picker.
+            return null;
+        }
+
+        // Get whether to show the 'Default' sound item, and the URI to play when it's clicked
+        boolean hasDefaultSoundItem =
+                intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
+
+        // The Uri to play when the 'Default' sound item is clicked.
+        Uri uriForDefaultSoundItem =
+                intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
+        if (uriForDefaultSoundItem == null) {
+            uriForDefaultSoundItem = RingtonePickerViewModel.getDefaultItemUriByType(ringtoneType);
+        }
+
+        // Get whether this list has the 'Silent' sound item.
+        boolean hasSilentSoundItem =
+                intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
+
+        // AudioAttributes flags
+        mAttributesFlags |= intent.getIntExtra(
+                RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
+                0 /*defaultValue == no flags*/);
+
+        // Get the sound URI whose list item should have a checkmark
+        Uri existingSoundUri = intent
+                .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
+
+        return new RingtoneListHandler.Config(hasDefaultSoundItem,
+                uriForDefaultSoundItem, hasSilentSoundItem, existingSoundUri);
+    }
+
+    private RingtoneListHandler.Config getVibrationListConfig(
+            RingtonePickerViewModel.PickerType pickerType, Intent intent) {
+        if (pickerType != RingtonePickerViewModel.PickerType.VIBRATION_PICKER
+                && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
+            // This ringtone picker does not require a vibration picker.
+            return null;
+        }
+
+        // Get whether to show the 'Default' vibration item, and the URI to play when it's clicked
+        boolean hasDefaultVibrationItem =
+                intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_DEFAULT, false);
+
+        // The Uri to play when the 'Default' vibration item is clicked.
+        Uri uriForDefaultVibrationItem = intent.getParcelableExtra(EXTRA_VIBRATION_DEFAULT_URI);
+
+        // Get whether this list has the 'Silent' vibration item.
+        boolean hasSilentVibrationItem =
+                intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_SILENT, true);
+
+        // Get the vibration URI whose list item should have a checkmark
+        Uri existingVibrationUri = intent.getParcelableExtra(EXTRA_VIBRATION_EXISTING_URI);
+
+        return new RingtoneListHandler.Config(
+                hasDefaultVibrationItem, uriForDefaultVibrationItem, hasSilentVibrationItem,
+                existingVibrationUri);
+    }
+
+    @Override
+    public void onDestroy() {
+        mRingtonePickerViewModel.cancelPendingAsyncTasks();
+        super.onDestroy();
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mRingtonePickerViewModel.onStop(isChangingConfigurations());
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mRingtonePickerViewModel.onPause(isChangingConfigurations());
+    }
+
+    /**
+     * Maps the ringtone picker category to the appropriate PickerType.
+     * If the category is null or the feature is still not released, then it defaults to sound
+     * picker.
+     *
+     * @param category the ringtone picker category.
+     * @return the corresponding picker type.
+     */
+    private static RingtonePickerViewModel.PickerType mapCategoryToPickerType(String category) {
+        if (category == null || !RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED) {
+            return RingtonePickerViewModel.PickerType.SOUND_PICKER;
+        }
+
+        switch (category) {
+            case "android.intent.category.RINGTONE_PICKER_RINGTONE":
+                return RingtonePickerViewModel.PickerType.RINGTONE_PICKER;
+            case "android.intent.category.RINGTONE_PICKER_SOUND":
+                return RingtonePickerViewModel.PickerType.SOUND_PICKER;
+            case "android.intent.category.RINGTONE_PICKER_VIBRATION":
+                return RingtonePickerViewModel.PickerType.VIBRATION_PICKER;
+            default:
+                Log.w(TAG, "Unrecognized category: " + category + ". Defaulting to sound picker.");
+                return RingtonePickerViewModel.PickerType.SOUND_PICKER;
+        }
+    }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerApplication.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtonePickerApplication.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerViewModel.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerViewModel.java
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java
new file mode 100644
index 0000000..6a34936
--- /dev/null
+++ b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2007 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.soundpicker;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class RingtoneReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final String action = intent.getAction();
+        if (Intent.ACTION_DEVICE_CUSTOMIZATION_READY.equals(action)) {
+            initResourceRingtones(context);
+        }
+    }
+
+    private void initResourceRingtones(Context context) {
+        context.startService(
+                new Intent(context, RingtoneOverlayService.class));
+    }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/SoundPickerFragment.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java
rename to packages/SoundPicker2/src/com/android/soundpicker/SoundPickerFragment.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/TabbedDialogFragment.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java
rename to packages/SoundPicker2/src/com/android/soundpicker/TabbedDialogFragment.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/VibrationPickerFragment.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.java
rename to packages/SoundPicker2/src/com/android/soundpicker/VibrationPickerFragment.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/ViewPagerAdapter.java b/packages/SoundPicker2/src/com/android/soundpicker/ViewPagerAdapter.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/ViewPagerAdapter.java
rename to packages/SoundPicker2/src/com/android/soundpicker/ViewPagerAdapter.java
diff --git a/packages/SoundPicker/tests/Android.bp b/packages/SoundPicker2/tests/Android.bp
similarity index 94%
rename from packages/SoundPicker/tests/Android.bp
rename to packages/SoundPicker2/tests/Android.bp
index c38426f..d88d442 100644
--- a/packages/SoundPicker/tests/Android.bp
+++ b/packages/SoundPicker2/tests/Android.bp
@@ -17,7 +17,7 @@
 }
 
 android_test {
-    name: "SoundPickerTests",
+    name: "SoundPicker2Tests",
     certificate: "platform",
     libs: [
         "android.test.runner",
@@ -30,7 +30,7 @@
         "androidx.test.ext.truth",
         "mockito-target-minus-junit4",
         "guava-android-testlib",
-        "SoundPickerLib",
+        "SoundPicker2Lib",
     ],
     srcs: [
         "src/**/*.java",
diff --git a/packages/SoundPicker/tests/AndroidManifest.xml b/packages/SoundPicker2/tests/AndroidManifest.xml
similarity index 100%
rename from packages/SoundPicker/tests/AndroidManifest.xml
rename to packages/SoundPicker2/tests/AndroidManifest.xml
diff --git a/packages/SoundPicker/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java b/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java
similarity index 100%
rename from packages/SoundPicker/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java
rename to packages/SoundPicker2/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java
diff --git a/packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java b/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
similarity index 100%
rename from packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
rename to packages/SoundPicker2/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java