Search in sources :

Example 1 with UserCommand

use of com.google.android.apps.muzei.api.UserCommand in project muzei by romannurik.

the class NewWallpaperNotificationReceiver method triggerUserCommandFromRemoteInput.

private void triggerUserCommandFromRemoteInput(Context context, Intent intent) {
    Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
    if (remoteInput == null) {
        return;
    }
    String selectedCommand = remoteInput.getCharSequence(EXTRA_USER_COMMAND).toString();
    Cursor selectedSource = context.getContentResolver().query(MuzeiContract.Sources.CONTENT_URI, new String[] { MuzeiContract.Sources.COLUMN_NAME_COMMANDS }, MuzeiContract.Sources.COLUMN_NAME_IS_SELECTED + "=1", null, null, null);
    if (selectedSource != null && selectedSource.moveToFirst()) {
        List<UserCommand> commands = MuzeiContract.Sources.parseCommands(selectedSource.getString(0));
        for (UserCommand action : commands) {
            if (TextUtils.equals(selectedCommand, action.getTitle())) {
                SourceManager.sendAction(context, action.getId());
                break;
            }
        }
    }
    if (selectedSource != null) {
        selectedSource.close();
    }
}
Also used : UserCommand(com.google.android.apps.muzei.api.UserCommand) Bundle(android.os.Bundle) Cursor(android.database.Cursor)

Example 2 with UserCommand

use of com.google.android.apps.muzei.api.UserCommand in project muzei by romannurik.

the class SourceManager method migrateDataToContentProvider.

/**
     * One time migration of source data from SharedPreferences to the ContentProvider
     */
private static void migrateDataToContentProvider(Context context) {
    SharedPreferences sharedPrefs = context.getSharedPreferences("muzei_art_sources", 0);
    String selectedSourceString = sharedPrefs.getString(PREF_SELECTED_SOURCE, null);
    Set<String> sourceStates = sharedPrefs.getStringSet(PREF_SOURCE_STATES, null);
    if (selectedSourceString == null || sourceStates == null) {
        return;
    }
    ComponentName selectedSource = ComponentName.unflattenFromString(selectedSourceString);
    final ArrayList<ContentProviderOperation> operations = new ArrayList<>();
    for (String sourceStatesPair : sourceStates) {
        String[] pair = sourceStatesPair.split("\\|", 2);
        ComponentName source = ComponentName.unflattenFromString(pair[0]);
        try {
            ContentValues values = new ContentValues();
            try {
                // Ensure the source is a valid Service
                context.getPackageManager().getServiceInfo(source, 0);
            } catch (PackageManager.NameNotFoundException e) {
                // No need to keep no longer valid sources
                continue;
            }
            values.put(MuzeiContract.Sources.COLUMN_NAME_COMPONENT_NAME, source.flattenToShortString());
            values.put(MuzeiContract.Sources.COLUMN_NAME_IS_SELECTED, source.equals(selectedSource));
            JSONObject jsonObject = (JSONObject) new JSONTokener(pair[1]).nextValue();
            values.put(MuzeiContract.Sources.COLUMN_NAME_DESCRIPTION, jsonObject.optString("description"));
            values.put(MuzeiContract.Sources.COLUMN_NAME_WANTS_NETWORK_AVAILABLE, jsonObject.optBoolean("wantsNetworkAvailable"));
            // Parse out the UserCommands. This ensures it is properly formatted and extracts the
            // Next Artwork built in command from the list
            List<UserCommand> commands = MuzeiContract.Sources.parseCommands(jsonObject.optJSONArray("userCommands").toString());
            JSONArray commandsSerialized = new JSONArray();
            boolean supportsNextArtwork = false;
            for (UserCommand command : commands) {
                if (command.getId() == MuzeiArtSource.BUILTIN_COMMAND_ID_NEXT_ARTWORK) {
                    supportsNextArtwork = true;
                } else {
                    commandsSerialized.put(command.serialize());
                }
            }
            values.put(MuzeiContract.Sources.COLUMN_NAME_SUPPORTS_NEXT_ARTWORK_COMMAND, supportsNextArtwork);
            values.put(MuzeiContract.Sources.COLUMN_NAME_COMMANDS, commandsSerialized.toString());
            operations.add(ContentProviderOperation.newInsert(MuzeiContract.Sources.CONTENT_URI).withValues(values).build());
        } catch (JSONException e) {
            Log.e(TAG, "Error loading source state for " + source, e);
        }
    }
    try {
        context.getContentResolver().applyBatch(MuzeiContract.AUTHORITY, operations);
        sharedPrefs.edit().remove(PREF_SELECTED_SOURCE).remove(PREF_SOURCE_STATES).apply();
        sendSelectedSourceAnalytics(context, selectedSource);
    } catch (RemoteException | OperationApplicationException e) {
        Log.e(TAG, "Error writing sources to ContentProvider", e);
    }
}
Also used : ContentValues(android.content.ContentValues) ContentProviderOperation(android.content.ContentProviderOperation) SharedPreferences(android.content.SharedPreferences) ArrayList(java.util.ArrayList) JSONArray(org.json.JSONArray) JSONException(org.json.JSONException) JSONTokener(org.json.JSONTokener) UserCommand(com.google.android.apps.muzei.api.UserCommand) PackageManager(android.content.pm.PackageManager) JSONObject(org.json.JSONObject) ComponentName(android.content.ComponentName) RemoteException(android.os.RemoteException) OperationApplicationException(android.content.OperationApplicationException)

