diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 97593ffd45ea2..e5fc104ddb2d3 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3681,6 +3681,7 @@ + diff --git a/packages/ExtServices/Android.mk b/packages/ExtServices/Android.mk index 1133499c87df7..a06dd86fe1886 100644 --- a/packages/ExtServices/Android.mk +++ b/packages/ExtServices/Android.mk @@ -25,10 +25,6 @@ LOCAL_PRIVATE_PLATFORM_APIS := true LOCAL_CERTIFICATE := platform -LOCAL_AAPT_FLAGS := --shared-lib - -LOCAL_EXPORT_PACKAGE_RESOURCES := true - LOCAL_PROGUARD_FLAG_FILES := proguard.proguard LOCAL_PRIVILEGED_MODULE := true diff --git a/packages/ExtServices/AndroidManifest.xml b/packages/ExtServices/AndroidManifest.xml index e47d53ca7692d..76e2fe7f39569 100644 --- a/packages/ExtServices/AndroidManifest.xml +++ b/packages/ExtServices/AndroidManifest.xml @@ -78,6 +78,10 @@ + + diff --git a/packages/ExtServices/res/values/strings.xml b/packages/ExtServices/res/values/strings.xml index a9a5450c536e8..de3544174c6bb 100644 --- a/packages/ExtServices/res/values/strings.xml +++ b/packages/ExtServices/res/values/strings.xml @@ -24,4 +24,10 @@ EDIT_DISTANCE EXACT_MATCH + + + Copy \u201c%1$s\u201c + + Code copied + diff --git a/packages/ExtServices/src/android/ext/services/notification/CopyCodeActivity.java b/packages/ExtServices/src/android/ext/services/notification/CopyCodeActivity.java new file mode 100644 index 0000000000000..6708d4d695ec4 --- /dev/null +++ b/packages/ExtServices/src/android/ext/services/notification/CopyCodeActivity.java @@ -0,0 +1,54 @@ +/* + * 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 android.ext.services.notification; + +import android.app.Activity; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Intent; +import android.ext.services.R; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; +import android.widget.Toast; + +/** + * An activity that copies text in the Bundle. + */ +public class CopyCodeActivity extends Activity { + private static final String TAG = "CopyCodeActivity"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + handleIntent(); + finish(); + } + + private void handleIntent() { + String code = getIntent().getStringExtra(Intent.EXTRA_TEXT); + if (TextUtils.isEmpty(code)) { + Log.w(TAG, "handleIntent: empty code"); + return; + } + ClipboardManager clipboardManager = getSystemService(ClipboardManager.class); + ClipData clipData = ClipData.newPlainText(null, code); + clipboardManager.setPrimaryClip(clipData); + Toast.makeText(getApplicationContext(), R.string.code_copied_to_clipboard, + Toast.LENGTH_SHORT).show(); + } +} diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java index 24fa87a7f39ba..bf8a3fad1d0fe 100644 --- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java +++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java @@ -15,11 +15,15 @@ */ package android.ext.services.notification; +import android.annotation.Nullable; import android.app.Notification; +import android.app.PendingIntent; import android.app.Person; import android.app.RemoteAction; import android.app.RemoteInput; import android.content.Context; +import android.content.Intent; +import android.ext.services.R; import android.graphics.drawable.Icon; import android.os.Bundle; import android.os.Parcelable; @@ -48,11 +52,12 @@ import java.util.Deque; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.stream.Collectors; public class SmartActionsHelper { - private static final String KEY_ACTION_TYPE = "action_type"; - private static final String KEY_ACTION_SCORE = "action_score"; + static final String ENTITIES_EXTRAS = "entities-extras"; + static final String KEY_ACTION_TYPE = "action_type"; + static final String KEY_ACTION_SCORE = "action_score"; + static final String KEY_TEXT = "text"; // If a notification has any of these flags set, it's inelgibile for actions being added. private static final int FLAG_MASK_INELGIBILE_FOR_ACTIONS = Notification.FLAG_ONGOING_EVENT @@ -109,11 +114,25 @@ public class SmartActionsHelper { repliesScore.put(textReply, conversationAction.getConfidenceScore()); } - ArrayList actions = conversationActions.stream() - .filter(conversationAction -> conversationAction.getAction() != null) - .map(action -> createNotificationAction( - action.getAction(), action.getType(), action.getConfidenceScore())) - .collect(Collectors.toCollection(ArrayList::new)); + ArrayList actions = new ArrayList<>(); + for (ConversationAction conversationAction : conversationActions) { + if (!TextUtils.isEmpty(conversationAction.getTextReply())) { + continue; + } + Notification.Action notificationAction; + if (conversationAction.getAction() == null) { + notificationAction = + createNotificationActionWithoutRemoteAction(conversationAction); + } else { + notificationAction = createNotificationActionFromRemoteAction( + conversationAction.getAction(), + conversationAction.getType(), + conversationAction.getConfidenceScore()); + } + if (notificationAction != null) { + actions.add(notificationAction); + } + } // Start a new session for logging if necessary. if (!TextUtils.isEmpty(resultId) @@ -126,6 +145,55 @@ public class SmartActionsHelper { return new SmartSuggestions(replies, actions); } + /** + * Creates notification action from ConversationAction that does not come up a RemoteAction. + * It could happen because we don't have common intents for some actions, like copying text. + */ + @Nullable + private Notification.Action createNotificationActionWithoutRemoteAction( + ConversationAction conversationAction) { + if (ConversationAction.TYPE_COPY.equals(conversationAction.getType())) { + return createCopyCodeAction(conversationAction); + } + return null; + } + + @Nullable + private Notification.Action createCopyCodeAction(ConversationAction conversationAction) { + Bundle extras = conversationAction.getExtras(); + if (extras == null) { + return null; + } + Bundle entitiesExtas = extras.getParcelable(ENTITIES_EXTRAS); + if (entitiesExtas == null) { + return null; + } + String code = entitiesExtas.getString(KEY_TEXT); + if (TextUtils.isEmpty(code)) { + return null; + } + String contentDescription = mContext.getString(R.string.copy_code_desc, code); + Intent intent = new Intent(mContext, CopyCodeActivity.class); + intent.putExtra(Intent.EXTRA_TEXT, code); + + RemoteAction remoteAction = new RemoteAction(Icon.createWithResource( + mContext.getResources(), + com.android.internal.R.drawable.ic_menu_copy_material), + code, + contentDescription, + PendingIntent.getActivity( + mContext, + code.hashCode(), + intent, + PendingIntent.FLAG_UPDATE_CURRENT + )); + + return createNotificationActionFromRemoteAction( + remoteAction, + ConversationAction.TYPE_COPY, + conversationAction.getConfidenceScore()); + } + /** * Returns whether the suggestion might be used in the notifications in SysUI. *

@@ -292,7 +360,7 @@ public class SmartActionsHelper { mTextClassifier.onTextClassifierEvent(textClassifierEvent); } - private Notification.Action createNotificationAction( + private Notification.Action createNotificationActionFromRemoteAction( RemoteAction remoteAction, String actionType, float score) { Icon icon = remoteAction.shouldShowIcon() ? remoteAction.getIcon() diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionsHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionsHelperTest.java index a1fbc7bef1342..3a7d799ea2ebb 100644 --- a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionsHelperTest.java +++ b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionsHelperTest.java @@ -36,6 +36,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.IPackageManager; import android.graphics.drawable.Icon; +import android.os.Bundle; import android.os.Process; import android.service.notification.NotificationAssistantService; import android.service.notification.StatusBarNotification; @@ -419,6 +420,31 @@ public class SmartActionsHelperTest { assertTextClassifierEvent(events.get(1), TextClassifierEvent.TYPE_ACTIONS_SHOWN); } + @Test + public void testCopyAction() { + Bundle extras = new Bundle(); + Bundle entitiesExtras = new Bundle(); + entitiesExtras.putString(SmartActionsHelper.KEY_TEXT, "12345"); + extras.putParcelable(SmartActionsHelper.ENTITIES_EXTRAS, entitiesExtras); + ConversationAction conversationAction = + new ConversationAction.Builder(ConversationAction.TYPE_COPY) + .setExtras(extras) + .build(); + when(mTextClassifier.suggestConversationActions(any(ConversationActions.Request.class))) + .thenReturn( + new ConversationActions( + Collections.singletonList(conversationAction), null)); + + Notification notification = createMessageNotification(); + when(mStatusBarNotification.getNotification()).thenReturn(notification); + SmartActionsHelper.SmartSuggestions suggestions = + mSmartActionsHelper.suggest(createNotificationEntry()); + + assertThat(suggestions.actions).hasSize(1); + Notification.Action action = suggestions.actions.get(0); + assertThat(action.title).isEqualTo("12345"); + } + private ZonedDateTime createZonedDateTimeFromMsUtc(long msUtc) { return ZonedDateTime.ofInstant(Instant.ofEpochMilli(msUtc), ZoneOffset.systemDefault()); }