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

Unverified Commit 226118bb authored by Michael Bestas's avatar Michael Bestas
Browse files

Merge tag 'android-14.0.0_r20' into staging/lineage-21.0_merge-android-14.0.0_r20

Android 14.0.0 release 20

# -----BEGIN PGP SIGNATURE-----
#
# iF0EABECAB0WIQRDQNE1cO+UXoOBCWTorT+BmrEOeAUCZZXyHgAKCRDorT+BmrEO
# eCX6AJ9oB0WI5biiG+EFRgFyEYuanDtv/wCfThY3UoaVM3Z2H4ZJ9C3ucFJVAJY=
# =ICM3
# -----END PGP SIGNATURE-----
# gpg: Signature made Thu Jan  4 01:47:42 2024 EET
# gpg:                using DSA key 4340D13570EF945E83810964E8AD3F819AB10E78
# gpg: Good signature from "The Android Open Source Project <initial-contribution@android.com>" [marginal]
# gpg: initial-contribution@android.com: Verified 2219 signatures in the past
#      2 years.  Encrypted 4 messages in the past 24 months.
# gpg: WARNING: This key is not certified with sufficiently trusted signatures!
# gpg:          It is not certain that the signature belongs to the owner.
# Primary key fingerprint: 4340 D135 70EF 945E 8381  0964 E8AD 3F81 9AB1 0E78

# By Evan Chen (1) and Valentin Iftime (1)
# Via Android Build Coastguard Worker
* tag 'android-14.0.0_r20':
  Validate ringtone URIs before setting
  Make sure work profile app cant enable notification listener services

Change-Id: Icf9a03e21b91cbc2d676606d75112e690f410d4d
parents 2452c62d 0dedf05a
Loading
Loading
Loading
Loading
+2 −9
Original line number Diff line number Diff line
@@ -51,16 +51,9 @@ public class DefaultRingtonePreference extends RingtonePreference {
            return;
        }

        String mimeType = mUserContext.getContentResolver().getType(ringtoneUri);
        if (mimeType == null) {
        if (!isValidRingtoneUri(ringtoneUri)) {
            Log.e(TAG, "onSaveRingtone for URI:" + ringtoneUri
                    + " ignored: failure to find mimeType (no access from this context?)");
            return;
        }

        if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg"))) {
            Log.e(TAG, "onSaveRingtone for URI:" + ringtoneUri
                    + " ignored: associated mimeType:" + mimeType + " is not an audio type");
                    + " ignored: invalid ringtone Uri");
            return;
        }

+83 −0
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.settings;

import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
@@ -23,9 +25,11 @@ import android.media.AudioAttributes;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings.System;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;

import androidx.preference.Preference;
import androidx.preference.PreferenceManager;
@@ -239,4 +243,83 @@ public class RingtonePreference extends Preference {
        return true;
    }

    public boolean isDefaultRingtone(Uri ringtoneUri) {
        // null URIs are valid (None/silence)
        return ringtoneUri == null || RingtoneManager.isDefault(ringtoneUri);
    }

    protected boolean isValidRingtoneUri(Uri ringtoneUri) {
        if (isDefaultRingtone(ringtoneUri)) {
            return true;
        }

        // Return early for android resource URIs
        if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(ringtoneUri.getScheme())) {
            return true;
        }

        String mimeType = mUserContext.getContentResolver().getType(ringtoneUri);
        if (mimeType == null) {
            Log.e(TAG, "isValidRingtoneUri for URI:" + ringtoneUri
                    + " failed: failure to find mimeType (no access from this context?)");
            return false;
        }

        if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg")
                || mimeType.equals("application/x-flac"))) {
            Log.e(TAG, "isValidRingtoneUri for URI:" + ringtoneUri
                    + " failed: associated mimeType:" + mimeType + " is not an audio type");
            return false;
        }

        // Validate userId from URIs: content://{userId}@...
        final int userIdFromUri = ContentProvider.getUserIdFromUri(ringtoneUri, mUserId);
        if (userIdFromUri != mUserId) {
            final UserManager userManager = mUserContext.getSystemService(UserManager.class);

            if (!userManager.isSameProfileGroup(mUserId, userIdFromUri)) {
                Log.e(TAG,
                    "isValidRingtoneUri for URI:" + ringtoneUri + " failed: user " + userIdFromUri
                        + " and user " + mUserId + " are not in the same profile group");
                return false;
            }

            final int parentUserId;
            final int profileUserId;
            if (userManager.isProfile()) {
                profileUserId = mUserId;
                parentUserId = userIdFromUri;
            } else {
                parentUserId = mUserId;
                profileUserId = userIdFromUri;
            }

            final UserHandle parent = userManager.getProfileParent(UserHandle.of(profileUserId));
            if (parent == null || parent.getIdentifier() != parentUserId) {
                Log.e(TAG,
                    "isValidRingtoneUri for URI:" + ringtoneUri + " failed: user " + profileUserId
                        + " is not a profile of user " + parentUserId);
                return false;
            }

            // Allow parent <-> managed profile sharing, unless restricted
            if (userManager.hasUserRestrictionForUser(
                UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, UserHandle.of(parentUserId))) {
                Log.e(TAG,
                    "isValidRingtoneUri for URI:" + ringtoneUri + " failed: user " + parentUserId
                        + " has restriction: " + UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE);
                return false;
            }

            if (!(userManager.isManagedProfile(profileUserId) || userManager.getUserProperties(
                    UserHandle.of(profileUserId)).isMediaSharedWithParent())) {
                Log.e(TAG, "isValidRingtoneUri for URI:" + ringtoneUri
                    + " failed: user " + profileUserId + " is not a cloned or managed profile");
                return false;
            }
        }

        return true;
    }

}
+20 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@