Example 3 with UserCommand

use of com.google.android.apps.muzei.api.UserCommand in project muzei by romannurik.

the class SourceSubscriberService method onHandleIntent.

@Override
protected void onHandleIntent(Intent intent) {
    if (intent == null || intent.getAction() == null) {
        return;
    }
    String action = intent.getAction();
    if (!ACTION_PUBLISH_STATE.equals(action)) {
        return;
    }
    // Handle API call from source
    String token = intent.getStringExtra(EXTRA_TOKEN);
    ComponentName selectedSource = SourceManager.getSelectedSource(this);
    if (selectedSource == null || !TextUtils.equals(token, selectedSource.flattenToShortString())) {
        Log.w(TAG, "Dropping update from non-selected source, token=" + token + " does not match token for " + selectedSource);
        return;
    }
    SourceState state = null;
    if (intent.hasExtra(EXTRA_STATE)) {
        Bundle bundle = intent.getBundleExtra(EXTRA_STATE);
        if (bundle != null) {
            state = SourceState.fromBundle(bundle);
        }
    }
    if (state == null) {
        // If there is no state, there is nothing to change
        return;
    }
    ContentValues values = new ContentValues();
    values.put(MuzeiContract.Sources.COLUMN_NAME_COMPONENT_NAME, selectedSource.flattenToShortString());
    values.put(MuzeiContract.Sources.COLUMN_NAME_IS_SELECTED, true);
    values.put(MuzeiContract.Sources.COLUMN_NAME_DESCRIPTION, state.getDescription());
    values.put(MuzeiContract.Sources.COLUMN_NAME_WANTS_NETWORK_AVAILABLE, state.getWantsNetworkAvailable());
    JSONArray commandsSerialized = new JSONArray();
    int numSourceActions = state.getNumUserCommands();
    boolean supportsNextArtwork = false;
    for (int i = 0; i < numSourceActions; i++) {
        UserCommand command = state.getUserCommandAt(i);
        if (command.getId() == MuzeiArtSource.BUILTIN_COMMAND_ID_NEXT_ARTWORK) {
            supportsNextArtwork = true;
        } else {
            commandsSerialized.put(command.serialize());
        }
    }
    values.put(MuzeiContract.Sources.COLUMN_NAME_SUPPORTS_NEXT_ARTWORK_COMMAND, supportsNextArtwork);
    values.put(MuzeiContract.Sources.COLUMN_NAME_COMMANDS, commandsSerialized.toString());
    ContentResolver contentResolver = getContentResolver();
    Cursor existingSource = contentResolver.query(MuzeiContract.Sources.CONTENT_URI, new String[] { BaseColumns._ID }, MuzeiContract.Sources.COLUMN_NAME_COMPONENT_NAME + "=?", new String[] { selectedSource.flattenToShortString() }, null, null);
    if (existingSource != null && existingSource.moveToFirst()) {
        Uri sourceUri = ContentUris.withAppendedId(MuzeiContract.Sources.CONTENT_URI, existingSource.getLong(0));
        contentResolver.update(sourceUri, values, null, null);
    } else {
        contentResolver.insert(MuzeiContract.Sources.CONTENT_URI, values);
    }
    if (existingSource != null) {
        existingSource.close();
    }
    Artwork artwork = state.getCurrentArtwork();
    if (artwork != null) {
        artwork.setComponentName(selectedSource);
        contentResolver.insert(MuzeiContract.Artwork.CONTENT_URI, artwork.toContentValues());
        // Download the artwork contained from the newly published SourceState
        startService(TaskQueueService.getDownloadCurrentArtworkIntent(this));
    }
}
Also used : ContentValues(android.content.ContentValues) SourceState(com.google.android.apps.muzei.api.internal.SourceState) Artwork(com.google.android.apps.muzei.api.Artwork) Bundle(android.os.Bundle) JSONArray(org.json.JSONArray) Cursor(android.database.Cursor) Uri(android.net.Uri) ContentResolver(android.content.ContentResolver) UserCommand(com.google.android.apps.muzei.api.UserCommand) ComponentName(android.content.ComponentName)

