/*
* Copyright (C) 2014 AChep@xda <artemchep@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package com.achep.acdisplay.notifications;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.PendingIntent;
import android.os.Build;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.RemoteInput;
import android.util.Log;
import com.achep.base.Device;
import java.lang.ref.SoftReference;
import java.lang.reflect.Field;
/**
* Structure to encapsulate a named action that can be shown as part of this notification.
* It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is
* selected by the user.
* <p>
* This is actually a wrapper around {@link android.app.Notification.Action} class that supports both
* Jelly Bean (via reflections) and KitKat Android versions.
*
* @author Artem Chepurnoy
*/
public class Action {
private static final String TAG = "Action";
@NonNull
private static SoftReference<Factory> sFactoryRef = new SoftReference<>(null);
@NonNull
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private static Factory getFactory() {
Factory factory = sFactoryRef.get();
if (factory == null) {
if (Device.hasKitKatWatchApi()) {
factory = new FactoryKitKatWatch();
} else {
factory = Device.hasKitKatApi()
? new FactoryKitKat()
: new FactoryJellyBean();
}
sFactoryRef = new SoftReference<>(factory);
return factory;
}
return factory;
}
/**
* Creates a list of actions based on given {@link android.app.Notification notification}
* instance.
*
* @param notification the notification to create from
* @return array of {@link com.achep.acdisplay.notifications.Action actions} or {@code null}
*/
@Nullable
static Action[] makeFor(@NonNull Notification notification) {
return getFactory().makeFor(notification);
}
/**
* Small icon representing the action.
*/
@DrawableRes
public final int icon;
/**
* Title of the action.
*/
@NonNull
public final CharSequence title;
/**
* Intent to send when the user invokes this action. May be null, in which case the action
* may be rendered in a disabled presentation by the system UI.
*/
@Nullable
public final PendingIntent intent;
/**
* The list of inputs to be collected from the user when this action is sent.
* May be null if no remote inputs were added.
*/
@Nullable
public final RemoteInput[] remoteInputs;
private Action(@DrawableRes int icon, @NonNull CharSequence title,
@Nullable PendingIntent intent, @Nullable RemoteInput[] remoteInputs) {
this.icon = icon;
this.title = title;
this.intent = intent;
this.remoteInputs = remoteInputs;
}
/**
* Base definition of {@link com.achep.acdisplay.notifications.Action} creator.
*
* @author Artem Chepurnoy
*/
public static abstract class Factory {
/**
* Creates a list of actions based on given {@link android.app.Notification notification}
* instance.
*
* @param notification notification to create from
* @return array of {@link com.achep.acdisplay.notifications.Action actions} or {@code null}
*/
@Nullable
public abstract Action[] makeFor(@NonNull Notification notification);
}
@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
private static class FactoryKitKatWatch extends FactoryKitKat {
/**
* {@inheritDoc}
*/
@Nullable
public RemoteInput[] getRemoteInputs(@NonNull Notification.Action action) {
return RemoteInputUtils.toCompat(action.getRemoteInputs());
}
}
@TargetApi(Build.VERSION_CODES.KITKAT)
private static class FactoryKitKat extends Factory {
/**
* {@inheritDoc}
*/
@Nullable
@Override
public Action[] makeFor(@NonNull Notification notification) {
Notification.Action[] src = notification.actions;
if (src == null) {
return null;
}
final int length = src.length;
final Action[] dst = new Action[src.length];
for (int i = 0; i < length; i++) {
RemoteInput[] remoteInputs = getRemoteInputs(src[i]);
dst[i] = new Action(src[i].icon, src[i].title, src[i].actionIntent, remoteInputs);
}
return dst;
}
/**
* Get the list of inputs to be collected from the user when this action is sent.
* May return null if no remote inputs were added.
*/
@Nullable
public RemoteInput[] getRemoteInputs(@NonNull Notification.Action action) {
return null;
}
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private static class FactoryJellyBean extends Factory {
/**
* {@inheritDoc}
*/
@Nullable
@Override
public Action[] makeFor(@NonNull Notification notification) {
// Getting actions from stupid Jelly Bean.
Object[] src;
try {
Field field = Notification.class.getDeclaredField("actions");
field.setAccessible(true);
src = (Object[]) field.get(notification);
} catch (Exception e) {
Log.w(TAG, "Failed to access actions field!");
return null;
}
if (src == null) {
return null;
}
final int length = src.length;
final Action[] dst = new Action[src.length];
for (int i = 0; i < length; i++) {
Object object = src[i];
try {
Field field = object.getClass().getDeclaredField("icon");
field.setAccessible(true);
final int icon = (int) field.get(object);
field = object.getClass().getDeclaredField("title");
field.setAccessible(true);
final CharSequence title = (CharSequence) field.get(object);
field = object.getClass().getDeclaredField("actionIntent");
field.setAccessible(true);
final PendingIntent intent = (PendingIntent) field.get(object);
dst[i] = new Action(icon, title, intent, null);
} catch (Exception e) {
Log.wtf(TAG, "Failed to access fields of the Action.");
return null;
}
}
return dst;
}
}
}