Can't update sound programmatically for Notification Channel on Android Oreo

时间:2019-04-16 23:12:26

标签: java android android-8.0-oreo

I am having some troubles updating notification sound for a channel into Android Oreo. I know that the sound can be set by the user manually by opening App notifications screen, but I want to do this programmatically by using RingtonePreference into a default Settings activity (user to be able to pick up notification sound from an activity inside my app).

Problem is that the first notification fired into application picks up the default sound value from the PreferenceManager.getDefaultSharedPreferences() and after manually changing it to other media (using RingtonePreference screen) it will still play the sound which was created initially on that channel and not the new one selected by the user.

I don't understand why the NotificationChannel sound is not updated according with the new sound value as I am doing something like this NotificationChannel mChannel = new NotificationChannel(id, title, importance); mChannel.setSound(ringtoneUri, audioAttributes);

Below is the full code:

 public static void sendNotification(Context context, NotificationType notificationType) {
        String id = "channel_1"; // default_channel_id
        String title = "Doppler Channel"; // Default Channel
        Intent intent;
        PendingIntent pendingIntent;
        NotificationCompat.Builder builder;

        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        Uri ringtoneUri = Uri.parse(preferences.getString("notifications_new_message_ringtone", "DEFAULT_RINGTONE_URI"));
        boolean isNotificationSticky = !Boolean.parseBoolean(preferences.getAll().get("stickyNotification").toString());

        AudioAttributes audioAttributes = new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_NOTIFICATION)
                .build();

        NotificationManager notifManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

            int importance = NotificationManager.IMPORTANCE_DEFAULT;

            NotificationChannel mChannel = notifManager.getNotificationChannel(id);

                mChannel = new NotificationChannel(id, title, importance);
                mChannel.setSound(ringtoneUri, audioAttributes);
                notifManager.createNotificationChannel(mChannel);

            builder = new NotificationCompat.Builder(context, id);
            intent = new Intent(context, MainActivity.class);
            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
            pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
            builder .setSmallIcon(notificationType.getNotificationIcon())
                    .setContentTitle(notificationType.getNotificationContent())
                    .setSubText(notificationType.getNotificationTitle())
                    .setOnlyAlertOnce(true)
                    .setOngoing(isNotificationSticky)
                    .setAutoCancel(true)
                    .setSound(ringtoneUri)
                    .setColor(notificationType.getNotificationColor())
                    .setContentIntent(pendingIntent);
        }
Notification notification = builder.build();
notifManager.notify(1, notification);

}

The only way I was able to update the sound was to give the channel id a random value UUID.randomUUID().toString() each time a notification is fired but this is causing a lot of garbage when user manually checks the App notification screen.

A hint on this would be much appreciated.

Thanks a lot!

2 个答案:

答案 0 :(得分:1)

I don't understand why the NotificationChannel sound is not updated according with the new sound value

For the most part, NotificationChannel is a write-once API. You cannot modify most of its characteristics after you create it.

but I want to do this programmatically by using RingtonePreference into a default Settings activity

I recommend removing this feature from your app, or only offering it on Android 7.1 and older devices.

答案 1 :(得分:0)

有一种方法可以使您“欺骗” Android。如果您使用WhatsApp应用程序,则会看到它们允许您从应用程序内部更改消息通知的声音。而且,如果您进行一些调查,您会发现它们实际上是在删除并创建频道。

但是,如果您删除并重新创建相同频道(换句话说,“新”频道与“旧”频道具有相同的频道ID),那么您所做的新更改将不适用。 Android显然会跟踪所有已删除的文件,并在重新创建时阻止对其进行更新。因此,假设您拥有自己的频道(并可以在您的应用中访问其字段)。例如,如果要用户更新频道声音,您需要做的是:

  • 删除旧频道
  • 使用不同的频道ID创建一个新频道,但具有新的更新声音和与刚才删除的频道相同的所有其他用户可见字段。

因此,如果我们假设您有两个已定义“ Base ID”:s的频道,那么我们将基于它们的所有频道ID。然后,每当您的应用启动时,您都可以检查频道是否已经存在,如果存在,请检索它们,否则创建它们。当您添加更多渠道时,这需要一些体力劳动,也许有更好的方法可以做到这一点。但是,如果执行此操作,则可以为每次更改创建一个新通道,并为其分配新的递增ID。下面是解决所有这些问题的类的完整工作示例。在这个示例中,我们有两个带有baseId的通道,并且分别用声音#1和声音#2进行了初始化,但是每次实例化该类时,我们都会更新第二个通道并将其赋予声音#3。

public class TestNotificationHandler {

    private static TestNotificationHandler instance;
    private NotificationManager mNotificationManager;
    private Context mContext;

    private Uri NOTIFICATION_SOUND_1, NOTIFICATION_SOUND_2, NOTIFICATION_SOUND_3, NOTIFICATION_SOUND_4;

    private static final String TAG = TestNotificationHandler.class.getSimpleName();

    public static TestNotificationHandler getInstance(Context context) {
        if (instance == null) {
            instance = new TestNotificationHandler(context);
        }
        return instance;
    }

