Merge "Finalize resource IDs for O-MR1" into oc-mr1-dev
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 785a8f7..e14b738 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -114,6 +114,7 @@
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityEvent;
import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillManager.AutofillClient;
import android.view.autofill.AutofillPopupWindow;
import android.view.autofill.IAutofillWindowPresenter;
import android.widget.AdapterView;
@@ -947,6 +948,18 @@
return mAutofillManager;
}
+ @Override
+ protected void attachBaseContext(Context newBase) {
+ super.attachBaseContext(newBase);
+ newBase.setAutofillClient(this);
+ }
+
+ /** @hide */
+ @Override
+ public final AutofillClient getAutofillClient() {
+ return this;
+ }
+
/**
* Called when the activity is starting. This is where most initialization
* should go: calling {@link #setContentView(int)} to inflate the
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index f01feea..45f7eba 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5766,7 +5766,7 @@
final int preloadedFontsResource = info.metaData.getInt(
ApplicationInfo.METADATA_PRELOADED_FONTS, 0);
if (preloadedFontsResource != 0) {
- data.info.mResources.preloadFonts(preloadedFontsResource);
+ data.info.getResources().preloadFonts(preloadedFontsResource);
}
}
} catch (RemoteException e) {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index c48be77..5f34322 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -74,6 +74,7 @@
import android.util.Slog;
import android.view.Display;
import android.view.DisplayAdjustments;
+import android.view.autofill.AutofillManager.AutofillClient;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
@@ -185,6 +186,8 @@
// The name of the split this Context is representing. May be null.
private @Nullable String mSplitName = null;
+ private AutofillClient mAutofillClient = null;
+
private final Object mSync = new Object();
@GuardedBy("mSync")
@@ -2225,6 +2228,18 @@
return mUser.getIdentifier();
}
+ /** @hide */
+ @Override
+ public AutofillClient getAutofillClient() {
+ return mAutofillClient;
+ }
+
+ /** @hide */
+ @Override
+ public void setAutofillClient(AutofillClient client) {
+ mAutofillClient = client;
+ }
+
static ContextImpl createSystemContext(ActivityThread mainThread) {
LoadedApk packageInfo = new LoadedApk(mainThread);
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index c208f1d..9383626 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -1265,7 +1265,7 @@
* {@link android.service.autofill.AutofillService} for more details.
*
* @return domain-only part of the document. For example, if the full URL is
- * {@code https://my.site/login?user=my_user}, it returns {@code my.site}.
+ * {@code https://example.com/login?user=my_user}, it returns {@code example.com}.
*/
@Nullable public String getWebDomain() {
return mWebDomain;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2d8249a..792e8f3 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -64,6 +64,7 @@
import android.view.View;
import android.view.ViewDebug;
import android.view.WindowManager;
+import android.view.autofill.AutofillManager.AutofillClient;
import android.view.textclassifier.TextClassificationManager;
import java.io.File;
@@ -4765,6 +4766,19 @@
}
/**
+ * @hide
+ */
+ public AutofillClient getAutofillClient() {
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ public void setAutofillClient(AutofillClient client) {
+ }
+
+ /**
* Throws an exception if the Context is using system resources,
* which are non-runtime-overlay-themable and may show inconsistent UI.
* @hide
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index a9fd58b..85acdc6 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -37,6 +37,7 @@
import android.os.UserHandle;
import android.view.Display;
import android.view.DisplayAdjustments;
+import android.view.autofill.AutofillManager.AutofillClient;
import java.io.File;
import java.io.FileInputStream;
@@ -967,7 +968,24 @@
/**
* @hide
*/
+ @Override
public int getNextAutofillId() {
return mBase.getNextAutofillId();
}
+
+ /**
+ * @hide
+ */
+ @Override
+ public AutofillClient getAutofillClient() {
+ return mBase.getAutofillClient();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void setAutofillClient(AutofillClient client) {
+ mBase.setAutofillClient(client);
+ }
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 54f587e..b1a2133 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10839,6 +10839,26 @@
*/
public static final String ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE =
"enable_deletion_helper_no_threshold_toggle";
+
+ /**
+ * The list of snooze options for notifications
+ * This is encoded as a key=value list, separated by commas. Ex:
+ *
+ * "default=60,options_array=15:30:60:120"
+ *
+ * The following keys are supported:
+ *
+ * <pre>
+ * default (int)
+ * options_array (string)
+ * </pre>
+ *
+ * All delays in integer minutes. Array order is respected.
+ * Options will be used in order up to the maximum allowed by the UI.
+ * @hide
+ */
+ public static final String NOTIFICATION_SNOOZE_OPTIONS =
+ "notification_snooze_options";
}
/**
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 045c833..1521e7e 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -503,13 +503,19 @@
@NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback);
/**
- * Called when user requests service to save the fields of a screen.
+ * Called when the user requests the service to save the contents of a screen.
*
* <p>Service must call one of the {@link SaveCallback} methods (like
* {@link SaveCallback#onSuccess()} or {@link SaveCallback#onFailure(CharSequence)})
- * to notify the result of the request.
+ * to notify the Android System of the result of the request.
*
- * <p><b>Note:</b> To retrieve the actual value of the field, the service should call
+ * <p>If the service could not handle the request right away—for example, because it must
+ * launch an activity asking the user to authenticate first or because the network is
+ * down—the service could keep the {@link SaveRequest request} and reuse it later,
+ * but the service must call {@link SaveCallback#onSuccess()} right away.
+ *
+ * <p><b>Note:</b> To retrieve the actual value of fields input by the user, the service
+ * should call
* {@link android.app.assist.AssistStructure.ViewNode#getAutofillValue()}; if it calls
* {@link android.app.assist.AssistStructure.ViewNode#getText()} or other methods, there is no
* guarantee such method will return the most recent value of the field.
diff --git a/core/java/android/service/autofill/SaveCallback.java b/core/java/android/service/autofill/SaveCallback.java
index 3a70138..7207f1d 100644
--- a/core/java/android/service/autofill/SaveCallback.java
+++ b/core/java/android/service/autofill/SaveCallback.java
@@ -34,9 +34,13 @@
/**
* Notifies the Android System that an
- * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} was successfully fulfilled
+ * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} was successfully handled
* by the service.
*
+ * <p>If the service could not handle the request right away—for example, because it must
+ * launch an activity asking the user to authenticate first or because the network is
+ * down—it should still call {@link #onSuccess()}.
+ *
* @throws RuntimeException if an error occurred while calling the Android System.
*/
public void onSuccess() {
@@ -51,9 +55,16 @@
/**
* Notifies the Android System that an
- * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} could not be fulfilled
+ * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} could not be handled
* by the service.
*
+ * <p>This method should only be called when the service could not handle the request right away
+ * and could not recover or retry it. If the service could retry or recover, it could keep
+ * the {@link SaveRequest} and call {@link #onSuccess()} instead.
+ *
+ * <p><b>Note:</b> The Android System displays an UI with the supplied error message; if
+ * you prefer to show your own message, call {@link #onSuccess()} instead.
+ *
* @param message error message to be displayed to the user.
*
* @throws RuntimeException if an error occurred while calling the Android System.
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index e906a1f..4fb2a99 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -937,10 +937,7 @@
}
private AutofillClient getClientLocked() {
- if (mContext instanceof AutofillClient) {
- return (AutofillClient) mContext;
- }
- return null;
+ return mContext.getAutofillClient();
}
/** @hide */
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 43e762d3..7d9c50d 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -277,6 +277,7 @@
Settings.Global.NEW_CONTACT_AGGREGATOR,
Settings.Global.NITZ_UPDATE_DIFF,
Settings.Global.NITZ_UPDATE_SPACING,
+ Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
Settings.Global.NSD_ON,
Settings.Global.NTP_SERVER,
Settings.Global.NTP_TIMEOUT,
diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java
index 4808d7a..09449a1 100644
--- a/media/java/android/media/PlayerBase.java
+++ b/media/java/android/media/PlayerBase.java
@@ -127,8 +127,9 @@
Log.e(TAG, "Error talking to audio service, STARTED state will not be tracked", e);
}
synchronized (mLock) {
+ boolean attributesChanged = (mAttributes != attr);
mAttributes = attr;
- updateAppOpsPlayAudio_sync();
+ updateAppOpsPlayAudio_sync(attributesChanged);
}
}
@@ -200,16 +201,13 @@
}
void baseSetVolume(float leftVolume, float rightVolume) {
- final boolean hasAppOpsPlayAudio;
+ final boolean isRestricted;
synchronized (mLock) {
mLeftVolume = leftVolume;
mRightVolume = rightVolume;
- hasAppOpsPlayAudio = mHasAppOpsPlayAudio;
- if (isRestricted_sync()) {
- return;
- }
+ isRestricted = isRestricted_sync();
}
- playerSetVolume(!hasAppOpsPlayAudio/*muting*/,
+ playerSetVolume(isRestricted/*muting*/,
leftVolume * mPanMultiplierL, rightVolume * mPanMultiplierR);
}
@@ -250,7 +248,7 @@
private void updateAppOpsPlayAudio() {
synchronized (mLock) {
- updateAppOpsPlayAudio_sync();
+ updateAppOpsPlayAudio_sync(false);
}
}
@@ -258,7 +256,7 @@
* To be called whenever a condition that might affect audibility of this player is updated.
* Must be called synchronized on mLock.
*/
- void updateAppOpsPlayAudio_sync() {
+ void updateAppOpsPlayAudio_sync(boolean attributesChanged) {
boolean oldHasAppOpsPlayAudio = mHasAppOpsPlayAudio;
try {
int mode = AppOpsManager.MODE_IGNORED;
@@ -275,9 +273,10 @@
// AppsOps alters a player's volume; when the restriction changes, reflect it on the actual
// volume used by the player
try {
- if (oldHasAppOpsPlayAudio != mHasAppOpsPlayAudio) {
+ if (oldHasAppOpsPlayAudio != mHasAppOpsPlayAudio ||
+ attributesChanged) {
getService().playerHasOpPlayAudio(mPlayerIId, mHasAppOpsPlayAudio);
- if (mHasAppOpsPlayAudio) {
+ if (!isRestricted_sync()) {
if (DEBUG_APP_OPS) {
Log.v(TAG, "updateAppOpsPlayAudio: unmuting player, vol=" + mLeftVolume
+ "/" + mRightVolume);
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java b/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java
index 8fc9fa6..9fbadee 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java
@@ -131,7 +131,7 @@
}
public long getTotalBytes() {
- return mStats.getCacheBytes() + mStats.getCodeBytes() + mStats.getDataBytes();
+ return mStats.getAppBytes() + mStats.getDataBytes();
}
}
}
\ No newline at end of file
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/StorageStatsSourceTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/StorageStatsSourceTest.java
new file mode 100644
index 0000000..3dabe99
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/StorageStatsSourceTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 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.settingslib.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.usage.StorageStats;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class StorageStatsSourceTest {
+ @Test
+ public void AppStorageStatsImpl_totalCorrectly() {
+ StorageStats storageStats = new StorageStats();
+ storageStats.cacheBytes = 1;
+ storageStats.codeBytes = 10;
+ storageStats.dataBytes = 100;
+ StorageStatsSource.AppStorageStatsImpl stats = new StorageStatsSource.AppStorageStatsImpl(
+ storageStats);
+
+ // Note that this does not double add the cache (111).
+ assertThat(stats.getTotalBytes()).isEqualTo(110);
+ }
+}
diff --git a/packages/SystemUI/res/drawable/recents_low_ram_stack_button_background.xml b/packages/SystemUI/res/drawable/recents_low_ram_stack_button_background.xml
index db2eb3a..bff97f6 100644
--- a/packages/SystemUI/res/drawable/recents_low_ram_stack_button_background.xml
+++ b/packages/SystemUI/res/drawable/recents_low_ram_stack_button_background.xml
@@ -17,6 +17,6 @@
<corners android:radius="@dimen/borderless_button_radius" />
- <solid android:color="#CC000000" />
+ <solid android:color="?attr/clearAllBackgroundColor" />
</shape>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 87f6306..8a1e0b9 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -422,4 +422,14 @@
increase the rate of unintentional unlocks. -->
<bool name="config_lockscreenAntiFalsingClassifierEnabled">true</bool>
+ <!-- Snooze: default notificaiton snooze time. -->
+ <integer name="config_notification_snooze_time_default">60</integer>
+
+ <!-- Snooze: List of snooze values in integer minutes. -->
+ <integer-array name="config_notification_snooze_times">
+ <item>15</item>
+ <item>30</item>
+ <item>60</item>
+ <item>120</item>
+ </integer-array>
</resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 2148c80..e5f8029 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -82,10 +82,10 @@
<!-- Accessibility actions for the notification menu -->
<item type="id" name="action_snooze_undo"/>
- <item type="id" name="action_snooze_15_min"/>
- <item type="id" name="action_snooze_30_min"/>
- <item type="id" name="action_snooze_1_hour"/>
- <item type="id" name="action_snooze_2_hours"/>
+ <item type="id" name="action_snooze_shorter"/>
+ <item type="id" name="action_snooze_short"/>
+ <item type="id" name="action_snooze_long"/>
+ <item type="id" name="action_snooze_longer"/>
<item type="id" name="action_snooze_assistant_suggestion_1"/>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 4a11fd05..f38420e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -203,10 +203,6 @@
mStackButtonShadowDistance.x, mStackButtonShadowDistance.y,
mStackButtonShadowColor);
}
- if (Recents.getConfiguration().isLowRamDevice) {
- int bgColor = Utils.getColorAttr(mContext, R.attr.clearAllBackgroundColor);
- mStackActionButton.setBackgroundColor(bgColor);
- }
}
// Let's also require dark status and nav bars if the text is dark
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
index c45ca54..492ab44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
@@ -16,8 +16,13 @@
*/
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.TimeUnit;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
@@ -28,12 +33,15 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Typeface;
+import android.metrics.LogMaker;
import android.os.Bundle;
+import android.provider.Settings;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.text.SpannableString;
import android.text.style.StyleSpan;
import android.util.AttributeSet;
+import android.util.KeyValueListParser;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -51,11 +59,23 @@
public class NotificationSnooze extends LinearLayout
implements NotificationGuts.GutsContent, View.OnClickListener {
+ private static final String TAG = "NotificationSnooze";
/**
* If this changes more number increases, more assistant action resId's should be defined for
* accessibility purposes, see {@link #setSnoozeOptions(List)}
*/
private static final int MAX_ASSISTANT_SUGGESTIONS = 1;
+ private static final String KEY_DEFAULT_SNOOZE = "default";
+ private static final String KEY_OPTIONS = "options_array";
+ private static final LogMaker OPTIONS_OPEN_LOG =
+ new LogMaker(MetricsEvent.NOTIFICATION_SNOOZE_OPTIONS)
+ .setType(MetricsEvent.TYPE_OPEN);
+ private static final LogMaker OPTIONS_CLOSE_LOG =
+ new LogMaker(MetricsEvent.NOTIFICATION_SNOOZE_OPTIONS)
+ .setType(MetricsEvent.TYPE_CLOSE);
+ private static final LogMaker UNDO_LOG =
+ new LogMaker(MetricsEvent.NOTIFICATION_UNDO_SNOOZE)
+ .setType(MetricsEvent.TYPE_ACTION);
private NotificationGuts mGutsContainer;
private NotificationSwipeActionHelper mSnoozeListener;
private StatusBarNotification mSbn;
@@ -72,9 +92,31 @@
private boolean mSnoozing;
private boolean mExpanded;
private AnimatorSet mExpandAnimation;
+ private KeyValueListParser mParser;
+
+ private final static int[] sAccessibilityActions = {
+ R.id.action_snooze_shorter,
+ R.id.action_snooze_short,
+ R.id.action_snooze_long,
+ R.id.action_snooze_longer,
+ };
+
+ private MetricsLogger mMetricsLogger = new MetricsLogger();
public NotificationSnooze(Context context, AttributeSet attrs) {
super(context, attrs);
+ mParser = new KeyValueListParser(',');
+ }
+
+ @VisibleForTesting
+ SnoozeOption getDefaultOption()
+ {
+ return mDefaultOption;
+ }
+
+ @VisibleForTesting
+ void setKeyValueListParser(KeyValueListParser parser) {
+ mParser = parser;
}
@Override
@@ -96,7 +138,13 @@
mSnoozeOptions = getDefaultSnoozeOptions();
createOptionViews();
- setSelected(mDefaultOption);
+ setSelected(mDefaultOption, false);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ logOptionSelection(MetricsEvent.NOTIFICATION_SNOOZE_CLICKED, mDefaultOption);
}
@Override
@@ -136,7 +184,7 @@
SnoozeOption so = mSnoozeOptions.get(i);
if (so.getAccessibilityAction() != null
&& so.getAccessibilityAction().getId() == action) {
- setSelected(so);
+ setSelected(so, true);
return true;
}
}
@@ -172,17 +220,49 @@
mSbn = sbn;
}
- private ArrayList<SnoozeOption> getDefaultSnoozeOptions() {
+ @VisibleForTesting
+ ArrayList<SnoozeOption> getDefaultSnoozeOptions() {
+ final Resources resources = getContext().getResources();
ArrayList<SnoozeOption> options = new ArrayList<>();
+ try {
+ final String config = Settings.Global.getString(getContext().getContentResolver(),
+ Settings.Global.NOTIFICATION_SNOOZE_OPTIONS);
+ mParser.setString(config);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Bad snooze constants");
+ }
- options.add(createOption(15 /* minutes */, R.id.action_snooze_15_min));
- options.add(createOption(30 /* minutes */, R.id.action_snooze_30_min));
- mDefaultOption = createOption(60 /* minutes */, R.id.action_snooze_1_hour);
- options.add(mDefaultOption);
- options.add(createOption(60 * 2 /* minutes */, R.id.action_snooze_2_hours));
+ final int defaultSnooze = mParser.getInt(KEY_DEFAULT_SNOOZE,
+ resources.getInteger(R.integer.config_notification_snooze_time_default));
+ final int[] snoozeTimes = parseIntArray(KEY_OPTIONS,
+ resources.getIntArray(R.array.config_notification_snooze_times));
+
+ for (int i = 0; i < snoozeTimes.length && i < sAccessibilityActions.length; i++) {
+ int snoozeTime = snoozeTimes[i];
+ SnoozeOption option = createOption(snoozeTime, sAccessibilityActions[i]);
+ if (i == 0 || snoozeTime == defaultSnooze) {
+ mDefaultOption = option;
+ }
+ options.add(option);
+ }
return options;
}
+ @VisibleForTesting
+ int[] parseIntArray(final String key, final int[] defaultArray) {
+ final String value = mParser.getString(key, null);
+ if (value != null) {
+ try {
+ return Arrays.stream(value.split(":")).map(String::trim).mapToInt(
+ Integer::parseInt).toArray();
+ } catch (NumberFormatException e) {
+ return defaultArray;
+ }
+ } else {
+ return defaultArray;
+ }
+ }
+
private SnoozeOption createOption(int minutes, int accessibilityActionId) {
Resources res = getResources();
boolean showInHours = minutes >= 60;
@@ -268,12 +348,24 @@
mExpandAnimation.start();
}
- private void setSelected(SnoozeOption option) {
+ private void setSelected(SnoozeOption option, boolean userAction) {
mSelectedOption = option;
mSelectedOptionText.setText(option.getConfirmation());
showSnoozeOptions(false);
hideSelectedOption();
sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ if (userAction) {
+ logOptionSelection(MetricsEvent.NOTIFICATION_SELECT_SNOOZE, option);
+ }
+ }
+
+ private void logOptionSelection(int category, SnoozeOption option) {
+ int index = mSnoozeOptions.indexOf(option);
+ long duration = TimeUnit.MINUTES.toMillis(option.getMinutesToSnoozeFor());
+ mMetricsLogger.write(new LogMaker(category)
+ .setType(MetricsEvent.TYPE_ACTION)
+ .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_SNOOZE_INDEX, index)
+ .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_SNOOZE_DURATION_MS, duration));
}
@Override
@@ -284,13 +376,15 @@
final int id = v.getId();
final SnoozeOption tag = (SnoozeOption) v.getTag();
if (tag != null) {
- setSelected(tag);
+ setSelected(tag, true);
} else if (id == R.id.notification_snooze) {
// Toggle snooze options
showSnoozeOptions(!mExpanded);
+ mMetricsLogger.write(!mExpanded ? OPTIONS_OPEN_LOG : OPTIONS_CLOSE_LOG);
} else {
// Undo snooze was selected
undoSnooze(v);
+ mMetricsLogger.write(UNDO_LOG);
}
}
@@ -321,7 +415,7 @@
@Override
public View getContentView() {
// Reset the view before use
- setSelected(mDefaultOption);
+ setSelected(mDefaultOption, false);
return this;
}
@@ -343,7 +437,7 @@
return true;
} else {
// The view should actually be closed
- setSelected(mSnoozeOptions.get(0));
+ setSelected(mSnoozeOptions.get(0), false);
return false; // Return false here so that guts handles closing the view
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationSnoozeTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationSnoozeTest.java
new file mode 100644
index 0000000..6b31c96
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationSnoozeTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2017 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.provider.Settings;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableResources;
+import android.testing.UiThreadTest;
+import android.util.KeyValueListParser;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@UiThreadTest
+public class NotificationSnoozeTest extends SysuiTestCase {
+ private static final int RES_DEFAULT = 2;
+ private static final int[] RES_OPTIONS = {1, 2, 3};
+ private NotificationSnooze mNotificationSnooze;
+ private KeyValueListParser mMockParser;
+
+ @Before
+ public void setUp() throws Exception {
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.NOTIFICATION_SNOOZE_OPTIONS, null);
+ TestableResources resources = mContext.getOrCreateTestableResources();
+ resources.addOverride(R.integer.config_notification_snooze_time_default, RES_DEFAULT);
+ resources.addOverride(R.array.config_notification_snooze_times, RES_OPTIONS);
+ mNotificationSnooze = new NotificationSnooze(mContext, null);
+ mMockParser = mock(KeyValueListParser.class);
+ }
+
+ @Test
+ public void testParseIntArrayNull() throws Exception {
+ when(mMockParser.getString(anyString(), isNull())).thenReturn(null);
+ mNotificationSnooze.setKeyValueListParser(mMockParser);
+
+ int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS);
+ assertEquals(RES_OPTIONS, result);
+ }
+
+ @Test
+ public void testParseIntArrayLeadingSep() throws Exception {
+ when(mMockParser.getString(anyString(), isNull())).thenReturn(":4:5:6");
+ mNotificationSnooze.setKeyValueListParser(mMockParser);
+
+ int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS);
+ assertEquals(RES_OPTIONS, result);
+ }
+
+ @Test
+ public void testParseIntArrayEmptyItem() throws Exception {
+ when(mMockParser.getString(anyString(), isNull())).thenReturn("4::6");
+ mNotificationSnooze.setKeyValueListParser(mMockParser);
+
+ int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS);
+ assertEquals(RES_OPTIONS, result);
+ }
+
+ @Test
+ public void testParseIntArrayTrailingSep() throws Exception {
+ when(mMockParser.getString(anyString(), isNull())).thenReturn("4:5:6:");
+ mNotificationSnooze.setKeyValueListParser(mMockParser);
+
+ int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS);
+ assertEquals(3, result.length);
+ assertEquals(4, result[0]); // respect order
+ assertEquals(5, result[1]);
+ assertEquals(6, result[2]);
+ }
+
+ @Test
+ public void testParseIntArrayGoodData() throws Exception {
+ when(mMockParser.getString(anyString(), isNull())).thenReturn("4:5:6");
+ mNotificationSnooze.setKeyValueListParser(mMockParser);
+
+ int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS);
+ assertEquals(3, result.length);
+ assertEquals(4, result[0]); // respect order
+ assertEquals(5, result[1]);
+ assertEquals(6, result[2]);
+ }
+
+ @Test
+ public void testGetOptionsWithNoConfig() throws Exception {
+ ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+ assertEquals(3, result.size());
+ assertEquals(1, result.get(0).getMinutesToSnoozeFor()); // respect order
+ assertEquals(2, result.get(1).getMinutesToSnoozeFor());
+ assertEquals(3, result.get(2).getMinutesToSnoozeFor());
+ assertEquals(2, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor());
+ }
+
+ @Test
+ public void testGetOptionsWithInvalidConfig() throws Exception {
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
+ "this is garbage");
+ ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+ assertEquals(3, result.size());
+ assertEquals(1, result.get(0).getMinutesToSnoozeFor()); // respect order
+ assertEquals(2, result.get(1).getMinutesToSnoozeFor());
+ assertEquals(3, result.get(2).getMinutesToSnoozeFor());
+ assertEquals(2, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor());
+ }
+
+ @Test
+ public void testGetOptionsWithValidDefault() throws Exception {
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
+ "default=10,options_array=4:5:6:7");
+ ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+ assertNotNull(mNotificationSnooze.getDefaultOption()); // pick one
+ }
+
+ @Test
+ public void testGetOptionsWithValidConfig() throws Exception {
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
+ "default=6,options_array=4:5:6:7");
+ ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+ assertEquals(4, result.size());
+ assertEquals(4, result.get(0).getMinutesToSnoozeFor()); // respect order
+ assertEquals(5, result.get(1).getMinutesToSnoozeFor());
+ assertEquals(6, result.get(2).getMinutesToSnoozeFor());
+ assertEquals(7, result.get(3).getMinutesToSnoozeFor());
+ assertEquals(6, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor());
+ }
+
+ @Test
+ public void testGetOptionsWithLongConfig() throws Exception {
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
+ "default=6,options_array=4:5:6:7:8:9:10:11:12:13:14:15:16:17");
+ ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+ assertTrue(result.size() > 3);
+ assertEquals(4, result.get(0).getMinutesToSnoozeFor()); // respect order
+ assertEquals(5, result.get(1).getMinutesToSnoozeFor());
+ assertEquals(6, result.get(2).getMinutesToSnoozeFor());
+ }
+}
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 38272a0..48f3b9c 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -4538,6 +4538,30 @@
// OS: O MR
AUTOFILL_UI_LATENCY = 1136;
+ // Action: the snooze leave-behind was shown after the user clicked the snooze icon
+ // OS: O MR
+ NOTIFICATION_SNOOZE_CLICKED = 1137;
+
+ // Action: user selected a notification snooze duration from the drop down
+ // OS: O MR
+ NOTIFICATION_SELECT_SNOOZE = 1138;
+
+ // attached to NOTIFICATION_SNOOZED and NOTIFICATION_SELECT_SNOOZE events
+ // OS: O MR
+ FIELD_NOTIFICATION_SNOOZE_DURATION_MS = 1139;
+
+ // attached to NOTIFICATION_SELECT_SNOOZE events to indicate the option selected
+ // OS: O MR
+ FIELD_NOTIFICATION_SNOOZE_INDEX = 1140;
+
+ // Action: user tapped undo on the notification snooze leave-behind
+ // OS: O MR
+ NOTIFICATION_UNDO_SNOOZE = 1141;
+
+ // Action: user togged the visibility of the notification snooze options drop down
+ // OS: O MR
+ NOTIFICATION_SNOOZE_OPTIONS = 1142;
+
// ---- End O-MR1 Constants, all O-MR1 constants go above this line ----
// Add new aosp constants above this line.
diff --git a/services/core/java/com/android/server/TextServicesManagerService.java b/services/core/java/com/android/server/TextServicesManagerService.java
index c297010..bf6fdf9 100644
--- a/services/core/java/com/android/server/TextServicesManagerService.java
+++ b/services/core/java/com/android/server/TextServicesManagerService.java
@@ -186,7 +186,7 @@
buildSpellCheckerMapLocked(mContext, mSpellCheckerList, mSpellCheckerMap, mSettings);
SpellCheckerInfo sci = getCurrentSpellChecker(null);
if (sci == null) {
- sci = findAvailSpellCheckerLocked(null);
+ sci = findAvailSystemSpellCheckerLocked(null);
if (sci != null) {
// Set the current spell checker if there is one or more spell checkers
// available. In this case, "sci" is the first one in the available spell
@@ -230,7 +230,7 @@
change == PACKAGE_PERMANENT_CHANGE || change == PACKAGE_TEMPORARY_CHANGE
// Package modified
|| isPackageModified(packageName)) {
- SpellCheckerInfo availSci = findAvailSpellCheckerLocked(packageName);
+ SpellCheckerInfo availSci = findAvailSystemSpellCheckerLocked(packageName);
// Set the spell checker settings if different than before
if (availSci != null && !availSci.getId().equals(sci.getId())) {
setCurrentSpellCheckerLocked(availSci);
@@ -375,18 +375,26 @@
mSpellCheckerBindGroups.clear();
}
- private SpellCheckerInfo findAvailSpellCheckerLocked(String prefPackage) {
- final int spellCheckersCount = mSpellCheckerList.size();
+ private SpellCheckerInfo findAvailSystemSpellCheckerLocked(String prefPackage) {
+ // Filter the spell checker list to remove spell checker services that are not pre-installed
+ ArrayList<SpellCheckerInfo> spellCheckerList = new ArrayList<>();
+ for (SpellCheckerInfo sci : mSpellCheckerList) {
+ if ((sci.getServiceInfo().applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ spellCheckerList.add(sci);
+ }
+ }
+
+ final int spellCheckersCount = spellCheckerList.size();
if (spellCheckersCount == 0) {
Slog.w(TAG, "no available spell checker services found");
return null;
}
if (prefPackage != null) {
for (int i = 0; i < spellCheckersCount; ++i) {
- final SpellCheckerInfo sci = mSpellCheckerList.get(i);
+ final SpellCheckerInfo sci = spellCheckerList.get(i);
if (prefPackage.equals(sci.getPackageName())) {
if (DBG) {
- Slog.d(TAG, "findAvailSpellCheckerLocked: " + sci.getPackageName());
+ Slog.d(TAG, "findAvailSystemSpellCheckerLocked: " + sci.getPackageName());
}
return sci;
}
@@ -400,7 +408,7 @@
final ArrayList<Locale> suitableLocales =
InputMethodUtils.getSuitableLocalesForSpellChecker(systemLocal);
if (DBG) {
- Slog.w(TAG, "findAvailSpellCheckerLocked suitableLocales="
+ Slog.w(TAG, "findAvailSystemSpellCheckerLocked suitableLocales="
+ Arrays.toString(suitableLocales.toArray(new Locale[suitableLocales.size()])));
}
final int localeCount = suitableLocales.size();
@@ -408,7 +416,7 @@
final Locale locale = suitableLocales.get(localeIndex);
for (int spellCheckersIndex = 0; spellCheckersIndex < spellCheckersCount;
++spellCheckersIndex) {
- final SpellCheckerInfo info = mSpellCheckerList.get(spellCheckersIndex);
+ final SpellCheckerInfo info = spellCheckerList.get(spellCheckersIndex);
final int subtypeCount = info.getSubtypeCount();
for (int subtypeIndex = 0; subtypeIndex < subtypeCount; ++subtypeIndex) {
final SpellCheckerSubtype subtype = info.getSubtypeAt(subtypeIndex);
@@ -427,7 +435,7 @@
if (spellCheckersCount > 1) {
Slog.w(TAG, "more than one spell checker service found, picking first");
}
- return mSpellCheckerList.get(0);
+ return spellCheckerList.get(0);
}
// TODO: Save SpellCheckerService by supported languages. Currently only one spell
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 027dc08..ac85e6b 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -517,11 +517,14 @@
} catch (PackageManager.NameNotFoundException e) {
}
}
- if (localForegroundNoti.getSmallIcon() == null) {
+ if (localForegroundNoti.getSmallIcon() == null
+ || nm.getNotificationChannel(localPackageName, appUid,
+ localForegroundNoti.getChannelId()) == null) {
// Notifications whose icon is 0 are defined to not show
// a notification, silently ignoring it. We don't want to
// just ignore it, we want to prevent the service from
// being foreground.
+ // Also every notification needs a channel.
throw new RuntimeException("invalid service notification: "
+ foregroundNoti);
}
diff --git a/services/core/java/com/android/server/audio/AudioEventLogger.java b/services/core/java/com/android/server/audio/AudioEventLogger.java
index c96138f..a2c0f76 100644
--- a/services/core/java/com/android/server/audio/AudioEventLogger.java
+++ b/services/core/java/com/android/server/audio/AudioEventLogger.java
@@ -16,6 +16,8 @@
package com.android.server.audio;
+import android.util.Log;
+
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -47,6 +49,22 @@
}
/**
+ * Causes the string message for the event to appear in the verbose logcat.
+ * Here is an example of how to create a new event (a StringEvent), adding it to the logger
+ * (an instance of AudioEventLogger) while also making it show in the verbose logcat:
+ * <pre>
+ * myLogger.log(
+ * (new StringEvent("something for logcat and logger")).printLog(MyClass.TAG) );
+ * </pre>
+ * @param tag the tag for the android.util.Log.v
+ * @return the same instance of the event
+ */
+ public Event printLog(String tag) {
+ Log.v(tag, eventToString());
+ return this;
+ }
+
+ /**
* Convert event to String.
* This method is only called when the logger history is about to the dumped,
* so this method is where expensive String conversions should be made, not when the Event
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0bc20a2e..5eb2a8d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -751,6 +751,9 @@
// relies on audio policy having correct ranges for volume indexes.
mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
+ mPlaybackMonitor =
+ new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);
+
mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
mRecordMonitor = new RecordingActivityMonitor(mContext);
@@ -4158,7 +4161,8 @@
newDevice, AudioSystem.getOutputDeviceName(newDevice)));
}
synchronized (mConnectedDevices) {
- if ((newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0
+ if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
+ && (newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0
&& mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted
&& mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0
&& (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0)
@@ -6976,7 +6980,7 @@
//======================
// Audio playback notification
//======================
- private final PlaybackActivityMonitor mPlaybackMonitor = new PlaybackActivityMonitor();
+ private final PlaybackActivityMonitor mPlaybackMonitor;
public void registerPlaybackCallback(IPlaybackConfigDispatcher pcdb) {
final boolean isPrivileged =
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index d1a37af..6506cf7 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -17,6 +17,8 @@
package com.android.server.audio;
import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
@@ -90,7 +92,14 @@
private final HashMap<Integer, AudioPlaybackConfiguration> mPlayers =
new HashMap<Integer, AudioPlaybackConfiguration>();
- PlaybackActivityMonitor() {
+ private final Context mContext;
+ private int mSavedAlarmVolume = -1;
+ private final int mMaxAlarmVolume;
+ private int mPrivilegedAlarmActiveCount = 0;
+
+ PlaybackActivityMonitor(Context context, int maxAlarmVolume) {
+ mContext = context;
+ mMaxAlarmVolume = maxAlarmVolume;
PlayMonitorClient.sListenerDeathMonitor = this;
AudioPlaybackConfiguration.sPlayerDeathMonitor = this;
}
@@ -105,7 +114,7 @@
if (index >= 0) {
if (!disable) {
if (DEBUG) { // hidden behind DEBUG, too noisy otherwise
- mEventLogger.log(new AudioEventLogger.StringEvent("unbanning uid:" + uid));
+ sEventLogger.log(new AudioEventLogger.StringEvent("unbanning uid:" + uid));
}
mBannedUids.remove(index);
// nothing else to do, future playback requests from this uid are ok
@@ -116,7 +125,7 @@
checkBanPlayer(apc, uid);
}
if (DEBUG) { // hidden behind DEBUG, too noisy otherwise
- mEventLogger.log(new AudioEventLogger.StringEvent("banning uid:" + uid));
+ sEventLogger.log(new AudioEventLogger.StringEvent("banning uid:" + uid));
}
mBannedUids.add(new Integer(uid));
} // no else to handle, uid already not in list, so enabling again is no-op
@@ -151,7 +160,7 @@
new AudioPlaybackConfiguration(pic, newPiid,
Binder.getCallingUid(), Binder.getCallingPid());
apc.init();
- mEventLogger.log(new NewPlayerEvent(apc));
+ sEventLogger.log(new NewPlayerEvent(apc));
synchronized(mPlayerLock) {
mPlayers.put(newPiid, apc);
}
@@ -163,7 +172,7 @@
synchronized(mPlayerLock) {
final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
if (checkConfigurationCaller(piid, apc, binderUid)) {
- mEventLogger.log(new AudioAttrEvent(piid, attr));
+ sEventLogger.log(new AudioAttrEvent(piid, attr));
change = apc.handleAudioAttributesEvent(attr);
} else {
Log.e(TAG, "Error updating audio attributes");
@@ -175,6 +184,38 @@
}
}
+ private void checkVolumeForPrivilegedAlarm(AudioPlaybackConfiguration apc, int event) {
+ if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED ||
+ apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+ if ((apc.getAudioAttributes().getAllFlags() &
+ AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0 &&
+ apc.getAudioAttributes().getUsage() == AudioAttributes.USAGE_ALARM &&
+ mContext.checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE,
+ apc.getClientPid(), apc.getClientUid()) ==
+ PackageManager.PERMISSION_GRANTED) {
+ if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED &&
+ apc.getPlayerState() != AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+ if (mPrivilegedAlarmActiveCount++ == 0) {
+ mSavedAlarmVolume = AudioSystem.getStreamVolumeIndex(
+ AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER);
+ AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM,
+ mMaxAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
+ }
+ } else if (event != AudioPlaybackConfiguration.PLAYER_STATE_STARTED &&
+ apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+ if (--mPrivilegedAlarmActiveCount == 0) {
+ if (AudioSystem.getStreamVolumeIndex(
+ AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER) ==
+ mMaxAlarmVolume) {
+ AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM,
+ mSavedAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
+ }
+ }
+ }
+ }
+ }
+ }
+
public void playerEvent(int piid, int event, int binderUid) {
if (DEBUG) { Log.v(TAG, String.format("playerEvent(piid=%d, event=%d)", piid, event)); }
final boolean change;
@@ -183,12 +224,12 @@
if (apc == null) {
return;
}
- mEventLogger.log(new PlayerEvent(piid, event));
+ sEventLogger.log(new PlayerEvent(piid, event));
if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
for (Integer uidInteger: mBannedUids) {
if (checkBanPlayer(apc, uidInteger.intValue())) {
// player was banned, do not update its state
- mEventLogger.log(new AudioEventLogger.StringEvent(
+ sEventLogger.log(new AudioEventLogger.StringEvent(
"not starting piid:" + piid + " ,is banned"));
return;
}
@@ -200,6 +241,7 @@
}
if (checkConfigurationCaller(piid, apc, binderUid)) {
//TODO add generation counter to only update to the latest state
+ checkVolumeForPrivilegedAlarm(apc, event);
change = apc.handleStateEvent(event);
} else {
Log.e(TAG, "Error handling event " + event);
@@ -216,7 +258,7 @@
public void playerHasOpPlayAudio(int piid, boolean hasOpPlayAudio, int binderUid) {
// no check on UID yet because this is only for logging at the moment
- mEventLogger.log(new PlayerOpPlayAudioEvent(piid, hasOpPlayAudio, binderUid));
+ sEventLogger.log(new PlayerOpPlayAudioEvent(piid, hasOpPlayAudio, binderUid));
}
public void releasePlayer(int piid, int binderUid) {
@@ -224,10 +266,11 @@
synchronized(mPlayerLock) {
final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
if (checkConfigurationCaller(piid, apc, binderUid)) {
- mEventLogger.log(new AudioEventLogger.StringEvent(
+ sEventLogger.log(new AudioEventLogger.StringEvent(
"releasing player piid:" + piid));
mPlayers.remove(new Integer(piid));
mDuckingManager.removeReleased(apc);
+ checkVolumeForPrivilegedAlarm(apc, AudioPlaybackConfiguration.PLAYER_STATE_RELEASED);
apc.handleStateEvent(AudioPlaybackConfiguration.PLAYER_STATE_RELEASED);
}
}
@@ -278,7 +321,7 @@
}
pw.println("\n");
// log
- mEventLogger.dump(pw);
+ sEventLogger.dump(pw);
}
}
@@ -456,7 +499,8 @@
}
if (mute) {
try {
- Log.v(TAG, "call: muting player" + piid + " uid:" + apc.getClientUid());
+ sEventLogger.log((new AudioEventLogger.StringEvent("call: muting piid:"
+ + piid + " uid:" + apc.getClientUid())).printLog(TAG));
apc.getPlayerProxy().setVolume(0.0f);
mMutedPlayers.add(new Integer(piid));
} catch (Exception e) {
@@ -480,7 +524,8 @@
final AudioPlaybackConfiguration apc = mPlayers.get(piid);
if (apc != null) {
try {
- Log.v(TAG, "call: unmuting player" + piid + " uid:" + apc.getClientUid());
+ sEventLogger.log(new AudioEventLogger.StringEvent("call: unmuting piid:"
+ + piid).printLog(TAG));
apc.getPlayerProxy().setVolume(1.0f);
} catch (Exception e) {
Log.e(TAG, "call: error unmuting player " + piid + " uid:"
@@ -669,8 +714,7 @@
return;
}
try {
- Log.v(TAG, "ducking (skipRamp=" + skipRamp + ") player piid:"
- + apc.getPlayerInterfaceId() + " uid:" + mUid);
+ sEventLogger.log((new DuckEvent(apc, skipRamp)).printLog(TAG));
apc.getPlayerProxy().applyVolumeShaper(
DUCK_VSHAPE,
skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
@@ -685,7 +729,8 @@
final AudioPlaybackConfiguration apc = players.get(piid);
if (apc != null) {
try {
- Log.v(TAG, "unducking player " + piid + " uid:" + mUid);
+ sEventLogger.log((new AudioEventLogger.StringEvent("unducking piid:"
+ + piid)).printLog(TAG));
apc.getPlayerProxy().applyVolumeShaper(
DUCK_ID,
VolumeShaper.Operation.REVERSE);
@@ -772,7 +817,28 @@
}
}
- private final static class AudioAttrEvent extends AudioEventLogger.Event {
+ private static final class DuckEvent extends AudioEventLogger.Event {
+ private final int mPlayerIId;
+ private final boolean mSkipRamp;
+ private final int mClientUid;
+ private final int mClientPid;
+
+ DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
+ mPlayerIId = apc.getPlayerInterfaceId();
+ mSkipRamp = skipRamp;
+ mClientUid = apc.getClientUid();
+ mClientPid = apc.getClientPid();
+ }
+
+ @Override
+ public String eventToString() {
+ return new StringBuilder("ducking player piid:").append(mPlayerIId)
+ .append(" uid/pid:").append(mClientUid).append("/").append(mClientPid)
+ .append(" skip ramp:").append(mSkipRamp).toString();
+ }
+ }
+
+ private static final class AudioAttrEvent extends AudioEventLogger.Event {
private final int mPlayerIId;
private final AudioAttributes mPlayerAttr;
@@ -787,6 +853,6 @@
}
}
- private final AudioEventLogger mEventLogger = new AudioEventLogger(100,
+ private static final AudioEventLogger sEventLogger = new AudioEventLogger(100,
"playback activity as reported through PlayerBase");
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 9fd54ec..5159c70 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -332,7 +332,6 @@
private static final int MSG_UPDATE_INTERFACE_QUOTA = 10;
private static final int MSG_REMOVE_INTERFACE_QUOTA = 11;
private static final int MSG_POLICIES_CHANGED = 13;
- private static final int MSG_SET_FIREWALL_RULES = 14;
private static final int MSG_RESET_FIREWALL_RULES_BY_UID = 15;
private static final int UID_MSG_STATE_CHANGED = 100;
@@ -3184,9 +3183,9 @@
uidRules.put(mUidState.keyAt(i), FIREWALL_RULE_ALLOW);
}
}
- setUidFirewallRulesAsync(chain, uidRules, CHAIN_TOGGLE_ENABLE);
+ setUidFirewallRulesUL(chain, uidRules, CHAIN_TOGGLE_ENABLE);
} else {
- setUidFirewallRulesAsync(chain, null, CHAIN_TOGGLE_DISABLE);
+ setUidFirewallRulesUL(chain, null, CHAIN_TOGGLE_DISABLE);
}
}
@@ -3253,7 +3252,7 @@
}
}
- setUidFirewallRulesAsync(FIREWALL_CHAIN_STANDBY, uidRules, CHAIN_TOGGLE_NONE);
+ setUidFirewallRulesUL(FIREWALL_CHAIN_STANDBY, uidRules, CHAIN_TOGGLE_NONE);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
@@ -3954,18 +3953,6 @@
removeInterfaceQuota((String) msg.obj);
return true;
}
- case MSG_SET_FIREWALL_RULES: {
- final int chain = msg.arg1;
- final int toggle = msg.arg2;
- final SparseIntArray uidRules = (SparseIntArray) msg.obj;
- if (uidRules != null) {
- setUidFirewallRules(chain, uidRules);
- }
- if (toggle != CHAIN_TOGGLE_NONE) {
- enableFirewallChainUL(chain, toggle == CHAIN_TOGGLE_ENABLE);
- }
- return true;
- }
case MSG_RESET_FIREWALL_RULES_BY_UID: {
resetUidFirewallRules(msg.arg1);
return true;
@@ -4111,15 +4098,20 @@
/**
* Calls {@link #setUidFirewallRules(int, SparseIntArray)} and
- * {@link #enableFirewallChainUL(int, boolean)} asynchronously.
+ * {@link #enableFirewallChainUL(int, boolean)} synchronously.
*
* @param chain firewall chain.
* @param uidRules new UID rules; if {@code null}, only toggles chain state.
* @param toggle whether the chain should be enabled, disabled, or not changed.
*/
- private void setUidFirewallRulesAsync(int chain, @Nullable SparseIntArray uidRules,
+ private void setUidFirewallRulesUL(int chain, @Nullable SparseIntArray uidRules,
@ChainToggleType int toggle) {
- mHandler.obtainMessage(MSG_SET_FIREWALL_RULES, chain, toggle, uidRules).sendToTarget();
+ if (uidRules != null) {
+ setUidFirewallRulesUL(chain, uidRules);
+ }
+ if (toggle != CHAIN_TOGGLE_NONE) {
+ enableFirewallChainUL(chain, toggle == CHAIN_TOGGLE_ENABLE);
+ }
}
/**
@@ -4127,7 +4119,7 @@
* here to netd. It will clean up dead rules and make sure the target chain only contains rules
* specified here.
*/
- private void setUidFirewallRules(int chain, SparseIntArray uidRules) {
+ private void setUidFirewallRulesUL(int chain, SparseIntArray uidRules) {
try {
int size = uidRules.size();
int[] uids = new int[size];
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index 4923b06..f1476b3 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -17,8 +17,10 @@
package com.android.server.notification;
import android.app.Notification;
+import android.app.NotificationChannel;
public interface NotificationManagerInternal {
+ NotificationChannel getNotificationChannel(String pkg, int uid, String channelId);
void enqueueNotification(String pkg, String basePkg, int callingUid, int callingPid,
String tag, int id, Notification notification, int userId);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c6f2e8a..4e46535 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3373,6 +3373,12 @@
*/
private final NotificationManagerInternal mInternalService = new NotificationManagerInternal() {
@Override
+ public NotificationChannel getNotificationChannel(String pkg, int uid, String
+ channelId) {
+ return mRankingHelper.getNotificationChannel(pkg, uid, channelId, false);
+ }
+
+ @Override
public void enqueueNotification(String pkg, String opPkg, int callingUid, int callingPid,
String tag, int id, Notification notification, int userId) {
enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification,
@@ -3741,6 +3747,8 @@
MetricsLogger.action(r.getLogMaker()
.setCategory(MetricsEvent.NOTIFICATION_SNOOZED)
.setType(MetricsEvent.TYPE_CLOSE)
+ .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_SNOOZE_DURATION_MS,
+ mDuration)
.addTaggedData(MetricsEvent.NOTIFICATION_SNOOZED_CRITERIA,
mSnoozeCriterionId == null ? 0 : 1));
boolean wasPosted = removeFromNotificationListsLocked(r);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 37695cb..ba99f38 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -950,10 +950,10 @@
final int lastOrientation = mLastOrientation;
final boolean oldAltOrientation = mAltOrientation;
int rotation = mService.mPolicy.rotationForOrientationLw(lastOrientation, oldRotation);
- final boolean rotateSeamlessly = mService.mPolicy.shouldRotateSeamlessly(oldRotation,
+ boolean mayRotateSeamlessly = mService.mPolicy.shouldRotateSeamlessly(oldRotation,
rotation);
- if (rotateSeamlessly) {
+ if (mayRotateSeamlessly) {
final WindowState seamlessRotated = getWindow((w) -> w.mSeamlesslyRotated);
if (seamlessRotated != null) {
// We can't rotate (seamlessly or not) while waiting for the last seamless rotation
@@ -962,7 +962,20 @@
// window-removal.
return false;
}
+
+ // In the presence of the PINNED stack or System Alert
+ // windows we unforuntately can not seamlessly rotate.
+ if (getStackById(PINNED_STACK_ID) != null) {
+ mayRotateSeamlessly = false;
+ }
+ for (int i = 0; i < mService.mSessions.size(); i++) {
+ if (mService.mSessions.valueAt(i).hasAlertWindowSurfaces()) {
+ mayRotateSeamlessly = false;
+ break;
+ }
+ }
}
+ final boolean rotateSeamlessly = mayRotateSeamlessly;
// TODO: Implement forced rotation changes.
// Set mAltOrientation to indicate that the application is receiving
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 1781247..4dd147e 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -719,4 +719,8 @@
public String toString() {
return mStringName;
}
+
+ boolean hasAlertWindowSurfaces() {
+ return !mAlertWindowSurfaces.isEmpty();
+ }
}
diff --git a/telephony/java/android/telephony/MbmsDownloadSession.java b/telephony/java/android/telephony/MbmsDownloadSession.java
index 01ed690..b35a9b8 100644
--- a/telephony/java/android/telephony/MbmsDownloadSession.java
+++ b/telephony/java/android/telephony/MbmsDownloadSession.java
@@ -523,8 +523,7 @@
* @param handler The {@link Handler} on which calls to {@code callback} should be enqueued on.
*/
public void registerStateCallback(@NonNull DownloadRequest request,
- @NonNull DownloadStateCallback callback,
- @NonNull Handler handler) {
+ @NonNull DownloadStateCallback callback, @NonNull Handler handler) {
IMbmsDownloadService downloadService = mService.get();
if (downloadService == null) {
throw new IllegalStateException("Middleware not yet bound");
@@ -534,7 +533,8 @@
new InternalDownloadStateCallback(callback, handler);
try {
- int result = downloadService.registerStateCallback(request, internalCallback);
+ int result = downloadService.registerStateCallback(request, internalCallback,
+ callback.getCallbackFilterFlags());
if (result != MbmsErrors.SUCCESS) {
if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
throw new IllegalArgumentException("Unknown download request.");
diff --git a/telephony/java/android/telephony/mbms/DownloadStateCallback.java b/telephony/java/android/telephony/mbms/DownloadStateCallback.java
index 26d6879..cab238e 100644
--- a/telephony/java/android/telephony/mbms/DownloadStateCallback.java
+++ b/telephony/java/android/telephony/mbms/DownloadStateCallback.java
@@ -16,8 +16,12 @@
package android.telephony.mbms;
+import android.annotation.IntDef;
import android.telephony.MbmsDownloadSession;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* A optional listener class used by download clients to track progress. Apps should extend this
* class and pass an instance into
@@ -30,6 +34,71 @@
public class DownloadStateCallback {
/**
+ * Bitmask flags used for filtering out callback methods. Used when constructing the
+ * DownloadStateCallback as an optional parameter.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ALL_UPDATES, PROGRESS_UPDATES, STATE_UPDATES})
+ public @interface FilterFlag {}
+
+ /**
+ * Receive all callbacks.
+ * Default value.
+ */
+ public static final int ALL_UPDATES = 0x00;
+ /**
+ * Receive callbacks for {@link #onProgressUpdated}.
+ */
+ public static final int PROGRESS_UPDATES = 0x01;
+ /**
+ * Receive callbacks for {@link #onStateUpdated}.
+ */
+ public static final int STATE_UPDATES = 0x02;
+
+ private final int mCallbackFilterFlags;
+
+ /**
+ * Creates a DownloadStateCallback that will receive all callbacks.
+ */
+ public DownloadStateCallback() {
+ mCallbackFilterFlags = ALL_UPDATES;
+ }
+
+ /**
+ * Creates a DownloadStateCallback that will only receive callbacks for the methods specified
+ * via the filterFlags parameter.
+ * @param filterFlags A bitmask of filter flags that will specify which callback this instance
+ * is interested in.
+ */
+ public DownloadStateCallback(int filterFlags) {
+ mCallbackFilterFlags = filterFlags;
+ }
+
+ /**
+ * Return the currently set filter flags.
+ * @return An integer containing the bitmask of flags that this instance is interested in.
+ * @hide
+ */
+ public int getCallbackFilterFlags() {
+ return mCallbackFilterFlags;
+ }
+
+ /**
+ * Returns true if a filter flag is set for a particular callback method. If the flag is set,
+ * the callback will be delivered to the listening process.
+ * @param flag A filter flag specifying whether or not a callback method is registered to
+ * receive callbacks.
+ * @return true if registered to receive callbacks in the listening process, false if not.
+ */
+ public final boolean isFilterFlagSet(@FilterFlag int flag) {
+ if (mCallbackFilterFlags == ALL_UPDATES) {
+ return true;
+ }
+ return (mCallbackFilterFlags & flag) > 0;
+ }
+
+ /**
* Called when the middleware wants to report progress for a file in a {@link DownloadRequest}.
*
* @param request a {@link DownloadRequest}, indicating which download is being referenced.
diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
index 93a7cad..ac196b5 100644
--- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -166,6 +166,12 @@
Log.w(LOG_TAG, "Download result did not include a result code. Ignoring.");
return false;
}
+ // We do not need to verify below extras if the result is not success.
+ if (MbmsDownloadSession.RESULT_SUCCESSFUL !=
+ intent.getIntExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT,
+ MbmsDownloadSession.RESULT_CANCELLED)) {
+ return true;
+ }
if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST)) {
Log.w(LOG_TAG, "Download result did not include the associated request. Ignoring.");
return false;
diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.java b/telephony/java/android/telephony/mbms/ServiceInfo.java
index 3b9cc0e..9404c19 100644
--- a/telephony/java/android/telephony/mbms/ServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/ServiceInfo.java
@@ -23,6 +23,7 @@
import android.text.TextUtils;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@@ -63,12 +64,6 @@
throw new RuntimeException("bad locales length " + newLocales.size());
}
- for (Locale l : newLocales) {
- if (!newNames.containsKey(l)) {
- throw new IllegalArgumentException("A name must be provided for each locale");
- }
- }
-
names = new HashMap(newNames.size());
names.putAll(newNames);
className = newClassName;
@@ -128,7 +123,7 @@
* Get the user-displayable name for this cell-broadcast service corresponding to the
* provided {@link Locale}.
* @param locale The {@link Locale} in which you want the name of the service. This must be a
- * value from the list returned by {@link #getLocales()} -- an
+ * value from the set returned by {@link #getNamedContentLocales()} -- an
* {@link java.util.NoSuchElementException} may be thrown otherwise.
* @return The {@link CharSequence} providing the name of the service in the given
* {@link Locale}
@@ -141,6 +136,17 @@
}
/**
+ * Return an unmodifiable set of the current {@link Locale}s that have a user-displayable name
+ * associated with them. The user-displayable name associated with any {@link Locale} in this
+ * set can be retrieved with {@link #getNameForLocale(Locale)}.
+ * @return An unmodifiable set of {@link Locale} objects corresponding to a user-displayable
+ * content name in that locale.
+ */
+ public @NonNull Set<Locale> getNamedContentLocales() {
+ return Collections.unmodifiableSet(names.keySet());
+ }
+
+ /**
* The class name for this service - used to categorize and filter
*/
public String getServiceClassName() {
diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl
index ed5e826..cb93542 100755
--- a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl
+++ b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl
@@ -36,7 +36,8 @@
int download(in DownloadRequest downloadRequest);
- int registerStateCallback(in DownloadRequest downloadRequest, IDownloadStateCallback listener);
+ int registerStateCallback(in DownloadRequest downloadRequest, IDownloadStateCallback listener,
+ int flags);
int unregisterStateCallback(in DownloadRequest downloadRequest,
IDownloadStateCallback listener);
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
index be686ee..c6dbc22 100644
--- a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -46,6 +46,47 @@
private final Map<IBinder, DownloadStateCallback> mDownloadCallbackBinderMap = new HashMap<>();
private final Map<IBinder, DeathRecipient> mDownloadCallbackDeathRecipients = new HashMap<>();
+
+ // Filters the DownloadStateCallbacks by its configuration from the app.
+ private abstract static class FilteredDownloadStateCallback extends DownloadStateCallback {
+
+ private final IDownloadStateCallback mCallback;
+ public FilteredDownloadStateCallback(IDownloadStateCallback callback, int callbackFlags) {
+ super(callbackFlags);
+ mCallback = callback;
+ }
+
+ @Override
+ public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo,
+ int currentDownloadSize, int fullDownloadSize, int currentDecodedSize,
+ int fullDecodedSize) {
+ if (!isFilterFlagSet(PROGRESS_UPDATES)) {
+ return;
+ }
+ try {
+ mCallback.onProgressUpdated(request, fileInfo, currentDownloadSize,
+ fullDownloadSize, currentDecodedSize, fullDecodedSize);
+ } catch (RemoteException e) {
+ onRemoteException(e);
+ }
+ }
+
+ @Override
+ public void onStateUpdated(DownloadRequest request, FileInfo fileInfo,
+ @MbmsDownloadSession.DownloadStatus int state) {
+ if (!isFilterFlagSet(STATE_UPDATES)) {
+ return;
+ }
+ try {
+ mCallback.onStateUpdated(request, fileInfo, state);
+ } catch (RemoteException e) {
+ onRemoteException(e);
+ }
+ }
+
+ protected abstract void onRemoteException(RemoteException e);
+ }
+
/**
* Initialize the download service for this app and subId, registering the listener.
*
@@ -196,9 +237,8 @@
* @hide
*/
@Override
- public final int registerStateCallback(
- final DownloadRequest downloadRequest, final IDownloadStateCallback callback)
- throws RemoteException {
+ public final int registerStateCallback(final DownloadRequest downloadRequest,
+ final IDownloadStateCallback callback, int flags) throws RemoteException {
final int uid = Binder.getCallingUid();
DeathRecipient deathRecipient = new DeathRecipient() {
@Override
@@ -211,28 +251,10 @@
mDownloadCallbackDeathRecipients.put(callback.asBinder(), deathRecipient);
callback.asBinder().linkToDeath(deathRecipient, 0);
- DownloadStateCallback exposedCallback = new DownloadStateCallback() {
+ DownloadStateCallback exposedCallback = new FilteredDownloadStateCallback(callback, flags) {
@Override
- public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo, int
- currentDownloadSize, int fullDownloadSize, int currentDecodedSize, int
- fullDecodedSize) {
- try {
- callback.onProgressUpdated(request, fileInfo, currentDownloadSize,
- fullDownloadSize,
- currentDecodedSize, fullDecodedSize);
- } catch (RemoteException e) {
- onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
- }
- }
-
- @Override
- public void onStateUpdated(DownloadRequest request, FileInfo fileInfo,
- @MbmsDownloadSession.DownloadStatus int state) {
- try {
- callback.onStateUpdated(request, fileInfo, state);
- } catch (RemoteException e) {
- onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
- }
+ protected void onRemoteException(RemoteException e) {
+ onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
}
};
diff --git a/telephony/java/android/telephony/mbms/vendor/VendorUtils.java b/telephony/java/android/telephony/mbms/vendor/VendorUtils.java
index 7bab734..e3abe2c 100644
--- a/telephony/java/android/telephony/mbms/vendor/VendorUtils.java
+++ b/telephony/java/android/telephony/mbms/vendor/VendorUtils.java
@@ -38,8 +38,9 @@
/**
* The MBMS middleware should send this when a download of single file has completed or
- * failed. Mandatory extras are
+ * failed. The only mandatory extra is
* {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_RESULT}
+ * and the following are required when the download has completed:
* {@link MbmsDownloadSession#EXTRA_MBMS_FILE_INFO}
* {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_REQUEST}
* {@link #EXTRA_TEMP_LIST}
diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp
index d7508d2..c139b73 100644
--- a/tools/aapt2/java/ClassDefinition.cpp
+++ b/tools/aapt2/java/ClassDefinition.cpp
@@ -41,18 +41,21 @@
ClassDefinition::Result ClassDefinition::AddMember(std::unique_ptr<ClassMember> member) {
Result result = Result::kAdded;
- auto iter = members_.find(member);
- if (iter != members_.end()) {
- members_.erase(iter);
+ auto iter = indexed_members_.find(member->GetName());
+ if (iter != indexed_members_.end()) {
+ // Overwrite the entry.
+ ordered_members_[iter->second].reset();
result = Result::kOverridden;
}
- members_.insert(std::move(member));
+
+ indexed_members_[member->GetName()] = ordered_members_.size();
+ ordered_members_.push_back(std::move(member));
return result;
}
bool ClassDefinition::empty() const {
- for (const std::unique_ptr<ClassMember>& member : members_) {
- if (!member->empty()) {
+ for (const std::unique_ptr<ClassMember>& member : ordered_members_) {
+ if (member != nullptr && !member->empty()) {
return false;
}
}
@@ -61,7 +64,7 @@
void ClassDefinition::WriteToStream(const StringPiece& prefix, bool final,
std::ostream* out) const {
- if (members_.empty() && !create_if_empty_) {
+ if (empty() && !create_if_empty_) {
return;
}
@@ -76,9 +79,14 @@
std::string new_prefix = prefix.to_string();
new_prefix.append(kIndent);
- for (const std::unique_ptr<ClassMember>& member : members_) {
- member->WriteToStream(new_prefix, final, out);
- *out << "\n";
+ for (const std::unique_ptr<ClassMember>& member : ordered_members_) {
+ // There can be nullptr members when a member is added to the ClassDefinition
+ // and takes precedence over a previous member with the same name. The overridden member is
+ // set to nullptr.
+ if (member != nullptr) {
+ member->WriteToStream(new_prefix, final, out);
+ *out << "\n";
+ }
}
*out << prefix << "}";
diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h
index 6c4bcad..28a3489 100644
--- a/tools/aapt2/java/ClassDefinition.h
+++ b/tools/aapt2/java/ClassDefinition.h
@@ -18,8 +18,9 @@
#define AAPT_JAVA_CLASSDEFINITION_H
#include <ostream>
-#include <set>
#include <string>
+#include <unordered_map>
+#include <vector>
#include "android-base/macros.h"
#include "androidfw/StringPiece.h"
@@ -73,21 +74,18 @@
void WriteToStream(const android::StringPiece& prefix, bool final,
std::ostream* out) const override {
ClassMember::WriteToStream(prefix, final, out);
-
- *out << prefix << "public static " << (final ? "final " : "") << "int "
- << name_ << "=" << val_ << ";";
+ *out << prefix << "public static " << (final ? "final " : "") << "int " << name_ << "=" << val_
+ << ";";
}
private:
+ DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
+
std::string name_;
T val_;
-
- DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
};
-/**
- * Specialization for strings so they get the right type and are quoted with "".
- */
+// Specialization for strings so they get the right type and are quoted with "".
template <>
class PrimitiveMember<std::string> : public ClassMember {
public:
@@ -111,10 +109,10 @@
}
private:
+ DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
+
std::string name_;
std::string val_;
-
- DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
};
using IntMember = PrimitiveMember<uint32_t>;
@@ -160,10 +158,10 @@
}
private:
+ DISALLOW_COPY_AND_ASSIGN(PrimitiveArrayMember);
+
std::string name_;
std::vector<T> elements_;
-
- DISALLOW_COPY_AND_ASSIGN(PrimitiveArrayMember);
};
using ResourceArrayMember = PrimitiveArrayMember<ResourceId>;
@@ -185,12 +183,16 @@
}
// Even if the method is empty, we always want to write the method signature.
- bool empty() const override { return false; }
+ bool empty() const override {
+ return false;
+ }
void WriteToStream(const android::StringPiece& prefix, bool final,
std::ostream* out) const override;
private:
+ DISALLOW_COPY_AND_ASSIGN(MethodDefinition);
+
std::string signature_;
std::vector<std::string> statements_;
};
@@ -222,19 +224,13 @@
std::ostream* out) const override;
private:
- struct ClassMemberCompare {
- using T = std::unique_ptr<ClassMember>;
- bool operator()(const T& a, const T& b) const {
- return a->GetName() < b->GetName();
- }
- };
+ DISALLOW_COPY_AND_ASSIGN(ClassDefinition);
std::string name_;
ClassQualifier qualifier_;
bool create_if_empty_;
- std::set<std::unique_ptr<ClassMember>, ClassMemberCompare> members_;
-
- DISALLOW_COPY_AND_ASSIGN(ClassDefinition);
+ std::vector<std::unique_ptr<ClassMember>> ordered_members_;
+ std::unordered_map<android::StringPiece, size_t> indexed_members_;
};
} // namespace aapt
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
index 271279f..84bf041 100644
--- a/tools/aapt2/java/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -23,6 +23,8 @@
#include "util/Util.h"
using android::StringPiece;
+using ::testing::Lt;
+using ::testing::Ne;
namespace aapt {
@@ -329,6 +331,53 @@
EXPECT_NE(std::string::npos, actual.find(styleable.GetComment().data()));
}
+TEST(JavaClassGeneratorTest, StyleableAndIndicesAreColocated) {
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .AddValue("android:attr/layout_gravity", util::make_unique<Attribute>())
+ .AddValue("android:attr/background", util::make_unique<Attribute>())
+ .AddValue("android:styleable/ActionBar",
+ test::StyleableBuilder()
+ .AddItem("android:attr/background", ResourceId(0x01010000))
+ .Build())
+ .AddValue("android:styleable/ActionBar.LayoutParams",
+ test::StyleableBuilder()
+ .AddItem("android:attr/layout_gravity", ResourceId(0x01010001))
+ .Build())
+ .Build();
+
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .SetNameManglerPolicy(NameManglerPolicy{"android"})
+ .Build();
+
+ JavaClassGeneratorOptions options;
+ JavaClassGenerator generator(context.get(), table.get(), {});
+ std::stringstream out;
+ ASSERT_TRUE(generator.Generate("android", &out));
+ std::string output = out.str();
+
+ std::string::size_type actionbar_pos = output.find("int[] ActionBar");
+ ASSERT_THAT(actionbar_pos, Ne(std::string::npos));
+
+ std::string::size_type actionbar_background_pos = output.find("int ActionBar_background");
+ ASSERT_THAT(actionbar_background_pos, Ne(std::string::npos));
+
+ std::string::size_type actionbar_layout_params_pos = output.find("int[] ActionBar_LayoutParams");
+ ASSERT_THAT(actionbar_layout_params_pos, Ne(std::string::npos));
+
+ std::string::size_type actionbar_layout_params_layout_gravity_pos =
+ output.find("int ActionBar_LayoutParams_layout_gravity");
+ ASSERT_THAT(actionbar_layout_params_layout_gravity_pos, Ne(std::string::npos));
+
+ EXPECT_THAT(actionbar_pos, Lt(actionbar_background_pos));
+ EXPECT_THAT(actionbar_pos, Lt(actionbar_layout_params_pos));
+ EXPECT_THAT(actionbar_background_pos, Lt(actionbar_layout_params_pos));
+ EXPECT_THAT(actionbar_layout_params_pos, Lt(actionbar_layout_params_layout_gravity_pos));
+}
+
TEST(JavaClassGeneratorTest, CommentsForRemovedAttributesAreNotPresentInClass) {
Attribute attr(false);
attr.SetComment(StringPiece("removed"));
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index cbb652e..1d122db 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -274,6 +274,8 @@
switch (code) {
case ResXMLParser::START_NAMESPACE: {
NamespaceDecl decl;
+ decl.line_number = tree.getLineNumber();
+
size_t len;
const char16_t* str16 = tree.getNamespacePrefix(&len);
if (str16) {
@@ -288,6 +290,7 @@
if (pending_element == nullptr) {
pending_element = util::make_unique<Element>();
}
+ pending_element->namespace_decls.push_back(std::move(decl));
break;
}
@@ -297,8 +300,8 @@
el = std::move(pending_element);
} else {
el = util::make_unique<Element>();
- ;
}
+ el->line_number = tree.getLineNumber();
size_t len;
const char16_t* str16 = tree.getElementNamespace(&len);
diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp
index 6ed2d61..b501cfd 100644
--- a/tools/aapt2/xml/XmlDom_test.cpp
+++ b/tools/aapt2/xml/XmlDom_test.cpp
@@ -18,6 +18,7 @@
#include <string>
+#include "flatten/XmlFlattener.h"
#include "io/StringInputStream.h"
#include "test/Test.h"
@@ -51,6 +52,36 @@
EXPECT_THAT(el->namespace_decls[0].prefix, StrEq("android"));
}
+TEST(XmlDomTest, BinaryInflate) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<XmlResource> doc = util::make_unique<XmlResource>();
+ doc->root = util::make_unique<Element>();
+ doc->root->name = "Layout";
+ doc->root->line_number = 2u;
+
+ NamespaceDecl decl;
+ decl.uri = kSchemaAndroid;
+ decl.prefix = "android";
+ decl.line_number = 2u;
+ doc->root->namespace_decls.push_back(decl);
+
+ BigBuffer buffer(4096);
+ XmlFlattener flattener(&buffer, {});
+ ASSERT_TRUE(flattener.Consume(context.get(), doc.get()));
+
+ auto block = util::Copy(buffer);
+ std::unique_ptr<XmlResource> new_doc =
+ Inflate(block.get(), buffer.size(), context->GetDiagnostics(), Source("test.xml"));
+ ASSERT_THAT(new_doc, NotNull());
+
+ EXPECT_THAT(new_doc->root->name, StrEq("Layout"));
+ EXPECT_THAT(new_doc->root->line_number, Eq(2u));
+ ASSERT_THAT(new_doc->root->namespace_decls, SizeIs(1u));
+ EXPECT_THAT(new_doc->root->namespace_decls[0].uri, StrEq(kSchemaAndroid));
+ EXPECT_THAT(new_doc->root->namespace_decls[0].prefix, StrEq("android"));
+ EXPECT_THAT(new_doc->root->namespace_decls[0].line_number, Eq(2u));
+}
+
// Escaping is handled after parsing of the values for resource-specific values.
TEST(XmlDomTest, ForwardEscapes) {
std::unique_ptr<XmlResource> doc = test::BuildXmlDom(R"(