Как установить файл в качестве мелодии звонка для Android 10? - PullRequest
1 голос
/ 06 октября 2019

Я использовал следующий код для сохранения необработанного звукового файла на внешнее хранилище и установки его в качестве мелодии звонка. Но, похоже, что в Android 10 все меняется. Можете ли вы мне помочь или показать некоторые рекомендации, как обновить мой код для работы с Android 10?

Вот код для сохранения файла на внешнем хранилище:

Сохранение файла:

String path = Environment.getExternalStorageDirectory() + "/customsounds";

 public File getFile() {
        boolean exists = (new File(path).exists());
        if (!exists) {
            new File(path).mkdirs();
        }
        File newSoundFile = new File(path, sound.getFileName() + ".mp3");
        Uri mUri = Uri.parse("android.resource://com.example.customsounds/" + sound.getId());
        ContentResolver mCr = getContentResolver();
        AssetFileDescriptor soundFile;
        try {
            soundFile = mCr.openAssetFileDescriptor(mUri, "r");
        } catch (FileNotFoundException e) {
            soundFile = null;
        }

        try {
            byte[] readData = new byte[1024];
            FileInputStream fis = soundFile.createInputStream();
            FileOutputStream fos = new FileOutputStream(newSoundFile);
            int i = fis.read(readData);

            while (i != -1) {
                fos.write(readData, 0, i);
                i = fis.read(readData);
            }

            fos.close();
        } catch (IOException io) {
            Log.e(TAG, "io exception");
            return null;
        }

        return newSoundFile;
    }

Установка в качестве мелодии звонка:

 public void setAsRingtone() {

    File newSoundFile = getFile();
    ContentValues values = new ContentValues();
    values.put(MediaStore.MediaColumns.DATA, newSoundFile.getAbsolutePath());
    values.put(MediaStore.MediaColumns.TITLE, sound.getTitle());
    values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mp3");
    values.put(MediaStore.MediaColumns.SIZE, newSoundFile.length());
    values.put(MediaStore.Audio.Media.ARTIST, R.string.app_name);
    values.put(MediaStore.Audio.Media.IS_RINGTONE, true);
    values.put(MediaStore.Audio.Media.IS_NOTIFICATION, true);
    values.put(MediaStore.Audio.Media.IS_ALARM, true);
    values.put(MediaStore.Audio.Media.IS_MUSIC, false);

    Uri uri = MediaStore.Audio.Media.getContentUriForPath(newSoundFile.getAbsolutePath());
    getContentResolver().delete(uri,
            MediaStore.MediaColumns.DATA + "=?", new String[]{newSoundFile.getAbsolutePath()});
    Uri newUri = getContentResolver().insert(uri, values);

    RingtoneUtils.setRingtone(this, newUri, type);
}

Вот RingtoneUtils:

import android.content.Context;
import android.content.Intent;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;


public class RingtoneUtils {

    private static final String LOG_TAG = "RingtoneUtils";

    public static boolean setRingtone(@NonNull Context context, @NonNull Uri ringtoneUri, int type) {
        Log.v(LOG_TAG, "Setting Ringtone to: " + ringtoneUri);

        if (!hasMarshmallow()) {
            Log.v(LOG_TAG, "On a Lollipop or below device, so go ahead and change device ringtone");
            setActualRingtone(context, ringtoneUri, type);
            return true;
        } else if (hasMarshmallow() && canEditSystemSettings(context)) {
            Log.v(LOG_TAG, "On a marshmallow or above device but app has the permission to edit system settings");
            setActualRingtone(context, ringtoneUri, type);
            return true;
        } else if (hasMarshmallow() && !canEditSystemSettings(context)) {
            Log.d(LOG_TAG, "On android Marshmallow and above but app does not have permission to" +
                    " edit system settings. Opening the manage write settings activity...");
            startManageWriteSettingsActivity(context);
            Toast.makeText(context, "Please allow app to edit settings so your ringtone/notification can be updated", Toast.LENGTH_LONG).show();
            return false;
        }

        return false;
    }

    private static void setActualRingtone(@NonNull Context context, @NonNull Uri ringtoneUri, int type) {
        RingtoneManager.setActualDefaultRingtoneUri(context, type, ringtoneUri);
        String message="";
        if(type == RingtoneManager.TYPE_RINGTONE) {
            message = context.getString(R.string.ringtone_set_success);
        } else if(type == RingtoneManager.TYPE_NOTIFICATION) {
            message = context.getString(R.string.notification_set_success);
        }
        if ((RingtoneManager.getActualDefaultRingtoneUri(context, type)).equals(ringtoneUri)) {
            Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(context, context.getString(R.string.operation_failed), Toast.LENGTH_SHORT).show();
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    private static void startManageWriteSettingsActivity(@NonNull Context context) {
        Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
        // Passing in the app package here allows the settings app to open the exact app
        intent.setData(Uri.parse("package:" + context.getApplicationContext().getPackageName()));
        // Optional. If you pass in a service context without setting this flag, you will get an exception
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
    }

    private static boolean hasMarshmallow() {
        // returns true if the device is Android Marshmallow or above, false otherwise
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    private static boolean canEditSystemSettings(@NonNull Context context) {
        // returns true if the app can edit system settings, false otherwise
        return Settings.System.canWrite(context.getApplicationContext());
    }

}

Исключение до Android 10: (@ greywolf82)

java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.lastIndexOf(int)' on a null object reference
        at android.os.Parcel.readException(Parcel.java:1957)
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:183)
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:135)
        at android.content.ContentProviderProxy.insert(ContentProviderNative.java:476)
        at android.content.ContentResolver.insert(ContentResolver.java:154

1 Ответ

2 голосов
/ 06 октября 2019

В Android 10 больше нет прямого доступа к интерфейсу файлов, кроме того, у вас нет доступа к столбцу DATA. Вы можете удалить свой метод getFile, и вам нужно изменить метод setAsRingtone():

 public void setAsRingtone() {


    ContentValues values = new ContentValues();
    values.put(MediaStore.MediaColumns.TITLE, sound.getTitle());
    values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mp3");
    values.put(MediaStore.MediaColumns.SIZE, newSoundFile.length());
    values.put(MediaStore.Audio.Media.ARTIST, R.string.app_name);
    values.put(MediaStore.Audio.Media.IS_RINGTONE, true);
    values.put(MediaStore.Audio.Media.IS_NOTIFICATION, true);
    values.put(MediaStore.Audio.Media.IS_ALARM, true);
    values.put(MediaStore.Audio.Media.IS_MUSIC, false);

    Uri newUri = getContentResolver().insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
    try (OutputStream os = getContentResolver().openOutputStream(newUri)) {
         //copy your file from asset into os here
    } catch(Exception ignored) {
    }
    RingtoneUtils.setRingtone(this, newUri, type);
}
...