    private TestNotificationHandler(Context context) {
        mContext = context;
        mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

        NOTIFICATION_SOUND_1 = Uri.parse("android.resource://" + mContext.getPackageName() + "/" + R.raw.sound_1);
        NOTIFICATION_SOUND_2 = Uri.parse("android.resource://" + mContext.getPackageName() + "/" + R.raw.sound_2);
        NOTIFICATION_SOUND_3 = Uri.parse("android.resource://" + mContext.getPackageName() + "/" + R.raw.sound_3);
        NOTIFICATION_SOUND_4 = Uri.parse("android.resource://" + mContext.getPackageName() + "/" + R.raw.sound_4);

        getOrCreateChannels();

        // Only added here for testing, this should of course be done when user actually performs a change from within the app
        setNewChannelSound(testChannel2, NOTIFICATION_SOUND_3);

       // Remember that we now effectively have deleted testChannel2, so running this will not work, b/c no channel with the id that the testChannel2 object has currently exists so we cannot delete it
        setNewChannelSound(testChannel2, NOTIFICATION_SOUND_4);

      // The easy and ugly way would be to simply do this which will update our objects
       getOrCreateChannels();

      // And this will now work
      setNewChannelSound(testChannel2, NOTIFICATION_SOUND_4);


      // If we changed so that setNewChannelSound and updateChannel actually returned the updated 
      // channel (or the already existing one if could not update), then we could do something like: 
      // testChannel2 = setNewChannelSound(testChannel2, NOTIFICATION_SOUND_3);
    }


    AudioAttributes mAudioAttributes = new AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
            .setUsage(AudioAttributes.USAGE_ALARM)
            .build();

    private NotificationChannel testChannel;
    private String baseTestChannelId = "TEST_CHANNEL_ID-";

    private NotificationChannel testChannel2;
    private String baseTest2ChannelId = "TEST_CHANNEL_2_ID-";

    private void getOrCreateChannels() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            for (NotificationChannel channel : mNotificationManager.getNotificationChannels()) {
                // Since we'll incrementally update the ID we'll look for the correct channel with startsWith
                if (channel.getId().startsWith(baseTestChannelId)) {
                    testChannel = channel;
                } else if (channel.getId().startsWith(baseTest2ChannelId)) {
                    testChannel2 = channel;
                }
            }
            if (testChannel == null) {
                // This should only happen the first time the app is launched or if you just added this channel to your app
                // We'll update the ID incrementally so if it doesn't exist will simply create it as number 0

                testChannel = new NotificationChannel(baseTestChannelId + "0", "TEST CHANNEL", NotificationManager.IMPORTANCE_HIGH);
                testChannel.setDescription("First test channel");
                testChannel.setSound(NOTIFICATION_SOUND_1, mAudioAttributes);
                mNotificationManager.createNotificationChannel(testChannel);
            }
            if (testChannel2 == null) {
                // This should only happen the first time the app is launched or if you just added this channel to your app
                // We'll update the ID incrementally so if it doesn't exist will simply create it as number 0
                testChannel2 = new NotificationChannel(baseTest2ChannelId + "0", "TEST CHANNEL 2", NotificationManager.IMPORTANCE_HIGH);
                testChannel2.setDescription("Second test channel");
                testChannel2.setSound(NOTIFICATION_SOUND_2, mAudioAttributes);
                mNotificationManager.createNotificationChannel(testChannel2);
            } 
        }
    }

    private boolean setNewChannelSound(NotificationChannel notificationChannel, Uri newSound) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            notificationChannel.setSound(newSound, mAudioAttributes);
            return updateChannel(notificationChannel);
        }
        return false;
    }


    private boolean updateChannel(NotificationChannel notificationChannel) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            String baseChannelId = getBaseChannelId(notificationChannel);
            if (baseChannelId == null) {
                Log.e(TAG, "Could not find baseChannelId from id: " + notificationChannel.getId());
                return false;
            }
            int oldIndex = getChannelIncrementedIndex(notificationChannel, baseChannelId);
            if (oldIndex == -1) {
                Log.e(TAG, String.format("Could not get old channel index from id: %s and baseChannelId: %d", notificationChannel.getId(), baseChannelId));
                return false;
            }
            NotificationChannel updatedChannel = new NotificationChannel(baseChannelId+(oldIndex+1), notificationChannel.getName(), NotificationManager.IMPORTANCE_HIGH);
            updatedChannel.setDescription(notificationChannel.getDescription());
            updatedChannel.setVibrationPattern(notificationChannel.getVibrationPattern());
            updatedChannel.setSound(notificationChannel.getSound(), mAudioAttributes);
            mNotificationManager.deleteNotificationChannel(notificationChannel.getId());
            mNotificationManager.createNotificationChannel(updatedChannel);
            return true;
        }
        return false;
    }

    /**********************************************************
     Some helper methods
     **********************************************************/
    private String getBaseChannelId(NotificationChannel notificationChannel) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (notificationChannel.getId().startsWith(baseTestChannelId)) {
                return baseTestChannelId;
            } else if (notificationChannel.getId().startsWith(baseTest2ChannelId)) {
                return baseTest2ChannelId;
            }
        }
        return null;
    }

    private int getChannelIncrementedIndex(NotificationChannel channel, String baseChannelId) {
        int index = -1;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            String channelIncIdx = channel.getId().substring(baseChannelId.length(), channel.getId().length());

            try {
                index = Integer.parseInt(channelIncIdx);
            } catch (Exception e) {
                Log.e(TAG, String.format("Could not parse channel index %s", channelIncIdx));
            }
        }
        return index;
    }
}

我要说的唯一缺点是:

  • 实际上您必须添加此额外的逻辑来维护您的频道(而不是简单地创建频道并让用户在OS中进行更改)。
  • 仅适用于版本> = OREO。因此,在较旧的设备上运行此示例将无法完成任何操作。因此,我们需要添加功能来处理该问题。在这种情况下,我们需要将“更新的”通知类型存储在首选项或数据库中,以便以不同于我们默认添加的方式通知操作系统。
  • Android将在“应用程序”通知设置中显示一个计数器,该计数器通知用户频道已被删除多少次。我想说大多数用户永远都不会在乎甚至看不到它,但是可能会有少数人对此感到恼火。