Merge "All non-blob variants of drawText are deprecated"
diff --git a/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarChartPreference.java b/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarChartPreference.java
index 305862a..0a4b24c 100644
--- a/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarChartPreference.java
+++ b/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarChartPreference.java
@@ -168,6 +168,7 @@
if (mDetailsId == 0) {
detailsView.setVisibility(View.GONE);
} else {
+ detailsView.setVisibility(View.VISIBLE);
detailsView.setText(mDetailsId);
detailsView.setOnClickListener(mDetailsOnClickListener);
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BarChartPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BarChartPreferenceTest.java
index 375b45c..96e8995 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BarChartPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BarChartPreferenceTest.java
@@ -81,6 +81,20 @@
}
@Test
+ public void onBindViewHolder_notSetDetailsRes_barChartDetailsViewIsGoneThenReappears() {
+ // We don't call BarChartPreference#setBarChartDetails yet.
+ mPreference.onBindViewHolder(mHolder);
+
+ assertThat(mDetailsView.getVisibility()).isEqualTo(View.GONE);
+
+ mPreference.setBarChartDetails(R.string.debug_app);
+ mPreference.onBindViewHolder(mHolder);
+
+ assertThat(mDetailsView.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mDetailsView.getText()).isEqualTo(mContext.getText(R.string.debug_app));
+ }
+
+ @Test
public void setBarChartDetailsRes_setDetailsRes_showInBarChartDetails() {
mPreference.setBarChartDetails(R.string.debug_app);
mPreference.onBindViewHolder(mHolder);
diff --git a/packages/SystemUI/src/com/android/systemui/dock/DockManager.java b/packages/SystemUI/src/com/android/systemui/dock/DockManager.java
index 47b56d0..fa5a114 100644
--- a/packages/SystemUI/src/com/android/systemui/dock/DockManager.java
+++ b/packages/SystemUI/src/com/android/systemui/dock/DockManager.java
@@ -22,22 +22,22 @@
public interface DockManager {
/**
- * Uninitialized / unknown dock states
+ * Uninitialized / undocking dock states
*/
int STATE_NONE = 0;
/**
* The state for docking
*/
- int STATE_DOCKING = 1;
+ int STATE_DOCKED = 1;
/**
- * The state for undocking
+ * The state for docking without showing UI
*/
- int STATE_UNDOCKING = 2;
+ int STATE_DOCKED_HIDE = 2;
/**
* Add a dock event listener into manager
*
- * @param callback A {@link DockEventListener} which want to add
+ * @param callback A {@link DockEventListener} which want to add
*/
void addListener(DockEventListener callback);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
index 8cd42c7..9fc2234 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
@@ -18,11 +18,13 @@
import android.content.Context;
import android.os.Handler;
+import android.os.UserHandle;
import android.util.Log;
import com.android.internal.hardware.AmbientDisplayConfiguration;
import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.dock.DockManager;
+import com.android.systemui.doze.DozeMachine.State;
import java.io.PrintWriter;
@@ -34,7 +36,6 @@
private static final String TAG = "DozeDockHandler";
private static final boolean DEBUG = DozeService.DEBUG;
- private final Context mContext;
private final DozeMachine mMachine;
private final DozeHost mDozeHost;
private final AmbientDisplayConfiguration mConfig;
@@ -42,11 +43,10 @@
private final DockEventListener mDockEventListener = new DockEventListener();
private final DockManager mDockManager;
- private boolean mDocking;
+ private int mDockState = DockManager.STATE_NONE;
public DozeDockHandler(Context context, DozeMachine machine, DozeHost dozeHost,
AmbientDisplayConfiguration config, Handler handler) {
- mContext = context;
mMachine = machine;
mDozeHost = dozeHost;
mConfig = config;
@@ -60,34 +60,35 @@
case INITIALIZED:
mDockEventListener.register();
break;
- case DOZE:
case DOZE_AOD:
- mHandler.post(() -> requestPulse());
+ if (mDockState == DockManager.STATE_DOCKED_HIDE) {
+ mMachine.requestState(State.DOZE);
+ break;
+ }
+ // continue below
+ case DOZE:
+ if (mDockState == DockManager.STATE_DOCKED) {
+ mHandler.post(() -> requestPulse(newState));
+ }
break;
case FINISH:
mDockEventListener.unregister();
break;
default:
+ // no-op
}
}
- private void requestPulse() {
- if (!mDocking || mDozeHost.isPulsingBlocked() || !canPulse()) {
+ private void requestPulse(State dozeState) {
+ if (mDozeHost.isPulsingBlocked() || !dozeState.canPulse()) {
return;
}
mMachine.requestPulse(DozeLog.PULSE_REASON_DOCKING);
}
- private boolean canPulse() {
- return mMachine.getState() == DozeMachine.State.DOZE
- || mMachine.getState() == DozeMachine.State.DOZE_AOD;
- }
-
- private void requestPulseOutNow() {
- final DozeMachine.State state = mMachine.getState();
- if (state == DozeMachine.State.DOZE_PULSING
- || state == DozeMachine.State.DOZE_REQUEST_PULSE) {
+ private void requestPulseOutNow(State dozeState) {
+ if (dozeState == State.DOZE_REQUEST_PULSE || dozeState == State.DOZE_PULSING) {
final int pulseReason = mMachine.getPulseReason();
if (pulseReason == DozeLog.PULSE_REASON_DOCKING) {
mDozeHost.stopPulsing();
@@ -95,9 +96,14 @@
}
}
+ private boolean isDocked() {
+ return mDockState == DockManager.STATE_DOCKED
+ || mDockState == DockManager.STATE_DOCKED_HIDE;
+ }
+
@Override
public void dump(PrintWriter pw) {
- pw.print(" DozeDockTriggers docking="); pw.println(mDocking);
+ pw.print(" DozeDockTriggers docking="); pw.println(isDocked());
}
private class DockEventListener implements DockManager.DockEventListener {
@@ -106,14 +112,21 @@
@Override
public void onEvent(int event) {
if (DEBUG) Log.d(TAG, "dock event = " + event);
- switch (event) {
- case DockManager.STATE_DOCKING:
- mDocking = true;
- requestPulse();
+ final DozeMachine.State dozeState = mMachine.getState();
+ mDockState = event;
+ switch (mDockState) {
+ case DockManager.STATE_DOCKED:
+ requestPulse(dozeState);
break;
- case DockManager.STATE_UNDOCKING:
- mDocking = false;
- requestPulseOutNow();
+ case DockManager.STATE_NONE:
+ if (dozeState == State.DOZE
+ && mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) {
+ mMachine.requestState(State.DOZE_AOD);
+ break;
+ }
+ // continue below
+ case DockManager.STATE_DOCKED_HIDE:
+ requestPulseOutNow(dozeState);
break;
default:
// no-op
@@ -124,7 +137,6 @@
if (mRegistered) {
return;
}
-
if (mDockManager != null) {
mDockManager.addListener(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 35b64ed..04362c1 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -359,7 +359,9 @@
}
protected boolean enabledBySetting() {
- if (TextUtils.isEmpty(mSetting)) {
+ if (!mConfig.enabled(UserHandle.USER_CURRENT)) {
+ return false;
+ } else if (TextUtils.isEmpty(mSetting)) {
return true;
}
return Settings.Secure.getIntForUser(mResolver, mSetting, mSettingDefault ? 1 : 0,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index 5329541..b1eab80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -40,6 +40,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
+import android.metrics.LogMaker;
import android.os.Handler;
import android.os.RemoteException;
import android.service.notification.StatusBarNotification;
@@ -150,6 +151,7 @@
// Reset exit counter that we'll log and record an undo event separately (not an exit event)
mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
logBlockingHelperCounter(NotificationCounters.BLOCKING_HELPER_UNDO);
+ mMetricsLogger.write(importanceChangeLogMaker().setType(MetricsEvent.TYPE_DISMISS));
swapContent(ACTION_UNDO, true /* animate */);
};
@@ -381,6 +383,17 @@
}
}
+ /**
+ * Returns an initialized LogMaker for logging importance changes.
+ * The caller may override the type (to DISMISS) before passing it to mMetricsLogger.
+ * @return new LogMaker
+ */
+ private LogMaker importanceChangeLogMaker() {
+ return new LogMaker(MetricsEvent.ACTION_SAVE_IMPORTANCE)
+ .setType(MetricsEvent.TYPE_ACTION)
+ .setSubtype(mChosenImportance - mStartingChannelImportance);
+ }
+
private boolean hasImportanceChanged() {
return mSingleNotificationChannel != null
&& mStartingChannelImportance != mChosenImportance;
@@ -397,8 +410,7 @@
* Commits the updated importance values on the background thread.
*/
private void updateImportance() {
- MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
- mChosenImportance - mStartingChannelImportance);
+ mMetricsLogger.write(importanceChangeLogMaker());
Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
bgHandler.post(new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
index 9946317..926ff69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
@@ -17,7 +17,9 @@
package com.android.systemui.doze;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -37,6 +39,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerFake;
+import com.android.systemui.doze.DozeMachine.State;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -66,6 +69,7 @@
mMachine = mock(DozeMachine.class);
mHost = spy(new DozeHostFake());
mConfig = DozeConfigurationUtil.createMockConfig();
+ doReturn(false).when(mConfig).alwaysOnEnabled(anyInt());
mDockManagerFake = spy(new DockManagerFake());
mContext.putComponent(DockManager.class, mDockManagerFake);
@@ -75,7 +79,7 @@
}
@Test
- public void testDockEventListener_registerAndUnregister() throws Exception {
+ public void testDockEventListener_registerAndUnregister() {
mDockHandler.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
verify(mDockManagerFake).addListener(any());
@@ -86,55 +90,115 @@
}
@Test
- public void testOnEvent_dockingWhenDoze_requestPulse() throws Exception {
+ public void testOnEvent_dockedWhenDoze_requestPulse() {
mDockHandler.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
- mDockManagerFake.setDockEvent(DockManager.STATE_DOCKING);
+ mDockManagerFake.setDockEvent(DockManager.STATE_DOCKED);
verify(mMachine).requestPulse(eq(DozeLog.PULSE_REASON_DOCKING));
}
@Test
- public void testOnEvent_dockingWhenPausing_neverRequestPulse() throws Exception {
+ public void testOnEvent_dockedWhenDozeAoD_requestPulse() {
mDockHandler.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
- when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE_AOD_PAUSING);
+ when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE_AOD);
- mDockManagerFake.setDockEvent(DockManager.STATE_DOCKING);
+ mDockManagerFake.setDockEvent(DockManager.STATE_DOCKED);
- verify(mMachine, never()).requestPulse(eq(DozeLog.PULSE_REASON_DOCKING));
+ verify(mMachine).requestPulse(eq(DozeLog.PULSE_REASON_DOCKING));
}
@Test
- public void testOnEvent_undockedWhenPulsing_requestPulseOut() throws Exception {
+ public void testOnEvent_dockedHideWhenPulsing_requestPulseOut() {
mDockHandler.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
- when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE_PULSING);
+ when(mMachine.getState()).thenReturn(State.DOZE_PULSING);
when(mMachine.getPulseReason()).thenReturn(DozeLog.PULSE_REASON_DOCKING);
- mDockManagerFake.setDockEvent(DockManager.STATE_UNDOCKING);
+ mDockManagerFake.setDockEvent(DockManager.STATE_DOCKED_HIDE);
verify(mHost).stopPulsing();
}
@Test
- public void testOnEvent_undockedWhenDoze_neverRequestPulseOut() throws Exception {
+ public void testOnEvent_undockedWhenPulsing_requestPulseOut() {
+ mDockHandler.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
+ when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE_PULSING);
+ when(mMachine.getPulseReason()).thenReturn(DozeLog.PULSE_REASON_DOCKING);
+
+ mDockManagerFake.setDockEvent(DockManager.STATE_NONE);
+
+ verify(mHost).stopPulsing();
+ }
+
+ @Test
+ public void testOnEvent_undockedWhenDoze_neverRequestPulseOut() {
mDockHandler.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
- mDockManagerFake.setDockEvent(DockManager.STATE_UNDOCKING);
+ mDockManagerFake.setDockEvent(DockManager.STATE_NONE);
verify(mHost, never()).stopPulsing();
}
@Test
- public void testTransitionToDozeWhenDocking_RequestPulse() throws Exception {
+ public void testOnEvent_undockedWhenDozeAndEnabledAoD_requestDozeAoD() {
+ doReturn(true).when(mConfig).alwaysOnEnabled(anyInt());
mDockHandler.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
- mDockManagerFake.setDockEvent(DockManager.STATE_DOCKING);
- mDockHandler.transitionTo(DozeMachine.State.DOZE_AOD_PAUSING, DozeMachine.State.DOZE);
when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+ mDockManagerFake.setDockEvent(DockManager.STATE_NONE);
+
+ verify(mMachine).requestState(eq(State.DOZE_AOD));
+ }
+
+ @Test
+ public void testTransitionToDoze_whenDocked_requestPulse() {
+ mDockHandler.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
+ when(mMachine.getState()).thenReturn(DozeMachine.State.INITIALIZED);
+ mDockManagerFake.setDockEvent(DockManager.STATE_DOCKED);
+ when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+ mDockHandler.transitionTo(State.INITIALIZED, DozeMachine.State.DOZE);
+
TestableLooper.get(this).processAllMessages();
verify(mMachine).requestPulse(eq(DozeLog.PULSE_REASON_DOCKING));
}
+
+ @Test
+ public void testTransitionToDozeAoD_whenDocked_requestPulse() {
+ mDockHandler.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
+ when(mMachine.getState()).thenReturn(DozeMachine.State.INITIALIZED);
+ mDockManagerFake.setDockEvent(DockManager.STATE_DOCKED);
+ when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE_AOD);
+ mDockHandler.transitionTo(State.INITIALIZED, DozeMachine.State.DOZE_AOD);
+
+ TestableLooper.get(this).processAllMessages();
+
+ verify(mMachine).requestPulse(eq(DozeLog.PULSE_REASON_DOCKING));
+ }
+
+ @Test
+ public void testTransitionToDoze_whenDockedHide_neverRequestPulse() {
+ mDockHandler.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
+ when(mMachine.getState()).thenReturn(DozeMachine.State.INITIALIZED);
+ mDockManagerFake.setDockEvent(DockManager.STATE_DOCKED_HIDE);
+ when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+
+ mDockHandler.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE);
+
+ verify(mMachine, never()).requestPulse(eq(DozeLog.PULSE_REASON_DOCKING));
+ }
+
+ @Test
+ public void testTransitionToDozeAoD_whenDockedHide_requestDoze() {
+ mDockHandler.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
+ when(mMachine.getState()).thenReturn(DozeMachine.State.INITIALIZED);
+ mDockManagerFake.setDockEvent(DockManager.STATE_DOCKED_HIDE);
+ when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE_AOD);
+
+ mDockHandler.transitionTo(DozeMachine.State.INITIALIZED, State.DOZE_AOD);
+
+ verify(mMachine).requestState(eq(State.DOZE));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index d28f017..ecb0cf8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -32,6 +32,7 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
@@ -44,6 +45,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.app.INotificationManager;
@@ -54,6 +56,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
+import android.metrics.LogMaker;
import android.os.IBinder;
import android.os.UserHandle;
import android.provider.Settings;
@@ -69,6 +72,7 @@
import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
@@ -79,6 +83,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -177,6 +182,29 @@
() -> VISIBLE == mNotificationInfo.findViewById(R.id.prompt).getVisibility());
}
+ class ImportanceChangeLogMaker implements ArgumentMatcher<LogMaker> {
+ private static final int CATEGORY = MetricsProto.MetricsEvent.ACTION_SAVE_IMPORTANCE;
+ private int mType, mSubtype;
+
+ ImportanceChangeLogMaker(int type, int subtype) {
+ mType = type;
+ mSubtype = subtype;
+ }
+ public boolean matches(LogMaker l) {
+ return (l.getCategory() == CATEGORY)
+ && (l.getType() == mType)
+ && (l.getSubtype() == mSubtype);
+ }
+
+ public String toString() {
+ return String.format("LogMaker(%d, %d, %d)", CATEGORY, mType, mSubtype);
+ }
+ }
+
+ private LogMaker importanceChangeLog(int type, int subtype) {
+ return argThat(new ImportanceChangeLogMaker(type, subtype));
+ }
+
@Test
public void testBindNotification_SetsTextApplicationName() throws Exception {
when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
@@ -475,7 +503,7 @@
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
IMPORTANCE_DEFAULT);
mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent");
- verify(mMetricsLogger, times(0)).count(anyString(), anyInt());
+ verifyZeroInteractions(mMetricsLogger);
}
@Test
@@ -484,7 +512,7 @@
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false, true,
true, true, IMPORTANCE_DEFAULT);
mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent");
- verify(mMetricsLogger, times(1)).count(anyString(), anyInt());
+ verify(mMetricsLogger).count(eq("HowCanNotifsBeRealIfAppsArent"), eq(1));
}
@Test
@@ -827,6 +855,9 @@
waitForUndoButton();
mNotificationInfo.handleCloseControls(true, false);
+ verify(mMetricsLogger).write(importanceChangeLog(
+ MetricsProto.MetricsEvent.TYPE_ACTION, IMPORTANCE_NONE - IMPORTANCE_LOW));
+
mTestableLooper.processAllMessages();
ArgumentCaptor<NotificationChannel> updated =
ArgumentCaptor.forClass(NotificationChannel.class);
@@ -860,6 +891,9 @@
waitForUndoButton();
mNotificationInfo.handleCloseControls(true, false);
+ verify(mMetricsLogger).write(importanceChangeLog(
+ MetricsProto.MetricsEvent.TYPE_ACTION, IMPORTANCE_NONE - IMPORTANCE_LOW));
+
mTestableLooper.processAllMessages();
ArgumentCaptor<NotificationChannel> updated =
ArgumentCaptor.forClass(NotificationChannel.class);
@@ -936,15 +970,14 @@
waitForUndoButton();
mNotificationInfo.findViewById(R.id.undo).performClick();
waitForStopButton();
- mNotificationInfo.handleCloseControls(true, false);
+ // mNotificationInfo.handleCloseControls doesn't get called by this interaction.
+
+ verify(mMetricsLogger).write(importanceChangeLog(
+ MetricsProto.MetricsEvent.TYPE_DISMISS, IMPORTANCE_NONE - IMPORTANCE_LOW));
mTestableLooper.processAllMessages();
- ArgumentCaptor<NotificationChannel> updated =
- ArgumentCaptor.forClass(NotificationChannel.class);
- verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
- anyString(), eq(TEST_UID), updated.capture());
- assertTrue(0 != (mNotificationChannel.getUserLockedFields() & USER_LOCKED_IMPORTANCE));
- assertEquals(IMPORTANCE_LOW, mNotificationChannel.getImportance());
+ verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
+ anyString(), eq(TEST_UID), any());
}
@Test
@@ -958,15 +991,11 @@
waitForUndoButton();
mNotificationInfo.findViewById(R.id.undo).performClick();
waitForStopButton();
- mNotificationInfo.handleCloseControls(true, false);
+ // mNotificationInfo.handleCloseControls doesn't get called by this code path
mTestableLooper.processAllMessages();
- ArgumentCaptor<NotificationChannel> updated =
- ArgumentCaptor.forClass(NotificationChannel.class);
- verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
- anyString(), eq(TEST_UID), updated.capture());
- assertTrue(0 != (mNotificationChannel.getUserLockedFields() & USER_LOCKED_IMPORTANCE));
- assertEquals(IMPORTANCE_LOW, mNotificationChannel.getImportance());
+ verify(mMockINotificationManager, times(0)).updateNotificationChannelForPackage(
+ anyString(), eq(TEST_UID), any());
}
@Test
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index e80e9e1..1aeb689 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -91,6 +91,7 @@
"/system/bin/mediaserver",
"/system/bin/sdcard",
"/system/bin/surfaceflinger",
+ "/system/bin/vold",
"media.extractor", // system/bin/mediaextractor
"media.metrics", // system/bin/mediametrics
"media.codec", // vendor/bin/hw/android.hardware.media.omx@1.0-service
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index d961bad..e2cb75e 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -144,7 +144,6 @@
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
-import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -3852,7 +3851,8 @@
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver resultReceiver)
throws RemoteException {
- new ShellCmd().exec(this, in, out, err, args, callback, resultReceiver);
+ new NotificationShellCmd(NotificationManagerService.this)
+ .exec(this, in, out, err, args, callback, resultReceiver);
}
};
@@ -7715,110 +7715,6 @@
}
}
- private class ShellCmd extends ShellCommand {
- public static final String USAGE = "help\n"
- + "allow_listener COMPONENT [user_id]\n"
- + "disallow_listener COMPONENT [user_id]\n"
- + "allow_assistant COMPONENT\n"
- + "remove_assistant COMPONENT\n"
- + "allow_dnd PACKAGE\n"
- + "disallow_dnd PACKAGE\n"
- + "suspend_package PACKAGE\n"
- + "unsuspend_package PACKAGE";
-
- @Override
- public int onCommand(String cmd) {
- if (cmd == null) {
- return handleDefaultCommands(cmd);
- }
- final PrintWriter pw = getOutPrintWriter();
- try {
- switch (cmd) {
- case "allow_dnd": {
- getBinderService().setNotificationPolicyAccessGranted(
- getNextArgRequired(), true);
- }
- break;
-
- case "disallow_dnd": {
- getBinderService().setNotificationPolicyAccessGranted(
- getNextArgRequired(), false);
- }
- break;
- case "allow_listener": {
- ComponentName cn = ComponentName.unflattenFromString(getNextArgRequired());
- if (cn == null) {
- pw.println("Invalid listener - must be a ComponentName");
- return -1;
- }
- String userId = getNextArg();
- if (userId == null) {
- getBinderService().setNotificationListenerAccessGranted(cn, true);
- } else {
- getBinderService().setNotificationListenerAccessGrantedForUser(
- cn, Integer.parseInt(userId), true);
- }
- }
- break;
- case "disallow_listener": {
- ComponentName cn = ComponentName.unflattenFromString(getNextArgRequired());
- if (cn == null) {
- pw.println("Invalid listener - must be a ComponentName");
- return -1;
- }
- String userId = getNextArg();
- if (userId == null) {
- getBinderService().setNotificationListenerAccessGranted(cn, false);
- } else {
- getBinderService().setNotificationListenerAccessGrantedForUser(
- cn, Integer.parseInt(userId), false);
- }
- }
- break;
- case "allow_assistant": {
- ComponentName cn = ComponentName.unflattenFromString(getNextArgRequired());
- if (cn == null) {
- pw.println("Invalid assistant - must be a ComponentName");
- return -1;
- }
- getBinderService().setNotificationAssistantAccessGranted(cn, true);
- }
- break;
- case "disallow_assistant": {
- ComponentName cn = ComponentName.unflattenFromString(getNextArgRequired());
- if (cn == null) {
- pw.println("Invalid assistant - must be a ComponentName");
- return -1;
- }
- getBinderService().setNotificationAssistantAccessGranted(cn, false);
- }
- break;
- case "suspend_package": {
- // only use for testing
- simulatePackageSuspendBroadcast(true, getNextArgRequired());
- }
- break;
- case "unsuspend_package": {
- // only use for testing
- simulatePackageSuspendBroadcast(false, getNextArgRequired());
- }
- break;
- default:
- return handleDefaultCommands(cmd);
- }
- } catch (Exception e) {
- pw.println("Error occurred. Check logcat for details. " + e.getMessage());
- Slog.e(TAG, "Error running shell command", e);
- }
- return 0;
- }
-
- @Override
- public void onHelp() {
- getOutPrintWriter().println(USAGE);
- }
- }
-
private void writeSecureNotificationsPolicy(XmlSerializer out) throws IOException {
out.startTag(null, LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG);
out.attribute(null, LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_VALUE,
diff --git a/services/core/java/com/android/server/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java
new file mode 100644
index 0000000..3d88f20
--- /dev/null
+++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java
@@ -0,0 +1,483 @@
+/*
+ * 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.server.notification;
+
+import android.app.INotificationManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Person;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
+import android.content.res.Resources;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.net.URISyntaxException;
+import java.util.Collections;
+
+/**
+ * Implementation of `cmd notification` in NotificationManagerService.
+ */
+public class NotificationShellCmd extends ShellCommand {
+ private static final String USAGE =
+ "usage: cmd notification SUBCMD [args]\n\n"
+ + "SUBCMDs:\n"
+ + " allow_listener COMPONENT [user_id]\n"
+ + " disallow_listener COMPONENT [user_id]\n"
+ + " allow_assistant COMPONENT\n"
+ + " remove_assistant COMPONENT\n"
+ + " allow_dnd PACKAGE\n"
+ + " disallow_dnd PACKAGE\n"
+ + " suspend_package PACKAGE\n"
+ + " unsuspend_package PACKAGE\n"
+ + " post [--help | flags] TAG TEXT";
+
+ private static final String NOTIFY_USAGE =
+ "usage: cmd notification post [flags] <tag> <text>\n\n"
+ + "flags:\n"
+ + " -h|--help\n"
+ + " -v|--verbose\n"
+ + " -t|--title <text>\n"
+ + " -i|--icon <iconspec>\n"
+ + " -I|--large-icon <iconspec>\n"
+ + " -S|--style <style> [styleargs]\n"
+ + " -c|--content-intent <intentspec>\n"
+ + "\n"
+ + "styles: (default none)\n"
+ + " bigtext\n"
+ + " bigpicture --picture <iconspec>\n"
+ + " inbox --line <text> --line <text> ...\n"
+ + " messaging --conversation <title> --message <who>:<text> ...\n"
+ + " media\n"
+ + "\n"
+ + "an <iconspec> is one of\n"
+ + " file:///data/local/tmp/<img.png>\n"
+ + " content://<provider>/<path>\n"
+ + " @[<package>:]drawable/<img>\n"
+ + " data:base64,<B64DATA==>\n"
+ + "\n"
+ + "an <intentspec> is (broadcast|service|activity) <args>\n"
+ + " <args> are as described in `am start`";
+
+ public static final int NOTIFICATION_ID = 1138;
+ public static final String NOTIFICATION_PACKAGE = "com.android.shell";
+ public static final String CHANNEL_ID = "shellcmd";
+ public static final String CHANNEL_NAME = "Shell command";
+ public static final int CHANNEL_IMP = NotificationManager.IMPORTANCE_DEFAULT;
+
+ private final NotificationManagerService mDirectService;
+ private final INotificationManager mBinderService;
+
+ public NotificationShellCmd(NotificationManagerService service) {
+ mDirectService = service;
+ mBinderService = service.getBinderService();
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ final PrintWriter pw = getOutPrintWriter();
+ try {
+ switch (cmd.replace('-', '_')) {
+ case "allow_dnd": {
+ mBinderService.setNotificationPolicyAccessGranted(
+ getNextArgRequired(), true);
+ }
+ break;
+
+ case "disallow_dnd": {
+ mBinderService.setNotificationPolicyAccessGranted(
+ getNextArgRequired(), false);
+ }
+ break;
+ case "allow_listener": {
+ ComponentName cn = ComponentName.unflattenFromString(getNextArgRequired());
+ if (cn == null) {
+ pw.println("Invalid listener - must be a ComponentName");
+ return -1;
+ }
+ String userId = getNextArg();
+ if (userId == null) {
+ mBinderService.setNotificationListenerAccessGranted(cn, true);
+ } else {
+ mBinderService.setNotificationListenerAccessGrantedForUser(
+ cn, Integer.parseInt(userId), true);
+ }
+ }
+ break;
+ case "disallow_listener": {
+ ComponentName cn = ComponentName.unflattenFromString(getNextArgRequired());
+ if (cn == null) {
+ pw.println("Invalid listener - must be a ComponentName");
+ return -1;
+ }
+ String userId = getNextArg();
+ if (userId == null) {
+ mBinderService.setNotificationListenerAccessGranted(cn, false);
+ } else {
+ mBinderService.setNotificationListenerAccessGrantedForUser(
+ cn, Integer.parseInt(userId), false);
+ }
+ }
+ break;
+ case "allow_assistant": {
+ ComponentName cn = ComponentName.unflattenFromString(getNextArgRequired());
+ if (cn == null) {
+ pw.println("Invalid assistant - must be a ComponentName");
+ return -1;
+ }
+ mBinderService.setNotificationAssistantAccessGranted(cn, true);
+ }
+ break;
+ case "disallow_assistant": {
+ ComponentName cn = ComponentName.unflattenFromString(getNextArgRequired());
+ if (cn == null) {
+ pw.println("Invalid assistant - must be a ComponentName");
+ return -1;
+ }
+ mBinderService.setNotificationAssistantAccessGranted(cn, false);
+ }
+ break;
+ case "suspend_package": {
+ // only use for testing
+ mDirectService.simulatePackageSuspendBroadcast(true, getNextArgRequired());
+ }
+ break;
+ case "unsuspend_package": {
+ // only use for testing
+ mDirectService.simulatePackageSuspendBroadcast(false, getNextArgRequired());
+ }
+ break;
+ case "post":
+ case "notify":
+ doNotify(pw);
+ break;
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ } catch (Exception e) {
+ pw.println("Error occurred. Check logcat for details. " + e.getMessage());
+ Slog.e(NotificationManagerService.TAG, "Error running shell command", e);
+ }
+ return 0;
+ }
+
+ void ensureChannel() throws RemoteException {
+ final int uid = Binder.getCallingUid();
+ final int userid = UserHandle.getCallingUserId();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (mBinderService.getNotificationChannelForPackage(NOTIFICATION_PACKAGE,
+ uid, CHANNEL_ID, false) == null) {
+ final NotificationChannel chan = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME,
+ CHANNEL_IMP);
+ Slog.v(NotificationManagerService.TAG,
+ "creating shell channel for user " + userid + " uid " + uid + ": " + chan);
+ mBinderService.createNotificationChannelsForPackage(NOTIFICATION_PACKAGE, uid,
+ new ParceledListSlice<NotificationChannel>(
+ Collections.singletonList(chan)));
+ Slog.v(NotificationManagerService.TAG, "created channel: "
+ + mBinderService.getNotificationChannelForPackage(NOTIFICATION_PACKAGE,
+ uid, CHANNEL_ID, false));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ Icon parseIcon(Resources res, String encoded) throws IllegalArgumentException {
+ if (TextUtils.isEmpty(encoded)) return null;
+ if (encoded.startsWith("/")) {
+ encoded = "file://" + encoded;
+ }
+ if (encoded.startsWith("http:")
+ || encoded.startsWith("https:")
+ || encoded.startsWith("content:")
+ || encoded.startsWith("file:")
+ || encoded.startsWith("android.resource:")) {
+ Uri asUri = Uri.parse(encoded);
+ return Icon.createWithContentUri(asUri);
+ } else if (encoded.startsWith("@")) {
+ final int resid = res.getIdentifier(encoded.substring(1),
+ "drawable", "android");
+ if (resid != 0) {
+ return Icon.createWithResource(res, resid);
+ }
+ } else if (encoded.startsWith("data:")) {
+ encoded = encoded.substring(encoded.indexOf(',') + 1);
+ byte[] bits = android.util.Base64.decode(encoded, android.util.Base64.DEFAULT);
+ return Icon.createWithData(bits, 0, bits.length);
+ }
+ return null;
+ }
+
+ private int doNotify(PrintWriter pw) throws RemoteException, URISyntaxException {
+ final Context context = mDirectService.getContext();
+ final Resources res = context.getResources();
+ final Notification.Builder builder = new Notification.Builder(context, CHANNEL_ID);
+ String opt;
+
+ boolean verbose = false;
+ Notification.BigPictureStyle bigPictureStyle = null;
+ Notification.BigTextStyle bigTextStyle = null;
+ Notification.InboxStyle inboxStyle = null;
+ Notification.MediaStyle mediaStyle = null;
+ Notification.MessagingStyle messagingStyle = null;
+
+ Icon smallIcon = null;
+ while ((opt = getNextOption()) != null) {
+ boolean large = false;
+ switch (opt) {
+ case "-v":
+ case "--verbose":
+ verbose = true;
+ break;
+ case "-t":
+ case "--title":
+ case "title":
+ builder.setContentTitle(getNextArgRequired());
+ break;
+ case "-I":
+ case "--large-icon":
+ case "--largeicon":
+ case "largeicon":
+ case "large-icon":
+ large = true;
+ // fall through
+ case "-i":
+ case "--icon":
+ case "icon":
+ final String iconSpec = getNextArgRequired();
+ final Icon icon = parseIcon(res, iconSpec);
+ if (icon == null) {
+ pw.println("error: invalid icon: " + iconSpec);
+ return -1;
+ }
+ if (large) {
+ builder.setLargeIcon(icon);
+ large = false;
+ } else {
+ smallIcon = icon;
+ }
+ break;
+ case "-c":
+ case "--content-intent":
+ case "content-intent":
+ case "--intent":
+ case "intent":
+ String intentKind = null;
+ switch (peekNextArg()) {
+ case "broadcast":
+ case "service":
+ case "activity":
+ intentKind = getNextArg();
+ }
+ final Intent intent = Intent.parseCommandArgs(this, null);
+ if (intent.getData() == null) {
+ // force unique intents unless you know what you're doing
+ intent.setData(Uri.parse("xyz:" + System.currentTimeMillis()));
+ }
+ final PendingIntent pi;
+ if ("broadcast".equals(intentKind)) {
+ pi = PendingIntent.getBroadcastAsUser(
+ context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT,
+ UserHandle.CURRENT);
+ } else if ("service".equals(intentKind)) {
+ pi = PendingIntent.getService(
+ context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ } else {
+ pi = PendingIntent.getActivityAsUser(
+ context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT, null,
+ UserHandle.CURRENT);
+ }
+ builder.setContentIntent(pi);
+ break;
+ case "-S":
+ case "--style":
+ final String styleSpec = getNextArgRequired().toLowerCase();
+ switch (styleSpec) {
+ case "bigtext":
+ bigTextStyle = new Notification.BigTextStyle();
+ builder.setStyle(bigTextStyle);
+ break;
+ case "bigpicture":
+ bigPictureStyle = new Notification.BigPictureStyle();
+ builder.setStyle(bigPictureStyle);
+ break;
+ case "inbox":
+ inboxStyle = new Notification.InboxStyle();
+ builder.setStyle(inboxStyle);
+ break;
+ case "messaging":
+ String name = "You";
+ if ("--user".equals(peekNextArg())) {
+ getNextArg();
+ name = getNextArgRequired();
+ }
+ messagingStyle = new Notification.MessagingStyle(
+ new Person.Builder().setName(name).build());
+ builder.setStyle(messagingStyle);
+ break;
+ case "media":
+ mediaStyle = new Notification.MediaStyle();
+ builder.setStyle(mediaStyle);
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "unrecognized notification style: " + styleSpec);
+ }
+ break;
+ case "--bigText": case "--bigtext": case "--big-text":
+ if (bigTextStyle == null) {
+ throw new IllegalArgumentException("--bigtext requires --style bigtext");
+ }
+ bigTextStyle.bigText(getNextArgRequired());
+ break;
+ case "--picture":
+ if (bigPictureStyle == null) {
+ throw new IllegalArgumentException("--picture requires --style bigpicture");
+ }
+ final String pictureSpec = getNextArgRequired();
+ final Icon pictureAsIcon = parseIcon(res, pictureSpec);
+ if (pictureAsIcon == null) {
+ throw new IllegalArgumentException("bad picture spec: " + pictureSpec);
+ }
+ final Drawable d = pictureAsIcon.loadDrawable(context);
+ if (d instanceof BitmapDrawable) {
+ bigPictureStyle.bigPicture(((BitmapDrawable) d).getBitmap());
+ } else {
+ throw new IllegalArgumentException("not a bitmap: " + pictureSpec);
+ }
+ break;
+ case "--line":
+ if (inboxStyle == null) {
+ throw new IllegalArgumentException("--line requires --style inbox");
+ }
+ inboxStyle.addLine(getNextArgRequired());
+ break;
+ case "--message":
+ if (messagingStyle == null) {
+ throw new IllegalArgumentException(
+ "--message requires --style messaging");
+ }
+ String arg = getNextArgRequired();
+ String[] parts = arg.split(":", 2);
+ if (parts.length > 1) {
+ messagingStyle.addMessage(parts[1], System.currentTimeMillis(),
+ parts[0]);
+ } else {
+ messagingStyle.addMessage(parts[0], System.currentTimeMillis(),
+ new String[]{
+ messagingStyle.getUserDisplayName().toString(),
+ "Them"
+ }[messagingStyle.getMessages().size() % 2]);
+ }
+ break;
+ case "--conversation":
+ if (messagingStyle == null) {
+ throw new IllegalArgumentException(
+ "--conversation requires --style messaging");
+ }
+ messagingStyle.setConversationTitle(getNextArgRequired());
+ break;
+ case "-h":
+ case "--help":
+ case "--wtf":
+ default:
+ pw.println(NOTIFY_USAGE);
+ return 0;
+ }
+ }
+
+ final String tag = getNextArg();
+ final String text = getNextArg();
+ if (tag == null || text == null) {
+ pw.println(NOTIFY_USAGE);
+ return -1;
+ }
+
+ builder.setContentText(text);
+
+ if (smallIcon == null) {
+ // uh oh, let's substitute something
+ builder.setSmallIcon(com.android.internal.R.drawable.stat_notify_chat);
+ } else {
+ builder.setSmallIcon(smallIcon);
+ }
+
+ ensureChannel();
+
+ final Notification n = builder.build();
+ pw.println("posting:\n " + n);
+ Slog.v("NotificationManager", "posting: " + n);
+
+ final int userId = UserHandle.getCallingUserId();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mBinderService.enqueueNotificationWithTag(
+ NOTIFICATION_PACKAGE, "android",
+ tag, NOTIFICATION_ID,
+ n, userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ if (verbose) {
+ NotificationRecord nr = mDirectService.findNotificationLocked(
+ NOTIFICATION_PACKAGE, tag, NOTIFICATION_ID, userId);
+ for (int tries = 3; tries-- > 0; ) {
+ if (nr != null) break;
+ try {
+ pw.println("waiting for notification to post...");
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ }
+ nr = mDirectService.findNotificationLocked(
+ NOTIFICATION_PACKAGE, tag, NOTIFICATION_ID, userId);
+ }
+ if (nr == null) {
+ pw.println("warning: couldn't find notification after enqueueing");
+ } else {
+ pw.println("posted: ");
+ nr.dump(pw, " ", context, false);
+ }
+ }
+
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ getOutPrintWriter().println(USAGE);
+ }
+}
+
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index ee06746..2b4ec03 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -179,13 +179,19 @@
List<OverlayInfo> getOverlaysForTarget(@NonNull final String targetPackageName,
final int userId) {
+ // Static RROs targeting "android" are loaded from AssetManager, and so they should be
+ // ignored in OverlayManagerService.
return selectWhereTarget(targetPackageName, userId)
+ .filter((i) -> !(i.isStatic() && "android".equals(i.getTargetPackageName())))
.map(SettingsItem::getOverlayInfo)
.collect(Collectors.toList());
}
ArrayMap<String, List<OverlayInfo>> getOverlaysForUser(final int userId) {
+ // Static RROs targeting "android" are loaded from AssetManager, and so they should be
+ // ignored in OverlayManagerService.
return selectWhereUser(userId)
+ .filter((i) -> !(i.isStatic() && "android".equals(i.getTargetPackageName())))
.map(SettingsItem::getOverlayInfo)
.collect(Collectors.groupingBy(info -> info.targetPackageName, ArrayMap::new,
Collectors.toList()));
diff --git a/services/core/java/com/android/server/slice/SliceClientPermissions.java b/services/core/java/com/android/server/slice/SliceClientPermissions.java
index e461e0d..ab94a59 100644
--- a/services/core/java/com/android/server/slice/SliceClientPermissions.java
+++ b/services/core/java/com/android/server/slice/SliceClientPermissions.java
@@ -282,9 +282,12 @@
public synchronized void writeTo(XmlSerializer out) throws IOException {
final int N = mPaths.size();
for (int i = 0; i < N; i++) {
- out.startTag(NAMESPACE, TAG_PATH);
- out.text(encodeSegments(mPaths.valueAt(i)));
- out.endTag(NAMESPACE, TAG_PATH);
+ final String[] segments = mPaths.valueAt(i);
+ if (segments != null) {
+ out.startTag(NAMESPACE, TAG_PATH);
+ out.text(encodeSegments(segments));
+ out.endTag(NAMESPACE, TAG_PATH);
+ }
}
}
diff --git a/services/core/java/com/android/server/slice/SlicePermissionManager.java b/services/core/java/com/android/server/slice/SlicePermissionManager.java
index 780bc96..315d5e3 100644
--- a/services/core/java/com/android/server/slice/SlicePermissionManager.java
+++ b/services/core/java/com/android/server/slice/SlicePermissionManager.java
@@ -315,7 +315,8 @@
return new AtomicFile(new File(mSliceDir, fileName));
}
- private void handlePersist() {
+ @VisibleForTesting
+ void handlePersist() {
synchronized (this) {
for (Persistable persistable : mDirty) {
AtomicFile file = getFile(persistable.getFileName());
@@ -335,7 +336,7 @@
out.flush();
file.finishWrite(stream);
- } catch (IOException | XmlPullParserException e) {
+ } catch (IOException | XmlPullParserException | RuntimeException e) {
Slog.w(TAG, "Failed to save access file, restoring backup", e);
file.failWrite(stream);
}
@@ -344,6 +345,12 @@
}
}
+ // use addPersistableDirty(); this is just for tests
+ @VisibleForTesting
+ void addDirtyImmediate(Persistable obj) {
+ mDirty.add(obj);
+ }
+
private void handleRemove(PkgUser pkgUser) {
getFile(SliceClientPermissions.getFileName(pkgUser)).delete();
getFile(SliceProviderPermissions.getFileName(pkgUser)).delete();
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 6755c73..49cf8c7 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -66,6 +66,8 @@
import static com.android.server.wm.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
+import static com.android.server.wm.ActivityStackSupervisor.dumpHistoryList;
+import static com.android.server.wm.ActivityStackSupervisor.printThisActivity;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_APP;
@@ -309,7 +311,7 @@
* The first entry in the list is the least recently used.
* It contains HistoryRecord objects.
*/
- final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<>();
+ private final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<>();
/**
* When we are in the process of pausing an activity, before starting the
@@ -5153,6 +5155,47 @@
}
}
+ boolean dump(FileDescriptor fd, PrintWriter pw, boolean dumpAll, boolean dumpClient,
+ String dumpPackage, boolean needSep) {
+ pw.println(" Stack #" + mStackId
+ + ": type=" + activityTypeToString(getActivityType())
+ + " mode=" + windowingModeToString(getWindowingMode()));
+ pw.println(" isSleeping=" + shouldSleepActivities());
+ pw.println(" mBounds=" + getRequestedOverrideBounds());
+
+ boolean printed = dumpActivitiesLocked(fd, pw, dumpAll, dumpClient, dumpPackage,
+ needSep);
+
+ printed |= dumpHistoryList(fd, pw, mLRUActivities, " ", "Run", false,
+ !dumpAll, false, dumpPackage, true,
+ " Running activities (most recent first):", null);
+
+ needSep = printed;
+ boolean pr = printThisActivity(pw, mPausingActivity, dumpPackage, needSep,
+ " mPausingActivity: ");
+ if (pr) {
+ printed = true;
+ needSep = false;
+ }
+ pr = printThisActivity(pw, getResumedActivity(), dumpPackage, needSep,
+ " mResumedActivity: ");
+ if (pr) {
+ printed = true;
+ needSep = false;
+ }
+ if (dumpAll) {
+ pr = printThisActivity(pw, mLastPausedActivity, dumpPackage, needSep,
+ " mLastPausedActivity: ");
+ if (pr) {
+ printed = true;
+ needSep = true;
+ }
+ printed |= printThisActivity(pw, mLastNoHistoryActivity, dumpPackage,
+ needSep, " mLastNoHistoryActivity: ");
+ }
+ return printed;
+ }
+
boolean dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, boolean dumpAll,
boolean dumpClient, String dumpPackage, boolean needSep) {
@@ -5172,7 +5215,7 @@
pw.println(prefix + "mLastNonFullscreenBounds=" + task.mLastNonFullscreenBounds);
pw.println(prefix + "* " + task);
task.dump(pw, prefix + " ");
- ActivityStackSupervisor.dumpHistoryList(fd, pw, mTaskHistory.get(taskNdx).mActivities,
+ dumpHistoryList(fd, pw, mTaskHistory.get(taskNdx).mActivities,
prefix, "Hist", true, !dumpAll, dumpClient, dumpPackage, false, null, task);
}
return true;
@@ -5241,11 +5284,6 @@
* {@link #REMOVE_TASK_MODE_MOVING}, {@link #REMOVE_TASK_MODE_MOVING_TO_TOP}.
*/
void removeTask(TaskRecord task, String reason, int mode) {
- // TODO(b/119259346): Move some logic below to TaskRecord. See bug for more context.
- for (ActivityRecord record : task.mActivities) {
- onActivityRemovedFromStack(record);
- }
-
final boolean removed = mTaskHistory.remove(task);
if (removed) {
@@ -5255,25 +5293,8 @@
removeActivitiesFromLRUListLocked(task);
updateTaskMovement(task, true);
- if (mode == REMOVE_TASK_MODE_DESTROYING && task.mActivities.isEmpty()) {
- // This task is going away, so save the last state if necessary.
- task.saveLaunchingStateIfNeeded();
-
- // TODO: VI what about activity?
- final boolean isVoiceSession = task.voiceSession != null;
- if (isVoiceSession) {
- try {
- task.voiceSession.taskFinished(task.intent, task.taskId);
- } catch (RemoteException e) {
- }
- }
- if (task.autoRemoveFromRecents() || isVoiceSession) {
- // Task creator asked to remove this when done, or this task was a voice
- // interaction, so it should not remain on the recent tasks list.
- mStackSupervisor.mRecentTasks.remove(task);
- }
-
- task.removeWindowContainer();
+ if (mode == REMOVE_TASK_MODE_DESTROYING) {
+ task.cleanUpResourcesForDestroy();
}
if (mTaskHistory.isEmpty()) {
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index 45d77de..5cfa7de 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -326,15 +326,11 @@
void applySettingsToDisplayLocked(DisplayContent dc) {
final DisplayInfo displayInfo = dc.getDisplayInfo();
- final Entry entry = getEntry(displayInfo);
+ final Entry entry = getOrCreateEntry(displayInfo);
// Setting windowing mode first, because it may override overscan values later.
dc.setWindowingMode(getWindowingModeLocked(entry, dc.getDisplayId()));
- if (entry == null) {
- return;
- }
-
displayInfo.overscanLeft = entry.mOverscanLeft;
displayInfo.overscanTop = entry.mOverscanTop;
displayInfo.overscanRight = entry.mOverscanRight;
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index 6f92e64..c63ee3e 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -29,8 +29,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.app.WindowConfiguration.activityTypeToString;
-import static android.app.WindowConfiguration.windowingModeToString;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
@@ -2301,42 +2299,7 @@
for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = display.getChildAt(stackNdx);
pw.println();
- pw.println(" Stack #" + stack.mStackId
- + ": type=" + activityTypeToString(stack.getActivityType())
- + " mode=" + windowingModeToString(stack.getWindowingMode()));
- pw.println(" isSleeping=" + stack.shouldSleepActivities());
- pw.println(" mBounds=" + stack.getRequestedOverrideBounds());
-
- printed |= stack.dumpActivitiesLocked(fd, pw, dumpAll, dumpClient, dumpPackage,
- needSep);
-
- printed |= dumpHistoryList(fd, pw, stack.mLRUActivities, " ", "Run", false,
- !dumpAll, false, dumpPackage, true,
- " Running activities (most recent first):", null);
-
- needSep = printed;
- boolean pr = printThisActivity(pw, stack.mPausingActivity, dumpPackage, needSep,
- " mPausingActivity: ");
- if (pr) {
- printed = true;
- needSep = false;
- }
- pr = printThisActivity(pw, stack.getResumedActivity(), dumpPackage, needSep,
- " mResumedActivity: ");
- if (pr) {
- printed = true;
- needSep = false;
- }
- if (dumpAll) {
- pr = printThisActivity(pw, stack.mLastPausedActivity, dumpPackage, needSep,
- " mLastPausedActivity: ");
- if (pr) {
- printed = true;
- needSep = true;
- }
- printed |= printThisActivity(pw, stack.mLastNoHistoryActivity, dumpPackage,
- needSep, " mLastNoHistoryActivity: ");
- }
+ printed = stack.dump(fd, pw, dumpAll, dumpClient, dumpPackage, needSep);
needSep = printed;
}
printThisActivity(pw, activityDisplay.getResumedActivity(), dumpPackage, needSep,
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index 8c80009..f1b0c0696 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -481,6 +481,32 @@
mTask = task;
}
+ void cleanUpResourcesForDestroy() {
+ if (!mActivities.isEmpty()) {
+ return;
+ }
+
+ // This task is going away, so save the last state if necessary.
+ saveLaunchingStateIfNeeded();
+
+ // TODO: VI what about activity?
+ final boolean isVoiceSession = voiceSession != null;
+ if (isVoiceSession) {
+ try {
+ voiceSession.taskFinished(intent, taskId);
+ } catch (RemoteException e) {
+ }
+ }
+ if (autoRemoveFromRecents() || isVoiceSession) {
+ // Task creator asked to remove this when done, or this task was a voice
+ // interaction, so it should not remain on the recent tasks list.
+ mService.mStackSupervisor.mRecentTasks.remove(this);
+ }
+
+ removeWindowContainer();
+ }
+
+ @VisibleForTesting
void removeWindowContainer() {
mService.getLockTaskController().clearLockedTask(this);
if (mTask == null) {
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 7a5eaa8..f4443fe 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -20,6 +20,7 @@
"android-support-test",
"mockito-target-inline-minus-junit4",
"platform-test-annotations",
+ "hamcrest-library",
"testables",
],
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationShellCmdTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationShellCmdTest.java
new file mode 100644
index 0000000..fa90b29
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationShellCmdTest.java
@@ -0,0 +1,270 @@
+/*
+ * 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.server.notification;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertSame;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+// this is a lazy way to do in/out/err but we're not particularly interested in the output
+import static java.io.FileDescriptor.err;
+import static java.io.FileDescriptor.in;
+import static java.io.FileDescriptor.out;
+
+import android.app.INotificationManager;
+import android.app.Notification;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.Icon;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.UserHandle;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class NotificationShellCmdTest extends UiServiceTestCase {
+ private final Binder mBinder = new Binder();
+ private final ShellCallback mCallback = new ShellCallback();
+ private final TestableContext mTestableContext = spy(getContext());
+ @Mock
+ NotificationManagerService mMockService;
+ @Mock
+ INotificationManager mMockBinderService;
+ private TestableLooper mTestableLooper;
+ private ResultReceiver mResultReceiver;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mTestableLooper = TestableLooper.get(this);
+ mResultReceiver = new ResultReceiver(new Handler(mTestableLooper.getLooper()));
+
+ when(mMockService.getContext()).thenReturn(mTestableContext);
+ when(mMockService.getBinderService()).thenReturn(mMockBinderService);
+ }
+
+ private Bitmap createTestBitmap() {
+ final Bitmap bits = Bitmap.createBitmap(512, 512, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bits);
+ final GradientDrawable grad = new GradientDrawable(GradientDrawable.Orientation.TL_BR,
+ new int[]{Color.RED, Color.YELLOW, Color.GREEN,
+ Color.CYAN, Color.BLUE, Color.MAGENTA});
+ grad.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ grad.draw(canvas);
+ return bits;
+ }
+
+ private void doCmd(String... args) {
+ new NotificationShellCmd(mMockService)
+ .exec(mBinder, in, out, err, args, mCallback, mResultReceiver);
+ }
+
+ @Test
+ public void testNoArgs() throws Exception {
+ doCmd();
+ }
+
+ @Test
+ public void testHelp() throws Exception {
+ doCmd("--help");
+ }
+
+ Notification captureNotification(String aTag) throws Exception {
+ ArgumentCaptor<Notification> notificationCaptor =
+ ArgumentCaptor.forClass(Notification.class);
+ verify(mMockBinderService).enqueueNotificationWithTag(
+ eq(NotificationShellCmd.NOTIFICATION_PACKAGE),
+ eq("android"),
+ eq(aTag),
+ eq(NotificationShellCmd.NOTIFICATION_ID),
+ notificationCaptor.capture(),
+ eq(UserHandle.getCallingUserId()));
+ return notificationCaptor.getValue();
+ }
+
+ @Test
+ public void testBasic() throws Exception {
+ final String aTag = "aTag";
+ final String aText = "someText";
+ final String aTitle = "theTitle";
+ doCmd("notify",
+ "--title", aTitle,
+ aTag, aText);
+ final Notification captured = captureNotification(aTag);
+ assertEquals(aText, captured.extras.getString(Notification.EXTRA_TEXT));
+ assertEquals(aTitle, captured.extras.getString(Notification.EXTRA_TITLE));
+ }
+
+ @Test
+ public void testIcon() throws Exception {
+ final String aTag = "aTag";
+ final String aText = "someText";
+ doCmd("notify", "--icon", "@android:drawable/stat_sys_adb", aTag, aText);
+ final Notification captured = captureNotification(aTag);
+ final Icon icon = captured.getSmallIcon();
+ assertEquals("android", icon.getResPackage());
+ assertEquals(com.android.internal.R.drawable.stat_sys_adb, icon.getResId());
+ }
+
+ @Test
+ public void testBigText() throws Exception {
+ final String aTag = "aTag";
+ final String aText = "someText";
+ final String bigText = "someBigText";
+ doCmd("notify",
+ "--style", "bigtext",
+ "--big-text", bigText,
+ aTag, aText);
+ final Notification captured = captureNotification(aTag);
+ assertSame(captured.getNotificationStyle(), Notification.BigTextStyle.class);
+ assertEquals(aText, captured.extras.getString(Notification.EXTRA_TEXT));
+ assertEquals(bigText, captured.extras.getString(Notification.EXTRA_BIG_TEXT));
+ }
+
+ @Test
+ public void testBigPicture() throws Exception {
+ final String aTag = "aTag";
+ final String aText = "someText";
+ final String bigPicture = "@android:drawable/default_wallpaper";
+ doCmd("notify",
+ "--style", "bigpicture",
+ "--picture", bigPicture,
+ aTag, aText);
+ final Notification captured = captureNotification(aTag);
+ assertSame(captured.getNotificationStyle(), Notification.BigPictureStyle.class);
+ final Object pic = captured.extras.get(Notification.EXTRA_PICTURE);
+ assertThat(pic, instanceOf(Bitmap.class));
+ }
+
+ @Test
+ public void testInbox() throws Exception {
+ final int n = 25;
+ final String aTag = "inboxTag";
+ final String aText = "inboxText";
+ ArrayList<String> args = new ArrayList<>();
+ args.add("notify");
+ args.add("--style");
+ args.add("inbox");
+ final int startOfLineArgs = args.size();
+ for (int i = 0; i < n; i++) {
+ args.add("--line");
+ args.add(String.format("Line %02d", i));
+ }
+ args.add(aTag);
+ args.add(aText);
+
+ doCmd(args.toArray(new String[0]));
+ final Notification captured = captureNotification(aTag);
+ assertSame(captured.getNotificationStyle(), Notification.InboxStyle.class);
+ final Notification.Builder builder =
+ Notification.Builder.recoverBuilder(mContext, captured);
+ final ArrayList<CharSequence> lines =
+ ((Notification.InboxStyle) (builder.getStyle())).getLines();
+ for (int i = 0; i < n; i++) {
+ assertEquals(lines.get(i), args.get(1 + 2 * i + startOfLineArgs));
+ }
+ }
+
+ static final String[] PEOPLE = {
+ "Alice",
+ "Bob",
+ "Charlotte"
+ };
+ static final String[] MESSAGES = {
+ "Shall I compare thee to a summer's day?",
+ "Thou art more lovely and more temperate:",
+ "Rough winds do shake the darling buds of May,",
+ "And summer's lease hath all too short a date;",
+ "Sometime too hot the eye of heaven shines,",
+ "And often is his gold complexion dimm'd;",
+ "And every fair from fair sometime declines,",
+ "By chance or nature's changing course untrimm'd;",
+ "But thy eternal summer shall not fade,",
+ "Nor lose possession of that fair thou ow'st;",
+ "Nor shall death brag thou wander'st in his shade,",
+ "When in eternal lines to time thou grow'st:",
+ " So long as men can breathe or eyes can see,",
+ " So long lives this, and this gives life to thee.",
+ };
+
+ @Test
+ public void testMessaging() throws Exception {
+ final String aTag = "messagingTag";
+ final String aText = "messagingText";
+ ArrayList<String> args = new ArrayList<>();
+ args.add("notify");
+ args.add("--style");
+ args.add("messaging");
+ args.add("--conversation");
+ args.add("Sonnet 18");
+ final int startOfLineArgs = args.size();
+ for (int i = 0; i < MESSAGES.length; i++) {
+ args.add("--message");
+ args.add(String.format("%s:%s",
+ PEOPLE[i % PEOPLE.length],
+ MESSAGES[i % MESSAGES.length]));
+ }
+ args.add(aTag);
+ args.add(aText);
+
+ doCmd(args.toArray(new String[0]));
+ final Notification captured = captureNotification(aTag);
+ assertSame(Notification.MessagingStyle.class, captured.getNotificationStyle());
+ final Notification.Builder builder =
+ Notification.Builder.recoverBuilder(mContext, captured);
+ final Notification.MessagingStyle messagingStyle =
+ (Notification.MessagingStyle) (builder.getStyle());
+
+ assertEquals("Sonnet 18", messagingStyle.getConversationTitle());
+ final List<Notification.MessagingStyle.Message> messages = messagingStyle.getMessages();
+ for (int i = 0; i < messages.size(); i++) {
+ final Notification.MessagingStyle.Message m = messages.get(i);
+ assertEquals(MESSAGES[i], m.getText());
+ assertEquals(PEOPLE[i % PEOPLE.length], m.getSenderPerson().getName());
+ }
+
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SlicePermissionManagerTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SlicePermissionManagerTest.java
index dc057d5..b315e51 100644
--- a/services/tests/uiservicestests/src/com/android/server/slice/SlicePermissionManagerTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/slice/SlicePermissionManagerTest.java
@@ -101,4 +101,34 @@
assertTrue(FileUtils.deleteContentsAndDir(sliceDir));
}
-}
\ No newline at end of file
+ @Test
+ public void testInvalid() throws Exception {
+ File sliceDir = new File(mContext.getCacheDir(), "slices-test");
+ if (!sliceDir.exists()) {
+ sliceDir.mkdir();
+ }
+ SlicePermissionManager permissions = new SlicePermissionManager(mContext,
+ TestableLooper.get(this).getLooper(), sliceDir);
+
+ DirtyTracker.Persistable junk = new DirtyTracker.Persistable() {
+ @Override
+ public String getFileName() {
+ return "invalidData";
+ }
+
+ @Override
+ public void writeTo(XmlSerializer out) throws IOException {
+ throw new RuntimeException("this doesn't work");
+ }
+ };
+
+ // let's put something bad in here
+ permissions.addDirtyImmediate(junk);
+ // force a persist. if this throws, it would take down system_server
+ permissions.handlePersist();
+
+ // Cleanup.
+ assertTrue(FileUtils.deleteContentsAndDir(sliceDir));
+ }
+
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index 8e881b5..992d017 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -22,10 +22,10 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -43,11 +43,13 @@
import androidx.test.filters.SmallTest;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.policy.WindowManagerPolicy;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.MockitoSession;
import java.io.File;
@@ -94,6 +96,12 @@
@After
public void tearDown() {
deleteRecursively(TEST_FOLDER);
+
+ // TODO(b/121296525): We may want to restore other display settings (not only overscans in
+ // testPersistOverscan*test) on mPrimaryDisplay and mSecondaryDisplay back to default
+ // values after each test finishes, since we are going to reuse a singleton
+ // WindowManagerService instance among all tests that extend {@link WindowTestsBase} class
+ // (b/113239988).
}
@Test
@@ -245,21 +253,35 @@
@Test
public void testPersistOverscanInSameInstance() {
final DisplayInfo info = mPrimaryDisplay.getDisplayInfo();
- mTarget.setOverscanLocked(info, 1 /* left */, 2 /* top */, 3 /* right */, 4 /* bottom */);
+ try {
+ mTarget.setOverscanLocked(info, 1 /* left */, 2 /* top */, 3 /* right */,
+ 4 /* bottom */);
- mTarget.applySettingsToDisplayLocked(mPrimaryDisplay);
+ mTarget.applySettingsToDisplayLocked(mPrimaryDisplay);
- assertOverscan(mPrimaryDisplay, 1 /* left */, 2 /* top */, 3 /* right */, 4 /* bottom */);
+ assertOverscan(mPrimaryDisplay, 1 /* left */, 2 /* top */, 3 /* right */,
+ 4 /* bottom */);
+ } finally {
+ mTarget.setOverscanLocked(info, 0, 0, 0, 0);
+ mTarget.applySettingsToDisplayLocked(mPrimaryDisplay);
+ }
}
@Test
public void testPersistOverscanAcrossInstances() {
final DisplayInfo info = mPrimaryDisplay.getDisplayInfo();
- mTarget.setOverscanLocked(info, 1 /* left */, 2 /* top */, 3 /* right */, 4 /* bottom */);
+ try {
+ mTarget.setOverscanLocked(info, 10 /* left */, 20 /* top */, 30 /* right */,
+ 40 /* bottom */);
- applySettingsToDisplayByNewInstance(mPrimaryDisplay);
+ applySettingsToDisplayByNewInstance(mPrimaryDisplay);
- assertOverscan(mPrimaryDisplay, 1 /* left */, 2 /* top */, 3 /* right */, 4 /* bottom */);
+ assertOverscan(mPrimaryDisplay, 10 /* left */, 20 /* top */, 30 /* right */,
+ 40 /* bottom */);
+ } finally {
+ mTarget.setOverscanLocked(info, 0, 0, 0, 0);
+ mTarget.applySettingsToDisplayLocked(mPrimaryDisplay);
+ }
}
@Test
@@ -389,26 +411,32 @@
mTarget.setUserRotation(mPrimaryDisplay, WindowManagerPolicy.USER_ROTATION_LOCKED,
Surface.ROTATION_0);
+ final MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
+ .startMocking();
final DisplayRotation displayRotation = mock(DisplayRotation.class);
- mPrimaryDisplay = spy(mPrimaryDisplay);
- when(mPrimaryDisplay.getDisplayRotation()).thenReturn(displayRotation);
+ spyOn(mPrimaryDisplay);
+ doReturn(displayRotation).when(mPrimaryDisplay).getDisplayRotation();
mTarget.applySettingsToDisplayLocked(mPrimaryDisplay);
verify(displayRotation).restoreSettings(anyInt(), anyInt(), eq(false));
+ mockitoSession.finishMocking();
}
@Test
public void testSetFixedToUserRotation() {
mTarget.setFixedToUserRotation(mPrimaryDisplay, true);
+ final MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
+ .startMocking();
final DisplayRotation displayRotation = mock(DisplayRotation.class);
- mPrimaryDisplay = spy(mPrimaryDisplay);
- when(mPrimaryDisplay.getDisplayRotation()).thenReturn(displayRotation);
+ spyOn(mPrimaryDisplay);
+ doReturn(displayRotation).when(mPrimaryDisplay).getDisplayRotation();
applySettingsToDisplayByNewInstance(mPrimaryDisplay);
verify(displayRotation).restoreSettings(anyInt(), anyInt(), eq(true));
+ mockitoSession.finishMocking();
}
private static void assertOverscan(DisplayContent display, int left, int top, int right,
diff --git a/startop/view_compiler/TEST_MAPPING b/startop/view_compiler/TEST_MAPPING
index 5d675b7..7006075 100644
--- a/startop/view_compiler/TEST_MAPPING
+++ b/startop/view_compiler/TEST_MAPPING
@@ -2,6 +2,10 @@
"presubmit": [
{
"name": "dex-builder-test"
+ },
+ {
+ "name": "view-compiler-tests",
+ "host": true
}
]
}
diff --git a/startop/view_compiler/main.cc b/startop/view_compiler/main.cc
index 55bfdc7..609bcf3 100644
--- a/startop/view_compiler/main.cc
+++ b/startop/view_compiler/main.cc
@@ -32,6 +32,7 @@
namespace {
using namespace tinyxml2;
+using namespace startop::util;
using std::string;
constexpr char kStdoutFilename[]{"stdout"};
diff --git a/startop/view_compiler/util.cc b/startop/view_compiler/util.cc
index 69df41d..a0637e6 100644
--- a/startop/view_compiler/util.cc
+++ b/startop/view_compiler/util.cc
@@ -18,6 +18,9 @@
using std::string;
+namespace startop {
+namespace util {
+
// TODO: see if we can borrow this from somewhere else, like aapt2.
string FindLayoutNameFromFilename(const string& filename) {
size_t start = filename.rfind("/");
@@ -30,3 +33,6 @@
return filename.substr(start, end - start);
}
+
+} // namespace util
+} // namespace startop
diff --git a/startop/view_compiler/util.h b/startop/view_compiler/util.h
index 03e0939..0176175 100644
--- a/startop/view_compiler/util.h
+++ b/startop/view_compiler/util.h
@@ -13,11 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#ifndef UTIL_H_
-#define UTIL_H_
+#ifndef VIEW_COMPILER_UTIL_H_
+#define VIEW_COMPILER_UTIL_H_
#include <string>
+namespace startop {
+namespace util {
+
std::string FindLayoutNameFromFilename(const std::string& filename);
-#endif // UTIL_H_
+} // namespace util
+} // namespace startop
+
+#endif // VIEW_COMPILER_UTIL_H_
diff --git a/startop/view_compiler/util_test.cc b/startop/view_compiler/util_test.cc
index d1540d3..50682a0 100644
--- a/startop/view_compiler/util_test.cc
+++ b/startop/view_compiler/util_test.cc
@@ -20,9 +20,15 @@
using std::string;
+namespace startop {
+namespace util {
+
TEST(UtilTest, FindLayoutNameFromFilename) {
- EXPECT_EQ("bar", ::FindLayoutNameFromFilename("foo/bar.xml"));
- EXPECT_EQ("bar", ::FindLayoutNameFromFilename("bar.xml"));
- EXPECT_EQ("bar", ::FindLayoutNameFromFilename("./foo/bar.xml"));
- EXPECT_EQ("bar", ::FindLayoutNameFromFilename("/foo/bar.xml"));
+ EXPECT_EQ("bar", startop::util::FindLayoutNameFromFilename("foo/bar.xml"));
+ EXPECT_EQ("bar", startop::util::FindLayoutNameFromFilename("bar.xml"));
+ EXPECT_EQ("bar", startop::util::FindLayoutNameFromFilename("./foo/bar.xml"));
+ EXPECT_EQ("bar", startop::util::FindLayoutNameFromFilename("/foo/bar.xml"));
}
+
+} // namespace util
+} // namespace startop
diff --git a/tests/ActivityViewTest/src/com/google/android/test/activityview/ActivityViewActivity.java b/tests/ActivityViewTest/src/com/google/android/test/activityview/ActivityViewActivity.java
index 1548d6e..f7c60fc 100644
--- a/tests/ActivityViewTest/src/com/google/android/test/activityview/ActivityViewActivity.java
+++ b/tests/ActivityViewTest/src/com/google/android/test/activityview/ActivityViewActivity.java
@@ -20,6 +20,7 @@
import android.app.ActivityView;
import android.content.Intent;
import android.os.Bundle;
+import android.os.Parcelable;
import android.widget.Button;
public class ActivityViewActivity extends Activity {
@@ -41,6 +42,10 @@
final Intent intent = Intent.makeMainActivity(null);
final Intent chooser = Intent.createChooser(intent,
"Pick an app to launch in ActivityView");
+ chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Parcelable[] {
+ new Intent(Intent.ACTION_MAIN)
+ .addCategory("com.android.internal.category.PLATLOGO")
+ });
if (intent.resolveActivity(getPackageManager()) != null) {
chooser.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_MULTIPLE_TASK);