diff options
| author | 2018-04-05 21:18:38 +0100 | |
|---|---|---|
| committer | 2018-04-11 10:09:03 +0100 | |
| commit | 23991105bd893854820009bc71503b595ea2f2fa (patch) | |
| tree | 132088223470b476a585cb70fd90cdde5830789a | |
| parent | 8b0b733a2b2c9a4081e3352a0ddb7ad11c0e543b (diff) | |
Add logging for smart replies in notifications.
Log the first time a notification with smart
replies is visible.
Log each click on a smart reply.
Test: atest SystemUITests
Bug: 72153458
Change-Id: I6dc498871000dbb9af978567db3d258b20978781
11 files changed, 215 insertions, 15 deletions
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 24f2fbf7f132..2b7221a1eb55 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -64,6 +64,8 @@ interface IStatusBarService in NotificationVisibility[] noLongerVisibleKeys); void onNotificationExpansionChanged(in String key, in boolean userAction, in boolean expanded); void onNotificationDirectReplied(String key); + void onNotificationSmartRepliesAdded(in String key, in int replyCount); + void onNotificationSmartReplySent(in String key, in int replyIndex); void onNotificationSettingsViewed(String key); void setSystemUiVisibility(int vis, int mask, String cause); diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index c01cafaaa9b8..52d458cee987 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -42,6 +42,7 @@ import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.ScrimView; +import com.android.systemui.statusbar.SmartReplyLogger; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBouncer; @@ -146,5 +147,6 @@ public class SystemUIFactory { () -> new NotificationViewHierarchyManager(context)); providers.put(NotificationEntryManager.class, () -> new NotificationEntryManager(context)); providers.put(KeyguardDismissUtil.class, KeyguardDismissUtil::new); + providers.put(SmartReplyLogger.class, () -> new SmartReplyLogger(context)); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index 29c2edc22f4c..4256cd63dc4b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -80,7 +80,7 @@ public class NotificationContentView extends FrameLayout { private RemoteInputView mHeadsUpRemoteInput; private SmartReplyConstants mSmartReplyConstants; - private SmartReplyView mExpandedSmartReplyView; + private SmartReplyLogger mSmartReplyLogger; private NotificationViewWrapper mContractedWrapper; private NotificationViewWrapper mExpandedWrapper; @@ -153,6 +153,7 @@ public class NotificationContentView extends FrameLayout { super(context, attrs); mHybridGroupManager = new HybridGroupManager(getContext(), this); mSmartReplyConstants = Dependency.get(SmartReplyConstants.class); + mSmartReplyLogger = Dependency.get(SmartReplyLogger.class); initView(); } @@ -1243,7 +1244,7 @@ public class NotificationContentView extends FrameLayout { } applyRemoteInput(entry, hasRemoteInput); - applySmartReplyView(remoteInputWithChoices, pendingIntentWithChoices); + applySmartReplyView(remoteInputWithChoices, pendingIntentWithChoices, entry); } private void applyRemoteInput(NotificationData.Entry entry, boolean hasRemoteInput) { @@ -1344,13 +1345,21 @@ public class NotificationContentView extends FrameLayout { return null; } - private void applySmartReplyView(RemoteInput remoteInput, PendingIntent pendingIntent) { - mExpandedSmartReplyView = mExpandedChild == null ? - null : applySmartReplyView(mExpandedChild, remoteInput, pendingIntent); + private void applySmartReplyView(RemoteInput remoteInput, PendingIntent pendingIntent, + NotificationData.Entry entry) { + if (mExpandedChild != null) { + SmartReplyView view = + applySmartReplyView(mExpandedChild, remoteInput, pendingIntent, entry); + if (view != null && remoteInput != null && remoteInput.getChoices() != null + && remoteInput.getChoices().length > 0) { + mSmartReplyLogger.smartRepliesAdded(entry, remoteInput.getChoices().length); + } + } } private SmartReplyView applySmartReplyView( - View view, RemoteInput remoteInput, PendingIntent pendingIntent) { + View view, RemoteInput remoteInput, PendingIntent pendingIntent, + NotificationData.Entry entry) { View smartReplyContainerCandidate = view.findViewById( com.android.internal.R.id.smart_reply_container); if (!(smartReplyContainerCandidate instanceof LinearLayout)) { @@ -1372,7 +1381,8 @@ public class NotificationContentView extends FrameLayout { } } if (smartReplyView != null) { - smartReplyView.setRepliesFromRemoteInput(remoteInput, pendingIntent); + smartReplyView.setRepliesFromRemoteInput(remoteInput, pendingIntent, + mSmartReplyLogger, entry); smartReplyContainer.setVisibility(View.VISIBLE); } return smartReplyView; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyLogger.java new file mode 100644 index 000000000000..75dd77d8ec3a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyLogger.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018 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.systemui.statusbar; + +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; +import com.android.internal.statusbar.IStatusBarService; + +/** + * Handles reporting when smart replies are added to a notification + * and clicked upon. + */ +public class SmartReplyLogger { + protected IStatusBarService mBarService; + + public SmartReplyLogger(Context context) { + mBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + } + + public void smartReplySent(NotificationData.Entry entry, int replyIndex) { + try { + mBarService.onNotificationSmartReplySent(entry.notification.getKey(), + replyIndex); + } catch (RemoteException e) { + // Nothing to do, system going down + } + } + + public void smartRepliesAdded(final NotificationData.Entry entry, int replyCount) { + try { + mBarService.onNotificationSmartRepliesAdded(entry.notification.getKey(), + replyCount); + } catch (RemoteException e) { + // Nothing to do, system going down + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java index 74b39268fc2d..4c79ee32f8e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java @@ -23,6 +23,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardHostView.OnDismissAction; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.statusbar.NotificationData; +import com.android.systemui.statusbar.SmartReplyLogger; import com.android.systemui.statusbar.phone.KeyguardDismissUtil; import java.text.BreakIterator; @@ -109,14 +111,16 @@ public class SmartReplyView extends ViewGroup { Math.max(getChildCount(), 1), DECREASING_MEASURED_WIDTH_WITHOUT_PADDING_COMPARATOR); } - public void setRepliesFromRemoteInput(RemoteInput remoteInput, PendingIntent pendingIntent) { + public void setRepliesFromRemoteInput(RemoteInput remoteInput, PendingIntent pendingIntent, + SmartReplyLogger smartReplyLogger, NotificationData.Entry entry) { removeAllViews(); if (remoteInput != null && pendingIntent != null) { CharSequence[] choices = remoteInput.getChoices(); if (choices != null) { - for (CharSequence choice : choices) { + for (int i = 0; i < choices.length; ++i) { Button replyButton = inflateReplyButton( - getContext(), this, choice, remoteInput, pendingIntent); + getContext(), this, i, choices[i], remoteInput, pendingIntent, + smartReplyLogger, entry); addView(replyButton); } } @@ -130,8 +134,9 @@ public class SmartReplyView extends ViewGroup { } @VisibleForTesting - Button inflateReplyButton(Context context, ViewGroup root, CharSequence choice, - RemoteInput remoteInput, PendingIntent pendingIntent) { + Button inflateReplyButton(Context context, ViewGroup root, int replyIndex, + CharSequence choice, RemoteInput remoteInput, PendingIntent pendingIntent, + SmartReplyLogger smartReplyLogger, NotificationData.Entry entry) { Button b = (Button) LayoutInflater.from(context).inflate( R.layout.smart_reply_button, root, false); b.setText(choice); @@ -147,6 +152,7 @@ public class SmartReplyView extends ViewGroup { } catch (PendingIntent.CanceledException e) { Log.w(TAG, "Unable to send smart reply", e); } + smartReplyLogger.smartReplySent(entry, replyIndex); return false; // do not defer }; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java index 56882c654b33..2bb810665f3a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java @@ -22,11 +22,16 @@ import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verify; + import android.app.PendingIntent; import android.app.RemoteInput; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; +import android.service.notification.StatusBarNotification; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -38,6 +43,8 @@ import android.widget.LinearLayout; import com.android.keyguard.KeyguardHostView.OnDismissAction; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.NotificationData; +import com.android.systemui.statusbar.SmartReplyLogger; import com.android.systemui.statusbar.phone.KeyguardDismissUtil; import java.util.concurrent.atomic.AtomicReference; @@ -46,6 +53,8 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -66,8 +75,12 @@ public class SmartReplyViewTest extends SysuiTestCase { private int mDoubleLinePaddingHorizontal; private int mSpacing; + @Mock private SmartReplyLogger mLogger; + private NotificationData.Entry mEntry; + @Before public void setUp() { + MockitoAnnotations.initMocks(this); mReceiver = new BlockingQueueIntentReceiver(); mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION)); mDependency.get(KeyguardDismissUtil.class).setDismissHandler( @@ -82,6 +95,10 @@ public class SmartReplyViewTest extends SysuiTestCase { mDoubleLinePaddingHorizontal = res.getDimensionPixelSize( R.dimen.smart_reply_button_padding_horizontal_double_line); mSpacing = res.getDimensionPixelSize(R.dimen.smart_reply_button_spacing); + + StatusBarNotification notification = mock(StatusBarNotification.class); + when(notification.getKey()).thenReturn("akey"); + mEntry = new NotificationData.Entry(notification); } @After @@ -138,6 +155,13 @@ public class SmartReplyViewTest extends SysuiTestCase { } @Test + public void testSendSmartReply_LoggerCall() { + setRepliesFromRemoteInput(TEST_CHOICES); + mView.getChildAt(2).performClick(); + verify(mLogger).smartReplySent(mEntry, 2); + } + + @Test public void testMeasure_empty() { mView.measure(WIDTH_SPEC, HEIGHT_SPEC); assertEquals(500, mView.getMeasuredWidthAndState()); @@ -316,7 +340,7 @@ public class SmartReplyViewTest extends SysuiTestCase { PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(TEST_ACTION), 0); RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).setChoices(choices).build(); - mView.setRepliesFromRemoteInput(input, pendingIntent); + mView.setRepliesFromRemoteInput(input, pendingIntent, mLogger, mEntry); } /** Builds a {@link ViewGroup} whose measures and layout mirror a {@link SmartReplyView}. */ @@ -343,8 +367,9 @@ public class SmartReplyViewTest extends SysuiTestCase { } Button previous = null; - for (CharSequence choice : choices) { - Button current = mView.inflateReplyButton(mContext, mView, choice, null, null); + for (int i = 0; i < choices.length; ++i) { + Button current = mView.inflateReplyButton(mContext, mView, i, choices[i], + null, null, null, null); current.setPadding(paddingHorizontal, current.getPaddingTop(), paddingHorizontal, current.getPaddingBottom()); if (previous != null) { diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index f27cca6bf366..97224087b69d 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -5618,6 +5618,24 @@ message MetricsEvent { // OS: P SETTINGS_AUTO_BRIGHTNESS = 1381; + // OPEN: Smart replies in a notification seen at least once + // CATEGORY: NOTIFICATION + // PACKAGE: App that posted the notification + // SUBTYPE: Number of smart replies. + // OS: P + SMART_REPLY_VISIBLE = 1382; + + // ACTION: Smart reply in a notification clicked. + // CATEGORY: NOTIFICATION + // PACKAGE: App that posted the notification + // SUBTYPE: Index of smart reply clicked. + // OS: P + SMART_REPLY_ACTION = 1383; + + // Tagged data for SMART_REPLY_VISIBLE. Count of number of smart replies. + // OS: P + NOTIFICATION_SMART_REPLY_COUNT = 1384; + // ---- End P Constants, all P constants go above this line ---- // Add new aosp constants above this line. // END OF AOSP CONSTANTS diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java index 36bc0962f65b..b61a27ac6c6d 100644 --- a/services/core/java/com/android/server/notification/NotificationDelegate.java +++ b/services/core/java/com/android/server/notification/NotificationDelegate.java @@ -40,4 +40,6 @@ public interface NotificationDelegate { void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded); void onNotificationDirectReplied(String key); void onNotificationSettingsViewed(String key); + void onNotificationSmartRepliesAdded(String key, int replyCount); + void onNotificationSmartReplySent(String key, int replyIndex); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 7e04d3337e68..30776e532799 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -121,6 +121,7 @@ import android.media.AudioAttributes; import android.media.AudioManager; import android.media.AudioManagerInternal; import android.media.IRingtonePlayer; +import android.metrics.LogMaker; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -395,6 +396,8 @@ public class NotificationManagerService extends SystemService { private GroupHelper mGroupHelper; private boolean mIsTelevision; + private MetricsLogger mMetricsLogger; + private static class Archive { final int mBufferSize; final ArrayDeque<StatusBarNotification> mBuffer; @@ -801,6 +804,18 @@ public class NotificationManagerService extends SystemService { // Report to usage stats that notification was made visible if (DBG) Slog.d(TAG, "Marking notification as visible " + nv.key); reportSeen(r); + + // If the newly visible notification has smart replies + // then log that the user has seen them. + if (r.getNumSmartRepliesAdded() > 0 + && !r.hasSeenSmartReplies()) { + r.setSeenSmartReplies(true); + LogMaker logMaker = r.getLogMaker() + .setCategory(MetricsEvent.SMART_REPLY_VISIBLE) + .addTaggedData(MetricsEvent.NOTIFICATION_SMART_REPLY_COUNT, + r.getNumSmartRepliesAdded()); + mMetricsLogger.write(logMaker); + } } r.setVisibility(true, nv.rank); nv.recycle(); @@ -855,6 +870,31 @@ public class NotificationManagerService extends SystemService { } @Override + public void onNotificationSmartRepliesAdded(String key, int replyCount) { + synchronized (mNotificationLock) { + NotificationRecord r = mNotificationsByKey.get(key); + if (r != null) { + r.setNumSmartRepliesAdded(replyCount); + } + } + } + + @Override + public void onNotificationSmartReplySent(String key, int replyIndex) { + synchronized (mNotificationLock) { + NotificationRecord r = mNotificationsByKey.get(key); + if (r != null) { + LogMaker logMaker = r.getLogMaker() + .setCategory(MetricsEvent.SMART_REPLY_ACTION) + .setSubtype(replyIndex); + mMetricsLogger.write(logMaker); + // Treat clicking on a smart reply as a user interaction. + reportUserInteraction(r); + } + } + } + + @Override public void onNotificationSettingsViewed(String key) { synchronized (mNotificationLock) { NotificationRecord r = mNotificationsByKey.get(key); @@ -1349,6 +1389,7 @@ public class NotificationManagerService extends SystemService { extractorNames = new String[0]; } mUsageStats = usageStats; + mMetricsLogger = new MetricsLogger(); mRankingHandler = new RankingHandlerWorker(mRankingThread.getLooper()); mConditionProviders = conditionProviders; mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders); diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index c88708551662..9bd3e529cb29 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -149,6 +149,8 @@ public final class NotificationRecord { private final NotificationStats mStats; private int mUserSentiment; private boolean mIsInterruptive; + private int mNumberOfSmartRepliesAdded; + private boolean mHasSeenSmartReplies; @VisibleForTesting public NotificationRecord(Context context, StatusBarNotification sbn, @@ -962,6 +964,22 @@ public final class NotificationRecord { mStats.setViewedSettings(); } + public void setNumSmartRepliesAdded(int noReplies) { + mNumberOfSmartRepliesAdded = noReplies; + } + + public int getNumSmartRepliesAdded() { + return mNumberOfSmartRepliesAdded; + } + + public boolean hasSeenSmartReplies() { + return mHasSeenSmartReplies; + } + + public void setSeenSmartReplies(boolean hasSeenSmartReplies) { + mHasSeenSmartReplies = hasSeenSmartReplies; + } + public Set<Uri> getNotificationUris() { Notification notification = getNotification(); Set<Uri> uris = new ArraySet<>(); diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 2db80394236a..36fa868ba0e4 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -1096,6 +1096,30 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } @Override + public void onNotificationSmartRepliesAdded(String key, int replyCount) + throws RemoteException { + enforceStatusBarService(); + long identity = Binder.clearCallingIdentity(); + try { + mNotificationDelegate.onNotificationSmartRepliesAdded(key, replyCount); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void onNotificationSmartReplySent(String key, int replyIndex) + throws RemoteException { + enforceStatusBarService(); + long identity = Binder.clearCallingIdentity(); + try { + mNotificationDelegate.onNotificationSmartReplySent(key, replyIndex); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void onNotificationSettingsViewed(String key) throws RemoteException { enforceStatusBarService(); long identity = Binder.clearCallingIdentity(); |