Move RINGTONE_PICKER to handle external storage.

To give RINGTONE_PICKER external storage access, move it from system
to MediaProvider.

Bug: 6346701
Change-Id: Ib5f8e8fa8a962be211bc60c6e09778c7e2b85f2a
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4b73de0..afffe51 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -64,5 +64,14 @@
  
         <service android:name="MtpService" />
 
+        <activity android:name="RingtonePickerActivity"
+                android:theme="@*android:style/Theme.Holo.Dialog.Alert"
+                android:excludeFromRecents="true">
+            <intent-filter>
+                <action android:name="android.intent.action.RINGTONE_PICKER" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
     </application>
 </manifest>
diff --git a/src/com/android/providers/media/RingtonePickerActivity.java b/src/com/android/providers/media/RingtonePickerActivity.java
new file mode 100644
index 0000000..2669fb7
--- /dev/null
+++ b/src/com/android/providers/media/RingtonePickerActivity.java
@@ -0,0 +1,362 @@
+/*
+ * 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.providers.media;
+
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.database.Cursor;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+
+/**
+ * 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
+ */
+public final class RingtonePickerActivity extends AlertActivity implements
+        AdapterView.OnItemSelectedListener, Runnable, DialogInterface.OnClickListener,
+        AlertController.AlertParams.OnPrepareListViewListener {
+
+    private static final String TAG = "RingtonePickerActivity";
+
+    private static final int DELAY_MS_SELECTION_PLAYED = 300;
+
+    private static final String SAVE_CLICKED_POS = "clicked_pos";
+
+    private RingtoneManager mRingtoneManager;
+
+    private Cursor mCursor;
+    private Handler mHandler;
+
+    /** The position in the list of the 'Silent' item. */
+    private int mSilentPos = -1;
+
+    /** The position in the list of the 'Default' item. */
+    private int mDefaultRingtonePos = -1;
+
+    /** The position in the list of the last clicked item. */
+    private int mClickedPos = -1;
+
+    /** The position in the list of the ringtone to sample. */
+    private int mSampleRingtonePos = -1;
+
+    /** 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;
+
+    /**
+     * 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;
+
+    private DialogInterface.OnClickListener mRingtoneClickListener =
+            new DialogInterface.OnClickListener() {
+
+        /*
+         * On item clicked
+         */
+        public void onClick(DialogInterface dialog, int which) {
+            // Save the position of most recently clicked item
+            mClickedPos = which;
+
+            // Play clip
+            playRingtone(which, 0);
+        }
+
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mHandler = new Handler();
+
+        Intent intent = getIntent();
+
+        /*
+         * 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) {
+            mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI;
+        }
+
+        if (savedInstanceState != null) {
+            mClickedPos = savedInstanceState.getInt(SAVE_CLICKED_POS, -1);
+        }
+        // Get whether to show the 'Silent' item
+        mHasSilentItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
+
+        // Give the Activity so it can do managed queries
+        mRingtoneManager = new RingtoneManager(this);
+
+        // Get whether to include DRM ringtones
+        final boolean includeDrm = intent.getBooleanExtra(
+                RingtoneManager.EXTRA_RINGTONE_INCLUDE_DRM, true);
+        mRingtoneManager.setIncludeDrm(includeDrm);
+
+        // Get the types of ringtones to show
+        int types = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, -1);
+        if (types != -1) {
+            mRingtoneManager.setType(types);
+        }
+
+        mCursor = mRingtoneManager.getCursor();
+
+        // The volume keys will control the stream that we are choosing a ringtone for
+        setVolumeControlStream(mRingtoneManager.inferStreamType());
+
+        // Get the URI whose list item should have a checkmark
+        mExistingUri = intent
+                .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
+
+        final AlertController.AlertParams p = mAlertParams;
+        p.mCursor = mCursor;
+        p.mOnClickListener = mRingtoneClickListener;
+        p.mLabelColumn = MediaStore.Audio.Media.TITLE;
+        p.mIsSingleChoice = true;
+        p.mOnItemSelectedListener = this;
+        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;
+
+        p.mTitle = intent.getCharSequenceExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
+        if (p.mTitle == null) {
+            p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title);
+        }
+
+        setupAlert();
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(SAVE_CLICKED_POS, mClickedPos);
+    }
+
+    public void onPrepareListView(ListView listView) {
+
+        if (mHasDefaultItem) {
+            mDefaultRingtonePos = addDefaultRingtoneItem(listView);
+
+            if (RingtoneManager.isDefault(mExistingUri)) {
+                mClickedPos = mDefaultRingtonePos;
+            }
+        }
+
+        if (mHasSilentItem) {
+            mSilentPos = addSilentItem(listView);
+
+            // The 'Silent' item should use a null Uri
+            if (mExistingUri == null) {
+                mClickedPos = mSilentPos;
+            }
+        }
+
+        if (mClickedPos == -1) {
+            mClickedPos = getListPosition(mRingtoneManager.getRingtonePosition(mExistingUri));
+        }
+
+        // Put a checkmark next to an item.
+        mAlertParams.mCheckedItem = mClickedPos;
+    }
+
+    /**
+     * 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_holo, listView, false);
+        textView.setText(textResId);
+        listView.addHeaderView(textView);
+        mStaticItemCount++;
+        return listView.getHeaderViewsCount() - 1;
+    }
+
+    private int addDefaultRingtoneItem(ListView listView) {
+        return addStaticItem(listView, com.android.internal.R.string.ringtone_default);
+    }
+
+    private int addSilentItem(ListView listView) {
+        return addStaticItem(listView, com.android.internal.R.string.ringtone_silent);
+    }
+
+    /*
+     * 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) {
+            Intent resultIntent = new Intent();
+            Uri uri = null;
+
+            if (mClickedPos == mDefaultRingtonePos) {
+                // Set it to the default Uri that they originally gave us
+                uri = mUriForDefaultItem;
+            } else if (mClickedPos == mSilentPos) {
+                // A null Uri is for the 'Silent' item
+                uri = null;
+            } else {
+                uri = mRingtoneManager.getRingtoneUri(getRingtoneManagerPosition(mClickedPos));
+            }
+
+            resultIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, uri);
+            setResult(RESULT_OK, resultIntent);
+        } else {
+            setResult(RESULT_CANCELED);
+        }
+
+        getWindow().getDecorView().post(new Runnable() {
+            public void run() {
+                mCursor.deactivate();
+            }
+        });
+
+        finish();
+    }
+
+    /*
+     * On item selected via keys
+     */
+    public void onItemSelected(AdapterView parent, View view, int position, long id) {
+        playRingtone(position, DELAY_MS_SELECTION_PLAYED);
+    }
+
+    public void onNothingSelected(AdapterView parent) {
+    }
+
+    private void playRingtone(int position, int delayMs) {
+        mHandler.removeCallbacks(this);
+        mSampleRingtonePos = position;
+        mHandler.postDelayed(this, delayMs);
+    }
+
+    public void run() {
+
+        if (mSampleRingtonePos == mSilentPos) {
+            mRingtoneManager.stopPreviousRingtone();
+            return;
+        }
+
+        /*
+         * Stop the default ringtone, if it's playing (other ringtones will be
+         * stopped by the RingtoneManager when we get another Ringtone from it.
+         */
+        if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) {
+            mDefaultRingtone.stop();
+            mDefaultRingtone = null;
+        }
+
+        Ringtone ringtone;
+        if (mSampleRingtonePos == mDefaultRingtonePos) {
+            if (mDefaultRingtone == null) {
+                mDefaultRingtone = RingtoneManager.getRingtone(this, mUriForDefaultItem);
+            }
+            ringtone = mDefaultRingtone;
+
+            /*
+             * Normally the non-static RingtoneManager.getRingtone stops the
+             * previous ringtone, but we're getting the default ringtone outside
+             * of the RingtoneManager instance, so let's stop the previous
+             * ringtone manually.
+             */
+            mRingtoneManager.stopPreviousRingtone();
+
+        } else {
+            ringtone = mRingtoneManager.getRingtone(getRingtoneManagerPosition(mSampleRingtonePos));
+        }
+
+        if (ringtone != null) {
+            ringtone.play();
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        stopAnyPlayingRingtone();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        stopAnyPlayingRingtone();
+    }
+
+    private void stopAnyPlayingRingtone() {
+
+        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;
+    }
+
+}