Example 4 with UserCommand

use of com.google.android.apps.muzei.api.UserCommand in project muzei by romannurik.

the class NewWallpaperNotificationReceiver method maybeShowNewArtworkNotification.

public static void maybeShowNewArtworkNotification(Context context) {
    ArtDetailOpenedClosedEvent adoce = EventBus.getDefault().getStickyEvent(ArtDetailOpenedClosedEvent.class);
    if (adoce != null && adoce.isArtDetailOpened()) {
        return;
    }
    SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
    if (!sp.getBoolean(PREF_ENABLED, true)) {
        return;
    }
    ContentResolver contentResolver = context.getContentResolver();
    Cursor artwork = contentResolver.query(MuzeiContract.Artwork.CONTENT_URI, new String[] { BaseColumns._ID, MuzeiContract.Artwork.COLUMN_NAME_IMAGE_URI, MuzeiContract.Artwork.COLUMN_NAME_TOKEN, MuzeiContract.Artwork.COLUMN_NAME_TITLE, MuzeiContract.Artwork.COLUMN_NAME_BYLINE, MuzeiContract.Artwork.COLUMN_NAME_VIEW_INTENT, MuzeiContract.Sources.COLUMN_NAME_SUPPORTS_NEXT_ARTWORK_COMMAND, MuzeiContract.Sources.COLUMN_NAME_COMMANDS }, null, null, null);
    if (artwork == null || !artwork.moveToFirst()) {
        if (artwork != null) {
            artwork.close();
        }
        return;
    }
    long currentArtworkId = artwork.getLong(artwork.getColumnIndex(BaseColumns._ID));
    long lastReadArtworkId = sp.getLong(PREF_LAST_READ_NOTIFICATION_ARTWORK_ID, -1);
    String currentImageUri = artwork.getString(artwork.getColumnIndex(MuzeiContract.Artwork.COLUMN_NAME_IMAGE_URI));
    String lastReadImageUri = sp.getString(PREF_LAST_READ_NOTIFICATION_ARTWORK_IMAGE_URI, null);
    String currentToken = artwork.getString(artwork.getColumnIndex(MuzeiContract.Artwork.COLUMN_NAME_TOKEN));
    String lastReadToken = sp.getString(PREF_LAST_READ_NOTIFICATION_ARTWORK_TOKEN, null);
    // We've already dismissed the notification if the IDs match
    boolean previouslyDismissedNotification = lastReadArtworkId == currentArtworkId;
    // We've already dismissed the notification if the image URIs match and both are not empty
    previouslyDismissedNotification = previouslyDismissedNotification || (!TextUtils.isEmpty(lastReadImageUri) && !TextUtils.isEmpty(currentImageUri) && TextUtils.equals(lastReadImageUri, currentImageUri));
    // We've already dismissed the notification if the tokens match and both are not empty
    previouslyDismissedNotification = previouslyDismissedNotification || (!TextUtils.isEmpty(lastReadToken) && !TextUtils.isEmpty(currentToken) && TextUtils.equals(lastReadToken, currentToken));
    if (previouslyDismissedNotification) {
        artwork.close();
        return;
    }
    Bitmap largeIcon;
    Bitmap background;
    try {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(contentResolver.openInputStream(MuzeiContract.Artwork.CONTENT_URI), null, options);
        int width = options.outWidth;
        int height = options.outHeight;
        int shortestLength = Math.min(width, height);
        options.inJustDecodeBounds = false;
        int largeIconHeight = context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
        options.inSampleSize = ImageUtil.calculateSampleSize(shortestLength, largeIconHeight);
        largeIcon = BitmapFactory.decodeStream(contentResolver.openInputStream(MuzeiContract.Artwork.CONTENT_URI), null, options);
        // Use the suggested 400x400 for Android Wear background images per
        // http://developer.android.com/training/wearables/notifications/creating.html#AddWearableFeatures
        options.inSampleSize = ImageUtil.calculateSampleSize(height, 400);
        background = BitmapFactory.decodeStream(contentResolver.openInputStream(MuzeiContract.Artwork.CONTENT_URI), null, options);
    } catch (FileNotFoundException e) {
        Log.e(TAG, "Unable to read artwork to show notification", e);
        return;
    }
    String artworkTitle = artwork.getString(artwork.getColumnIndex(MuzeiContract.Artwork.COLUMN_NAME_TITLE));
    String title = TextUtils.isEmpty(artworkTitle) ? context.getString(R.string.app_name) : artworkTitle;
    NotificationCompat.Builder nb = new NotificationCompat.Builder(context).setSmallIcon(R.drawable.ic_stat_muzei).setColor(ContextCompat.getColor(context, R.color.notification)).setPriority(Notification.PRIORITY_MIN).setAutoCancel(true).setContentTitle(title).setContentText(context.getString(R.string.notification_new_wallpaper)).setLargeIcon(largeIcon).setContentIntent(PendingIntent.getActivity(context, 0, Intent.makeMainActivity(new ComponentName(context, MuzeiActivity.class)), PendingIntent.FLAG_UPDATE_CURRENT)).setDeleteIntent(PendingIntent.getBroadcast(context, 0, new Intent(context, NewWallpaperNotificationReceiver.class).setAction(ACTION_MARK_NOTIFICATION_READ), PendingIntent.FLAG_UPDATE_CURRENT));
    NotificationCompat.BigPictureStyle style = new NotificationCompat.BigPictureStyle().bigLargeIcon(null).setBigContentTitle(title).setSummaryText(artwork.getString(artwork.getColumnIndex(MuzeiContract.Artwork.COLUMN_NAME_BYLINE))).bigPicture(background);
    nb.setStyle(style);
    NotificationCompat.WearableExtender extender = new NotificationCompat.WearableExtender();
    // Support Next Artwork
    if (artwork.getInt(artwork.getColumnIndex(MuzeiContract.Sources.COLUMN_NAME_SUPPORTS_NEXT_ARTWORK_COMMAND)) != 0) {
        PendingIntent nextPendingIntent = PendingIntent.getBroadcast(context, 0, new Intent(context, NewWallpaperNotificationReceiver.class).setAction(ACTION_NEXT_ARTWORK), PendingIntent.FLAG_UPDATE_CURRENT);
        nb.addAction(R.drawable.ic_notif_next_artwork, context.getString(R.string.action_next_artwork_condensed), nextPendingIntent);
        // Android Wear uses larger action icons so we build a
        // separate action
        extender.addAction(new NotificationCompat.Action.Builder(R.drawable.ic_notif_full_next_artwork, context.getString(R.string.action_next_artwork_condensed), nextPendingIntent).extend(new NotificationCompat.Action.WearableExtender().setAvailableOffline(false)).build());
    }
    List<UserCommand> commands = MuzeiContract.Sources.parseCommands(artwork.getString(artwork.getColumnIndex(MuzeiContract.Sources.COLUMN_NAME_COMMANDS)));
    // Show custom actions as a selectable list on Android Wear devices
    if (!commands.isEmpty()) {
        String[] actions = new String[commands.size()];
        for (int h = 0; h < commands.size(); h++) {
            actions[h] = commands.get(h).getTitle();
        }
        PendingIntent userCommandPendingIntent = PendingIntent.getBroadcast(context, 0, new Intent(context, NewWallpaperNotificationReceiver.class).setAction(ACTION_USER_COMMAND), PendingIntent.FLAG_UPDATE_CURRENT);
        RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_USER_COMMAND).setAllowFreeFormInput(false).setLabel(context.getString(R.string.action_user_command_prompt)).setChoices(actions).build();
        extender.addAction(new NotificationCompat.Action.Builder(R.drawable.ic_notif_full_user_command, context.getString(R.string.action_user_command), userCommandPendingIntent).addRemoteInput(remoteInput).extend(new NotificationCompat.Action.WearableExtender().setAvailableOffline(false)).build());
    }
    Intent viewIntent = null;
    try {
        String viewIntentString = artwork.getString(artwork.getColumnIndex(MuzeiContract.Artwork.COLUMN_NAME_VIEW_INTENT));
        if (!TextUtils.isEmpty(viewIntentString)) {
            viewIntent = Intent.parseUri(viewIntentString, Intent.URI_INTENT_SCHEME);
        }
    } catch (URISyntaxException ignored) {
    }
    if (viewIntent != null) {
        viewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        try {
            PendingIntent nextPendingIntent = PendingIntent.getActivity(context, 0, viewIntent, PendingIntent.FLAG_UPDATE_CURRENT);
            nb.addAction(R.drawable.ic_notif_info, context.getString(R.string.action_artwork_info), nextPendingIntent);
            // Android Wear uses larger action icons so we build a
            // separate action
            extender.addAction(new NotificationCompat.Action.Builder(R.drawable.ic_notif_full_info, context.getString(R.string.action_artwork_info), nextPendingIntent).extend(new NotificationCompat.Action.WearableExtender().setAvailableOffline(false)).build());
        } catch (RuntimeException ignored) {
        // This is actually meant to catch a FileUriExposedException, but you can't
        // have catch statements for exceptions that don't exist at your minSdkVersion
        }
    }
    nb.extend(extender);
    // Hide the image and artwork title for the public version
    NotificationCompat.Builder publicBuilder = new NotificationCompat.Builder(context).setSmallIcon(R.drawable.ic_stat_muzei).setColor(ContextCompat.getColor(context, R.color.notification)).setPriority(Notification.PRIORITY_MIN).setAutoCancel(true).setContentTitle(context.getString(R.string.app_name)).setContentText(context.getString(R.string.notification_new_wallpaper)).setContentIntent(PendingIntent.getActivity(context, 0, Intent.makeMainActivity(new ComponentName(context, MuzeiActivity.class)), PendingIntent.FLAG_UPDATE_CURRENT)).setDeleteIntent(PendingIntent.getBroadcast(context, 0, new Intent(context, NewWallpaperNotificationReceiver.class).setAction(ACTION_MARK_NOTIFICATION_READ), PendingIntent.FLAG_UPDATE_CURRENT));
    nb.setPublicVersion(publicBuilder.build());
    NotificationManagerCompat nm = NotificationManagerCompat.from(context);
    nm.notify(NOTIFICATION_ID, nb.build());
    artwork.close();
}
Also used : FileNotFoundException(java.io.FileNotFoundException) NotificationManagerCompat(android.support.v4.app.NotificationManagerCompat) URISyntaxException(java.net.URISyntaxException) Cursor(android.database.Cursor) ContentResolver(android.content.ContentResolver) Bitmap(android.graphics.Bitmap) NotificationCompat(android.support.v4.app.NotificationCompat) ComponentName(android.content.ComponentName) BitmapFactory(android.graphics.BitmapFactory) RemoteInput(android.support.v4.app.RemoteInput) SharedPreferences(android.content.SharedPreferences) ArtDetailOpenedClosedEvent(com.google.android.apps.muzei.event.ArtDetailOpenedClosedEvent) Intent(android.content.Intent) PendingIntent(android.app.PendingIntent) UserCommand(com.google.android.apps.muzei.api.UserCommand) PendingIntent(android.app.PendingIntent)

