diff --git a/CHANGELOG.md b/CHANGELOG.md index cdcb4b655..5acad340c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,18 @@ # App Center SDK for Android Change Log -## Version 5.0.3 (In development) +## Version 5.0.4 + +### App Center Distribute + +* **[Fix]** Add RECEIVER_EXPORTED flag for install receiver. +* **[Fix]** Add FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT flag for broadcast pending intent. + +## Version 5.0.3 + +### AppCenter + +* **[Internal]** Add `dataResidencyRegion` option. + ## Version 5.0.2 diff --git a/README.md b/README.md index 49dde2a16..be3497cba 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![Coverage Status](https://coveralls.io/repos/github/Microsoft/AppCenter-SDK-Android/badge.svg?branch=develop)](https://coveralls.io/github/Microsoft/AppCenter-SDK-Android?branch=develop) [![GitHub Release](https://img.shields.io/github/release/microsoft/appcenter-sdk-android.svg)](https://github.com/microsoft/appcenter-sdk-android/releases/latest) -[![Bintray](https://api.bintray.com/packages/vsappcenter/appcenter/appcenter/images/download.svg)](https://bintray.com/vsappcenter/appcenter) [![license](https://img.shields.io/badge/license-MIT%20License-00AAAA.svg)](https://github.com/microsoft/appcenter-sdk-android/blob/master/license.txt) +[![Project Map](https://img.shields.io/badge/SourceSpy-Project_Map-blue.svg)](https://sourcespy.com/github/microsoftappcentersdkandroid/) # Visual Studio App Center SDK for Android diff --git a/apps/sasquatch/src/main/java/com/microsoft/appcenter/sasquatch/activities/SettingsActivity.java b/apps/sasquatch/src/main/java/com/microsoft/appcenter/sasquatch/activities/SettingsActivity.java index 6c2f1dfae..7d5cfe6d9 100644 --- a/apps/sasquatch/src/main/java/com/microsoft/appcenter/sasquatch/activities/SettingsActivity.java +++ b/apps/sasquatch/src/main/java/com/microsoft/appcenter/sasquatch/activities/SettingsActivity.java @@ -13,6 +13,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.FileObserver; import android.preference.CheckBoxPreference; @@ -738,7 +739,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == FILE_ATTACHMENT_DIALOG_ID) { Uri fileAttachment = resultCode == RESULT_OK && data != null ? data.getData() : null; if (fileAttachment != null) { - getActivity().getContentResolver().takePersistableUriPermission(fileAttachment, data.getFlags() & Intent.FLAG_GRANT_READ_URI_PERMISSION); + getActivity().getContentResolver().takePersistableUriPermission(fileAttachment, Intent.FLAG_GRANT_READ_URI_PERMISSION); } MainActivity.setFileAttachment(fileAttachment); Preference preference = getPreferenceManager().findPreference(getString(R.string.appcenter_crashes_file_attachment_key)); diff --git a/sdk/appcenter-distribute/src/main/java/com/microsoft/appcenter/distribute/install/session/InstallStatusReceiver.java b/sdk/appcenter-distribute/src/main/java/com/microsoft/appcenter/distribute/install/session/InstallStatusReceiver.java index a91aad3ab..84db739f1 100644 --- a/sdk/appcenter-distribute/src/main/java/com/microsoft/appcenter/distribute/install/session/InstallStatusReceiver.java +++ b/sdk/appcenter-distribute/src/main/java/com/microsoft/appcenter/distribute/install/session/InstallStatusReceiver.java @@ -7,6 +7,7 @@ import static com.microsoft.appcenter.distribute.DistributeConstants.LOG_TAG; +import android.annotation.SuppressLint; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; @@ -32,6 +33,15 @@ class InstallStatusReceiver extends BroadcastReceiver { @VisibleForTesting static final String INSTALL_STATUS_ACTION = "com.microsoft.appcenter.action.INSTALL_STATUS"; + /** + * Raw value of PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT. + * https://developer.android.com/reference/android/app/PendingIntent#FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT + * This flag will appear only in Android target SDK 34. + */ + @VisibleForTesting + private static final int FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT_VALUE = 16777216; + + static IntentFilter getInstallerReceiverFilter() { IntentFilter installerReceiverFilter = new IntentFilter(); installerReceiverFilter.addAction(INSTALL_STATUS_ACTION); @@ -49,8 +59,12 @@ static IntentSender getInstallStatusIntentSender(Context context, int requestCod int broadcastFlags = 0; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { broadcastFlags = PendingIntent.FLAG_MUTABLE; + if (Build.VERSION.SDK_INT >= 34) { + broadcastFlags |= FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT_VALUE; + } } - PendingIntent pendingIntent = PendingIntent.getBroadcast( + // Suppress the warning as the flag PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT is unavailable on Android SDK < 34. + @SuppressLint("WrongConstant") PendingIntent pendingIntent = PendingIntent.getBroadcast( context, requestCode, new Intent(INSTALL_STATUS_ACTION), diff --git a/sdk/appcenter-distribute/src/main/java/com/microsoft/appcenter/distribute/install/session/SessionReleaseInstaller.java b/sdk/appcenter-distribute/src/main/java/com/microsoft/appcenter/distribute/install/session/SessionReleaseInstaller.java index c855c63b7..1e40d2b4d 100644 --- a/sdk/appcenter-distribute/src/main/java/com/microsoft/appcenter/distribute/install/session/SessionReleaseInstaller.java +++ b/sdk/appcenter-distribute/src/main/java/com/microsoft/appcenter/distribute/install/session/SessionReleaseInstaller.java @@ -14,6 +14,7 @@ import android.content.IntentSender; import android.content.pm.PackageInstaller; import android.net.Uri; +import android.os.Build; import android.os.Handler; import android.os.ParcelFileDescriptor; @@ -233,7 +234,11 @@ private synchronized void registerListeners() { if (mInstallStatusReceiver == null) { AppCenterLog.debug(LOG_TAG, "Register receiver for installing a new release."); mInstallStatusReceiver = new InstallStatusReceiver(this); - mContext.registerReceiver(mInstallStatusReceiver, InstallStatusReceiver.getInstallerReceiverFilter()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + mContext.registerReceiver(mInstallStatusReceiver, InstallStatusReceiver.getInstallerReceiverFilter(), Context.RECEIVER_EXPORTED); + } else { + mContext.registerReceiver(mInstallStatusReceiver, InstallStatusReceiver.getInstallerReceiverFilter()); + } } if (mSessionCallback == null) { PackageInstaller packageInstaller = getPackageInstaller(); diff --git a/sdk/appcenter-distribute/src/test/java/com/microsoft/appcenter/distribute/install/session/InstallStatusReceiverTest.java b/sdk/appcenter-distribute/src/test/java/com/microsoft/appcenter/distribute/install/session/InstallStatusReceiverTest.java index cf461e8ca..c7ead5c86 100644 --- a/sdk/appcenter-distribute/src/test/java/com/microsoft/appcenter/distribute/install/session/InstallStatusReceiverTest.java +++ b/sdk/appcenter-distribute/src/test/java/com/microsoft/appcenter/distribute/install/session/InstallStatusReceiverTest.java @@ -53,6 +53,7 @@ public class InstallStatusReceiverTest { private static final int SESSION_ID = 42; + private static final int FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT_VALUE = 16777216; @Rule public PowerMockRule mRule = new PowerMockRule(); @@ -216,6 +217,14 @@ public void installerReceiverFilter() throws Exception { verify(filter).addAction(INSTALL_STATUS_ACTION); } + @Test + public void createIntentSenderOnAndroid34() { + + /* Mock SDK_INT to 34 target SDK. */ + Whitebox.setInternalState(Build.VERSION.class, "SDK_INT", 34); + createIntentSender(PendingIntent.FLAG_MUTABLE | FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT_VALUE); + } + @Test public void createIntentSenderOnAndroidS() { diff --git a/sdk/appcenter-distribute/src/test/java/com/microsoft/appcenter/distribute/install/session/SessionReleaseInstallerTest.java b/sdk/appcenter-distribute/src/test/java/com/microsoft/appcenter/distribute/install/session/SessionReleaseInstallerTest.java index 4a98e311b..ec9fd594a 100644 --- a/sdk/appcenter-distribute/src/test/java/com/microsoft/appcenter/distribute/install/session/SessionReleaseInstallerTest.java +++ b/sdk/appcenter-distribute/src/test/java/com/microsoft/appcenter/distribute/install/session/SessionReleaseInstallerTest.java @@ -29,6 +29,7 @@ import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.net.Uri; +import android.os.Build; import android.os.Handler; import android.os.ParcelFileDescriptor; @@ -41,12 +42,14 @@ import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.rule.PowerMockRule; +import org.powermock.reflect.Whitebox; import java.io.FileInputStream; import java.io.IOException; @@ -58,7 +61,7 @@ InstallStatusReceiver.class, PackageInstallerListener.class, ReleaseInstallerActivity.class, - SessionReleaseInstaller.class + SessionReleaseInstaller.class, }) public class SessionReleaseInstallerTest { @@ -153,7 +156,9 @@ public Boolean answer(InvocationOnMock invocation) { } @Test - public void installSuccess() throws IOException { + public void installSuccessForSV2() throws IOException { + Whitebox.setInternalState(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.S_V2); + Uri uri = mock(Uri.class); mInstaller.install(uri); @@ -177,6 +182,33 @@ public void installSuccess() throws IOException { verify(mPackageInstaller).abandonSession(eq(SESSION_ID)); } + @Test + public void installSuccessForTiramisu() throws IOException { + Whitebox.setInternalState(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.TIRAMISU); + + Uri uri = mock(Uri.class); + mInstaller.install(uri); + + /* Verify that all required things called. */ + verify(mHandler).post(any(Runnable.class)); + verify(mContext).registerReceiver(eq(mInstallStatusReceiver), any(), anyInt()); + verify(mPackageInstaller).registerSessionCallback(eq(mPackageInstallerListener)); + verify(mInputStream).close(); + verify(mOutputStream).close(); + verify(mSession).commit(any(IntentSender.class)); + verify(mSession, never()).abandon(); + verify(mSession).close(); + verifyNoInteractions(mListener); + + /* Try to star install second time. It's valid case if something goes wrong with previous try. */ + mInstaller.install(uri); + + /* Cancel previous session and re-use callbacks. */ + verify(mContext).registerReceiver(eq(mInstallStatusReceiver), any(), anyInt()); + verify(mPackageInstaller).registerSessionCallback(eq(mPackageInstallerListener)); + verify(mPackageInstaller).abandonSession(eq(SESSION_ID)); + } + @Test public void throwIOExceptionWhenTryToOpenWriteSession() throws IOException { @@ -246,7 +278,7 @@ public void clear() { mInstaller.install(uri); /* Registering callbacks. */ - verify(mContext).registerReceiver(eq(mInstallStatusReceiver), any()); + verify(mContext).registerReceiver(eq(mInstallStatusReceiver), any(), anyInt()); verify(mPackageInstaller).registerSessionCallback(eq(mPackageInstallerListener)); /* Clear after start should clear registered callbacks and abandon the session. */ diff --git a/versions.gradle b/versions.gradle index f8f00f897..006914d96 100644 --- a/versions.gradle +++ b/versions.gradle @@ -6,8 +6,8 @@ // Version constants ext { - versionCode = 72 - versionName = '5.0.3' + versionCode = 73 + versionName = '5.0.4' minSdkVersion = 21 compileSdkVersion = 33 targetSdkVersion = 33