diff options
author | 2023-08-04 14:53:55 +0000 | |
---|---|---|
committer | 2023-08-22 20:36:06 +0000 | |
commit | d073d325be8c36f4f7c2dc8792170e24618cce5d (patch) | |
tree | b31e93216a4f53a0435d9cbcd755601d4189f660 | |
parent | 8c5aa74b217171d25e8b6bd93d38c1a6bbfd52c5 (diff) |
Create haptic vibration library
Add an api to set RingtoneManager media type, and then use this type to determine whether the cursor will return Sound or Vibration items.
Bug: 273903859
Test: atest RingtoneManagerTest
Change-Id: I5a1cc0355fc52d738b6ae266846410556f1f2f1e
-rw-r--r-- | media/TEST_MAPPING | 11 | ||||
-rw-r--r-- | media/java/android/media/RingtoneManager.java | 190 | ||||
-rw-r--r-- | media/tests/ringtone/Android.bp | 30 | ||||
-rw-r--r-- | media/tests/ringtone/AndroidManifest.xml | 41 | ||||
-rw-r--r-- | media/tests/ringtone/TEST_MAPPING | 20 | ||||
-rw-r--r-- | media/tests/ringtone/res/raw/test_haptic_file.ahv | 17 | ||||
-rw-r--r-- | media/tests/ringtone/res/raw/test_sound_file.mp3 | bin | 0 -> 4491 bytes | |||
-rw-r--r-- | media/tests/ringtone/src/com/android/media/RingtoneManagerTest.java | 233 |
8 files changed, 492 insertions, 50 deletions
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING index 5ae77b5a8e2f..a9da832b2a5a 100644 --- a/media/TEST_MAPPING +++ b/media/TEST_MAPPING @@ -37,6 +37,17 @@ } ], "file_patterns": ["(?i)drm|crypto"] + }, + { + "file_patterns": [ + "[^/]*(Ringtone)[^/]*\\.java" + ], + "name": "MediaRingtoneTests", + "options": [ + {"exclude-annotation": "androidx.test.filters.LargeTest"}, + {"exclude-annotation": "androidx.test.filters.FlakyTest"}, + {"exclude-annotation": "org.junit.Ignore"} + ] } ] } diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 0ff1b1e19811..9234479ea8fd 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -16,6 +16,7 @@ package android.media; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -35,19 +36,20 @@ import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.database.StaleDataException; import android.net.Uri; -import android.os.Build; import android.os.Environment; import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.os.vibrator.persistence.VibrationXmlParser; import android.provider.BaseColumns; import android.provider.MediaStore; import android.provider.MediaStore.Audio.AudioColumns; import android.provider.MediaStore.MediaColumns; import android.provider.Settings; import android.provider.Settings.System; +import android.text.TextUtils; import android.util.Log; import com.android.internal.database.SortCursor; @@ -58,6 +60,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; @@ -209,21 +213,30 @@ public class RingtoneManager { */ public static final String EXTRA_RINGTONE_PICKED_URI = "android.intent.extra.ringtone.PICKED_URI"; - + + /** + * Declares the allowed types of media for this RingtoneManager. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "MEDIA_", value = { + Ringtone.MEDIA_SOUND, + Ringtone.MEDIA_VIBRATION, + }) + public @interface MediaType {} + // Make sure the column ordering and then ..._COLUMN_INDEX are in sync - private static final String[] INTERNAL_COLUMNS = new String[] { + private static final String[] MEDIA_AUDIO_COLUMNS = new String[] { MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.TITLE_KEY, }; - private static final String[] MEDIA_COLUMNS = new String[] { - MediaStore.Audio.Media._ID, - MediaStore.Audio.Media.TITLE, - MediaStore.Audio.Media.TITLE, - MediaStore.Audio.Media.TITLE_KEY, + private static final String[] MEDIA_VIBRATION_COLUMNS = new String[]{ + MediaStore.Files.FileColumns._ID, + MediaStore.Files.FileColumns.TITLE, }; /** @@ -251,7 +264,9 @@ public class RingtoneManager { private Cursor mCursor; private int mType = TYPE_RINGTONE; - + @MediaType + private int mMediaType = Ringtone.MEDIA_SOUND; + /** * If a column (item from this list) exists in the Cursor, its value must * be true (value of 1) for the row to be returned. @@ -318,6 +333,41 @@ public class RingtoneManager { } /** + * Sets the media type that will be listed by the RingtoneManager. + * + * <p>This method should be called before calling {@link RingtoneManager#getCursor()}. + * + * @hide + */ + public void setMediaType(@MediaType int mediaType) { + if (mCursor != null) { + throw new IllegalStateException( + "Setting media should be done before calling getCursor()."); + } + + switch (mediaType) { + case Ringtone.MEDIA_SOUND: + case Ringtone.MEDIA_VIBRATION: + mMediaType = mediaType; + break; + default: + throw new IllegalArgumentException("Unsupported media type " + mediaType); + } + } + + /** + * Returns the RingtoneManagers media type. + * + * @return the media type. + * @see #setMediaType + * @hide + */ + @MediaType + public int getMediaType() { + return mMediaType; + } + + /** * Sets which type(s) of ringtones will be listed by this. * * @param type The type(s), one or more of {@link #TYPE_RINGTONE}, @@ -454,19 +504,19 @@ public class RingtoneManager { return mCursor; } - ArrayList<Cursor> ringtoneCursors = new ArrayList<Cursor>(); - ringtoneCursors.add(getInternalRingtones()); - ringtoneCursors.add(getMediaRingtones()); + ArrayList<Cursor> cursors = new ArrayList<>(); + + cursors.add(queryMediaStore(/* internal= */ true)); + cursors.add(queryMediaStore(/* internal= */ false)); if (mIncludeParentRingtones) { Cursor parentRingtonesCursor = getParentProfileRingtones(); if (parentRingtonesCursor != null) { - ringtoneCursors.add(parentRingtonesCursor); + cursors.add(parentRingtonesCursor); } } - - return mCursor = new SortCursor(ringtoneCursors.toArray(new Cursor[ringtoneCursors.size()]), - MediaStore.Audio.Media.DEFAULT_SORT_ORDER); + return mCursor = new SortCursor(cursors.toArray(new Cursor[cursors.size()]), + getSortOrderForMedia(mMediaType)); } private Cursor getParentProfileRingtones() { @@ -478,9 +528,7 @@ public class RingtoneManager { // We don't need to re-add the internal ringtones for the work profile since // they are the same as the personal profile. We just need the external // ringtones. - final Cursor res = getMediaRingtones(parentContext); - return new ExternalRingtonesCursorWrapper(res, ContentProvider.maybeAddUserId( - MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, parentInfo.id)); + return queryMediaStore(parentContext, /* internal= */ false); } } return null; @@ -502,7 +550,7 @@ public class RingtoneManager { Uri positionUri = getRingtoneUri(position); if (Ringtone.useRingtoneV2()) { mPreviousRingtone = new Ringtone.Builder( - mContext, Ringtone.MEDIA_SOUND, getDefaultAudioAttributes(mType)) + mContext, mMediaType, getDefaultAudioAttributes(mType)) .setUri(positionUri) .build(); } else { @@ -675,11 +723,13 @@ public class RingtoneManager { */ public static Uri getValidRingtoneUri(Context context) { final RingtoneManager rm = new RingtoneManager(context); - - Uri uri = getValidRingtoneUriFromCursorAndClose(context, rm.getInternalRingtones()); + + Uri uri = getValidRingtoneUriFromCursorAndClose(context, + rm.queryMediaStore(/* internal= */ true)); if (uri == null) { - uri = getValidRingtoneUriFromCursorAndClose(context, rm.getMediaRingtones()); + uri = getValidRingtoneUriFromCursorAndClose(context, + rm.queryMediaStore(/* internal= */ false)); } return uri; @@ -700,28 +750,26 @@ public class RingtoneManager { } } - @UnsupportedAppUsage - private Cursor getInternalRingtones() { - final Cursor res = query( - MediaStore.Audio.Media.INTERNAL_CONTENT_URI, INTERNAL_COLUMNS, - constructBooleanTrueWhereClause(mFilterColumns), - null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); - return new ExternalRingtonesCursorWrapper(res, MediaStore.Audio.Media.INTERNAL_CONTENT_URI); + private Cursor queryMediaStore(boolean internal) { + return queryMediaStore(mContext, internal); } - private Cursor getMediaRingtones() { - final Cursor res = getMediaRingtones(mContext); - return new ExternalRingtonesCursorWrapper(res, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI); - } + private Cursor queryMediaStore(Context context, boolean internal) { + Uri contentUri = getContentUriForMedia(mMediaType, internal); + String[] columns = + mMediaType == Ringtone.MEDIA_VIBRATION ? MEDIA_VIBRATION_COLUMNS + : MEDIA_AUDIO_COLUMNS; + String whereClause = getWhereClauseForMedia(mMediaType, mFilterColumns); + String sortOrder = getSortOrderForMedia(mMediaType); + + Cursor cursor = query(contentUri, columns, whereClause, /* selectionArgs= */ null, + sortOrder, context); - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private Cursor getMediaRingtones(Context context) { - // MediaStore now returns ringtones on other storage devices, even when - // we don't have storage or audio permissions - return query( - MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MEDIA_COLUMNS, - constructBooleanTrueWhereClause(mFilterColumns), null, - MediaStore.Audio.Media.DEFAULT_SORT_ORDER, context); + if (context.getUserId() != mContext.getUserId()) { + contentUri = ContentProvider.maybeAddUserId(contentUri, context.getUserId()); + } + + return new ExternalRingtonesCursorWrapper(cursor, contentUri); } private void setFilterColumnsList(int type) { @@ -740,6 +788,56 @@ public class RingtoneManager { columns.add(MediaStore.Audio.AudioColumns.IS_ALARM); } } + + /** + * Returns the sort order for the specified media. + * + * @param media The RingtoneManager media type. + * @return The sort order column. + */ + private static String getSortOrderForMedia(@MediaType int media) { + return media == Ringtone.MEDIA_VIBRATION ? MediaStore.Files.FileColumns.TITLE + : MediaStore.Audio.Media.DEFAULT_SORT_ORDER; + } + + /** + * Returns the content URI based on the specified media and whether it's internal or external + * storage. + * + * @param media The RingtoneManager media type. + * @param internal Whether it's for internal or external storage. + * @return The media content URI. + */ + private static Uri getContentUriForMedia(@MediaType int media, boolean internal) { + switch (media) { + case Ringtone.MEDIA_VIBRATION: + return MediaStore.Files.getContentUri( + internal ? MediaStore.VOLUME_INTERNAL : MediaStore.VOLUME_EXTERNAL); + case Ringtone.MEDIA_SOUND: + return internal ? MediaStore.Audio.Media.INTERNAL_CONTENT_URI + : MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + default: + throw new IllegalArgumentException("Unsupported media type " + media); + } + } + + /** + * Constructs a where clause based on the media type. This will be used to find all matching + * sound or vibration files. + * + * @param media The RingtoneManager media type. + * @param columns The columns that must be true, when media type is {@link Ringtone#MEDIA_SOUND} + * @return The where clause. + */ + private static String getWhereClauseForMedia(@MediaType int media, List<String> columns) { + // TODO(b/296213309): Filtering by ringtone-type isn't supported yet for vibrations. + if (media == Ringtone.MEDIA_VIBRATION) { + return TextUtils.formatSimple("(%s='%s')", MediaStore.Files.FileColumns.MIME_TYPE, + VibrationXmlParser.APPLICATION_VIBRATION_XML_MIME_TYPE); + } + + return constructBooleanTrueWhereClause(columns); + } /** * Constructs a where clause that consists of at least one column being 1 @@ -769,14 +867,6 @@ public class RingtoneManager { return sb.toString(); } - - private Cursor query(Uri uri, - String[] projection, - String selection, - String[] selectionArgs, - String sortOrder) { - return query(uri, projection, selection, selectionArgs, sortOrder, mContext); - } private Cursor query(Uri uri, String[] projection, diff --git a/media/tests/ringtone/Android.bp b/media/tests/ringtone/Android.bp new file mode 100644 index 000000000000..55b98c4704b1 --- /dev/null +++ b/media/tests/ringtone/Android.bp @@ -0,0 +1,30 @@ +package { + // See: http://go/android-license-faq + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "MediaRingtoneTests", + + srcs: ["src/**/*.java"], + + libs: [ + "android.test.runner", + "android.test.base", + ], + + static_libs: [ + "androidx.test.rules", + "testng", + "androidx.test.ext.truth", + "frameworks-base-testutils", + ], + + test_suites: [ + "device-tests", + "automotive-tests", + ], + + platform_apis: true, + certificate: "platform", +} diff --git a/media/tests/ringtone/AndroidManifest.xml b/media/tests/ringtone/AndroidManifest.xml new file mode 100644 index 000000000000..27eda07cd0d3 --- /dev/null +++ b/media/tests/ringtone/AndroidManifest.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2023 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.framework.base.media.ringtone.tests"> + + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.MANAGE_USERS" /> + + <application android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + + <activity android:name="MediaRingtoneTests" + android:label="Media Ringtone Tests" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.framework.base.media.ringtone.tests" + android:label="Media Ringtone Tests"/> +</manifest> diff --git a/media/tests/ringtone/TEST_MAPPING b/media/tests/ringtone/TEST_MAPPING new file mode 100644 index 000000000000..6f25c147076c --- /dev/null +++ b/media/tests/ringtone/TEST_MAPPING @@ -0,0 +1,20 @@ +{ + "presubmit": [ + { + "name": "MediaRingtoneTests", + "options": [ + {"exclude-annotation": "androidx.test.filters.LargeTest"}, + {"exclude-annotation": "androidx.test.filters.FlakyTest"}, + {"exclude-annotation": "org.junit.Ignore"} + ] + } + ], + "postsubmit": [ + { + "name": "MediaRingtoneTests", + "options": [ + {"exclude-annotation": "org.junit.Ignore"} + ] + } + ] +}
\ No newline at end of file diff --git a/media/tests/ringtone/res/raw/test_haptic_file.ahv b/media/tests/ringtone/res/raw/test_haptic_file.ahv new file mode 100644 index 000000000000..18c99c79814f --- /dev/null +++ b/media/tests/ringtone/res/raw/test_haptic_file.ahv @@ -0,0 +1,17 @@ +<vibration> + <waveform-effect> + <waveform-entry durationMs="63" amplitude="255"/> + <waveform-entry durationMs="63" amplitude="231"/> + <waveform-entry durationMs="63" amplitude="208"/> + <waveform-entry durationMs="63" amplitude="185"/> + <waveform-entry durationMs="63" amplitude="162"/> + <waveform-entry durationMs="63" amplitude="139"/> + <waveform-entry durationMs="63" amplitude="115"/> + <waveform-entry durationMs="63" amplitude="92"/> + <waveform-entry durationMs="63" amplitude="69"/> + <waveform-entry durationMs="63" amplitude="46"/> + <waveform-entry durationMs="63" amplitude="23"/> + <waveform-entry durationMs="63" amplitude="0"/> + <waveform-entry durationMs="1250" amplitude="0"/> + </waveform-effect> +</vibration> diff --git a/media/tests/ringtone/res/raw/test_sound_file.mp3 b/media/tests/ringtone/res/raw/test_sound_file.mp3 Binary files differnew file mode 100644 index 000000000000..c1b2fdf93991 --- /dev/null +++ b/media/tests/ringtone/res/raw/test_sound_file.mp3 diff --git a/media/tests/ringtone/src/com/android/media/RingtoneManagerTest.java b/media/tests/ringtone/src/com/android/media/RingtoneManagerTest.java new file mode 100644 index 000000000000..a92b29883ce7 --- /dev/null +++ b/media/tests/ringtone/src/com/android/media/RingtoneManagerTest.java @@ -0,0 +1,233 @@ +/* + * Copyright 2023 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.media; + +import static com.google.android.mms.ContentType.AUDIO_MP3; +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; +import static org.junit.Assume.assumeTrue; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Environment; +import android.os.ParcelFileDescriptor; +import android.os.SystemClock; +import android.os.vibrator.persistence.VibrationXmlParser; +import android.provider.MediaStore; +import android.text.TextUtils; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.framework.base.media.ringtone.tests.R; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.FileOutputStream; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@RunWith(Parameterized.class) +public class RingtoneManagerTest { + @RingtoneManager.MediaType + private final int mMediaType; + private final List<Uri> mAddedFilesUri; + private Context mContext; + private RingtoneManager mRingtoneManager; + private long mTimestamp; + + @Parameterized.Parameters(name = "media = {0}") + public static Iterable<?> data() { + return Arrays.asList(Ringtone.MEDIA_SOUND, Ringtone.MEDIA_VIBRATION); + } + + public RingtoneManagerTest(@RingtoneManager.MediaType int mediaType) { + mMediaType = mediaType; + mAddedFilesUri = new ArrayList<>(); + } + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mTimestamp = SystemClock.uptimeMillis(); + mRingtoneManager = new RingtoneManager(mContext); + mRingtoneManager.setMediaType(mMediaType); + } + + @After + public void tearDown() { + // Clean up media store + for (Uri fileUri : mAddedFilesUri) { + mContext.getContentResolver().delete(fileUri, null); + } + } + + @Test + public void testSetMediaType_withValidValue_setsMediaCorrectly() { + mRingtoneManager.setMediaType(mMediaType); + assertThat(mRingtoneManager.getMediaType()).isEqualTo(mMediaType); + } + + @Test + public void testSetMediaType_withInvalidValue_throwsException() { + assertThrows(IllegalArgumentException.class, () -> mRingtoneManager.setMediaType(999)); + } + + @Test + public void testSetMediaType_afterCallingGetCursor_throwsException() { + mRingtoneManager.getCursor(); + assertThrows(IllegalStateException.class, () -> mRingtoneManager.setMediaType(mMediaType)); + } + + @Test + public void testGetRingtone_ringtoneHasCorrectTitle() throws Exception { + String fileName = generateUniqueFileName("new_file"); + Ringtone ringtone = addNewRingtoneToMediaStore(mRingtoneManager, fileName); + + assertThat(ringtone.getTitle(mContext)).isEqualTo(fileName); + } + + @Test + public void testGetRingtone_ringtoneCanBePlayedAndStopped() throws Exception { + //TODO(b/261571543) Remove this assumption once we support playing vibrations. + assumeTrue(mMediaType == Ringtone.MEDIA_SOUND); + String fileName = generateUniqueFileName("new_file"); + Ringtone ringtone = addNewRingtoneToMediaStore(mRingtoneManager, fileName); + + ringtone.play(); + assertThat(ringtone.isPlaying()).isTrue(); + + ringtone.stop(); + assertThat(ringtone.isPlaying()).isFalse(); + } + + @Test + public void testGetCursor_withDifferentMedia_returnsCorrectCursor() throws Exception { + RingtoneManager audioRingtoneManager = new RingtoneManager(mContext); + String audioFileName = generateUniqueFileName("ringtone"); + addNewRingtoneToMediaStore(audioRingtoneManager, audioFileName); + + RingtoneManager vibrationRingtoneManager = new RingtoneManager(mContext); + vibrationRingtoneManager.setMediaType(Ringtone.MEDIA_VIBRATION); + String vibrationFileName = generateUniqueFileName("vibration"); + addNewRingtoneToMediaStore(vibrationRingtoneManager, vibrationFileName); + + Cursor audioCursor = audioRingtoneManager.getCursor(); + Cursor vibrationCursor = vibrationRingtoneManager.getCursor(); + + List<String> audioTitles = extractRecordTitles(audioCursor); + List<String> vibrationTitles = extractRecordTitles(vibrationCursor); + + assertThat(audioTitles).contains(audioFileName); + assertThat(audioTitles).doesNotContain(vibrationFileName); + + assertThat(vibrationTitles).contains(vibrationFileName); + assertThat(vibrationTitles).doesNotContain(audioFileName); + } + + private List<String> extractRecordTitles(Cursor cursor) { + List<String> titles = new ArrayList<>(); + + if (cursor.moveToFirst()) { + do { + String title = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX); + titles.add(title); + } while (cursor.moveToNext()); + } + + return titles; + } + + private Ringtone addNewRingtoneToMediaStore(RingtoneManager ringtoneManager, String fileName) + throws Exception { + Uri fileUri = ringtoneManager.getMediaType() == Ringtone.MEDIA_SOUND ? addAudioFile( + fileName) : addVibrationFile(fileName); + mAddedFilesUri.add(fileUri); + + int ringtonePosition = ringtoneManager.getRingtonePosition(fileUri); + Ringtone ringtone = ringtoneManager.getRingtone(ringtonePosition); + // Validate this is the expected ringtone. + assertThat(ringtone.getUri()).isEqualTo(fileUri); + return ringtone; + } + + private Uri addAudioFile(String fileName) throws Exception { + ContentResolver resolver = mContext.getContentResolver(); + ContentValues contentValues = new ContentValues(); + contentValues.put(MediaStore.Audio.Media.DISPLAY_NAME, fileName + ".mp3"); + contentValues.put(MediaStore.Audio.Media.RELATIVE_PATH, Environment.DIRECTORY_RINGTONES); + contentValues.put(MediaStore.Audio.Media.MIME_TYPE, AUDIO_MP3); + contentValues.put(MediaStore.Audio.Media.TITLE, fileName); + contentValues.put(MediaStore.Audio.Media.IS_RINGTONE, 1); + + Uri contentUri = resolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + contentValues); + writeRawDataToFile(resolver, contentUri, R.raw.test_sound_file); + + return resolver.canonicalizeOrElse(contentUri); + } + + private Uri addVibrationFile(String fileName) throws Exception { + ContentResolver resolver = mContext.getContentResolver(); + ContentValues contentValues = new ContentValues(); + contentValues.put(MediaStore.Files.FileColumns.DISPLAY_NAME, fileName + ".ahv"); + contentValues.put(MediaStore.Files.FileColumns.RELATIVE_PATH, + Environment.DIRECTORY_DOWNLOADS); + contentValues.put(MediaStore.Files.FileColumns.MIME_TYPE, + VibrationXmlParser.APPLICATION_VIBRATION_XML_MIME_TYPE); + contentValues.put(MediaStore.Files.FileColumns.TITLE, fileName); + + Uri contentUri = resolver.insert(MediaStore.Files.getContentUri(MediaStore + .VOLUME_EXTERNAL), contentValues); + writeRawDataToFile(resolver, contentUri, R.raw.test_haptic_file); + + return resolver.canonicalizeOrElse(contentUri); + } + + private void writeRawDataToFile(ContentResolver resolver, Uri contentUri, int rawResource) + throws Exception { + try (ParcelFileDescriptor pfd = + resolver.openFileDescriptor(contentUri, "w", null)) { + InputStream inputStream = mContext.getResources().openRawResource(rawResource); + FileOutputStream outputStream = new FileOutputStream(pfd.getFileDescriptor()); + outputStream.write(inputStream.readAllBytes()); + + inputStream.close(); + outputStream.flush(); + outputStream.close(); + + } catch (Exception e) { + throw new Exception("Failed to write data to file", e); + } + } + + private String generateUniqueFileName(String prefix) { + return TextUtils.formatSimple("%s_%d", prefix, mTimestamp); + } + +} |