package com.android.settings.notification;

import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;

import static com.android.internal.notification.NotificationAccessConfirmationActivityContract.EXTRA_COMPONENT_NAME;
@@ -26,6 +27,7 @@ import android.Manifest;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.NotificationManager;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
@@ -35,10 +37,12 @@ import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
import android.util.Slog;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.widget.Toast;

import com.android.internal.app.AlertActivity;
import com.android.internal.app.AlertController;
@@ -55,12 +59,28 @@ public class NotificationAccessConfirmationActivity extends Activity
    private ComponentName mComponentName;
    private NotificationManager mNm;

    private DevicePolicyManager mDpm;
    private UserManager mUm;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);

        mUm = getSystemService(UserManager.class);
        mDpm = getSystemService(DevicePolicyManager.class);

        if (mUm.isManagedProfile()) {
            Slog.w(LOG_TAG, "Apps in the work profile do not support notification listeners");
            Toast.makeText(this,
                    mDpm.getResources().getString(WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS,
                            () -> getString(R.string.notification_settings_work_profile)),
                    Toast.LENGTH_SHORT).show();
            finish();
            return;
        }

        mNm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        mComponentName = getIntent().getParcelableExtra(EXTRA_COMPONENT_NAME);
+10 −2
Original line number Diff line number Diff line
@@ -25,10 +25,13 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.util.AttributeSet;

import android.util.Log;
import com.android.settings.R;
import com.android.settings.RingtonePreference;

public class NotificationSoundPreference extends RingtonePreference {
    private static final String TAG = "NotificationSoundPreference";

    private Uri mRingtone;

