Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 44b81c6c authored by Iavor-Valentin Iftime's avatar Iavor-Valentin Iftime Committed by Automerger Merge Worker
Browse files

Merge "Restore notification sounds in 2 passes from backup" into udc-dev am: 762da669

parents ad2db050 762da669
Loading
Loading
Loading
Loading
+75 −11
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -246,6 +247,7 @@ public final class NotificationChannel implements Parcelable {
    private boolean mBypassDnd;
    private int mLockscreenVisibility = DEFAULT_VISIBILITY;
    private Uri mSound = Settings.System.DEFAULT_NOTIFICATION_URI;
    private boolean mSoundRestored = false;
    private boolean mLights;
    private int mLightColor = DEFAULT_LIGHT_COLOR;
    private long[] mVibration;
@@ -929,8 +931,9 @@ public final class NotificationChannel implements Parcelable {
    /**
     * @hide
     */
    public void populateFromXmlForRestore(XmlPullParser parser, Context context) {
        populateFromXml(XmlUtils.makeTyped(parser), true, context);
    public void populateFromXmlForRestore(XmlPullParser parser, boolean pkgInstalled,
            Context context) {
        populateFromXml(XmlUtils.makeTyped(parser), true, pkgInstalled, context);
    }

    /**
@@ -938,14 +941,14 @@ public final class NotificationChannel implements Parcelable {
     */
    @SystemApi
    public void populateFromXml(XmlPullParser parser) {
        populateFromXml(XmlUtils.makeTyped(parser), false, null);
        populateFromXml(XmlUtils.makeTyped(parser), false, true, null);
    }

    /**
     * If {@param forRestore} is true, {@param Context} MUST be non-null.
     */
    private void populateFromXml(TypedXmlPullParser parser, boolean forRestore,
            @Nullable Context context) {
            boolean pkgInstalled, @Nullable Context context) {
        Preconditions.checkArgument(!forRestore || context != null,
                "forRestore is true but got null context");

@@ -956,7 +959,8 @@ public final class NotificationChannel implements Parcelable {
        setLockscreenVisibility(safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));

        Uri sound = safeUri(parser, ATT_SOUND);
        setSound(forRestore ? restoreSoundUri(context, sound) : sound, safeAudioAttributes(parser));
        setSound(forRestore ? restoreSoundUri(context, sound, pkgInstalled) : sound,
                safeAudioAttributes(parser));

        enableLights(safeBool(parser, ATT_LIGHTS, false));
        setLightColor(safeInt(parser, ATT_LIGHT_COLOR, DEFAULT_LIGHT_COLOR));
@@ -978,8 +982,58 @@ public final class NotificationChannel implements Parcelable {
        setImportantConversation(safeBool(parser, ATT_IMP_CONVERSATION, false));
    }

    /**
     * Returns whether the sound for this channel was successfully restored
     *  from backup.
     * @return false if the sound was not restored successfully. true otherwise (default value)
     * @hide
     */
    public boolean isSoundRestored() {
        return mSoundRestored;
    }

    @Nullable
    private Uri getCanonicalizedSoundUri(ContentResolver contentResolver, @NonNull Uri uri) {
        if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(uri)) {
            return uri;
        }

        if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())) {
            try {
                contentResolver.getResourceId(uri);
                return uri;
            } catch (FileNotFoundException e) {
                return null;
            }
        }

        if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
            return uri;
        }

        return contentResolver.canonicalize(uri);
    }

    @Nullable
    private Uri restoreSoundUri(Context context, @Nullable Uri uri) {
    private Uri getUncanonicalizedSoundUri(ContentResolver contentResolver, @NonNull Uri uri) {
        if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(uri)
                || ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())
                || ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
            return uri;
        }
        return contentResolver.uncanonicalize(uri);
    }

    /**
     * Restore/validate sound Uri from backup
     * @param context The Context
     * @param uri The sound Uri to restore
     * @param pkgInstalled If the parent package is installed
     * @return restored and validated Uri
     * @hide
     */
    @Nullable
    public Uri restoreSoundUri(Context context, @Nullable Uri uri, boolean pkgInstalled) {
        if (uri == null || Uri.EMPTY.equals(uri)) {
            return null;
        }
@@ -991,12 +1045,22 @@ public final class NotificationChannel implements Parcelable {
        // the uri and in the case of not having the resource we end up with the default - better
        // than broken. As a side effect we'll canonicalize already canonicalized uris, this is fine
        // according to the docs because canonicalize method has to handle canonical uris as well.
        Uri canonicalizedUri = contentResolver.canonicalize(uri);
        Uri canonicalizedUri = getCanonicalizedSoundUri(contentResolver, uri);
        if (canonicalizedUri == null) {
            // We got a null because the uri in the backup does not exist here, so we return default
            // Uri failed to restore with package installed
            if (!mSoundRestored && pkgInstalled) {
                mSoundRestored = true;
                // We got a null because the uri in the backup does not exist here, so we return
                // default
                return Settings.System.DEFAULT_NOTIFICATION_URI;
            } else {
                // Flag as unrestored and try again later (on package install)
                mSoundRestored = false;
                return uri;
            }
        }
        return contentResolver.uncanonicalize(canonicalizedUri);
        mSoundRestored = true;
        return getUncanonicalizedSoundUri(contentResolver, canonicalizedUri);
    }

    /**
@@ -1019,7 +1083,7 @@ public final class NotificationChannel implements Parcelable {
        if (sound == null || Uri.EMPTY.equals(sound)) {
            return null;
        }
        Uri canonicalSound = context.getContentResolver().canonicalize(sound);
        Uri canonicalSound = getCanonicalizedSoundUri(context.getContentResolver(), sound);
        if (canonicalSound == null) {
            // The content provider does not support canonical uris so we backup the default
            return Settings.System.DEFAULT_NOTIFICATION_URI;
+19 −1
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.UserHandle;
@@ -60,6 +61,7 @@ import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseBooleanArray;
@@ -387,7 +389,8 @@ public class PreferencesHelper implements RankingConfig {
                NotificationChannel channel = new NotificationChannel(
                        id, channelName, channelImportance);
                if (forRestore) {
                    channel.populateFromXmlForRestore(parser, mContext);
                    final boolean pkgInstalled = r.uid != UNKNOWN_UID;
                    channel.populateFromXmlForRestore(parser, pkgInstalled, mContext);
                } else {
                    channel.populateFromXml(parser);
                }
@@ -2412,6 +2415,21 @@ public class PreferencesHelper implements RankingConfig {
                        mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, changeUserId));
                        synchronized (mPackagePreferences) {
                            mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r);

                            // Try to restore any unrestored sound resources
                            for (NotificationChannel channel : r.channels.values()) {
                                if (!channel.isSoundRestored()) {
                                    Uri uri = channel.getSound();
                                    Uri restoredUri = channel.restoreSoundUri(mContext, uri, true);
                                    if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(
                                            restoredUri)) {
                                        Log.w(TAG,
                                                "Could not restore sound: " + uri + " for channel: "
                                                        + channel);
                                    }
                                    channel.setSound(restoredUri, channel.getAudioAttributes());
                                }
                            }
                        }
                        if (r.migrateToPm) {
                            try {
+1 −2
Original line number Diff line number Diff line
@@ -40,7 +40,6 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MAX;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
@@ -5312,7 +5311,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        parser.setInput(new BufferedInputStream(
                new ByteArrayInputStream(baos.toByteArray())), null);
        NotificationChannel restored = new NotificationChannel("a", "ab", IMPORTANCE_DEFAULT);
        restored.populateFromXmlForRestore(parser, getContext());
        restored.populateFromXmlForRestore(parser, true, getContext());
        assertNull(restored.getSound());
    }
+127 −0
Original line number Diff line number Diff line
@@ -140,6 +140,7 @@ import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
@@ -170,6 +171,12 @@ public class PreferencesHelperTest extends UiServiceTestCase {
            Uri.parse("content://" + TEST_AUTHORITY
                    + "/internal/audio/media/10?title=Test&canonical=1");

    private static final Uri ANDROID_RES_SOUND_URI =
            Uri.parse("android.resource://" + TEST_AUTHORITY + "/raw/test");

    private static final Uri FILE_SOUND_URI =
            Uri.parse("file://" + TEST_AUTHORITY + "/product/media/test.ogg");

    @Mock PermissionHelper mPermissionHelper;
    @Mock RankingHandler mHandler;
    @Mock PackageManager mPm;
@@ -1338,6 +1345,57 @@ public class PreferencesHelperTest extends UiServiceTestCase {
        assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
    }

    /**
     * Test sound Uri restore retry behavior when channel is restored before package
     *  and then package is installed.
     */
    @Test
    public void testRestoreXml_withNonExistentCanonicalizedSoundUriAndMissingPackage()
            throws Exception {
        // canonicalization returns CANONICAL_SOUND_URI for getSoundForBackup (backup part)
        doReturn(CANONICAL_SOUND_URI)
                .when(mTestIContentProvider).canonicalize(any(), eq(SOUND_URI));

        NotificationChannel channel =
                new NotificationChannel("id", "name", IMPORTANCE_LOW);
        channel.setSound(SOUND_URI, mAudioAttributes);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, true,
                USER_SYSTEM, channel.getId());

        // canonicalization / uncanonicalization returns null for the restore part
        doReturn(null)
                .when(mTestIContentProvider).canonicalize(any(), eq(CANONICAL_SOUND_URI));
        doReturn(null)
                .when(mTestIContentProvider).uncanonicalize(any(), any());

        // simulate package not installed
        when(mPm.getPackageUidAsUser(PKG_N_MR1, USER_SYSTEM)).thenReturn(UNKNOWN_UID);
        when(mPm.getApplicationInfoAsUser(eq(PKG_N_MR1), anyInt(), anyInt())).thenThrow(
                new PackageManager.NameNotFoundException());

        loadStreamXml(baos, true, USER_SYSTEM);

        // 1st restore pass fails
        NotificationChannel actualChannel = mHelper.getNotificationChannel(
                PKG_N_MR1, UNKNOWN_UID, channel.getId(), false);
        // sound is CANONICAL_SOUND_URI, unchanged from backup
        assertEquals(CANONICAL_SOUND_URI, actualChannel.getSound());
        // sound is flagged as not restored
        assertFalse(actualChannel.isSoundRestored());

        // package is "installed"
        when(mPm.getPackageUidAsUser(PKG_N_MR1, USER_SYSTEM)).thenReturn(UID_N_MR1);

        // Trigger 2nd restore pass
        mHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_N_MR1},
                new int[]{UID_N_MR1});

        // sound is flagged as restored and set to default URI
        assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
        assertTrue(actualChannel.isSoundRestored());
    }


    /**
     * Although we don't make backups with uncanonicalized uris anymore, we used to, so we have to
@@ -1363,7 +1421,9 @@ public class PreferencesHelperTest extends UiServiceTestCase {
                backupWithUncanonicalizedSoundUri.getBytes(), true, USER_SYSTEM);

        NotificationChannel actualChannel = mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, id, false);

        assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
        assertTrue(actualChannel.isSoundRestored());
    }

    @Test
@@ -1388,6 +1448,73 @@ public class PreferencesHelperTest extends UiServiceTestCase {
        assertEquals(null, actualChannel.getSound());
    }

    @Test
    public void testBackupRestoreXml_withAndroidResourceSoundUri() throws Exception {
        // Mock ContentResolver.getResourceId:
        // throw exception on restore 1st pass => simulate app not installed yet
        // then return a valid resource on package update => sim. app installed
        ContentResolver contentResolver = mock(ContentResolver.class);
        when(mContext.getContentResolver()).thenReturn(contentResolver);
        ContentResolver.OpenResourceIdResult resId = mock(
                ContentResolver.OpenResourceIdResult.class);
        when(contentResolver.getResourceId(ANDROID_RES_SOUND_URI)).thenReturn(resId).thenThrow(
                new FileNotFoundException("")).thenReturn(resId);

        mHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);

        NotificationChannel channel =
                new NotificationChannel("id", "name", IMPORTANCE_LOW);
        channel.setSound(ANDROID_RES_SOUND_URI, mAudioAttributes);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, true,
                USER_SYSTEM, channel.getId());

        // simulate package not installed
        when(mPm.getPackageUidAsUser(PKG_N_MR1, USER_SYSTEM)).thenReturn(UNKNOWN_UID);
        when(mPm.getApplicationInfoAsUser(eq(PKG_N_MR1), anyInt(), anyInt())).thenThrow(
                new PackageManager.NameNotFoundException());

        loadStreamXml(baos, true, USER_SYSTEM);

        NotificationChannel actualChannel = mHelper.getNotificationChannel(
                PKG_N_MR1, UNKNOWN_UID, channel.getId(), false);
        // sound is ANDROID_RES_SOUND_URI, unchanged from backup
        assertEquals(ANDROID_RES_SOUND_URI, actualChannel.getSound());
        // sound is flagged as not restored
        assertFalse(actualChannel.isSoundRestored());

        // package is "installed"
        when(mPm.getPackageUidAsUser(PKG_N_MR1, USER_SYSTEM)).thenReturn(UID_N_MR1);

        // Trigger 2nd restore pass
        mHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_N_MR1},
                new int[]{UID_N_MR1});

        // sound is flagged as restored
        assertEquals(ANDROID_RES_SOUND_URI, actualChannel.getSound());
        assertTrue(actualChannel.isSoundRestored());
    }

    @Test
    public void testBackupRestoreXml_withFileResourceSoundUri() throws Exception {
        NotificationChannel channel =
                new NotificationChannel("id", "name", IMPORTANCE_LOW);
        channel.setSound(FILE_SOUND_URI, mAudioAttributes);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, true,
                USER_SYSTEM, channel.getId());

        loadStreamXml(baos, true, USER_SYSTEM);

        NotificationChannel actualChannel = mHelper.getNotificationChannel(
                PKG_N_MR1, UID_N_MR1, channel.getId(), false);
        // sound is FILE_SOUND_URI, unchanged from backup
        assertEquals(FILE_SOUND_URI, actualChannel.getSound());
        // sound is flagged as restored
        assertTrue(actualChannel.isSoundRestored());
    }

    @Test
    public void testChannelXml_backup() throws Exception {
        NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");