Skip to content
Permalink
Browse files

Replace openArtworkInfo() with getArtworkInfo()

Instead of having MuzeiArtProviders directly call startActivity from the background (which is restricted on Android Q), provide a separate getArtworkInfo() API that allows a MuzeiArtProvider to give Muzei a PendingIntent for it to launch.
  • Loading branch information...
ianhanniballake committed May 11, 2019
1 parent 5f30277 commit 13786b01b88c88f3d2924193f91ac4540d683873
@@ -16,16 +16,23 @@

package com.google.android.apps.muzei.room

import android.app.PendingIntent
import android.content.Context
import android.os.Bundle
import android.os.RemoteException
import android.util.Log
import com.google.android.apps.muzei.api.MuzeiContract
import com.google.android.apps.muzei.api.UserCommand
import com.google.android.apps.muzei.api.internal.ProtocolConstants.DEFAULT_VERSION
import com.google.android.apps.muzei.api.internal.ProtocolConstants.GET_ARTWORK_INFO_MIN_VERSION
import com.google.android.apps.muzei.api.internal.ProtocolConstants.KEY_COMMAND
import com.google.android.apps.muzei.api.internal.ProtocolConstants.KEY_COMMANDS
import com.google.android.apps.muzei.api.internal.ProtocolConstants.KEY_GET_ARTWORK_INFO
import com.google.android.apps.muzei.api.internal.ProtocolConstants.KEY_OPEN_ARTWORK_INFO_SUCCESS
import com.google.android.apps.muzei.api.internal.ProtocolConstants.KEY_VERSION
import com.google.android.apps.muzei.api.internal.ProtocolConstants.METHOD_GET_ARTWORK_INFO
import com.google.android.apps.muzei.api.internal.ProtocolConstants.METHOD_GET_COMMANDS
import com.google.android.apps.muzei.api.internal.ProtocolConstants.METHOD_GET_VERSION
import com.google.android.apps.muzei.api.internal.ProtocolConstants.METHOD_OPEN_ARTWORK_INFO
import com.google.android.apps.muzei.api.internal.ProtocolConstants.METHOD_TRIGGER_COMMAND
import com.google.android.apps.muzei.util.ContentProviderClientCompat
@@ -39,8 +46,28 @@ suspend fun Artwork.openArtworkInfo(context: Context) {
val success = ContentProviderClientCompat.getClient(
context, imageUri)?.use { client ->
try {
val result = client.call(METHOD_OPEN_ARTWORK_INFO, imageUri.toString())
result?.getBoolean(KEY_OPEN_ARTWORK_INFO_SUCCESS)
val versionResult = client.call(METHOD_GET_VERSION)
val version = versionResult?.getInt(KEY_VERSION) ?: DEFAULT_VERSION
if (version >= GET_ARTWORK_INFO_MIN_VERSION) {
val result = client.call(METHOD_GET_ARTWORK_INFO, imageUri.toString())
val artworkInfo = result?.getParcelable<PendingIntent>(KEY_GET_ARTWORK_INFO)
try {
artworkInfo?.run {
send()
true
} ?: false
} catch (e: PendingIntent.CanceledException) {
Log.w(TAG, "Provider for $imageUri returned a cancelled " +
"PendingIntent: $artworkInfo", e)
false
} catch (e: Exception) {
Log.w(TAG, "Failed to send() the PendingIntent $artworkInfo for $imageUri", e)
false
}
} else {
val result = client.call(METHOD_OPEN_ARTWORK_INFO, imageUri.toString())
result?.getBoolean(KEY_OPEN_ARTWORK_INFO_SUCCESS)
}
} catch (e: RemoteException) {
Log.i(TAG, "Provider for $imageUri crashed while opening artwork info", e)
false
@@ -17,6 +17,7 @@
package com.google.android.apps.muzei.sources

import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Intent
import android.content.pm.PackageManager
import android.util.Log
@@ -115,31 +116,23 @@ class SourceArtProvider : MuzeiArtProvider() {
}
}

override fun openArtworkInfo(artwork: Artwork): Boolean {
val context = context ?: return false
val webUri = artwork.webUri ?: return false
override fun getArtworkInfo(artwork: Artwork): PendingIntent? {
val context = context ?: return null
val webUri = artwork.webUri ?: return null
try {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Opening artwork info for ${artwork.id}")
}
// Try to parse the metadata as an Intent
Intent.parseUri(webUri.toString(), Intent.URI_INTENT_SCHEME)?.run {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
// Make sure any data URIs granted to Muzei are passed onto the started Activity
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
try {
context.startActivity(this)
return true
} catch (e: RuntimeException) {
// Catch ActivityNotFoundException, SecurityException,
// and FileUriExposedException
Log.w(TAG, "Unable to start view Intent ${this}", e)
}
return PendingIntent.getActivity(context, 0, this, 0)
}
} catch (e: URISyntaxException) {
Log.i(TAG, "Unable to parse viewIntent ${this}", e)
}
return false
return null
}

override fun openFile(artwork: Artwork) =
@@ -39,6 +39,7 @@
private static final String PREFIX = "com.google.android.apps.muzei.api.";
public static final String METHOD_GET_VERSION = PREFIX + "GET_VERSION";
public static final String KEY_VERSION = PREFIX + "VERSION";
public static final int DEFAULT_VERSION = 310000;
public static final String METHOD_REQUEST_LOAD = PREFIX + "REQUEST_LOAD";
public static final String METHOD_MARK_ARTWORK_INVALID = PREFIX + "MARK_ARTWORK_INVALID";
public static final String METHOD_MARK_ARTWORK_LOADED = PREFIX + "MARK_ARTWORK_LOADED";
@@ -54,6 +55,9 @@
public static final String KEY_COMMAND = PREFIX + "COMMAND";
public static final String METHOD_OPEN_ARTWORK_INFO = PREFIX + "OPEN_ARTWORK_INFO";
public static final String KEY_OPEN_ARTWORK_INFO_SUCCESS = PREFIX + "ARTWORK_INFO_SUCCESS";
public static final int GET_ARTWORK_INFO_MIN_VERSION = 320000;
public static final String METHOD_GET_ARTWORK_INFO = PREFIX + "GET_ARTWORK_INFO";
public static final String KEY_GET_ARTWORK_INFO = PREFIX + "ARTWORK_INFO";

private ProtocolConstants() {
}
@@ -219,7 +219,7 @@ public void setPersistentUri(@Nullable Uri persistentUri) {

/**
* Returns the artwork's web URI.
* This is used by default in {@link MuzeiArtProvider#openArtworkInfo(Artwork)} to
* This is used by default in {@link MuzeiArtProvider#getArtworkInfo(Artwork)} to
* allow the user to view more details about the artwork.
*
* @return the artwork's web URI, or null if it doesn't exist
@@ -231,11 +231,11 @@ public Uri getWebUri() {

/**
* Sets the artwork's web URI. This is used by default in
* {@link MuzeiArtProvider#openArtworkInfo(Artwork)} to allow the user to view more details
* {@link MuzeiArtProvider#getArtworkInfo(Artwork)} to allow the user to view more details
* about the artwork.
*
* @param webUri a Uri to more details about the artwork.
* @see MuzeiArtProvider#openArtworkInfo(Artwork)
* @see MuzeiArtProvider#getArtworkInfo(Artwork)
*/
public void setWebUri(@Nullable Uri webUri) {
this.webUri = webUri;
@@ -550,12 +550,12 @@ public Builder persistentUri(@Nullable Uri persistentUri) {

/**
* Sets the artwork's web URI. This is used by default in
* {@link MuzeiArtProvider#openArtworkInfo(Artwork)}
* {@link MuzeiArtProvider#getArtworkInfo(Artwork)}
* to allow the user to view more details about the artwork.
*
* @param webUri a Uri to more details about the artwork.
* @return this {@link Builder}.
* @see MuzeiArtProvider#openArtworkInfo(Artwork)
* @see MuzeiArtProvider#getArtworkInfo(Artwork)
*/
@NonNull
public Builder webUri(@Nullable Uri webUri) {
@@ -16,6 +16,7 @@
package com.google.android.apps.muzei.api.provider;

import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.ContentProvider;
@@ -72,11 +73,13 @@
import static com.google.android.apps.muzei.api.internal.ProtocolConstants.KEY_COMMAND;
import static com.google.android.apps.muzei.api.internal.ProtocolConstants.KEY_COMMANDS;
import static com.google.android.apps.muzei.api.internal.ProtocolConstants.KEY_DESCRIPTION;
import static com.google.android.apps.muzei.api.internal.ProtocolConstants.KEY_GET_ARTWORK_INFO;
import static com.google.android.apps.muzei.api.internal.ProtocolConstants.KEY_LAST_LOADED_TIME;
import static com.google.android.apps.muzei.api.internal.ProtocolConstants.KEY_MAX_LOADED_ARTWORK_ID;
import static com.google.android.apps.muzei.api.internal.ProtocolConstants.KEY_OPEN_ARTWORK_INFO_SUCCESS;
import static com.google.android.apps.muzei.api.internal.ProtocolConstants.KEY_RECENT_ARTWORK_IDS;
import static com.google.android.apps.muzei.api.internal.ProtocolConstants.KEY_VERSION;
import static com.google.android.apps.muzei.api.internal.ProtocolConstants.METHOD_GET_ARTWORK_INFO;
import static com.google.android.apps.muzei.api.internal.ProtocolConstants.METHOD_GET_COMMANDS;
import static com.google.android.apps.muzei.api.internal.ProtocolConstants.METHOD_GET_DESCRIPTION;
import static com.google.android.apps.muzei.api.internal.ProtocolConstants.METHOD_GET_LOAD_INFO;
@@ -183,7 +186,7 @@
* used.
* <p>
* All artwork should support opening an Activity to view more details about the artwork.
* You can provider your own functionality by overriding {@link #openArtworkInfo(Artwork)}.
* You can provider your own functionality by overriding {@link #getArtworkInfo(Artwork)}.
* <p>
* If custom behavior is needed to retrieve the artwork's binary data (for example,
* authentication with a remote server), this behavior can be added to
@@ -202,7 +205,7 @@
* This is a signature permission that only Muzei can hold.
* </p>
*/
@SuppressWarnings({"WeakerAccess", "unused"})
@SuppressWarnings({"unused"})
public static final String ACCESS_PERMISSION
= "com.google.android.apps.muzei.api.ACCESS_PROVIDER";
/**
@@ -511,6 +514,7 @@ public Bundle call(
null, null, null, null)) {
if (data != null && data.moveToNext()) {
Bundle bundle = new Bundle();
@SuppressWarnings("deprecation")
boolean success = openArtworkInfo(Artwork.fromCursor(data));
bundle.putBoolean(KEY_OPEN_ARTWORK_INFO_SUCCESS, success);
if (DEBUG) {
@@ -520,6 +524,20 @@ public Bundle call(
}
}
break;
case METHOD_GET_ARTWORK_INFO:
try (Cursor data = context.getContentResolver().query(Uri.parse(arg),
null, null, null, null)) {
if (data != null && data.moveToNext()) {
Bundle bundle = new Bundle();
PendingIntent artworkInfo = getArtworkInfo(Artwork.fromCursor(data));
bundle.putParcelable(KEY_GET_ARTWORK_INFO, artworkInfo);
if (DEBUG) {
Log.d(TAG, "For " + METHOD_GET_ARTWORK_INFO + " returning " + bundle);
}
return bundle;
}
}
break;
}
return null;
} finally {
@@ -609,7 +627,12 @@ protected void onCommand(@NonNull final Artwork artwork, int id) {
*
* @param artwork The artwork the user wants to see more information about.
* @return True if the artwork info was successfully opened.
* @deprecated Override {@link #getArtworkInfo(Artwork)} to return a {@link PendingIntent}
* that starts your artwork info. This method will still be called on devices that
* have an older version of Muzei installed.
*/
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
protected boolean openArtworkInfo(@NonNull Artwork artwork) {
if (artwork.getWebUri() != null && getContext() != null) {
try {
@@ -625,6 +648,24 @@ protected boolean openArtworkInfo(@NonNull Artwork artwork) {
return false;
}

/**
* Callback when the user wishes to see more information about the given artwork. The default
* implementation constructs a {@link PendingIntent} to the
* {@link ProviderContract.Artwork#WEB_URI web uri} of the artwork.
*
* @param artwork The artwork the user wants to see more information about.
* @return A {@link PendingIntent} generally constructed with
* {@link PendingIntent#getActivity(Context, int, Intent, int)}.
*/
@Nullable
protected PendingIntent getArtworkInfo(@NonNull Artwork artwork) {
if (artwork.getWebUri() != null && getContext() != null) {
Intent intent = new Intent(Intent.ACTION_VIEW, artwork.getWebUri());
return PendingIntent.getActivity(getContext(), 0, intent, 0);
}
return null;
}

@CallSuper
@Override
public boolean onCreate() {
@@ -17,8 +17,8 @@
package com.google.android.apps.muzei.gallery

import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Intent
import android.util.Log
import androidx.core.net.toUri
import com.google.android.apps.muzei.api.provider.Artwork
import com.google.android.apps.muzei.api.provider.MuzeiArtProvider
@@ -27,10 +27,6 @@ import java.io.IOException
import java.io.InputStream

class GalleryArtProvider: MuzeiArtProvider() {
companion object {
private const val TAG = "GalleryArtProvider"
}

override fun onLoadRequested(initial: Boolean) {
GalleryScanWorker.enqueueRescan()
}
@@ -52,20 +48,13 @@ class GalleryArtProvider: MuzeiArtProvider() {
}
}

override fun openArtworkInfo(artwork: Artwork): Boolean {
val context = context ?: return false
val uri = artwork.webUri ?: artwork.persistentUri ?: return false
return try {
context.startActivity(Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "image/*")
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_GRANT_READ_URI_PERMISSION)
})
true
} catch (e: Exception) {
Log.w(TAG, "Could not open artwork info for $uri", e)
false
}
override fun getArtworkInfo(artwork: Artwork): PendingIntent? {
val context = context ?: return null
val uri = artwork.webUri ?: artwork.persistentUri ?: return null
return PendingIntent.getActivity(context, 0, Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "image/*")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}, 0)
}

override fun isArtworkValid(artwork: Artwork): Boolean {
@@ -17,7 +17,7 @@
package com.google.android.apps.muzei.single

import android.annotation.SuppressLint
import android.content.ActivityNotFoundException
import android.app.PendingIntent
import android.content.ContentUris
import android.content.Context
import android.content.Intent
@@ -140,20 +140,13 @@ class SingleArtProvider : MuzeiArtProvider() {
// so there's never any additional artwork to load
}

override fun openArtworkInfo(artwork: Artwork): Boolean {
val context = context ?: return false
override fun getArtworkInfo(artwork: Artwork): PendingIntent? {
val context = context ?: return null
val uri = ContentUris.withAppendedId(contentUri, artwork.id)
return try {
context.startActivity(Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "image/*")
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_GRANT_READ_URI_PERMISSION)
})
true
} catch (e: ActivityNotFoundException) {
Log.w(TAG, "Could not open artwork info for $uri", e)
false
}
return PendingIntent.getActivity(context, 0, Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "image/*")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}, 0)
}

override fun openFile(artwork: Artwork) = FileInputStream(getArtworkFile(
@@ -14,8 +14,8 @@
# limitations under the License.
#

apiName = 3.1.0
apiCode = 310000
apiName = 3.2.0-alpha01
apiCode = 320000

name = 3.1.0
# Version number + unique ID for the build

0 comments on commit 13786b0

Please sign in to comment.
You can’t perform that action at this time.