    public NotificationSoundPreference(Context context, AttributeSet attrs) {
@@ -50,8 +53,13 @@ public class NotificationSoundPreference extends RingtonePreference {
    public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
        if (data != null) {
            Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
            if (isValidRingtoneUri(uri)) {
                setRingtone(uri);
                callChangeListener(uri);
            } else {
                Log.e(TAG, "onActivityResult for URI:" + uri
                    + " ignored: invalid ringtone Uri");
            }
        }

        return true;
+77 −4
Original line number Diff line number Diff line
@@ -16,16 +16,20 @@

package com.android.settings;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.ContentInterface;
import android.content.ContentResolver;
import android.content.Context;
import android.media.RingtoneManager;
import android.content.pm.UserProperties;
import android.net.Uri;
import android.os.UserHandle;
import android.os.UserManager;

import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -34,17 +38,22 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

/** Unittest for DefaultRingtonePreference. */
@RunWith(AndroidJUnit4.class)
public class DefaultRingtonePreferenceTest {

    private static final int OWNER_USER_ID = 1;
    private static final int OTHER_USER_ID = 10;
    private static final int INVALID_RINGTONE_TYPE = 0;
    private DefaultRingtonePreference mDefaultRingtonePreference;

    @Mock
    private ContentResolver mContentResolver;
    @Mock
    private UserManager mUserManager;
    private Uri mRingtoneUri;

    @Before
@@ -52,14 +61,29 @@ public class DefaultRingtonePreferenceTest {
        MockitoAnnotations.initMocks(this);

        Context context = spy(ApplicationProvider.getApplicationContext());
        doReturn(mContentResolver).when(context).getContentResolver();
        mContentResolver = ContentResolver.wrap(Mockito.mock(ContentInterface.class));
        when(context.getContentResolver()).thenReturn(mContentResolver);

        mDefaultRingtonePreference = spy(new DefaultRingtonePreference(context, null /* attrs */));
        doReturn(context).when(mDefaultRingtonePreference).getContext();

        // Use INVALID_RINGTONE_TYPE to return early in RingtoneManager.setActualDefaultRingtoneUri
        when(mDefaultRingtonePreference.getRingtoneType())
                .thenReturn(RingtoneManager.TYPE_RINGTONE);
        mDefaultRingtonePreference.setUserId(1);
                .thenReturn(INVALID_RINGTONE_TYPE);

        mDefaultRingtonePreference.setUserId(OWNER_USER_ID);
        mDefaultRingtonePreference.mUserContext = context;
        when(mDefaultRingtonePreference.isDefaultRingtone(any(Uri.class))).thenReturn(false);

        when(context.getSystemServiceName(UserManager.class)).thenReturn(Context.USER_SERVICE);
        when(context.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);

        UserProperties userProperties = new UserProperties.Builder().setMediaSharedWithParent(false)
                .build();
        when(mUserManager.getUserProperties(UserHandle.of(OTHER_USER_ID))).thenReturn(
                userProperties);

        mRingtoneUri = Uri.parse("content://none");
    }

    @Test
@@ -79,4 +103,53 @@ public class DefaultRingtonePreferenceTest {

        verify(mDefaultRingtonePreference, never()).setActualDefaultRingtoneUri(mRingtoneUri);
    }

    @Test
    public void onSaveRingtone_notManagedProfile_shouldNotSetRingtone() {
        mRingtoneUri = Uri.parse("content://" + OTHER_USER_ID + "@ringtone");
        when(mContentResolver.getType(mRingtoneUri)).thenReturn("audio/*");
        when(mUserManager.isSameProfileGroup(OWNER_USER_ID, OTHER_USER_ID)).thenReturn(true);
        when(mUserManager.getProfileParent(UserHandle.of(OTHER_USER_ID))).thenReturn(
                UserHandle.of(OWNER_USER_ID));
        when(mUserManager.isManagedProfile(OTHER_USER_ID)).thenReturn(false);

        mDefaultRingtonePreference.onSaveRingtone(mRingtoneUri);

        verify(mDefaultRingtonePreference, never()).setActualDefaultRingtoneUri(mRingtoneUri);
    }

    @Test
    public void onSaveRingtone_notSameUser_shouldNotSetRingtone() {
        mRingtoneUri = Uri.parse("content://" + OTHER_USER_ID + "@ringtone");
        when(mContentResolver.getType(mRingtoneUri)).thenReturn("audio/*");
        when(mUserManager.isSameProfileGroup(OWNER_USER_ID, OTHER_USER_ID)).thenReturn(false);

        mDefaultRingtonePreference.onSaveRingtone(mRingtoneUri);

        verify(mDefaultRingtonePreference, never()).setActualDefaultRingtoneUri(mRingtoneUri);
    }

    @Test
    public void onSaveRingtone_isManagedProfile_shouldSetRingtone() {
        mRingtoneUri = Uri.parse("content://" + OTHER_USER_ID + "@ringtone");
        when(mContentResolver.getType(mRingtoneUri)).thenReturn("audio/*");
        when(mUserManager.isSameProfileGroup(OWNER_USER_ID, OTHER_USER_ID)).thenReturn(true);
        when(mUserManager.getProfileParent(UserHandle.of(OTHER_USER_ID))).thenReturn(
                UserHandle.of(OWNER_USER_ID));
        when(mUserManager.isManagedProfile(OTHER_USER_ID)).thenReturn(true);

        mDefaultRingtonePreference.onSaveRingtone(mRingtoneUri);

        verify(mDefaultRingtonePreference).setActualDefaultRingtoneUri(mRingtoneUri);
    }

    @Test
    public void onSaveRingtone_defaultUri_shouldSetRingtone() {
        mRingtoneUri = Uri.parse("default_ringtone");
        when(mDefaultRingtonePreference.isDefaultRingtone(any(Uri.class))).thenReturn(true);

        mDefaultRingtonePreference.onSaveRingtone(mRingtoneUri);

        verify(mDefaultRingtonePreference).setActualDefaultRingtoneUri(mRingtoneUri);
    }
}