Example 5 with UserCommand

use of com.google.android.apps.muzei.api.UserCommand in project muzei by romannurik.

the class FeaturedArtSource method onUpdate.

@Override
protected void onUpdate(@UpdateReason int reason) {
    List<UserCommand> commands = new ArrayList<>();
    if (reason == UPDATE_REASON_INITIAL) {
        // Show initial photo (starry night)
        publishArtwork(new Artwork.Builder().imageUri(Uri.parse("file:///android_asset/starrynight.jpg")).title("The Starry Night").token("initial").byline("Vincent van Gogh, 1889.\nMuzei shows a new painting every day.").attribution("wikiart.org").viewIntent(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.wikiart.org/en/vincent-van-gogh/the-starry-night-1889"))).metaFont(MuzeiContract.Artwork.META_FONT_TYPE_ELEGANT).build());
        commands.add(new UserCommand(BUILTIN_COMMAND_ID_NEXT_ARTWORK));
        // show the latest photo in 15 minutes
        scheduleUpdate(System.currentTimeMillis() + 15 * 60 * 1000);
    } else {
        // For everything but the initial update, defer to RemoteMuzeiArtSource
        super.onUpdate(reason);
    }
    commands.add(new UserCommand(COMMAND_ID_SHARE, getString(R.string.featuredart_action_share_artwork)));
    commands.add(new UserCommand(COMMAND_ID_VIEW_ARCHIVE, getString(R.string.featuredart_source_action_view_archive)));
    if (BuildConfig.DEBUG) {
        commands.add(new UserCommand(COMMAND_ID_DEBUG_INFO, "Debug info"));
    }
    setUserCommands(commands);
}
Also used : UserCommand(com.google.android.apps.muzei.api.UserCommand) Artwork(com.google.android.apps.muzei.api.Artwork) ArrayList(java.util.ArrayList) CustomTabsIntent(android.support.customtabs.CustomTabsIntent) Intent(android.content.Intent)

Aggregations

UserCommand (com.google.android.apps.muzei.api.UserCommand)6 ComponentName (android.content.ComponentName)3 Cursor (android.database.Cursor)3 JSONArray (org.json.JSONArray)3 ContentResolver (android.content.ContentResolver)2 ContentValues (android.content.ContentValues)2 Intent (android.content.Intent)2 SharedPreferences (android.content.SharedPreferences)2 Bundle (android.os.Bundle)2 Artwork (com.google.android.apps.muzei.api.Artwork)2 ArrayList (java.util.ArrayList)2 JSONObject (org.json.JSONObject)2 PendingIntent (android.app.PendingIntent)1 ContentProviderOperation (android.content.ContentProviderOperation)1 OperationApplicationException (android.content.OperationApplicationException)1 PackageManager (android.content.pm.PackageManager)1 Bitmap (android.graphics.Bitmap)1 BitmapFactory (android.graphics.BitmapFactory)1 Uri (android.net.Uri)1 RemoteException (android.os.RemoteException)1