|
@@ -45,13 +45,25 @@ import com.owncloud.android.datamodel.SignatureVerification;
|
|
|
import com.owncloud.android.lib.common.OwnCloudAccount;
|
|
|
import com.owncloud.android.lib.common.OwnCloudClient;
|
|
|
import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
|
|
|
+import com.owncloud.android.lib.common.operations.RemoteOperation;
|
|
|
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
|
|
|
import com.owncloud.android.lib.common.utils.Log_OC;
|
|
|
import com.owncloud.android.lib.resources.notifications.DeleteNotificationRemoteOperation;
|
|
|
+import com.owncloud.android.lib.resources.notifications.GetNotificationRemoteOperation;
|
|
|
+import com.owncloud.android.lib.resources.notifications.models.Action;
|
|
|
+import com.owncloud.android.lib.resources.notifications.models.Notification;
|
|
|
import com.owncloud.android.ui.activity.NotificationsActivity;
|
|
|
import com.owncloud.android.ui.notifications.NotificationUtils;
|
|
|
import com.owncloud.android.utils.PushUtils;
|
|
|
import com.owncloud.android.utils.ThemeUtils;
|
|
|
|
|
|
+import org.apache.commons.httpclient.HttpMethod;
|
|
|
+import org.apache.commons.httpclient.HttpStatus;
|
|
|
+import org.apache.commons.httpclient.methods.DeleteMethod;
|
|
|
+import org.apache.commons.httpclient.methods.GetMethod;
|
|
|
+import org.apache.commons.httpclient.methods.PostMethod;
|
|
|
+import org.apache.commons.httpclient.methods.PutMethod;
|
|
|
+
|
|
|
import java.io.IOException;
|
|
|
import java.security.InvalidKeyException;
|
|
|
import java.security.NoSuchAlgorithmException;
|
|
@@ -64,23 +76,26 @@ import javax.crypto.NoSuchPaddingException;
|
|
|
import androidx.annotation.NonNull;
|
|
|
import androidx.core.app.NotificationCompat;
|
|
|
import androidx.core.app.NotificationManagerCompat;
|
|
|
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
|
|
|
|
|
public class NotificationJob extends Job {
|
|
|
public static final String TAG = "NotificationJob";
|
|
|
|
|
|
+ public static final String KEY_NOTIFICATION_ACCOUNT = "KEY_NOTIFICATION_ACCOUNT";
|
|
|
public static final String KEY_NOTIFICATION_SUBJECT = "subject";
|
|
|
public static final String KEY_NOTIFICATION_SIGNATURE = "signature";
|
|
|
- public static final String KEY_NOTIFICATION_ACCOUNT = "KEY_NOTIFICATION_ACCOUNT";
|
|
|
+ private static final String KEY_NOTIFICATION_ACTION_LINK = "KEY_NOTIFICATION_ACTION_LINK";
|
|
|
+ private static final String KEY_NOTIFICATION_ACTION_TYPE = "KEY_NOTIFICATION_ACTION_TYPE";
|
|
|
private static final String PUSH_NOTIFICATION_ID = "PUSH_NOTIFICATION_ID";
|
|
|
private static final String NUMERIC_NOTIFICATION_ID = "NUMERIC_NOTIFICATION_ID";
|
|
|
|
|
|
private SecureRandom randomId = new SecureRandom();
|
|
|
+ private Context context;
|
|
|
|
|
|
@NonNull
|
|
|
@Override
|
|
|
protected Result onRunJob(@NonNull Params params) {
|
|
|
-
|
|
|
- Context context = getContext();
|
|
|
+ context = getContext();
|
|
|
PersistableBundleCompat persistableBundleCompat = getParams().getExtras();
|
|
|
String subject = persistableBundleCompat.getString(KEY_NOTIFICATION_SUBJECT, "");
|
|
|
String signature = persistableBundleCompat.getString(KEY_NOTIFICATION_SIGNATURE, "");
|
|
@@ -96,7 +111,7 @@ public class NotificationJob extends Job {
|
|
|
base64DecodedSignature,
|
|
|
base64DecodedSubject);
|
|
|
|
|
|
- if (signatureVerification.isSignatureValid()) {
|
|
|
+ if (signatureVerification != null && signatureVerification.isSignatureValid()) {
|
|
|
Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding");
|
|
|
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
|
|
byte[] decryptedSubject = cipher.doFinal(base64DecodedSubject);
|
|
@@ -107,7 +122,7 @@ public class NotificationJob extends Job {
|
|
|
|
|
|
// We ignore Spreed messages for now
|
|
|
if (!"spreed".equals(decryptedPushMessage.getApp())) {
|
|
|
- sendNotification(decryptedPushMessage, signatureVerification.getAccount());
|
|
|
+ fetchCompleteNotification(signatureVerification.getAccount(), decryptedPushMessage);
|
|
|
}
|
|
|
}
|
|
|
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e1) {
|
|
@@ -121,9 +136,8 @@ public class NotificationJob extends Job {
|
|
|
return Result.SUCCESS;
|
|
|
}
|
|
|
|
|
|
- private void sendNotification(DecryptedPushMessage pushMessage, Account account) {
|
|
|
- Context context = getContext();
|
|
|
- Intent intent = new Intent(getContext(), NotificationsActivity.class);
|
|
|
+ private void sendNotification(Notification notification, Account account) {
|
|
|
+ Intent intent = new Intent(context, NotificationsActivity.class);
|
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
|
intent.putExtra(KEY_NOTIFICATION_ACCOUNT, account.name);
|
|
|
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT);
|
|
@@ -136,26 +150,95 @@ public class NotificationJob extends Job {
|
|
|
.setColor(ThemeUtils.primaryColor(account, false, context))
|
|
|
.setShowWhen(true)
|
|
|
.setSubText(account.name)
|
|
|
- .setContentTitle(pushMessage.getSubject())
|
|
|
+ .setContentTitle(notification.getSubject())
|
|
|
+ .setContentText(notification.getMessage())
|
|
|
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
|
|
|
.setAutoCancel(true)
|
|
|
+ .setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
|
|
|
.setContentIntent(pendingIntent);
|
|
|
|
|
|
- Intent disableDetection = new Intent(context, NotificationJob.NotificationReceiver.class);
|
|
|
- disableDetection.putExtra(NUMERIC_NOTIFICATION_ID, pushMessage.getNid());
|
|
|
- disableDetection.putExtra(PUSH_NOTIFICATION_ID, pushNotificationId);
|
|
|
- disableDetection.putExtra(KEY_NOTIFICATION_ACCOUNT, account.name);
|
|
|
+ // Remove
|
|
|
+ if (notification.getActions().isEmpty()) {
|
|
|
+ Intent disableDetection = new Intent(context, NotificationJob.NotificationReceiver.class);
|
|
|
+ disableDetection.putExtra(NUMERIC_NOTIFICATION_ID, notification.getNotificationId());
|
|
|
+ disableDetection.putExtra(PUSH_NOTIFICATION_ID, pushNotificationId);
|
|
|
+ disableDetection.putExtra(KEY_NOTIFICATION_ACCOUNT, account.name);
|
|
|
+
|
|
|
+ PendingIntent disableIntent = PendingIntent.getBroadcast(context, pushNotificationId, disableDetection,
|
|
|
+ PendingIntent.FLAG_CANCEL_CURRENT);
|
|
|
+
|
|
|
+ notificationBuilder.addAction(new NotificationCompat.Action(R.drawable.ic_close,
|
|
|
+ context.getString(R.string.remove_push_notification), disableIntent));
|
|
|
+ } else {
|
|
|
+ // Actions
|
|
|
+ for (Action action : notification.getActions()) {
|
|
|
+ Intent actionIntent = new Intent(context, NotificationJob.NotificationReceiver.class);
|
|
|
+ actionIntent.putExtra(NUMERIC_NOTIFICATION_ID, notification.getNotificationId());
|
|
|
+ actionIntent.putExtra(PUSH_NOTIFICATION_ID, pushNotificationId);
|
|
|
+ actionIntent.putExtra(KEY_NOTIFICATION_ACCOUNT, account.name);
|
|
|
+ actionIntent.putExtra(KEY_NOTIFICATION_ACTION_LINK, action.link);
|
|
|
+ actionIntent.putExtra(KEY_NOTIFICATION_ACTION_TYPE, action.type);
|
|
|
+
|
|
|
+
|
|
|
+ PendingIntent actionPendingIntent = PendingIntent.getBroadcast(context, randomId.nextInt(),
|
|
|
+ actionIntent,
|
|
|
+ PendingIntent.FLAG_CANCEL_CURRENT);
|
|
|
+
|
|
|
+ int icon;
|
|
|
+ if (action.primary) {
|
|
|
+ icon = R.drawable.ic_check_circle;
|
|
|
+ } else {
|
|
|
+ icon = R.drawable.ic_check_circle_outline;
|
|
|
+ }
|
|
|
|
|
|
- PendingIntent disableIntent = PendingIntent.getBroadcast(context, pushNotificationId, disableDetection,
|
|
|
- PendingIntent.FLAG_CANCEL_CURRENT);
|
|
|
+ notificationBuilder.addAction(new NotificationCompat.Action(icon, action.label, actionPendingIntent));
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- notificationBuilder.addAction(new NotificationCompat.Action(R.drawable.ic_close,
|
|
|
- context.getString(R.string.remove_push_notification), disableIntent));
|
|
|
+ notificationBuilder.setPublicVersion(
|
|
|
+ new NotificationCompat.Builder(context, NotificationUtils.NOTIFICATION_CHANNEL_PUSH)
|
|
|
+ .setSmallIcon(R.drawable.notification_icon)
|
|
|
+ .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.notification_icon))
|
|
|
+ .setColor(ThemeUtils.primaryColor(account, false, context))
|
|
|
+ .setShowWhen(true)
|
|
|
+ .setSubText(account.name)
|
|
|
+ .setContentTitle(context.getString(R.string.new_notification))
|
|
|
+ .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
|
|
|
+ .setAutoCancel(true)
|
|
|
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
|
|
+ .setContentIntent(pendingIntent).build());
|
|
|
|
|
|
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
|
|
|
notificationManager.notify(pushNotificationId, notificationBuilder.build());
|
|
|
}
|
|
|
|
|
|
+ private void fetchCompleteNotification(Account account, DecryptedPushMessage decryptedPushMessage) {
|
|
|
+ Account currentAccount = AccountUtils.getOwnCloudAccountByName(context, account.name);
|
|
|
+
|
|
|
+ if (currentAccount == null) {
|
|
|
+ Log_OC.e(this, "Account may not be null");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ OwnCloudAccount ocAccount = new OwnCloudAccount(currentAccount, context);
|
|
|
+ OwnCloudClient client = OwnCloudClientManagerFactory.getDefaultSingleton()
|
|
|
+ .getClientFor(ocAccount, context);
|
|
|
+ client.setOwnCloudVersion(AccountUtils.getServerVersion(currentAccount));
|
|
|
+
|
|
|
+ RemoteOperationResult result = new GetNotificationRemoteOperation(decryptedPushMessage.nid)
|
|
|
+ .execute(client);
|
|
|
+
|
|
|
+ if (result.isSuccess()) {
|
|
|
+ Notification notification = result.getNotificationData().get(0);
|
|
|
+ sendNotification(notification, account);
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ Log_OC.e(this, "Error creating account", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
public static class NotificationReceiver extends BroadcastReceiver {
|
|
|
|
|
|
@Override
|
|
@@ -179,10 +262,20 @@ public class NotificationJob extends Job {
|
|
|
.getClientFor(ocAccount, context);
|
|
|
client.setOwnCloudVersion(AccountUtils.getServerVersion(currentAccount));
|
|
|
|
|
|
- new DeleteNotificationRemoteOperation(numericNotificationId).execute(client);
|
|
|
+ String actionType = intent.getStringExtra(KEY_NOTIFICATION_ACTION_TYPE);
|
|
|
+ String actionLink = intent.getStringExtra(KEY_NOTIFICATION_ACTION_LINK);
|
|
|
|
|
|
- cancel(context, pushNotificationId);
|
|
|
+ boolean success;
|
|
|
+ if (!TextUtils.isEmpty(actionType) && !TextUtils.isEmpty(actionLink)) {
|
|
|
+ success = executeAction(actionType, actionLink, client) == HttpStatus.SC_OK;
|
|
|
+ } else {
|
|
|
+ success = new DeleteNotificationRemoteOperation(numericNotificationId)
|
|
|
+ .execute(client).isSuccess();
|
|
|
+ }
|
|
|
|
|
|
+ if (success) {
|
|
|
+ cancel(context, pushNotificationId);
|
|
|
+ }
|
|
|
} catch (com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException |
|
|
|
IOException | OperationCanceledException | AuthenticatorException e) {
|
|
|
Log_OC.e(TAG, "Error initializing client", e);
|
|
@@ -191,6 +284,43 @@ public class NotificationJob extends Job {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ @SuppressFBWarnings(value = "HTTP_PARAMETER_POLLUTION",
|
|
|
+ justification = "link and type are from server and expected to be safe")
|
|
|
+ private int executeAction(String actionType, String actionLink, OwnCloudClient client) {
|
|
|
+ HttpMethod method;
|
|
|
+
|
|
|
+ switch (actionType) {
|
|
|
+ case "GET":
|
|
|
+ method = new GetMethod(actionLink);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case "POST":
|
|
|
+ method = new PostMethod(actionLink);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case "DELETE":
|
|
|
+ method = new DeleteMethod(actionLink);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case "PUT":
|
|
|
+ method = new PutMethod(actionLink);
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ // do nothing
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ method.setRequestHeader(RemoteOperation.OCS_API_HEADER, RemoteOperation.OCS_API_HEADER_VALUE);
|
|
|
+
|
|
|
+ try {
|
|
|
+ return client.executeMethod(method);
|
|
|
+ } catch (IOException e) {
|
|
|
+ Log_OC.e(TAG, "Execution of notification action failed: " + e);
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
private void cancel(Context context, int notificationId) {
|
|
|
NotificationManager notificationManager = (NotificationManager) context.getSystemService(
|
|
|
Activity.NOTIFICATION_SERVICE);
|