diff --git a/tools/EditTextVariations/AndroidManifest.xml b/tools/EditTextVariations/AndroidManifest.xml index 7694f4db9..96c244b32 100644 --- a/tools/EditTextVariations/AndroidManifest.xml +++ b/tools/EditTextVariations/AndroidManifest.xml @@ -40,5 +40,8 @@ + diff --git a/tools/EditTextVariations/res/values/strings.xml b/tools/EditTextVariations/res/values/strings.xml index 02387f2ff..cb896e8b6 100644 --- a/tools/EditTextVariations/res/values/strings.xml +++ b/tools/EditTextVariations/res/values/strings.xml @@ -33,6 +33,8 @@ Keyboard Visible Keyboard Hidden + + Direct Reply Custom diff --git a/tools/EditTextVariations/src/com/android/inputmethod/tools/edittextvariations/EditTextVariations.java b/tools/EditTextVariations/src/com/android/inputmethod/tools/edittextvariations/EditTextVariations.java index 44e0a4d55..6eb85a528 100644 --- a/tools/EditTextVariations/src/com/android/inputmethod/tools/edittextvariations/EditTextVariations.java +++ b/tools/EditTextVariations/src/com/android/inputmethod/tools/edittextvariations/EditTextVariations.java @@ -60,6 +60,7 @@ public final class EditTextVariations extends Activity implements TextView.OnEdi private static final int MENU_NAVIGATE_OFF = 3; private static final int MENU_SOFTINPUT_VISIBLE = 4; private static final int MENU_SOFTINPUT_HIDDEN = 5; + private static final int MENU_DIRECT_REPLY = 6; private static final String PREF_THEME = "theme"; private static final String PREF_NAVIGATE = "navigate"; private static final String PREF_SOFTINPUT = "softinput"; @@ -162,9 +163,12 @@ public final class EditTextVariations extends Activity implements TextView.OnEdi menu.add(Menu.NONE, MENU_SOFTINPUT_VISIBLE, 2, getString(R.string.menu_softinput_visible)); menu.add(Menu.NONE, MENU_SOFTINPUT_HIDDEN, 3, getString(R.string.menu_softinput_hidden)); menu.add(Menu.NONE, MENU_CHANGE_THEME, 4, R.string.menu_change_theme); + if (NotificationUtils.DIRECT_REPLY_SUPPORTED) { + menu.add(Menu.NONE, MENU_DIRECT_REPLY, 5, R.string.menu_direct_reply); + } try { final PackageInfo pinfo = getPackageManager().getPackageInfo(getPackageName(), 0); - menu.add(Menu.NONE, MENU_VERSION, 5, + menu.add(Menu.NONE, MENU_VERSION, 6, getString(R.string.menu_version, pinfo.versionName)) .setEnabled(false); } catch (NameNotFoundException e) { @@ -194,6 +198,8 @@ public final class EditTextVariations extends Activity implements TextView.OnEdi } else if (itemId == MENU_SOFTINPUT_VISIBLE || itemId == MENU_SOFTINPUT_HIDDEN) { saveSoftInputMode(itemId == MENU_SOFTINPUT_VISIBLE); restartActivity(); + } else if (itemId == MENU_DIRECT_REPLY) { + NotificationUtils.sendDirectReplyNotification(this); } return true; } diff --git a/tools/EditTextVariations/src/com/android/inputmethod/tools/edittextvariations/NotificationBroadcastReceiver.java b/tools/EditTextVariations/src/com/android/inputmethod/tools/edittextvariations/NotificationBroadcastReceiver.java new file mode 100644 index 000000000..97db49b15 --- /dev/null +++ b/tools/EditTextVariations/src/com/android/inputmethod/tools/edittextvariations/NotificationBroadcastReceiver.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.tools.edittextvariations; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +/** + * A non-exported {@link BroadcastReceiver} to receive {@link Intent} from notifications. + */ +public final class NotificationBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + NotificationUtils.onReceiveDirectReply(context, intent); + } +} diff --git a/tools/EditTextVariations/src/com/android/inputmethod/tools/edittextvariations/NotificationUtils.java b/tools/EditTextVariations/src/com/android/inputmethod/tools/edittextvariations/NotificationUtils.java new file mode 100644 index 000000000..64480bbf0 --- /dev/null +++ b/tools/EditTextVariations/src/com/android/inputmethod/tools/edittextvariations/NotificationUtils.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.tools.edittextvariations; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.RemoteInput; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.os.Process; +import android.os.UserHandle; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +final class NotificationUtils { + private static final String REPLY_ACTION = "REPLY_ACTION"; + private static final String KEY_REPLY = "KEY_REPLY"; + private static final String KEY_NOTIFICATION_ID = "KEY_NOTIFICATION_ID"; + private static final String CHANNEL_NAME = "Channel Name"; + private static final String CHANNEL_DESCRIPTION = "Channel Description"; + private static final String CHANNEL_ID = "Channel ID"; + private static final AtomicBoolean sNotificationChannelInitialized = new AtomicBoolean(); + private static final AtomicInteger sNextNotificationId = new AtomicInteger(1); + + static final boolean DIRECT_REPLY_SUPPORTED = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N; + + static void ensureNotificationChannel(Context context) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + // NotificationChannel is not implemented. No need to set up notification channel. + return; + } + if (!sNotificationChannelInitialized.compareAndSet(false, true)) { + // Already initialized. + return; + } + + // Create the NotificationChannel + final NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, + NotificationManager.IMPORTANCE_DEFAULT); + channel.setDescription(CHANNEL_DESCRIPTION); + // Register the channel with the system; you can't change the importance + // or other notification behaviors after this + context.getSystemService(NotificationManager.class).createNotificationChannel(channel); + } + + static void sendDirectReplyNotification(Context context) { + if (!DIRECT_REPLY_SUPPORTED) { + // DirectReply is not supported. + return; + } + + ensureNotificationChannel(context); + + RemoteInput remoteInput = new RemoteInput.Builder(KEY_REPLY) + .setLabel("Reply Label") + .build(); + + final int notificationId = sNextNotificationId.getAndIncrement(); + final PendingIntent pendingIntent = getReplyPendingIntent(context, notificationId); + final Notification.Action action = + new Notification.Action.Builder(null, "Direct Reply Test", pendingIntent) + .addRemoteInput(remoteInput) + .build(); + final Notification notification = new Notification.Builder(context, CHANNEL_ID) + .setContentText("Content Title") + .setSmallIcon(R.drawable.ic_launcher) + .setContentText("Message from " + UserHandle.getUserHandleForUid(Process.myUid())) + .setShowWhen(true) + .addAction(action) + .build(); + context.getSystemService(NotificationManager.class).notify(notificationId, notification); + } + + static void onReceiveDirectReply(Context context, Intent intent) { + final Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); + if (remoteInput == null) { + return; + } + final CharSequence reply = remoteInput.getCharSequence(KEY_REPLY); + final int notificationId = intent.getIntExtra(KEY_NOTIFICATION_ID, 0); + final Notification.Builder notificationBuilder = + new Notification.Builder(context, CHANNEL_ID); + notificationBuilder.setContentText("Content Title") + .setSmallIcon(R.drawable.ic_launcher) + .setContentText(String.format("Sent \"%s\" to %s", reply, + UserHandle.getUserHandleForUid(Process.myUid()))); + context.getSystemService(NotificationManager.class) + .notify(notificationId, notificationBuilder.build()); + } + + private static PendingIntent getReplyPendingIntent(Context context, int notificationId) { + final Intent intent = new Intent(context, NotificationBroadcastReceiver.class); + intent.setAction(REPLY_ACTION); + intent.putExtra(KEY_NOTIFICATION_ID, notificationId); + // Pass notificationId as the result code to get a new PendingIntent rather than an existing + // one. + return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, intent, + PendingIntent.FLAG_ONE_SHOT); + } +}