summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt1
-rw-r--r--api/system-current.txt1
-rw-r--r--api/test-current.txt1
-rw-r--r--cmds/sm/src/com/android/commands/sm/Sm.java41
-rw-r--r--core/java/android/app/Notification.java311
-rw-r--r--core/java/android/net/ConnectivityManager.java29
-rw-r--r--core/java/android/os/Parcel.java2
-rw-r--r--core/java/android/os/ParcelFileDescriptor.java2
-rw-r--r--core/java/android/os/StrictMode.java108
-rw-r--r--core/java/android/os/storage/IStorageManager.aidl5
-rw-r--r--core/java/android/os/storage/StorageManager.java29
-rwxr-xr-xcore/java/android/provider/Settings.java29
-rw-r--r--core/java/com/android/internal/widget/ImageFloatingTextView.java4
-rw-r--r--core/java/com/android/internal/widget/MessagingGroup.java393
-rw-r--r--core/java/com/android/internal/widget/MessagingLayout.java444
-rw-r--r--core/java/com/android/internal/widget/MessagingLinearLayout.java217
-rw-r--r--core/java/com/android/internal/widget/MessagingMessage.java197
-rw-r--r--core/java/com/android/internal/widget/MessagingPropertyAnimator.java233
-rw-r--r--core/java/com/android/internal/widget/RemeasuringLinearLayout.java68
-rw-r--r--core/java/com/android/internal/widget/ViewClippingUtil.java108
-rw-r--r--core/jni/android_os_Parcel.cpp13
-rw-r--r--core/res/res/drawable/ic_reply_notification_large.xml29
-rw-r--r--core/res/res/drawable/messaging_message_background.xml26
-rw-r--r--core/res/res/layout/chooser_row.xml3
-rw-r--r--core/res/res/layout/notification_template_material_messaging.xml42
-rw-r--r--core/res/res/layout/notification_template_messaging_group.xml50
-rw-r--r--core/res/res/layout/notification_template_messaging_message.xml21
-rw-r--r--core/res/res/layout/notification_template_right_icon.xml4
-rw-r--r--core/res/res/values-in/strings.xml18
-rw-r--r--core/res/res/values-mr/strings.xml109
-rw-r--r--core/res/res/values-ta/strings.xml2
-rw-r--r--core/res/res/values/dimens.xml2
-rw-r--r--core/res/res/values/ids.xml21
-rw-r--r--core/res/res/values/styles_material.xml11
-rw-r--r--core/res/res/values/symbols.xml16
-rw-r--r--core/tests/coretests/src/android/provider/SettingsBackupTest.java1
-rw-r--r--core/tests/coretests/src/android/widget/TextViewActivityTest.java17
-rw-r--r--core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java94
-rw-r--r--data/etc/privapp-permissions-platform.xml1
-rw-r--r--media/jni/Android.bp1
-rw-r--r--packages/MtpDocumentsProvider/res/values-mr/strings.xml2
-rw-r--r--packages/PrintSpooler/res/values-mr/strings.xml6
-rw-r--r--packages/SettingsLib/res/values-en-rXC/strings.xml7
-rw-r--r--packages/SettingsLib/res/values-es-rUS/strings.xml7
-rw-r--r--packages/SettingsLib/res/values-mr/strings.xml29
-rw-r--r--packages/SettingsLib/res/values/strings.xml12
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java158
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java19
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/drawable/UserIconDrawableTest.java (renamed from packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawable/UserIconDrawableTest.java)15
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/AbstractPreferenceControllerTest.java (renamed from packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/PreferenceControllerTest.java)46
-rw-r--r--packages/SystemUI/AndroidManifest.xml8
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml61
-rw-r--r--packages/SystemUI/res/values/dimens.xml4
-rw-r--r--packages/SystemUI/res/values/ids.xml3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java160
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeService.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java159
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java405
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java137
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java108
-rw-r--r--packages/VpnDialogs/res/values-ta/strings.xml2
-rw-r--r--services/core/java/com/android/server/BatteryService.java247
-rw-r--r--services/core/java/com/android/server/NetworkManagementService.java43
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java48
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java124
-rw-r--r--services/core/java/com/android/server/media/AudioPlaybackMonitor.java289
-rw-r--r--services/core/java/com/android/server/media/AudioPlayerStateMonitor.java327
-rw-r--r--services/core/java/com/android/server/media/MediaRouterService.java74
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java28
-rw-r--r--services/core/java/com/android/server/media/MediaSessionStack.java14
-rw-r--r--services/net/java/android/net/apf/ApfFilter.java31
-rw-r--r--services/net/java/android/net/ip/IpClient.java14
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java3
-rw-r--r--test-runner/Android.mk10
-rw-r--r--tests/UiBench/AndroidManifest.xml9
-rw-r--r--tests/UiBench/src/com/android/test/uibench/TrivialAnimationActivityWideGamut.java18
-rw-r--r--tests/net/java/android/net/apf/ApfTest.java95
-rw-r--r--tools/aapt2/Android.bp1
-rw-r--r--tools/aapt2/LoadedApk.cpp146
-rw-r--r--tools/aapt2/LoadedApk.h43
-rw-r--r--tools/aapt2/Main.cpp5
-rw-r--r--tools/aapt2/cmd/Convert.cpp228
-rw-r--r--tools/aapt2/cmd/Diff.cpp5
-rw-r--r--tools/aapt2/cmd/Dump.cpp7
-rw-r--r--tools/aapt2/cmd/Link.cpp162
-rw-r--r--tools/aapt2/cmd/Optimize.cpp9
-rw-r--r--tools/aapt2/cmd/Util.cpp74
-rw-r--r--tools/aapt2/cmd/Util.h3
-rw-r--r--tools/aapt2/format/binary/BinaryResourceParser.cpp115
-rw-r--r--tools/aapt2/format/binary/BinaryResourceParser.h4
-rw-r--r--tools/aapt2/format/binary/TableFlattener.cpp16
-rw-r--r--tools/aapt2/format/binary/TableFlattener_test.cpp5
-rw-r--r--tools/aapt2/format/proto/ProtoDeserialize.cpp35
-rw-r--r--tools/aapt2/format/proto/ProtoDeserialize.h11
-rw-r--r--tools/aapt2/format/proto/ProtoSerialize_test.cpp18
-rw-r--r--tools/aapt2/integration-tests/NamespaceTest/App/Android.mk1
-rw-r--r--tools/aapt2/link/TableMerger.cpp71
-rw-r--r--tools/aapt2/link/TableMerger.h25
-rw-r--r--tools/aapt2/link/TableMerger_test.cpp27
-rw-r--r--tools/aapt2/optimize/MultiApkGenerator.cpp8
-rw-r--r--tools/aapt2/optimize/MultiApkGenerator_test.cpp6
-rw-r--r--tools/aapt2/test/Builders.cpp20
-rw-r--r--tools/aapt2/test/Builders.h9
-rw-r--r--tools/aapt2/xml/XmlDom.cpp54
-rw-r--r--tools/aapt2/xml/XmlDom.h20
-rw-r--r--tools/aapt2/xml/XmlDom_test.cpp3
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java1
118 files changed, 4824 insertions, 1931 deletions
diff --git a/api/current.txt b/api/current.txt
index 12a3f07febf7..a3a46766ae31 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -35177,6 +35177,7 @@ package android.provider {
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
field public static final java.lang.String EXTRA_INPUT_METHOD_ID = "input_method_id";
+ field public static final java.lang.String EXTRA_SUB_ID = "android.provider.extra.SUB_ID";
field public static final java.lang.String INTENT_CATEGORY_USAGE_ACCESS_CONFIG = "android.intent.category.USAGE_ACCESS_CONFIG";
field public static final java.lang.String METADATA_USAGE_ACCESS_REASON = "android.settings.metadata.USAGE_ACCESS_REASON";
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 0b34782dbbba..3eeadaf007b6 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -38243,6 +38243,7 @@ package android.provider {
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
field public static final java.lang.String EXTRA_INPUT_METHOD_ID = "input_method_id";
+ field public static final java.lang.String EXTRA_SUB_ID = "android.provider.extra.SUB_ID";
field public static final java.lang.String INTENT_CATEGORY_USAGE_ACCESS_CONFIG = "android.intent.category.USAGE_ACCESS_CONFIG";
field public static final java.lang.String METADATA_USAGE_ACCESS_REASON = "android.settings.metadata.USAGE_ACCESS_REASON";
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 4eba70cce70f..110cd9a76c10 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -35449,6 +35449,7 @@ package android.provider {
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
field public static final java.lang.String EXTRA_INPUT_METHOD_ID = "input_method_id";
+ field public static final java.lang.String EXTRA_SUB_ID = "android.provider.extra.SUB_ID";
field public static final java.lang.String INTENT_CATEGORY_USAGE_ACCESS_CONFIG = "android.intent.category.USAGE_ACCESS_CONFIG";
field public static final java.lang.String METADATA_USAGE_ACCESS_REASON = "android.settings.metadata.USAGE_ACCESS_REASON";
}
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index a9a4118a8e98..699de94a0240 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -20,6 +20,9 @@ import static android.os.storage.StorageManager.PROP_ADOPTABLE_FBE;
import static android.os.storage.StorageManager.PROP_HAS_ADOPTABLE;
import static android.os.storage.StorageManager.PROP_VIRTUAL_DISK;
+import android.os.IBinder;
+import android.os.IVoldTaskListener;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
@@ -29,6 +32,8 @@ import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.util.Log;
+import java.util.concurrent.CompletableFuture;
+
public final class Sm {
private static final String TAG = "Sm";
@@ -221,9 +226,23 @@ public final class Sm {
mSm.format(volId);
}
- public void runBenchmark() throws RemoteException {
+ public void runBenchmark() throws Exception {
final String volId = nextArg();
- mSm.benchmark(volId);
+ final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
+ mSm.benchmark(volId, new IVoldTaskListener.Stub() {
+ @Override
+ public void onStatus(int status, PersistableBundle extras) {
+ // Ignored
+ }
+
+ @Override
+ public void onFinished(int status, PersistableBundle extras) {
+ // Touch to unparcel
+ extras.size();
+ result.complete(extras);
+ }
+ });
+ System.out.println(result.get());
}
public void runForget() throws RemoteException {
@@ -235,8 +254,22 @@ public final class Sm {
}
}
- public void runFstrim() throws RemoteException {
- mSm.fstrim(0);
+ public void runFstrim() throws Exception {
+ final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
+ mSm.fstrim(0, new IVoldTaskListener.Stub() {
+ @Override
+ public void onStatus(int status, PersistableBundle extras) {
+ // Ignored
+ }
+
+ @Override
+ public void onFinished(int status, PersistableBundle extras) {
+ // Touch to unparcel
+ extras.size();
+ result.complete(extras);
+ }
+ });
+ System.out.println(result.get());
}
public void runSetVirtualDisk() throws RemoteException {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8226e0fb4e71..d5d95fb85a6a 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -22,6 +22,7 @@ import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
@@ -3900,7 +3901,7 @@ public class Notification implements Parcelable
final Bundle ex = mN.extras;
updateBackgroundColor(contentView);
bindNotificationHeader(contentView, p.ambient);
- bindLargeIcon(contentView);
+ bindLargeIcon(contentView, p.hideLargeIcon, p.alwaysShowReply);
boolean showProgress = handleProgressBar(p.hasProgress, contentView, ex);
if (p.title != null) {
contentView.setViewVisibility(R.id.title, View.VISIBLE);
@@ -4110,11 +4111,13 @@ public class Notification implements Parcelable
}
}
- private void bindLargeIcon(RemoteViews contentView) {
+ private void bindLargeIcon(RemoteViews contentView, boolean hideLargeIcon,
+ boolean alwaysShowReply) {
if (mN.mLargeIcon == null && mN.largeIcon != null) {
mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
}
- if (mN.mLargeIcon != null) {
+ boolean showLargeIcon = mN.mLargeIcon != null && !hideLargeIcon;
+ if (showLargeIcon) {
contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon);
processLargeLegacyIcon(mN.mLargeIcon, contentView);
@@ -4122,32 +4125,45 @@ public class Notification implements Parcelable
contentView.setViewLayoutMarginEndDimen(R.id.line1, endMargin);
contentView.setViewLayoutMarginEndDimen(R.id.text, endMargin);
contentView.setViewLayoutMarginEndDimen(R.id.progress, endMargin);
- // Bind the reply action
- Action action = findReplyAction();
- contentView.setViewVisibility(R.id.reply_icon_action, action != null
- ? View.VISIBLE
- : View.GONE);
-
- if (action != null) {
- int contrastColor = resolveContrastColor();
+ }
+ // Bind the reply action
+ Action action = findReplyAction();
+
+ boolean actionVisible = action != null && (showLargeIcon || alwaysShowReply);
+ int replyId = showLargeIcon ? R.id.reply_icon_action : R.id.right_icon;
+ if (actionVisible) {
+ // We're only showing the icon as big if we're hiding the large icon
+ int contrastColor = resolveContrastColor();
+ int iconColor;
+ if (showLargeIcon) {
contentView.setDrawableTint(R.id.reply_icon_action,
true /* targetBackground */,
contrastColor, PorterDuff.Mode.SRC_ATOP);
- int iconColor = NotificationColorUtil.isColorLight(contrastColor)
- ? Color.BLACK : Color.WHITE;
- contentView.setDrawableTint(R.id.reply_icon_action,
- false /* targetBackground */,
- iconColor, PorterDuff.Mode.SRC_ATOP);
contentView.setOnClickPendingIntent(R.id.right_icon,
action.actionIntent);
- contentView.setOnClickPendingIntent(R.id.reply_icon_action,
- action.actionIntent);
contentView.setRemoteInputs(R.id.right_icon, action.mRemoteInputs);
- contentView.setRemoteInputs(R.id.reply_icon_action, action.mRemoteInputs);
-
+ iconColor = NotificationColorUtil.isColorLight(contrastColor)
+ ? Color.BLACK : Color.WHITE;
+ } else {
+ contentView.setImageViewResource(R.id.right_icon,
+ R.drawable.ic_reply_notification_large);
+ contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
+ iconColor = contrastColor;
}
+ contentView.setDrawableTint(replyId,
+ false /* targetBackground */,
+ iconColor,
+ PorterDuff.Mode.SRC_ATOP);
+ contentView.setOnClickPendingIntent(replyId,
+ action.actionIntent);
+ contentView.setRemoteInputs(replyId, action.mRemoteInputs);
+ } else {
+ contentView.setRemoteInputs(R.id.right_icon, null);
}
- contentView.setViewVisibility(R.id.right_icon_container, mN.mLargeIcon != null
+ contentView.setViewVisibility(R.id.reply_icon_action, actionVisible && showLargeIcon
+ ? View.VISIBLE
+ : View.GONE);
+ contentView.setViewVisibility(R.id.right_icon_container, actionVisible || showLargeIcon
? View.VISIBLE
: View.GONE);
}
@@ -6055,18 +6071,12 @@ public class Notification implements Parcelable
protected void restoreFromExtras(Bundle extras) {
super.restoreFromExtras(extras);
- mMessages.clear();
- mHistoricMessages.clear();
mUserDisplayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
- if (messages != null && messages instanceof Parcelable[]) {
- mMessages = Message.getMessagesFromBundleArray(messages);
- }
+ mMessages = Message.getMessagesFromBundleArray(messages);
Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
- if (histMessages != null && histMessages instanceof Parcelable[]) {
- mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
- }
+ mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
}
/**
@@ -6074,38 +6084,34 @@ public class Notification implements Parcelable
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
- if (!increasedHeight) {
- Message m = findLatestIncomingMessage();
- CharSequence title = mConversationTitle != null
- ? mConversationTitle
- : (m == null) ? null : m.mSender;
- CharSequence text = (m == null)
- ? null
- : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
-
- return mBuilder.applyStandardTemplate(mBuilder.getBaseLayoutResource(),
- mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
- } else {
- mBuilder.mOriginalActions = mBuilder.mActions;
- mBuilder.mActions = new ArrayList<>();
- RemoteViews remoteViews = makeBigContentView();
- mBuilder.mActions = mBuilder.mOriginalActions;
- mBuilder.mOriginalActions = null;
- return remoteViews;
- }
+ mBuilder.mOriginalActions = mBuilder.mActions;
+ mBuilder.mActions = new ArrayList<>();
+ RemoteViews remoteViews = makeBigContentView();
+ mBuilder.mActions = mBuilder.mOriginalActions;
+ mBuilder.mOriginalActions = null;
+ return remoteViews;
}
private Message findLatestIncomingMessage() {
- for (int i = mMessages.size() - 1; i >= 0; i--) {
- Message m = mMessages.get(i);
+ return findLatestIncomingMessage(mMessages);
+ }
+
+ /**
+ * @hide
+ */
+ @Nullable
+ public static Message findLatestIncomingMessage(
+ List<Message> messages) {
+ for (int i = messages.size() - 1; i >= 0; i--) {
+ Message m = messages.get(i);
// Incoming messages have a non-empty sender.
if (!TextUtils.isEmpty(m.mSender)) {
return m;
}
}
- if (!mMessages.isEmpty()) {
+ if (!messages.isEmpty()) {
// No incoming messages, fall back to outgoing message
- return mMessages.get(mMessages.size() - 1);
+ return messages.get(messages.size() - 1);
}
return null;
}
@@ -6115,118 +6121,82 @@ public class Notification implements Parcelable
*/
@Override
public RemoteViews makeBigContentView() {
- CharSequence title = !TextUtils.isEmpty(super.mBigContentTitle)
+ CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
? super.mBigContentTitle
: mConversationTitle;
- boolean hasTitle = !TextUtils.isEmpty(title);
-
- if (mMessages.size() == 1) {
- // Special case for a single message: Use the big text style
- // so the collapsed and expanded versions match nicely.
- CharSequence bigTitle;
- CharSequence text;
- if (hasTitle) {
- bigTitle = title;
- text = makeMessageLine(mMessages.get(0), mBuilder);
- } else {
- bigTitle = mMessages.get(0).mSender;
- text = mMessages.get(0).mText;
- }
- RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
- mBuilder.getBigTextLayoutResource(),
- mBuilder.mParams.reset().hasProgress(false).title(bigTitle).text(null));
- BigTextStyle.applyBigTextContentView(mBuilder, contentView, text);
- return contentView;
- }
-
+ boolean isOneToOne = TextUtils.isEmpty(conversationTitle);
+ if (isOneToOne) {
+ // Let's add the conversationTitle in case we didn't have one before and all
+ // messages are from the same sender
+ conversationTitle = createConversationTitleFromMessages();
+ } else if (hasOnlyWhiteSpaceSenders()) {
+ isOneToOne = true;
+ }
+ boolean hasTitle = !TextUtils.isEmpty(conversationTitle);
RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
mBuilder.getMessagingLayoutResource(),
- mBuilder.mParams.reset().hasProgress(false).title(title).text(null));
-
- int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
- R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
-
- // Make sure all rows are gone in case we reuse a view.
- for (int rowId : rowIds) {
- contentView.setViewVisibility(rowId, View.GONE);
- }
+ mBuilder.mParams.reset().hasProgress(false).title(conversationTitle).text(null)
+ .hideLargeIcon(isOneToOne).alwaysShowReply(true));
+ addExtras(mBuilder.mN.extras);
+ contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
+ mBuilder.resolveContrastColor());
+ contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
+ mBuilder.mN.mLargeIcon);
+ contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne",
+ isOneToOne);
+ contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
+ mBuilder.mN.extras);
+ return contentView;
+ }
- int i=0;
- contentView.setViewLayoutMarginBottomDimen(R.id.line1,
- hasTitle ? R.dimen.notification_messaging_spacing : 0);
- contentView.setInt(R.id.notification_messaging, "setNumIndentLines",
- !mBuilder.mN.hasLargeIcon() ? 0 : (hasTitle ? 1 : 2));
-
- int contractedChildId = View.NO_ID;
- Message contractedMessage = findLatestIncomingMessage();
- int firstHistoricMessage = Math.max(0, mHistoricMessages.size()
- - (rowIds.length - mMessages.size()));
- while (firstHistoricMessage + i < mHistoricMessages.size() && i < rowIds.length) {
- Message m = mHistoricMessages.get(firstHistoricMessage + i);
- int rowId = rowIds[i];
-
- contentView.setTextViewText(rowId, makeMessageLine(m, mBuilder));
-
- if (contractedMessage == m) {
- contractedChildId = rowId;
+ private boolean hasOnlyWhiteSpaceSenders() {
+ for (int i = 0; i < mMessages.size(); i++) {
+ Message m = mMessages.get(i);
+ CharSequence sender = m.getSender();
+ if (!isWhiteSpace(sender)) {
+ return false;
}
-
- i++;
}
+ return true;
+ }
- int firstMessage = Math.max(0, mMessages.size() - rowIds.length);
- while (firstMessage + i < mMessages.size() && i < rowIds.length) {
- Message m = mMessages.get(firstMessage + i);
- int rowId = rowIds[i];
-
- contentView.setViewVisibility(rowId, View.VISIBLE);
- contentView.setTextViewText(rowId, mBuilder.processTextSpans(
- makeMessageLine(m, mBuilder)));
- mBuilder.setTextViewColorSecondary(contentView, rowId);
-
- if (contractedMessage == m) {
- contractedChildId = rowId;
- }
-
- i++;
+ private boolean isWhiteSpace(CharSequence sender) {
+ if (TextUtils.isEmpty(sender)) {
+ return true;
}
- // Clear the remaining views for reapply. Ensures that historic message views can
- // reliably be identified as being GONE and having non-null text.
- while (i < rowIds.length) {
- int rowId = rowIds[i];
- contentView.setTextViewText(rowId, null);
- i++;
+ if (sender.toString().matches("^\\s*$")) {
+ return true;
}
-
- // Record this here to allow transformation between the contracted and expanded views.
- contentView.setInt(R.id.notification_messaging, "setContractedChildId",
- contractedChildId);
- return contentView;
+ // Let's check if we only have 0 whitespace chars. Some apps did this as a workaround
+ // For the presentation that we had.
+ for (int i = 0; i < sender.length(); i++) {
+ char c = sender.charAt(i);
+ if (c != '\u200B') {
+ return false;
+ }
+ }
+ return true;
}
- private CharSequence makeMessageLine(Message m, Builder builder) {
- BidiFormatter bidi = BidiFormatter.getInstance();
- SpannableStringBuilder sb = new SpannableStringBuilder();
- boolean colorize = builder.isColorized();
- TextAppearanceSpan colorSpan;
- CharSequence messageName;
- if (TextUtils.isEmpty(m.mSender)) {
- CharSequence replyName = mUserDisplayName == null ? "" : mUserDisplayName;
- sb.append(bidi.unicodeWrap(replyName),
- makeFontColorSpan(colorize
- ? builder.getPrimaryTextColor()
- : mBuilder.resolveContrastColor()),
- 0 /* flags */);
- } else {
- sb.append(bidi.unicodeWrap(m.mSender),
- makeFontColorSpan(colorize
- ? builder.getPrimaryTextColor()
- : Color.BLACK),
- 0 /* flags */);
+ private CharSequence createConversationTitleFromMessages() {
+ ArraySet<CharSequence> names = new ArraySet<>();
+ for (int i = 0; i < mMessages.size(); i++) {
+ Message m = mMessages.get(i);
+ CharSequence sender = m.getSender();
+ if (sender != null) {
+ names.add(sender);
+ }
+ }
+ SpannableStringBuilder title = new SpannableStringBuilder();
+ int size = names.size();
+ for (int i = 0; i < size; i++) {
+ CharSequence name = names.valueAt(i);
+ if (!TextUtils.isEmpty(title)) {
+ title.append(", ");
+ }
+ title.append(BidiFormatter.getInstance().unicodeWrap(name));
}
- CharSequence text = m.mText == null ? "" : m.mText;
- sb.append(" ").append(bidi.unicodeWrap(text));
- return sb;
+ return title;
}
/**
@@ -6234,19 +6204,9 @@ public class Notification implements Parcelable
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
- if (increasedHeight) {
- return makeBigContentView();
- }
- Message m = findLatestIncomingMessage();
- CharSequence title = mConversationTitle != null
- ? mConversationTitle
- : (m == null) ? null : m.mSender;
- CharSequence text = (m == null)
- ? null
- : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
-
- return mBuilder.applyStandardTemplateWithActions(mBuilder.getBigBaseLayoutResource(),
- mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
+ RemoteViews remoteViews = makeBigContentView();
+ remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
+ return remoteViews;
}
private static TextAppearanceSpan makeFontColorSpan(int color) {
@@ -6394,7 +6354,15 @@ public class Notification implements Parcelable
return bundles;
}
- static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
+ /**
+ * @return A list of messages read from the bundles.
+ *
+ * @hide
+ */
+ public static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
+ if (bundles == null) {
+ return new ArrayList<>();
+ }
List<Message> messages = new ArrayList<>(bundles.length);
for (int i = 0; i < bundles.length; i++) {
if (bundles[i] instanceof Bundle) {
@@ -8487,6 +8455,8 @@ public class Notification implements Parcelable
boolean ambient = false;
CharSequence title;
CharSequence text;
+ boolean hideLargeIcon;
+ public boolean alwaysShowReply;
final StandardTemplateParams reset() {
hasProgress = true;
@@ -8511,6 +8481,16 @@ public class Notification implements Parcelable
return this;
}
+ final StandardTemplateParams alwaysShowReply(boolean alwaysShowReply) {
+ this.alwaysShowReply = alwaysShowReply;
+ return this;
+ }
+
+ final StandardTemplateParams hideLargeIcon(boolean hideLargeIcon) {
+ this.hideLargeIcon = hideLargeIcon;
+ return this;
+ }
+
final StandardTemplateParams ambient(boolean ambient) {
Preconditions.checkState(title == null && text == null, "must set ambient before text");
this.ambient = ambient;
@@ -8527,7 +8507,6 @@ public class Notification implements Parcelable
text = extras.getCharSequence(EXTRA_TEXT);
}
this.text = b.processLegacyText(text, ambient);
-
return this;
}
}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index d7ecc81ffdba..8071e8b83738 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -619,6 +619,35 @@ public class ConnectivityManager {
*/
public static final int NETID_UNSET = 0;
+ /**
+ * Private DNS Mode values.
+ *
+ * The "private_dns_mode" global setting stores a String value which is
+ * expected to be one of the following.
+ */
+
+ /**
+ * @hide
+ */
+ public static final String PRIVATE_DNS_MODE_OFF = "off";
+ /**
+ * @hide
+ */
+ public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic";
+ /**
+ * @hide
+ */
+ public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname";
+ /**
+ * The default Private DNS mode.
+ *
+ * This may change from release to release or may become dependent upon
+ * the capabilities of the underlying platform.
+ *
+ * @hide
+ */
+ public static final String PRIVATE_DNS_DEFAULT_MODE = PRIVATE_DNS_MODE_OPPORTUNISTIC;
+
private final IConnectivityManager mService;
/**
* A kludge to facilitate static access where a Context pointer isn't available, like in the
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index c2cf396716b8..10adb5a61aaf 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -2020,8 +2020,6 @@ public final class Parcel {
@Deprecated
static native void closeFileDescriptor(FileDescriptor desc) throws IOException;
- static native void clearFileDescriptor(FileDescriptor desc);
-
/**
* Read a byte value from the parcel at the current dataPosition().
*/
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 7f588adbd69d..7556f0921b4d 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -683,7 +683,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
throw new IllegalStateException("Already closed");
}
final int fd = getFd();
- Parcel.clearFileDescriptor(mFd);
+ mFd.setInt$(-1);
writeCommStatusAndClose(Status.DETACHED, null);
mClosed = true;
mGuard.close();
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 53d87ffb317c..d5820b67a8ae 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -29,6 +29,8 @@ import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.net.TrafficStats;
import android.net.Uri;
+import android.os.StrictMode.ThreadPolicy;
+import android.os.StrictMode.VmPolicy;
import android.os.strictmode.CleartextNetworkViolation;
import android.os.strictmode.ContentUriWithoutPermissionViolation;
import android.os.strictmode.CustomViolation;
@@ -54,6 +56,7 @@ import android.util.Slog;
import android.view.IWindowManager;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.os.RuntimeInit;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.HexDump;
@@ -1533,7 +1536,6 @@ public final class StrictMode {
if (violationMaskSubset != 0) {
violationMaskSubset |= info.getViolationBit();
- final int savedPolicyMask = getThreadPolicyMask();
final boolean justDropBox = (info.mPolicy & THREAD_PENALTY_MASK) == PENALTY_DROPBOX;
if (justDropBox) {
@@ -1544,29 +1546,8 @@ public final class StrictMode {
// isn't always super fast, despite the implementation
// in the ActivityManager trying to be mostly async.
dropboxViolationAsync(violationMaskSubset, info);
- return;
- }
-
- // Normal synchronous call to the ActivityManager.
- try {
- // First, remove any policy before we call into the Activity Manager,
- // otherwise we'll infinite recurse as we try to log policy violations
- // to disk, thus violating policy, thus requiring logging, etc...
- // We restore the current policy below, in the finally block.
- setThreadPolicyMask(0);
-
- ActivityManager.getService()
- .handleApplicationStrictModeViolation(
- RuntimeInit.getApplicationObject(), violationMaskSubset, info);
- } catch (RemoteException e) {
- if (e instanceof DeadObjectException) {
- // System process is dead; ignore
- } else {
- Log.e(TAG, "RemoteException trying to handle StrictMode violation", e);
- }
- } finally {
- // Restore the policy.
- setThreadPolicyMask(savedPolicyMask);
+ } else {
+ handleApplicationStrictModeViolation(violationMaskSubset, info);
}
}
@@ -1598,28 +1579,39 @@ public final class StrictMode {
if (LOG_V) Log.d(TAG, "Dropboxing async; in-flight=" + outstanding);
- new Thread("callActivityManagerForStrictModeDropbox") {
- public void run() {
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- try {
- IActivityManager am = ActivityManager.getService();
- if (am == null) {
- Log.d(TAG, "No activity manager; failed to Dropbox violation.");
- } else {
- am.handleApplicationStrictModeViolation(
- RuntimeInit.getApplicationObject(), violationMaskSubset, info);
- }
- } catch (RemoteException e) {
- if (e instanceof DeadObjectException) {
- // System process is dead; ignore
- } else {
- Log.e(TAG, "RemoteException handling StrictMode violation", e);
- }
- }
- int outstanding = sDropboxCallsInFlight.decrementAndGet();
- if (LOG_V) Log.d(TAG, "Dropbox complete; in-flight=" + outstanding);
+ BackgroundThread.getHandler().post(() -> {
+ handleApplicationStrictModeViolation(violationMaskSubset, info);
+ int outstandingInner = sDropboxCallsInFlight.decrementAndGet();
+ if (LOG_V) Log.d(TAG, "Dropbox complete; in-flight=" + outstandingInner);
+ });
+ }
+
+ private static void handleApplicationStrictModeViolation(int violationMaskSubset,
+ ViolationInfo info) {
+ final int oldMask = getThreadPolicyMask();
+ try {
+ // First, remove any policy before we call into the Activity Manager,
+ // otherwise we'll infinite recurse as we try to log policy violations
+ // to disk, thus violating policy, thus requiring logging, etc...
+ // We restore the current policy below, in the finally block.
+ setThreadPolicyMask(0);
+
+ IActivityManager am = ActivityManager.getService();
+ if (am == null) {
+ Log.w(TAG, "No activity manager; failed to Dropbox violation.");
+ } else {
+ am.handleApplicationStrictModeViolation(
+ RuntimeInit.getApplicationObject(), violationMaskSubset, info);
+ }
+ } catch (RemoteException e) {
+ if (e instanceof DeadObjectException) {
+ // System process is dead; ignore
+ } else {
+ Log.e(TAG, "RemoteException handling StrictMode violation", e);
}
- }.start();
+ } finally {
+ setThreadPolicyMask(oldMask);
+ }
}
private static class AndroidCloseGuardReporter implements CloseGuard.Reporter {
@@ -1908,31 +1900,7 @@ public final class StrictMode {
}
if (penaltyDropbox && lastViolationTime == 0) {
- // The violationMask, passed to ActivityManager, is a
- // subset of the original StrictMode policy bitmask, with
- // only the bit violated and penalty bits to be executed
- // by the ActivityManagerService remaining set.
- final int savedPolicyMask = getThreadPolicyMask();
- try {
- // First, remove any policy before we call into the Activity Manager,
- // otherwise we'll infinite recurse as we try to log policy violations
- // to disk, thus violating policy, thus requiring logging, etc...
- // We restore the current policy below, in the finally block.
- setThreadPolicyMask(0);
-
- ActivityManager.getService()
- .handleApplicationStrictModeViolation(
- RuntimeInit.getApplicationObject(), violationMaskSubset, info);
- } catch (RemoteException e) {
- if (e instanceof DeadObjectException) {
- // System process is dead; ignore
- } else {
- Log.e(TAG, "RemoteException trying to handle StrictMode violation", e);
- }
- } finally {
- // Restore the policy.
- setThreadPolicyMask(savedPolicyMask);
- }
+ handleApplicationStrictModeViolation(violationMaskSubset, info);
}
if (penaltyDeath) {
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index e865ed16fd24..0b76eec4ae0b 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -17,6 +17,7 @@
package android.os.storage;
import android.content.pm.IPackageMoveObserver;
+import android.os.IVoldTaskListener;
import android.os.ParcelFileDescriptor;
import android.os.storage.DiskInfo;
import android.os.storage.IStorageEventListener;
@@ -165,7 +166,7 @@ interface IStorageManager {
void forgetAllVolumes() = 56;
String getPrimaryStorageUuid() = 57;
void setPrimaryStorageUuid(in String volumeUuid, IPackageMoveObserver callback) = 58;
- long benchmark(in String volId) = 59;
+ void benchmark(in String volId, IVoldTaskListener listener) = 59;
void setDebugFlags(int flags, int mask) = 60;
void createUserKey(int userId, int serialNumber, boolean ephemeral) = 61;
void destroyUserKey(int userId) = 62;
@@ -177,7 +178,7 @@ interface IStorageManager {
boolean isConvertibleToFBE() = 68;
void addUserKeyAuth(int userId, int serialNumber, in byte[] token, in byte[] secret) = 70;
void fixateNewestUserKeyAuth(int userId) = 71;
- void fstrim(int flags) = 72;
+ void fstrim(int flags, IVoldTaskListener listener) = 72;
AppFuseMount mountProxyFileDescriptorBridge() = 73;
ParcelFileDescriptor openProxyFileDescriptor(int mountPointId, int fileId, int mode) = 74;
long getCacheQuotaBytes(String volumeUuid, int uid) = 75;
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 6594cd070d24..0b007ddf50a1 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -42,10 +42,12 @@ import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
import android.os.IVold;
+import android.os.IVoldTaskListener;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.ParcelableException;
+import android.os.PersistableBundle;
import android.os.ProxyFileDescriptorCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -87,7 +89,9 @@ import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -884,9 +888,32 @@ public class StorageManager {
}
/** {@hide} */
+ @Deprecated
public long benchmark(String volId) {
+ final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
+ benchmark(volId, new IVoldTaskListener.Stub() {
+ @Override
+ public void onStatus(int status, PersistableBundle extras) {
+ // Ignored
+ }
+
+ @Override
+ public void onFinished(int status, PersistableBundle extras) {
+ result.complete(extras);
+ }
+ });
+ try {
+ // Convert ms to ns
+ return result.get(3, TimeUnit.MINUTES).getLong("run", Long.MAX_VALUE) * 1000000;
+ } catch (Exception e) {
+ return Long.MAX_VALUE;
+ }
+ }
+
+ /** {@hide} */
+ public void benchmark(String volId, IVoldTaskListener listener) {
try {
- return mStorageManager.benchmark(volId);
+ mStorageManager.benchmark(volId, listener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 433878e9511a..0a20c4348462 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -211,8 +211,13 @@ public final class Settings {
/** @hide */
public static final String EXTRA_NETWORK_TEMPLATE = "network_template";
- /** @hide */
- public static final String EXTRA_SUB_ID = "sub_id";
+
+ /**
+ * An int extra specifying a subscription ID.
+ *
+ * @see android.telephony.SubscriptionInfo#getSubscriptionId
+ */
+ public static final String EXTRA_SUB_ID = "android.provider.extra.SUB_ID";
/**
* Activity Action: Modify Airplane mode settings using a voice command.
@@ -916,6 +921,9 @@ public final class Settings {
* In some cases, a matching Activity may not exist, so ensure you
* safeguard against this.
* <p>
+ * The subscription ID of the subscription for which available network operators should be
+ * displayed may be optionally specified with {@link #EXTRA_SUB_ID}.
+ * <p>
* Input: Nothing.
* <p>
* Output: Nothing.
@@ -9297,11 +9305,20 @@ public final class Settings {
public static final String DEFAULT_DNS_SERVER = "default_dns_server";
/**
- * Whether to disable DNS over TLS (boolean)
+ * The requested Private DNS mode (string), and an accompanying specifier (string).
+ *
+ * Currently, the specifier holds the chosen provider name when the mode requests
+ * a specific provider. It may be used to store the provider name even when the
+ * mode changes so that temporarily disabling and re-enabling the specific
+ * provider mode does not necessitate retyping the provider hostname.
*
* @hide
*/
- public static final String DNS_TLS_DISABLED = "dns_tls_disabled";
+ public static final String PRIVATE_DNS_MODE = "private_dns_mode";
+ /**
+ * @hide
+ */
+ public static final String PRIVATE_DNS_SPECIFIER = "private_dns_specifier";
/** {@hide} */
public static final String
@@ -10387,7 +10404,9 @@ public final class Settings {
DOCK_AUDIO_MEDIA_ENABLED,
ENCODED_SURROUND_OUTPUT,
LOW_POWER_MODE_TRIGGER_LEVEL,
- BLUETOOTH_ON
+ BLUETOOTH_ON,
+ PRIVATE_DNS_MODE,
+ PRIVATE_DNS_SPECIFIER
};
/** @hide */
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
index 7870333cd82d..09f7282f097a 100644
--- a/core/java/com/android/internal/widget/ImageFloatingTextView.java
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -176,8 +176,4 @@ public class ImageFloatingTextView extends TextView {
}
return false;
}
-
- public int getLayoutHeight() {
- return getLayout().getHeight();
- }
}
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
new file mode 100644
index 000000000000..792f9212d93e
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -0,0 +1,393 @@
+/*
+ * 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.internal.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Pools;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.ViewTreeObserver;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+
+import com.android.internal.R;
+import com.android.internal.util.NotificationColorUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A message of a {@link MessagingLayout}.
+ */
+@RemoteViews.RemoteView
+public class MessagingGroup extends LinearLayout implements MessagingLinearLayout.MessagingChild {
+ private static Pools.SimplePool<MessagingGroup> sInstancePool
+ = new Pools.SynchronizedPool<>(10);
+ private MessagingLinearLayout mMessageContainer;
+ private ImageFloatingTextView mSenderName;
+ private ImageView mAvatarView;
+ private String mAvatarSymbol = "";
+ private int mLayoutColor;
+ private CharSequence mAvatarName = "";
+ private Icon mAvatarIcon;
+ private ColorFilter mMessageBackgroundFilter;
+ private int mTextColor;
+ private List<MessagingMessage> mMessages;
+ private ArrayList<MessagingMessage> mAddedMessages = new ArrayList<>();
+ private boolean mFirstLayout;
+ private boolean mIsHidingAnimated;
+
+ public MessagingGroup(@NonNull Context context) {
+ super(context);
+ }
+
+ public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mMessageContainer = findViewById(R.id.group_message_container);
+ mSenderName = findViewById(R.id.message_name);
+ mSenderName.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
+ mAvatarView = findViewById(R.id.message_icon);
+ }
+
+ public void setSender(CharSequence sender) {
+ if (sender == null) {
+ mAvatarView.setVisibility(GONE);
+ mSenderName.setVisibility(GONE);
+ setGravity(Gravity.END);
+ mMessageBackgroundFilter = new PorterDuffColorFilter(mLayoutColor,
+ PorterDuff.Mode.SRC_ATOP);
+ mTextColor = NotificationColorUtil.isColorLight(mLayoutColor) ? getNormalTextColor()
+ : Color.WHITE;
+ } else {
+ mSenderName.setText(sender);
+ mAvatarView.setVisibility(VISIBLE);
+ mSenderName.setVisibility(VISIBLE);
+ setGravity(Gravity.START);
+ mMessageBackgroundFilter = null;
+ mTextColor = getNormalTextColor();
+ }
+ }
+
+ private int getNormalTextColor() {
+ return mContext.getColor(R.color.notification_primary_text_color_light);
+ }
+
+ public void setAvatar(Icon icon) {
+ mAvatarIcon = icon;
+ mAvatarView.setImageIcon(icon);
+ mAvatarSymbol = "";
+ mLayoutColor = 0;
+ mAvatarName = "";
+ }
+
+ static MessagingGroup createGroup(MessagingLinearLayout layout) {;
+ MessagingGroup createdGroup = sInstancePool.acquire();
+ if (createdGroup == null) {
+ createdGroup = (MessagingGroup) LayoutInflater.from(layout.getContext()).inflate(
+ R.layout.notification_template_messaging_group, layout,
+ false);
+ createdGroup.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
+ }
+ layout.addView(createdGroup);
+ return createdGroup;
+ }
+
+ public void removeMessage(MessagingMessage messagingMessage) {
+ mMessageContainer.removeView(messagingMessage);
+ Runnable recycleRunnable = () -> {
+ mMessageContainer.removeTransientView(messagingMessage);
+ messagingMessage.recycle();
+ if (mMessageContainer.getChildCount() == 0
+ && mMessageContainer.getTransientViewCount() == 0) {
+ ViewParent parent = getParent();
+ if (parent instanceof ViewGroup) {
+ ((ViewGroup) parent).removeView(MessagingGroup.this);
+ }
+ setAvatar(null);
+ mAvatarView.setAlpha(1.0f);
+ mAvatarView.setTranslationY(0.0f);
+ mSenderName.setAlpha(1.0f);
+ mSenderName.setTranslationY(0.0f);
+ sInstancePool.release(MessagingGroup.this);
+ }
+ };
+ if (isShown()) {
+ mMessageContainer.addTransientView(messagingMessage, 0);
+ performRemoveAnimation(messagingMessage, recycleRunnable);
+ if (mMessageContainer.getChildCount() == 0) {
+ removeGroupAnimated(null);
+ }
+ } else {
+ recycleRunnable.run();
+ }
+
+ }
+
+ private void removeGroupAnimated(Runnable endAction) {
+ MessagingPropertyAnimator.fadeOut(mAvatarView, null);
+ MessagingPropertyAnimator.startLocalTranslationTo(mAvatarView,
+ (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
+ MessagingPropertyAnimator.fadeOut(mSenderName, null);
+ MessagingPropertyAnimator.startLocalTranslationTo(mSenderName,
+ (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
+ boolean endActionTriggered = false;
+ for (int i = mMessageContainer.getChildCount() - 1; i >= 0; i--) {
+ View child = mMessageContainer.getChildAt(i);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+ final ViewGroup.LayoutParams lp = child.getLayoutParams();
+ if (lp instanceof MessagingLinearLayout.LayoutParams
+ && ((MessagingLinearLayout.LayoutParams) lp).hide
+ && !((MessagingLinearLayout.LayoutParams) lp).visibleBefore) {
+ continue;
+ }
+ Runnable childEndAction = endActionTriggered ? null : endAction;
+ performRemoveAnimation(child, childEndAction);
+ endActionTriggered = true;
+ }
+ if (!endActionTriggered && endAction != null) {
+ endAction.run();
+ }
+ }
+
+ public void performRemoveAnimation(View message,
+ Runnable recycleRunnable) {
+ MessagingPropertyAnimator.fadeOut(message, recycleRunnable);
+ MessagingPropertyAnimator.startLocalTranslationTo(message,
+ (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
+ }
+
+ public CharSequence getSenderName() {
+ return mSenderName.getText();
+ }
+
+ public void setSenderVisible(boolean visible) {
+ mSenderName.setVisibility(visible ? VISIBLE : GONE);
+ }
+
+ public static void dropCache() {
+ sInstancePool = new Pools.SynchronizedPool<>(10);
+ }
+
+ @Override
+ public int getMeasuredType() {
+ boolean hasNormal = false;
+ for (int i = mMessageContainer.getChildCount() - 1; i >= 0; i--) {
+ View child = mMessageContainer.getChildAt(i);
+ if (child instanceof MessagingLinearLayout.MessagingChild) {
+ int type = ((MessagingLinearLayout.MessagingChild) child).getMeasuredType();
+ if (type == MEASURED_TOO_SMALL) {
+ if (hasNormal) {
+ return MEASURED_SHORTENED;
+ } else {
+ return MEASURED_TOO_SMALL;
+ }
+ } else if (type == MEASURED_SHORTENED) {
+ return MEASURED_SHORTENED;
+ } else {
+ hasNormal = true;
+ }
+ }
+ }
+ return MEASURED_NORMAL;
+ }
+
+ @Override
+ public int getConsumedLines() {
+ int result = 0;
+ for (int i = 0; i < mMessageContainer.getChildCount(); i++) {
+ View child = mMessageContainer.getChildAt(i);
+ if (child instanceof MessagingLinearLayout.MessagingChild) {
+ result += ((MessagingLinearLayout.MessagingChild) child).getConsumedLines();
+ }
+ }
+ // A group is usually taking up quite some space with the padding and the name, let's add 1
+ return result + 1;
+ }
+
+ @Override
+ public void setMaxDisplayedLines(int lines) {
+ mMessageContainer.setMaxDisplayedLines(lines);
+ }
+
+ @Override
+ public void hideAnimated() {
+ setIsHidingAnimated(true);
+ removeGroupAnimated(() -> setIsHidingAnimated(false));
+ }
+
+ @Override
+ public boolean isHidingAnimated() {
+ return mIsHidingAnimated;
+ }
+
+ private void setIsHidingAnimated(boolean isHiding) {
+ ViewParent parent = getParent();
+ mIsHidingAnimated = isHiding;
+ invalidate();
+ if (parent instanceof ViewGroup) {
+ ((ViewGroup) parent).invalidate();
+ }
+ }
+
+ public Icon getAvatarSymbolIfMatching(CharSequence avatarName, String avatarSymbol,
+ int layoutColor) {
+ if (mAvatarName.equals(avatarName) && mAvatarSymbol.equals(avatarSymbol)
+ && layoutColor == mLayoutColor) {
+ return mAvatarIcon;
+ }
+ return null;
+ }
+
+ public void setCreatedAvatar(Icon cachedIcon, CharSequence avatarName, String avatarSymbol,
+ int layoutColor) {
+ if (!mAvatarName.equals(avatarName) || !mAvatarSymbol.equals(avatarSymbol)
+ || layoutColor != mLayoutColor) {
+ setAvatar(cachedIcon);
+ mAvatarSymbol = avatarSymbol;
+ mLayoutColor = layoutColor;
+ mAvatarName = avatarName;
+ }
+ }
+
+ public void setLayoutColor(int layoutColor) {
+ mLayoutColor = layoutColor;
+ }
+
+ public void setMessages(List<MessagingMessage> group) {
+ // Let's now make sure all children are added and in the correct order
+ for (int messageIndex = 0; messageIndex < group.size(); messageIndex++) {
+ MessagingMessage message = group.get(messageIndex);
+ if (message.getGroup() != this) {
+ message.setMessagingGroup(this);
+ ViewParent parent = mMessageContainer.getParent();
+ if (parent instanceof ViewGroup) {
+ ((ViewGroup) parent).removeView(message);
+ }
+ mMessageContainer.addView(message, messageIndex);
+ mAddedMessages.add(message);
+ }
+ if (messageIndex != mMessageContainer.indexOfChild(message)) {
+ mMessageContainer.removeView(message);
+ mMessageContainer.addView(message, messageIndex);
+ }
+ // Let's make sure the message color is correct
+ Drawable targetDrawable = message.getBackground();
+
+ if (targetDrawable != null) {
+ targetDrawable.mutate().setColorFilter(mMessageBackgroundFilter);
+ }
+ message.setTextColor(mTextColor);
+ }
+ mMessages = group;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (!mAddedMessages.isEmpty()) {
+ getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ for (MessagingMessage message : mAddedMessages) {
+ if (!message.isShown()) {
+ continue;
+ }
+ MessagingPropertyAnimator.fadeIn(message);
+ if (!mFirstLayout) {
+ MessagingPropertyAnimator.startLocalTranslationFrom(message,
+ message.getHeight(), MessagingLayout.LINEAR_OUT_SLOW_IN);
+ }
+ }
+ mAddedMessages.clear();
+ getViewTreeObserver().removeOnPreDrawListener(this);
+ return true;
+ }
+ });
+ }
+ mFirstLayout = false;
+ }
+
+ /**
+ * Calculates the group compatibility between this and another group.
+ *
+ * @param otherGroup the other group to compare it with
+ *
+ * @return 0 if the groups are totally incompatible or 1 + the number of matching messages if
+ * they match.
+ */
+ public int calculateGroupCompatibility(MessagingGroup otherGroup) {
+ if (TextUtils.equals(getSenderName(),otherGroup.getSenderName())) {
+ int result = 1;
+ for (int i = 0; i < mMessages.size() && i < otherGroup.mMessages.size(); i++) {
+ MessagingMessage ownMessage = mMessages.get(mMessages.size() - 1 - i);
+ MessagingMessage otherMessage = otherGroup.mMessages.get(
+ otherGroup.mMessages.size() - 1 - i);
+ if (!ownMessage.sameAs(otherMessage)) {
+ return result;
+ }
+ result++;
+ }
+ return result;
+ }
+ return 0;
+ }
+
+ public View getSender() {
+ return mSenderName;
+ }
+
+ public View getAvatar() {
+ return mAvatarView;
+ }
+
+ public MessagingLinearLayout getMessageContainer() {
+ return mMessageContainer;
+ }
+}
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
new file mode 100644
index 000000000000..2acdc015f8ef
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -0,0 +1,444 @@
+/*
+ * 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.internal.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.app.Notification;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.RemotableViewMethod;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.widget.FrameLayout;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.internal.graphics.ColorUtils;
+import com.android.internal.util.NotificationColorUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A custom-built layout for the Notification.MessagingStyle allows dynamic addition and removal
+ * messages and adapts the layout accordingly.
+ */
+@RemoteViews.RemoteView
+public class MessagingLayout extends FrameLayout {
+
+ private static final float COLOR_SHIFT_AMOUNT = 60;
+ private static final Consumer<MessagingMessage> REMOVE_MESSAGE
+ = MessagingMessage::removeMessage;
+ public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
+ public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
+ public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+ public static final OnLayoutChangeListener MESSAGING_PROPERTY_ANIMATOR
+ = new MessagingPropertyAnimator();
+ private List<MessagingMessage> mMessages = new ArrayList<>();
+ private List<MessagingMessage> mHistoricMessages = new ArrayList<>();
+ private MessagingLinearLayout mMessagingLinearLayout;
+ private View mContractedMessage;
+ private boolean mShowHistoricMessages;
+ private ArrayList<MessagingGroup> mGroups = new ArrayList<>();
+ private TextView mTitleView;
+ private int mLayoutColor;
+ private int mAvatarSize;
+ private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private Paint mTextPaint = new Paint();
+ private CharSequence mConversationTitle;
+ private Icon mLargeIcon;
+ private boolean mIsOneToOne;
+ private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>();
+
+ public MessagingLayout(@NonNull Context context) {
+ super(context);
+ }
+
+ public MessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public MessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mMessagingLinearLayout = findViewById(R.id.notification_messaging);
+ mMessagingLinearLayout.setMessagingLayout(this);
+ // We still want to clip, but only on the top, since views can temporarily out of bounds
+ // during transitions.
+ DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+ Rect rect = new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels);
+ mMessagingLinearLayout.setClipBounds(rect);
+ mTitleView = findViewById(R.id.title);
+ mAvatarSize = getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
+ mTextPaint.setTextAlign(Paint.Align.CENTER);
+ mTextPaint.setAntiAlias(true);
+ }
+
+ @RemotableViewMethod
+ public void setLargeIcon(Icon icon) {
+ mLargeIcon = icon;
+ }
+
+ @RemotableViewMethod
+ public void setData(Bundle extras) {
+ Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
+ List<Notification.MessagingStyle.Message> newMessages
+ = Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages);
+ Parcelable[] histMessages = extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES);
+ List<Notification.MessagingStyle.Message> newHistoricMessages
+ = Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages);
+ mConversationTitle = null;
+ TextView headerText = findViewById(R.id.header_text);
+ if (headerText != null) {
+ mConversationTitle = headerText.getText();
+ }
+ bind(newMessages, newHistoricMessages);
+ }
+
+ private void bind(List<Notification.MessagingStyle.Message> newMessages,
+ List<Notification.MessagingStyle.Message> newHistoricMessages) {
+
+ List<MessagingMessage> historicMessages = createMessages(newHistoricMessages,
+ true /* isHistoric */);
+ List<MessagingMessage> messages = createMessages(newMessages, false /* isHistoric */);
+ addMessagesToGroups(historicMessages, messages);
+
+ // Let's remove the remaining messages
+ mMessages.forEach(REMOVE_MESSAGE);
+ mHistoricMessages.forEach(REMOVE_MESSAGE);
+
+ mMessages = messages;
+ mHistoricMessages = historicMessages;
+
+ updateContractedMessage();
+ updateHistoricMessageVisibility();
+ updateTitleAndNamesDisplay();
+ }
+
+ private void updateTitleAndNamesDisplay() {
+ ArrayMap<CharSequence, String> uniqueNames = new ArrayMap<>();
+ ArrayMap<Character, CharSequence> uniqueCharacters = new ArrayMap<>();
+ for (int i = 0; i < mGroups.size(); i++) {
+ MessagingGroup group = mGroups.get(i);
+ CharSequence senderName = group.getSenderName();
+ if (TextUtils.isEmpty(senderName)) {
+ continue;
+ }
+ boolean visible = !mIsOneToOne;
+ group.setSenderVisible(visible);
+ if ((visible || mLargeIcon == null) && !uniqueNames.containsKey(senderName)) {
+ char c = senderName.charAt(0);
+ if (uniqueCharacters.containsKey(c)) {
+ // this character was already used, lets make it more unique. We first need to
+ // resolve the existing character if it exists
+ CharSequence existingName = uniqueCharacters.get(c);
+ if (existingName != null) {
+ uniqueNames.put(existingName, findNameSplit((String) existingName));
+ uniqueCharacters.put(c, null);
+ }
+ uniqueNames.put(senderName, findNameSplit((String) senderName));
+ } else {
+ uniqueNames.put(senderName, Character.toString(c));
+ uniqueCharacters.put(c, senderName);
+ }
+ }
+ }
+
+ // Now that we have the correct symbols, let's look what we have cached
+ ArrayMap<CharSequence, Icon> cachedAvatars = new ArrayMap<>();
+ for (int i = 0; i < mGroups.size(); i++) {
+ // Let's now set the avatars
+ MessagingGroup group = mGroups.get(i);
+ CharSequence senderName = group.getSenderName();
+ if (TextUtils.isEmpty(senderName) || (mIsOneToOne && mLargeIcon != null)) {
+ continue;
+ }
+ String symbol = uniqueNames.get(senderName);
+ Icon cachedIcon = group.getAvatarSymbolIfMatching(senderName,
+ symbol, mLayoutColor);
+ if (cachedIcon != null) {
+ cachedAvatars.put(senderName, cachedIcon);
+ }
+ }
+
+ for (int i = 0; i < mGroups.size(); i++) {
+ // Let's now set the avatars
+ MessagingGroup group = mGroups.get(i);
+ CharSequence senderName = group.getSenderName();
+ if (TextUtils.isEmpty(senderName)) {
+ continue;
+ }
+ if (mIsOneToOne && mLargeIcon != null) {
+ group.setAvatar(mLargeIcon);
+ } else {
+ Icon cachedIcon = cachedAvatars.get(senderName);
+ if (cachedIcon == null) {
+ cachedIcon = createAvatarSymbol(senderName, uniqueNames.get(senderName),
+ mLayoutColor);
+ cachedAvatars.put(senderName, cachedIcon);
+ }
+ group.setCreatedAvatar(cachedIcon, senderName, uniqueNames.get(senderName),
+ mLayoutColor);
+ }
+ }
+ }
+
+ public Icon createAvatarSymbol(CharSequence senderName, String symbol, int layoutColor) {
+ Bitmap bitmap = Bitmap.createBitmap(mAvatarSize, mAvatarSize, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ float radius = mAvatarSize / 2.0f;
+ int color = findColor(senderName, layoutColor);
+ mPaint.setColor(color);
+ canvas.drawCircle(radius, radius, radius, mPaint);
+ boolean needDarkText = ColorUtils.calculateLuminance(color) > 0.5f;
+ mTextPaint.setColor(needDarkText ? Color.BLACK : Color.WHITE);
+ mTextPaint.setTextSize(symbol.length() == 1 ? mAvatarSize * 0.75f : mAvatarSize * 0.4f);
+ int yPos = (int) (radius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2)) ;
+ canvas.drawText(symbol, radius, yPos, mTextPaint);
+ return Icon.createWithBitmap(bitmap);
+ }
+
+ private int findColor(CharSequence senderName, int layoutColor) {
+ double luminance = NotificationColorUtil.calculateLuminance(layoutColor);
+ float shift = Math.abs(senderName.hashCode()) % 5 / 4.0f - 0.5f;
+
+ // we need to offset the range if the luminance is too close to the borders
+ shift += Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - luminance, 0);
+ shift -= Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - (1.0f - luminance), 0);
+ return NotificationColorUtil.getShiftedColor(layoutColor,
+ (int) (shift * COLOR_SHIFT_AMOUNT));
+ }
+
+ private String findNameSplit(String existingName) {
+ String[] split = existingName.split(" ");
+ if (split.length > 1) {
+ return Character.toString(split[0].charAt(0))
+ + Character.toString(split[1].charAt(0));
+ }
+ return existingName.substring(0, 1);
+ }
+
+ @RemotableViewMethod
+ public void setLayoutColor(int color) {
+ mLayoutColor = color;
+ }
+
+ @RemotableViewMethod
+ public void setIsOneToOne(boolean oneToOne) {
+ mIsOneToOne = oneToOne;
+ }
+
+ private void addMessagesToGroups(List<MessagingMessage> historicMessages,
+ List<MessagingMessage> messages) {
+ // Let's first find our groups!
+ List<List<MessagingMessage>> groups = new ArrayList<>();
+ List<CharSequence> senders = new ArrayList<>();
+
+ // Lets first find the groups
+ findGroups(historicMessages, messages, groups, senders);
+
+ // Let's now create the views and reorder them accordingly
+ createGroupViews(groups, senders);
+ }
+
+ private void createGroupViews(List<List<MessagingMessage>> groups, List<CharSequence> senders) {
+ mGroups.clear();
+ for (int groupIndex = 0; groupIndex < groups.size(); groupIndex++) {
+ List<MessagingMessage> group = groups.get(groupIndex);
+ MessagingGroup newGroup = null;
+ // we'll just take the first group that exists or create one there is none
+ for (int messageIndex = group.size() - 1; messageIndex >= 0; messageIndex--) {
+ MessagingMessage message = group.get(messageIndex);
+ newGroup = message.getGroup();
+ if (newGroup != null) {
+ break;
+ }
+ }
+ if (newGroup == null) {
+ newGroup = MessagingGroup.createGroup(mMessagingLinearLayout);
+ mAddedGroups.add(newGroup);
+ }
+ newGroup.setLayoutColor(mLayoutColor);
+ newGroup.setSender(senders.get(groupIndex));
+ mGroups.add(newGroup);
+
+ if (mMessagingLinearLayout.indexOfChild(newGroup) != groupIndex) {
+ mMessagingLinearLayout.removeView(newGroup);
+ mMessagingLinearLayout.addView(newGroup, groupIndex);
+ }
+ newGroup.setMessages(group);
+ }
+ }
+
+ private void findGroups(List<MessagingMessage> historicMessages,
+ List<MessagingMessage> messages, List<List<MessagingMessage>> groups,
+ List<CharSequence> senders) {
+ CharSequence currentSender = null;
+ List<MessagingMessage> currentGroup = null;
+ int histSize = historicMessages.size();
+ for (int i = 0; i < histSize + messages.size(); i++) {
+ MessagingMessage message;
+ if (i < histSize) {
+ message = historicMessages.get(i);
+ } else {
+ message = messages.get(i - histSize);
+ }
+ boolean isNewGroup = currentGroup == null;
+ CharSequence sender = message.getMessage().getSender();
+ isNewGroup |= !TextUtils.equals(sender, currentSender);
+ if (isNewGroup) {
+ currentGroup = new ArrayList<>();
+ groups.add(currentGroup);
+ senders.add(sender);
+ currentSender = sender;
+ }
+ currentGroup.add(message);
+ }
+ }
+
+ private void updateContractedMessage() {
+ for (int i = mMessages.size() - 1; i >= 0; i--) {
+ MessagingMessage m = mMessages.get(i);
+ // Incoming messages have a non-empty sender.
+ if (!TextUtils.isEmpty(m.getMessage().getSender())) {
+ mContractedMessage = m;
+ return;
+ }
+ }
+ if (!mMessages.isEmpty()) {
+ // No incoming messages, fall back to outgoing message
+ mContractedMessage = mMessages.get(mMessages.size() - 1);
+ return;
+ }
+ mContractedMessage = null;
+ }
+
+ /**
+ * Creates new messages, reusing existing ones if they are available.
+ *
+ * @param newMessages the messages to parse.
+ */
+ private List<MessagingMessage> createMessages(
+ List<Notification.MessagingStyle.Message> newMessages, boolean historic) {
+ List<MessagingMessage> result = new ArrayList<>();;
+ for (int i = 0; i < newMessages.size(); i++) {
+ Notification.MessagingStyle.Message m = newMessages.get(i);
+ MessagingMessage message = findAndRemoveMatchingMessage(m);
+ if (message == null) {
+ message = MessagingMessage.createMessage(this, m);
+ message.addOnLayoutChangeListener(MESSAGING_PROPERTY_ANIMATOR);
+ }
+ message.setIsHistoric(historic);
+ result.add(message);
+ }
+ return result;
+ }
+
+ private MessagingMessage findAndRemoveMatchingMessage(Notification.MessagingStyle.Message m) {
+ for (int i = 0; i < mMessages.size(); i++) {
+ MessagingMessage existing = mMessages.get(i);
+ if (existing.sameAs(m)) {
+ mMessages.remove(i);
+ return existing;
+ }
+ }
+ for (int i = 0; i < mHistoricMessages.size(); i++) {
+ MessagingMessage existing = mHistoricMessages.get(i);
+ if (existing.sameAs(m)) {
+ mHistoricMessages.remove(i);
+ return existing;
+ }
+ }
+ return null;
+ }
+
+ public void showHistoricMessages(boolean show) {
+ mShowHistoricMessages = show;
+ updateHistoricMessageVisibility();
+ }
+
+ private void updateHistoricMessageVisibility() {
+ for (int i = 0; i < mHistoricMessages.size(); i++) {
+ MessagingMessage existing = mHistoricMessages.get(i);
+ existing.setVisibility(mShowHistoricMessages ? VISIBLE : GONE);
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (!mAddedGroups.isEmpty()) {
+ getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ for (MessagingGroup group : mAddedGroups) {
+ if (!group.isShown()) {
+ continue;
+ }
+ MessagingPropertyAnimator.fadeIn(group.getAvatar());
+ MessagingPropertyAnimator.fadeIn(group.getSender());
+ MessagingPropertyAnimator.startLocalTranslationFrom(group,
+ group.getHeight(), LINEAR_OUT_SLOW_IN);
+ }
+ mAddedGroups.clear();
+ getViewTreeObserver().removeOnPreDrawListener(this);
+ return true;
+ }
+ });
+ }
+ }
+
+ public View getContractedMessage() {
+ return mContractedMessage;
+ }
+
+ public MessagingLinearLayout getMessagingLinearLayout() {
+ return mMessagingLinearLayout;
+ }
+
+ public ArrayList<MessagingGroup> getMessagingGroups() {
+ return mGroups;
+ }
+}
diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java
index 70473a014c0d..f0ef37076618 100644
--- a/core/java/com/android/internal/widget/MessagingLinearLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java
@@ -36,28 +36,14 @@ import com.android.internal.R;
@RemoteViews.RemoteView
public class MessagingLinearLayout extends ViewGroup {
- private static final int NOT_MEASURED_BEFORE = -1;
/**
* Spacing to be applied between views.
*/
private int mSpacing;
- /**
- * The maximum height allowed.
- */
- private int mMaxHeight;
+ private int mMaxDisplayedLines = Integer.MAX_VALUE;
- private int mIndentLines;
-
- /**
- * Id of the child that's also visible in the contracted layout.
- */
- private int mContractedChildId;
- /**
- * The last measured with in a layout pass if it was measured before or
- * {@link #NOT_MEASURED_BEFORE} if this is the first layout pass.
- */
- private int mLastMeasuredWidth = NOT_MEASURED_BEFORE;
+ private MessagingLayout mMessagingLayout;
public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -79,7 +65,6 @@ public class MessagingLinearLayout extends ViewGroup {
a.recycle();
}
-
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// This is essentially a bottom-up linear layout that only adds children that fit entirely
@@ -91,118 +76,67 @@ public class MessagingLinearLayout extends ViewGroup {
break;
}
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- boolean recalculateVisibility = mLastMeasuredWidth == NOT_MEASURED_BEFORE
- || getMeasuredHeight() != targetHeight
- || mLastMeasuredWidth != widthSize;
-
- final int count = getChildCount();
- if (recalculateVisibility) {
- // We only need to recalculate the view visibilities if the view wasn't measured already
- // in this pass, otherwise we may drop messages here already since we are measured
- // exactly with what we returned before, which was optimized already with the
- // line-indents.
- for (int i = 0; i < count; ++i) {
- final View child = getChildAt(i);
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- lp.hide = true;
- }
-
- int totalHeight = mPaddingTop + mPaddingBottom;
- boolean first = true;
-
- // Starting from the bottom: we measure every view as if it were the only one. If it still
-
- // fits, we take it, otherwise we stop there.
- for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) {
- if (getChildAt(i).getVisibility() == GONE) {
- continue;
- }
- final View child = getChildAt(i);
- LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
- ImageFloatingTextView textChild = null;
- if (child instanceof ImageFloatingTextView) {
- // Pretend we need the image padding for all views, we don't know which
- // one will end up needing to do this (might end up not using all the space,
- // but calculating this exactly would be more expensive).
- textChild = (ImageFloatingTextView) child;
- textChild.setNumIndentLines(mIndentLines == 2 ? 3 : mIndentLines);
- }
-
- int spacing = first ? 0 : mSpacing;
- measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight
- - mPaddingTop - mPaddingBottom + spacing);
-
- final int childHeight = child.getMeasuredHeight();
- int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin +
- lp.bottomMargin + spacing);
- first = false;
- boolean measuredTooSmall = false;
- if (textChild != null) {
- measuredTooSmall = childHeight < textChild.getLayoutHeight()
- + textChild.getPaddingTop() + textChild.getPaddingBottom();
- }
-
- if (newHeight <= targetHeight && !measuredTooSmall) {
- totalHeight = newHeight;
- lp.hide = false;
- } else {
- break;
- }
- }
- }
// Now that we know which views to take, fix up the indents and see what width we get.
int measuredWidth = mPaddingLeft + mPaddingRight;
- int imageLines = mIndentLines;
- // Need to redo the height because it may change due to changing indents.
- int totalHeight = mPaddingTop + mPaddingBottom;
- boolean first = true;
- for (int i = 0; i < count; i++) {
+ final int count = getChildCount();
+ int totalHeight;
+ for (int i = 0; i < count; ++i) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ lp.hide = true;
+ }
+
+ totalHeight = mPaddingTop + mPaddingBottom;
+ boolean first = true;
+ int linesRemaining = mMaxDisplayedLines;
- if (child.getVisibility() == GONE || lp.hide) {
+ // Starting from the bottom: we measure every view as if it were the only one. If it still
+ // fits, we take it, otherwise we stop there.
+ for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) {
+ if (getChildAt(i).getVisibility() == GONE) {
continue;
}
-
- if (child instanceof ImageFloatingTextView) {
- ImageFloatingTextView textChild = (ImageFloatingTextView) child;
- if (imageLines == 2 && textChild.getLineCount() > 2) {
- // HACK: If we need indent for two lines, and they're coming from the same
- // view, we need extra spacing to compensate for the lack of margins,
- // so add an extra line of indent.
- imageLines = 3;
- }
- boolean changed = textChild.setNumIndentLines(Math.max(0, imageLines));
- if (changed || !recalculateVisibility) {
- final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
- mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
- lp.width);
- // we want to measure it at most as high as it is currently, otherwise we'll
- // drop later lines
- final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
- targetHeight - child.getMeasuredHeight(), lp.height);
-
- child.measure(childWidthMeasureSpec, childHeightMeasureSpec);;
- }
- imageLines -= textChild.getLineCount();
+ final View child = getChildAt(i);
+ LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
+ MessagingChild messagingChild = null;
+ if (child instanceof MessagingChild) {
+ messagingChild = (MessagingChild) child;
+ messagingChild.setMaxDisplayedLines(linesRemaining);
}
+ int spacing = first ? 0 : mSpacing;
+ measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight
+ - mPaddingTop - mPaddingBottom + spacing);
- measuredWidth = Math.max(measuredWidth,
- child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
- + mPaddingLeft + mPaddingRight);
- totalHeight = Math.max(totalHeight, totalHeight + child.getMeasuredHeight() +
- lp.topMargin + lp.bottomMargin + (first ? 0 : mSpacing));
+ final int childHeight = child.getMeasuredHeight();
+ int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin +
+ lp.bottomMargin + spacing);
first = false;
+ int measureType = MessagingChild.MEASURED_NORMAL;
+ if (messagingChild != null) {
+ measureType = messagingChild.getMeasuredType();
+ linesRemaining -= messagingChild.getConsumedLines();
+ }
+ boolean isShortened = measureType == MessagingChild.MEASURED_SHORTENED;
+ boolean isTooSmall = measureType == MessagingChild.MEASURED_TOO_SMALL;
+ if (newHeight <= targetHeight && !isTooSmall) {
+ totalHeight = newHeight;
+ measuredWidth = Math.max(measuredWidth,
+ child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
+ + mPaddingLeft + mPaddingRight);
+ lp.hide = false;
+ if (isShortened || linesRemaining <= 0) {
+ break;
+ }
+ } else {
+ break;
+ }
}
-
setMeasuredDimension(
resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth),
widthMeasureSpec),
- resolveSize(Math.max(getSuggestedMinimumHeight(), totalHeight),
- heightMeasureSpec));
- mLastMeasuredWidth = widthSize;
+ Math.max(getSuggestedMinimumHeight(), totalHeight));
}
@Override
@@ -221,13 +155,22 @@ public class MessagingLinearLayout extends ViewGroup {
childTop = mPaddingTop;
boolean first = true;
-
+ final boolean shown = isShown();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
+ if (child.getVisibility() == GONE) {
+ continue;
+ }
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
- if (child.getVisibility() == GONE || lp.hide) {
+ MessagingChild messagingChild = (MessagingChild) child;
+ if (lp.hide) {
+ if (shown && lp.visibleBefore) {
+ messagingChild.hideAnimated();
+ }
+ lp.visibleBefore = false;
continue;
+ } else {
+ lp.visibleBefore = true;
}
final int childWidth = child.getMeasuredWidth();
@@ -251,14 +194,16 @@ public class MessagingLinearLayout extends ViewGroup {
first = false;
}
- mLastMeasuredWidth = NOT_MEASURED_BEFORE;
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.hide) {
- return true;
+ MessagingChild messagingChild = (MessagingChild) child;
+ if (!messagingChild.isHidingAnimated()) {
+ return true;
+ }
}
return super.drawChild(canvas, child, drawingTime);
}
@@ -284,31 +229,37 @@ public class MessagingLinearLayout extends ViewGroup {
}
/**
- * Sets how many lines should be indented to avoid a floating image.
+ * Sets how many lines should be displayed at most
*/
@RemotableViewMethod
- public void setNumIndentLines(int numberLines) {
- mIndentLines = numberLines;
+ public void setMaxDisplayedLines(int numberLines) {
+ mMaxDisplayedLines = numberLines;
}
- /**
- * Set id of the child that's also visible in the contracted layout.
- */
- @RemotableViewMethod
- public void setContractedChildId(int contractedChildId) {
- mContractedChildId = contractedChildId;
+ public void setMessagingLayout(MessagingLayout layout) {
+ mMessagingLayout = layout;
}
- /**
- * Get id of the child that's also visible in the contracted layout.
- */
- public int getContractedChildId() {
- return mContractedChildId;
+ public MessagingLayout getMessagingLayout() {
+ return mMessagingLayout;
+ }
+
+ public interface MessagingChild {
+ int MEASURED_NORMAL = 0;
+ int MEASURED_SHORTENED = 1;
+ int MEASURED_TOO_SMALL = 2;
+
+ int getMeasuredType();
+ int getConsumedLines();
+ void setMaxDisplayedLines(int lines);
+ void hideAnimated();
+ boolean isHidingAnimated();
}
public static class LayoutParams extends MarginLayoutParams {
- boolean hide = false;
+ public boolean hide = false;
+ public boolean visibleBefore = false;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java
new file mode 100644
index 000000000000..f09621f544bc
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingMessage.java
@@ -0,0 +1,197 @@
+/*
+ * 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.internal.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.app.Notification;
+import android.content.Context;
+import android.text.Layout;
+import android.util.AttributeSet;
+import android.util.Pools;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.RemoteViews;
+
+import com.android.internal.R;
+
+import java.util.Objects;
+
+/**
+ * A message of a {@link MessagingLayout}.
+ */
+@RemoteViews.RemoteView
+public class MessagingMessage extends ImageFloatingTextView implements
+ MessagingLinearLayout.MessagingChild {
+
+ private static Pools.SimplePool<MessagingMessage> sInstancePool
+ = new Pools.SynchronizedPool<>(10);
+ private Notification.MessagingStyle.Message mMessage;
+ private MessagingGroup mGroup;
+ private boolean mIsHistoric;
+ private boolean mIsHidingAnimated;
+
+ public MessagingMessage(@NonNull Context context) {
+ super(context);
+ }
+
+ public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ private void setMessage(Notification.MessagingStyle.Message message) {
+ mMessage = message;
+ setText(message.getText());
+ }
+
+ public Notification.MessagingStyle.Message getMessage() {
+ return mMessage;
+ }
+
+ boolean sameAs(Notification.MessagingStyle.Message message) {
+ if (!Objects.equals(message.getText(), mMessage.getText())) {
+ return false;
+ }
+ if (!Objects.equals(message.getSender(), mMessage.getSender())) {
+ return false;
+ }
+ if (!Objects.equals(message.getTimestamp(), mMessage.getTimestamp())) {
+ return false;
+ }
+ return true;
+ }
+
+ boolean sameAs(MessagingMessage message) {
+ return sameAs(message.getMessage());
+ }
+
+ static MessagingMessage createMessage(MessagingLayout layout,
+ Notification.MessagingStyle.Message m) {
+ MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
+ MessagingMessage createdMessage = sInstancePool.acquire();
+ if (createdMessage == null) {
+ createdMessage = (MessagingMessage) LayoutInflater.from(layout.getContext()).inflate(
+ R.layout.notification_template_messaging_message, messagingLinearLayout,
+ false);
+ }
+ createdMessage.setMessage(m);
+ return createdMessage;
+ }
+
+ public void removeMessage() {
+ mGroup.removeMessage(this);
+ }
+
+ public void recycle() {
+ mGroup = null;
+ mMessage = null;
+ setAlpha(1.0f);
+ setTranslationY(0);
+ sInstancePool.release(this);
+ }
+
+ public void setMessagingGroup(MessagingGroup group) {
+ mGroup = group;
+ }
+
+ public static void dropCache() {
+ sInstancePool = new Pools.SynchronizedPool<>(10);
+ }
+
+ public void setIsHistoric(boolean isHistoric) {
+ mIsHistoric = isHistoric;
+ }
+
+ public MessagingGroup getGroup() {
+ return mGroup;
+ }
+
+ @Override
+ public int getMeasuredType() {
+ boolean measuredTooSmall = getMeasuredHeight()
+ < getLayoutHeight() + getPaddingTop() + getPaddingBottom();
+ if (measuredTooSmall) {
+ return MEASURED_TOO_SMALL;
+ } else {
+ Layout layout = getLayout();
+ if (layout == null) {
+ return MEASURED_TOO_SMALL;
+ }
+ if (layout.getEllipsisCount(layout.getLineCount() - 1) > 0) {
+ return MEASURED_SHORTENED;
+ } else {
+ return MEASURED_NORMAL;
+ }
+ }
+ }
+
+ @Override
+ public void hideAnimated() {
+ setIsHidingAnimated(true);
+ mGroup.performRemoveAnimation(this, () -> setIsHidingAnimated(false));
+ }
+
+ private void setIsHidingAnimated(boolean isHiding) {
+ ViewParent parent = getParent();
+ mIsHidingAnimated = isHiding;
+ invalidate();
+ if (parent instanceof ViewGroup) {
+ ((ViewGroup) parent).invalidate();
+ }
+ }
+
+ @Override
+ public boolean isHidingAnimated() {
+ return mIsHidingAnimated;
+ }
+
+ @Override
+ public void setMaxDisplayedLines(int lines) {
+ setMaxLines(lines);
+ }
+
+ @Override
+ public int getConsumedLines() {
+ return getLineCount();
+ }
+
+ public int getLayoutHeight() {
+ Layout layout = getLayout();
+ if (layout == null) {
+ return 0;
+ }
+ return layout.getHeight();
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+}
diff --git a/core/java/com/android/internal/widget/MessagingPropertyAnimator.java b/core/java/com/android/internal/widget/MessagingPropertyAnimator.java
new file mode 100644
index 000000000000..7c3ab7f9547f
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingPropertyAnimator.java
@@ -0,0 +1,233 @@
+/*
+ * 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.internal.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.util.IntProperty;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+import com.android.internal.R;
+
+/**
+ * A listener that automatically starts animations when the layout bounds change.
+ */
+public class MessagingPropertyAnimator implements View.OnLayoutChangeListener {
+ static final long APPEAR_ANIMATION_LENGTH = 210;
+ private static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
+ public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
+ private static final int TAG_LOCAL_TRANSLATION_ANIMATOR = R.id.tag_local_translation_y_animator;
+ private static final int TAG_LOCAL_TRANSLATION_Y = R.id.tag_local_translation_y;
+ private static final int TAG_LAYOUT_TOP = R.id.tag_layout_top;
+ private static final int TAG_ALPHA_ANIMATOR = R.id.tag_alpha_animator;
+ private static final ViewClippingUtil.ClippingParameters CLIPPING_PARAMETERS =
+ view -> view.getId() == com.android.internal.R.id.notification_messaging;
+ private static final IntProperty<View> LOCAL_TRANSLATION_Y =
+ new IntProperty<View>("localTranslationY") {
+ @Override
+ public void setValue(View object, int value) {
+ setLocalTranslationY(object, value);
+ }
+
+ @Override
+ public Integer get(View object) {
+ return getLocalTranslationY(object);
+ }
+ };
+
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+ int oldTop, int oldRight, int oldBottom) {
+ int oldHeight = oldBottom - oldTop;
+ Integer layoutTop = (Integer) v.getTag(TAG_LAYOUT_TOP);
+ if (layoutTop != null) {
+ oldTop = layoutTop;
+ }
+ int topChange = oldTop - top;
+ if (oldHeight == 0 || topChange == 0 || !v.isShown() || isGone(v)) {
+ // First layout
+ return;
+ }
+ if (layoutTop != null) {
+ v.setTagInternal(TAG_LAYOUT_TOP, top);
+ }
+ int newHeight = bottom - top;
+ int heightDifference = oldHeight - newHeight;
+ // Only add the difference if the height changes and it's getting smaller
+ heightDifference = Math.max(heightDifference, 0);
+ startLocalTranslationFrom(v, topChange + heightDifference + getLocalTranslationY(v));
+ }
+
+ private boolean isGone(View view) {
+ if (view.getVisibility() == View.GONE) {
+ return true;
+ }
+ final ViewGroup.LayoutParams lp = view.getLayoutParams();
+ if (lp instanceof MessagingLinearLayout.LayoutParams
+ && ((MessagingLinearLayout.LayoutParams) lp).hide) {
+ return true;
+ }
+ return false;
+ }
+
+ public static void startLocalTranslationFrom(View v, int startTranslation) {
+ startLocalTranslationFrom(v, startTranslation, MessagingLayout.FAST_OUT_SLOW_IN);
+ }
+
+ public static void startLocalTranslationFrom(View v, int startTranslation,
+ Interpolator interpolator) {
+ startLocalTranslation(v, startTranslation, 0, interpolator);
+ }
+
+ public static void startLocalTranslationTo(View v, int endTranslation,
+ Interpolator interpolator) {
+ startLocalTranslation(v, getLocalTranslationY(v), endTranslation, interpolator);
+ }
+
+ public static int getLocalTranslationY(View v) {
+ Integer tag = (Integer) v.getTag(TAG_LOCAL_TRANSLATION_Y);
+ if (tag == null) {
+ return 0;
+ }
+ return tag;
+ }
+
+ private static void setLocalTranslationY(View v, int value) {
+ v.setTagInternal(TAG_LOCAL_TRANSLATION_Y, value);
+ updateTopAndBottom(v);
+ }
+
+ private static void updateTopAndBottom(View v) {
+ int layoutTop = (int) v.getTag(TAG_LAYOUT_TOP);
+ int localTranslation = getLocalTranslationY(v);
+ int height = v.getHeight();
+ v.setTop(layoutTop + localTranslation);
+ v.setBottom(layoutTop + height + localTranslation);
+ }
+
+ private static void startLocalTranslation(final View v, int start, int end,
+ Interpolator interpolator) {
+ ObjectAnimator existing = (ObjectAnimator) v.getTag(TAG_LOCAL_TRANSLATION_ANIMATOR);
+ if (existing != null) {
+ existing.cancel();
+ }
+ ObjectAnimator animator = ObjectAnimator.ofInt(v, LOCAL_TRANSLATION_Y, start, end);
+ Integer layoutTop = (Integer) v.getTag(TAG_LAYOUT_TOP);
+ if (layoutTop == null) {
+ layoutTop = v.getTop();
+ v.setTagInternal(TAG_LAYOUT_TOP, layoutTop);
+ }
+ setLocalTranslationY(v, start);
+ animator.setInterpolator(interpolator);
+ animator.setDuration(APPEAR_ANIMATION_LENGTH);
+ animator.addListener(new AnimatorListenerAdapter() {
+ public boolean mCancelled;
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ v.setTagInternal(TAG_LOCAL_TRANSLATION_ANIMATOR, null);
+ setClippingDeactivated(v, false);
+ if (!mCancelled) {
+ setLocalTranslationY(v, 0);
+ v.setTagInternal(TAG_LAYOUT_TOP, null);
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+ });
+ setClippingDeactivated(v, true);
+ v.setTagInternal(TAG_LOCAL_TRANSLATION_ANIMATOR, animator);
+ animator.start();
+ }
+
+ public static void fadeIn(final View v) {
+ ObjectAnimator existing = (ObjectAnimator) v.getTag(TAG_ALPHA_ANIMATOR);
+ if (existing != null) {
+ existing.cancel();
+ }
+ if (v.getVisibility() == View.INVISIBLE) {
+ v.setVisibility(View.VISIBLE);
+ }
+ ObjectAnimator animator = ObjectAnimator.ofFloat(v, View.ALPHA,
+ 0.0f, 1.0f);
+ v.setAlpha(0.0f);
+ animator.setInterpolator(ALPHA_IN);
+ animator.setDuration(APPEAR_ANIMATION_LENGTH);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ v.setTagInternal(TAG_ALPHA_ANIMATOR, null);
+ updateLayerType(v, false /* animating */);
+ }
+ });
+ updateLayerType(v, true /* animating */);
+ v.setTagInternal(TAG_ALPHA_ANIMATOR, animator);
+ animator.start();
+ }
+
+ private static void updateLayerType(View view, boolean animating) {
+ if (view.hasOverlappingRendering() && animating) {
+ view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ } else if (view.getLayerType() == View.LAYER_TYPE_HARDWARE) {
+ view.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ }
+
+ public static void fadeOut(final View view, Runnable endAction) {
+ ObjectAnimator existing = (ObjectAnimator) view.getTag(TAG_ALPHA_ANIMATOR);
+ if (existing != null) {
+ existing.cancel();
+ }
+ ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.ALPHA,
+ view.getAlpha(), 0.0f);
+ animator.setInterpolator(ALPHA_OUT);
+ animator.setDuration(APPEAR_ANIMATION_LENGTH);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setTagInternal(TAG_ALPHA_ANIMATOR, null);
+ updateLayerType(view, false /* animating */);
+ if (endAction != null) {
+ endAction.run();
+ }
+ }
+ });
+ updateLayerType(view, true /* animating */);
+ view.setTagInternal(TAG_ALPHA_ANIMATOR, animator);
+ animator.start();
+ }
+
+ public static void setClippingDeactivated(final View transformedView, boolean deactivated) {
+ ViewClippingUtil.setClippingDeactivated(transformedView, deactivated,
+ CLIPPING_PARAMETERS);
+ }
+
+ public static boolean isAnimatingTranslation(View v) {
+ return v.getTag(TAG_LOCAL_TRANSLATION_ANIMATOR) != null;
+ }
+
+ public static boolean isAnimatingAlpha(View v) {
+ return v.getTag(TAG_ALPHA_ANIMATOR) != null;
+ }
+}
diff --git a/core/java/com/android/internal/widget/RemeasuringLinearLayout.java b/core/java/com/android/internal/widget/RemeasuringLinearLayout.java
new file mode 100644
index 000000000000..e352b45ef413
--- /dev/null
+++ b/core/java/com/android/internal/widget/RemeasuringLinearLayout.java
@@ -0,0 +1,68 @@
+/*
+ * 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.internal.widget;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+
+/**
+ * A LinearLayout that sets it's height again after the last measure pass. This is needed for
+ * MessagingLayouts where groups need to be able to snap it's height to.
+ */
+@RemoteViews.RemoteView
+public class RemeasuringLinearLayout extends LinearLayout {
+
+ public RemeasuringLinearLayout(Context context) {
+ super(context);
+ }
+
+ public RemeasuringLinearLayout(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public RemeasuringLinearLayout(Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public RemeasuringLinearLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ int count = getChildCount();
+ int height = 0;
+ for (int i = 0; i < count; ++i) {
+ final View child = getChildAt(i);
+ if (child == null || child.getVisibility() == View.GONE) {
+ continue;
+ }
+
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ height = Math.max(height, height + child.getMeasuredHeight() + lp.topMargin +
+ lp.bottomMargin);
+ }
+ setMeasuredDimension(getMeasuredWidth(), height);
+ }
+}
diff --git a/core/java/com/android/internal/widget/ViewClippingUtil.java b/core/java/com/android/internal/widget/ViewClippingUtil.java
new file mode 100644
index 000000000000..59bbed443193
--- /dev/null
+++ b/core/java/com/android/internal/widget/ViewClippingUtil.java
@@ -0,0 +1,108 @@
+/*
+ * 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.internal.widget;
+
+import android.util.ArraySet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+import com.android.internal.R;
+
+/**
+ * A utility class that allows to clip views and their parents to allow for better transitions
+ */
+public class ViewClippingUtil {
+ private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
+ private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
+ private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
+
+ public static void setClippingDeactivated(final View transformedView, boolean deactivated,
+ ClippingParameters clippingParameters) {
+ if (!deactivated && !clippingParameters.isClippingEnablingAllowed(transformedView)) {
+ return;
+ }
+ if (!(transformedView.getParent() instanceof ViewGroup)) {
+ return;
+ }
+ ViewGroup parent = (ViewGroup) transformedView.getParent();
+ while (true) {
+ if (!deactivated && !clippingParameters.isClippingEnablingAllowed(transformedView)) {
+ return;
+ }
+ ArraySet<View> clipSet = (ArraySet<View>) parent.getTag(CLIP_CLIPPING_SET);
+ if (clipSet == null) {
+ clipSet = new ArraySet<>();
+ parent.setTagInternal(CLIP_CLIPPING_SET, clipSet);
+ }
+ Boolean clipChildren = (Boolean) parent.getTag(CLIP_CHILDREN_TAG);
+ if (clipChildren == null) {
+ clipChildren = parent.getClipChildren();
+ parent.setTagInternal(CLIP_CHILDREN_TAG, clipChildren);
+ }
+ Boolean clipToPadding = (Boolean) parent.getTag(CLIP_TO_PADDING);
+ if (clipToPadding == null) {
+ clipToPadding = parent.getClipToPadding();
+ parent.setTagInternal(CLIP_TO_PADDING, clipToPadding);
+ }
+ if (!deactivated) {
+ clipSet.remove(transformedView);
+ if (clipSet.isEmpty()) {
+ parent.setClipChildren(clipChildren);
+ parent.setClipToPadding(clipToPadding);
+ parent.setTagInternal(CLIP_CLIPPING_SET, null);
+ clippingParameters.onClippingStateChanged(parent, true);
+ }
+ } else {
+ clipSet.add(transformedView);
+ parent.setClipChildren(false);
+ parent.setClipToPadding(false);
+ clippingParameters.onClippingStateChanged(parent, false);
+ }
+ if (clippingParameters.shouldFinish(parent)) {
+ return;
+ }
+ final ViewParent viewParent = parent.getParent();
+ if (viewParent instanceof ViewGroup) {
+ parent = (ViewGroup) viewParent;
+ } else {
+ return;
+ }
+ }
+ }
+
+ public interface ClippingParameters {
+ /**
+ * Should we stop clipping at this view? If true is returned, {@param view} is the last view
+ * where clipping is activated / deactivated.
+ */
+ boolean shouldFinish(View view);
+
+ /**
+ * Is it allowed to enable clipping on this view.
+ */
+ default boolean isClippingEnablingAllowed(View view) {
+ return !MessagingPropertyAnimator.isAnimatingTranslation(view);
+ }
+
+ /**
+ * A method that is called whenever the view starts clipping again / stops clipping to the
+ * children and padding.
+ */
+ default void onClippingStateChanged(View view, boolean isClipping) {};
+ }
+}
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index f0ac79acd7de..d18c17269181 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -554,18 +554,6 @@ static void android_os_Parcel_closeFileDescriptor(JNIEnv* env, jclass clazz, job
}
}
-static void android_os_Parcel_clearFileDescriptor(JNIEnv* env, jclass clazz, jobject object)
-{
- if (object == NULL) {
- jniThrowNullPointerException(env, NULL);
- return;
- }
- int fd = jniGetFDFromFileDescriptor(env, object);
- if (fd >= 0) {
- jniSetFileDescriptorOfFD(env, object, -1);
- }
-}
-
static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz)
{
Parcel* parcel = new Parcel();
@@ -811,7 +799,6 @@ static const JNINativeMethod gParcelMethods[] = {
{"openFileDescriptor", "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_openFileDescriptor},
{"dupFileDescriptor", "(Ljava/io/FileDescriptor;)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_dupFileDescriptor},
{"closeFileDescriptor", "(Ljava/io/FileDescriptor;)V", (void*)android_os_Parcel_closeFileDescriptor},
- {"clearFileDescriptor", "(Ljava/io/FileDescriptor;)V", (void*)android_os_Parcel_clearFileDescriptor},
{"nativeCreate", "()J", (void*)android_os_Parcel_create},
{"nativeFreeBuffer", "(J)J", (void*)android_os_Parcel_freeBuffer},
diff --git a/core/res/res/drawable/ic_reply_notification_large.xml b/core/res/res/drawable/ic_reply_notification_large.xml
new file mode 100644
index 000000000000..e75afddfead3
--- /dev/null
+++ b/core/res/res/drawable/ic_reply_notification_large.xml
@@ -0,0 +1,29 @@
+<!--
+ ~ 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
+ -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:inset="8dp">
+ <vector android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M10.0,9.0L10.0,5.0l-7.0,7.0 7.0,7.0l0.0,-4.1c5.0,0.0 8.5,1.6 11.0,5.1 -1.0,-5.0 -4.0,-10.0 -11.0,-11.0z"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0 0h24v24H0z"/>
+ </vector>
+</inset>
diff --git a/core/res/res/drawable/messaging_message_background.xml b/core/res/res/drawable/messaging_message_background.xml
new file mode 100644
index 000000000000..8a2096a62473
--- /dev/null
+++ b/core/res/res/drawable/messaging_message_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle"
+ android:tint="#14000000">
+ <corners android:radius="4dp" />
+ <padding android:bottom="6dp"
+ android:left="8dp"
+ android:right="8dp"
+ android:top="6dp" />
+</shape>
diff --git a/core/res/res/layout/chooser_row.xml b/core/res/res/layout/chooser_row.xml
index 6c1271d6d72b..cf812603fb89 100644
--- a/core/res/res/layout/chooser_row.xml
+++ b/core/res/res/layout/chooser_row.xml
@@ -22,8 +22,7 @@
android:layout_height="100dp"
android:gravity="start|top"
android:paddingStart="@dimen/chooser_grid_padding"
- android:paddingEnd="@dimen/chooser_grid_padding"
- android:weightSum="4">
+ android:paddingEnd="@dimen/chooser_grid_padding">
</LinearLayout>
diff --git a/core/res/res/layout/notification_template_material_messaging.xml b/core/res/res/layout/notification_template_material_messaging.xml
index fd5154af3b0c..f72230be0833 100644
--- a/core/res/res/layout/notification_template_material_messaging.xml
+++ b/core/res/res/layout/notification_template_material_messaging.xml
@@ -14,13 +14,17 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.internal.widget.MessagingLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="messaging"
>
- <include layout="@layout/notification_template_header" />
+ <include layout="@layout/notification_template_header"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/notification_header_height"
+ android:layout_marginEnd="56dp"/>
<LinearLayout
android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
@@ -39,7 +43,6 @@
android:paddingEnd="@dimen/notification_content_margin_end"
android:minHeight="@dimen/notification_min_content_height"
android:layout_marginBottom="@dimen/notification_content_margin_bottom"
- android:clipToPadding="false"
android:orientation="vertical"
>
<include layout="@layout/notification_template_part_line1"
@@ -50,31 +53,14 @@
android:id="@+id/notification_messaging"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:spacing="@dimen/notification_messaging_spacing" >
- <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text0"
- style="@style/Widget.Material.Notification.MessagingText"
- />
- <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text1"
- style="@style/Widget.Material.Notification.MessagingText"
- />
- <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text2"
- style="@style/Widget.Material.Notification.MessagingText"
- />
- <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text3"
- style="@style/Widget.Material.Notification.MessagingText"
- />
- <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text4"
- style="@style/Widget.Material.Notification.MessagingText"
- />
- <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text5"
- style="@style/Widget.Material.Notification.MessagingText"
- />
- <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text6"
- style="@style/Widget.Material.Notification.MessagingText"
- />
- </com.android.internal.widget.MessagingLinearLayout>
+ android:layout_marginTop="8dp"
+ android:spacing="@dimen/notification_messaging_spacing" />
</LinearLayout>
</LinearLayout>
<include layout="@layout/notification_material_action_list" />
- <include layout="@layout/notification_template_right_icon" />
-</FrameLayout>
+ <include layout="@layout/notification_template_right_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="18dp"
+ android:layout_gravity="top|end"/>
+</com.android.internal.widget.MessagingLayout>
diff --git a/core/res/res/layout/notification_template_messaging_group.xml b/core/res/res/layout/notification_template_messaging_group.xml
new file mode 100644
index 000000000000..8973ceb5c3a1
--- /dev/null
+++ b/core/res/res/layout/notification_template_messaging_group.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 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
+ -->
+<com.android.internal.widget.MessagingGroup
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+ <ImageView
+ android:id="@+id/message_icon"
+ android:layout_width="@dimen/messaging_avatar_size"
+ android:layout_height="@dimen/messaging_avatar_size"
+ android:layout_marginEnd="8dp"
+ android:scaleType="centerCrop"
+ android:importantForAccessibility="no" />
+ <com.android.internal.widget.RemeasuringLinearLayout
+ android:id="@+id/message_group_and_sender_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <com.android.internal.widget.MessagingLinearLayout
+ android:id="@+id/group_message_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:spacing="2dp"
+ android:layout_weight="1"/>
+ <com.android.internal.widget.ImageFloatingTextView
+ android:id="@+id/message_name"
+ style="@style/Widget.Material.Notification.MessagingName"
+ android:layout_width="wrap_content"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ android:paddingTop="2dp"
+ />
+ </com.android.internal.widget.RemeasuringLinearLayout>
+</com.android.internal.widget.MessagingGroup>
diff --git a/core/res/res/layout/notification_template_messaging_message.xml b/core/res/res/layout/notification_template_messaging_message.xml
new file mode 100644
index 000000000000..ab6466cc7b3c
--- /dev/null
+++ b/core/res/res/layout/notification_template_messaging_message.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<com.android.internal.widget.MessagingMessage
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/message_text"
+ style="@style/Widget.Material.Notification.MessagingText"
+/>
diff --git a/core/res/res/layout/notification_template_right_icon.xml b/core/res/res/layout/notification_template_right_icon.xml
index d379256f7753..8fb28877a81f 100644
--- a/core/res/res/layout/notification_template_right_icon.xml
+++ b/core/res/res/layout/notification_template_right_icon.xml
@@ -19,12 +19,12 @@
android:id="@+id/right_icon_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginTop="36dp"
android:layout_gravity="top|end">
<ImageView android:id="@+id/right_icon"
android:layout_width="@dimen/notification_right_icon_size"
android:layout_height="@dimen/notification_right_icon_size"
android:layout_gravity="top|end"
- android:layout_marginTop="36dp"
android:layout_marginEnd="@dimen/notification_content_margin_end"
android:scaleType="centerCrop"
android:importantForAccessibility="no" />
@@ -32,7 +32,7 @@
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="top|end"
- android:layout_marginTop="64dp"
+ android:layout_marginTop="28dp"
android:layout_marginEnd="12dp"
android:background="@drawable/notification_reply_background"
android:src="@drawable/ic_reply_notification"
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 57312e7444e0..2389d4d829a8 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -146,7 +146,7 @@
<string name="httpErrorConnect" msgid="8714273236364640549">"Tidak dapat tersambung ke server."</string>
<string name="httpErrorIO" msgid="2340558197489302188">"Tidak dapat berkomunikasi dengan server. Coba lagi nanti."</string>
<string name="httpErrorTimeout" msgid="4743403703762883954">"Sambungan ke server terputus."</string>
- <string name="httpErrorRedirectLoop" msgid="8679596090392779516">"Laman ini berisi terlalu banyak pengalihan server."</string>
+ <string name="httpErrorRedirectLoop" msgid="8679596090392779516">"Halaman ini berisi terlalu banyak pengalihan server."</string>
<string name="httpErrorUnsupportedScheme" msgid="5015730812906192208">"Protokol tidak didukung."</string>
<string name="httpErrorFailedSslHandshake" msgid="96549606000658641">"Tidak dapat membuat sambungan aman."</string>
<string name="httpErrorBadUrl" msgid="3636929722728881972">"Tidak dapat membuka halaman karena URL tidak valid."</string>
@@ -295,7 +295,7 @@
<string name="capability_title_canControlMagnification" msgid="3593493281059424855">"Mengontrol perbesaran layar"</string>
<string name="capability_desc_canControlMagnification" msgid="4791858203568383773">"Mengontrol tingkat zoom dan pemosisian layar."</string>
<string name="capability_title_canPerformGestures" msgid="7418984730362576862">"Melakukan isyarat"</string>
- <string name="capability_desc_canPerformGestures" msgid="8296373021636981249">"Dapat mengetuk, menggesek, mencubit, dan melakukan isyarat lainnya."</string>
+ <string name="capability_desc_canPerformGestures" msgid="8296373021636981249">"Dapat mengetuk, menggeser, mencubit, dan melakukan isyarat lainnya."</string>
<string name="capability_title_canCaptureFingerprintGestures" msgid="6309568287512278670">"Gestur sidik jari"</string>
<string name="capability_desc_canCaptureFingerprintGestures" msgid="7102111919385702482">"Dapat merekam gestur yang dilakukan di sensor sidik jari perangkat."</string>
<string name="permlab_statusBar" msgid="7417192629601890791">"nonaktifkan atau ubah bilah status"</string>
@@ -318,8 +318,8 @@
<string name="permdesc_receiveMms" msgid="533019437263212260">"Memungkinkan aplikasi menerima dan memproses pesan MMS. Ini artinya aplikasi dapat memantau atau menghapus pesan yang dikirim ke perangkat Anda tanpa menunjukkannya kepada Anda."</string>
<string name="permlab_readCellBroadcasts" msgid="1598328843619646166">"membaca pesan siaran seluler"</string>
<string name="permdesc_readCellBroadcasts" msgid="6361972776080458979">"Mengizinkan aplikasi membaca pesan siaran seluler yang diterima perangkat Anda. Notifikasi siaran seluler dikirimkan di beberapa lokasi untuk memperingatkan Anda tentang situasi darurat. Aplikasi berbahaya dapat mengganggu kinerja atau operasi perangkat Anda saat siaran seluler darurat diterima."</string>
- <string name="permlab_subscribedFeedsRead" msgid="4756609637053353318">"baca umpan langganan"</string>
- <string name="permdesc_subscribedFeedsRead" msgid="5557058907906144505">"Mengizinkan apl mendapatkan detail tentang umpan yang saat ini sedang disinkronkan."</string>
+ <string name="permlab_subscribedFeedsRead" msgid="4756609637053353318">"baca feed langganan"</string>
+ <string name="permdesc_subscribedFeedsRead" msgid="5557058907906144505">"Mengizinkan apl mendapatkan detail tentang feed yang saat ini sedang disinkronkan."</string>
<string name="permlab_sendSms" msgid="7544599214260982981">"mengirim dan melihat pesan SMS"</string>
<string name="permdesc_sendSms" msgid="7094729298204937667">"Memungkinkan aplikasi mengirim pesan SMS. Izin ini dapat mengakibatkan biaya tak terduga. Aplikasi berbahaya dapat membebankan biaya kepada Anda dengan mengirim pesan tanpa konfirmasi dari Anda."</string>
<string name="permlab_readSms" msgid="8745086572213270480">"membaca pesan teks (SMS atau MMS) Anda"</string>
@@ -802,11 +802,11 @@
<string name="factorytest_not_system" msgid="4435201656767276723">"Tindakan FACTORY_TEST hanya didukung untuk paket yang terpasang pada /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Tidak ada paket yang memberikan tindakan FACTORY_TEST."</string>
<string name="factorytest_reboot" msgid="6320168203050791643">"Mulai ulang"</string>
- <string name="js_dialog_title" msgid="1987483977834603872">"Laman pada \"<xliff:g id="TITLE">%s</xliff:g>\" menyatakan:"</string>
+ <string name="js_dialog_title" msgid="1987483977834603872">"Halaman pada \"<xliff:g id="TITLE">%s</xliff:g>\" menyatakan:"</string>
<string name="js_dialog_title_default" msgid="6961903213729667573">"JavaScript"</string>
<string name="js_dialog_before_unload_title" msgid="2619376555525116593">"Konfirmasi Navigasi"</string>
- <string name="js_dialog_before_unload_positive_button" msgid="3112752010600484130">"Keluar dari Laman ini"</string>
- <string name="js_dialog_before_unload_negative_button" msgid="5614861293026099715">"Tetap di Laman ini"</string>
+ <string name="js_dialog_before_unload_positive_button" msgid="3112752010600484130">"Keluar dari Halaman ini"</string>
+ <string name="js_dialog_before_unload_negative_button" msgid="5614861293026099715">"Tetap di Halaman ini"</string>
<string name="js_dialog_before_unload" msgid="3468816357095378590">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nYakin ingin beranjak dari halaman ini?"</string>
<string name="save_password_label" msgid="6860261758665825069">"Konfirmasi"</string>
<string name="double_tap_toast" msgid="4595046515400268881">"Kiat: Ketuk dua kali untuk memperbesar dan memperkecil."</string>
@@ -1042,7 +1042,7 @@
<string name="force_close" msgid="8346072094521265605">"Oke"</string>
<string name="report" msgid="4060218260984795706">"Laporkan"</string>
<string name="wait" msgid="7147118217226317732">"Tunggu"</string>
- <string name="webpage_unresponsive" msgid="3272758351138122503">"Laman ini tidak menanggapi.\n\nApakah Anda ingin menutupnya?"</string>
+ <string name="webpage_unresponsive" msgid="3272758351138122503">"Halaman ini tidak menanggapi.\n\nApakah Anda ingin menutupnya?"</string>
<string name="launch_warning_title" msgid="1547997780506713581">"Apl dialihkan"</string>
<string name="launch_warning_replace" msgid="6202498949970281412">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang berjalan."</string>
<string name="launch_warning_original" msgid="188102023021668683">"<xliff:g id="APP_NAME">%1$s</xliff:g> telah diluncurkan aslinya."</string>
@@ -1371,7 +1371,7 @@
<string name="shareactionprovider_share_with" msgid="806688056141131819">"Berbagi dengan"</string>
<string name="shareactionprovider_share_with_application" msgid="5627411384638389738">"Berbagi dengan <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
<string name="content_description_sliding_handle" msgid="415975056159262248">"Gagang geser. Sentuh &amp; tahan."</string>
- <string name="description_target_unlock_tablet" msgid="3833195335629795055">"Gesek untuk membuka kunci."</string>
+ <string name="description_target_unlock_tablet" msgid="3833195335629795055">"Geser untuk membuka kunci."</string>
<string name="action_bar_home_description" msgid="5293600496601490216">"Navigasi ke beranda"</string>
<string name="action_bar_up_description" msgid="2237496562952152589">"Navigasi naik"</string>
<string name="action_menu_overflow_description" msgid="2295659037509008453">"Opsi lainnya"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 96a9eda75b29..af37300f379c 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -36,7 +36,7 @@
<string name="serviceDisabled" msgid="1937553226592516411">"सेवा अक्षम केली गेली आहे."</string>
<string name="serviceRegistered" msgid="6275019082598102493">"नोंदणी यशस्वी झाली."</string>
<string name="serviceErased" msgid="1288584695297200972">"मिटवणे यशस्वी झाले."</string>
- <string name="passwordIncorrect" msgid="7612208839450128715">"अयोग्य संकेतशब्द."</string>
+ <string name="passwordIncorrect" msgid="7612208839450128715">"अयोग्य पासवर्ड."</string>
<string name="mmiComplete" msgid="8232527495411698359">"MMI पूर्ण."</string>
<string name="badPin" msgid="9015277645546710014">"आपण टाइप केलेला जुना पिन योग्य नाही."</string>
<string name="badPuk" msgid="5487257647081132201">"आपण टाइप केलेला PUK योग्य नाही."</string>
@@ -59,7 +59,7 @@
<string name="CfMmi" msgid="5123218989141573515">"कॉल फॉरवर्डिंग"</string>
<string name="CwMmi" msgid="9129678056795016867">"कॉल प्रतीक्षा"</string>
<string name="BaMmi" msgid="455193067926770581">"कॉल सोडून"</string>
- <string name="PwdMmi" msgid="7043715687905254199">"संकेतशब्द बदल"</string>
+ <string name="PwdMmi" msgid="7043715687905254199">"पासवर्ड बदल"</string>
<string name="PinMmi" msgid="3113117780361190304">"पिन बदल"</string>
<string name="CnipMmi" msgid="3110534680557857162">"कॉल करण्‍याचा नंबर आहे"</string>
<string name="CnirMmi" msgid="3062102121430548731">"कॉल करणारे नंबर प्रतिबंधित"</string>
@@ -192,9 +192,9 @@
<string name="reboot_to_update_title" msgid="6212636802536823850">"Android सिस्टम अपडेट"</string>
<string name="reboot_to_update_prepare" msgid="6305853831955310890">"अपडेट करण्याची तयारी करत आहे…"</string>
<string name="reboot_to_update_package" msgid="3871302324500927291">"अपडेट पॅकेज प्रक्रिया करत आहे…"</string>
- <string name="reboot_to_update_reboot" msgid="6428441000951565185">"रीस्टार्ट करीत आहे..."</string>
+ <string name="reboot_to_update_reboot" msgid="6428441000951565185">"रीस्टार्ट करत आहे..."</string>
<string name="reboot_to_reset_title" msgid="4142355915340627490">"फॅक्‍टरी डेटा रीसेट"</string>
- <string name="reboot_to_reset_message" msgid="2432077491101416345">"रीस्टार्ट करीत आहे..."</string>
+ <string name="reboot_to_reset_message" msgid="2432077491101416345">"रीस्टार्ट करत आहे..."</string>
<string name="shutdown_progress" msgid="2281079257329981203">"बंद होत आहे…"</string>
<string name="shutdown_confirm" product="tablet" msgid="3385745179555731470">"आपला टॅबलेट बंद होईल."</string>
<string name="shutdown_confirm" product="tv" msgid="476672373995075359">"आपला टीव्ही बंद होईल."</string>
@@ -215,7 +215,7 @@
<string name="bugreport_title" msgid="2667494803742548533">"बग रीपोर्ट घ्या"</string>
<string name="bugreport_message" msgid="398447048750350456">"ई-मेल संदेश म्हणून पाठविण्यासाठी, हे तुमच्या सद्य डिव्हाइस स्थितीविषयी माहिती संकलित करेल. बग रीपोर्ट सुरू करण्यापासून तो पाठविण्यापर्यंत थोडा वेळ लागेल; कृपया धीर धरा."</string>
<string name="bugreport_option_interactive_title" msgid="8635056131768862479">"परस्परसंवादी अहवाल"</string>
- <string name="bugreport_option_interactive_summary" msgid="229299488536107968">"बहुतांश प्रसंगांमध्ये याचा वापर करा. ते आपल्याला अहवालाच्या प्रगतीचा मागोवा घेण्याची, समस्येविषयी आणखी तपाशील प्रविष्ट करण्याची आणि स्क्रीनशॉट घेण्याची अनुमती देते. ते कदाचित अहवाल देण्यासाठी बराच वेळ घेणारे कमी-वापरलेले विभाग वगळू शकते."</string>
+ <string name="bugreport_option_interactive_summary" msgid="229299488536107968">"बहुतांश प्रसंगांमध्ये याचा वापर करा. ते आपल्याला अहवालाच्या प्रगतीचा मागोवा घेण्याची, समस्येविषयी आणखी तपाशील एंटर करण्याची आणि स्क्रीनशॉट घेण्याची अनुमती देते. ते कदाचित अहवाल देण्यासाठी बराच वेळ घेणारे कमी-वापरलेले विभाग वगळू शकते."</string>
<string name="bugreport_option_full_title" msgid="6354382025840076439">"संपूर्ण अहवाल"</string>
<string name="bugreport_option_full_summary" msgid="7210859858969115745">"तुमचे डिव्हाइस प्रतिसाद देत नाही किंवा खूप धीमे असते किंवा तुम्हाला सर्व अहवाल विभागांची आवश्यकता असते तेव्हा कमीतकमी सिस्टम हस्तक्षेपासाठी या पर्यायाचा वापर करा. तुम्हाला आणखी तपशील एंटर करण्याची किंवा अतिरिक्त स्क्रीनशॉट घेण्याची अनुमती देत नाही."</string>
<plurals name="bugreport_countdown" formatted="false" msgid="6878900193900090368">
@@ -461,13 +461,13 @@
<string name="permdesc_changeWimaxState" product="tv" msgid="6022307083934827718">"WiMAX नेटवर्कवरून टीव्ही कनेक्ट करण्यासाठी आणि त्यावरून टीव्ही डिस्कनेक्ट करण्यासाठी अॅपला अनुमती देते."</string>
<string name="permdesc_changeWimaxState" product="default" msgid="697025043004923798">"WiMAX नेटवर्कवर फोन कनेक्ट करण्यास आणि त्यावरून फोन डिस्कनेक्ट करण्यास अॅप ला अनुमती देते."</string>
<string name="permlab_bluetooth" msgid="6127769336339276828">"ब्लूटूथ डीव्हाइससह जोडा"</string>
- <string name="permdesc_bluetooth" product="tablet" msgid="3480722181852438628">"टॅबलेटवर ब्लूटूथ चे कॉंफिगरेशन पाहण्यासाठी आणि पेअर केलेल्या डीव्हाइससह कनेक्शन स्थापित करण्यासाठी आणि स्वीकारण्यासाठी, अॅप ला अनुमती देते."</string>
- <string name="permdesc_bluetooth" product="tv" msgid="3974124940101104206">"टीव्हीवर ब्लूटूथचे कॉंफिगरेशन पाहण्यासाठी आणि जोडलेल्या डीव्हाइससह कनेक्शन स्थापित करण्यासाठी आणि स्वीकारण्यासाठी अॅपला अनुमती देते."</string>
- <string name="permdesc_bluetooth" product="default" msgid="3207106324452312739">"फोनवर ब्लूटूथ चे कॉंफिगरेशन पाहण्यासाठी आणि पेअर केलेल्या डीव्हाइससह कनेक्शन स्थापित करण्यासाठी आणि स्वीकारण्यासाठी, अॅप ला अनुमती देते."</string>
+ <string name="permdesc_bluetooth" product="tablet" msgid="3480722181852438628">"टॅबलेटवर ब्लूटूथ चे कॉंफिगरेशन पाहण्यासाठी आणि पेअर केलेल्या डीव्हाइससह कनेक्शन इंस्टॉल करण्यासाठी आणि स्वीकारण्यासाठी, अॅप ला अनुमती देते."</string>
+ <string name="permdesc_bluetooth" product="tv" msgid="3974124940101104206">"टीव्हीवर ब्लूटूथचे कॉंफिगरेशन पाहण्यासाठी आणि जोडलेल्या डीव्हाइससह कनेक्शन इंस्टॉल करण्यासाठी आणि स्वीकारण्यासाठी अॅपला अनुमती देते."</string>
+ <string name="permdesc_bluetooth" product="default" msgid="3207106324452312739">"फोनवर ब्लूटूथ चे कॉंफिगरेशन पाहण्यासाठी आणि पेअर केलेल्या डीव्हाइससह कनेक्शन इंस्टॉल करण्यासाठी आणि स्वीकारण्यासाठी, अॅप ला अनुमती देते."</string>
<string name="permlab_nfc" msgid="4423351274757876953">"फील्ड जवळील कम्युनिकेशन नियंत्रित करा"</string>
<string name="permdesc_nfc" msgid="7120611819401789907">"फील्ड जवळील कम्युनिकेशन (NFC) टॅग, कार्डे आणि वाचक यांच्यासह संवाद करण्यासाठी अॅपला अनुमती देते."</string>
<string name="permlab_disableKeyguard" msgid="3598496301486439258">"आपले स्क्रीन लॉक अक्षम करा"</string>
- <string name="permdesc_disableKeyguard" msgid="6034203065077122992">"कीलॉक आणि कोणतीही संबद्ध संकेतशब्द सुरक्षितता अक्षम करण्यासाठी अॅप ला अनुमती देते. उदाहरणार्थ, येणारा फोन कॉल प्राप्त करताना फोन कीलॉक अक्षम करतो, नंतर जेव्हा कॉल समाप्त होतो तेव्हा तो कीलॉक पुन्हा-सक्षम करतो."</string>
+ <string name="permdesc_disableKeyguard" msgid="6034203065077122992">"कीलॉक आणि कोणतीही संबद्ध पासवर्ड सुरक्षितता अक्षम करण्यासाठी अॅप ला अनुमती देते. उदाहरणार्थ, येणारा फोन कॉल प्राप्त करताना फोन कीलॉक अक्षम करतो, नंतर जेव्हा कॉल समाप्त होतो तेव्हा तो कीलॉक पुन्हा-सक्षम करतो."</string>
<string name="permlab_manageFingerprint" msgid="5640858826254575638">"फिंगरप्रिंट हार्डवेअर व्यवस्थापित करा"</string>
<string name="permdesc_manageFingerprint" msgid="178208705828055464">"वापर करण्याकरिता फिंगरप्रिंट टेम्पलेट जोडण्यासाठी आणि हटविण्यासाठी पद्धती रद्द करण्यास अॅपला अनुमती देते."</string>
<string name="permlab_useFingerprint" msgid="3150478619915124905">"फिंगरप्रिंट हार्डवेअर वापरा"</string>
@@ -550,15 +550,15 @@
<string name="permdesc_bindCarrierServices" msgid="1391552602551084192">"वाहक सेवांवर प्रतिबद्ध करण्यासाठी होल्डरला अनुमती देते. सामान्य अॅप्ससाठी कधीही आवश्यकता नसावी."</string>
<string name="permlab_access_notification_policy" msgid="4247510821662059671">"व्यत्यय आणू नका अॅक्सेस करा"</string>
<string name="permdesc_access_notification_policy" msgid="3296832375218749580">"व्यत्यय आणू नका कॉन्फिगरेशन वाचण्यासाठी आणि लिहिण्यासाठी अॅपला अनुमती देते."</string>
- <string name="policylab_limitPassword" msgid="4497420728857585791">"संकेतशब्द नियम सेट करा"</string>
+ <string name="policylab_limitPassword" msgid="4497420728857585791">"पासवर्ड नियम सेट करा"</string>
<string name="policydesc_limitPassword" msgid="2502021457917874968">"स्क्रीन लॉक पासवर्ड आणि पिन मध्ये अनुमती दिलेले लांबी आणि वर्ण नियंत्रित करा."</string>
<string name="policylab_watchLogin" msgid="5091404125971980158">"स्क्रीन अनलॉक प्रयत्नांचे परीक्षण करा"</string>
- <string name="policydesc_watchLogin" product="tablet" msgid="3215729294215070072">"टाइप केलेल्या अयोग्य संकेतशब्दांच्या अंकांचे परीक्षण करा. स्क्रीन अनलॉक केली जाते, तेव्हा टॅबलेट लॉक करा किंवा बरेच संकेतशब्द टाइप केले असल्यास टॅबलेटचा सर्व डेटा मिटवा."</string>
- <string name="policydesc_watchLogin" product="TV" msgid="2707817988309890256">"स्क्रीन अनलॉक करताना टाइप केलेल्या चुकीच्या संकेतशब्दांच्या संख्येचे परीक्षण करा आणि टीव्ही लॉक करा किंवा अनेक चुकीचे संकेतशब्द टाइप केले असल्यास टीव्हीचा सर्व डेटा मिटवा."</string>
- <string name="policydesc_watchLogin" product="default" msgid="5712323091846761073">"टाइप केलेल्या अयोग्य संकेतशब्दांच्या अंकांचे परीक्षण करा. स्क्रीन अनलॉक केली जाते, तेव्हा फोन लॉक करा किंवा बरेच संकेतशब्द टाइप केले असल्यास फोनचा सर्व डेटा मिटवा."</string>
- <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="4280246270601044505">"स्क्रीन अनलॉक करताना टाइप केलेल्या चुकीच्या संकेतशब्दांच्या संख्येचे परीक्षण करा आणि टॅबलेट लॉक करा किंवा अनेक चुकीचे संकेतशब्द टाइप केले असल्यास या वापरकर्त्याचा सर्व डेटा मिटवा."</string>
- <string name="policydesc_watchLogin_secondaryUser" product="TV" msgid="3484832653564483250">"स्क्रीन अनलॉक करताना टाइप केलेल्या चुकीच्या संकेतशब्दांच्या संख्येचे परीक्षण करा आणि टीव्ही लॉक करा किंवा अनेक चुकीचे संकेतशब्द टाइप केले असल्यास या वापरकर्त्याचा सर्व डेटा मिटवा."</string>
- <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="2185480427217127147">"टाइप केलेल्या अयोग्य संकेतशब्दांच्या अंकांचे परीक्षण करा. स्क्रीन अनलॉक केली जाते, तेव्हा फोन लॉक करा किंवा बरेच संकेतशब्द टाइप केले असल्यास या वापरकर्त्याचा सर्व डेटा मिटवा."</string>
+ <string name="policydesc_watchLogin" product="tablet" msgid="3215729294215070072">"टाइप केलेल्या अयोग्य पासवर्डांच्या अंकांचे परीक्षण करा. स्क्रीन अनलॉक केली जाते, तेव्हा टॅबलेट लॉक करा किंवा बरेच पासवर्ड टाइप केले असल्यास टॅबलेटचा सर्व डेटा मिटवा."</string>
+ <string name="policydesc_watchLogin" product="TV" msgid="2707817988309890256">"स्क्रीन अनलॉक करताना टाइप केलेल्या चुकीच्या पासवर्डांच्या संख्येचे परीक्षण करा आणि टीव्ही लॉक करा किंवा अनेक चुकीचे पासवर्ड टाइप केले असल्यास टीव्हीचा सर्व डेटा मिटवा."</string>
+ <string name="policydesc_watchLogin" product="default" msgid="5712323091846761073">"टाइप केलेल्या अयोग्य पासवर्डांच्या अंकांचे परीक्षण करा. स्क्रीन अनलॉक केली जाते, तेव्हा फोन लॉक करा किंवा बरेच पासवर्ड टाइप केले असल्यास फोनचा सर्व डेटा मिटवा."</string>
+ <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="4280246270601044505">"स्क्रीन अनलॉक करताना टाइप केलेल्या चुकीच्या पासवर्डांच्या संख्येचे परीक्षण करा आणि टॅबलेट लॉक करा किंवा अनेक चुकीचे पासवर्ड टाइप केले असल्यास या वापरकर्त्याचा सर्व डेटा मिटवा."</string>
+ <string name="policydesc_watchLogin_secondaryUser" product="TV" msgid="3484832653564483250">"स्क्रीन अनलॉक करताना टाइप केलेल्या चुकीच्या पासवर्डांच्या संख्येचे परीक्षण करा आणि टीव्ही लॉक करा किंवा अनेक चुकीचे पासवर्ड टाइप केले असल्यास या वापरकर्त्याचा सर्व डेटा मिटवा."</string>
+ <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="2185480427217127147">"टाइप केलेल्या अयोग्य पासवर्डांच्या अंकांचे परीक्षण करा. स्क्रीन अनलॉक केली जाते, तेव्हा फोन लॉक करा किंवा बरेच पासवर्ड टाइप केले असल्यास या वापरकर्त्याचा सर्व डेटा मिटवा."</string>
<string name="policylab_resetPassword" msgid="4934707632423915395">"स्क्रीन लॉक बदला"</string>
<string name="policydesc_resetPassword" msgid="1278323891710619128">"स्क्रीन लॉक बदला."</string>
<string name="policylab_forceLock" msgid="2274085384704248431">"स्क्रीन लॉक करा"</string>
@@ -573,8 +573,8 @@
<string name="policydesc_wipeData_secondaryUser" product="default" msgid="6787904546711590238">"कोणत्याही चेतावणी शिवाय या वापरकर्त्याचा या फोनवरील डेटा मिटवा."</string>
<string name="policylab_setGlobalProxy" msgid="2784828293747791446">"डिव्हाइस समग्र प्रॉक्सी सेट करा"</string>
<string name="policydesc_setGlobalProxy" msgid="8459859731153370499">"धोरण सक्षम असताना वापरण्यासाठी डिव्हाइस समग्र प्रॉक्सी सेट करा. फक्त डिव्हाइस मालक समग्र प्रॉक्सी सेट करु शकतो."</string>
- <string name="policylab_expirePassword" msgid="5610055012328825874">"स्क्रीन लॉक संकेतशब्द कालबाह्यता सेट करा"</string>
- <string name="policydesc_expirePassword" msgid="5367525762204416046">"लॉक-स्क्रीन संकेतशब्द किती वारंवार बदलणे आवश्यक आहे ते बदला."</string>
+ <string name="policylab_expirePassword" msgid="5610055012328825874">"स्क्रीन लॉक पासवर्ड कालबाह्यता सेट करा"</string>
+ <string name="policydesc_expirePassword" msgid="5367525762204416046">"लॉक-स्क्रीन पासवर्ड किती वारंवार बदलणे आवश्यक आहे ते बदला."</string>
<string name="policylab_encryptedStorage" msgid="8901326199909132915">"स्टोरेज एंक्रिप्शन सेट करा"</string>
<string name="policydesc_encryptedStorage" msgid="2637732115325316992">"स्टोअर केलेला अॅप डेटा एंक्रिप्ट केला जाणे आवश्यक आहे."</string>
<string name="policylab_disableCamera" msgid="6395301023152297826">"कॅमेरे अक्षम करा"</string>
@@ -699,8 +699,8 @@
<string name="keyguard_password_enter_puk_code" msgid="4800725266925845333">"PUK आणि नवीन पिन कोड टाइप करा"</string>
<string name="keyguard_password_enter_puk_prompt" msgid="1341112146710087048">"PUK कोड"</string>
<string name="keyguard_password_enter_pin_prompt" msgid="8027680321614196258">"नवीन पिन कोड"</string>
- <string name="keyguard_password_entry_touch_hint" msgid="2644215452200037944"><font size="17">"संकेतशब्द टाइप करण्यासाठी टॅप करा"</font></string>
- <string name="keyguard_password_enter_password_code" msgid="1054721668279049780">"अनलॉक करण्यासाठी संकेतशब्द टाइप करा"</string>
+ <string name="keyguard_password_entry_touch_hint" msgid="2644215452200037944"><font size="17">"पासवर्ड टाइप करण्यासाठी टॅप करा"</font></string>
+ <string name="keyguard_password_enter_password_code" msgid="1054721668279049780">"अनलॉक करण्यासाठी पासवर्ड टाइप करा"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="6391755146112503443">"अनलॉक करण्यासाठी पिन टाइप करा"</string>
<string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"अयोग्य पिन कोड."</string>
<string name="keyguard_label_text" msgid="861796461028298424">"अनलॉक करण्यासाठी, मेनू दाबा नंतर 0."</string>
@@ -739,7 +739,7 @@
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"सिम कार्ड लॉक केलेले आहे."</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"सिम कार्ड अनलॉक करत आहे…"</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6481623830344107222">"तुम्ही आपला अनलॉक पॅटर्न <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा अयोग्यरितीने काढला. \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string>
- <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="2725973286239344555">"आपण आपला संकेतशब्द <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा अयोग्यरितीने टाइप केला आहे. \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="2725973286239344555">"आपण आपला पासवर्ड <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा अयोग्यरितीने टाइप केला आहे. \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string>
<string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6216672706545696955">"आपण आपला पिन <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा अयोग्यरितीने टाइप केला आहे. \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string>
<string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="9191611984625460820">"तुम्ही आपला अनलॉक पॅटर्न <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा चुकीचा रेखांकित केला आहे. <xliff:g id="NUMBER_1">%2$d</xliff:g> अधिक अयशस्वी प्रयत्नांनंतर, तुमच्याला आपले Google साइन इन वापरून आपला टॅब्लेट अनलॉक करण्यास सांगितले जाईल.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string>
<string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="5316664559603394684">"तुम्ही <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा आपला अनलॉक पॅटर्न अयोग्यरीत्या काढला आहे. आणखी <xliff:g id="NUMBER_1">%2$d</xliff:g> अयशस्वी प्रयत्नांनंतर, तुमच्याला आपले Google साइन इन वापरून आपला टीव्ही अनलॉक करण्यास सांगितले जाईल.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> सेकंदांनी पुन्हा प्रयत्न करा."</string>
@@ -756,10 +756,10 @@
<string name="lockscreen_glogin_too_many_attempts" msgid="2751368605287288808">"बरेच पॅटर्न प्रयत्न"</string>
<string name="lockscreen_glogin_instructions" msgid="3931816256100707784">"अनलॉक करण्यासाठी, आपल्या Google खात्यासह साइन इन करा."</string>
<string name="lockscreen_glogin_username_hint" msgid="8846881424106484447">"वापरकर्तानाव (ईमेल)"</string>
- <string name="lockscreen_glogin_password_hint" msgid="5958028383954738528">"संकेतशब्द"</string>
+ <string name="lockscreen_glogin_password_hint" msgid="5958028383954738528">"पासवर्ड"</string>
<string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"साइन इन करा"</string>
<string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"अवैध वापरकर्तानाव किंवा पासवर्ड."</string>
- <string name="lockscreen_glogin_account_recovery_hint" msgid="1696924763690379073">"आपले वापरकर्तानाव किंवा संकेतशब्द विसरलात?\n "<b>"google.com/accounts/recovery"</b>" ला भेट द्या."</string>
+ <string name="lockscreen_glogin_account_recovery_hint" msgid="1696924763690379073">"आपले वापरकर्तानाव किंवा पासवर्ड विसरलात?\n "<b>"google.com/accounts/recovery"</b>" ला भेट द्या."</string>
<string name="lockscreen_glogin_checking_password" msgid="7114627351286933867">"तपासत आहे..."</string>
<string name="lockscreen_unlock_label" msgid="737440483220667054">"अनलॉक करा"</string>
<string name="lockscreen_sound_on_label" msgid="9068877576513425970">"ध्वनी सुरु"</string>
@@ -788,7 +788,7 @@
<string name="keyguard_accessibility_pattern_unlock" msgid="1490840706075246612">"पॅटर्न अनलॉक."</string>
<string name="keyguard_accessibility_face_unlock" msgid="4817282543351718535">"चेहरा अनलॉक."</string>
<string name="keyguard_accessibility_pin_unlock" msgid="2469687111784035046">"पिन अनलॉक."</string>
- <string name="keyguard_accessibility_password_unlock" msgid="7675777623912155089">"संकेतशब्द अनलॉक."</string>
+ <string name="keyguard_accessibility_password_unlock" msgid="7675777623912155089">"पासवर्ड अनलॉक."</string>
<string name="keyguard_accessibility_pattern_area" msgid="7679891324509597904">"पॅटर्न क्षेत्र."</string>
<string name="keyguard_accessibility_slide_area" msgid="6736064494019979544">"स्लाइड क्षेत्र."</string>
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
@@ -840,7 +840,7 @@
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"आपल्या व्हॉइसमेल इनबॉक्समध्ये संदेश जोडण्यासाठी अॅप ला अनुमती देते."</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"ब्राउझर भौगोलिक स्थान परवानग्या सुधारित करा"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"ब्राउझरच्या भौगोलिक स्थान परवानग्या सुधारित करण्यासाठी अॅप ला अनुमती देते. दुर्भावनापूर्ण अॅप्स यादृच्छिक वेबसाइटवर स्थान माहिती पाठविण्यास अनुमती देण्यासाठी याचा वापर करू शकतात."</string>
- <string name="save_password_message" msgid="767344687139195790">"ब्राउझरने हा संकेतशब्द लक्षात ठेवावा असे आपण इच्छिता?"</string>
+ <string name="save_password_message" msgid="767344687139195790">"ब्राउझरने हा पासवर्ड लक्षात ठेवावा असे आपण इच्छिता?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"आत्ता नाही"</string>
<string name="save_password_remember" msgid="6491879678996749466">"लक्षात ठेवा"</string>
<string name="save_password_never" msgid="8274330296785855105">"कधीही नाही"</string>
@@ -994,7 +994,7 @@
<string name="yes" msgid="5362982303337969312">"ठीक"</string>
<string name="no" msgid="5141531044935541497">"रद्द करा"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"लक्ष द्या"</string>
- <string name="loading" msgid="7933681260296021180">"लोड करीत आहे..."</string>
+ <string name="loading" msgid="7933681260296021180">"लोड करत आहे..."</string>
<string name="capital_on" msgid="1544682755514494298">"चालू"</string>
<string name="capital_off" msgid="6815870386972805832">"बंद"</string>
<string name="whichApplication" msgid="4533185947064773386">"याचा वापर करून क्रिया पूर्ण करा"</string>
@@ -1058,9 +1058,9 @@
<string name="android_upgrading_fstrim" msgid="8036718871534640010">"संचयन ऑप्टिमाइझ करत आहे."</string>
<string name="android_upgrading_notification_title" msgid="8428357096969413169">"Android अपडेट संपवत आहे..."</string>
<string name="android_upgrading_notification_body" msgid="5761201379457064286">"श्रेणीसुधारणा पूर्ण होईपर्यंत काही अॅप्स योग्यरित्या कार्य करणार नाहीत"</string>
- <string name="app_upgrading_toast" msgid="3008139776215597053">"<xliff:g id="APPLICATION">%1$s</xliff:g> श्रेणीसुधारित करीत आहे…"</string>
+ <string name="app_upgrading_toast" msgid="3008139776215597053">"<xliff:g id="APPLICATION">%1$s</xliff:g> श्रेणीसुधारित करत आहे…"</string>
<string name="android_upgrading_apk" msgid="7904042682111526169">"<xliff:g id="NUMBER_1">%2$d</xliff:g> पैकी <xliff:g id="NUMBER_0">%1$d</xliff:g> अॅप ऑप्टिमाइझ करत आहे."</string>
- <string name="android_preparing_apk" msgid="8162599310274079154">"<xliff:g id="APPNAME">%1$s</xliff:g> तयार करीत आहे."</string>
+ <string name="android_preparing_apk" msgid="8162599310274079154">"<xliff:g id="APPNAME">%1$s</xliff:g> तयार करत आहे."</string>
<string name="android_upgrading_starting_apps" msgid="451464516346926713">"अॅप्स प्रारंभ करत आहे."</string>
<string name="android_upgrading_complete" msgid="1405954754112999229">"बूट समाप्त होत आहे."</string>
<string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> चालत आहे"</string>
@@ -1074,7 +1074,7 @@
<string name="dump_heap_notification" msgid="2618183274836056542">"<xliff:g id="PROC">%1$s</xliff:g> ने मेमेरी मर्यादा वाढविली"</string>
<string name="dump_heap_notification_detail" msgid="6901391084243999274">"हीप डंप संकलित केला गेला आहे; सामायिक करण्यासाठी टॅप करा"</string>
<string name="dump_heap_title" msgid="5864292264307651673">"हीप डंप सामायिक करायचे?"</string>
- <string name="dump_heap_text" msgid="4809417337240334941">"<xliff:g id="PROC">%1$s</xliff:g> प्रक्रियेने त्याची <xliff:g id="SIZE">%2$s</xliff:g> ची प्रक्रिया मेमरी मर्यादा ओलांडली आहे. त्याच्या विकासकासह सामायिक करण्यासाठी आपल्याकरिता हीप डंप उपलब्ध आहे. सावधगिरी बाळगा: या हीप डंपमध्ये आपली कोणतीही वैयक्तिक माहिती असू शकते ज्यात अॅप्लिकेशन प्रवेश करू शकतो."</string>
+ <string name="dump_heap_text" msgid="4809417337240334941">"<xliff:g id="PROC">%1$s</xliff:g> प्रक्रियेने त्याची <xliff:g id="SIZE">%2$s</xliff:g> ची प्रक्रिया मेमरी मर्यादा ओलांडली आहे. त्याच्या विकासकासह सामायिक करण्यासाठी तुमच्यासाठी हीप डंप उपलब्ध आहे. सावधगिरी बाळगा: या हीप डंपमध्ये आपली कोणतीही वैयक्तिक माहिती असू शकते ज्यात अॅप्लिकेशन प्रवेश करू शकतो."</string>
<string name="sendText" msgid="5209874571959469142">"मजकुरासाठी क्रिया निवडा"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"रिंगर व्हॉल्यूम"</string>
<string name="volume_music" msgid="5421651157138628171">"मीडिया व्हॉल्यूम"</string>
@@ -1185,7 +1185,7 @@
<string name="perm_costs_money" msgid="4902470324142151116">"यासाठी आपले पैसे खर्च होऊ शकतात"</string>
<string name="dlg_ok" msgid="7376953167039865701">"ठीक"</string>
<string name="usb_charging_notification_title" msgid="6895185153353640787">"USB हे डिव्हाइस चार्ज करत आहे"</string>
- <string name="usb_supplying_notification_title" msgid="5310642257296510271">"USB संलग्न केलेल्या डिव्हाइसला पॉवरचा पुरवठा करीत आहे"</string>
+ <string name="usb_supplying_notification_title" msgid="5310642257296510271">"USB संलग्न केलेल्या डिव्हाइसला पॉवरचा पुरवठा करत आहे"</string>
<string name="usb_mtp_notification_title" msgid="8396264943589760855">"स्थानांतरणासाठी USB"</string>
<string name="usb_ptp_notification_title" msgid="1347328437083192112">"फोटो स्थानांतरणासाठी USB"</string>
<string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI साठी USB"</string>
@@ -1210,11 +1210,11 @@
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="alert_windows_notification_channel_group_name" msgid="1463953341148606396">"इतर अ‍ॅप्सवर दाखवा"</string>
- <string name="alert_windows_notification_channel_name" msgid="3116610965549449803">"<xliff:g id="NAME">%s</xliff:g> इतर अॅप्सवर प्रदर्शित करीत आहे"</string>
- <string name="alert_windows_notification_title" msgid="3697657294867638947">"<xliff:g id="NAME">%s</xliff:g> अन्‍य अॅप्सवर प्रदर्शित करीत आहे"</string>
+ <string name="alert_windows_notification_channel_name" msgid="3116610965549449803">"<xliff:g id="NAME">%s</xliff:g> इतर अॅप्सवर प्रदर्शित करत आहे"</string>
+ <string name="alert_windows_notification_title" msgid="3697657294867638947">"<xliff:g id="NAME">%s</xliff:g> अन्‍य अॅप्सवर प्रदर्शित करत आहे"</string>
<string name="alert_windows_notification_message" msgid="8917232109522912560">"<xliff:g id="NAME">%s</xliff:g> ने हे वैशिष्ट्य वापरू नये असे आपण इच्छित असल्यास, सेटिंग्ज उघडण्यासाठी टॅप करा आणि ते बंद करा."</string>
<string name="alert_windows_notification_turn_off_action" msgid="3367294525884949878">"बंद करा"</string>
- <string name="ext_media_checking_notification_title" msgid="5734005953288045806">"<xliff:g id="NAME">%s</xliff:g> तयार करीत आहे"</string>
+ <string name="ext_media_checking_notification_title" msgid="5734005953288045806">"<xliff:g id="NAME">%s</xliff:g> तयार करत आहे"</string>
<string name="ext_media_checking_notification_message" msgid="4747432538578886744">"त्रुटींसाठी तपासत आहे"</string>
<string name="ext_media_new_notification_message" msgid="7589986898808506239">"नवीन <xliff:g id="NAME">%s</xliff:g> आढळले"</string>
<string name="ext_media_ready_notification_message" msgid="4083398150380114462">"फोटो आणि मीडिया स्थानांतरित करण्‍यासाठी"</string>
@@ -1250,7 +1250,7 @@
<string name="ext_media_status_unmountable" msgid="805594039236667894">"दूषित झाले"</string>
<string name="ext_media_status_unsupported" msgid="4691436711745681828">"समर्थित नसलेले"</string>
<string name="ext_media_status_ejecting" msgid="5463887263101234174">"बाहेर काढत आहे…"</string>
- <string name="ext_media_status_formatting" msgid="1085079556538644861">"फॉर्मेट करीत आहे..."</string>
+ <string name="ext_media_status_formatting" msgid="1085079556538644861">"फॉर्मेट करत आहे..."</string>
<string name="ext_media_status_missing" msgid="5638633895221670766">"घातले नाही"</string>
<string name="activity_list_empty" msgid="1675388330786841066">"कोणत्याही जुळणाऱ्या अॅक्टिव्हिटी आढळल्या नाहीत."</string>
<string name="permlab_route_media_output" msgid="6243022988998972085">"मीडिया आउटपुट मार्गस्थ करा"</string>
@@ -1365,7 +1365,7 @@
<string name="keyboardview_keycode_done" msgid="1992571118466679775">"पूर्ण झाले"</string>
<string name="keyboardview_keycode_mode_change" msgid="4547387741906537519">"मोड बदल"</string>
<string name="keyboardview_keycode_shift" msgid="2270748814315147690">"Shift"</string>
- <string name="keyboardview_keycode_enter" msgid="2985864015076059467">"प्रविष्ट करा"</string>
+ <string name="keyboardview_keycode_enter" msgid="2985864015076059467">"एंटर करा"</string>
<string name="activitychooserview_choose_application" msgid="2125168057199941199">"एक अ‍ॅप निवडा"</string>
<string name="activitychooserview_choose_application_error" msgid="8624618365481126668">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> लाँच करू शकलो नाही"</string>
<string name="shareactionprovider_share_with" msgid="806688056141131819">"यांच्यासह सामायिक करा"</string>
@@ -1438,7 +1438,7 @@
<string name="media_route_chooser_extended_settings" msgid="87015534236701604">"सेटिंग्ज"</string>
<string name="media_route_controller_disconnect" msgid="8966120286374158649">"‍डिस्कनेक्ट करा"</string>
<string name="media_route_status_scanning" msgid="7279908761758293783">"स्कॅन करत आहे..."</string>
- <string name="media_route_status_connecting" msgid="6422571716007825440">"कनेक्ट करीत आहे..."</string>
+ <string name="media_route_status_connecting" msgid="6422571716007825440">"कनेक्ट करत आहे..."</string>
<string name="media_route_status_available" msgid="6983258067194649391">"उपलब्ध"</string>
<string name="media_route_status_not_available" msgid="6739899962681886401">"उपलब्ध नाही"</string>
<string name="media_route_status_in_use" msgid="4533786031090198063">"वापरात आहे"</string>
@@ -1449,18 +1449,18 @@
<string name="display_manager_overlay_display_secure_suffix" msgid="6022119702628572080">", सुरक्षित"</string>
<string name="kg_forgot_pattern_button_text" msgid="8852021467868220608">"पॅटर्न विसरलात"</string>
<string name="kg_wrong_pattern" msgid="1850806070801358830">"चुकीचा पॅटर्न"</string>
- <string name="kg_wrong_password" msgid="2333281762128113157">"चुकीचा संकेतशब्द"</string>
+ <string name="kg_wrong_password" msgid="2333281762128113157">"चुकीचा पासवर्ड"</string>
<string name="kg_wrong_pin" msgid="1131306510833563801">"चुकीचा पिन"</string>
<plurals name="kg_too_many_failed_attempts_countdown" formatted="false" msgid="8790651267324125694">
<item quantity="one"><xliff:g id="NUMBER">%d</xliff:g> सेकंदात पुन्‍हा प्रयत्न करा.</item>
<item quantity="other"><xliff:g id="NUMBER">%d</xliff:g> सेकंदांत पुन्‍हा प्रयत्न करा.</item>
</plurals>
<string name="kg_pattern_instructions" msgid="398978611683075868">"तुमचा पॅटर्न काढा"</string>
- <string name="kg_sim_pin_instructions" msgid="2319508550934557331">"सिम पिन प्रविष्ट करा"</string>
- <string name="kg_pin_instructions" msgid="2377242233495111557">"पिन प्रविष्ट करा"</string>
- <string name="kg_password_instructions" msgid="5753646556186936819">"संकेतशब्द प्रविष्ट करा"</string>
- <string name="kg_puk_enter_puk_hint" msgid="453227143861735537">"सिम आता अक्षम केले आहे. सुरु ठेवण्यासाठी PUK कोड प्रविष्ट करा. तपशीलांसाठी वाहकाशी संपर्क साधा."</string>
- <string name="kg_puk_enter_pin_hint" msgid="7871604527429602024">"इच्छित पिन कोड प्रविष्ट करा"</string>
+ <string name="kg_sim_pin_instructions" msgid="2319508550934557331">"सिम पिन एंटर करा"</string>
+ <string name="kg_pin_instructions" msgid="2377242233495111557">"पिन एंटर करा"</string>
+ <string name="kg_password_instructions" msgid="5753646556186936819">"पासवर्ड एंटर करा"</string>
+ <string name="kg_puk_enter_puk_hint" msgid="453227143861735537">"सिम आता अक्षम केले आहे. सुरु ठेवण्यासाठी PUK कोड एंटर करा. तपशीलांसाठी वाहकाशी संपर्क साधा."</string>
+ <string name="kg_puk_enter_pin_hint" msgid="7871604527429602024">"इच्छित पिन कोड एंटर करा"</string>
<string name="kg_enter_confirm_pin_hint" msgid="325676184762529976">"इच्छित पिन कोड ची पुष्टी करा"</string>
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"सिम कार्ड अनलॉक करत आहे…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"अयोग्य पिन कोड."</string>
@@ -1471,13 +1471,13 @@
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"बरेच पॅटर्न प्रयत्न"</string>
<string name="kg_login_instructions" msgid="1100551261265506448">"अनलॉक करण्यासाठी, आपल्या Google खात्यासह साइन इन करा."</string>
<string name="kg_login_username_hint" msgid="5718534272070920364">"वापरकर्तानाव (ईमेल)"</string>
- <string name="kg_login_password_hint" msgid="9057289103827298549">"संकेतशब्द"</string>
+ <string name="kg_login_password_hint" msgid="9057289103827298549">"पासवर्ड"</string>
<string name="kg_login_submit_button" msgid="5355904582674054702">"साइन इन करा"</string>
<string name="kg_login_invalid_input" msgid="5754664119319872197">"अवैध वापरकर्तानाव किंवा पासवर्ड."</string>
- <string name="kg_login_account_recovery_hint" msgid="5690709132841752974">"आपले वापरकर्तानाव किंवा संकेतशब्द विसरलात?\n "<b>"google.com/accounts/recovery"</b>" ला भेट द्या."</string>
+ <string name="kg_login_account_recovery_hint" msgid="5690709132841752974">"आपले वापरकर्तानाव किंवा पासवर्ड विसरलात?\n "<b>"google.com/accounts/recovery"</b>" ला भेट द्या."</string>
<string name="kg_login_checking_password" msgid="1052685197710252395">"खाते तपासत आहे…"</string>
<string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="8276745642049502550">"आपण आपला पिन <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा अयोग्यरितीने टाइप केला आहे. \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string>
- <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="7813713389422226531">"आपण आपला संकेतशब्द <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा अयोग्यरितीने टाइप केला आहे. \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string>
+ <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="7813713389422226531">"आपण आपला पासवर्ड <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा अयोग्यरितीने टाइप केला आहे. \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string>
<string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="74089475965050805">"तुम्ही आपला अनलॉक पॅटर्न <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा अयोग्यरितीने काढला. \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string>
<string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="1575557200627128949">"आपण <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा टॅबलेट अनलॉक करण्याचा अयोग्यपणे प्रयत्न केला. <xliff:g id="NUMBER_1">%2$d</xliff:g> आणखी अयशस्वी प्रयत्नांनंतर, टॅबलेट फॅक्टरी डीफॉल्टवर रीसेट केला जाईल आणि वापरकर्ता डेटा गमावेल."</string>
<string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="5621231220154419413">"आपण <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा टीव्ही अनलॉक करण्याचा अयोग्यरित्या प्रयत्न केला. आणखी <xliff:g id="NUMBER_1">%2$d</xliff:g> अयशस्वी प्रयत्नांनंतर, टीव्ही फॅक्टरी डीफॉल्टवर रीसेट केला जाईल आणि सर्व वापरकर्ता डेटा गमावेल."</string>
@@ -1502,7 +1502,7 @@
<string name="accessibility_magnification_chooser_text" msgid="1227146738764986237">"मोठे करणे"</string>
<string name="user_switched" msgid="3768006783166984410">"वर्तमान वापरकर्ता <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="2871009331809089783">"<xliff:g id="NAME">%1$s</xliff:g> वर स्विच करत आहे…"</string>
- <string name="user_logging_out_message" msgid="8939524935808875155">"<xliff:g id="NAME">%1$s</xliff:g> लॉग आउट करीत आहे…"</string>
+ <string name="user_logging_out_message" msgid="8939524935808875155">"<xliff:g id="NAME">%1$s</xliff:g> लॉग आउट करत आहे…"</string>
<string name="owner_name" msgid="2716755460376028154">"मालक"</string>
<string name="error_message_title" msgid="4510373083082500195">"एरर"</string>
<string name="error_message_change_not_allowed" msgid="1238035947357923497">"या बदलास आपल्या प्रशासकाद्वारे अनुमती नाही"</string>
@@ -1598,7 +1598,7 @@
<string name="print_service_installed_title" msgid="2246317169444081628">"<xliff:g id="NAME">%s</xliff:g> सेवा स्‍थापित केली"</string>
<string name="print_service_installed_message" msgid="5897362931070459152">"सक्षम करण्यासाठी टॅप करा"</string>
<string name="restr_pin_enter_admin_pin" msgid="8641662909467236832">"प्रशासक पिन एंटर करा"</string>
- <string name="restr_pin_enter_pin" msgid="3395953421368476103">"पिन प्रविष्ट करा"</string>
+ <string name="restr_pin_enter_pin" msgid="3395953421368476103">"पिन एंटर करा"</string>
<string name="restr_pin_incorrect" msgid="8571512003955077924">"चुकीचा"</string>
<string name="restr_pin_enter_old_pin" msgid="1462206225512910757">"वर्तमान पिन"</string>
<string name="restr_pin_enter_new_pin" msgid="5959606691619959184">"नवीन पिन"</string>
@@ -1626,7 +1626,6 @@
<string name="managed_profile_label_badge_2" msgid="5048136430082124036">"2 रे कार्य <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="managed_profile_label_badge_3" msgid="2808305070321719040">"3 रे कार्य <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="lock_to_app_toast" msgid="6820571533009838261">"हा स्क्रीन अनपिन करण्यासाठी, मागे आणि अवलोकन बटणांना स्पर्श करून धरून ठेवा"</string>
- <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"हे अ‍ॅप अनपिन केले जाऊ शकत नाही"</string>
<string name="lock_to_app_start" msgid="6643342070839862795">"स्क्रीन पिन केली"</string>
<string name="lock_to_app_exit" msgid="8598219838213787430">"स्क्रीन अनपिन केली"</string>
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"अनपिन करण्‍यापूर्वी पिन साठी विचारा"</string>
@@ -1771,7 +1770,7 @@
<string name="autofill_save_title_with_3types" msgid="6943161834231458441">"&lt;b&gt;<xliff:g id="LABEL">%4$s</xliff:g>&lt;/b&gt;मध्ये <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> आणि <xliff:g id="TYPE_2">%3$s</xliff:g> सेव्ह करायची?"</string>
<string name="autofill_save_yes" msgid="6398026094049005921">"सेव्ह करा"</string>
<string name="autofill_save_no" msgid="2625132258725581787">"नाही, नको"</string>
- <string name="autofill_save_type_password" msgid="5288448918465971568">"संकेतशब्द"</string>
+ <string name="autofill_save_type_password" msgid="5288448918465971568">"पासवर्ड"</string>
<string name="autofill_save_type_address" msgid="4936707762193009542">"पत्ता"</string>
<string name="autofill_save_type_credit_card" msgid="7127694776265563071">"क्रेडिट कार्ड"</string>
<string name="autofill_save_type_username" msgid="239040540379769562">"वापरकर्तानाव"</string>
@@ -1782,14 +1781,10 @@
<string name="etws_primary_default_message_test" msgid="2709597093560037455">"आणीबाणी संदेश चाचणी"</string>
<string name="notification_reply_button_accessibility" msgid="3621714652387814344">"प्रत्युत्तर द्या"</string>
<string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
- <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
- <skip />
- <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
- <skip />
- <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
- <skip />
- <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
- <skip />
+ <string name="mmcc_authentication_reject" msgid="5767701075994754356">"व्‍हॉइसची सिमला अनुमती नाही"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"सिममध्‍ये व्‍हॉइसची तरतूद नाही"</string>
+ <string name="mmcc_illegal_ms" msgid="807334478177362062">"व्‍हॉइसची सिमला अनुमती नाही"</string>
+ <string name="mmcc_illegal_me" msgid="1950705155760872972">"व्‍हॉइसची फोनला अनुमती नाही"</string>
<string name="popup_window_default_title" msgid="4874318849712115433">"पॉपअप विंडो"</string>
<string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"या शॉर्टकटला नवीनतम अॅपची आवश्यकता आहे"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 429ecf109bfd..bf0f46a43451 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -282,7 +282,7 @@
<string name="permgrouprequest_camera" msgid="810824326507258410">"படங்களை எடுக்கவும் வீடியோவைப் பதிவுசெய்யவும், &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;க்கு அணுகல் வழங்கவும்"</string>
<string name="permgrouplab_phone" msgid="5229115638567440675">"ஃபோன்"</string>
<string name="permgroupdesc_phone" msgid="6234224354060641055">"யாரையும் தொலைபேசியில் அழைக்கலாம்"</string>
- <string name="permgrouprequest_phone" msgid="7084161459732093690">"மொபைல் அழைப்புகளைச் செய்யவும், அவற்றை நிர்வகிக்கவும், &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;க்கு அனுமதி வழங்கவும்"</string>
+ <string name="permgrouprequest_phone" msgid="7084161459732093690">"மொபைல் அழைப்புகளைச் செய்யவும், அவற்றை நிர்வகிக்கவும், &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;க்கு அனுமதி வழங்கவா"</string>
<string name="permgrouplab_sensors" msgid="416037179223226722">"உடல் சென்சார்கள்"</string>
<string name="permgroupdesc_sensors" msgid="7147968539346634043">"உங்கள் உடல் இயக்கம் பற்றி உணர்விகள் கூறும் தகவலைப் பார்க்கலாம்"</string>
<string name="permgrouprequest_sensors" msgid="8631146669524259656">"உடலியக்கக் குறிகள் பற்றிய உணர்வித் தரவை அணுக, &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;க்கு அனுமதி வழங்கவும்"</string>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 947fcf127b9c..946216c06a01 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -605,6 +605,8 @@
<!-- The size of the right icon image when on low ram -->
<dimen name="notification_right_icon_size_low_ram">40dp</dimen>
+ <dimen name="messaging_avatar_size">24dp</dimen>
+
<!-- Max width/height of the autofill data set picker as a fraction of the screen width/height -->
<dimen name="autofill_dataset_picker_max_size">90%</dimen>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index cd3624db9afa..b40117e9e40b 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -139,6 +139,27 @@
<!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_MOVE_WINDOW}. -->
<item type="id" name="accessibilityActionMoveWindow" />
+ <!-- A tag used to save an animator in local y translation -->
+ <item type="id" name="tag_local_translation_y_animator" />
+
+ <!-- A tag used to save the local translation y -->
+ <item type="id" name="tag_local_translation_y" />
+
+ <!-- A tag used to save the original top of a view -->
+ <item type="id" name="tag_layout_top" />
+
+ <!-- A tag used to save an animator in alpha -->
+ <item type="id" name="tag_alpha_animator" />
+
+ <!-- A tag used to save the clip children in a tag -->
+ <item type="id" name="clip_children_tag" />
+
+ <!-- A tag used to save the set of deactivated children that clip -->
+ <item type="id" name="clip_children_set_tag" />
+
+ <!-- A tag used to save the clip to padding in a tag -->
+ <item type="id" name="clip_to_padding_tag" />
+
<!-- Action used to manually trigger an autofill request -->
<item type="id" name="autofill" />
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index cddf99a2df23..2cd4dcb84a5f 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -505,8 +505,17 @@ please see styles_device_defaults.xml.
<item name="layout_width">match_parent</item>
<item name="layout_height">wrap_content</item>
<item name="ellipsize">end</item>
- <item name="visibility">gone</item>
<item name="textAppearance">@style/TextAppearance.Material.Notification</item>
+ <item name="background">@drawable/messaging_message_background</item>
+ </style>
+
+ <style name="Widget.Material.Notification.MessagingName" parent="Widget.Material.Light.TextView">
+ <item name="layout_width">wrap_content</item>
+ <item name="layout_height">wrap_content</item>
+ <item name="ellipsize">end</item>
+ <item name="textAppearance">@style/TextAppearance.Material.Notification</item>
+ <item name="textColor">@color/notification_primary_text_color_light</item>
+ <item name="textSize">12sp</item>
</style>
<!-- Widget Styles -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 32758e83f9b7..fd0012dff22d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3105,6 +3105,22 @@
<java-symbol type="dimen" name="chooser_service_spacing" />
<java-symbol type="bool" name="config_showSysuiShutdown" />
+ <java-symbol type="layout" name="notification_template_messaging_message" />
+ <java-symbol type="layout" name="notification_template_messaging_group" />
+ <java-symbol type="id" name="message_text" />
+ <java-symbol type="id" name="message_name" />
+ <java-symbol type="id" name="message_icon" />
+ <java-symbol type="id" name="group_message_container" />
+ <java-symbol type="id" name="tag_local_translation_y_animator" />
+ <java-symbol type="id" name="tag_local_translation_y" />
+ <java-symbol type="id" name="tag_layout_top" />
+ <java-symbol type="id" name="tag_alpha_animator" />
+ <java-symbol type="id" name="clip_children_set_tag" />
+ <java-symbol type="id" name="clip_to_padding_tag" />
+ <java-symbol type="id" name="clip_children_tag" />
+ <java-symbol type="drawable" name="ic_reply_notification_large" />
+ <java-symbol type="dimen" name="messaging_avatar_size" />
+
<java-symbol type="integer" name="config_stableDeviceDisplayWidth" />
<java-symbol type="integer" name="config_stableDeviceDisplayHeight" />
<java-symbol type="bool" name="config_display_no_service_when_sim_unready" />
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index ebe0527cbbec..1002939c7fa4 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -180,7 +180,6 @@ public class SettingsBackupTest {
Settings.Global.DNS_RESOLVER_MIN_SAMPLES,
Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS,
Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT,
- Settings.Global.DNS_TLS_DISABLED,
Settings.Global.DOCK_SOUNDS_ENABLED_WHEN_ACCESSIBILITY,
Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE,
Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE,
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index bbdbdb13b3f7..0245570b9e1e 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -49,6 +49,11 @@ import static junit.framework.Assert.assertTrue;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.is;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.Activity;
import android.app.Instrumentation;
@@ -633,15 +638,21 @@ public class TextViewActivityTest {
@Test
public void testSetSelectionAndActionMode() throws Throwable {
+ final TextView textView = mActivity.findViewById(R.id.textview);
+ final ActionMode.Callback amCallback = mock(ActionMode.Callback.class);
+ when(amCallback.onCreateActionMode(any(ActionMode.class), any(Menu.class)))
+ .thenReturn(true);
+ when(amCallback.onPrepareActionMode(any(ActionMode.class), any(Menu.class)))
+ .thenReturn(true);
+ textView.setCustomSelectionActionModeCallback(amCallback);
+
final String text = "abc def";
onView(withId(R.id.textview)).perform(replaceText(text));
-
- final TextView textView = mActivity.findViewById(R.id.textview);
mActivityRule.runOnUiThread(
() -> Selection.setSelection((Spannable) textView.getText(), 0, 3));
mInstrumentation.waitForIdleSync();
// Don't automatically start action mode.
- // TODO: Implement assertActionModeNotStarted()
+ verify(amCallback, never()).onCreateActionMode(any(ActionMode.class), any(Menu.class));
// Make sure that "Select All" is included in the selection action mode when the entire text
// is not selected.
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('e')));
diff --git a/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
index dfe851104e1c..3919fdd7082f 100644
--- a/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
@@ -23,11 +23,11 @@ import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
+import android.text.Layout;
import android.view.LayoutInflater;
import android.view.View.MeasureSpec;
import com.android.frameworks.coretests.R;
-import com.google.common.base.Function;
import org.junit.Before;
import org.junit.Test;
@@ -52,33 +52,28 @@ public class MessagingLinearLayoutTest {
@Test
public void testSingleChild() {
- FakeImageFloatingTextView child = fakeChild((i) -> 3);
+ FakeImageFloatingTextView child = fakeChild(3);
- mView.setNumIndentLines(2);
mView.addView(child);
mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
- assertEquals(3, child.getNumIndentLines());
assertFalse(child.isHidden());
assertEquals(150, mView.getMeasuredHeight());
}
@Test
public void testLargeSmall() {
- FakeImageFloatingTextView child1 = fakeChild((i) -> 3);
- FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
+ FakeImageFloatingTextView child1 = fakeChild(3);
+ FakeImageFloatingTextView child2 = fakeChild(1);
- mView.setNumIndentLines(2);
mView.addView(child1);
mView.addView(child2);
mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
- assertEquals(3, child1.getNumIndentLines());
- assertEquals(0, child2.getNumIndentLines());
assertFalse("child1 should not be hidden", child1.isHidden());
assertFalse("child2 should not be hidden", child2.isHidden());
assertEquals(205, mView.getMeasuredHeight());
@@ -86,18 +81,15 @@ public class MessagingLinearLayoutTest {
@Test
public void testSmallSmall() {
- FakeImageFloatingTextView child1 = fakeChild((i) -> 1);
- FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
+ FakeImageFloatingTextView child1 = fakeChild(1);
+ FakeImageFloatingTextView child2 = fakeChild(1);
- mView.setNumIndentLines(2);
mView.addView(child1);
mView.addView(child2);
mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
- assertEquals(2, child1.getNumIndentLines());
- assertEquals(1, child2.getNumIndentLines());
assertFalse("child1 should not be hidden", child1.isHidden());
assertFalse("child2 should not be hidden", child2.isHidden());
assertEquals(105, mView.getMeasuredHeight());
@@ -105,17 +97,15 @@ public class MessagingLinearLayoutTest {
@Test
public void testLargeLarge() {
- FakeImageFloatingTextView child1 = fakeChild((i) -> 7);
- FakeImageFloatingTextView child2 = fakeChild((i) -> 7);
+ FakeImageFloatingTextView child1 = fakeChild(7);
+ FakeImageFloatingTextView child2 = fakeChild(7);
- mView.setNumIndentLines(2);
mView.addView(child1);
mView.addView(child2);
mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
- assertEquals(3, child2.getNumIndentLines());
assertTrue("child1 should be hidden", child1.isHidden());
assertFalse("child2 should not be hidden", child2.isHidden());
assertEquals(350, mView.getMeasuredHeight());
@@ -123,10 +113,9 @@ public class MessagingLinearLayoutTest {
@Test
public void testLargeSmall_largeWrapsWith3indentbutNotFullHeight_andHitsMax() {
- FakeImageFloatingTextView child1 = fakeChild((i) -> i > 2 ? 7 : 6);
- FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
+ FakeImageFloatingTextView child1 = fakeChild(7);
+ FakeImageFloatingTextView child2 = fakeChild(1);
- mView.setNumIndentLines(2);
mView.addView(child1);
mView.addView(child2);
@@ -135,51 +124,18 @@ public class MessagingLinearLayoutTest {
assertFalse("child1 should not be hidden", child1.isHidden());
assertFalse("child2 should not be hidden", child2.isHidden());
- assertEquals(355, mView.getMeasuredHeight());
- assertEquals(3, child1.getNumIndentLines());
- assertEquals(0, child2.getNumIndentLines());
+ assertEquals(355, mView.getMeasuredHeight());;
}
- @Test
- public void testLargeSmall_largeWrapsWith3indentbutnot3() {
- FakeImageFloatingTextView child1 = fakeChild((i) -> i > 2 ? 4 : 3);
- FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
-
- mView.setNumIndentLines(2);
- mView.addView(child1);
- mView.addView(child2);
-
- mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
- mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
-
- assertFalse("child1 should not be hidden", child1.isHidden());
- assertFalse("child2 should not be hidden", child2.isHidden());
- assertEquals(255, mView.getMeasuredHeight());
- assertEquals(3, child1.getNumIndentLines());
- assertEquals(0, child2.getNumIndentLines());
- }
-
- private class FakeImageFloatingTextView extends ImageFloatingTextView {
+ private class FakeImageFloatingTextView extends MessagingMessage {
public static final int LINE_HEIGHT = 50;
- private final Function<Integer, Integer> mLinesForIndent;
- private int mNumIndentLines;
+ private final int mNumLines;
public FakeImageFloatingTextView(Context context,
- Function<Integer, Integer> linesForIndent) {
+ int linesForIndent) {
super(context, null, 0, 0);
- mLinesForIndent = linesForIndent;
- }
-
- @Override
- public boolean setNumIndentLines(int lines) {
- boolean changed = (mNumIndentLines != lines);
- mNumIndentLines = lines;
- return changed;
- }
-
- public int getNumIndentLines() {
- return mNumIndentLines;
+ mNumLines = linesForIndent;
}
@Override
@@ -195,6 +151,20 @@ public class MessagingLinearLayoutTest {
heightMeasureSpec)));
}
+ public int getMeasuredType() {
+ boolean measuredTooSmall = getMeasuredHeight()
+ < getLayoutHeight() + getPaddingTop() + getPaddingBottom();
+ if (measuredTooSmall) {
+ return MEASURED_TOO_SMALL;
+ } else {
+ if (getMeasuredHeight() == getDesiredHeight()) {
+ return MEASURED_NORMAL;
+ } else {
+ return MEASURED_SHORTENED;
+ }
+ }
+ }
+
private int clampToMultiplesOfLineHeight(int size) {
if (size <= LINE_HEIGHT) {
return size;
@@ -204,7 +174,7 @@ public class MessagingLinearLayoutTest {
@Override
public int getLineCount() {
- return mLinesForIndent.apply(mNumIndentLines);
+ return mNumLines;
}
public int getDesiredHeight() {
@@ -229,7 +199,7 @@ public class MessagingLinearLayoutTest {
}
}
- private FakeImageFloatingTextView fakeChild(Function<Integer,Integer> linesForIndent) {
- return new FakeImageFloatingTextView(mContext, linesForIndent);
+ private FakeImageFloatingTextView fakeChild(int numLines) {
+ return new FakeImageFloatingTextView(mContext, numLines);
}
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 82aa803ef0ca..f7e0b46ca265 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -328,6 +328,7 @@ applications that come with the platform
<privapp-permissions package="com.android.systemui">
<permission name="android.permission.BATTERY_STATS"/>
<permission name="android.permission.BIND_APPWIDGET"/>
+ <permission name="android.permission.BIND_SLICE" />
<permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
<permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
<permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 1d85c97239ac..597336bc5fae 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -35,6 +35,7 @@ cc_library_shared {
"libutils",
"libbinder",
"libmedia",
+ "libmedia_omx",
"libmediametrics",
"libmediadrm",
"libmidi",
diff --git a/packages/MtpDocumentsProvider/res/values-mr/strings.xml b/packages/MtpDocumentsProvider/res/values-mr/strings.xml
index 89a9d14243d9..d581e10f6a1d 100644
--- a/packages/MtpDocumentsProvider/res/values-mr/strings.xml
+++ b/packages/MtpDocumentsProvider/res/values-mr/strings.xml
@@ -19,7 +19,7 @@
<string name="app_label" msgid="6271216747302322594">"MTP होस्ट"</string>
<string name="downloads_app_label" msgid="7120690641874849726">"डाउनलोड"</string>
<string name="root_name" msgid="5819495383921089536">"<xliff:g id="DEVICE_MODEL">%1$s</xliff:g> <xliff:g id="STORAGE_NAME">%2$s</xliff:g>"</string>
- <string name="accessing_notification_title" msgid="3030133609230917944">"<xliff:g id="DEVICE_MODEL">%1$s</xliff:g> मधून फायलींंमध्ये प्रवेश करीत आहे"</string>
+ <string name="accessing_notification_title" msgid="3030133609230917944">"<xliff:g id="DEVICE_MODEL">%1$s</xliff:g> मधून फायलींंमध्ये प्रवेश करत आहे"</string>
<string name="error_busy_device" msgid="3997316850357386589">"दुसरे डिव्हाइस व्यस्त आहे. ते उपलब्‍ध होईपर्यंत तुम्ही फायली ट्रांसफर करू शकत नाही."</string>
<string name="error_locked_device" msgid="7557872102188356147">"कोणत्याही फायली आढळल्या नाहीत. दुसरे डिव्हाइस कदाचित बंद असू शकते. तसे असल्यास, ते अनलॉक करा आणि पुन्हा प्रयत्न करा."</string>
</resources>
diff --git a/packages/PrintSpooler/res/values-mr/strings.xml b/packages/PrintSpooler/res/values-mr/strings.xml
index 05eb8539444f..862d19305d91 100644
--- a/packages/PrintSpooler/res/values-mr/strings.xml
+++ b/packages/PrintSpooler/res/values-mr/strings.xml
@@ -34,7 +34,7 @@
<string name="print_preview" msgid="8010217796057763343">"मुद्रण पूर्वावलोकन"</string>
<string name="install_for_print_preview" msgid="6366303997385509332">"पूर्वावलोकनासाठी पीडीएफ व्ह्यूअर इंस्टॉल करा"</string>
<string name="printing_app_crashed" msgid="854477616686566398">"प्रिंटिंग अ‍ॅप क्रॅश झाले"</string>
- <string name="generating_print_job" msgid="3119608742651698916">"मुद्रण कार्य व्‍युत्‍पन्न करीत आहे"</string>
+ <string name="generating_print_job" msgid="3119608742651698916">"मुद्रण कार्य व्‍युत्‍पन्न करत आहे"</string>
<string name="save_as_pdf" msgid="5718454119847596853">"पीडीएफ म्‍हणून सेव्ह करा"</string>
<string name="all_printers" msgid="5018829726861876202">"सर्व प्रिंटर..."</string>
<string name="print_dialog" msgid="32628687461331979">"मुद्रण संवाद"</string>
@@ -79,8 +79,8 @@
<item quantity="one"><xliff:g id="COUNT_1">%1$s</xliff:g> प्रिंटर शोधण्यासाठी इंस्टॉल करा</item>
<item quantity="other"><xliff:g id="COUNT_1">%1$s</xliff:g> प्रिंटर शोधण्यासाठी इंस्टॉल करा</item>
</plurals>
- <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> मुद्रण करीत आहे"</string>
- <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> रद्द करीत आहे"</string>
+ <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> मुद्रण करत आहे"</string>
+ <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> रद्द करत आहे"</string>
<string name="failed_notification_title_template" msgid="2256217208186530973">"प्रिंटर एरर <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="blocked_notification_title_template" msgid="1175435827331588646">"प्रिंटरने <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> अवरोधित केले"</string>
<string name="cancel" msgid="4373674107267141885">"रद्द करा"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index a3430fc8e98e..2a4983aa00de 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -99,6 +99,13 @@
<string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‎‏‏‏‏‎‎‏‏‏‎‏‏‏‎‏‏‎‏‏‎‏‎‎‎‎‎‏‎‎‎‏‎‏‎‏‎‎‎‏‎‏‏‎‏‎‏‏‎‏‎‎Couldn\'t pair with ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ because of an incorrect PIN or passkey.‎‏‎‎‏‎"</string>
<string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‎‏‏‎‏‏‎‎‏‏‏‎‏‎‏‏‎‎‎‎‏‏‎‎‎‎‏‎‎‏‏‎‏‏‎‎‎‏‏‏‏‎‏‏‎‏‎‎‏‎‏‎Can\'t communicate with ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎.‎‏‎‎‏‎"</string>
<string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‏‎‏‏‎‏‏‏‏‏‎‏‏‎‏‏‏‎‎‎‏‎‏‎‎‎‎‏‏‎‏‎‏‏‎‏‏‎‏‎‏‏‎‏‎‎‏‏‎‏‏‏‎‎‎‏‏‎‎Pairing rejected by ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎.‎‏‎‎‏‎"</string>
+ <string name="bluetooth_talkback_computer" msgid="4875089335641234463">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‏‎‎‏‏‏‏‏‎‎‏‎‏‏‏‏‏‎‏‎‏‏‏‏‏‎‎‎‏‏‎‎‏‏‎‎‎‎‏‏‎‏‏‏‎‎‎‎‎‏‏‏‏‏‎Computer‎‏‎‎‏‎"</string>
+ <string name="bluetooth_talkback_headset" msgid="5140152177885220949">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‎‏‎‏‎‏‎‏‏‏‏‏‎‏‎‎‏‎‏‏‎‎‎‎‎‎‏‏‏‎‎‏‎‎‏‏‎‎‏‎‎‏‎‎‎‎‎‏‎‏‎‏‎‏‎Headset‎‏‎‎‏‎"</string>
+ <string name="bluetooth_talkback_phone" msgid="4260255181240622896">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‎‎‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‎‏‎‏‎‏‏‏‏‏‎‎‏‏‎‎‎‎‎Phone‎‏‎‎‏‎"</string>
+ <string name="bluetooth_talkback_imaging" msgid="551146170554589119">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‎‏‎‎‎‎‏‎‎‎‎‏‎‏‏‎‏‎‎‎‏‏‎‎‏‏‏‎‎‏‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎Imaging‎‏‎‎‏‎"</string>
+ <string name="bluetooth_talkback_headphone" msgid="26580326066627664">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‎‏‏‏‏‎‏‏‏‏‎‎‏‏‎‏‏‏‎‏‎‏‎‏‎‏‎‎‏‏‎‏‏‎‏‎‎‏‏‎‏‏‏‎‎‏‎‎‏‎‎‎‏‎‏‎‎‎‎‎Headphone‎‏‎‎‏‎"</string>
+ <string name="bluetooth_talkback_input_peripheral" msgid="5165842622743212268">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‎‎‏‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‏‎‏‏‏‎‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎Input Peripheral‎‏‎‎‏‎"</string>
+ <string name="bluetooth_talkback_bluetooth" msgid="5615463912185280812">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‎‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‎‏‎‏‎‏‏‎‏‎‎‎‏‎‏‏‎‏‏‎‏‎‏‎‎‎‎‏‏‏‎‎‏‎‎‏‎‏‏‎‎‎Bluetooth‎‏‎‎‏‎"</string>
<string name="accessibility_wifi_off" msgid="1166761729660614716">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎‎‏‎‏‎‏‏‏‎‎‏‏‏‎‎‏‎‎‎‎‎‎‏‏‏‏‏‎‎‎‎‏‏‏‏‏‎‎‎‎‎‏‏‏‏‎‎‎Wifi off.‎‏‎‎‏‎"</string>
<string name="accessibility_no_wifi" msgid="8834610636137374508">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‏‎‏‏‎‏‎‏‏‏‏‎‎‏‎‎‎‎‏‏‎‏‎‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‎‎‏‏‎‎‏‎‏‏‎‎‎Wifi disconnected.‎‏‎‎‏‎"</string>
<string name="accessibility_wifi_one_bar" msgid="4869376278894301820">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‎‏‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‎‏‎‏‎‎‏‏‎‏‎‏‎‏‎‏‎‎‏‎‎‏‏‏‏‏‎‎‎Wifi one bar.‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index ea62383fe9a5..f351e708cfa1 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -99,6 +99,13 @@
<string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"No se pudo sincronizar con <xliff:g id="DEVICE_NAME">%1$s</xliff:g> debido a que el PIN o la clave de acceso son incorrectos."</string>
<string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"No se puede establecer la comunicación con <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
<string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"Vínculo rechazado por <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+ <string name="bluetooth_talkback_computer" msgid="4875089335641234463">"Computadora"</string>
+ <string name="bluetooth_talkback_headset" msgid="5140152177885220949">"Auriculares"</string>
+ <string name="bluetooth_talkback_phone" msgid="4260255181240622896">"Teléfono"</string>
+ <string name="bluetooth_talkback_imaging" msgid="551146170554589119">"Imágenes"</string>
+ <string name="bluetooth_talkback_headphone" msgid="26580326066627664">"Auriculares"</string>
+ <string name="bluetooth_talkback_input_peripheral" msgid="5165842622743212268">"Periférico de entrada"</string>
+ <string name="bluetooth_talkback_bluetooth" msgid="5615463912185280812">"Bluetooth"</string>
<string name="accessibility_wifi_off" msgid="1166761729660614716">"Wi-Fi inhabilitado"</string>
<string name="accessibility_no_wifi" msgid="8834610636137374508">"Wi-Fi desconectado"</string>
<string name="accessibility_wifi_one_bar" msgid="4869376278894301820">"Una barra de Wi-Fi"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 42ee5817e341..17d7191fae46 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -22,7 +22,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="wifi_fail_to_scan" msgid="1265540342578081461">"नेटवर्कसाठी स्कॅन करू शकत नाही"</string>
<string name="wifi_security_none" msgid="7985461072596594400">"काहीही नाही"</string>
- <string name="wifi_remembered" msgid="4955746899347821096">"जतन केले"</string>
+ <string name="wifi_remembered" msgid="4955746899347821096">"सेव्ह केले"</string>
<string name="wifi_disabled_generic" msgid="4259794910584943386">"अक्षम"</string>
<string name="wifi_disabled_network_failure" msgid="2364951338436007124">"IP कॉन्फिगरेशन अयशस्वी"</string>
<string name="wifi_disabled_by_recommendation_provider" msgid="5168315140978066096">"कमी दर्जाच्या नेटवर्कमुळे कनेक्ट केलेले नाही"</string>
@@ -34,7 +34,7 @@
<string name="wifi_not_in_range" msgid="1136191511238508967">"परिक्षेत्रामध्ये नाही"</string>
<string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"स्वयंचलितपणे कनेक्ट करणार नाही"</string>
<string name="wifi_no_internet" msgid="3880396223819116454">"इंटरनेट अॅक्सेस नाही"</string>
- <string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> द्वारे जतन केले"</string>
+ <string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> द्वारे सेव्ह केले"</string>
<string name="connected_via_network_scorer" msgid="5713793306870815341">"%1$s द्वारे स्वयंचलितपणे कनेक्ट केले"</string>
<string name="connected_via_network_scorer_default" msgid="7867260222020343104">"नेटवर्क रेटिंग प्रदात्याद्वारे स्वयंचलितपणे कनेक्ट केले"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s द्वारे कनेक्‍ट केले"</string>
@@ -52,7 +52,7 @@
<string name="preference_summary_default_combination" msgid="8532964268242666060">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
<string name="bluetooth_disconnected" msgid="6557104142667339895">"डिस्कनेक्ट केले"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"डिस्कनेक्ट करत आहे..."</string>
- <string name="bluetooth_connecting" msgid="8555009514614320497">"कनेक्ट करीत आहे..."</string>
+ <string name="bluetooth_connecting" msgid="8555009514614320497">"कनेक्ट करत आहे..."</string>
<string name="bluetooth_connected" msgid="6038755206916626419">"कनेक्ट केले"</string>
<string name="bluetooth_pairing" msgid="1426882272690346242">"जोडत आहे…"</string>
<string name="bluetooth_connected_no_headset" msgid="2866994875046035609">"कनेक्ट केले (फोन नाही)"</string>
@@ -99,6 +99,13 @@
<string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"अयोग्य पिन किंवा पासकीमुळे <xliff:g id="DEVICE_NAME">%1$s</xliff:g> सह जोडू शकलो नाही."</string>
<string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> शी संप्रेषण करू शकत नाही."</string>
<string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> द्वारे पेअरींग नाकारले."</string>
+ <string name="bluetooth_talkback_computer" msgid="4875089335641234463">"संगणक"</string>
+ <string name="bluetooth_talkback_headset" msgid="5140152177885220949">"हेडसेट"</string>
+ <string name="bluetooth_talkback_phone" msgid="4260255181240622896">"फोन"</string>
+ <string name="bluetooth_talkback_imaging" msgid="551146170554589119">"इमेज"</string>
+ <string name="bluetooth_talkback_headphone" msgid="26580326066627664">"हेडफोन"</string>
+ <string name="bluetooth_talkback_input_peripheral" msgid="5165842622743212268">"इनपुट परिधीय"</string>
+ <string name="bluetooth_talkback_bluetooth" msgid="5615463912185280812">"ब्लूटूथ"</string>
<string name="accessibility_wifi_off" msgid="1166761729660614716">"वाय फाय बंद."</string>
<string name="accessibility_no_wifi" msgid="8834610636137374508">"वाय फाय डिस्कनेक्ट झाले."</string>
<string name="accessibility_wifi_one_bar" msgid="4869376278894301820">"वाय फाय एक बार."</string>
@@ -135,7 +142,7 @@
<string name="tts_play_example_summary" msgid="8029071615047894486">"उच्चार संश्लेषणाचे एक छोटेसे प्रात्यक्षिक प्ले करा"</string>
<string name="tts_install_data_title" msgid="4264378440508149986">"व्हॉइस डेटा इंस्टॉल करा"</string>
<string name="tts_install_data_summary" msgid="5742135732511822589">"उच्चार संश्लेषणासाठी आवश्यक आवाज डेटा इंस्टॉल करा"</string>
- <string name="tts_engine_security_warning" msgid="8786238102020223650">"हे उच्चार संश्लेषण इंजिन संकेतशब्द आणि क्रेडिट कार्ड नंबर यासारख्या वैयक्तिक मजकुरासह, बोलला जाणारा सर्व मजकूर संकलित करण्यात सक्षम होऊ शकते. हे <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g> इंजिनवरून येते. या उच्चार संश्लेषण इंजिनचा वापर सक्षम करायचा?"</string>
+ <string name="tts_engine_security_warning" msgid="8786238102020223650">"हे उच्चार संश्लेषण इंजिन पासवर्ड आणि क्रेडिट कार्ड नंबर यासारख्या वैयक्तिक मजकुरासह, बोलला जाणारा सर्व मजकूर संकलित करण्यात सक्षम होऊ शकते. हे <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g> इंजिनवरून येते. या उच्चार संश्लेषण इंजिनचा वापर सक्षम करायचा?"</string>
<string name="tts_engine_network_required" msgid="1190837151485314743">"या भाषेस टेक्‍स्‍ट टू स्‍पीचसाठी एका नेटवर्क कनेक्शनची आवश्यकता आहे."</string>
<string name="tts_default_sample_string" msgid="4040835213373086322">"हे उच्चार संश्लेषणाचे एक उदाहरण आहे"</string>
<string name="tts_status_title" msgid="7268566550242584413">"डीफॉल्ट भाषा स्थिती"</string>
@@ -245,7 +252,7 @@
<string name="debug_debugging_category" msgid="6781250159513471316">"डीबग करणे"</string>
<string name="debug_app" msgid="8349591734751384446">"डीबग अॅप निवडा"</string>
<string name="debug_app_not_set" msgid="718752499586403499">"कोणतेही डीबग अॅप्लिकेशन सेट नाही"</string>
- <string name="debug_app_set" msgid="2063077997870280017">"अॅप्लिकेशन डीबग करीत आहे: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="debug_app_set" msgid="2063077997870280017">"अॅप्लिकेशन डीबग करत आहे: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="select_application" msgid="5156029161289091703">"अॅप्लिकेशन निवडा"</string>
<string name="no_application" msgid="2813387563129153880">"काहीही नाही"</string>
<string name="wait_for_debugger" msgid="1202370874528893091">"डीबगरची प्रतीक्षा करा"</string>
@@ -303,11 +310,11 @@
<string name="force_resizable_activities_summary" msgid="6667493494706124459">"manifest मूल्यांकडे दुर्लक्ष करून, एकाधिक-विंडोसाठी सर्व क्रियाकलापांचा आकार बदलण्यायोग्य करा."</string>
<string name="enable_freeform_support" msgid="1461893351278940416">"freeform विंडो सक्षम करा"</string>
<string name="enable_freeform_support_summary" msgid="8247310463288834487">"प्रायोगिक मुक्तस्वरूपाच्या विंडोसाठी समर्थन सक्षम करा."</string>
- <string name="local_backup_password_title" msgid="3860471654439418822">"डेस्कटॉप बॅकअप संकेतशब्द"</string>
+ <string name="local_backup_password_title" msgid="3860471654439418822">"डेस्कटॉप बॅकअप पासवर्ड"</string>
<string name="local_backup_password_summary_none" msgid="6951095485537767956">"डेस्कटॉप पूर्ण बॅक अप सध्या संरक्षित नाहीत"</string>
- <string name="local_backup_password_summary_change" msgid="5376206246809190364">"डेस्कटॉपच्या पूर्ण बॅकअपसाठी असलेला संकेतशब्द बदलण्यासाठी किंवा काढण्यासाठी टॅप करा"</string>
+ <string name="local_backup_password_summary_change" msgid="5376206246809190364">"डेस्कटॉपच्या पूर्ण बॅकअपसाठी असलेला पासवर्ड बदलण्यासाठी किंवा काढण्यासाठी टॅप करा"</string>
<string name="local_backup_password_toast_success" msgid="582016086228434290">"नवीन बॅक अप पासवर्ड सेट झाला"</string>
- <string name="local_backup_password_toast_confirmation_mismatch" msgid="7805892532752708288">"नवीन संकेतशब्द आणि पुष्टीकरण जुळत नाही"</string>
+ <string name="local_backup_password_toast_confirmation_mismatch" msgid="7805892532752708288">"नवीन पासवर्ड आणि पुष्टीकरण जुळत नाही"</string>
<string name="local_backup_password_toast_validation_failure" msgid="5646377234895626531">"बॅक अप पासवर्ड सेट करणे अयशस्वी"</string>
<string-array name="color_mode_names">
<item msgid="2425514299220523812">"सशक्त (डीफॉल्ट)"</item>
@@ -382,13 +389,13 @@
<string name="screen_zoom_summary_custom" msgid="5611979864124160447">"सानुकूल करा (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string>
<string name="help_feedback_label" msgid="6815040660801785649">"मदत आणि अभिप्राय"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"मेनू"</string>
- <string name="retail_demo_reset_message" msgid="118771671364131297">"डेमो मोडमध्ये फॅक्टरी रीसेट करण्यासाठी पासवर्ड प्रविष्ट करा"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"डेमो मोडमध्ये फॅक्टरी रीसेट करण्यासाठी पासवर्ड एंटर करा"</string>
<string name="retail_demo_reset_next" msgid="8356731459226304963">"पुढील"</string>
- <string name="retail_demo_reset_title" msgid="696589204029930100">"संकेतशब्द आवश्यक"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"पासवर्ड आवश्यक"</string>
<string name="active_input_method_subtypes" msgid="3596398805424733238">"सक्रिय इनपुट पद्धती"</string>
<string name="use_system_language_to_select_input_method_subtypes" msgid="5747329075020379587">"सिस्टम भाषा वापरा"</string>
<string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> साठी सेटिंग्ज उघडण्यात अयशस्वी"</string>
- <string name="ime_security_warning" msgid="4135828934735934248">"ही इनपुट पद्धत संकेतशब्द आणि क्रेडिट कार्ड नंबर यासह, आपण टाइप करता तो सर्व मजकूर संकलित करण्यात सक्षम होऊ शकते. ही <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> अॅपवरून येते. ही इनपुट पद्धत वापरायची?"</string>
+ <string name="ime_security_warning" msgid="4135828934735934248">"ही इनपुट पद्धत पासवर्ड आणि क्रेडिट कार्ड नंबर यासह, आपण टाइप करता तो सर्व मजकूर संकलित करण्यात सक्षम होऊ शकते. ही <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> अॅपवरून येते. ही इनपुट पद्धत वापरायची?"</string>
<string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"टीप: रीबूट केल्यानंतर, तुम्ही आपला फोन अनलॉक करे पर्यंत हे अॅप सुरू होऊ शकत नाही"</string>
<string name="ims_reg_title" msgid="7609782759207241443">"IMS नोंदणी स्थिती"</string>
<string name="ims_reg_status_registered" msgid="933003316932739188">"नोंदवलेले"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 360848f72ddd..4bb16ffe597e 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -546,11 +546,13 @@
<!-- [CHAR LIMIT=NONE] Label for displaying Bluetooth Audio Codec Parameters while streaming -->
<string name="bluetooth_select_a2dp_codec_streaming_label">Streaming: <xliff:g id="streaming_parameter">%1$s</xliff:g></string>
- <!-- Title of the developer option for DNS over TLS. -->
- <string name="dns_tls">DNS over TLS</string>
- <!-- Summary to explain the developer option for DNS over TLS. This allows the user to
- request that the system attempt TLS with all DNS servers, or none. -->
- <string name="dns_tls_summary">If enabled, attempt DNS over TLS on port 853.</string>
+ <!-- Developer option setting for Private DNS -->
+ <string name="select_private_dns_configuration_title">Private DNS</string>
+ <string name="select_private_dns_configuration_dialog_title">Select Private DNS Mode</string>
+ <string name="private_dns_mode_off">Off</string>
+ <string name="private_dns_mode_opportunistic">Opportunistic</string>
+ <string name="private_dns_mode_provider">Private DNS provider hostname</string>
+ <string name="private_dns_mode_provider_hostname_hint">Enter hostname of DNS provider</string>
<!-- setting Checkbox summary whether to show options for wireless display certification -->
<string name="wifi_display_certification_summary">Show options for wireless display certification</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
index 38fe8790e4d0..386c4e0fc466 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
@@ -10,79 +10,89 @@ import android.support.v7.preference.PreferenceScreen;
*/
public abstract class AbstractPreferenceController {
- protected final Context mContext;
-
- public AbstractPreferenceController(Context context) {
- mContext = context;
- }
-
- /**
- * Displays preference in this controller.
- */
- public void displayPreference(PreferenceScreen screen) {
- if (isAvailable()) {
- if (this instanceof Preference.OnPreferenceChangeListener) {
- final Preference preference = screen.findPreference(getPreferenceKey());
- preference.setOnPreferenceChangeListener(
- (Preference.OnPreferenceChangeListener) this);
- }
- } else {
- removePreference(screen, getPreferenceKey());
- }
- }
-
- /**
- * Updates the current status of preference (summary, switch state, etc)
- */
- public void updateState(Preference preference) {
-
- }
-
- /**
- * Returns true if preference is available (should be displayed)
- */
- public abstract boolean isAvailable();
-
- /**
- * Handles preference tree click
- *
- * @param preference the preference being clicked
- * @return true if click is handled
- */
- public boolean handlePreferenceTreeClick(Preference preference) {
- return false;
- }
-
- /**
- * Returns the key for this preference.
- */
- public abstract String getPreferenceKey();
-
- /**
- * Removes preference from screen.
- */
- protected final void removePreference(PreferenceScreen screen, String key) {
- findAndRemovePreference(screen, key);
- }
-
- // finds the preference recursively and removes it from its parent
- private boolean findAndRemovePreference(PreferenceGroup prefGroup, String key) {
- final int preferenceCount = prefGroup.getPreferenceCount();
- for (int i = 0; i < preferenceCount; i++) {
- final Preference preference = prefGroup.getPreference(i);
- final String curKey = preference.getKey();
-
- if (curKey != null && curKey.equals(key)) {
- return prefGroup.removePreference(preference);
- }
-
- if (preference instanceof PreferenceGroup) {
- if (findAndRemovePreference((PreferenceGroup) preference, key)) {
- return true;
- }
- }
- }
- return false;
- }
+ protected final Context mContext;
+
+ public AbstractPreferenceController(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Displays preference in this controller.
+ */
+ public void displayPreference(PreferenceScreen screen) {
+ if (isAvailable()) {
+ if (this instanceof Preference.OnPreferenceChangeListener) {
+ final Preference preference = screen.findPreference(getPreferenceKey());
+ preference.setOnPreferenceChangeListener(
+ (Preference.OnPreferenceChangeListener) this);
+ }
+ } else {
+ removePreference(screen, getPreferenceKey());
+ }
+ }
+
+ /**
+ * Updates the current status of preference (summary, switch state, etc)
+ */
+ public void updateState(Preference preference) {
+
+ }
+
+ /**
+ * Returns true if preference is available (should be displayed)
+ */
+ public abstract boolean isAvailable();
+
+ /**
+ * Handles preference tree click
+ *
+ * @param preference the preference being clicked
+ * @return true if click is handled
+ */
+ public boolean handlePreferenceTreeClick(Preference preference) {
+ return false;
+ }
+
+ /**
+ * Returns the key for this preference.
+ */
+ public abstract String getPreferenceKey();
+
+ /**
+ * Removes preference from screen.
+ */
+ protected final void removePreference(PreferenceScreen screen, String key) {
+ findAndRemovePreference(screen, key);
+ }
+
+ /**
+ * Show/hide a preference.
+ */
+ protected final void setVisible(PreferenceGroup group, String key, boolean isVisible) {
+ final Preference pref = group.findPreference(key);
+ if (pref != null) {
+ pref.setVisible(isVisible);
+ }
+ }
+
+ // finds the preference recursively and removes it from its parent
+ private boolean findAndRemovePreference(PreferenceGroup prefGroup, String key) {
+ final int preferenceCount = prefGroup.getPreferenceCount();
+ for (int i = 0; i < preferenceCount; i++) {
+ final Preference preference = prefGroup.getPreference(i);
+ final String curKey = preference.getKey();
+
+ if (curKey != null && curKey.equals(key)) {
+ return prefGroup.removePreference(preference);
+ }
+
+ if (preference instanceof PreferenceGroup) {
+ if (findAndRemovePreference((PreferenceGroup) preference, key)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index fdbbf14aa958..dd55188e390f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -31,15 +31,17 @@ import android.support.v7.preference.PreferenceViewHolder;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.SparseArray;
+import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.settingslib.R;
import com.android.settingslib.TronUtils;
+import com.android.settingslib.TwoTargetPreference;
import com.android.settingslib.Utils;
import com.android.settingslib.wifi.AccessPoint.Speed;
-public class AccessPointPreference extends Preference {
+public class AccessPointPreference extends TwoTargetPreference {
private static final int[] STATE_SECURED = {
R.attr.state_encrypted
@@ -126,7 +128,6 @@ public class AccessPointPreference extends Preference {
int iconResId, boolean forSavedNetworks, StateListDrawable frictionSld,
int level, IconInjector iconInjector) {
super(context);
- setWidgetLayoutResource(R.layout.access_point_friction_widget);
mBadgeCache = cache;
mAccessPoint = accessPoint;
mForSavedNetworks = forSavedNetworks;
@@ -165,6 +166,20 @@ public class AccessPointPreference extends Preference {
ImageView frictionImageView = (ImageView) view.findViewById(R.id.friction_icon);
bindFrictionImage(frictionImageView);
+ setDividerVisibility(view, View.GONE);
+ }
+
+ protected void setDividerVisibility(final PreferenceViewHolder view,
+ @View.Visibility int visibility) {
+ final View divider = view.findViewById(R.id.two_target_divider);
+ if (divider != null) {
+ divider.setVisibility(visibility);
+ }
+ }
+
+ @Override
+ protected int getSecondTargetResId() {
+ return R.layout.access_point_friction_widget;
}
protected void updateIcon(int level, Context context) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawable/UserIconDrawableTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawable/UserIconDrawableTest.java
index 5fdd1149b90f..508107c1d9ce 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawable/UserIconDrawableTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawable/UserIconDrawableTest.java
@@ -16,22 +16,19 @@
package com.android.settingslib.drawable;
+import static com.google.common.truth.Truth.assertThat;
+
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
import com.android.settingslib.R;
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-import com.android.settingslib.TestConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-
-import static com.google.common.truth.Truth.assertThat;
-@RunWith(SettingsLibRobolectricTestRunner.class)
-@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+@RunWith(AndroidJUnit4.class)
public class UserIconDrawableTest {
private UserIconDrawable mDrawable;
@@ -39,7 +36,7 @@ public class UserIconDrawableTest {
@Test
public void getConstantState_shouldNotBeNull() {
final Bitmap b = BitmapFactory.decodeResource(
- RuntimeEnvironment.application.getResources(),
+ InstrumentationRegistry.getTargetContext().getResources(),
R.drawable.home);
mDrawable = new UserIconDrawable(100 /* size */).setIcon(b).bake();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/PreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/AbstractPreferenceControllerTest.java
index 9d7cd11ec7d4..c08bc2a193d3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/PreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/AbstractPreferenceControllerTest.java
@@ -15,45 +15,49 @@
*/
package com.android.settingslib.core;
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.content.Context;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceGroup;
import android.support.v7.preference.PreferenceManager;
import android.support.v7.preference.PreferenceScreen;
+
import com.android.settingslib.TestConfig;
-import com.android.settingslib.core.AbstractPreferenceController;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication;
-import org.robolectric.RobolectricTestRunner;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
-public class PreferenceControllerTest {
+public class AbstractPreferenceControllerTest {
@Mock
private Context mContext;
@Mock
private PreferenceScreen mScreen;
- @Mock
- private Preference mPreference;
+ private Preference mPreference;
private TestPrefController mTestPrefController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mPreference = new Preference(RuntimeEnvironment.application);
+ mPreference.setKey(TestPrefController.KEY_PREF);
mTestPrefController = new TestPrefController(mContext);
}
@@ -61,7 +65,6 @@ public class PreferenceControllerTest {
public void removeExistingPref_shouldBeRemoved() {
when(mScreen.getPreferenceCount()).thenReturn(1);
when(mScreen.getPreference(0)).thenReturn(mPreference);
- when(mPreference.getKey()).thenReturn(TestPrefController.KEY_PREF);
mTestPrefController.removePreference(mScreen, TestPrefController.KEY_PREF);
@@ -85,10 +88,25 @@ public class PreferenceControllerTest {
}
@Test
+ public void setVisible_prefIsVisible_shouldSetToVisible() {
+ when(mScreen.findPreference(TestPrefController.KEY_PREF)).thenReturn(mPreference);
+
+ mTestPrefController.setVisible(mScreen, TestPrefController.KEY_PREF, true /* visible */);
+ assertThat(mPreference.isVisible()).isTrue();
+ }
+
+ @Test
+ public void setVisible_prefNotVisible_shouldSetToInvisible() {
+ when(mScreen.findPreference(TestPrefController.KEY_PREF)).thenReturn(mPreference);
+
+ mTestPrefController.setVisible(mScreen, TestPrefController.KEY_PREF, false /* visible */);
+ assertThat(mPreference.isVisible()).isFalse();
+ }
+
+ @Test
public void doNotDisplayPref_ifNotAvailable() {
when(mScreen.getPreferenceCount()).thenReturn(1);
when(mScreen.getPreference(0)).thenReturn(mPreference);
- when(mPreference.getKey()).thenReturn(TestPrefController.KEY_PREF);
mTestPrefController.isAvailable = false;
mTestPrefController.displayPreference(mScreen);
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 7b8d0db9739e..29ecac0080ee 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -120,6 +120,7 @@
<uses-permission android:name="android.permission.TRUST_LISTENER" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.RESET_FINGERPRINT_LOCKOUT" />
+ <uses-permission android:name="android.permission.BIND_SLICE" />
<!-- Needed for WallpaperManager.clear in ImageWallpaper.updateWallpaperLocked -->
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
@@ -222,7 +223,7 @@
-->
<service android:name="SystemUIService"
android:exported="true"
- />
+ />
<!-- Recents depends on every user having their own SystemUI process, so on user switch,
ensure that the process is created by starting this service.
@@ -568,6 +569,11 @@
android:resource="@xml/fileprovider" />
</provider>
+ <provider android:name=".keyguard.KeyguardSliceProvider"
+ android:authorities="com.android.systemui.keyguard"
+ android:exported="true">
+ </provider>
+
<receiver
android:name=".statusbar.KeyboardShortcutsReceiver">
<intent-filter>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
index 56fb73faf868..020cfeeaa194 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
@@ -18,34 +18,37 @@
-->
<!-- This is a view that shows general status information in Keyguard. -->
-<LinearLayout
+<com.android.keyguard.KeyguardSliceView
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:gravity="center">
- <com.android.systemui.statusbar.policy.DateView
- android:id="@+id/date_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textColor="?attr/wallpaperTextColor"
- style="@style/widget_label"
- android:letterSpacing="0.05"
- android:gravity="center"
- />
- <TextView android:id="@+id/alarm_status"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:drawablePadding="6dp"
- android:drawableStart="@drawable/ic_access_alarms_big"
- android:drawableTint="?attr/wallpaperTextColorSecondary"
- android:drawableTintMode="src_in"
- android:textColor="?attr/wallpaperTextColorSecondary"
- android:letterSpacing="0.05"
- style="@style/widget_label"
- android:layout_marginStart="6dp"
- android:gravity="center"
- android:visibility="gone"
- />
-</LinearLayout>
+ android:layout_marginTop="@dimen/date_owner_info_margin"
+ android:layout_gravity="center_horizontal"
+ android:paddingTop="4dp"
+ android:clipToPadding="false"
+ android:orientation="vertical"
+ android:layout_centerHorizontal="true">
+ <TextView android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:fadingEdge="horizontal"
+ android:gravity="center"
+ android:textSize="22sp"
+ android:textColor="?attr/wallpaperTextColor"
+ />
+ <TextView android:id="@+id/text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:gravity="center"
+ android:visibility="gone"
+ android:textSize="16sp"
+ android:textColor="?attr/wallpaperTextColor"
+ android:layout_marginTop="4dp"
+ android:ellipsize="end"
+ />
+</com.android.keyguard.KeyguardSliceView> \ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9901f6ff8fda..c678111f29c7 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -95,6 +95,10 @@
<!-- Height of a heads up notification in the status bar -->
<dimen name="notification_max_heads_up_height_increased">188dp</dimen>
+ <!-- Height of a messaging notifications with actions at least. Not that this is an upper bound
+ and the notification won't use this much, but is measured with wrap_content -->
+ <dimen name="notification_messaging_actions_min_height">196dp</dimen>
+
<!-- a threshold in dp per second that is considered fast scrolling -->
<dimen name="scroll_fast_threshold">1500dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index e5f802944ff3..cfd95b41825a 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -66,9 +66,6 @@
<!-- For notification icons for which targetSdk < L, this caches whether the icon is grayscale -->
<item type="id" name="icon_is_grayscale" />
- <item type="id" name="clip_children_tag" />
- <item type="id" name="clip_children_set_tag" />
- <item type="id" name="clip_to_padding_tag" />
<item type="id" name="image_icon_tag" />
<item type="id" name="contains_transformed_view" />
<item type="id" name="is_clicked_heads_up_tag" />
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
new file mode 100644
index 000000000000..c18f9b61c044
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -0,0 +1,160 @@
+/*
+ * 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.keyguard;
+
+import android.app.PendingIntent;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.graphics.ColorUtils;
+import com.android.systemui.R;
+import com.android.systemui.keyguard.KeyguardSliceProvider;
+
+/**
+ * View visible under the clock on the lock screen and AoD.
+ */
+public class KeyguardSliceView extends LinearLayout {
+
+ private final Uri mKeyguardSliceUri;
+ private TextView mTitle;
+ private TextView mText;
+ private Slice mSlice;
+ private PendingIntent mSliceAction;
+ private int mTextColor;
+ private float mDarkAmount = 0;
+
+ private final ContentObserver mObserver;
+
+ public KeyguardSliceView(Context context) {
+ this(context, null, 0);
+ }
+
+ public KeyguardSliceView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public KeyguardSliceView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mObserver = new KeyguardSliceObserver(new Handler());
+ mKeyguardSliceUri = Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI);;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mTitle = findViewById(R.id.title);
+ mText = findViewById(R.id.text);
+ mTextColor = mTitle.getCurrentTextColor();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ // Set initial content
+ showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri));
+
+ // Make sure we always have the most current slice
+ getContext().getContentResolver().registerContentObserver(mKeyguardSliceUri,
+ false /* notifyDescendants */, mObserver);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ getContext().getContentResolver().unregisterContentObserver(mObserver);
+ }
+
+ private void showSlice(Slice slice) {
+ // Items will be wrapped into an action when they have tap targets.
+ SliceItem actionSlice = SliceQuery.find(slice, SliceItem.TYPE_ACTION);
+ if (actionSlice != null) {
+ mSlice = actionSlice.getSlice();
+ mSliceAction = actionSlice.getAction();
+ } else {
+ mSlice = slice;
+ mSliceAction = null;
+ }
+
+ if (mSlice == null) {
+ setVisibility(GONE);
+ return;
+ }
+
+ SliceItem title = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE, null);
+ if (title == null) {
+ mTitle.setVisibility(GONE);
+ } else {
+ mTitle.setVisibility(VISIBLE);
+ mTitle.setText(title.getText());
+ }
+
+ SliceItem text = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, null, Slice.HINT_TITLE);
+ if (text == null) {
+ mText.setVisibility(GONE);
+ } else {
+ mText.setVisibility(VISIBLE);
+ mText.setText(text.getText());
+ }
+
+ final int visibility = title == null && text == null ? GONE : VISIBLE;
+ if (visibility != getVisibility()) {
+ setVisibility(visibility);
+ }
+ }
+
+ public void setDark(float darkAmount) {
+ mDarkAmount = darkAmount;
+ updateTextColors();
+ }
+
+ public void setTextColor(int textColor) {
+ mTextColor = textColor;
+ }
+
+ private void updateTextColors() {
+ final int blendedColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
+ mTitle.setTextColor(blendedColor);
+ mText.setTextColor(blendedColor);
+ }
+
+ private class KeyguardSliceObserver extends ContentObserver {
+ KeyguardSliceObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ this.onChange(selfChange, null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri));
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index bc2a59df42a6..78cf2b9bf1ed 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -19,11 +19,9 @@ package com.android.keyguard;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.content.Context;
-import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
-import android.graphics.PorterDuff;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
@@ -42,8 +40,8 @@ import android.widget.TextView;
import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.LockPatternUtils;
+import com.android.settingslib.Utils;
import com.android.systemui.ChargingView;
-import com.android.systemui.statusbar.policy.DateView;
import java.util.Locale;
@@ -55,13 +53,11 @@ public class KeyguardStatusView extends GridLayout {
private final LockPatternUtils mLockPatternUtils;
private final AlarmManager mAlarmManager;
- private TextView mAlarmStatusView;
- private DateView mDateView;
private TextClock mClockView;
private TextView mOwnerInfo;
private ViewGroup mClockContainer;
private ChargingView mBatteryDoze;
- private View mKeyguardStatusArea;
+ private KeyguardSliceView mKeyguardSlice;
private Runnable mPendingMarqueeStart;
private Handler mHandler;
@@ -69,8 +65,6 @@ public class KeyguardStatusView extends GridLayout {
private boolean mPulsing;
private float mDarkAmount = 0;
private int mTextColor;
- private int mDateTextColor;
- private int mAlarmTextColor;
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
@@ -141,7 +135,6 @@ public class KeyguardStatusView extends GridLayout {
private void setEnableMarqueeImpl(boolean enabled) {
if (DEBUG) Log.v(TAG, (enabled ? "Enable" : "Disable") + " transport text marquee");
- if (mAlarmStatusView != null) mAlarmStatusView.setSelected(enabled);
if (mOwnerInfo != null) mOwnerInfo.setSelected(enabled);
}
@@ -149,8 +142,6 @@ public class KeyguardStatusView extends GridLayout {
protected void onFinishInflate() {
super.onFinishInflate();
mClockContainer = findViewById(R.id.keyguard_clock_container);
- mAlarmStatusView = findViewById(R.id.alarm_status);
- mDateView = findViewById(R.id.date_view);
mClockView = findViewById(R.id.clock_view);
mClockView.setShowCurrentUserTime(true);
if (KeyguardClockAccessibilityDelegate.isNeeded(mContext)) {
@@ -158,11 +149,9 @@ public class KeyguardStatusView extends GridLayout {
}
mOwnerInfo = findViewById(R.id.owner_info);
mBatteryDoze = findViewById(R.id.battery_doze);
- mKeyguardStatusArea = findViewById(R.id.keyguard_status_area);
- mVisibleInDoze = new View[]{mBatteryDoze, mClockView, mKeyguardStatusArea};
+ mKeyguardSlice = findViewById(R.id.keyguard_status_area);
+ mVisibleInDoze = new View[]{mBatteryDoze, mClockView, mKeyguardSlice};
mTextColor = mClockView.getCurrentTextColor();
- mDateTextColor = mDateView.getCurrentTextColor();
- mAlarmTextColor = mAlarmStatusView.getCurrentTextColor();
boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
setEnableMarquee(shouldMarquee);
@@ -184,8 +173,6 @@ public class KeyguardStatusView extends GridLayout {
layoutParams.bottomMargin = getResources().getDimensionPixelSize(
R.dimen.bottom_text_spacing_digital);
mClockView.setLayoutParams(layoutParams);
- mDateView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
- getResources().getDimensionPixelSize(R.dimen.widget_label_font_size));
if (mOwnerInfo != null) {
mOwnerInfo.setTextSize(TypedValue.COMPLEX_UNIT_PX,
getResources().getDimensionPixelSize(R.dimen.widget_label_font_size));
@@ -193,8 +180,6 @@ public class KeyguardStatusView extends GridLayout {
}
public void refreshTime() {
- mDateView.setDatePattern(Patterns.dateViewSkel);
-
mClockView.setFormat12Hour(Patterns.clockView12);
mClockView.setFormat24Hour(Patterns.clockView24);
}
@@ -205,23 +190,11 @@ public class KeyguardStatusView extends GridLayout {
Patterns.update(mContext, nextAlarm != null);
refreshTime();
- refreshAlarmStatus(nextAlarm);
- }
-
- void refreshAlarmStatus(AlarmManager.AlarmClockInfo nextAlarm) {
- if (nextAlarm != null) {
- String alarm = formatNextAlarm(mContext, nextAlarm);
- mAlarmStatusView.setText(alarm);
- mAlarmStatusView.setContentDescription(
- getResources().getString(R.string.keyguard_accessibility_next_alarm, alarm));
- mAlarmStatusView.setVisibility(View.VISIBLE);
- } else {
- mAlarmStatusView.setVisibility(View.GONE);
- }
}
public int getClockBottom() {
- return mKeyguardStatusArea.getBottom();
+ return mKeyguardSlice.getVisibility() == VISIBLE ? mKeyguardSlice.getBottom()
+ : mClockView.getBottom();
}
public float getClockTextSize() {
@@ -341,11 +314,8 @@ public class KeyguardStatusView extends GridLayout {
updateDozeVisibleViews();
mBatteryDoze.setDark(dark);
+ mKeyguardSlice.setDark(darkAmount);
mClockView.setTextColor(ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount));
- mDateView.setTextColor(ColorUtils.blendARGB(mDateTextColor, Color.WHITE, darkAmount));
- int blendedAlarmColor = ColorUtils.blendARGB(mAlarmTextColor, Color.WHITE, darkAmount);
- mAlarmStatusView.setTextColor(blendedAlarmColor);
- mAlarmStatusView.setCompoundDrawableTintList(ColorStateList.valueOf(blendedAlarmColor));
}
public void setPulsing(boolean pulsing) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 98b110619a1c..6650cc634700 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -92,6 +92,7 @@ public class DozeService extends DreamService
@Override
protected void dumpOnHandler(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dumpOnHandler(fd, pw, args);
if (mDozeMachine != null) {
mDozeMachine.dump(pw);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
new file mode 100644
index 000000000000..03018f7d8778
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -0,0 +1,159 @@
+/*
+ * 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.keyguard;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.icu.text.DateFormat;
+import android.icu.text.DisplayContext;
+import android.net.Uri;
+import android.os.Handler;
+import android.app.slice.Slice;
+import android.app.slice.SliceProvider;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * Simple Slice provider that shows the current date.
+ */
+public class KeyguardSliceProvider extends SliceProvider {
+
+ public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main";
+
+ private final Date mCurrentTime = new Date();
+ protected final Uri mSliceUri;
+ private final Handler mHandler;
+ private String mDatePattern;
+ private DateFormat mDateFormat;
+ private String mLastText;
+ private boolean mRegistered;
+ private boolean mRegisteredEveryMinute;
+
+ /**
+ * Receiver responsible for time ticking and updating the date format.
+ */
+ @VisibleForTesting
+ final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_TIME_TICK.equals(action)
+ || Intent.ACTION_DATE_CHANGED.equals(action)
+ || Intent.ACTION_TIME_CHANGED.equals(action)
+ || Intent.ACTION_TIMEZONE_CHANGED.equals(action)
+ || Intent.ACTION_LOCALE_CHANGED.equals(action)) {
+ if (Intent.ACTION_LOCALE_CHANGED.equals(action)
+ || Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
+ // need to get a fresh date format
+ mHandler.post(KeyguardSliceProvider.this::cleanDateFormat);
+ }
+ mHandler.post(KeyguardSliceProvider.this::updateClock);
+ }
+ }
+ };
+
+ public KeyguardSliceProvider() {
+ this(new Handler());
+ }
+
+ @VisibleForTesting
+ KeyguardSliceProvider(Handler handler) {
+ mHandler = handler;
+ mSliceUri = Uri.parse(KEYGUARD_SLICE_URI);
+ }
+
+ @Override
+ public Slice onBindSlice(Uri sliceUri) {
+ return new Slice.Builder(sliceUri).addText(mLastText, Slice.HINT_TITLE).build();
+ }
+
+ @Override
+ public boolean onCreate() {
+
+ mDatePattern = getContext().getString(R.string.system_ui_date_pattern);
+
+ registerClockUpdate(false /* everyMinute */);
+ updateClock();
+ return true;
+ }
+
+ protected void registerClockUpdate(boolean everyMinute) {
+ if (mRegistered) {
+ if (mRegisteredEveryMinute == everyMinute) {
+ return;
+ } else {
+ unregisterClockUpdate();
+ }
+ }
+
+ IntentFilter filter = new IntentFilter();
+ if (everyMinute) {
+ filter.addAction(Intent.ACTION_TIME_TICK);
+ }
+ filter.addAction(Intent.ACTION_DATE_CHANGED);
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
+ filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+ filter.addAction(Intent.ACTION_LOCALE_CHANGED);
+ getContext().registerReceiver(mIntentReceiver, filter, null /* permission*/,
+ null /* scheduler */);
+ mRegistered = true;
+ mRegisteredEveryMinute = everyMinute;
+ }
+
+ protected void unregisterClockUpdate() {
+ if (!mRegistered) {
+ return;
+ }
+ getContext().unregisterReceiver(mIntentReceiver);
+ mRegistered = false;
+ }
+
+ @VisibleForTesting
+ boolean isRegistered() {
+ return mRegistered;
+ }
+
+ protected void updateClock() {
+ final String text = getFormattedDate();
+ if (!text.equals(mLastText)) {
+ mLastText = text;
+ getContext().getContentResolver().notifyChange(mSliceUri, null /* observer */);
+ }
+ }
+
+ protected String getFormattedDate() {
+ if (mDateFormat == null) {
+ final Locale l = Locale.getDefault();
+ DateFormat format = DateFormat.getInstanceForSkeleton(mDatePattern, l);
+ format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE);
+ mDateFormat = format;
+ }
+ mCurrentTime.setTime(System.currentTimeMillis());
+ return mDateFormat.format(mCurrentTime);
+ }
+
+ @VisibleForTesting
+ void cleanDateFormat() {
+ mDateFormat = null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 6c5f4b237033..8ff950ed1c24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -69,6 +69,7 @@ import com.android.systemui.statusbar.notification.AboveShelfObserver;
import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.NotificationInflater;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -453,6 +454,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
} else {
headsUpheight = mMaxHeadsUpHeight;
}
+ NotificationViewWrapper headsUpWrapper = layout.getVisibleWrapper(
+ NotificationContentView.VISIBLE_TYPE_HEADSUP);
+ if (headsUpWrapper != null) {
+ headsUpheight = Math.max(headsUpheight, headsUpWrapper.getMinLayoutHeight());
+ }
layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight,
mNotificationAmbientHeight);
}
@@ -1256,16 +1262,21 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
private void initDimens() {
- mNotificationMinHeightLegacy = getFontScaledHeight(R.dimen.notification_min_height_legacy);
- mNotificationMinHeight = getFontScaledHeight(R.dimen.notification_min_height);
- mNotificationMinHeightLarge = getFontScaledHeight(
+ mNotificationMinHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_min_height_legacy);
+ mNotificationMinHeight = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_min_height);
+ mNotificationMinHeightLarge = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_min_height_increased);
- mNotificationMaxHeight = getFontScaledHeight(R.dimen.notification_max_height);
- mNotificationAmbientHeight = getFontScaledHeight(R.dimen.notification_ambient_height);
- mMaxHeadsUpHeightLegacy = getFontScaledHeight(
+ mNotificationMaxHeight = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_max_height);
+ mNotificationAmbientHeight = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_ambient_height);
+ mMaxHeadsUpHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_heads_up_height_legacy);
- mMaxHeadsUpHeight = getFontScaledHeight(R.dimen.notification_max_heads_up_height);
- mMaxHeadsUpHeightIncreased = getFontScaledHeight(
+ mMaxHeadsUpHeight = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_max_heads_up_height);
+ mMaxHeadsUpHeightIncreased = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_heads_up_height_increased);
Resources res = getResources();
@@ -1280,17 +1291,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
/**
- * @param dimenId the dimen to look up
- * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp
- */
- private int getFontScaledHeight(int dimenId) {
- int dimensionPixelSize = getResources().getDimensionPixelSize(dimenId);
- float factor = Math.max(1.0f, getResources().getDisplayMetrics().scaledDensity /
- getResources().getDisplayMetrics().density);
- return (int) (dimensionPixelSize * factor);
- }
-
- /**
* Resets this view so it can be re-used for an updated notification.
*/
public void reset() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
index 535300573c6e..09b11c295701 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
@@ -35,7 +35,8 @@ import java.util.Stack;
/**
* A view that can be transformed to and from.
*/
-public class ViewTransformationHelper implements TransformableView {
+public class ViewTransformationHelper implements TransformableView,
+ TransformState.TransformInfo {
private static final int TAG_CONTAINS_TRANSFORMED_VIEW = R.id.contains_transformed_view;
@@ -59,7 +60,7 @@ public class ViewTransformationHelper implements TransformableView {
public TransformState getCurrentState(int fadingView) {
View view = mTransformedViews.get(fadingView);
if (view != null && view.getVisibility() != View.GONE) {
- return TransformState.createFrom(view);
+ return TransformState.createFrom(view, this);
}
return null;
}
@@ -88,6 +89,7 @@ public class ViewTransformationHelper implements TransformableView {
endRunnable.run();
}
setVisible(false);
+ mViewTransformationAnimation = null;
} else {
abortTransformations();
}
@@ -245,7 +247,7 @@ public class ViewTransformationHelper implements TransformableView {
}
public void resetTransformedView(View view) {
- TransformState state = TransformState.createFrom(view);
+ TransformState state = TransformState.createFrom(view, this);
state.setVisible(true /* visible */, true /* force */);
state.recycle();
}
@@ -257,6 +259,11 @@ public class ViewTransformationHelper implements TransformableView {
return new ArraySet<>(mTransformedViews.values());
}
+ @Override
+ public boolean isAnimating() {
+ return mViewTransformationAnimation != null && mViewTransformationAnimation.isRunning();
+ }
+
public static abstract class CustomTransformation {
/**
* Transform a state to the given view
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
index 92f26d65f25e..8227b7797706 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
@@ -39,8 +39,8 @@ public class ImageTransformState extends TransformState {
private Icon mIcon;
@Override
- public void initFrom(View view) {
- super.initFrom(view);
+ public void initFrom(View view, TransformInfo transformInfo) {
+ super.initFrom(view, transformInfo);
if (view instanceof ImageView) {
mIcon = (Icon) view.getTag(ICON_TAG);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
new file mode 100644
index 000000000000..4c2a2f572083
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
@@ -0,0 +1,405 @@
+/*
+ * 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.notification;
+
+import android.content.res.Resources;
+import android.util.Pools;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.widget.MessagingGroup;
+import com.android.internal.widget.MessagingLayout;
+import com.android.internal.widget.MessagingLinearLayout;
+import com.android.internal.widget.MessagingMessage;
+import com.android.internal.widget.MessagingPropertyAnimator;
+import com.android.internal.widget.ViewClippingUtil;
+import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * A transform state of the action list
+*/
+public class MessagingLayoutTransformState extends TransformState {
+
+ private static Pools.SimplePool<MessagingLayoutTransformState> sInstancePool
+ = new Pools.SimplePool<>(40);
+ private MessagingLinearLayout mMessageContainer;
+ private MessagingLayout mMessagingLayout;
+ private HashMap<MessagingGroup, MessagingGroup> mGroupMap = new HashMap<>();
+ private float mRelativeTranslationOffset;
+
+ public static MessagingLayoutTransformState obtain() {
+ MessagingLayoutTransformState instance = sInstancePool.acquire();
+ if (instance != null) {
+ return instance;
+ }
+ return new MessagingLayoutTransformState();
+ }
+
+ @Override
+ public void initFrom(View view, TransformInfo transformInfo) {
+ super.initFrom(view, transformInfo);
+ if (mTransformedView instanceof MessagingLinearLayout) {
+ mMessageContainer = (MessagingLinearLayout) mTransformedView;
+ mMessagingLayout = mMessageContainer.getMessagingLayout();
+ Resources resources = view.getContext().getResources();
+ mRelativeTranslationOffset = resources.getDisplayMetrics().density * 8;
+ }
+ }
+
+ @Override
+ public boolean transformViewTo(TransformState otherState, float transformationAmount) {
+ if (otherState instanceof MessagingLayoutTransformState) {
+ // It's a party! Let's transform between these two layouts!
+ transformViewInternal((MessagingLayoutTransformState) otherState, transformationAmount,
+ true /* to */);
+ return true;
+ } else {
+ return super.transformViewTo(otherState, transformationAmount);
+ }
+ }
+
+ @Override
+ public void transformViewFrom(TransformState otherState, float transformationAmount) {
+ if (otherState instanceof MessagingLayoutTransformState) {
+ // It's a party! Let's transform between these two layouts!
+ transformViewInternal((MessagingLayoutTransformState) otherState, transformationAmount,
+ false /* to */);
+ } else {
+ super.transformViewFrom(otherState, transformationAmount);
+ }
+ }
+
+ private void transformViewInternal(MessagingLayoutTransformState mlt,
+ float transformationAmount, boolean to) {
+ ArrayList<MessagingGroup> ownGroups = filterHiddenGroups(
+ mMessagingLayout.getMessagingGroups());
+ ArrayList<MessagingGroup> otherGroups = filterHiddenGroups(
+ mlt.mMessagingLayout.getMessagingGroups());
+ HashMap<MessagingGroup, MessagingGroup> pairs = findPairs(ownGroups, otherGroups);
+ MessagingGroup lastPairedGroup = null;
+ float currentTranslation = 0;
+ float transformationDistanceRemaining = 0;
+ for (int i = ownGroups.size() - 1; i >= 0; i--) {
+ MessagingGroup ownGroup = ownGroups.get(i);
+ MessagingGroup matchingGroup = pairs.get(ownGroup);
+ if (!isGone(ownGroup)) {
+ if (matchingGroup != null) {
+ transformGroups(ownGroup, matchingGroup, transformationAmount, to);
+ if (lastPairedGroup == null) {
+ lastPairedGroup = ownGroup;
+ if (to){
+ float totalTranslation = ownGroup.getTop() - matchingGroup.getTop();
+ transformationDistanceRemaining
+ = matchingGroup.getAvatar().getTranslationY();
+ currentTranslation = transformationDistanceRemaining - totalTranslation;
+ } else {
+ float totalTranslation = matchingGroup.getTop() - ownGroup.getTop();
+ currentTranslation = ownGroup.getAvatar().getTranslationY();
+ transformationDistanceRemaining = currentTranslation - totalTranslation;
+ }
+ }
+ } else {
+ float groupTransformationAmount = transformationAmount;
+ if (lastPairedGroup != null) {
+ adaptGroupAppear(ownGroup, transformationAmount, currentTranslation,
+ to);
+ int distance = lastPairedGroup.getTop() - ownGroup.getTop();
+ float transformationDistance = mTransformInfo.isAnimating()
+ ? distance
+ : ownGroup.getHeight() * 0.75f;
+ float translationProgress = transformationDistanceRemaining
+ - (distance - transformationDistance);
+ groupTransformationAmount =
+ translationProgress / transformationDistance;
+ groupTransformationAmount = Math.max(0.0f, Math.min(1.0f,
+ groupTransformationAmount));
+ if (to) {
+ groupTransformationAmount = 1.0f - groupTransformationAmount;
+ }
+ }
+ if (to) {
+ disappear(ownGroup, groupTransformationAmount);
+ } else {
+ appear(ownGroup, groupTransformationAmount);
+ }
+ }
+ }
+ }
+ }
+
+ private void appear(MessagingGroup ownGroup, float transformationAmount) {
+ MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+ for (int j = 0; j < ownMessages.getChildCount(); j++) {
+ View child = ownMessages.getChildAt(j);
+ if (isGone(child)) {
+ continue;
+ }
+ appear(child, transformationAmount);
+ setClippingDeactivated(child, true);
+ }
+ appear(ownGroup.getAvatar(), transformationAmount);
+ appear(ownGroup.getSender(), transformationAmount);
+ setClippingDeactivated(ownGroup.getSender(), true);
+ setClippingDeactivated(ownGroup.getAvatar(), true);
+ }
+
+ private void adaptGroupAppear(MessagingGroup ownGroup, float transformationAmount,
+ float overallTranslation, boolean to) {
+ float relativeOffset;
+ if (to) {
+ relativeOffset = transformationAmount * mRelativeTranslationOffset;
+ } else {
+ relativeOffset = (1.0f - transformationAmount) * mRelativeTranslationOffset;
+ }
+ if (ownGroup.getSender().getVisibility() != View.GONE) {
+ relativeOffset *= 0.5f;
+ }
+ ownGroup.getMessageContainer().setTranslationY(relativeOffset);
+ ownGroup.setTranslationY(overallTranslation * 0.85f);
+ }
+
+ private void disappear(MessagingGroup ownGroup, float transformationAmount) {
+ MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+ for (int j = 0; j < ownMessages.getChildCount(); j++) {
+ View child = ownMessages.getChildAt(j);
+ if (isGone(child)) {
+ continue;
+ }
+ disappear(child, transformationAmount);
+ setClippingDeactivated(child, true);
+ }
+ disappear(ownGroup.getAvatar(), transformationAmount);
+ disappear(ownGroup.getSender(), transformationAmount);
+ setClippingDeactivated(ownGroup.getSender(), true);
+ setClippingDeactivated(ownGroup.getAvatar(), true);
+ }
+
+ private void appear(View child, float transformationAmount) {
+ if (child.getVisibility() == View.GONE) {
+ return;
+ }
+ TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+ ownState.appear(transformationAmount, null);
+ ownState.recycle();
+ }
+
+ private void disappear(View child, float transformationAmount) {
+ if (child.getVisibility() == View.GONE) {
+ return;
+ }
+ TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+ ownState.disappear(transformationAmount, null);
+ ownState.recycle();
+ }
+
+ private ArrayList<MessagingGroup> filterHiddenGroups(
+ ArrayList<MessagingGroup> groups) {
+ ArrayList<MessagingGroup> result = new ArrayList<>(groups);
+ for (int i = 0; i < result.size(); i++) {
+ MessagingGroup messagingGroup = result.get(i);
+ if (isGone(messagingGroup)) {
+ result.remove(i);
+ i--;
+ }
+ }
+ return result;
+ }
+
+ private void transformGroups(MessagingGroup ownGroup, MessagingGroup otherGroup,
+ float transformationAmount, boolean to) {
+ transformView(transformationAmount, to, ownGroup.getSender(), otherGroup.getSender(),
+ true /* sameAsAny */);
+ transformView(transformationAmount, to, ownGroup.getAvatar(), otherGroup.getAvatar(),
+ true /* sameAsAny */);
+ MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+ MessagingLinearLayout otherMessages = otherGroup.getMessageContainer();
+ float previousTranslation = 0;
+ for (int i = 0; i < ownMessages.getChildCount(); i++) {
+ View child = ownMessages.getChildAt(ownMessages.getChildCount() - 1 - i);
+ if (isGone(child)) {
+ continue;
+ }
+ int otherIndex = otherMessages.getChildCount() - 1 - i;
+ View otherChild = null;
+ if (otherIndex >= 0) {
+ otherChild = otherMessages.getChildAt(otherIndex);
+ if (isGone(otherChild)) {
+ otherChild = null;
+ }
+ }
+ if (otherChild == null) {
+ float distanceToTop = child.getTop() + child.getHeight() + previousTranslation;
+ transformationAmount = distanceToTop / child.getHeight();
+ transformationAmount = Math.max(0.0f, Math.min(1.0f, transformationAmount));
+ if (to) {
+ transformationAmount = 1.0f - transformationAmount;
+ }
+ }
+ transformView(transformationAmount, to, child, otherChild, false /* sameAsAny */);
+ if (otherChild == null) {
+ child.setTranslationY(previousTranslation);
+ setClippingDeactivated(child, true);
+ } else if (to) {
+ float totalTranslation = child.getTop() + ownGroup.getTop()
+ - otherChild.getTop() - otherChild.getTop();
+ previousTranslation = otherChild.getTranslationY() - totalTranslation;
+ } else {
+ previousTranslation = child.getTranslationY();
+ }
+ }
+ }
+
+ private void transformView(float transformationAmount, boolean to, View ownView,
+ View otherView, boolean sameAsAny) {
+ TransformState ownState = TransformState.createFrom(ownView, mTransformInfo);
+ if (!mTransformInfo.isAnimating()) {
+ ownState.setDefaultInterpolator(Interpolators.LINEAR);
+ }
+ ownState.setIsSameAsAnyView(sameAsAny);
+ if (to) {
+ if (otherView != null) {
+ TransformState otherState = TransformState.createFrom(otherView, mTransformInfo);
+ ownState.transformViewTo(otherState, transformationAmount);
+ otherState.recycle();
+ } else {
+ ownState.disappear(transformationAmount, null);
+ }
+ } else {
+ if (otherView != null) {
+ TransformState otherState = TransformState.createFrom(otherView, mTransformInfo);
+ ownState.transformViewFrom(otherState, transformationAmount);
+ otherState.recycle();
+ } else {
+ ownState.appear(transformationAmount, null);
+ }
+ }
+ ownState.recycle();
+ }
+
+ private HashMap<MessagingGroup, MessagingGroup> findPairs(ArrayList<MessagingGroup> ownGroups,
+ ArrayList<MessagingGroup> otherGroups) {
+ mGroupMap.clear();
+ int lastMatch = Integer.MAX_VALUE;
+ for (int i = ownGroups.size() - 1; i >= 0; i--) {
+ MessagingGroup ownGroup = ownGroups.get(i);
+ MessagingGroup bestMatch = null;
+ int bestCompatibility = 0;
+ for (int j = Math.min(otherGroups.size(), lastMatch) - 1; j >= 0; j--) {
+ MessagingGroup otherGroup = otherGroups.get(j);
+ int compatibility = ownGroup.calculateGroupCompatibility(otherGroup);
+ if (compatibility > bestCompatibility) {
+ bestCompatibility = compatibility;
+ bestMatch = otherGroup;
+ lastMatch = j;
+ }
+ }
+ if (bestMatch != null) {
+ mGroupMap.put(ownGroup, bestMatch);
+ }
+ }
+ return mGroupMap;
+ }
+
+ private boolean isGone(View view) {
+ if (view.getVisibility() == View.GONE) {
+ return true;
+ }
+ final ViewGroup.LayoutParams lp = view.getLayoutParams();
+ if (lp instanceof MessagingLinearLayout.LayoutParams
+ && ((MessagingLinearLayout.LayoutParams) lp).hide) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void setVisible(boolean visible, boolean force) {
+ resetTransformedView();
+ ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups();
+ for (int i = 0; i < ownGroups.size(); i++) {
+ MessagingGroup ownGroup = ownGroups.get(i);
+ if (!isGone(ownGroup)) {
+ MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+ for (int j = 0; j < ownMessages.getChildCount(); j++) {
+ MessagingMessage child = (MessagingMessage) ownMessages.getChildAt(j);
+ setVisible(child, visible, force);
+ }
+ setVisible(ownGroup.getAvatar(), visible, force);
+ setVisible(ownGroup.getSender(), visible, force);
+ }
+ }
+ }
+
+ private void setVisible(View child, boolean visible, boolean force) {
+ if (isGone(child) || MessagingPropertyAnimator.isAnimatingAlpha(child)) {
+ return;
+ }
+ TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+ ownState.setVisible(visible, force);
+ ownState.recycle();
+ }
+
+ @Override
+ protected void resetTransformedView() {
+ super.resetTransformedView();
+ ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups();
+ for (int i = 0; i < ownGroups.size(); i++) {
+ MessagingGroup ownGroup = ownGroups.get(i);
+ if (!isGone(ownGroup)) {
+ MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+ for (int j = 0; j < ownMessages.getChildCount(); j++) {
+ View child = ownMessages.getChildAt(j);
+ if (isGone(child)) {
+ continue;
+ }
+ resetTransformedView(child);
+ setClippingDeactivated(child, false);
+ }
+ resetTransformedView(ownGroup.getAvatar());
+ resetTransformedView(ownGroup.getSender());
+ setClippingDeactivated(ownGroup.getAvatar(), false);
+ setClippingDeactivated(ownGroup.getSender(), false);
+ ownGroup.setTranslationY(0);
+ ownGroup.getMessageContainer().setTranslationY(0);
+ }
+ }
+ }
+
+ private void resetTransformedView(View child) {
+ TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+ ownState.resetTransformedView();
+ ownState.recycle();
+ }
+
+ @Override
+ protected void reset() {
+ super.reset();
+ mMessageContainer = null;
+ mMessagingLayout = null;
+ }
+
+ @Override
+ public void recycle() {
+ super.recycle();
+ mGroupMap.clear();;
+ sInstancePool.release(this);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
index f6ee1cac9c3d..fb5644f76ce3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.notification;
+import com.android.internal.widget.MessagingLayout;
import com.android.internal.widget.MessagingLinearLayout;
+import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.TransformableView;
@@ -32,41 +34,20 @@ import java.util.ArrayList;
*/
public class NotificationMessagingTemplateViewWrapper extends NotificationTemplateViewWrapper {
- private View mContractedMessage;
- private ArrayList<View> mHistoricMessages = new ArrayList<View>();
+ private final int mMinHeightWithActions;
+ private MessagingLayout mMessagingLayout;
+ private MessagingLinearLayout mMessagingLinearLayout;
protected NotificationMessagingTemplateViewWrapper(Context ctx, View view,
ExpandableNotificationRow row) {
super(ctx, view, row);
+ mMessagingLayout = (MessagingLayout) view;
+ mMinHeightWithActions = NotificationUtils.getFontScaledHeight(ctx,
+ R.dimen.notification_messaging_actions_min_height);
}
private void resolveViews() {
- mContractedMessage = null;
-
- View container = mView.findViewById(com.android.internal.R.id.notification_messaging);
- if (container instanceof MessagingLinearLayout
- && ((MessagingLinearLayout) container).getChildCount() > 0) {
- MessagingLinearLayout messagingContainer = (MessagingLinearLayout) container;
-
- int childCount = messagingContainer.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = messagingContainer.getChildAt(i);
-
- if (child.getVisibility() == View.GONE
- && child instanceof TextView
- && !TextUtils.isEmpty(((TextView) child).getText())) {
- mHistoricMessages.add(child);
- }
-
- // Only consider the first visible child - transforming to a position other than the
- // first looks bad because we have to move across other messages that are fading in.
- if (child.getId() == messagingContainer.getContractedChildId()) {
- mContractedMessage = child;
- } else if (child.getVisibility() == View.VISIBLE) {
- break;
- }
- }
- }
+ mMessagingLinearLayout = mMessagingLayout.getMessagingLinearLayout();
}
@Override
@@ -81,16 +62,22 @@ public class NotificationMessagingTemplateViewWrapper extends NotificationTempla
protected void updateTransformedTypes() {
// This also clears the existing types
super.updateTransformedTypes();
- if (mContractedMessage != null) {
- mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT,
- mContractedMessage);
+ if (mMessagingLinearLayout != null) {
+ mTransformationHelper.addTransformedView(mMessagingLinearLayout.getId(),
+ mMessagingLinearLayout);
}
}
@Override
public void setRemoteInputVisible(boolean visible) {
- for (int i = 0; i < mHistoricMessages.size(); i++) {
- mHistoricMessages.get(i).setVisibility(visible ? View.VISIBLE : View.GONE);
+ mMessagingLayout.showHistoricMessages(visible);
+ }
+
+ @Override
+ public int getMinLayoutHeight() {
+ if (mActionsContainer != null && mActionsContainer.getVisibility() != View.GONE) {
+ return mMinHeightWithActions;
}
+ return super.getMinLayoutHeight();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index bb979ebd1288..fd085d9c2391 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -41,7 +41,7 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp
private ProgressBar mProgressBar;
private TextView mTitle;
private TextView mText;
- private View mActionsContainer;
+ protected View mActionsContainer;
private View mReplyAction;
private Rect mTmpRect = new Rect();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
index 3115361c6927..af393c91730f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
@@ -66,4 +66,14 @@ public class NotificationUtils {
Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) == 0;
}
+ /**
+ * @param dimenId the dimen to look up
+ * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp
+ */
+ public static int getFontScaledHeight(Context context, int dimenId) {
+ int dimensionPixelSize = context.getResources().getDimensionPixelSize(dimenId);
+ float factor = Math.max(1.0f, context.getResources().getDisplayMetrics().scaledDensity /
+ context.getResources().getDisplayMetrics().density);
+ return (int) (dimensionPixelSize * factor);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 5200d6962ed5..1cd5f15b4f80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -190,4 +190,8 @@ public abstract class NotificationViewWrapper implements TransformableView {
public boolean disallowSingleClick(float x, float y) {
return false;
}
+
+ public int getMinLayoutHeight() {
+ return 0;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java
index c4aabe48f61c..178c8130307f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java
@@ -33,8 +33,8 @@ public class TextViewTransformState extends TransformState {
private TextView mText;
@Override
- public void initFrom(View view) {
- super.initFrom(view);
+ public void initFrom(View view, TransformInfo transformInfo) {
+ super.initFrom(view, transformInfo);
if (view instanceof TextView) {
mText = (TextView) view;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index bafedff4f78c..ad07af0a3e46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -26,6 +26,8 @@ import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
+import com.android.internal.widget.MessagingPropertyAnimator;
+import com.android.internal.widget.ViewClippingUtil;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
@@ -43,23 +45,46 @@ public class TransformState {
public static final int TRANSFORM_ALL = TRANSFORM_X | TRANSFORM_Y;
private static final float UNDEFINED = -1f;
- private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
- private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
- private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
private static final int TRANSFORMATION_START_X = R.id.transformation_start_x_tag;
private static final int TRANSFORMATION_START_Y = R.id.transformation_start_y_tag;
private static final int TRANSFORMATION_START_SCLALE_X = R.id.transformation_start_scale_x_tag;
private static final int TRANSFORMATION_START_SCLALE_Y = R.id.transformation_start_scale_y_tag;
private static Pools.SimplePool<TransformState> sInstancePool = new Pools.SimplePool<>(40);
+ private static ViewClippingUtil.ClippingParameters CLIPPING_PARAMETERS
+ = new ViewClippingUtil.ClippingParameters() {
+ @Override
+ public boolean shouldFinish(View view) {
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ return !row.isChildInGroup();
+ }
+ return false;
+ }
+
+ @Override
+ public void onClippingStateChanged(View view, boolean isClipping) {
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (isClipping) {
+ row.setClipToActualHeight(true);
+ } else if (row.isChildInGroup()) {
+ row.setClipToActualHeight(false);
+ }
+ }
+ }
+ };
protected View mTransformedView;
+ protected TransformInfo mTransformInfo;
private int[] mOwnPosition = new int[2];
private boolean mSameAsAny;
private float mTransformationEndY = UNDEFINED;
private float mTransformationEndX = UNDEFINED;
+ private Interpolator mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
- public void initFrom(View view) {
+ public void initFrom(View view, TransformInfo transformInfo) {
mTransformedView = view;
+ mTransformInfo = transformInfo;
}
/**
@@ -108,13 +133,16 @@ public class TransformState {
final View transformedView = mTransformedView;
boolean transformX = (transformationFlags & TRANSFORM_X) != 0;
boolean transformY = (transformationFlags & TRANSFORM_Y) != 0;
- boolean transformScale = transformScale(otherState);
+ boolean differentHeight = otherState.getViewHeight() != getViewHeight();
+ boolean differentWidth = otherState.getViewWidth() != getViewWidth();
+ boolean transformScale = transformScale(otherState) && (differentHeight || differentWidth);
// lets animate the positions correctly
if (transformationAmount == 0.0f
|| transformX && getTransformationStartX() == UNDEFINED
|| transformY && getTransformationStartY() == UNDEFINED
- || transformScale && getTransformationStartScaleX() == UNDEFINED
- || transformScale && getTransformationStartScaleY() == UNDEFINED) {
+ || transformScale && getTransformationStartScaleX() == UNDEFINED && differentWidth
+ || transformScale && getTransformationStartScaleY() == UNDEFINED
+ && differentHeight) {
int[] otherPosition;
if (transformationAmount != 0.0f) {
otherPosition = otherState.getLaidOutLocationOnScreen();
@@ -132,14 +160,14 @@ public class TransformState {
}
// we also want to animate the scale if we're the same
View otherView = otherState.getTransformedView();
- if (transformScale && otherState.getViewWidth() != getViewWidth()) {
+ if (transformScale && differentWidth) {
setTransformationStartScaleX(otherState.getViewWidth() * otherView.getScaleX()
/ (float) getViewWidth());
transformedView.setPivotX(0);
} else {
setTransformationStartScaleX(UNDEFINED);
}
- if (transformScale && otherState.getViewHeight() != getViewHeight()) {
+ if (transformScale && differentHeight) {
setTransformationStartScaleY(otherState.getViewHeight() * otherView.getScaleY()
/ (float) getViewHeight());
transformedView.setPivotY(0);
@@ -159,7 +187,7 @@ public class TransformState {
}
setClippingDeactivated(transformedView, true);
}
- float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+ float interpolatedValue = mDefaultInterpolator.getInterpolation(
transformationAmount);
if (transformX) {
float interpolation = interpolatedValue;
@@ -297,7 +325,7 @@ public class TransformState {
}
setClippingDeactivated(transformedView, true);
}
- float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+ float interpolatedValue = mDefaultInterpolator.getInterpolation(
transformationAmount);
int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
int[] ownPosition = getLaidOutLocationOnScreen();
@@ -354,59 +382,8 @@ public class TransformState {
}
}
- public static void setClippingDeactivated(final View transformedView, boolean deactivated) {
- if (!(transformedView.getParent() instanceof ViewGroup)) {
- return;
- }
- ViewGroup view = (ViewGroup) transformedView.getParent();
- while (true) {
- ArraySet<View> clipSet = (ArraySet<View>) view.getTag(CLIP_CLIPPING_SET);
- if (clipSet == null) {
- clipSet = new ArraySet<>();
- view.setTag(CLIP_CLIPPING_SET, clipSet);
- }
- Boolean clipChildren = (Boolean) view.getTag(CLIP_CHILDREN_TAG);
- if (clipChildren == null) {
- clipChildren = view.getClipChildren();
- view.setTag(CLIP_CHILDREN_TAG, clipChildren);
- }
- Boolean clipToPadding = (Boolean) view.getTag(CLIP_TO_PADDING);
- if (clipToPadding == null) {
- clipToPadding = view.getClipToPadding();
- view.setTag(CLIP_TO_PADDING, clipToPadding);
- }
- ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
- ? (ExpandableNotificationRow) view
- : null;
- if (!deactivated) {
- clipSet.remove(transformedView);
- if (clipSet.isEmpty()) {
- view.setClipChildren(clipChildren);
- view.setClipToPadding(clipToPadding);
- view.setTag(CLIP_CLIPPING_SET, null);
- if (row != null) {
- row.setClipToActualHeight(true);
- }
- }
- } else {
- clipSet.add(transformedView);
- view.setClipChildren(false);
- view.setClipToPadding(false);
- if (row != null && row.isChildInGroup()) {
- // We still want to clip to the parent's height
- row.setClipToActualHeight(false);
- }
- }
- if (row != null && !row.isChildInGroup()) {
- return;
- }
- final ViewParent parent = view.getParent();
- if (parent instanceof ViewGroup) {
- view = (ViewGroup) parent;
- } else {
- return;
- }
- }
+ protected void setClippingDeactivated(final View transformedView, boolean deactivated) {
+ ViewClippingUtil.setClippingDeactivated(transformedView, deactivated, CLIPPING_PARAMETERS);
}
public int[] getLaidOutLocationOnScreen() {
@@ -423,6 +400,9 @@ public class TransformState {
// remove scale
mOwnPosition[0] -= (1.0f - mTransformedView.getScaleX()) * mTransformedView.getPivotX();
mOwnPosition[1] -= (1.0f - mTransformedView.getScaleY()) * mTransformedView.getPivotY();
+
+ // Remove local translations
+ mOwnPosition[1] -= MessagingPropertyAnimator.getLocalTranslationY(mTransformedView);
return mOwnPosition;
}
@@ -444,20 +424,26 @@ public class TransformState {
CrossFadeHelper.fadeOut(mTransformedView, transformationAmount);
}
- public static TransformState createFrom(View view) {
+ public static TransformState createFrom(View view,
+ TransformInfo transformInfo) {
if (view instanceof TextView) {
TextViewTransformState result = TextViewTransformState.obtain();
- result.initFrom(view);
+ result.initFrom(view, transformInfo);
return result;
}
if (view.getId() == com.android.internal.R.id.actions_container) {
ActionListTransformState result = ActionListTransformState.obtain();
- result.initFrom(view);
+ result.initFrom(view, transformInfo);
+ return result;
+ }
+ if (view.getId() == com.android.internal.R.id.notification_messaging) {
+ MessagingLayoutTransformState result = MessagingLayoutTransformState.obtain();
+ result.initFrom(view, transformInfo);
return result;
}
if (view instanceof ImageView) {
ImageTransformState result = ImageTransformState.obtain();
- result.initFrom(view);
+ result.initFrom(view, transformInfo);
if (view.getId() == com.android.internal.R.id.reply_icon_action) {
((TransformState) result).setIsSameAsAnyView(true);
}
@@ -465,15 +451,15 @@ public class TransformState {
}
if (view instanceof ProgressBar) {
ProgressTransformState result = ProgressTransformState.obtain();
- result.initFrom(view);
+ result.initFrom(view, transformInfo);
return result;
}
TransformState result = obtain();
- result.initFrom(view);
+ result.initFrom(view, transformInfo);
return result;
}
- private void setIsSameAsAnyView(boolean sameAsAny) {
+ public void setIsSameAsAnyView(boolean sameAsAny) {
mSameAsAny = sameAsAny;
}
@@ -533,6 +519,7 @@ public class TransformState {
mSameAsAny = false;
mTransformationEndX = UNDEFINED;
mTransformationEndY = UNDEFINED;
+ mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
}
public void setVisible(boolean visible, boolean force) {
@@ -578,4 +565,12 @@ public class TransformState {
public View getTransformedView() {
return mTransformedView;
}
+
+ public void setDefaultInterpolator(Interpolator interpolator) {
+ mDefaultInterpolator = interpolator;
+ }
+
+ public interface TransformInfo {
+ boolean isAnimating();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index d226fedef706..1c361caa4a1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -175,9 +175,8 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
private boolean isLight(int vis, int barMode, int flag) {
boolean isTransparentBar = (barMode == MODE_TRANSPARENT
|| barMode == MODE_LIGHTS_OUT_TRANSPARENT);
- boolean allowLight = isTransparentBar && !mBatteryController.isPowerSave();
boolean light = (vis & flag) != 0;
- return allowLight && light;
+ return isTransparentBar && light;
}
private boolean animateChange() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 4a58c6d6b3ff..46e3aa190813 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -146,6 +146,8 @@ import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.NotificationMessagingUtil;
import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.MessagingGroup;
+import com.android.internal.widget.MessagingMessage;
import com.android.keyguard.KeyguardHostView.OnDismissAction;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -273,7 +275,7 @@ public class StatusBar extends SystemUI implements DemoMode,
public static final boolean ENABLE_CHILD_NOTIFICATIONS
= SystemProperties.getBoolean("debug.child_notifs", true);
public static final boolean FORCE_REMOTE_INPUT_HISTORY =
- SystemProperties.getBoolean("debug.force_remoteinput_history", false);
+ SystemProperties.getBoolean("debug.force_remoteinput_history", true);
private static final boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
protected static final int MSG_HIDE_RECENT_APPS = 1020;
@@ -1232,6 +1234,8 @@ public class StatusBar extends SystemUI implements DemoMode,
}
public void onDensityOrFontScaleChanged() {
+ MessagingMessage.dropCache();
+ MessagingGroup.dropCache();
// start old BaseStatusBar.onDensityOrFontScaleChanged().
if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
updateNotificationsOnDensityOrFontScaleChanged();
@@ -1685,8 +1689,9 @@ public class StatusBar extends SystemUI implements DemoMode,
clearCurrentMediaNotification();
updateMediaMetaData(true, true);
}
- if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key)) {
- Entry entry = mNotificationData.get(key);
+ Entry entry = mNotificationData.get(key);
+ if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key)
+ && entry.row != null && !entry.row.isDismissed()) {
StatusBarNotification sbn = entry.notification;
Notification.Builder b = Notification.Builder
@@ -1722,6 +1727,7 @@ public class StatusBar extends SystemUI implements DemoMode,
deferRemoval = false;
}
if (updated) {
+ Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key);
mKeysKeptForRemoteInput.add(entry.key);
return;
}
@@ -1731,7 +1737,6 @@ public class StatusBar extends SystemUI implements DemoMode,
mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
return;
}
- Entry entry = mNotificationData.get(key);
if (entry != null && mRemoteInputController.isRemoteInputActive(entry)
&& (entry.row != null && !entry.row.isDismissed())) {
@@ -3278,12 +3283,8 @@ public class StatusBar extends SystemUI implements DemoMode,
}
void checkBarMode(int mode, int windowState, BarTransitions transitions) {
- final boolean powerSave = mBatteryController.isPowerSave();
final boolean anim = !mNoAnimationOnNextBarModeChange && mDeviceInteractive
- && windowState != WINDOW_STATE_HIDDEN && !powerSave;
- if (powerSave && getBarState() == StatusBarState.SHADE) {
- mode = MODE_WARNING;
- }
+ && windowState != WINDOW_STATE_HIDDEN;
transitions.transitionTo(mode, anim);
}
@@ -7117,7 +7118,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mAllowLockscreenRemoteInput = allowLockscreenRemoteInput;
}
- private void updateLockscreenNotificationSetting() {
+ protected void updateLockscreenNotificationSetting() {
final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
1,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
new file mode 100644
index 000000000000..4437aaca521d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.keyguard;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Handler;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper(setAsMainLooper = true)
+public class KeyguardSliceProviderTest extends SysuiTestCase {
+
+ private TestableKeyguardSliceProvider mProvider;
+
+ @Before
+ public void setup() {
+ mProvider = new TestableKeyguardSliceProvider();
+ mProvider.attachInfo(getContext(), null);
+ }
+
+ @Test
+ public void registersClockUpdate() {
+ Assert.assertTrue("registerClockUpdate should have been called during initialization.",
+ mProvider.isRegistered());
+ }
+
+ @Test
+ public void unregisterClockUpdate() {
+ mProvider.unregisterClockUpdate();
+ Assert.assertFalse("Clock updates should have been unregistered.",
+ mProvider.isRegistered());
+ }
+
+ @Test
+ public void returnsValidSlice() {
+ Slice slice = mProvider.onBindSlice(Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI));
+ SliceItem text = SliceQuery.find(slice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE,
+ null /* nonHints */);
+ Assert.assertNotNull("Slice must provide a title.", text);
+ }
+
+ @Test
+ public void cleansDateFormat() {
+ mProvider.mIntentReceiver.onReceive(getContext(), new Intent(Intent.ACTION_TIMEZONE_CHANGED));
+ TestableLooper.get(this).processAllMessages();
+ Assert.assertEquals("Date format should have been cleaned.", 1 /* expected */,
+ mProvider.mCleanDateFormatInvokations);
+ }
+
+ @Test
+ public void updatesClock() {
+ mProvider.mIntentReceiver.onReceive(getContext(), new Intent(Intent.ACTION_TIME_TICK));
+ TestableLooper.get(this).processAllMessages();
+ Assert.assertEquals("Clock should have been updated.", 2 /* expected */,
+ mProvider.mUpdateClockInvokations);
+ }
+
+ private class TestableKeyguardSliceProvider extends KeyguardSliceProvider {
+ int mCleanDateFormatInvokations;
+ int mUpdateClockInvokations;
+
+ TestableKeyguardSliceProvider() {
+ super(new Handler(TestableLooper.get(KeyguardSliceProviderTest.this).getLooper()));
+ }
+
+ @Override
+ void cleanDateFormat() {
+ super.cleanDateFormat();
+ mCleanDateFormatInvokations++;
+ }
+
+ @Override
+ protected void updateClock() {
+ super.updateClock();
+ mUpdateClockInvokations++;
+ }
+ }
+
+}
diff --git a/packages/VpnDialogs/res/values-ta/strings.xml b/packages/VpnDialogs/res/values-ta/strings.xml
index 81a198440c44..7daa11b4a173 100644
--- a/packages/VpnDialogs/res/values-ta/strings.xml
+++ b/packages/VpnDialogs/res/values-ta/strings.xml
@@ -17,7 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"இணைப்புக் கோரிக்கை"</string>
- <string name="warning" msgid="809658604548412033">"VPN இணைப்பை அமைக்க <xliff:g id="APP">%s</xliff:g> விழைகிறது அதன்மூலம் இது நெட்வொர்க் ட்ராஃபிக்கைக் கண்காணிக்கும் அனுமதியைப் பெறும். நம்பகமான மூலத்தை மட்டுமே ஏற்கவும். VPN இயக்கத்தில் உள்ளபோது திரையில் மேல் பகுதியில் &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; தோன்றும்."</string>
+ <string name="warning" msgid="809658604548412033">"VPN இணைப்பை அமைக்க <xliff:g id="APP">%s</xliff:g> விழைகிறது அதன்மூலம் இது நெட்வொர்க் ட்ராஃபிக்கைக் கண்காணிக்கும் அனுமதியைப் பெறும். நம்பகமான மூலத்தை மட்டுமே ஏற்கவும். &lt;br /&gt; &lt;br /&gt; VPN இயக்கத்தில் உள்ளபோது திரையில் மேல் பகுதியில் &lt;img src=vpn_icon /&gt; தோன்றும்."</string>
<string name="legacy_title" msgid="192936250066580964">"VPN இணைக்கப்பட்டது"</string>
<string name="session" msgid="6470628549473641030">"அமர்வு:"</string>
<string name="duration" msgid="3584782459928719435">"காலஅளவு:"</string>
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 7a60a5d1e11d..ea0ed2719ad0 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -38,7 +38,7 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.hidl.manager.V1_0.IServiceManager;
import android.hidl.manager.V1_0.IServiceNotification;
-import android.hardware.health.V2_0.HealthInfo;
+import android.hardware.health.V1_0.HealthInfo;
import android.hardware.health.V2_0.IHealthInfoCallback;
import android.hardware.health.V2_0.IHealth;
import android.hardware.health.V2_0.Result;
@@ -311,16 +311,16 @@ public final class BatteryService extends SystemService {
private boolean isPoweredLocked(int plugTypeSet) {
// assume we are powered if battery state is unknown so
// the "stay on while plugged in" option will work.
- if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
+ if (mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
return true;
}
- if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mHealthInfo.legacy.chargerAcOnline) {
+ if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mHealthInfo.chargerAcOnline) {
return true;
}
- if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mHealthInfo.legacy.chargerUsbOnline) {
+ if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mHealthInfo.chargerUsbOnline) {
return true;
}
- if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mHealthInfo.legacy.chargerWirelessOnline) {
+ if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mHealthInfo.chargerWirelessOnline) {
return true;
}
return false;
@@ -337,15 +337,15 @@ public final class BatteryService extends SystemService {
* (becomes <= mLowBatteryWarningLevel).
*/
return !plugged
- && mHealthInfo.legacy.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
- && mHealthInfo.legacy.batteryLevel <= mLowBatteryWarningLevel
+ && mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
+ && mHealthInfo.batteryLevel <= mLowBatteryWarningLevel
&& (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel);
}
private void shutdownIfNoPowerLocked() {
// shut down gracefully if our battery is critically low and we are not powered.
// wait until the system has booted before attempting to display the shutdown dialog.
- if (mHealthInfo.legacy.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) {
+ if (mHealthInfo.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) {
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -366,7 +366,7 @@ public final class BatteryService extends SystemService {
// shut down gracefully if temperature is too high (> 68.0C by default)
// wait until the system has booted before attempting to display the
// shutdown dialog.
- if (mHealthInfo.legacy.batteryTemperature > mShutdownBatteryTemperature) {
+ if (mHealthInfo.batteryTemperature > mShutdownBatteryTemperature) {
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -399,37 +399,34 @@ public final class BatteryService extends SystemService {
}
private static void copy(HealthInfo dst, HealthInfo src) {
- dst.legacy.chargerAcOnline = src.legacy.chargerAcOnline;
- dst.legacy.chargerUsbOnline = src.legacy.chargerUsbOnline;
- dst.legacy.chargerWirelessOnline = src.legacy.chargerWirelessOnline;
- dst.legacy.maxChargingCurrent = src.legacy.maxChargingCurrent;
- dst.legacy.maxChargingVoltage = src.legacy.maxChargingVoltage;
- dst.legacy.batteryStatus = src.legacy.batteryStatus;
- dst.legacy.batteryHealth = src.legacy.batteryHealth;
- dst.legacy.batteryPresent = src.legacy.batteryPresent;
- dst.legacy.batteryLevel = src.legacy.batteryLevel;
- dst.legacy.batteryVoltage = src.legacy.batteryVoltage;
- dst.legacy.batteryTemperature = src.legacy.batteryTemperature;
- dst.legacy.batteryCurrent = src.legacy.batteryCurrent;
- dst.legacy.batteryCycleCount = src.legacy.batteryCycleCount;
- dst.legacy.batteryFullCharge = src.legacy.batteryFullCharge;
- dst.legacy.batteryChargeCounter = src.legacy.batteryChargeCounter;
- dst.legacy.batteryTechnology = src.legacy.batteryTechnology;
- dst.batteryCurrentAverage = src.batteryCurrentAverage;
- dst.batteryCapacity = src.batteryCapacity;
- dst.energyCounter = src.energyCounter;
+ dst.chargerAcOnline = src.chargerAcOnline;
+ dst.chargerUsbOnline = src.chargerUsbOnline;
+ dst.chargerWirelessOnline = src.chargerWirelessOnline;
+ dst.maxChargingCurrent = src.maxChargingCurrent;
+ dst.maxChargingVoltage = src.maxChargingVoltage;
+ dst.batteryStatus = src.batteryStatus;
+ dst.batteryHealth = src.batteryHealth;
+ dst.batteryPresent = src.batteryPresent;
+ dst.batteryLevel = src.batteryLevel;
+ dst.batteryVoltage = src.batteryVoltage;
+ dst.batteryTemperature = src.batteryTemperature;
+ dst.batteryCurrent = src.batteryCurrent;
+ dst.batteryCycleCount = src.batteryCycleCount;
+ dst.batteryFullCharge = src.batteryFullCharge;
+ dst.batteryChargeCounter = src.batteryChargeCounter;
+ dst.batteryTechnology = src.batteryTechnology;
}
private void processValuesLocked(boolean force) {
boolean logOutlier = false;
long dischargeDuration = 0;
- mBatteryLevelCritical = (mHealthInfo.legacy.batteryLevel <= mCriticalBatteryLevel);
- if (mHealthInfo.legacy.chargerAcOnline) {
+ mBatteryLevelCritical = (mHealthInfo.batteryLevel <= mCriticalBatteryLevel);
+ if (mHealthInfo.chargerAcOnline) {
mPlugType = BatteryManager.BATTERY_PLUGGED_AC;
- } else if (mHealthInfo.legacy.chargerUsbOnline) {
+ } else if (mHealthInfo.chargerUsbOnline) {
mPlugType = BatteryManager.BATTERY_PLUGGED_USB;
- } else if (mHealthInfo.legacy.chargerWirelessOnline) {
+ } else if (mHealthInfo.chargerWirelessOnline) {
mPlugType = BatteryManager.BATTERY_PLUGGED_WIRELESS;
} else {
mPlugType = BATTERY_PLUGGED_NONE;
@@ -444,10 +441,10 @@ public final class BatteryService extends SystemService {
// Let the battery stats keep track of the current level.
try {
- mBatteryStats.setBatteryState(mHealthInfo.legacy.batteryStatus, mHealthInfo.legacy.batteryHealth,
- mPlugType, mHealthInfo.legacy.batteryLevel, mHealthInfo.legacy.batteryTemperature,
- mHealthInfo.legacy.batteryVoltage, mHealthInfo.legacy.batteryChargeCounter,
- mHealthInfo.legacy.batteryFullCharge);
+ mBatteryStats.setBatteryState(mHealthInfo.batteryStatus, mHealthInfo.batteryHealth,
+ mPlugType, mHealthInfo.batteryLevel, mHealthInfo.batteryTemperature,
+ mHealthInfo.batteryVoltage, mHealthInfo.batteryChargeCounter,
+ mHealthInfo.batteryFullCharge);
} catch (RemoteException e) {
// Should never happen.
}
@@ -455,16 +452,16 @@ public final class BatteryService extends SystemService {
shutdownIfNoPowerLocked();
shutdownIfOverTempLocked();
- if (force || (mHealthInfo.legacy.batteryStatus != mLastBatteryStatus ||
- mHealthInfo.legacy.batteryHealth != mLastBatteryHealth ||
- mHealthInfo.legacy.batteryPresent != mLastBatteryPresent ||
- mHealthInfo.legacy.batteryLevel != mLastBatteryLevel ||
+ if (force || (mHealthInfo.batteryStatus != mLastBatteryStatus ||
+ mHealthInfo.batteryHealth != mLastBatteryHealth ||
+ mHealthInfo.batteryPresent != mLastBatteryPresent ||
+ mHealthInfo.batteryLevel != mLastBatteryLevel ||
mPlugType != mLastPlugType ||
- mHealthInfo.legacy.batteryVoltage != mLastBatteryVoltage ||
- mHealthInfo.legacy.batteryTemperature != mLastBatteryTemperature ||
- mHealthInfo.legacy.maxChargingCurrent != mLastMaxChargingCurrent ||
- mHealthInfo.legacy.maxChargingVoltage != mLastMaxChargingVoltage ||
- mHealthInfo.legacy.batteryChargeCounter != mLastChargeCounter ||
+ mHealthInfo.batteryVoltage != mLastBatteryVoltage ||
+ mHealthInfo.batteryTemperature != mLastBatteryTemperature ||
+ mHealthInfo.maxChargingCurrent != mLastMaxChargingCurrent ||
+ mHealthInfo.maxChargingVoltage != mLastMaxChargingVoltage ||
+ mHealthInfo.batteryChargeCounter != mLastChargeCounter ||
mInvalidCharger != mLastInvalidCharger)) {
if (mPlugType != mLastPlugType) {
@@ -473,33 +470,33 @@ public final class BatteryService extends SystemService {
// There's no value in this data unless we've discharged at least once and the
// battery level has changed; so don't log until it does.
- if (mDischargeStartTime != 0 && mDischargeStartLevel != mHealthInfo.legacy.batteryLevel) {
+ if (mDischargeStartTime != 0 && mDischargeStartLevel != mHealthInfo.batteryLevel) {
dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
logOutlier = true;
EventLog.writeEvent(EventLogTags.BATTERY_DISCHARGE, dischargeDuration,
- mDischargeStartLevel, mHealthInfo.legacy.batteryLevel);
+ mDischargeStartLevel, mHealthInfo.batteryLevel);
// make sure we see a discharge event before logging again
mDischargeStartTime = 0;
}
} else if (mPlugType == BATTERY_PLUGGED_NONE) {
// charging -> discharging or we just powered up
mDischargeStartTime = SystemClock.elapsedRealtime();
- mDischargeStartLevel = mHealthInfo.legacy.batteryLevel;
+ mDischargeStartLevel = mHealthInfo.batteryLevel;
}
}
- if (mHealthInfo.legacy.batteryStatus != mLastBatteryStatus ||
- mHealthInfo.legacy.batteryHealth != mLastBatteryHealth ||
- mHealthInfo.legacy.batteryPresent != mLastBatteryPresent ||
+ if (mHealthInfo.batteryStatus != mLastBatteryStatus ||
+ mHealthInfo.batteryHealth != mLastBatteryHealth ||
+ mHealthInfo.batteryPresent != mLastBatteryPresent ||
mPlugType != mLastPlugType) {
EventLog.writeEvent(EventLogTags.BATTERY_STATUS,
- mHealthInfo.legacy.batteryStatus, mHealthInfo.legacy.batteryHealth, mHealthInfo.legacy.batteryPresent ? 1 : 0,
- mPlugType, mHealthInfo.legacy.batteryTechnology);
+ mHealthInfo.batteryStatus, mHealthInfo.batteryHealth, mHealthInfo.batteryPresent ? 1 : 0,
+ mPlugType, mHealthInfo.batteryTechnology);
}
- if (mHealthInfo.legacy.batteryLevel != mLastBatteryLevel) {
+ if (mHealthInfo.batteryLevel != mLastBatteryLevel) {
// Don't do this just from voltage or temperature changes, that is
// too noisy.
EventLog.writeEvent(EventLogTags.BATTERY_LEVEL,
- mHealthInfo.legacy.batteryLevel, mHealthInfo.legacy.batteryVoltage, mHealthInfo.legacy.batteryTemperature);
+ mHealthInfo.batteryLevel, mHealthInfo.batteryVoltage, mHealthInfo.batteryTemperature);
}
if (mBatteryLevelCritical && !mLastBatteryLevelCritical &&
mPlugType == BATTERY_PLUGGED_NONE) {
@@ -512,16 +509,16 @@ public final class BatteryService extends SystemService {
if (!mBatteryLevelLow) {
// Should we now switch in to low battery mode?
if (mPlugType == BATTERY_PLUGGED_NONE
- && mHealthInfo.legacy.batteryLevel <= mLowBatteryWarningLevel) {
+ && mHealthInfo.batteryLevel <= mLowBatteryWarningLevel) {
mBatteryLevelLow = true;
}
} else {
// Should we now switch out of low battery mode?
if (mPlugType != BATTERY_PLUGGED_NONE) {
mBatteryLevelLow = false;
- } else if (mHealthInfo.legacy.batteryLevel >= mLowBatteryCloseWarningLevel) {
+ } else if (mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel) {
mBatteryLevelLow = false;
- } else if (force && mHealthInfo.legacy.batteryLevel >= mLowBatteryWarningLevel) {
+ } else if (force && mHealthInfo.batteryLevel >= mLowBatteryWarningLevel) {
// If being forced, the previous state doesn't matter, we will just
// absolutely check to see if we are now above the warning level.
mBatteryLevelLow = false;
@@ -568,7 +565,7 @@ public final class BatteryService extends SystemService {
}
});
} else if (mSentLowBatteryBroadcast &&
- mHealthInfo.legacy.batteryLevel >= mLowBatteryCloseWarningLevel) {
+ mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel) {
mSentLowBatteryBroadcast = false;
final Intent statusIntent = new Intent(Intent.ACTION_BATTERY_OKAY);
statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
@@ -594,16 +591,16 @@ public final class BatteryService extends SystemService {
logOutlierLocked(dischargeDuration);
}
- mLastBatteryStatus = mHealthInfo.legacy.batteryStatus;
- mLastBatteryHealth = mHealthInfo.legacy.batteryHealth;
- mLastBatteryPresent = mHealthInfo.legacy.batteryPresent;
- mLastBatteryLevel = mHealthInfo.legacy.batteryLevel;
+ mLastBatteryStatus = mHealthInfo.batteryStatus;
+ mLastBatteryHealth = mHealthInfo.batteryHealth;
+ mLastBatteryPresent = mHealthInfo.batteryPresent;
+ mLastBatteryLevel = mHealthInfo.batteryLevel;
mLastPlugType = mPlugType;
- mLastBatteryVoltage = mHealthInfo.legacy.batteryVoltage;
- mLastBatteryTemperature = mHealthInfo.legacy.batteryTemperature;
- mLastMaxChargingCurrent = mHealthInfo.legacy.maxChargingCurrent;
- mLastMaxChargingVoltage = mHealthInfo.legacy.maxChargingVoltage;
- mLastChargeCounter = mHealthInfo.legacy.batteryChargeCounter;
+ mLastBatteryVoltage = mHealthInfo.batteryVoltage;
+ mLastBatteryTemperature = mHealthInfo.batteryTemperature;
+ mLastMaxChargingCurrent = mHealthInfo.maxChargingCurrent;
+ mLastMaxChargingVoltage = mHealthInfo.maxChargingVoltage;
+ mLastChargeCounter = mHealthInfo.batteryChargeCounter;
mLastBatteryLevelCritical = mBatteryLevelCritical;
mLastInvalidCharger = mInvalidCharger;
}
@@ -615,23 +612,23 @@ public final class BatteryService extends SystemService {
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_REPLACE_PENDING);
- int icon = getIconLocked(mHealthInfo.legacy.batteryLevel);
+ int icon = getIconLocked(mHealthInfo.batteryLevel);
intent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence);
- intent.putExtra(BatteryManager.EXTRA_STATUS, mHealthInfo.legacy.batteryStatus);
- intent.putExtra(BatteryManager.EXTRA_HEALTH, mHealthInfo.legacy.batteryHealth);
- intent.putExtra(BatteryManager.EXTRA_PRESENT, mHealthInfo.legacy.batteryPresent);
- intent.putExtra(BatteryManager.EXTRA_LEVEL, mHealthInfo.legacy.batteryLevel);
+ intent.putExtra(BatteryManager.EXTRA_STATUS, mHealthInfo.batteryStatus);
+ intent.putExtra(BatteryManager.EXTRA_HEALTH, mHealthInfo.batteryHealth);
+ intent.putExtra(BatteryManager.EXTRA_PRESENT, mHealthInfo.batteryPresent);
+ intent.putExtra(BatteryManager.EXTRA_LEVEL, mHealthInfo.batteryLevel);
intent.putExtra(BatteryManager.EXTRA_SCALE, BATTERY_SCALE);
intent.putExtra(BatteryManager.EXTRA_ICON_SMALL, icon);
intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType);
- intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mHealthInfo.legacy.batteryVoltage);
- intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mHealthInfo.legacy.batteryTemperature);
- intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mHealthInfo.legacy.batteryTechnology);
+ intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mHealthInfo.batteryVoltage);
+ intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mHealthInfo.batteryTemperature);
+ intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mHealthInfo.batteryTechnology);
intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger);
- intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mHealthInfo.legacy.maxChargingCurrent);
- intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, mHealthInfo.legacy.maxChargingVoltage);
- intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.legacy.batteryChargeCounter);
+ intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mHealthInfo.maxChargingCurrent);
+ intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, mHealthInfo.maxChargingVoltage);
+ intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.batteryChargeCounter);
if (DEBUG) {
Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. scale:" + BATTERY_SCALE
+ ", info:" + mHealthInfo.toString());
@@ -695,14 +692,14 @@ public final class BatteryService extends SystemService {
long durationThreshold = Long.parseLong(durationThresholdString);
int dischargeThreshold = Integer.parseInt(dischargeThresholdString);
if (duration <= durationThreshold &&
- mDischargeStartLevel - mHealthInfo.legacy.batteryLevel >= dischargeThreshold) {
+ mDischargeStartLevel - mHealthInfo.batteryLevel >= dischargeThreshold) {
// If the discharge cycle is bad enough we want to know about it.
logBatteryStatsLocked();
}
if (DEBUG) Slog.v(TAG, "duration threshold: " + durationThreshold +
" discharge threshold: " + dischargeThreshold);
if (DEBUG) Slog.v(TAG, "duration: " + duration + " discharge: " +
- (mDischargeStartLevel - mHealthInfo.legacy.batteryLevel));
+ (mDischargeStartLevel - mHealthInfo.batteryLevel));
} catch (NumberFormatException e) {
Slog.e(TAG, "Invalid DischargeThresholds GService string: " +
durationThresholdString + " or " + dischargeThresholdString);
@@ -711,14 +708,14 @@ public final class BatteryService extends SystemService {
}
private int getIconLocked(int level) {
- if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) {
+ if (mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) {
return com.android.internal.R.drawable.stat_sys_battery_charge;
- } else if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) {
+ } else if (mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) {
return com.android.internal.R.drawable.stat_sys_battery;
- } else if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING
- || mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_FULL) {
+ } else if (mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING
+ || mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_FULL) {
if (isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)
- && mHealthInfo.legacy.batteryLevel >= 100) {
+ && mHealthInfo.batteryLevel >= 100) {
return com.android.internal.R.drawable.stat_sys_battery_charge;
} else {
return com.android.internal.R.drawable.stat_sys_battery;
@@ -782,9 +779,9 @@ public final class BatteryService extends SystemService {
if (!mUpdatesStopped) {
copy(mLastHealthInfo, mHealthInfo);
}
- mHealthInfo.legacy.chargerAcOnline = false;
- mHealthInfo.legacy.chargerUsbOnline = false;
- mHealthInfo.legacy.chargerWirelessOnline = false;
+ mHealthInfo.chargerAcOnline = false;
+ mHealthInfo.chargerUsbOnline = false;
+ mHealthInfo.chargerWirelessOnline = false;
long ident = Binder.clearCallingIdentity();
try {
mUpdatesStopped = true;
@@ -816,25 +813,25 @@ public final class BatteryService extends SystemService {
boolean update = true;
switch (key) {
case "present":
- mHealthInfo.legacy.batteryPresent = Integer.parseInt(value) != 0;
+ mHealthInfo.batteryPresent = Integer.parseInt(value) != 0;
break;
case "ac":
- mHealthInfo.legacy.chargerAcOnline = Integer.parseInt(value) != 0;
+ mHealthInfo.chargerAcOnline = Integer.parseInt(value) != 0;
break;
case "usb":
- mHealthInfo.legacy.chargerUsbOnline = Integer.parseInt(value) != 0;
+ mHealthInfo.chargerUsbOnline = Integer.parseInt(value) != 0;
break;
case "wireless":
- mHealthInfo.legacy.chargerWirelessOnline = Integer.parseInt(value) != 0;
+ mHealthInfo.chargerWirelessOnline = Integer.parseInt(value) != 0;
break;
case "status":
- mHealthInfo.legacy.batteryStatus = Integer.parseInt(value);
+ mHealthInfo.batteryStatus = Integer.parseInt(value);
break;
case "level":
- mHealthInfo.legacy.batteryLevel = Integer.parseInt(value);
+ mHealthInfo.batteryLevel = Integer.parseInt(value);
break;
case "temp":
- mHealthInfo.legacy.batteryTemperature = Integer.parseInt(value);
+ mHealthInfo.batteryTemperature = Integer.parseInt(value);
break;
case "invalid":
mInvalidCharger = Integer.parseInt(value);
@@ -893,20 +890,20 @@ public final class BatteryService extends SystemService {
if (mUpdatesStopped) {
pw.println(" (UPDATES STOPPED -- use 'reset' to restart)");
}
- pw.println(" AC powered: " + mHealthInfo.legacy.chargerAcOnline);
- pw.println(" USB powered: " + mHealthInfo.legacy.chargerUsbOnline);
- pw.println(" Wireless powered: " + mHealthInfo.legacy.chargerWirelessOnline);
- pw.println(" Max charging current: " + mHealthInfo.legacy.maxChargingCurrent);
- pw.println(" Max charging voltage: " + mHealthInfo.legacy.maxChargingVoltage);
- pw.println(" Charge counter: " + mHealthInfo.legacy.batteryChargeCounter);
- pw.println(" status: " + mHealthInfo.legacy.batteryStatus);
- pw.println(" health: " + mHealthInfo.legacy.batteryHealth);
- pw.println(" present: " + mHealthInfo.legacy.batteryPresent);
- pw.println(" level: " + mHealthInfo.legacy.batteryLevel);
+ pw.println(" AC powered: " + mHealthInfo.chargerAcOnline);
+ pw.println(" USB powered: " + mHealthInfo.chargerUsbOnline);
+ pw.println(" Wireless powered: " + mHealthInfo.chargerWirelessOnline);
+ pw.println(" Max charging current: " + mHealthInfo.maxChargingCurrent);
+ pw.println(" Max charging voltage: " + mHealthInfo.maxChargingVoltage);
+ pw.println(" Charge counter: " + mHealthInfo.batteryChargeCounter);
+ pw.println(" status: " + mHealthInfo.batteryStatus);
+ pw.println(" health: " + mHealthInfo.batteryHealth);
+ pw.println(" present: " + mHealthInfo.batteryPresent);
+ pw.println(" level: " + mHealthInfo.batteryLevel);
pw.println(" scale: " + BATTERY_SCALE);
- pw.println(" voltage: " + mHealthInfo.legacy.batteryVoltage);
- pw.println(" temperature: " + mHealthInfo.legacy.batteryTemperature);
- pw.println(" technology: " + mHealthInfo.legacy.batteryTechnology);
+ pw.println(" voltage: " + mHealthInfo.batteryVoltage);
+ pw.println(" temperature: " + mHealthInfo.batteryTemperature);
+ pw.println(" technology: " + mHealthInfo.batteryTechnology);
} else {
Shell shell = new Shell();
shell.exec(mBinderService, null, fd, null, args, null, new ResultReceiver(null));
@@ -920,25 +917,25 @@ public final class BatteryService extends SystemService {
synchronized (mLock) {
proto.write(BatteryServiceDumpProto.ARE_UPDATES_STOPPED, mUpdatesStopped);
int batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_NONE;
- if (mHealthInfo.legacy.chargerAcOnline) {
+ if (mHealthInfo.chargerAcOnline) {
batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_AC;
- } else if (mHealthInfo.legacy.chargerUsbOnline) {
+ } else if (mHealthInfo.chargerUsbOnline) {
batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_USB;
- } else if (mHealthInfo.legacy.chargerWirelessOnline) {
+ } else if (mHealthInfo.chargerWirelessOnline) {
batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_WIRELESS;
}
proto.write(BatteryServiceDumpProto.PLUGGED, batteryPluggedValue);
- proto.write(BatteryServiceDumpProto.MAX_CHARGING_CURRENT, mHealthInfo.legacy.maxChargingCurrent);
- proto.write(BatteryServiceDumpProto.MAX_CHARGING_VOLTAGE, mHealthInfo.legacy.maxChargingVoltage);
- proto.write(BatteryServiceDumpProto.CHARGE_COUNTER, mHealthInfo.legacy.batteryChargeCounter);
- proto.write(BatteryServiceDumpProto.STATUS, mHealthInfo.legacy.batteryStatus);
- proto.write(BatteryServiceDumpProto.HEALTH, mHealthInfo.legacy.batteryHealth);
- proto.write(BatteryServiceDumpProto.IS_PRESENT, mHealthInfo.legacy.batteryPresent);
- proto.write(BatteryServiceDumpProto.LEVEL, mHealthInfo.legacy.batteryLevel);
+ proto.write(BatteryServiceDumpProto.MAX_CHARGING_CURRENT, mHealthInfo.maxChargingCurrent);
+ proto.write(BatteryServiceDumpProto.MAX_CHARGING_VOLTAGE, mHealthInfo.maxChargingVoltage);
+ proto.write(BatteryServiceDumpProto.CHARGE_COUNTER, mHealthInfo.batteryChargeCounter);
+ proto.write(BatteryServiceDumpProto.STATUS, mHealthInfo.batteryStatus);
+ proto.write(BatteryServiceDumpProto.HEALTH, mHealthInfo.batteryHealth);
+ proto.write(BatteryServiceDumpProto.IS_PRESENT, mHealthInfo.batteryPresent);
+ proto.write(BatteryServiceDumpProto.LEVEL, mHealthInfo.batteryLevel);
proto.write(BatteryServiceDumpProto.SCALE, BATTERY_SCALE);
- proto.write(BatteryServiceDumpProto.VOLTAGE, mHealthInfo.legacy.batteryVoltage);
- proto.write(BatteryServiceDumpProto.TEMPERATURE, mHealthInfo.legacy.batteryTemperature);
- proto.write(BatteryServiceDumpProto.TECHNOLOGY, mHealthInfo.legacy.batteryTechnology);
+ proto.write(BatteryServiceDumpProto.VOLTAGE, mHealthInfo.batteryVoltage);
+ proto.write(BatteryServiceDumpProto.TEMPERATURE, mHealthInfo.batteryTemperature);
+ proto.write(BatteryServiceDumpProto.TECHNOLOGY, mHealthInfo.batteryTechnology);
}
proto.flush();
}
@@ -979,8 +976,8 @@ public final class BatteryService extends SystemService {
* Synchronize on BatteryService.
*/
public void updateLightsLocked() {
- final int level = mHealthInfo.legacy.batteryLevel;
- final int status = mHealthInfo.legacy.batteryStatus;
+ final int level = mHealthInfo.batteryLevel;
+ final int status = mHealthInfo.batteryStatus;
if (level < mLowBatteryWarningLevel) {
if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
// Solid red when battery is charging
@@ -1157,7 +1154,7 @@ public final class BatteryService extends SystemService {
@Override
public int getBatteryLevel() {
synchronized (mLock) {
- return mHealthInfo.legacy.batteryLevel;
+ return mHealthInfo.batteryLevel;
}
}
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index c60d7b075ac6..8a15ded2960f 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -20,6 +20,9 @@ import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
import static android.Manifest.permission.DUMP;
import static android.Manifest.permission.NETWORK_STACK;
import static android.Manifest.permission.SHUTDOWN;
+import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_NONE;
@@ -92,6 +95,7 @@ import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.PhoneStateListener;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseBooleanArray;
@@ -1946,9 +1950,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub
public void setDnsConfigurationForNetwork(int netId, String[] servers, String domains) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- ContentResolver resolver = mContext.getContentResolver();
+ final ContentResolver cr = mContext.getContentResolver();
- int sampleValidity = Settings.Global.getInt(resolver,
+ int sampleValidity = Settings.Global.getInt(cr,
Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS,
DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS);
if (sampleValidity < 0 || sampleValidity > 65535) {
@@ -1957,7 +1961,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
sampleValidity = DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS;
}
- int successThreshold = Settings.Global.getInt(resolver,
+ int successThreshold = Settings.Global.getInt(cr,
Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT,
DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT);
if (successThreshold < 0 || successThreshold > 100) {
@@ -1966,9 +1970,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub
successThreshold = DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT;
}
- int minSamples = Settings.Global.getInt(resolver,
+ int minSamples = Settings.Global.getInt(cr,
Settings.Global.DNS_RESOLVER_MIN_SAMPLES, DNS_RESOLVER_DEFAULT_MIN_SAMPLES);
- int maxSamples = Settings.Global.getInt(resolver,
+ int maxSamples = Settings.Global.getInt(cr,
Settings.Global.DNS_RESOLVER_MAX_SAMPLES, DNS_RESOLVER_DEFAULT_MAX_SAMPLES);
if (minSamples < 0 || minSamples > maxSamples || maxSamples > 64) {
Slog.w(TAG, "Invalid sample count (min, max)=(" + minSamples + ", " + maxSamples +
@@ -1980,8 +1984,24 @@ public class NetworkManagementService extends INetworkManagementService.Stub
final String[] domainStrs = domains == null ? new String[0] : domains.split(" ");
final int[] params = { sampleValidity, successThreshold, minSamples, maxSamples };
- final boolean useTls = Settings.Global.getInt(resolver,
- Settings.Global.DNS_TLS_DISABLED, 0) == 0;
+ final boolean useTls = shouldUseTls(cr);
+ // TODO: Populate tlsHostname once it's decided how the hostname's IP
+ // addresses will be resolved:
+ //
+ // [1] network-provided DNS servers are included here with the
+ // hostname and netd will use the network-provided servers to
+ // resolve the hostname and fix up its internal structures, or
+ //
+ // [2] network-provided DNS servers are included here without the
+ // hostname, the ConnectivityService layer resolves the given
+ // hostname, and then reconfigures netd with this information.
+ //
+ // In practice, there will always be a need for ConnectivityService or
+ // the captive portal app to use the network-provided services to make
+ // some queries. This argues in favor of [1], in concert with another
+ // mechanism, perhaps setting a high bit in the netid, to indicate
+ // via existing DNS APIs which set of servers (network-provided or
+ // non-network-provided private DNS) should be queried.
final String tlsHostname = "";
final String[] tlsFingerprints = new String[0];
try {
@@ -1992,6 +2012,15 @@ public class NetworkManagementService extends INetworkManagementService.Stub
}
}
+ private static boolean shouldUseTls(ContentResolver cr) {
+ String privateDns = Settings.Global.getString(cr, Settings.Global.PRIVATE_DNS_MODE);
+ if (TextUtils.isEmpty(privateDns)) {
+ privateDns = PRIVATE_DNS_DEFAULT_MODE;
+ }
+ return privateDns.equals(PRIVATE_DNS_MODE_OPPORTUNISTIC) ||
+ privateDns.startsWith(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
+ }
+
@Override
public void addVpnUidRanges(int netId, UidRange[] ranges) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index bfbce408cb28..75e8000ea2c1 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -56,6 +56,7 @@ import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.IProgressListener;
import android.os.IStoraged;
import android.os.IVold;
import android.os.IVoldListener;
@@ -570,7 +571,7 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon
}
// TODO: Reintroduce shouldBenchmark() test
- fstrim(0);
+ fstrim(0, null);
// invoke the completion callback, if any
// TODO: fstrim is non-blocking, so remove this useless callback
@@ -1576,21 +1577,19 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon
}
@Override
- public long benchmark(String volId) {
+ public void benchmark(String volId, IVoldTaskListener listener) {
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
- // TODO: refactor for callers to provide a listener
try {
- final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
mVold.benchmark(volId, new IVoldTaskListener.Stub() {
@Override
public void onStatus(int status, PersistableBundle extras) {
- // Not currently used
+ dispatchOnStatus(listener, status, extras);
}
@Override
public void onFinished(int status, PersistableBundle extras) {
- result.complete(extras);
+ dispatchOnFinished(listener, status, extras);
final String path = extras.getString("path");
final String ident = extras.getString("ident");
@@ -1611,10 +1610,8 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon
}
}
});
- return result.get(3, TimeUnit.MINUTES).getLong("run", Long.MAX_VALUE);
- } catch (Exception e) {
- Slog.wtf(TAG, e);
- return Long.MAX_VALUE;
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -1742,13 +1739,15 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon
}
@Override
- public void fstrim(int flags) {
+ public void fstrim(int flags, IVoldTaskListener listener) {
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
try {
mVold.fstrim(flags, new IVoldTaskListener.Stub() {
@Override
public void onStatus(int status, PersistableBundle extras) {
+ dispatchOnStatus(listener, status, extras);
+
// Ignore trim failures
if (status != 0) return;
@@ -1770,12 +1769,13 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon
@Override
public void onFinished(int status, PersistableBundle extras) {
- // Not currently used
+ dispatchOnFinished(listener, status, extras);
+
// TODO: benchmark when desired
}
});
- } catch (Exception e) {
- Slog.wtf(TAG, e);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -3239,6 +3239,26 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon
}
}
+ private void dispatchOnStatus(IVoldTaskListener listener, int status,
+ PersistableBundle extras) {
+ if (listener != null) {
+ try {
+ listener.onStatus(status, extras);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
+ private void dispatchOnFinished(IVoldTaskListener listener, int status,
+ PersistableBundle extras) {
+ if (listener != null) {
+ try {
+ listener.onFinished(status, extras);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
private static class Callbacks extends Handler {
private static final int MSG_STORAGE_STATE_CHANGED = 1;
private static final int MSG_VOLUME_STATE_CHANGED = 2;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5eb2a8d2066f..3f237370f638 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -122,6 +122,7 @@ import android.util.SparseIntArray;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityManager;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.XmlUtils;
import com.android.server.EventLogTags;
@@ -398,8 +399,9 @@ public class AudioService extends IAudioService.Stub
* {@link AudioManager#RINGER_MODE_SILENT}, or
* {@link AudioManager#RINGER_MODE_VIBRATE}.
*/
- // protected by mSettingsLock
+ @GuardedBy("mSettingsLock")
private int mRingerMode; // internal ringer mode, affects muting of underlying streams
+ @GuardedBy("mSettingsLock")
private int mRingerModeExternal = -1; // reported ringer mode to outside clients (AudioManager)
/** @see System#MODE_RINGER_STREAMS_AFFECTED */
@@ -929,8 +931,11 @@ public class AudioService extends IAudioService.Stub
mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_RECORD, mForcedUseForComm,
"onAudioServerDied"));
AudioSystem.setForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm);
- final int forSys = mCameraSoundForced ?
- AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE;
+ final int forSys;
+ synchronized (mSettingsLock) {
+ forSys = mCameraSoundForced ?
+ AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE;
+ }
mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_SYSTEM, forSys,
"onAudioServerDied"));
AudioSystem.setForceUse(AudioSystem.FOR_SYSTEM, forSys);
@@ -3797,6 +3802,7 @@ public class AudioService extends IAudioService.Stub
return (mRingerModeMutedStreams & (1 << streamType)) != 0;
}
+ @GuardedBy("mSettingsLock")
private boolean updateRingerModeAffectedStreams() {
int ringerModeAffectedStreams = Settings.System.getIntForUser(mContentResolver,
Settings.System.MODE_RINGER_STREAMS_AFFECTED,
@@ -3810,12 +3816,10 @@ public class AudioService extends IAudioService.Stub
ringerModeAffectedStreams = mRingerModeDelegate
.getRingerModeAffectedStreams(ringerModeAffectedStreams);
}
- synchronized (mCameraSoundForced) {
- if (mCameraSoundForced) {
- ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
- } else {
- ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
- }
+ if (mCameraSoundForced) {
+ ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
+ } else {
+ ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
}
if (mStreamVolumeAlias[AudioSystem.STREAM_DTMF] == AudioSystem.STREAM_RING) {
ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_DTMF);
@@ -4185,7 +4189,6 @@ public class AudioService extends IAudioService.Stub
// 2 mSetModeDeathHandlers
// 3 mSettingsLock
// 4 VolumeStreamState.class
- // 5 mCameraSoundForced
public class VolumeStreamState {
private final int mStreamType;
private final int mIndexMin;
@@ -4252,27 +4255,28 @@ public class AudioService extends IAudioService.Stub
}
public void readSettings() {
- synchronized (VolumeStreamState.class) {
- // force maximum volume on all streams if fixed volume property is set
- if (mUseFixedVolume) {
- mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
- return;
- }
- // do not read system stream volume from settings: this stream is always aliased
- // to another stream type and its volume is never persisted. Values in settings can
- // only be stale values
- if ((mStreamType == AudioSystem.STREAM_SYSTEM) ||
- (mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED)) {
- int index = 10 * AudioSystem.DEFAULT_STREAM_VOLUME[mStreamType];
- synchronized (mCameraSoundForced) {
+ synchronized (mSettingsLock) {
+ synchronized (VolumeStreamState.class) {
+ // force maximum volume on all streams if fixed volume property is set
+ if (mUseFixedVolume) {
+ mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
+ return;
+ }
+ // do not read system stream volume from settings: this stream is always aliased
+ // to another stream type and its volume is never persisted. Values in settings can
+ // only be stale values
+ if ((mStreamType == AudioSystem.STREAM_SYSTEM) ||
+ (mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED)) {
+ int index = 10 * AudioSystem.DEFAULT_STREAM_VOLUME[mStreamType];
if (mCameraSoundForced) {
index = mIndexMax;
}
+ mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, index);
+ return;
}
- mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, index);
- return;
}
-
+ }
+ synchronized (VolumeStreamState.class) {
int remainingDevices = AudioSystem.DEVICE_OUT_ALL;
for (int i = 0; remainingDevices != 0; i++) {
@@ -4385,34 +4389,34 @@ public class AudioService extends IAudioService.Stub
public boolean setIndex(int index, int device, String caller) {
boolean changed = false;
int oldIndex;
- synchronized (VolumeStreamState.class) {
- oldIndex = getIndex(device);
- index = getValidIndex(index);
- synchronized (mCameraSoundForced) {
+ synchronized (mSettingsLock) {
+ synchronized (VolumeStreamState.class) {
+ oldIndex = getIndex(device);
+ index = getValidIndex(index);
if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) {
index = mIndexMax;
}
- }
- mIndexMap.put(device, index);
-
- changed = oldIndex != index;
- // Apply change to all streams using this one as alias if:
- // - the index actually changed OR
- // - there is no volume index stored for this device on alias stream.
- // If changing volume of current device, also change volume of current
- // device on aliased stream
- final boolean currentDevice = (device == getDeviceForStream(mStreamType));
- final int numStreamTypes = AudioSystem.getNumStreamTypes();
- for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
- final VolumeStreamState aliasStreamState = mStreamStates[streamType];
- if (streamType != mStreamType &&
- mStreamVolumeAlias[streamType] == mStreamType &&
- (changed || !aliasStreamState.hasIndexForDevice(device))) {
- final int scaledIndex = rescaleIndex(index, mStreamType, streamType);
- aliasStreamState.setIndex(scaledIndex, device, caller);
- if (currentDevice) {
- aliasStreamState.setIndex(scaledIndex,
- getDeviceForStream(streamType), caller);
+ mIndexMap.put(device, index);
+
+ changed = oldIndex != index;
+ // Apply change to all streams using this one as alias if:
+ // - the index actually changed OR
+ // - there is no volume index stored for this device on alias stream.
+ // If changing volume of current device, also change volume of current
+ // device on aliased stream
+ final boolean currentDevice = (device == getDeviceForStream(mStreamType));
+ final int numStreamTypes = AudioSystem.getNumStreamTypes();
+ for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
+ final VolumeStreamState aliasStreamState = mStreamStates[streamType];
+ if (streamType != mStreamType &&
+ mStreamVolumeAlias[streamType] == mStreamType &&
+ (changed || !aliasStreamState.hasIndexForDevice(device))) {
+ final int scaledIndex = rescaleIndex(index, mStreamType, streamType);
+ aliasStreamState.setIndex(scaledIndex, device, caller);
+ if (currentDevice) {
+ aliasStreamState.setIndex(scaledIndex,
+ getDeviceForStream(streamType), caller);
+ }
}
}
}
@@ -6022,13 +6026,8 @@ public class AudioService extends IAudioService.Stub
boolean cameraSoundForced = readCameraSoundForced();
synchronized (mSettingsLock) {
- boolean cameraSoundForcedChanged = false;
- synchronized (mCameraSoundForced) {
- if (cameraSoundForced != mCameraSoundForced) {
- mCameraSoundForced = cameraSoundForced;
- cameraSoundForcedChanged = true;
- }
- }
+ final boolean cameraSoundForcedChanged = (cameraSoundForced != mCameraSoundForced);
+ mCameraSoundForced = cameraSoundForced;
if (cameraSoundForcedChanged) {
if (!mIsSingleVolume) {
VolumeStreamState s = mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED];
@@ -6408,11 +6407,12 @@ public class AudioService extends IAudioService.Stub
//==========================================================================================
// cached value of com.android.internal.R.bool.config_camera_sound_forced
- private Boolean mCameraSoundForced;
+ @GuardedBy("mSettingsLock")
+ private boolean mCameraSoundForced;
// called by android.hardware.Camera to populate CameraInfo.canDisableShutterSound
public boolean isCameraSoundForced() {
- synchronized (mCameraSoundForced) {
+ synchronized (mSettingsLock) {
return mCameraSoundForced;
}
}
@@ -6734,7 +6734,9 @@ public class AudioService extends IAudioService.Stub
public void setRingerModeDelegate(RingerModeDelegate delegate) {
mRingerModeDelegate = delegate;
if (mRingerModeDelegate != null) {
- updateRingerModeAffectedStreams();
+ synchronized (mSettingsLock) {
+ updateRingerModeAffectedStreams();
+ }
setRingerModeInternal(getRingerModeInternal(), TAG + ".setRingerModeDelegate");
}
}
diff --git a/services/core/java/com/android/server/media/AudioPlaybackMonitor.java b/services/core/java/com/android/server/media/AudioPlaybackMonitor.java
deleted file mode 100644
index 791ee821b357..000000000000
--- a/services/core/java/com/android/server/media/AudioPlaybackMonitor.java
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * 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.server.media;
-
-import android.content.Context;
-import android.media.AudioManager.AudioPlaybackCallback;
-import android.media.AudioPlaybackConfiguration;
-import android.media.IAudioService;
-import android.media.IPlaybackConfigDispatcher;
-import android.os.Binder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.IntArray;
-import android.util.Log;
-import android.util.SparseArray;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Monitors changes in audio playback, and notify the newly started audio playback through the
- * {@link OnAudioPlaybackStartedListener} and the activeness change through the
- * {@link OnAudioPlaybackActiveStateListener}.
- */
-class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub {
- private static boolean DEBUG = MediaSessionService.DEBUG;
- private static String TAG = "AudioPlaybackMonitor";
-
- private static AudioPlaybackMonitor sInstance;
-
- /**
- * Called when audio playback is started for a given UID.
- */
- interface OnAudioPlaybackStartedListener {
- void onAudioPlaybackStarted(int uid);
- }
-
- /**
- * Called when audio player state is changed.
- */
- interface OnAudioPlayerActiveStateChangedListener {
- void onAudioPlayerActiveStateChanged(int uid, boolean active);
- }
-
- private final Object mLock = new Object();
- private final Context mContext;
- private final List<OnAudioPlaybackStartedListener> mAudioPlaybackStartedListeners
- = new ArrayList<>();
- private final List<OnAudioPlayerActiveStateChangedListener>
- mAudioPlayerActiveStateChangedListeners = new ArrayList<>();
- private final Map<Integer, Integer> mAudioPlaybackStates = new HashMap<>();
- private final Set<Integer> mActiveAudioPlaybackClientUids = new HashSet<>();
-
- // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video)
- // The UID whose audio playback becomes active at the last comes first.
- // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID.
- private final IntArray mSortedAudioPlaybackClientUids = new IntArray();
-
- static AudioPlaybackMonitor getInstance(Context context, IAudioService audioService) {
- if (sInstance == null) {
- sInstance = new AudioPlaybackMonitor(context, audioService);
- }
- return sInstance;
- }
-
- private AudioPlaybackMonitor(Context context, IAudioService audioService) {
- mContext = context;
- try {
- audioService.registerPlaybackCallback(this);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Failed to register playback callback", e);
- }
- }
-
- /**
- * Called when the {@link AudioPlaybackConfiguration} is updated.
- * <p>If an app starts audio playback, the app's local media session will be the media button
- * session. If the app has multiple media sessions, the playback active local session will be
- * picked.
- *
- * @param configs List of the current audio playback configuration
- */
- @Override
- public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
- boolean flush) {
- if (flush) {
- Binder.flushPendingCommands();
- }
- final long token = Binder.clearCallingIdentity();
- try {
- List<Integer> newActiveAudioPlaybackClientUids = new ArrayList<>();
- List<OnAudioPlayerActiveStateChangedListener> audioPlayerActiveStateChangedListeners;
- List<OnAudioPlaybackStartedListener> audioPlaybackStartedListeners;
- synchronized (mLock) {
- // Update mActiveAudioPlaybackClientUids and mSortedAudioPlaybackClientUids,
- // and find newly activated audio playbacks.
- mActiveAudioPlaybackClientUids.clear();
- for (AudioPlaybackConfiguration config : configs) {
- // Ignore inactive (i.e. not playing) or PLAYER_TYPE_JAM_SOUNDPOOL
- // (i.e. playback from the SoundPool class which is only for sound effects)
- // playback.
- // Note that we shouldn't ignore PLAYER_TYPE_UNKNOWN because it might be OEM
- // specific audio/video players.
- if (!config.isActive() || config.getPlayerType()
- == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
- continue;
- }
-
- mActiveAudioPlaybackClientUids.add(config.getClientUid());
- Integer oldState = mAudioPlaybackStates.get(config.getPlayerInterfaceId());
- if (!isActiveState(oldState)) {
- if (DEBUG) {
- Log.d(TAG, "Found a new active media playback. " +
- AudioPlaybackConfiguration.toLogFriendlyString(config));
- }
- // New active audio playback.
- newActiveAudioPlaybackClientUids.add(config.getClientUid());
- int index = mSortedAudioPlaybackClientUids.indexOf(config.getClientUid());
- if (index == 0) {
- // It's the lastly played music app already. Skip updating.
- continue;
- } else if (index > 0) {
- mSortedAudioPlaybackClientUids.remove(index);
- }
- mSortedAudioPlaybackClientUids.add(0, config.getClientUid());
- }
- }
- audioPlayerActiveStateChangedListeners = new ArrayList<>(
- mAudioPlayerActiveStateChangedListeners);
- audioPlaybackStartedListeners = new ArrayList<>(mAudioPlaybackStartedListeners);
- }
- // Notify the change of audio playback states.
- for (AudioPlaybackConfiguration config : configs) {
- boolean wasActive = isActiveState(
- mAudioPlaybackStates.get(config.getPlayerInterfaceId()));
- boolean isActive = config.isActive();
- if (wasActive != isActive) {
- for (OnAudioPlayerActiveStateChangedListener listener
- : audioPlayerActiveStateChangedListeners) {
- listener.onAudioPlayerActiveStateChanged(config.getClientUid(),
- isActive);
- }
- }
- }
- // Notify the start of audio playback
- for (int uid : newActiveAudioPlaybackClientUids) {
- for (OnAudioPlaybackStartedListener listener : audioPlaybackStartedListeners) {
- listener.onAudioPlaybackStarted(uid);
- }
- }
- mAudioPlaybackStates.clear();
- for (AudioPlaybackConfiguration config : configs) {
- mAudioPlaybackStates.put(config.getPlayerInterfaceId(), config.getPlayerState());
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- /**
- * Registers OnAudioPlaybackStartedListener.
- */
- public void registerOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) {
- synchronized (mLock) {
- mAudioPlaybackStartedListeners.add(listener);
- }
- }
-
- /**
- * Unregisters OnAudioPlaybackStartedListener.
- */
- public void unregisterOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) {
- synchronized (mLock) {
- mAudioPlaybackStartedListeners.remove(listener);
- }
- }
-
- /**
- * Registers OnAudioPlayerActiveStateChangedListener.
- */
- public void registerOnAudioPlayerActiveStateChangedListener(
- OnAudioPlayerActiveStateChangedListener listener) {
- synchronized (mLock) {
- mAudioPlayerActiveStateChangedListeners.add(listener);
- }
- }
-
- /**
- * Unregisters OnAudioPlayerActiveStateChangedListener.
- */
- public void unregisterOnAudioPlayerActiveStateChangedListener(
- OnAudioPlayerActiveStateChangedListener listener) {
- synchronized (mLock) {
- mAudioPlayerActiveStateChangedListeners.remove(listener);
- }
- }
-
- /**
- * Returns the sorted list of UIDs that have had active audio playback. (i.e. playing an
- * audio/video) The UID whose audio playback becomes active at the last comes first.
- */
- public IntArray getSortedAudioPlaybackClientUids() {
- IntArray sortedAudioPlaybackClientUids = new IntArray();
- synchronized (mLock) {
- sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids);
- }
- return sortedAudioPlaybackClientUids;
- }
-
- /**
- * Returns if the audio playback is active for the uid.
- */
- public boolean isPlaybackActive(int uid) {
- synchronized (mLock) {
- return mActiveAudioPlaybackClientUids.contains(uid);
- }
- }
-
- /**
- * Cleans up the sorted list of audio playback client UIDs with given {@param
- * mediaButtonSessionUid}.
- * <p>UIDs whose audio playback started after the media button session's audio playback
- * cannot be the lastly played media app. So they won't needed anymore.
- *
- * @param mediaButtonSessionUid UID of the media button session.
- */
- public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) {
- synchronized (mLock) {
- int userId = UserHandle.getUserId(mediaButtonSessionUid);
- for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) {
- if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) {
- break;
- }
- int uid = mSortedAudioPlaybackClientUids.get(i);
- if (userId == UserHandle.getUserId(uid) && !isPlaybackActive(uid)) {
- // Clean up unnecessary UIDs.
- // It doesn't need to be managed profile aware because it's just to prevent
- // the list from increasing indefinitely. The media button session updating
- // shouldn't be affected by cleaning up.
- mSortedAudioPlaybackClientUids.remove(i);
- }
- }
- }
- }
-
- /**
- * Dumps {@link AudioPlaybackMonitor}.
- */
- public void dump(PrintWriter pw, String prefix) {
- synchronized (mLock) {
- pw.println(prefix + "Audio playback (lastly played comes first)");
- String indent = prefix + " ";
- for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) {
- int uid = mSortedAudioPlaybackClientUids.get(i);
- pw.print(indent + "uid=" + uid + " packages=");
- String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
- if (packages != null && packages.length > 0) {
- for (int j = 0; j < packages.length; j++) {
- pw.print(packages[j] + " ");
- }
- }
- pw.println();
- }
- }
- }
-
- private boolean isActiveState(Integer state) {
- return state != null && state.equals(AudioPlaybackConfiguration.PLAYER_STATE_STARTED);
- }
-}
diff --git a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
new file mode 100644
index 000000000000..be223f1e56df
--- /dev/null
+++ b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
@@ -0,0 +1,327 @@
+/*
+ * 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.server.media;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.AudioPlaybackConfiguration;
+import android.media.IAudioService;
+import android.media.IPlaybackConfigDispatcher;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.IntArray;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Monitors the state changes of audio players.
+ */
+class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub {
+ private static boolean DEBUG = MediaSessionService.DEBUG;
+ private static String TAG = "AudioPlayerStateMonitor";
+
+ private static AudioPlayerStateMonitor sInstance = new AudioPlayerStateMonitor();
+
+ /**
+ * Called when the state of audio player is changed.
+ */
+ interface OnAudioPlayerStateChangedListener {
+ void onAudioPlayerStateChanged(
+ int uid, int prevState, @Nullable AudioPlaybackConfiguration config);
+ }
+
+ private final static class MessageHandler extends Handler {
+ private static final int MSG_AUDIO_PLAYER_STATE_CHANGED = 1;
+
+ private final OnAudioPlayerStateChangedListener mListsner;
+
+ public MessageHandler(Looper looper, OnAudioPlayerStateChangedListener listener) {
+ super(looper);
+ mListsner = listener;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_AUDIO_PLAYER_STATE_CHANGED:
+ mListsner.onAudioPlayerStateChanged(
+ msg.arg1, msg.arg2, (AudioPlaybackConfiguration) msg.obj);
+ break;
+ }
+ }
+
+ public void sendAudioPlayerStateChangedMessage(int uid, int prevState,
+ AudioPlaybackConfiguration config) {
+ obtainMessage(MSG_AUDIO_PLAYER_STATE_CHANGED, uid, prevState, config).sendToTarget();
+ }
+ }
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final Map<OnAudioPlayerStateChangedListener, MessageHandler> mListenerMap =
+ new HashMap<>();
+ @GuardedBy("mLock")
+ private final Map<Integer, Integer> mAudioPlayerStates = new HashMap<>();
+ @GuardedBy("mLock")
+ private final Map<Integer, HashSet<Integer>> mAudioPlayersForUid = new HashMap<>();
+ // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video)
+ // The UID whose audio playback becomes active at the last comes first.
+ // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID.
+ @GuardedBy("mLock")
+ private final IntArray mSortedAudioPlaybackClientUids = new IntArray();
+
+ @GuardedBy("mLock")
+ private boolean mRegisteredToAudioService;
+
+ static AudioPlayerStateMonitor getInstance() {
+ return sInstance;
+ }
+
+ private AudioPlayerStateMonitor() {
+ }
+
+ /**
+ * Called when the {@link AudioPlaybackConfiguration} is updated.
+ * <p>If an app starts audio playback, the app's local media session will be the media button
+ * session. If the app has multiple media sessions, the playback active local session will be
+ * picked.
+ *
+ * @param configs List of the current audio playback configuration
+ */
+ @Override
+ public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
+ boolean flush) {
+ if (flush) {
+ Binder.flushPendingCommands();
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ final Map<Integer, Integer> prevAudioPlayerStates = new HashMap<>(mAudioPlayerStates);
+ final Map<Integer, HashSet<Integer>> prevAudioPlayersForUid =
+ new HashMap<>(mAudioPlayersForUid);
+ synchronized (mLock) {
+ mAudioPlayerStates.clear();
+ mAudioPlayersForUid.clear();
+ for (AudioPlaybackConfiguration config : configs) {
+ int pii = config.getPlayerInterfaceId();
+ int uid = config.getClientUid();
+ mAudioPlayerStates.put(pii, config.getPlayerState());
+ HashSet<Integer> players = mAudioPlayersForUid.get(uid);
+ if (players == null) {
+ players = new HashSet<Integer>();
+ players.add(pii);
+ mAudioPlayersForUid.put(uid, players);
+ } else {
+ players.add(pii);
+ }
+ }
+ for (AudioPlaybackConfiguration config : configs) {
+ if (!config.isActive()) {
+ continue;
+ }
+
+ int uid = config.getClientUid();
+ if (!isActiveState(prevAudioPlayerStates.get(config.getPlayerInterfaceId()))) {
+ if (DEBUG) {
+ Log.d(TAG, "Found a new active media playback. " +
+ AudioPlaybackConfiguration.toLogFriendlyString(config));
+ }
+ // New active audio playback.
+ int index = mSortedAudioPlaybackClientUids.indexOf(uid);
+ if (index == 0) {
+ // It's the lastly played music app already. Skip updating.
+ continue;
+ } else if (index > 0) {
+ mSortedAudioPlaybackClientUids.remove(index);
+ }
+ mSortedAudioPlaybackClientUids.add(0, uid);
+ }
+ }
+ // Notify the change of audio player states.
+ for (AudioPlaybackConfiguration config : configs) {
+ final Integer prevState = prevAudioPlayerStates.get(config.getPlayerInterfaceId());
+ final int prevStateInt =
+ (prevState == null) ? AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN :
+ prevState.intValue();
+ if (prevStateInt != config.getPlayerState()) {
+ sendAudioPlayerStateChangedMessageLocked(
+ config.getClientUid(), prevStateInt, config);
+ }
+ }
+ for (Integer prevUid : prevAudioPlayersForUid.keySet()) {
+ // If all players for prevUid is removed, notify the prev state was
+ // PLAYER_STATE_STARTED only when there were a player whose state was
+ // PLAYER_STATE_STARTED, otherwise any inactive state is okay to notify.
+ if (!mAudioPlayersForUid.containsKey(prevUid)) {
+ Set<Integer> prevPlayers = prevAudioPlayersForUid.get(prevUid);
+ int prevState = AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN;
+ for (int pii : prevPlayers) {
+ Integer state = prevAudioPlayerStates.get(pii);
+ if (state == null) {
+ continue;
+ }
+ if (state == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+ prevState = state;
+ break;
+ } else if (prevState
+ == AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN) {
+ prevState = state;
+ }
+ }
+ sendAudioPlayerStateChangedMessageLocked(prevUid, prevState, null);
+ }
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Registers OnAudioPlayerStateChangedListener.
+ */
+ public void registerListener(OnAudioPlayerStateChangedListener listener, Handler handler) {
+ synchronized (mLock) {
+ mListenerMap.put(listener, new MessageHandler((handler == null) ?
+ Looper.myLooper() : handler.getLooper(), listener));
+ }
+ }
+
+ /**
+ * Unregisters OnAudioPlayerStateChangedListener.
+ */
+ public void unregisterListener(OnAudioPlayerStateChangedListener listener) {
+ synchronized (mLock) {
+ mListenerMap.remove(listener);
+ }
+ }
+
+ /**
+ * Returns the sorted list of UIDs that have had active audio playback. (i.e. playing an
+ * audio/video) The UID whose audio playback becomes active at the last comes first.
+ */
+ public IntArray getSortedAudioPlaybackClientUids() {
+ IntArray sortedAudioPlaybackClientUids = new IntArray();
+ synchronized (mLock) {
+ sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids);
+ }
+ return sortedAudioPlaybackClientUids;
+ }
+
+ /**
+ * Returns if the audio playback is active for the uid.
+ */
+ public boolean isPlaybackActive(int uid) {
+ synchronized (mLock) {
+ Set<Integer> players = mAudioPlayersForUid.get(uid);
+ if (players == null) {
+ return false;
+ }
+ for (Integer pii : players) {
+ if (isActiveState(mAudioPlayerStates.get(pii))) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Cleans up the sorted list of audio playback client UIDs with given {@param
+ * mediaButtonSessionUid}.
+ * <p>UIDs whose audio playback are inactive and have started before the media button session's
+ * audio playback cannot be the lastly played media app. So they won't needed anymore.
+ *
+ * @param mediaButtonSessionUid UID of the media button session.
+ */
+ public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) {
+ synchronized (mLock) {
+ int userId = UserHandle.getUserId(mediaButtonSessionUid);
+ for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) {
+ if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) {
+ break;
+ }
+ int uid = mSortedAudioPlaybackClientUids.get(i);
+ if (userId == UserHandle.getUserId(uid) && !isPlaybackActive(uid)) {
+ // Clean up unnecessary UIDs.
+ // It doesn't need to be managed profile aware because it's just to prevent
+ // the list from increasing indefinitely. The media button session updating
+ // shouldn't be affected by cleaning up.
+ mSortedAudioPlaybackClientUids.remove(i);
+ }
+ }
+ }
+ }
+
+ /**
+ * Dumps {@link AudioPlayerStateMonitor}.
+ */
+ public void dump(Context context, PrintWriter pw, String prefix) {
+ synchronized (mLock) {
+ pw.println(prefix + "Audio playback (lastly played comes first)");
+ String indent = prefix + " ";
+ for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) {
+ int uid = mSortedAudioPlaybackClientUids.get(i);
+ pw.print(indent + "uid=" + uid + " packages=");
+ String[] packages = context.getPackageManager().getPackagesForUid(uid);
+ if (packages != null && packages.length > 0) {
+ for (int j = 0; j < packages.length; j++) {
+ pw.print(packages[j] + " ");
+ }
+ }
+ pw.println();
+ }
+ }
+ }
+
+ public void registerSelfIntoAudioServiceIfNeeded(IAudioService audioService) {
+ synchronized (mLock) {
+ try {
+ if (!mRegisteredToAudioService) {
+ audioService.registerPlaybackCallback(this);
+ mRegisteredToAudioService = true;
+ }
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Failed to register playback callback", e);
+ mRegisteredToAudioService = false;
+ }
+ }
+ }
+
+ private void sendAudioPlayerStateChangedMessageLocked(
+ final int uid, final int prevState, final AudioPlaybackConfiguration config) {
+ for (MessageHandler messageHandler : mListenerMap.values()) {
+ messageHandler.sendAudioPlayerStateChangedMessage(uid, prevState, config);
+ }
+ }
+
+ private static boolean isActiveState(Integer state) {
+ return state != null && state.equals(AudioPlaybackConfiguration.PLAYER_STATE_STARTED);
+ }
+}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 1cfd5f02e810..3c9e1d456c6f 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -19,12 +19,14 @@ package com.android.server.media;
import com.android.internal.util.DumpUtils;
import com.android.server.Watchdog;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.media.AudioPlaybackConfiguration;
import android.media.AudioRoutesInfo;
import android.media.AudioSystem;
import android.media.IAudioRoutesObserver;
@@ -96,7 +98,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub
private int mCurrentUserId = -1;
private boolean mGlobalBluetoothA2dpOn = false;
private final IAudioService mAudioService;
- private final AudioPlaybackMonitor mAudioPlaybackMonitor;
+ private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
+ private final Handler mHandler = new Handler();
private final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
public MediaRouterService(Context context) {
@@ -106,31 +109,57 @@ public final class MediaRouterService extends IMediaRouterService.Stub
mAudioService = IAudioService.Stub.asInterface(
ServiceManager.getService(Context.AUDIO_SERVICE));
- mAudioPlaybackMonitor = AudioPlaybackMonitor.getInstance(context, mAudioService);
- mAudioPlaybackMonitor.registerOnAudioPlayerActiveStateChangedListener(
- new AudioPlaybackMonitor.OnAudioPlayerActiveStateChangedListener() {
+ mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
+ mAudioPlayerStateMonitor.registerListener(
+ new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() {
+ static final long WAIT_MS = 500;
+ final Runnable mRestoreBluetoothA2dpRunnable = new Runnable() {
+ @Override
+ public void run() {
+ restoreBluetoothA2dp();
+ }
+ };
+
@Override
- public void onAudioPlayerActiveStateChanged(int uid, boolean active) {
+ public void onAudioPlayerStateChanged(
+ int uid, int prevState, @Nullable AudioPlaybackConfiguration config) {
+ int restoreUid = -1;
+ boolean active = config == null ? false : config.isActive();
if (active) {
- restoreRoute(uid);
+ restoreUid = uid;
+ } else if (prevState != AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+ // Noting to do if the prev state is not an active state.
+ return;
} else {
IntArray sortedAudioPlaybackClientUids =
- mAudioPlaybackMonitor.getSortedAudioPlaybackClientUids();
- boolean restored = false;
- for (int i = 0; i < sortedAudioPlaybackClientUids.size(); i++) {
- if (mAudioPlaybackMonitor.isPlaybackActive(
+ mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids();
+ for (int i = 0; i < sortedAudioPlaybackClientUids.size(); ++i) {
+ if (mAudioPlayerStateMonitor.isPlaybackActive(
sortedAudioPlaybackClientUids.get(i))) {
- restoreRoute(sortedAudioPlaybackClientUids.get(i));
- restored = true;
+ restoreUid = sortedAudioPlaybackClientUids.get(i);
break;
}
}
- if (!restored) {
- restoreBluetoothA2dp();
+ }
+
+ mHandler.removeCallbacks(mRestoreBluetoothA2dpRunnable);
+ if (restoreUid >= 0) {
+ restoreRoute(restoreUid);
+ if (DEBUG) {
+ Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid
+ + " active " + active + " restoring " + restoreUid);
+ }
+ } else {
+ mHandler.postDelayed(mRestoreBluetoothA2dpRunnable, WAIT_MS);
+ if (DEBUG) {
+ Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid
+ + " active " + active + " delaying");
}
}
}
- });
+ }, mHandler);
+ mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
+
AudioRoutesInfo audioRoutes = null;
try {
audioRoutes = mAudioService.startWatchingRoutes(new IAudioRoutesObserver.Stub() {
@@ -261,9 +290,14 @@ public final class MediaRouterService extends IMediaRouterService.Stub
final long token = Binder.clearCallingIdentity();
try {
+ ClientRecord clientRecord;
synchronized (mLock) {
- return isPlaybackActiveLocked(client);
+ clientRecord = mAllClientRecords.get(client.asBinder());
+ }
+ if (clientRecord != null) {
+ return mAudioPlayerStateMonitor.isPlaybackActive(clientRecord.mUid);
}
+ return false;
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -480,14 +514,6 @@ public final class MediaRouterService extends IMediaRouterService.Stub
return null;
}
- private boolean isPlaybackActiveLocked(IMediaRouterClient client) {
- ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
- if (clientRecord != null) {
- return mAudioPlaybackMonitor.isPlaybackActive(clientRecord.mUid);
- }
- return false;
- }
-
private void setDiscoveryRequestLocked(IMediaRouterClient client,
int routeTypes, boolean activeScan) {
final IBinder binder = client.asBinder();
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index aa652445c45a..f6a81d07277a 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -16,6 +16,7 @@
package com.android.server.media;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.KeyguardManager;
@@ -31,7 +32,7 @@ import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.media.AudioManager;
-import android.media.AudioManagerInternal;
+import android.media.AudioPlaybackConfiguration;
import android.media.AudioSystem;
import android.media.IAudioService;
import android.media.IRemoteVolumeController;
@@ -68,7 +69,6 @@ import android.view.KeyEvent;
import android.view.ViewConfiguration;
import com.android.internal.util.DumpUtils;
-import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.Watchdog;
import com.android.server.Watchdog.Monitor;
@@ -104,7 +104,6 @@ public class MediaSessionService extends SystemService implements Monitor {
private KeyguardManager mKeyguardManager;
private IAudioService mAudioService;
- private AudioManagerInternal mAudioManagerInternal;
private ContentResolver mContentResolver;
private SettingsObserver mSettingsObserver;
private INotificationManager mNotificationManager;
@@ -114,7 +113,7 @@ public class MediaSessionService extends SystemService implements Monitor {
// It's always not null after the MediaSessionService is started.
private FullUserRecord mCurrentFullUserRecord;
private MediaSessionRecord mGlobalPrioritySession;
- private AudioPlaybackMonitor mAudioPlaybackMonitor;
+ private AudioPlayerStateMonitor mAudioPlayerStateMonitor;
// Used to notify system UI when remote volume was changed. TODO find a
// better way to handle this.
@@ -137,11 +136,16 @@ public class MediaSessionService extends SystemService implements Monitor {
mKeyguardManager =
(KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
mAudioService = getAudioService();
- mAudioPlaybackMonitor = AudioPlaybackMonitor.getInstance(getContext(), mAudioService);
- mAudioPlaybackMonitor.registerOnAudioPlaybackStartedListener(
- new AudioPlaybackMonitor.OnAudioPlaybackStartedListener() {
+ mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
+ mAudioPlayerStateMonitor.registerListener(
+ new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() {
@Override
- public void onAudioPlaybackStarted(int uid) {
+ public void onAudioPlayerStateChanged(
+ int uid, int prevState, @Nullable AudioPlaybackConfiguration config) {
+ if (config == null || !config.isActive() || config.getPlayerType()
+ == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+ return;
+ }
synchronized (mLock) {
FullUserRecord user =
getFullUserRecordLocked(UserHandle.getUserId(uid));
@@ -150,8 +154,8 @@ public class MediaSessionService extends SystemService implements Monitor {
}
}
}
- });
- mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
+ }, null /* handler */);
+ mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
mContentResolver = getContext().getContentResolver();
mSettingsObserver = new SettingsObserver();
mSettingsObserver.observe();
@@ -650,7 +654,7 @@ public class MediaSessionService extends SystemService implements Monitor {
public FullUserRecord(int fullUserId) {
mFullUserId = fullUserId;
- mPriorityStack = new MediaSessionStack(mAudioPlaybackMonitor, this);
+ mPriorityStack = new MediaSessionStack(mAudioPlayerStateMonitor, this);
// Restore the remembered media button receiver before the boot.
String mediaButtonReceiver = Settings.Secure.getStringForUser(mContentResolver,
Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId);
@@ -1309,7 +1313,7 @@ public class MediaSessionService extends SystemService implements Monitor {
for (int i = 0; i < count; i++) {
mUserRecords.valueAt(i).dumpLocked(pw, "");
}
- mAudioPlaybackMonitor.dump(pw, "");
+ mAudioPlayerStateMonitor.dump(getContext(), pw, "");
}
}
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index d9fe72e0c8bb..719ec362e6e8 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -75,7 +75,7 @@ class MediaSessionStack {
*/
private final List<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
- private final AudioPlaybackMonitor mAudioPlaybackMonitor;
+ private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
private final OnMediaButtonSessionChangedListener mOnMediaButtonSessionChangedListener;
/**
@@ -84,7 +84,6 @@ class MediaSessionStack {
*/
private MediaSessionRecord mMediaButtonSession;
- private MediaSessionRecord mCachedDefault;
private MediaSessionRecord mCachedVolumeDefault;
/**
@@ -93,8 +92,8 @@ class MediaSessionStack {
private final SparseArray<ArrayList<MediaSessionRecord>> mCachedActiveLists =
new SparseArray<>();
- MediaSessionStack(AudioPlaybackMonitor monitor, OnMediaButtonSessionChangedListener listener) {
- mAudioPlaybackMonitor = monitor;
+ MediaSessionStack(AudioPlayerStateMonitor monitor, OnMediaButtonSessionChangedListener listener) {
+ mAudioPlayerStateMonitor = monitor;
mOnMediaButtonSessionChangedListener = listener;
}
@@ -187,13 +186,13 @@ class MediaSessionStack {
if (DEBUG) {
Log.d(TAG, "updateMediaButtonSessionIfNeeded, callers=" + Debug.getCallers(2));
}
- IntArray audioPlaybackUids = mAudioPlaybackMonitor.getSortedAudioPlaybackClientUids();
+ IntArray audioPlaybackUids = mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids();
for (int i = 0; i < audioPlaybackUids.size(); i++) {
MediaSessionRecord mediaButtonSession =
findMediaButtonSession(audioPlaybackUids.get(i));
if (mediaButtonSession != null) {
// Found the media button session.
- mAudioPlaybackMonitor.cleanUpAudioPlaybackUids(mediaButtonSession.getUid());
+ mAudioPlayerStateMonitor.cleanUpAudioPlaybackUids(mediaButtonSession.getUid());
if (mMediaButtonSession != mediaButtonSession) {
updateMediaButtonSession(mediaButtonSession);
}
@@ -216,7 +215,7 @@ class MediaSessionStack {
for (MediaSessionRecord session : mSessions) {
if (uid == session.getUid()) {
if (session.getPlaybackState() != null && session.isPlaybackActive() ==
- mAudioPlaybackMonitor.isPlaybackActive(session.getUid())) {
+ mAudioPlayerStateMonitor.isPlaybackActive(session.getUid())) {
// If there's a media session whose PlaybackState matches
// the audio playback state, return it immediately.
return session;
@@ -376,7 +375,6 @@ class MediaSessionStack {
}
private void clearCache(int userId) {
- mCachedDefault = null;
mCachedVolumeDefault = null;
mCachedActiveLists.remove(userId);
// mCachedActiveLists may also include the list of sessions for UserHandle.USER_ALL,
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index 5c2b66f6b24f..31a1abb39461 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -86,6 +86,14 @@ import libcore.io.IoBridge;
*/
public class ApfFilter {
+ // Helper class for specifying functional filter parameters.
+ public static class ApfConfiguration {
+ public ApfCapabilities apfCapabilities;
+ public boolean multicastFilter;
+ public boolean ieee802_3Filter;
+ public int[] ethTypeBlackList;
+ }
+
// Enums describing the outcome of receiving an RA packet.
private static enum ProcessRaResult {
MATCH, // Received RA matched a known RA
@@ -261,17 +269,16 @@ public class ApfFilter {
private int mIPv4PrefixLength;
@VisibleForTesting
- ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface,
- IpClient.Callback ipClientCallback, boolean multicastFilter,
- boolean ieee802_3Filter, int[] ethTypeBlackList, IpConnectivityLog log) {
- mApfCapabilities = apfCapabilities;
+ ApfFilter(ApfConfiguration config, NetworkInterface networkInterface,
+ IpClient.Callback ipClientCallback, IpConnectivityLog log) {
+ mApfCapabilities = config.apfCapabilities;
mIpClientCallback = ipClientCallback;
mNetworkInterface = networkInterface;
- mMulticastFilter = multicastFilter;
- mDrop802_3Frames = ieee802_3Filter;
+ mMulticastFilter = config.multicastFilter;
+ mDrop802_3Frames = config.ieee802_3Filter;
// Now fill the black list from the passed array
- mEthTypeBlackList = filterEthTypeBlackList(ethTypeBlackList);
+ mEthTypeBlackList = filterEthTypeBlackList(config.ethTypeBlackList);
mMetricsLog = log;
@@ -1160,9 +1167,10 @@ public class ApfFilter {
* Create an {@link ApfFilter} if {@code apfCapabilities} indicates support for packet
* filtering using APF programs.
*/
- public static ApfFilter maybeCreate(ApfCapabilities apfCapabilities,
- NetworkInterface networkInterface, IpClient.Callback ipClientCallback,
- boolean multicastFilter, boolean ieee802_3Filter, int[] ethTypeBlackList) {
+ public static ApfFilter maybeCreate(ApfConfiguration config,
+ NetworkInterface networkInterface, IpClient.Callback ipClientCallback) {
+ if (config == null) return null;
+ ApfCapabilities apfCapabilities = config.apfCapabilities;
if (apfCapabilities == null || networkInterface == null) return null;
if (apfCapabilities.apfVersionSupported == 0) return null;
if (apfCapabilities.maximumApfProgramSize < 512) {
@@ -1178,8 +1186,7 @@ public class ApfFilter {
Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported);
return null;
}
- return new ApfFilter(apfCapabilities, networkInterface, ipClientCallback,
- multicastFilter, ieee802_3Filter, ethTypeBlackList, new IpConnectivityLog());
+ return new ApfFilter(config, networkInterface, ipClientCallback, new IpConnectivityLog());
}
public synchronized void shutdown() {
diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java
index 24f1cf340968..70983c864ffc 100644
--- a/services/net/java/android/net/ip/IpClient.java
+++ b/services/net/java/android/net/ip/IpClient.java
@@ -1429,15 +1429,15 @@ public class IpClient extends StateMachine {
@Override
public void enter() {
+ ApfFilter.ApfConfiguration apfConfig = new ApfFilter.ApfConfiguration();
+ apfConfig.apfCapabilities = mConfiguration.mApfCapabilities;
+ apfConfig.multicastFilter = mMulticastFiltering;
// Get the Configuration for ApfFilter from Context
- final boolean filter802_3Frames =
+ apfConfig.ieee802_3Filter =
mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
-
- final int[] ethTypeBlackList = mContext.getResources().getIntArray(
- R.array.config_apfEthTypeBlackList);
-
- mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface,
- mCallback, mMulticastFiltering, filter802_3Frames, ethTypeBlackList);
+ apfConfig.ethTypeBlackList =
+ mContext.getResources().getIntArray(R.array.config_apfEthTypeBlackList);
+ mApfFilter = ApfFilter.maybeCreate(apfConfig, mNetworkInterface, mCallback);
// TODO: investigate the effects of any multicast filtering racing/interfering with the
// rest of this IP configuration startup.
if (mApfFilter == null) {
diff --git a/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java b/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java
index 209fdf1605a6..9c805446fb9b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java
@@ -67,7 +67,8 @@ import java.util.ArrayList;
// TODO: Test non-Activity windows.
// TODO: Test secondary display.
@SmallTest
-@Presubmit
+// TODO(b/68957554)
+//@Presubmit
@RunWith(AndroidJUnit4.class)
public class ScreenDecorWindowTests {
diff --git a/test-runner/Android.mk b/test-runner/Android.mk
index 29a95e65dbf3..3367aba5c0f9 100644
--- a/test-runner/Android.mk
+++ b/test-runner/Android.mk
@@ -50,6 +50,9 @@ LOCAL_MODULE:= repackaged.android.test.runner
include $(BUILD_STATIC_JAVA_LIBRARY)
+# For unbundled build we'll use the prebuilt jar from prebuilts/sdk.
+ifeq (,$(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)))
+
# Generate the stub source files for android.test.runner.stubs
# ============================================================
include $(CLEAR_VARS)
@@ -149,6 +152,8 @@ update-android-test-runner-api: $(ANDROID_TEST_RUNNER_OUTPUT_API_FILE) | $(ACP)
@echo Copying removed.txt
$(hide) $(ACP) $(ANDROID_TEST_RUNNER_OUTPUT_REMOVED_API_FILE) $(ANDROID_TEST_RUNNER_REMOVED_API_FILE)
+endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true
+
# Build the android.test.mock library
# ===================================
include $(CLEAR_VARS)
@@ -161,6 +166,9 @@ LOCAL_MODULE:= android.test.mock
include $(BUILD_JAVA_LIBRARY)
+# For unbundled build we'll use the prebuilt jar from prebuilts/sdk.
+ifeq (,$(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)))
+
# Generate the stub source files for android.test.mock.stubs
# ==========================================================
include $(CLEAR_VARS)
@@ -257,3 +265,5 @@ include $(BUILD_STATIC_JAVA_LIBRARY)
# additionally, build unit tests in a separate .apk
include $(call all-makefiles-under,$(LOCAL_PATH))
+
+endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true
diff --git a/tests/UiBench/AndroidManifest.xml b/tests/UiBench/AndroidManifest.xml
index f2de7db936e1..c6b4a54f3b0b 100644
--- a/tests/UiBench/AndroidManifest.xml
+++ b/tests/UiBench/AndroidManifest.xml
@@ -92,6 +92,15 @@
</intent-filter>
</activity>
<activity
+ android:name=".TrivialAnimationActivityWideGamut"
+ android:label="General/Trivial Animation (Wide Gamut)"
+ android:colorMode="wideColorGamut">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="com.android.test.uibench.TEST" />
+ </intent-filter>
+ </activity>
+ <activity
android:name=".TrivialListActivity"
android:label="General/Trivial ListView" >
<intent-filter>
diff --git a/tests/UiBench/src/com/android/test/uibench/TrivialAnimationActivityWideGamut.java b/tests/UiBench/src/com/android/test/uibench/TrivialAnimationActivityWideGamut.java
new file mode 100644
index 000000000000..c492753d8b7a
--- /dev/null
+++ b/tests/UiBench/src/com/android/test/uibench/TrivialAnimationActivityWideGamut.java
@@ -0,0 +1,18 @@
+/*
+ * 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.test.uibench;
+
+public class TrivialAnimationActivityWideGamut extends TrivialAnimationActivity { }
diff --git a/tests/net/java/android/net/apf/ApfTest.java b/tests/net/java/android/net/apf/ApfTest.java
index 99a2ad942003..725ddb9ccbca 100644
--- a/tests/net/java/android/net/apf/ApfTest.java
+++ b/tests/net/java/android/net/apf/ApfTest.java
@@ -29,9 +29,7 @@ import static org.mockito.Mockito.verify;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkUtils;
-import android.net.apf.ApfCapabilities;
-import android.net.apf.ApfFilter;
-import android.net.apf.ApfGenerator;
+import android.net.apf.ApfFilter.ApfConfiguration;
import android.net.apf.ApfGenerator.IllegalInstructionException;
import android.net.apf.ApfGenerator.Register;
import android.net.ip.IpManager;
@@ -99,12 +97,24 @@ public class ApfTest {
// least the minimum packet size.
private final static int MIN_PKT_SIZE = 15;
+ private static final ApfCapabilities MOCK_APF_CAPABILITIES =
+ new ApfCapabilities(2, 1700, ARPHRD_ETHER);
+
private final static boolean DROP_MULTICAST = true;
private final static boolean ALLOW_MULTICAST = false;
private final static boolean DROP_802_3_FRAMES = true;
private final static boolean ALLOW_802_3_FRAMES = false;
+ private static ApfConfiguration getDefaultConfig() {
+ ApfFilter.ApfConfiguration config = new ApfConfiguration();
+ config.apfCapabilities = MOCK_APF_CAPABILITIES;
+ config.multicastFilter = ALLOW_MULTICAST;
+ config.ieee802_3Filter = ALLOW_802_3_FRAMES;
+ config.ethTypeBlackList = new int[0];
+ return config;
+ }
+
private static String label(int code) {
switch (code) {
case PASS: return "PASS";
@@ -619,15 +629,13 @@ public class ApfTest {
private static class TestApfFilter extends ApfFilter {
public final static byte[] MOCK_MAC_ADDR = {1,2,3,4,5,6};
- private FileDescriptor mWriteSocket;
+ private FileDescriptor mWriteSocket;
private final long mFixedTimeMs = SystemClock.elapsedRealtime();
- public TestApfFilter(IpManager.Callback ipManagerCallback, boolean multicastFilter,
- boolean ieee802_3Filter, int[] ethTypeBlackList,
+ public TestApfFilter(ApfConfiguration config, IpManager.Callback ipManagerCallback,
IpConnectivityLog log) throws Exception {
- super(new ApfCapabilities(2, 1700, ARPHRD_ETHER), NetworkInterface.getByName("lo"),
- ipManagerCallback, multicastFilter, ieee802_3Filter, ethTypeBlackList, log);
+ super(config, NetworkInterface.getByName("lo"), ipManagerCallback, log);
}
// Pretend an RA packet has been received and show it to ApfFilter.
@@ -755,10 +763,10 @@ public class ApfTest {
LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
LinkProperties lp = new LinkProperties();
lp.addLinkAddress(link);
- final int[] ethTypeBlackList = {};
- ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST,
- ALLOW_802_3_FRAMES, ethTypeBlackList, mLog);
+ ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ TestApfFilter apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
apfFilter.setLinkProperties(lp);
byte[] program = ipManagerCallback.getApfProgram();
@@ -808,10 +816,9 @@ public class ApfTest {
@Test
public void testApfFilterIPv6() throws Exception {
- final int[] ethTypeBlackList = {};
MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
- ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST,
- ALLOW_802_3_FRAMES, ethTypeBlackList, mLog);
+ ApfConfiguration config = getDefaultConfig();
+ TestApfFilter apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
byte[] program = ipManagerCallback.getApfProgram();
// Verify empty IPv6 packet is passed
@@ -846,15 +853,15 @@ public class ApfTest {
final byte[] broadcastIpv4Addr = {(byte)192,0,2,(byte)255};
final byte[] multicastIpv4Addr = {(byte)224,0,0,1};
final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb};
- final int[] ethTypeBlackList = {};
MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
LinkAddress link = new LinkAddress(InetAddress.getByAddress(unicastIpv4Addr), 24);
LinkProperties lp = new LinkProperties();
lp.addLinkAddress(link);
- ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST,
- DROP_802_3_FRAMES, ethTypeBlackList, mLog);
+ ApfConfiguration config = getDefaultConfig();
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
apfFilter.setLinkProperties(lp);
byte[] program = ipManagerCallback.getApfProgram();
@@ -916,8 +923,9 @@ public class ApfTest {
// Verify it can be initialized to on
ipManagerCallback.resetApfProgramWait();
apfFilter.shutdown();
- apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST,
- DROP_802_3_FRAMES, ethTypeBlackList, mLog);
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
apfFilter.setLinkProperties(lp);
program = ipManagerCallback.getApfProgram();
assertDrop(program, mcastv4packet.array());
@@ -938,10 +946,9 @@ public class ApfTest {
LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
LinkProperties lp = new LinkProperties();
lp.addLinkAddress(link);
- final int[] ethTypeBlackList = {};
- ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST,
- ALLOW_802_3_FRAMES, ethTypeBlackList, mLog);
+ ApfConfiguration config = getDefaultConfig();
+ TestApfFilter apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
apfFilter.setLinkProperties(lp);
byte[] program = ipManagerCallback.getApfProgram();
@@ -962,8 +969,8 @@ public class ApfTest {
// Now turn on the filter
ipManagerCallback.resetApfProgramWait();
apfFilter.shutdown();
- apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST,
- DROP_802_3_FRAMES, ethTypeBlackList, mLog);
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
apfFilter.setLinkProperties(lp);
program = ipManagerCallback.getApfProgram();
@@ -993,8 +1000,8 @@ public class ApfTest {
final int[] ipv4BlackList = {ETH_P_IP};
final int[] ipv4Ipv6BlackList = {ETH_P_IP, ETH_P_IPV6};
- ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST,
- ALLOW_802_3_FRAMES, emptyBlackList, mLog);
+ ApfConfiguration config = getDefaultConfig();
+ TestApfFilter apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
apfFilter.setLinkProperties(lp);
byte[] program = ipManagerCallback.getApfProgram();
@@ -1015,8 +1022,8 @@ public class ApfTest {
// Now add IPv4 to the black list
ipManagerCallback.resetApfProgramWait();
apfFilter.shutdown();
- apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST,
- ALLOW_802_3_FRAMES, ipv4BlackList, mLog);
+ config.ethTypeBlackList = ipv4BlackList;
+ apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
apfFilter.setLinkProperties(lp);
program = ipManagerCallback.getApfProgram();
@@ -1031,8 +1038,8 @@ public class ApfTest {
// Now let us have both IPv4 and IPv6 in the black list
ipManagerCallback.resetApfProgramWait();
apfFilter.shutdown();
- apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST,
- ALLOW_802_3_FRAMES, ipv4Ipv6BlackList, mLog);
+ config.ethTypeBlackList = ipv4Ipv6BlackList;
+ apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
apfFilter.setLinkProperties(lp);
program = ipManagerCallback.getApfProgram();
@@ -1070,10 +1077,11 @@ public class ApfTest {
@Test
public void testApfFilterArp() throws Exception {
- final int[] ethTypeBlackList = {};
MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
- ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST,
- DROP_802_3_FRAMES, ethTypeBlackList, mLog);
+ ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
// Verify initially ARP request filter is off, and GARP filter is on.
verifyArpFilter(ipManagerCallback.getApfProgram(), PASS);
@@ -1194,9 +1202,10 @@ public class ApfTest {
@Test
public void testApfFilterRa() throws Exception {
MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
- final int[] ethTypeBlackList = {};
- TestApfFilter apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST,
- DROP_802_3_FRAMES, ethTypeBlackList, mLog);
+ ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
byte[] program = ipManagerCallback.getApfProgram();
final int ROUTER_LIFETIME = 1000;
@@ -1338,10 +1347,11 @@ public class ApfTest {
public void testRaParsing() throws Exception {
final int maxRandomPacketSize = 512;
final Random r = new Random();
- final int[] ethTypeBlackList = {};
MockIpManagerCallback cb = new MockIpManagerCallback();
- TestApfFilter apfFilter = new TestApfFilter(cb, DROP_MULTICAST,
- DROP_802_3_FRAMES, ethTypeBlackList, mLog);
+ ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(config, cb, mLog);
for (int i = 0; i < 1000; i++) {
byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
r.nextBytes(packet);
@@ -1358,10 +1368,11 @@ public class ApfTest {
public void testRaProcessing() throws Exception {
final int maxRandomPacketSize = 512;
final Random r = new Random();
- final int[] ethTypeBlackList = {};
MockIpManagerCallback cb = new MockIpManagerCallback();
- TestApfFilter apfFilter = new TestApfFilter(cb, DROP_MULTICAST,
- DROP_802_3_FRAMES, ethTypeBlackList, mLog);
+ ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(config, cb, mLog);
for (int i = 0; i < 1000; i++) {
byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
r.nextBytes(packet);
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 058504d7520d..ae67f617d3d7 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -16,6 +16,7 @@
toolSources = [
"cmd/Compile.cpp",
+ "cmd/Convert.cpp",
"cmd/Diff.cpp",
"cmd/Dump.cpp",
"cmd/Link.cpp",
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index ae32ee965b48..921d8531cbd3 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -21,43 +21,139 @@
#include "format/Archive.h"
#include "format/binary/TableFlattener.h"
#include "format/binary/XmlFlattener.h"
+#include "format/proto/ProtoDeserialize.h"
#include "io/BigBufferStream.h"
#include "io/Util.h"
#include "xml/XmlDom.h"
-namespace aapt {
+using ::aapt::io::IFile;
+using ::aapt::io::IFileCollection;
+using ::aapt::xml::XmlResource;
+using ::android::StringPiece;
+using ::std::unique_ptr;
-using xml::XmlResource;
+namespace aapt {
-std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(IAaptContext* context,
- const android::StringPiece& path) {
+std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(const StringPiece& path, IDiagnostics* diag) {
Source source(path);
std::string error;
std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::Create(path, &error);
- if (!apk) {
- context->GetDiagnostics()->Error(DiagMessage(source) << error);
+ if (apk == nullptr) {
+ diag->Error(DiagMessage(path) << "failed opening zip: " << error);
+ return {};
+ }
+
+ if (apk->FindFile("resources.arsc") != nullptr) {
+ return LoadBinaryApkFromFileCollection(source, std::move(apk), diag);
+ } else if (apk->FindFile("resources.pb") != nullptr) {
+ return LoadProtoApkFromFileCollection(source, std::move(apk), diag);
+ }
+ diag->Error(DiagMessage(path) << "no resource table found");
+ return {};
+}
+
+std::unique_ptr<LoadedApk> LoadedApk::LoadProtoApkFromFileCollection(
+ const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) {
+ io::IFile* table_file = collection->FindFile(kProtoResourceTablePath);
+ if (table_file == nullptr) {
+ diag->Error(DiagMessage(source) << "failed to find " << kProtoResourceTablePath);
return {};
}
- io::IFile* file = apk->FindFile("resources.arsc");
- if (!file) {
- context->GetDiagnostics()->Error(DiagMessage(source) << "no resources.arsc found");
+ std::unique_ptr<io::InputStream> in = table_file->OpenInputStream();
+ if (in == nullptr) {
+ diag->Error(DiagMessage(source) << "failed to open " << kProtoResourceTablePath);
return {};
}
- std::unique_ptr<io::IData> data = file->OpenAsData();
- if (!data) {
- context->GetDiagnostics()->Error(DiagMessage(source) << "could not open resources.arsc");
+ pb::ResourceTable pb_table;
+ io::ZeroCopyInputAdaptor adaptor(in.get());
+ if (!pb_table.ParseFromZeroCopyStream(&adaptor)) {
+ diag->Error(DiagMessage(source) << "failed to read " << kProtoResourceTablePath);
return {};
}
+ std::string error;
std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
- BinaryResourceParser parser(context, table.get(), source, data->data(), data->size(), apk.get());
+ if (!DeserializeTableFromPb(pb_table, collection.get(), table.get(), &error)) {
+ diag->Error(DiagMessage(source)
+ << "failed to deserialize " << kProtoResourceTablePath << ": " << error);
+ return {};
+ }
+
+ io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath);
+ if (manifest_file == nullptr) {
+ diag->Error(DiagMessage(source) << "failed to find " << kAndroidManifestPath);
+ return {};
+ }
+
+ std::unique_ptr<io::InputStream> manifest_in = manifest_file->OpenInputStream();
+ if (manifest_in == nullptr) {
+ diag->Error(DiagMessage(source) << "failed to open " << kAndroidManifestPath);
+ return {};
+ }
+
+ pb::XmlNode pb_node;
+ io::ZeroCopyInputAdaptor manifest_adaptor(manifest_in.get());
+ if (!pb_node.ParseFromZeroCopyStream(&manifest_adaptor)) {
+ diag->Error(DiagMessage(source) << "failed to read proto " << kAndroidManifestPath);
+ return {};
+ }
+
+ std::unique_ptr<xml::XmlResource> manifest = DeserializeXmlResourceFromPb(pb_node, &error);
+ if (manifest == nullptr) {
+ diag->Error(DiagMessage(source)
+ << "failed to deserialize proto " << kAndroidManifestPath << ": " << error);
+ return {};
+ }
+ return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table),
+ std::move(manifest));
+}
+
+std::unique_ptr<LoadedApk> LoadedApk::LoadBinaryApkFromFileCollection(
+ const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) {
+ io::IFile* table_file = collection->FindFile(kApkResourceTablePath);
+ if (table_file == nullptr) {
+ diag->Error(DiagMessage(source) << "failed to find " << kApkResourceTablePath);
+
+ return {};
+ }
+
+ std::unique_ptr<io::IData> data = table_file->OpenAsData();
+ if (data == nullptr) {
+ diag->Error(DiagMessage(source) << "failed to open " << kApkResourceTablePath);
+ return {};
+ }
+
+ std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
+ BinaryResourceParser parser(diag, table.get(), source, data->data(), data->size(),
+ collection.get());
if (!parser.Parse()) {
return {};
}
- return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table));
+ io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath);
+ if (manifest_file == nullptr) {
+ diag->Error(DiagMessage(source) << "failed to find " << kAndroidManifestPath);
+ return {};
+ }
+
+ std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
+ if (manifest_data == nullptr) {
+ diag->Error(DiagMessage(source) << "failed to open " << kAndroidManifestPath);
+ return {};
+ }
+
+ std::string error;
+ std::unique_ptr<xml::XmlResource> manifest =
+ xml::Inflate(manifest_data->data(), manifest_data->size(), &error);
+ if (manifest == nullptr) {
+ diag->Error(DiagMessage(source)
+ << "failed to parse binary " << kAndroidManifestPath << ": " << error);
+ return {};
+ }
+ return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table),
+ std::move(manifest));
}
bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
@@ -148,26 +244,4 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table
return true;
}
-std::unique_ptr<xml::XmlResource> LoadedApk::InflateManifest(IAaptContext* context) {
- IDiagnostics* diag = context->GetDiagnostics();
-
- io::IFile* manifest_file = GetFileCollection()->FindFile("AndroidManifest.xml");
- if (manifest_file == nullptr) {
- diag->Error(DiagMessage(source_) << "no AndroidManifest.xml found");
- return {};
- }
-
- std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
- if (manifest_data == nullptr) {
- diag->Error(DiagMessage(manifest_file->GetSource()) << "could not open AndroidManifest.xml");
- return {};
- }
-
- std::unique_ptr<xml::XmlResource> manifest =
- xml::Inflate(manifest_data->data(), manifest_data->size(), diag, manifest_file->GetSource());
- if (manifest == nullptr) {
- diag->Error(DiagMessage() << "failed to read binary AndroidManifest.xml");
- }
- return manifest;
-}
} // namespace aapt
diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h
index d2dd5cf2bc67..ef97de301ad5 100644
--- a/tools/aapt2/LoadedApk.h
+++ b/tools/aapt2/LoadedApk.h
@@ -29,20 +29,41 @@
namespace aapt {
+constexpr static const char kApkResourceTablePath[] = "resources.arsc";
+constexpr static const char kProtoResourceTablePath[] = "resources.pb";
+constexpr static const char kAndroidManifestPath[] = "AndroidManifest.xml";
+
// Info about an APK loaded in memory.
class LoadedApk {
public:
- LoadedApk(
- const Source& source,
- std::unique_ptr<io::IFileCollection> apk,
- std::unique_ptr<ResourceTable> table)
- : source_(source), apk_(std::move(apk)), table_(std::move(table)) {}
- virtual ~LoadedApk() = default;
+ // Loads both binary and proto APKs from disk.
+ static std::unique_ptr<LoadedApk> LoadApkFromPath(const ::android::StringPiece& path,
+ IDiagnostics* diag);
+
+ // Loads a proto APK from the given file collection.
+ static std::unique_ptr<LoadedApk> LoadProtoApkFromFileCollection(
+ const Source& source, std::unique_ptr<io::IFileCollection> collection, IDiagnostics* diag);
+
+ // Loads a binary APK from the given file collection.
+ static std::unique_ptr<LoadedApk> LoadBinaryApkFromFileCollection(
+ const Source& source, std::unique_ptr<io::IFileCollection> collection, IDiagnostics* diag);
+
+ LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk,
+ std::unique_ptr<ResourceTable> table, std::unique_ptr<xml::XmlResource> manifest)
+ : source_(source),
+ apk_(std::move(apk)),
+ table_(std::move(table)),
+ manifest_(std::move(manifest)) {
+ }
io::IFileCollection* GetFileCollection() {
return apk_.get();
}
+ const ResourceTable* GetResourceTable() const {
+ return table_.get();
+ }
+
ResourceTable* GetResourceTable() {
return table_.get();
}
@@ -51,6 +72,10 @@ class LoadedApk {
return source_;
}
+ const xml::XmlResource* GetManifest() const {
+ return manifest_.get();
+ }
+
/**
* Writes the APK on disk at the given path, while also removing the resource
* files that are not referenced in the resource table.
@@ -71,11 +96,6 @@ class LoadedApk {
const TableFlattenerOptions& options, FilterChain* filters,
IArchiveWriter* writer, xml::XmlResource* manifest = nullptr);
- /** Inflates the AndroidManifest.xml file from the APK. */
- std::unique_ptr<xml::XmlResource> InflateManifest(IAaptContext* context);
-
- static std::unique_ptr<LoadedApk> LoadApkFromPath(IAaptContext* context,
- const android::StringPiece& path);
private:
DISALLOW_COPY_AND_ASSIGN(LoadedApk);
@@ -83,6 +103,7 @@ class LoadedApk {
Source source_;
std::unique_ptr<io::IFileCollection> apk_;
std::unique_ptr<ResourceTable> table_;
+ std::unique_ptr<xml::XmlResource> manifest_;
};
} // namespace aapt
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 36ab30c7fb6e..808b29cfd844 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -50,7 +50,7 @@ static void PrintVersion() {
}
static void PrintUsage() {
- std::cerr << "\nusage: aapt2 [compile|link|dump|diff|optimize|version] ..." << std::endl;
+ std::cerr << "\nusage: aapt2 [compile|link|dump|diff|optimize|convert|version] ..." << std::endl;
}
extern int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics);
@@ -58,6 +58,7 @@ extern int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics)
extern int Dump(const std::vector<StringPiece>& args);
extern int Diff(const std::vector<StringPiece>& args);
extern int Optimize(const std::vector<StringPiece>& args);
+extern int Convert(const std::vector<StringPiece>& args);
static int ExecuteCommand(const StringPiece& command, const std::vector<StringPiece>& args,
IDiagnostics* diagnostics) {
@@ -71,6 +72,8 @@ static int ExecuteCommand(const StringPiece& command, const std::vector<StringPi
return Diff(args);
} else if (command == "optimize") {
return Optimize(args);
+ } else if (command == "convert") {
+ return Convert(args);
} else if (command == "version") {
PrintVersion();
return 0;
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
new file mode 100644
index 000000000000..89ae9e82f603
--- /dev/null
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -0,0 +1,228 @@
+/*
+ * 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.
+ */
+
+#include <vector>
+
+#include "android-base/macros.h"
+#include "androidfw/StringPiece.h"
+
+#include "Flags.h"
+#include "LoadedApk.h"
+#include "ValueVisitor.h"
+#include "cmd/Util.h"
+#include "format/binary/TableFlattener.h"
+#include "format/binary/XmlFlattener.h"
+#include "format/proto/ProtoDeserialize.h"
+#include "io/BigBufferStream.h"
+#include "io/Util.h"
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+
+using ::android::StringPiece;
+using ::std::unique_ptr;
+using ::std::vector;
+
+namespace aapt {
+
+static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml,
+ const std::string& entry_path, bool utf16, IArchiveWriter* writer) {
+ BigBuffer buffer(4096);
+ XmlFlattenerOptions options = {};
+ options.use_utf16 = utf16;
+ XmlFlattener flattener(&buffer, options);
+ if (!flattener.Consume(context, &xml)) {
+ return false;
+ }
+ io::BigBufferInputStream input_stream(&buffer);
+ return io::CopyInputStreamToArchive(context, &input_stream, entry_path, ArchiveEntry::kCompress,
+ writer);
+}
+
+bool ConvertProtoApkToBinaryApk(IAaptContext* context, unique_ptr<LoadedApk> apk,
+ const TableFlattenerOptions& options, IArchiveWriter* writer) {
+ if (!FlattenXml(context, *apk->GetManifest(), kAndroidManifestPath, true /*utf16*/, writer)) {
+ return false;
+ }
+
+ BigBuffer buffer(4096);
+ TableFlattener table_flattener(options, &buffer);
+ if (!table_flattener.Consume(context, apk->GetResourceTable())) {
+ return false;
+ }
+
+ io::BigBufferInputStream input_stream(&buffer);
+ if (!io::CopyInputStreamToArchive(context, &input_stream, kApkResourceTablePath,
+ ArchiveEntry::kAlign, writer)) {
+ return false;
+ }
+
+ for (const auto& package : apk->GetResourceTable()->packages) {
+ for (const auto& type : package->types) {
+ for (const auto& entry : type->entries) {
+ for (const auto& config_value : entry->values) {
+ const FileReference* file = ValueCast<FileReference>(config_value->value.get());
+ if (file != nullptr) {
+ if (file->file == nullptr) {
+ context->GetDiagnostics()->Warn(DiagMessage(apk->GetSource())
+ << "no file associated with " << *file);
+ return false;
+ }
+
+ if (file->type == ResourceFile::Type::kProtoXml) {
+ unique_ptr<io::InputStream> in = file->file->OpenInputStream();
+ if (in == nullptr) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to open file " << *file->path);
+ return false;
+ }
+
+ pb::XmlNode pb_node;
+ io::ZeroCopyInputAdaptor adaptor(in.get());
+ if (!pb_node.ParseFromZeroCopyStream(&adaptor)) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to parse proto XML " << *file->path);
+ return false;
+ }
+
+ std::string error;
+ unique_ptr<xml::XmlResource> xml = DeserializeXmlResourceFromPb(pb_node, &error);
+ if (xml == nullptr) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to deserialize proto XML "
+ << *file->path << ": " << error);
+ return false;
+ }
+
+ if (!FlattenXml(context, *xml, *file->path, false /*utf16*/, writer)) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to serialize XML " << *file->path);
+ return false;
+ }
+ } else {
+ if (!io::CopyFileToArchive(context, file->file, *file->path,
+ file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u,
+ writer)) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to copy file " << *file->path);
+ return false;
+ }
+ }
+
+ } // file
+ } // config_value
+ } // entry
+ } // type
+ } // package
+ return true;
+}
+
+class Context : public IAaptContext {
+ public:
+ Context() : mangler_({}), symbols_(&mangler_) {
+ }
+
+ PackageType GetPackageType() override {
+ return PackageType::kApp;
+ }
+
+ SymbolTable* GetExternalSymbols() override {
+ return &symbols_;
+ }
+
+ IDiagnostics* GetDiagnostics() override {
+ return &diag_;
+ }
+
+ const std::string& GetCompilationPackage() override {
+ return package_;
+ }
+
+ uint8_t GetPackageId() override {
+ // Nothing should call this.
+ UNIMPLEMENTED(FATAL) << "PackageID should not be necessary";
+ return 0;
+ }
+
+ NameMangler* GetNameMangler() override {
+ UNIMPLEMENTED(FATAL);
+ return nullptr;
+ }
+
+ bool IsVerbose() override {
+ return verbose_;
+ }
+
+ int GetMinSdkVersion() override {
+ return 0u;
+ }
+
+ bool verbose_ = false;
+ std::string package_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Context);
+
+ NameMangler mangler_;
+ SymbolTable symbols_;
+ StdErrDiagnostics diag_;
+};
+
+int Convert(const vector<StringPiece>& args) {
+ Context context;
+ std::string output_path;
+ TableFlattenerOptions options;
+ Flags flags =
+ Flags()
+ .RequiredFlag("-o", "Output path", &output_path)
+ .OptionalSwitch("--enable-sparse-encoding",
+ "Enables encoding sparse entries using a binary search tree.\n"
+ "This decreases APK size at the cost of resource retrieval performance.",
+ &options.use_sparse_entries)
+ .OptionalSwitch("-v", "Enables verbose logging", &context.verbose_);
+ if (!flags.Parse("aapt2 convert", args, &std::cerr)) {
+ return 1;
+ }
+
+ if (flags.GetArgs().size() != 1) {
+ std::cerr << "must supply a single proto APK\n";
+ flags.Usage("aapt2 convert", &std::cerr);
+ return 1;
+ }
+
+ const StringPiece& path = flags.GetArgs()[0];
+ unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics());
+ if (apk == nullptr) {
+ context.GetDiagnostics()->Error(DiagMessage(path) << "failed to load APK");
+ return 1;
+ }
+
+ Maybe<AppInfo> app_info =
+ ExtractAppInfoFromBinaryManifest(*apk->GetManifest(), context.GetDiagnostics());
+ if (!app_info) {
+ return 1;
+ }
+
+ context.package_ = app_info.value().package;
+
+ unique_ptr<IArchiveWriter> writer =
+ CreateZipFileArchiveWriter(context.GetDiagnostics(), output_path);
+ if (writer == nullptr) {
+ return 1;
+ }
+ return ConvertProtoApkToBinaryApk(&context, std::move(apk), options, writer.get()) ? 0 : 1;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index 625c47cec939..fc1f1d6342ae 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -357,8 +357,9 @@ int Diff(const std::vector<StringPiece>& args) {
return 1;
}
- std::unique_ptr<LoadedApk> apk_a = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[0]);
- std::unique_ptr<LoadedApk> apk_b = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[1]);
+ IDiagnostics* diag = context.GetDiagnostics();
+ std::unique_ptr<LoadedApk> apk_a = LoadedApk::LoadApkFromPath(flags.GetArgs()[0], diag);
+ std::unique_ptr<LoadedApk> apk_b = LoadedApk::LoadApkFromPath(flags.GetArgs()[1], diag);
if (!apk_a || !apk_b) {
return 1;
}
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index 090c3fbc5731..bc8f1dcb2289 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -78,7 +78,7 @@ static bool TryDumpFile(IAaptContext* context, const std::string& file_path) {
return false;
}
- if (!DeserializeTableFromPb(pb_table, &table, &err)) {
+ if (!DeserializeTableFromPb(pb_table, zip.get(), &table, &err)) {
context->GetDiagnostics()->Error(DiagMessage(file_path)
<< "failed to parse table: " << err);
return false;
@@ -90,7 +90,8 @@ static bool TryDumpFile(IAaptContext* context, const std::string& file_path) {
return false;
}
- BinaryResourceParser parser(context, &table, Source(file_path), data->data(), data->size());
+ BinaryResourceParser parser(context->GetDiagnostics(), &table, Source(file_path),
+ data->data(), data->size());
if (!parser.Parse()) {
return false;
}
@@ -129,7 +130,7 @@ static bool TryDumpFile(IAaptContext* context, const std::string& file_path) {
ResourceTable table;
err.clear();
- if (!DeserializeTableFromPb(pb_table, &table, &err)) {
+ if (!DeserializeTableFromPb(pb_table, nullptr /*files*/, &table, &err)) {
context->GetDiagnostics()->Error(DiagMessage(file_path)
<< "failed to parse table: " << err);
continue;
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index e0dae1b7ed4e..b372923e4010 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -29,6 +29,7 @@
#include "AppInfo.h"
#include "Debug.h"
#include "Flags.h"
+#include "LoadedApk.h"
#include "Locale.h"
#include "NameMangler.h"
#include "ResourceUtils.h"
@@ -39,7 +40,6 @@
#include "filter/ConfigFilter.h"
#include "format/Archive.h"
#include "format/Container.h"
-#include "format/binary/BinaryResourceParser.h"
#include "format/binary/TableFlattener.h"
#include "format/binary/XmlFlattener.h"
#include "format/proto/ProtoDeserialize.h"
@@ -71,9 +71,6 @@ using ::android::base::StringPrintf;
namespace aapt {
-constexpr static const char kApkResourceTablePath[] = "resources.arsc";
-constexpr static const char kProtoResourceTablePath[] = "resources.pb";
-
enum class OutputFormat {
kApk,
kProto,
@@ -298,23 +295,6 @@ static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res,
return false;
}
-static std::unique_ptr<ResourceTable> LoadTableFromPb(const Source& source, const void* data,
- size_t len, IDiagnostics* diag) {
- pb::ResourceTable pb_table;
- if (!pb_table.ParseFromArray(data, len)) {
- diag->Error(DiagMessage(source) << "invalid compiled table");
- return {};
- }
-
- std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
- std::string error;
- if (!DeserializeTableFromPb(pb_table, table.get(), &error)) {
- diag->Error(DiagMessage(source) << "invalid compiled table: " << error);
- return {};
- }
- return table;
-}
-
// Inflates an XML file from the source path.
static std::unique_ptr<xml::XmlResource> LoadXml(const std::string& path, IDiagnostics* diag) {
FileInputStream fin(path);
@@ -587,7 +567,7 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv
pb::XmlNode pb_xml_node;
if (!pb_xml_node.ParseFromArray(data->data(), static_cast<int>(data->size()))) {
context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
- << "failed to parse proto xml");
+ << "failed to parse proto XML");
return false;
}
@@ -595,13 +575,15 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv
file_op.xml_to_flatten = DeserializeXmlResourceFromPb(pb_xml_node, &error);
if (file_op.xml_to_flatten == nullptr) {
context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
- << "failed to deserialize proto xml: " << error);
+ << "failed to deserialize proto XML: " << error);
return false;
}
} else {
- file_op.xml_to_flatten = xml::Inflate(data->data(), data->size(),
- context_->GetDiagnostics(), file->GetSource());
+ std::string error_str;
+ file_op.xml_to_flatten = xml::Inflate(data->data(), data->size(), &error_str);
if (file_op.xml_to_flatten == nullptr) {
+ context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
+ << "failed to parse binary XML: " << error_str);
return false;
}
}
@@ -748,22 +730,29 @@ class LinkCommand {
file_collection_(util::make_unique<io::FileCollection>()) {
}
- /**
- * Creates a SymbolTable that loads symbols from the various APKs and caches
- * the results for faster lookup.
- */
+ // Creates a SymbolTable that loads symbols from the various APKs.
bool LoadSymbolsFromIncludePaths() {
- std::unique_ptr<AssetManagerSymbolSource> asset_source =
- util::make_unique<AssetManagerSymbolSource>();
+ auto asset_source = util::make_unique<AssetManagerSymbolSource>();
for (const std::string& path : options_.include_paths) {
if (context_->IsVerbose()) {
context_->GetDiagnostics()->Note(DiagMessage() << "including " << path);
}
- // First try to load the file as a static lib.
- std::string error_str;
- std::unique_ptr<ResourceTable> include_static = LoadStaticLibrary(path, &error_str);
- if (include_static) {
+ std::string error;
+ auto zip_collection = io::ZipFileCollection::Create(path, &error);
+ if (zip_collection == nullptr) {
+ context_->GetDiagnostics()->Error(DiagMessage() << "failed to open APK: " << error);
+ return false;
+ }
+
+ if (zip_collection->FindFile(kProtoResourceTablePath) != nullptr) {
+ // Load this as a static library include.
+ std::unique_ptr<LoadedApk> static_apk = LoadedApk::LoadProtoApkFromFileCollection(
+ Source(path), std::move(zip_collection), context_->GetDiagnostics());
+ if (static_apk == nullptr) {
+ return false;
+ }
+
if (context_->GetPackageType() != PackageType::kStaticLib) {
// Can't include static libraries when not building a static library (they have no IDs
// assigned).
@@ -772,13 +761,15 @@ class LinkCommand {
return false;
}
- // If we are using --no-static-lib-packages, we need to rename the
- // package of this table to our compilation package.
+ ResourceTable* table = static_apk->GetResourceTable();
+
+ // If we are using --no-static-lib-packages, we need to rename the package of this table to
+ // our compilation package.
if (options_.no_static_lib_packages) {
// Since package names can differ, and multiple packages can exist in a ResourceTable,
// we place the requirement that all static libraries are built with the package
// ID 0x7f. So if one is not found, this is an error.
- if (ResourceTablePackage* pkg = include_static->FindPackageById(kAppPackageId)) {
+ if (ResourceTablePackage* pkg = table->FindPackageById(kAppPackageId)) {
pkg->name = context_->GetCompilationPackage();
} else {
context_->GetDiagnostics()->Error(DiagMessage(path)
@@ -788,19 +779,14 @@ class LinkCommand {
}
context_->GetExternalSymbols()->AppendSource(
- util::make_unique<ResourceTableSymbolSource>(include_static.get()));
-
- static_table_includes_.push_back(std::move(include_static));
-
- } else if (!error_str.empty()) {
- // We had an error with reading, so fail.
- context_->GetDiagnostics()->Error(DiagMessage(path) << error_str);
- return false;
- }
-
- if (!asset_source->AddAssetPath(path)) {
- context_->GetDiagnostics()->Error(DiagMessage(path) << "failed to load include path");
- return false;
+ util::make_unique<ResourceTableSymbolSource>(table));
+ static_library_includes_.push_back(std::move(static_apk));
+ } else {
+ if (!asset_source->AddAssetPath(path)) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed to load include path " << path);
+ return false;
+ }
}
}
@@ -1194,46 +1180,18 @@ class LinkCommand {
return true;
}
- std::unique_ptr<ResourceTable> LoadStaticLibrary(const std::string& input,
- std::string* out_error) {
- std::unique_ptr<io::ZipFileCollection> collection =
- io::ZipFileCollection::Create(input, out_error);
- if (!collection) {
- return {};
- }
- return LoadTablePbFromCollection(collection.get());
- }
-
- std::unique_ptr<ResourceTable> LoadTablePbFromCollection(io::IFileCollection* collection) {
- io::IFile* file = collection->FindFile(kProtoResourceTablePath);
- if (!file) {
- return {};
- }
-
- std::unique_ptr<io::IData> data = file->OpenAsData();
- return LoadTableFromPb(file->GetSource(), data->data(), data->size(),
- context_->GetDiagnostics());
- }
-
bool MergeStaticLibrary(const std::string& input, bool override) {
if (context_->IsVerbose()) {
context_->GetDiagnostics()->Note(DiagMessage() << "merging static library " << input);
}
- std::string error_str;
- std::unique_ptr<io::ZipFileCollection> collection =
- io::ZipFileCollection::Create(input, &error_str);
- if (!collection) {
- context_->GetDiagnostics()->Error(DiagMessage(input) << error_str);
- return false;
- }
-
- std::unique_ptr<ResourceTable> table = LoadTablePbFromCollection(collection.get());
- if (!table) {
+ std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(input, context_->GetDiagnostics());
+ if (apk == nullptr) {
context_->GetDiagnostics()->Error(DiagMessage(input) << "invalid static library");
return false;
}
+ ResourceTable* table = apk->GetResourceTable();
ResourceTablePackage* pkg = table->FindPackageById(kAppPackageId);
if (!pkg) {
context_->GetDiagnostics()->Error(DiagMessage(input) << "static library has no package");
@@ -1254,13 +1212,12 @@ class LinkCommand {
// Clear the package name, so as to make the resources look like they are coming from the
// local package.
pkg->name = "";
- result = table_merger_->Merge(Source(input), table.get(), override, collection.get());
+ result = table_merger_->Merge(Source(input), table, override);
} else {
// This is the proper way to merge libraries, where the package name is
// preserved and resource names are mangled.
- result =
- table_merger_->MergeAndMangle(Source(input), pkg->name, table.get(), collection.get());
+ result = table_merger_->MergeAndMangle(Source(input), pkg->name, table);
}
if (!result) {
@@ -1268,31 +1225,10 @@ class LinkCommand {
}
// Make sure to move the collection into the set of IFileCollections.
- collections_.push_back(std::move(collection));
+ merged_apks_.push_back(std::move(apk));
return true;
}
- bool MergeResourceTable(io::IFile* file, bool override) {
- if (context_->IsVerbose()) {
- context_->GetDiagnostics()->Note(DiagMessage() << "merging resource table "
- << file->GetSource());
- }
-
- std::unique_ptr<io::IData> data = file->OpenAsData();
- if (!data) {
- context_->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << "failed to open file");
- return false;
- }
-
- std::unique_ptr<ResourceTable> table =
- LoadTableFromPb(file->GetSource(), data->data(), data->size(), context_->GetDiagnostics());
- if (!table) {
- return false;
- }
-
- return table_merger_->Merge(file->GetSource(), table.get(), override);
- }
-
bool MergeCompiledFile(const ResourceFile& compiled_file, io::IFile* file, bool override) {
if (context_->IsVerbose()) {
context_->GetDiagnostics()->Note(DiagMessage()
@@ -1423,7 +1359,7 @@ class LinkCommand {
ResourceTable table;
std::string error;
- if (!DeserializeTableFromPb(pb_table, &table, &error)) {
+ if (!DeserializeTableFromPb(pb_table, nullptr /*files*/, &table, &error)) {
context_->GetDiagnostics()->Error(DiagMessage(src)
<< "failed to deserialize resource table: " << error);
return false;
@@ -1868,13 +1804,15 @@ class LinkCommand {
// A pointer to the FileCollection representing the filesystem (not archives).
std::unique_ptr<io::FileCollection> file_collection_;
- // A vector of IFileCollections. This is mainly here to keep ownership of the
+ // A vector of IFileCollections. This is mainly here to retain ownership of the
// collections.
std::vector<std::unique_ptr<io::IFileCollection>> collections_;
- // A vector of ResourceTables. This is here to retain ownership, so that the
- // SymbolTable can use these.
- std::vector<std::unique_ptr<ResourceTable>> static_table_includes_;
+ // The set of merged APKs. This is mainly here to retain ownership of the APKs.
+ std::vector<std::unique_ptr<LoadedApk>> merged_apks_;
+
+ // The set of included APKs (not merged). This is mainly here to retain ownership of the APKs.
+ std::vector<std::unique_ptr<LoadedApk>> static_library_includes_;
// The set of shared libraries being used, mapping their assigned package ID to package name.
std::map<size_t, std::string> shared_libs_;
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 44e148ee9732..688b6a7c3b3c 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -281,15 +281,14 @@ class OptimizeCommand {
OptimizeContext* context_;
};
-bool ExtractAppDataFromManifest(OptimizeContext* context, LoadedApk* apk,
+bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk,
OptimizeOptions* out_options) {
- std::unique_ptr<xml::XmlResource> manifest = apk->InflateManifest(context);
+ const xml::XmlResource* manifest = apk->GetManifest();
if (manifest == nullptr) {
return false;
}
- Maybe<AppInfo> app_info =
- ExtractAppInfoFromBinaryManifest(manifest.get(), context->GetDiagnostics());
+ Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*manifest, context->GetDiagnostics());
if (!app_info) {
context->GetDiagnostics()->Error(DiagMessage()
<< "failed to extract data from AndroidManifest.xml");
@@ -355,7 +354,7 @@ int Optimize(const std::vector<StringPiece>& args) {
}
const std::string& apk_path = flags.GetArgs()[0];
- std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(&context, apk_path);
+ std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics());
if (!apk) {
return 1;
}
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index 708bed80555b..d39f43e8bf08 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -214,9 +214,10 @@ std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info,
return doc;
}
-static Maybe<std::string> ExtractCompiledString(xml::Attribute* attr, std::string* out_error) {
- if (attr->compiled_value != nullptr) {
- String* compiled_str = ValueCast<String>(attr->compiled_value.get());
+static Maybe<std::string> ExtractCompiledString(const xml::Attribute& attr,
+ std::string* out_error) {
+ if (attr.compiled_value != nullptr) {
+ const String* compiled_str = ValueCast<String>(attr.compiled_value.get());
if (compiled_str != nullptr) {
if (!compiled_str->value->empty()) {
return *compiled_str->value;
@@ -230,16 +231,16 @@ static Maybe<std::string> ExtractCompiledString(xml::Attribute* attr, std::strin
}
// Fallback to the plain text value if there is one.
- if (!attr->value.empty()) {
- return attr->value;
+ if (!attr.value.empty()) {
+ return attr.value;
}
*out_error = "value is an empty string";
return {};
}
-static Maybe<uint32_t> ExtractCompiledInt(xml::Attribute* attr, std::string* out_error) {
- if (attr->compiled_value != nullptr) {
- BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr->compiled_value.get());
+static Maybe<uint32_t> ExtractCompiledInt(const xml::Attribute& attr, std::string* out_error) {
+ if (attr.compiled_value != nullptr) {
+ const BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr.compiled_value.get());
if (compiled_prim != nullptr) {
if (compiled_prim->value.dataType >= android::Res_value::TYPE_FIRST_INT &&
compiled_prim->value.dataType <= android::Res_value::TYPE_LAST_INT) {
@@ -251,19 +252,19 @@ static Maybe<uint32_t> ExtractCompiledInt(xml::Attribute* attr, std::string* out
}
// Fallback to the plain text value if there is one.
- Maybe<uint32_t> integer = ResourceUtils::ParseInt(attr->value);
+ Maybe<uint32_t> integer = ResourceUtils::ParseInt(attr.value);
if (integer) {
return integer;
}
std::stringstream error_msg;
- error_msg << "'" << attr->value << "' is not a valid integer";
+ error_msg << "'" << attr.value << "' is not a valid integer";
*out_error = error_msg.str();
return {};
}
-static Maybe<int> ExtractSdkVersion(xml::Attribute* attr, std::string* out_error) {
- if (attr->compiled_value != nullptr) {
- BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr->compiled_value.get());
+static Maybe<int> ExtractSdkVersion(const xml::Attribute& attr, std::string* out_error) {
+ if (attr.compiled_value != nullptr) {
+ const BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr.compiled_value.get());
if (compiled_prim != nullptr) {
if (compiled_prim->value.dataType >= android::Res_value::TYPE_FIRST_INT &&
compiled_prim->value.dataType <= android::Res_value::TYPE_LAST_INT) {
@@ -273,7 +274,7 @@ static Maybe<int> ExtractSdkVersion(xml::Attribute* attr, std::string* out_error
return {};
}
- String* compiled_str = ValueCast<String>(attr->compiled_value.get());
+ const String* compiled_str = ValueCast<String>(attr.compiled_value.get());
if (compiled_str != nullptr) {
Maybe<int> sdk_version = ResourceUtils::ParseSdkVersion(*compiled_str->value);
if (sdk_version) {
@@ -288,19 +289,20 @@ static Maybe<int> ExtractSdkVersion(xml::Attribute* attr, std::string* out_error
}
// Fallback to the plain text value if there is one.
- Maybe<int> sdk_version = ResourceUtils::ParseSdkVersion(attr->value);
+ Maybe<int> sdk_version = ResourceUtils::ParseSdkVersion(attr.value);
if (sdk_version) {
return sdk_version;
}
std::stringstream error_msg;
- error_msg << "'" << attr->value << "' is not a valid SDK version";
+ error_msg << "'" << attr.value << "' is not a valid SDK version";
*out_error = error_msg.str();
return {};
}
-Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(xml::XmlResource* xml_res, IDiagnostics* diag) {
+Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res,
+ IDiagnostics* diag) {
// Make sure the first element is <manifest> with package attribute.
- xml::Element* manifest_el = xml_res->root.get();
+ const xml::Element* manifest_el = xml_res.root.get();
if (manifest_el == nullptr) {
return {};
}
@@ -308,63 +310,63 @@ Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(xml::XmlResource* xml_res, IDiag
AppInfo app_info;
if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") {
- diag->Error(DiagMessage(xml_res->file.source) << "root tag must be <manifest>");
+ diag->Error(DiagMessage(xml_res.file.source) << "root tag must be <manifest>");
return {};
}
- xml::Attribute* package_attr = manifest_el->FindAttribute({}, "package");
+ const xml::Attribute* package_attr = manifest_el->FindAttribute({}, "package");
if (!package_attr) {
- diag->Error(DiagMessage(xml_res->file.source) << "<manifest> must have a 'package' attribute");
+ diag->Error(DiagMessage(xml_res.file.source) << "<manifest> must have a 'package' attribute");
return {};
}
std::string error_msg;
- Maybe<std::string> maybe_package = ExtractCompiledString(package_attr, &error_msg);
+ Maybe<std::string> maybe_package = ExtractCompiledString(*package_attr, &error_msg);
if (!maybe_package) {
- diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
+ diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number))
<< "invalid package name: " << error_msg);
return {};
}
app_info.package = maybe_package.value();
- if (xml::Attribute* version_code_attr =
+ if (const xml::Attribute* version_code_attr =
manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode")) {
- Maybe<uint32_t> maybe_code = ExtractCompiledInt(version_code_attr, &error_msg);
+ Maybe<uint32_t> maybe_code = ExtractCompiledInt(*version_code_attr, &error_msg);
if (!maybe_code) {
- diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
+ diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number))
<< "invalid android:versionCode: " << error_msg);
return {};
}
app_info.version_code = maybe_code.value();
}
- if (xml::Attribute* revision_code_attr =
+ if (const xml::Attribute* revision_code_attr =
manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode")) {
- Maybe<uint32_t> maybe_code = ExtractCompiledInt(revision_code_attr, &error_msg);
+ Maybe<uint32_t> maybe_code = ExtractCompiledInt(*revision_code_attr, &error_msg);
if (!maybe_code) {
- diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
+ diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number))
<< "invalid android:revisionCode: " << error_msg);
return {};
}
app_info.revision_code = maybe_code.value();
}
- if (xml::Attribute* split_name_attr = manifest_el->FindAttribute({}, "split")) {
- Maybe<std::string> maybe_split_name = ExtractCompiledString(split_name_attr, &error_msg);
+ if (const xml::Attribute* split_name_attr = manifest_el->FindAttribute({}, "split")) {
+ Maybe<std::string> maybe_split_name = ExtractCompiledString(*split_name_attr, &error_msg);
if (!maybe_split_name) {
- diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
+ diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number))
<< "invalid split name: " << error_msg);
return {};
}
app_info.split_name = maybe_split_name.value();
}
- if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
- if (xml::Attribute* min_sdk =
+ if (const xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
+ if (const xml::Attribute* min_sdk =
uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
- Maybe<int> maybe_sdk = ExtractSdkVersion(min_sdk, &error_msg);
+ Maybe<int> maybe_sdk = ExtractSdkVersion(*min_sdk, &error_msg);
if (!maybe_sdk) {
- diag->Error(DiagMessage(xml_res->file.source.WithLine(uses_sdk_el->line_number))
+ diag->Error(DiagMessage(xml_res.file.source.WithLine(uses_sdk_el->line_number))
<< "invalid android:minSdkVersion: " << error_msg);
return {};
}
diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h
index fd9b39c67380..7611c1526104 100644
--- a/tools/aapt2/cmd/Util.h
+++ b/tools/aapt2/cmd/Util.h
@@ -57,7 +57,8 @@ std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info,
const SplitConstraints& constraints);
// Extracts relevant info from the AndroidManifest.xml.
-Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(xml::XmlResource* xml_res, IDiagnostics* diag);
+Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res,
+ IDiagnostics* diag);
} // namespace aapt
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index 95eec4ae8248..66510b0e3a9b 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -73,29 +73,22 @@ class ReferenceIdToNameVisitor : public DescendingValueVisitor {
} // namespace
-BinaryResourceParser::BinaryResourceParser(IAaptContext* context, ResourceTable* table,
+BinaryResourceParser::BinaryResourceParser(IDiagnostics* diag, ResourceTable* table,
const Source& source, const void* data, size_t len,
io::IFileCollection* files)
- : context_(context),
- table_(table),
- source_(source),
- data_(data),
- data_len_(len),
- files_(files) {
+ : diag_(diag), table_(table), source_(source), data_(data), data_len_(len), files_(files) {
}
bool BinaryResourceParser::Parse() {
ResChunkPullParser parser(data_, data_len_);
if (!ResChunkPullParser::IsGoodEvent(parser.Next())) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "corrupt resources.arsc: " << parser.error());
+ diag_->Error(DiagMessage(source_) << "corrupt resources.arsc: " << parser.error());
return false;
}
if (parser.chunk()->type != android::RES_TABLE_TYPE) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << StringPrintf("unknown chunk of type 0x%02x",
+ diag_->Error(DiagMessage(source_) << StringPrintf("unknown chunk of type 0x%02x",
static_cast<int>(parser.chunk()->type)));
return false;
}
@@ -106,13 +99,12 @@ bool BinaryResourceParser::Parse() {
if (parser.Next() != ResChunkPullParser::Event::kEndDocument) {
if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
- context_->GetDiagnostics()->Warn(
- DiagMessage(source_) << "invalid chunk trailing RES_TABLE_TYPE: " << parser.error());
+ diag_->Warn(DiagMessage(source_)
+ << "invalid chunk trailing RES_TABLE_TYPE: " << parser.error());
} else {
- context_->GetDiagnostics()->Warn(
- DiagMessage(source_) << StringPrintf(
- "unexpected chunk of type 0x%02x trailing RES_TABLE_TYPE",
- static_cast<int>(parser.chunk()->type)));
+ diag_->Warn(DiagMessage(source_)
+ << StringPrintf("unexpected chunk of type 0x%02x trailing RES_TABLE_TYPE",
+ static_cast<int>(parser.chunk()->type)));
}
}
return true;
@@ -122,7 +114,7 @@ bool BinaryResourceParser::Parse() {
bool BinaryResourceParser::ParseTable(const ResChunk_header* chunk) {
const ResTable_header* table_header = ConvertTo<ResTable_header>(chunk);
if (!table_header) {
- context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_header chunk");
+ diag_->Error(DiagMessage(source_) << "corrupt ResTable_header chunk");
return false;
}
@@ -135,17 +127,15 @@ bool BinaryResourceParser::ParseTable(const ResChunk_header* chunk) {
status_t err =
value_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
if (err != NO_ERROR) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "corrupt string pool in ResTable: "
- << value_pool_.getError());
+ diag_->Error(DiagMessage(source_)
+ << "corrupt string pool in ResTable: " << value_pool_.getError());
return false;
}
// Reserve some space for the strings we are going to add.
table_->string_pool.HintWillAdd(value_pool_.size(), value_pool_.styleCount());
} else {
- context_->GetDiagnostics()->Warn(DiagMessage(source_)
- << "unexpected string pool in ResTable");
+ diag_->Warn(DiagMessage(source_) << "unexpected string pool in ResTable");
}
break;
@@ -156,16 +146,15 @@ bool BinaryResourceParser::ParseTable(const ResChunk_header* chunk) {
break;
default:
- context_->GetDiagnostics()->Warn(
- DiagMessage(source_) << "unexpected chunk type "
- << static_cast<int>(util::DeviceToHost16(parser.chunk()->type)));
+ diag_->Warn(DiagMessage(source_)
+ << "unexpected chunk type "
+ << static_cast<int>(util::DeviceToHost16(parser.chunk()->type)));
break;
}
}
if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "corrupt resource table: " << parser.error());
+ diag_->Error(DiagMessage(source_) << "corrupt resource table: " << parser.error());
return false;
}
return true;
@@ -176,14 +165,13 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) {
sizeof(ResTable_package) - sizeof(ResTable_package::typeIdOffset);
const ResTable_package* package_header = ConvertTo<ResTable_package, kMinPackageSize>(chunk);
if (!package_header) {
- context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_package chunk");
+ diag_->Error(DiagMessage(source_) << "corrupt ResTable_package chunk");
return false;
}
uint32_t package_id = util::DeviceToHost32(package_header->id);
if (package_id > std::numeric_limits<uint8_t>::max()) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "package ID is too big (" << package_id << ")");
+ diag_->Error(DiagMessage(source_) << "package ID is too big (" << package_id << ")");
return false;
}
@@ -198,9 +186,8 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) {
ResourceTablePackage* package =
table_->CreatePackage(util::Utf16ToUtf8(package_name), static_cast<uint8_t>(package_id));
if (!package) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "incompatible package '" << package_name << "' with ID "
- << package_id);
+ diag_->Error(DiagMessage(source_)
+ << "incompatible package '" << package_name << "' with ID " << package_id);
return false;
}
@@ -218,8 +205,7 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) {
status_t err =
type_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
if (err != NO_ERROR) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "corrupt type string pool in "
+ diag_->Error(DiagMessage(source_) << "corrupt type string pool in "
<< "ResTable_package: " << type_pool_.getError());
return false;
}
@@ -227,13 +213,12 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) {
status_t err =
key_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
if (err != NO_ERROR) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "corrupt key string pool in "
+ diag_->Error(DiagMessage(source_) << "corrupt key string pool in "
<< "ResTable_package: " << key_pool_.getError());
return false;
}
} else {
- context_->GetDiagnostics()->Warn(DiagMessage(source_) << "unexpected string pool");
+ diag_->Warn(DiagMessage(source_) << "unexpected string pool");
}
break;
@@ -256,16 +241,15 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) {
break;
default:
- context_->GetDiagnostics()->Warn(
- DiagMessage(source_) << "unexpected chunk type "
- << static_cast<int>(util::DeviceToHost16(parser.chunk()->type)));
+ diag_->Warn(DiagMessage(source_)
+ << "unexpected chunk type "
+ << static_cast<int>(util::DeviceToHost16(parser.chunk()->type)));
break;
}
}
if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "corrupt ResTable_package: " << parser.error());
+ diag_->Error(DiagMessage(source_) << "corrupt ResTable_package: " << parser.error());
return false;
}
@@ -278,19 +262,18 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) {
bool BinaryResourceParser::ParseTypeSpec(const ResChunk_header* chunk) {
if (type_pool_.getError() != NO_ERROR) {
- context_->GetDiagnostics()->Error(DiagMessage(source_) << "missing type string pool");
+ diag_->Error(DiagMessage(source_) << "missing type string pool");
return false;
}
const ResTable_typeSpec* type_spec = ConvertTo<ResTable_typeSpec>(chunk);
if (!type_spec) {
- context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_typeSpec chunk");
+ diag_->Error(DiagMessage(source_) << "corrupt ResTable_typeSpec chunk");
return false;
}
if (type_spec->id == 0) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "ResTable_typeSpec has invalid id: " << type_spec->id);
+ diag_->Error(DiagMessage(source_) << "ResTable_typeSpec has invalid id: " << type_spec->id);
return false;
}
return true;
@@ -299,12 +282,12 @@ bool BinaryResourceParser::ParseTypeSpec(const ResChunk_header* chunk) {
bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
const ResChunk_header* chunk) {
if (type_pool_.getError() != NO_ERROR) {
- context_->GetDiagnostics()->Error(DiagMessage(source_) << "missing type string pool");
+ diag_->Error(DiagMessage(source_) << "missing type string pool");
return false;
}
if (key_pool_.getError() != NO_ERROR) {
- context_->GetDiagnostics()->Error(DiagMessage(source_) << "missing key string pool");
+ diag_->Error(DiagMessage(source_) << "missing key string pool");
return false;
}
@@ -312,13 +295,12 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
// a lot and has its own code to handle variable size.
const ResTable_type* type = ConvertTo<ResTable_type, kResTableTypeMinSize>(chunk);
if (!type) {
- context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_type chunk");
+ diag_->Error(DiagMessage(source_) << "corrupt ResTable_type chunk");
return false;
}
if (type->id == 0) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "ResTable_type has invalid id: " << (int)type->id);
+ diag_->Error(DiagMessage(source_) << "ResTable_type has invalid id: " << (int)type->id);
return false;
}
@@ -329,9 +311,8 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
const ResourceType* parsed_type = ParseResourceType(type_str);
if (!parsed_type) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "invalid type name '" << type_str << "' for type with ID "
- << (int)type->id);
+ diag_->Error(DiagMessage(source_)
+ << "invalid type name '" << type_str << "' for type with ID " << (int)type->id);
return false;
}
@@ -360,14 +341,13 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
}
if (!resource_value) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "failed to parse value for resource " << name << " ("
+ diag_->Error(DiagMessage(source_) << "failed to parse value for resource " << name << " ("
<< res_id << ") with configuration '" << config << "'");
return false;
}
if (!table_->AddResourceAllowMangled(name, res_id, config, {}, std::move(resource_value),
- context_->GetDiagnostics())) {
+ diag_)) {
return false;
}
@@ -375,7 +355,7 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
Symbol symbol;
symbol.state = SymbolState::kPublic;
symbol.source = source_.WithLine(0);
- if (!table_->SetSymbolStateAllowMangled(name, res_id, symbol, context_->GetDiagnostics())) {
+ if (!table_->SetSymbolStateAllowMangled(name, res_id, symbol, diag_)) {
return false;
}
}
@@ -410,15 +390,14 @@ std::unique_ptr<Item> BinaryResourceParser::ParseValue(const ResourceNameRef& na
const android::Res_value& value) {
std::unique_ptr<Item> item = ResourceUtils::ParseBinaryResValue(name.type, config, value_pool_,
value, &table_->string_pool);
- if (files_ != nullptr && item != nullptr) {
+ if (files_ != nullptr) {
FileReference* file_ref = ValueCast<FileReference>(item.get());
if (file_ref != nullptr) {
file_ref->file = files_->FindFile(*file_ref->path);
if (file_ref->file == nullptr) {
- context_->GetDiagnostics()->Warn(DiagMessage()
- << "resource " << name << " for config '" << config
- << "' is a file reference to '" << *file_ref->path
- << "' but no such path exists");
+ diag_->Warn(DiagMessage() << "resource " << name << " for config '" << config
+ << "' is a file reference to '" << *file_ref->path
+ << "' but no such path exists");
}
}
}
@@ -432,7 +411,7 @@ std::unique_ptr<Value> BinaryResourceParser::ParseMapEntry(const ResourceNameRef
case ResourceType::kStyle:
return ParseStyle(name, config, map);
case ResourceType::kAttrPrivate:
- // fallthrough
+ // fallthrough
case ResourceType::kAttr:
return ParseAttr(name, config, map);
case ResourceType::kArray:
@@ -445,8 +424,8 @@ std::unique_ptr<Value> BinaryResourceParser::ParseMapEntry(const ResourceNameRef
// We can ignore the value here.
return util::make_unique<Id>();
default:
- context_->GetDiagnostics()->Error(DiagMessage() << "illegal map type '" << ToString(name.type)
- << "' (" << (int)name.type << ")");
+ diag_->Error(DiagMessage() << "illegal map type '" << ToString(name.type) << "' ("
+ << (int)name.type << ")");
break;
}
return {};
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.h b/tools/aapt2/format/binary/BinaryResourceParser.h
index dc9a3847a9c6..052f806e3b95 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.h
+++ b/tools/aapt2/format/binary/BinaryResourceParser.h
@@ -39,7 +39,7 @@ class BinaryResourceParser {
public:
// Creates a parser, which will read `len` bytes from `data`, and add any resources parsed to
// `table`. `source` is for logging purposes.
- BinaryResourceParser(IAaptContext* context, ResourceTable* table, const Source& source,
+ BinaryResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
const void* data, size_t data_len, io::IFileCollection* files = nullptr);
// Parses the binary resource table and returns true if successful.
@@ -80,7 +80,7 @@ class BinaryResourceParser {
*/
bool CollectMetaData(const android::ResTable_map& map_entry, Value* value);
- IAaptContext* context_;
+ IDiagnostics* diag_;
ResourceTable* table_;
const Source source_;
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 57565a53794b..259f2e9cfcc8 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -568,14 +568,6 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) {
ResTable_header* table_header = table_writer.StartChunk<ResTable_header>(RES_TABLE_TYPE);
table_header->packageCount = util::HostToDevice32(table->packages.size());
- // Write a self mapping entry for this package if the ID is non-standard (0x7f).
- if (context->GetPackageType() == PackageType::kApp) {
- const uint8_t package_id = context->GetPackageId();
- if (package_id != kFrameworkPackageId && package_id != kAppPackageId) {
- table->included_packages_[package_id] = context->GetCompilationPackage();
- }
- }
-
// Flatten the values string pool.
StringPool::FlattenUtf8(table_writer.buffer(), table->string_pool);
@@ -583,6 +575,14 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) {
// Flatten each package.
for (auto& package : table->packages) {
+ if (context->GetPackageType() == PackageType::kApp) {
+ // Write a self mapping entry for this package if the ID is non-standard (0x7f).
+ const uint8_t package_id = package->id.value();
+ if (package_id != kFrameworkPackageId && package_id != kAppPackageId) {
+ table->included_packages_[package_id] = package->name;
+ }
+ }
+
PackageFlattener flattener(context, package.get(), &table->included_packages_,
options_.use_sparse_entries);
if (!flattener.FlattenPackage(&package_buffer)) {
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index 6d7597372234..e11890bbd078 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -71,7 +71,8 @@ class TableFlattenerTest : public ::testing::Test {
return result;
}
- BinaryResourceParser parser(context, out_table, {}, content.data(), content.size());
+ BinaryResourceParser parser(context->GetDiagnostics(), out_table, {}, content.data(),
+ content.size());
if (!parser.Parse()) {
return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
}
@@ -278,7 +279,7 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkO) {
// Attempt to parse the sparse contents.
ResourceTable sparse_table;
- BinaryResourceParser parser(context.get(), &sparse_table, Source("test.arsc"),
+ BinaryResourceParser parser(context->GetDiagnostics(), &sparse_table, Source("test.arsc"),
sparse_contents.data(), sparse_contents.size());
ASSERT_TRUE(parser.Parse());
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index 86bd86536cc4..0f0bce8bf5a7 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -371,7 +371,8 @@ static SymbolState DeserializeVisibilityFromPb(const pb::SymbolStatus_Visibility
}
static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStringPool& src_pool,
- ResourceTable* out_table, std::string* out_error) {
+ io::IFileCollection* files, ResourceTable* out_table,
+ std::string* out_error) {
Maybe<uint8_t> id;
if (pb_package.has_package_id()) {
id = static_cast<uint8_t>(pb_package.package_id().id());
@@ -444,7 +445,7 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr
}
config_value->value = DeserializeValueFromPb(pb_config_value.value(), src_pool, config,
- &out_table->string_pool, out_error);
+ &out_table->string_pool, files, out_error);
if (config_value->value == nullptr) {
return false;
}
@@ -457,8 +458,8 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr
return true;
}
-bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, ResourceTable* out_table,
- std::string* out_error) {
+bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, io::IFileCollection* files,
+ ResourceTable* out_table, std::string* out_error) {
// We import the android namespace because on Windows NO_ERROR is a macro, not an enum, which
// causes errors when qualifying it with android::
using namespace android;
@@ -474,7 +475,7 @@ bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, ResourceTable* ou
}
for (const pb::Package& pb_package : pb_table.package()) {
- if (!DeserializePackageFromPb(pb_package, source_pool, out_table, out_error)) {
+ if (!DeserializePackageFromPb(pb_package, source_pool, files, out_table, out_error)) {
return false;
}
}
@@ -600,10 +601,11 @@ static size_t DeserializePluralEnumFromPb(const pb::Plural_Arity& arity) {
std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,
const android::ResStringPool& src_pool,
const ConfigDescription& config,
- StringPool* value_pool, std::string* out_error) {
+ StringPool* value_pool, io::IFileCollection* files,
+ std::string* out_error) {
std::unique_ptr<Value> value;
if (pb_value.has_item()) {
- value = DeserializeItemFromPb(pb_value.item(), src_pool, config, value_pool, out_error);
+ value = DeserializeItemFromPb(pb_value.item(), src_pool, config, value_pool, files, out_error);
if (value == nullptr) {
return {};
}
@@ -651,8 +653,8 @@ std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,
return {};
}
DeserializeItemMetaDataFromPb(pb_entry, src_pool, &entry.key);
- entry.value =
- DeserializeItemFromPb(pb_entry.item(), src_pool, config, value_pool, out_error);
+ entry.value = DeserializeItemFromPb(pb_entry.item(), src_pool, config, value_pool, files,
+ out_error);
if (entry.value == nullptr) {
return {};
}
@@ -680,8 +682,8 @@ std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,
const pb::Array& pb_array = pb_compound_value.array();
std::unique_ptr<Array> array = util::make_unique<Array>();
for (const pb::Array_Element& pb_entry : pb_array.element()) {
- std::unique_ptr<Item> item =
- DeserializeItemFromPb(pb_entry.item(), src_pool, config, value_pool, out_error);
+ std::unique_ptr<Item> item = DeserializeItemFromPb(pb_entry.item(), src_pool, config,
+ value_pool, files, out_error);
if (item == nullptr) {
return {};
}
@@ -697,8 +699,8 @@ std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,
std::unique_ptr<Plural> plural = util::make_unique<Plural>();
for (const pb::Plural_Entry& pb_entry : pb_plural.entry()) {
size_t plural_idx = DeserializePluralEnumFromPb(pb_entry.arity());
- plural->values[plural_idx] =
- DeserializeItemFromPb(pb_entry.item(), src_pool, config, value_pool, out_error);
+ plural->values[plural_idx] = DeserializeItemFromPb(pb_entry.item(), src_pool, config,
+ value_pool, files, out_error);
if (!plural->values[plural_idx]) {
return {};
}
@@ -727,7 +729,7 @@ std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,
std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item,
const android::ResStringPool& src_pool,
const ConfigDescription& config, StringPool* value_pool,
- std::string* out_error) {
+ io::IFileCollection* files, std::string* out_error) {
switch (pb_item.value_case()) {
case pb::Item::kRef: {
const pb::Reference& pb_ref = pb_item.ref();
@@ -774,6 +776,9 @@ std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item,
util::make_unique<FileReference>(value_pool->MakeRef(
pb_file.path(), StringPool::Context(StringPool::Context::kHighPriority, config)));
file_ref->type = DeserializeFileReferenceTypeFromPb(pb_file.type());
+ if (files != nullptr) {
+ file_ref->file = files->FindFile(*file_ref->path);
+ }
return std::move(file_ref);
} break;
@@ -825,7 +830,7 @@ bool DeserializeXmlFromPb(const pb::XmlNode& pb_node, xml::Element* out_el, Stri
}
if (pb_attr.has_compiled_item()) {
attr.compiled_value =
- DeserializeItemFromPb(pb_attr.compiled_item(), {}, {}, value_pool, out_error);
+ DeserializeItemFromPb(pb_attr.compiled_item(), {}, {}, value_pool, nullptr, out_error);
if (attr.compiled_value == nullptr) {
return {};
}
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.h b/tools/aapt2/format/proto/ProtoDeserialize.h
index 7dc54f242d94..0c581a17fa2e 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.h
+++ b/tools/aapt2/format/proto/ProtoDeserialize.h
@@ -27,6 +27,7 @@
#include "Resources.pb.h"
#include "ResourcesInternal.pb.h"
#include "StringPool.h"
+#include "io/File.h"
#include "xml/XmlDom.h"
namespace aapt {
@@ -34,12 +35,13 @@ namespace aapt {
std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,
const android::ResStringPool& src_pool,
const ConfigDescription& config,
- StringPool* value_pool, std::string* out_error);
+ StringPool* value_pool, io::IFileCollection* files,
+ std::string* out_error);
std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item,
const android::ResStringPool& src_pool,
const ConfigDescription& config, StringPool* value_pool,
- std::string* out_error);
+ io::IFileCollection* files, std::string* out_error);
std::unique_ptr<xml::XmlResource> DeserializeXmlResourceFromPb(const pb::XmlNode& pb_node,
std::string* out_error);
@@ -50,8 +52,9 @@ bool DeserializeXmlFromPb(const pb::XmlNode& pb_node, xml::Element* out_el, Stri
bool DeserializeConfigFromPb(const pb::Configuration& pb_config, ConfigDescription* out_config,
std::string* out_error);
-bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, ResourceTable* out_table,
- std::string* out_error);
+// Optional io::IFileCollection used to lookup references to files in the ResourceTable.
+bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, io::IFileCollection* files,
+ ResourceTable* out_table, std::string* out_error);
bool DeserializeCompiledFileFromPb(const pb::internal::CompiledFile& pb_file,
ResourceFile* out_file, std::string* out_error);
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index 8efac8ae6ffd..9649a4de2fe4 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -29,6 +29,12 @@ using ::testing::StrEq;
namespace aapt {
+class MockFileCollection : public io::IFileCollection {
+ public:
+ MOCK_METHOD1(FindFile, io::IFile*(const StringPiece& path));
+ MOCK_METHOD0(Iterator, std::unique_ptr<io::IFileCollectionIterator>());
+};
+
TEST(ProtoSerializeTest, SerializeSinglePackage) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
std::unique_ptr<ResourceTable> table =
@@ -86,9 +92,14 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) {
pb::ResourceTable pb_table;
SerializeTableToPb(*table, &pb_table);
+ test::TestFile file_a("res/layout/main.xml");
+ MockFileCollection files;
+ EXPECT_CALL(files, FindFile(Eq("res/layout/main.xml")))
+ .WillRepeatedly(::testing::Return(&file_a));
+
ResourceTable new_table;
std::string error;
- ASSERT_TRUE(DeserializeTableFromPb(pb_table, &new_table, &error));
+ ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error));
EXPECT_THAT(error, IsEmpty());
Id* new_id = test::GetValue<Id>(&new_table, "com.app.a:id/foo");
@@ -124,6 +135,11 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) {
ASSERT_TRUE(actual_ref->id);
EXPECT_THAT(*actual_ref, Eq(expected_ref));
+ FileReference* actual_file_ref =
+ test::GetValue<FileReference>(&new_table, "com.app.a:layout/main");
+ ASSERT_THAT(actual_file_ref, NotNull());
+ EXPECT_THAT(actual_file_ref->file, Eq(&file_a));
+
StyledString* actual_styled_str =
test::GetValue<StyledString>(&new_table, "com.app.a:string/styled");
ASSERT_THAT(actual_styled_str, NotNull());
diff --git a/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk
index 6ed07b0c5c73..94686c095bd8 100644
--- a/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk
+++ b/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk
@@ -25,5 +25,4 @@ LOCAL_SRC_FILES := $(call all-java-files-under,src)
LOCAL_STATIC_ANDROID_LIBRARIES := \
AaptTestNamespace_LibOne \
AaptTestNamespace_LibTwo
-LOCAL_AAPT_FLAGS := -v
include $(BUILD_PACKAGE)
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 21c6b111b855..58d0607ed7b3 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -37,17 +37,14 @@ TableMerger::TableMerger(IAaptContext* context, ResourceTable* out_table,
CHECK(master_package_ != nullptr) << "package name or ID already taken";
}
-bool TableMerger::Merge(const Source& src, ResourceTable* table, bool overlay,
- io::IFileCollection* collection) {
+bool TableMerger::Merge(const Source& src, ResourceTable* table, bool overlay) {
// We allow adding new resources if this is not an overlay, or if the options allow overlays
// to add new resources.
- return MergeImpl(src, table, collection, overlay,
- options_.auto_add_overlay || !overlay /*allow_new*/);
+ return MergeImpl(src, table, overlay, options_.auto_add_overlay || !overlay /*allow_new*/);
}
// This will merge packages with the same package name (or no package name).
-bool TableMerger::MergeImpl(const Source& src, ResourceTable* table,
- io::IFileCollection* collection, bool overlay, bool allow_new) {
+bool TableMerger::MergeImpl(const Source& src, ResourceTable* table, bool overlay, bool allow_new) {
bool error = false;
for (auto& package : table->packages) {
// Only merge an empty package or the package we're building.
@@ -55,37 +52,20 @@ bool TableMerger::MergeImpl(const Source& src, ResourceTable* table,
// This is because at compile time it is unknown if the attributes are
// simply uses of the attribute or definitions.
if (package->name.empty() || context_->GetCompilationPackage() == package->name) {
- FileMergeCallback callback;
- if (collection) {
- callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
- FileReference* new_file, FileReference* old_file) -> bool {
- // The old file's path points inside the APK, so we can use it as is.
- io::IFile* f = collection->FindFile(*old_file->path);
- if (!f) {
- context_->GetDiagnostics()->Error(DiagMessage(src)
- << "file '" << *old_file->path << "' not found");
- return false;
- }
-
- new_file->file = f;
- return true;
- };
- }
-
// Merge here. Once the entries are merged and mangled, any references to them are still
// valid. This is because un-mangled references are mangled, then looked up at resolution
// time. Also, when linking, we convert references with no package name to use the compilation
// package name.
- error |=
- !DoMerge(src, table, package.get(), false /* mangle */, overlay, allow_new, callback);
+ error |= !DoMerge(src, table, package.get(), false /*mangle*/, overlay, allow_new);
}
}
return !error;
}
-// This will merge and mangle resources from a static library.
+// This will merge and mangle resources from a static library. It is assumed that all FileReferences
+// have correctly set their io::IFile*.
bool TableMerger::MergeAndMangle(const Source& src, const StringPiece& package_name,
- ResourceTable* table, io::IFileCollection* collection) {
+ ResourceTable* table) {
bool error = false;
for (auto& package : table->packages) {
// Warn of packages with an unrelated ID.
@@ -96,23 +76,7 @@ bool TableMerger::MergeAndMangle(const Source& src, const StringPiece& package_n
bool mangle = package_name != context_->GetCompilationPackage();
merged_packages_.insert(package->name);
-
- auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
- FileReference* new_file, FileReference* old_file) -> bool {
- // The old file's path points inside the APK, so we can use it as is.
- io::IFile* f = collection->FindFile(*old_file->path);
- if (!f) {
- context_->GetDiagnostics()->Error(DiagMessage(src)
- << "file '" << *old_file->path << "' not found");
- return false;
- }
-
- new_file->file = f;
- return true;
- };
-
- error |= !DoMerge(src, table, package.get(), mangle, false /*overlay*/, true /*allow_new*/,
- callback);
+ error |= !DoMerge(src, table, package.get(), mangle, false /*overlay*/, true /*allow_new*/);
}
return !error;
}
@@ -187,7 +151,7 @@ static ResourceTable::CollisionResult ResolveMergeCollision(Value* existing, Val
static ResourceTable::CollisionResult MergeConfigValue(IAaptContext* context,
const ResourceNameRef& res_name,
- const bool overlay,
+ bool overlay,
ResourceConfigValue* dst_config_value,
ResourceConfigValue* src_config_value,
StringPool* pool) {
@@ -220,10 +184,8 @@ static ResourceTable::CollisionResult MergeConfigValue(IAaptContext* context,
}
bool TableMerger::DoMerge(const Source& src, ResourceTable* src_table,
- ResourceTablePackage* src_package,
- const bool mangle_package, const bool overlay,
- const bool allow_new_resources,
- const FileMergeCallback& callback) {
+ ResourceTablePackage* src_package, bool mangle_package, bool overlay,
+ bool allow_new_resources) {
bool error = false;
for (auto& src_type : src_package->types) {
@@ -292,13 +254,6 @@ bool TableMerger::DoMerge(const Source& src, ResourceTable* src_table,
} else {
new_file_ref = std::unique_ptr<FileReference>(f->Clone(&master_table_->string_pool));
}
-
- if (callback) {
- if (!callback(res_name, src_config_value->config, new_file_ref.get(), f)) {
- error = true;
- continue;
- }
- }
dst_config_value->value = std::move(new_file_ref);
} else {
@@ -343,8 +298,8 @@ bool TableMerger::MergeFile(const ResourceFile& file_desc, bool overlay, io::IFi
->FindOrCreateValue(file_desc.config, {})
->value = std::move(file_ref);
- return DoMerge(file->GetSource(), &table, pkg, false /* mangle */, overlay /* overlay */,
- true /* allow_new */, {});
+ return DoMerge(file->GetSource(), &table, pkg, false /*mangle*/, overlay /*overlay*/,
+ true /*allow_new*/);
}
} // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index d024aa47d952..47e23dded26f 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -40,6 +40,9 @@ struct TableMergerOptions {
// TableMerger takes resource tables and merges all packages within the tables that have the same
// package ID.
//
+// It is assumed that any FileReference values have their io::IFile pointer set to point to the
+// file they represent.
+//
// If a package has a different name, all the entries in that table have their names mangled
// to include the package name. This way there are no collisions. In order to do this correctly,
// the TableMerger needs to also mangle any FileReference paths. Once these are mangled, the
@@ -60,14 +63,11 @@ class TableMerger {
// Merges resources from the same or empty package. This is for local sources.
// If overlay is true, the resources are treated as overlays.
- // An io::IFileCollection is optional and used to find the referenced Files and process them.
- bool Merge(const Source& src, ResourceTable* table, bool overlay,
- io::IFileCollection* collection = nullptr);
+ bool Merge(const Source& src, ResourceTable* table, bool overlay);
// Merges resources from the given package, mangling the name. This is for static libraries.
- // An io::IFileCollection is needed in order to find the referenced Files and process them.
- bool MergeAndMangle(const Source& src, const android::StringPiece& package, ResourceTable* table,
- io::IFileCollection* collection);
+ // All FileReference values must have their io::IFile set.
+ bool MergeAndMangle(const Source& src, const android::StringPiece& package, ResourceTable* table);
// Merges a compiled file that belongs to this same or empty package.
bool MergeFile(const ResourceFile& fileDesc, bool overlay, io::IFile* file);
@@ -75,23 +75,16 @@ class TableMerger {
private:
DISALLOW_COPY_AND_ASSIGN(TableMerger);
- using FileMergeCallback = std::function<bool(const ResourceNameRef&,
- const ConfigDescription& config,
- FileReference*, FileReference*)>;
-
IAaptContext* context_;
ResourceTable* master_table_;
TableMergerOptions options_;
ResourceTablePackage* master_package_;
std::set<std::string> merged_packages_;
- bool MergeImpl(const Source& src, ResourceTable* src_table,
- io::IFileCollection* collection, bool overlay, bool allow_new);
+ bool MergeImpl(const Source& src, ResourceTable* src_table, bool overlay, bool allow_new);
- bool DoMerge(const Source& src, ResourceTable* src_table,
- ResourceTablePackage* src_package, const bool mangle_package,
- const bool overlay, const bool allow_new_resources,
- const FileMergeCallback& callback);
+ bool DoMerge(const Source& src, ResourceTable* src_table, ResourceTablePackage* src_package,
+ bool mangle_package, bool overlay, bool allow_new_resources);
std::unique_ptr<FileReference> CloneAndMangleFile(const std::string& package,
const FileReference& value);
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index 3499809e5a83..6aab8ded24a5 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -22,11 +22,12 @@
using ::aapt::test::ValueEq;
using ::testing::Contains;
+using ::testing::Eq;
+using ::testing::Field;
using ::testing::NotNull;
-using ::testing::UnorderedElementsAreArray;
using ::testing::Pointee;
-using ::testing::Field;
-using ::testing::Eq;
+using ::testing::StrEq;
+using ::testing::UnorderedElementsAreArray;
namespace aapt {
@@ -67,10 +68,9 @@ TEST_F(TableMergerTest, SimpleMerge) {
ResourceTable final_table;
TableMerger merger(context_.get(), &final_table, TableMergerOptions{});
- io::FileCollection collection;
ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
- ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection));
+ ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get()));
EXPECT_TRUE(merger.merged_packages().count("com.app.b") != 0);
@@ -122,32 +122,35 @@ TEST_F(TableMergerTest, MergeFileOverlay) {
}
TEST_F(TableMergerTest, MergeFileReferences) {
+ test::TestFile file_a("res/xml/file.xml");
+ test::TestFile file_b("res/xml/file.xml");
+
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
- .AddFileReference("com.app.a:xml/file", "res/xml/file.xml")
+ .AddFileReference("com.app.a:xml/file", "res/xml/file.xml", &file_a)
.Build();
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
.SetPackageId("com.app.b", 0x7f)
- .AddFileReference("com.app.b:xml/file", "res/xml/file.xml")
+ .AddFileReference("com.app.b:xml/file", "res/xml/file.xml", &file_b)
.Build();
ResourceTable final_table;
TableMerger merger(context_.get(), &final_table, TableMergerOptions{});
- io::FileCollection collection;
- collection.InsertFile("res/xml/file.xml");
ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
- ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection));
+ ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get()));
FileReference* f = test::GetValue<FileReference>(&final_table, "com.app.a:xml/file");
ASSERT_THAT(f, NotNull());
- EXPECT_EQ(std::string("res/xml/file.xml"), *f->path);
+ EXPECT_THAT(*f->path, StrEq("res/xml/file.xml"));
+ EXPECT_THAT(f->file, Eq(&file_a));
f = test::GetValue<FileReference>(&final_table, "com.app.a:xml/com.app.b$file");
ASSERT_THAT(f, NotNull());
- EXPECT_EQ(std::string("res/xml/com.app.b$file.xml"), *f->path);
+ EXPECT_THAT(*f->path, StrEq("res/xml/com.app.b$file.xml"));
+ EXPECT_THAT(f->file, Eq(&file_b));
}
TEST_F(TableMergerTest, OverrideResourceWithOverlay) {
diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp
index 680308871af7..473693ce4917 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator.cpp
@@ -265,12 +265,14 @@ bool MultiApkGenerator::UpdateManifest(const Artifact& artifact,
const PostProcessingConfiguration& config,
std::unique_ptr<XmlResource>* updated_manifest,
IDiagnostics* diag) {
- *updated_manifest = apk_->InflateManifest(context_);
- XmlResource* manifest = updated_manifest->get();
- if (manifest == nullptr) {
+ const xml::XmlResource* apk_manifest = apk_->GetManifest();
+ if (apk_manifest == nullptr) {
return false;
}
+ *updated_manifest = apk_manifest->Clone();
+ XmlResource* manifest = updated_manifest->get();
+
// Make sure the first element is <manifest> with package attribute.
xml::Element* manifest_el = manifest->root.get();
if (manifest_el == nullptr) {
diff --git a/tools/aapt2/optimize/MultiApkGenerator_test.cpp b/tools/aapt2/optimize/MultiApkGenerator_test.cpp
index c8f3524748b6..30c9146b5444 100644
--- a/tools/aapt2/optimize/MultiApkGenerator_test.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator_test.cpp
@@ -106,7 +106,7 @@ class MultiApkGeneratorTest : public ::testing::Test {
TEST_F(MultiApkGeneratorTest, VersionFilterNewerVersion) {
std::unique_ptr<ResourceTable> table = BuildTable();
- LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
+ LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}};
std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(19).Build();
PostProcessingConfiguration empty_config;
TableFlattenerOptions table_flattener_options;
@@ -147,7 +147,7 @@ TEST_F(MultiApkGeneratorTest, VersionFilterNewerVersion) {
TEST_F(MultiApkGeneratorTest, VersionFilterOlderVersion) {
std::unique_ptr<ResourceTable> table = BuildTable();
- LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
+ LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}};
std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build();
PostProcessingConfiguration empty_config;
TableFlattenerOptions table_flattener_options;
@@ -186,7 +186,7 @@ TEST_F(MultiApkGeneratorTest, VersionFilterOlderVersion) {
TEST_F(MultiApkGeneratorTest, VersionFilterNoVersion) {
std::unique_ptr<ResourceTable> table = BuildTable();
- LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
+ LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}};
std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build();
PostProcessingConfiguration empty_config;
TableFlattenerOptions table_flattener_options;
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index 5a62e9777065..ecec63f3cf31 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -78,21 +78,27 @@ ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, c
}
ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
- const StringPiece& path) {
- return AddFileReference(name, {}, path);
+ const StringPiece& path,
+ io::IFile* file) {
+ return AddFileReference(name, {}, path, file);
}
ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
const ResourceId& id,
- const StringPiece& path) {
- return AddValue(name, id, util::make_unique<FileReference>(table_->string_pool.MakeRef(path)));
+ const StringPiece& path,
+ io::IFile* file) {
+ auto file_ref = util::make_unique<FileReference>(table_->string_pool.MakeRef(path));
+ file_ref->file = file;
+ return AddValue(name, id, std::move(file_ref));
}
ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
const StringPiece& path,
- const ConfigDescription& config) {
- return AddValue(name, config, {},
- util::make_unique<FileReference>(table_->string_pool.MakeRef(path)));
+ const ConfigDescription& config,
+ io::IFile* file) {
+ auto file_ref = util::make_unique<FileReference>(table_->string_pool.MakeRef(path));
+ file_ref->file = file;
+ return AddValue(name, config, {}, std::move(file_ref));
}
ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name,
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 263fb5562b25..4cdfc33e92e3 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -52,12 +52,15 @@ class ResourceTableBuilder {
ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id,
const ConfigDescription& config, const android::StringPiece& str);
ResourceTableBuilder& AddFileReference(const android::StringPiece& name,
- const android::StringPiece& path);
+ const android::StringPiece& path,
+ io::IFile* file = nullptr);
ResourceTableBuilder& AddFileReference(const android::StringPiece& name, const ResourceId& id,
- const android::StringPiece& path);
+ const android::StringPiece& path,
+ io::IFile* file = nullptr);
ResourceTableBuilder& AddFileReference(const android::StringPiece& name,
const android::StringPiece& path,
- const ConfigDescription& config);
+ const ConfigDescription& config,
+ io::IFile* file = nullptr);
ResourceTableBuilder& AddValue(const android::StringPiece& name, std::unique_ptr<Value> value);
ResourceTableBuilder& AddValue(const android::StringPiece& name, const ResourceId& id,
std::unique_ptr<Value> value);
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index 35225066633c..b0cf44accafa 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -258,8 +258,7 @@ static void CopyAttributes(Element* el, android::ResXMLParser* parser, StringPoo
}
}
-std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnostics* diag,
- const Source& source) {
+std::unique_ptr<XmlResource> Inflate(const void* data, size_t len, std::string* out_error) {
// We import the android namespace because on Windows NO_ERROR is a macro, not
// an enum, which causes errors when qualifying it with android::
using namespace android;
@@ -270,7 +269,10 @@ std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnos
std::unique_ptr<Element> pending_element;
ResXMLTree tree;
- if (tree.setTo(data, data_len) != NO_ERROR) {
+ if (tree.setTo(data, len) != NO_ERROR) {
+ if (out_error != nullptr) {
+ *out_error = "failed to initialize ResXMLTree";
+ }
return {};
}
@@ -361,6 +363,27 @@ std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnos
return util::make_unique<XmlResource>(ResourceFile{}, std::move(string_pool), std::move(root));
}
+std::unique_ptr<XmlResource> XmlResource::Clone() const {
+ std::unique_ptr<XmlResource> cloned = util::make_unique<XmlResource>(file);
+ if (root != nullptr) {
+ cloned->root = root->CloneElement([&](const xml::Element& src, xml::Element* dst) {
+ dst->attributes.reserve(src.attributes.size());
+ for (const xml::Attribute& attr : src.attributes) {
+ xml::Attribute cloned_attr;
+ cloned_attr.name = attr.name;
+ cloned_attr.namespace_uri = attr.namespace_uri;
+ cloned_attr.value = attr.value;
+ cloned_attr.compiled_attribute = attr.compiled_attribute;
+ if (attr.compiled_value != nullptr) {
+ cloned_attr.compiled_value.reset(attr.compiled_value->Clone(&cloned->string_pool));
+ }
+ dst->attributes.push_back(std::move(cloned_attr));
+ }
+ });
+ }
+ return cloned;
+}
+
Element* FindRootElement(Node* node) {
if (node == nullptr) {
return nullptr;
@@ -383,12 +406,7 @@ void Element::InsertChild(size_t index, std::unique_ptr<Node> child) {
}
Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) {
- for (auto& attr : attributes) {
- if (ns == attr.namespace_uri && name == attr.name) {
- return &attr;
- }
- }
- return nullptr;
+ return const_cast<Attribute*>(static_cast<const Element*>(this)->FindAttribute(ns, name));
}
const Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) const {
@@ -404,17 +422,29 @@ Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) {
return FindChildWithAttribute(ns, name, {}, {}, {});
}
+const Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) const {
+ return FindChildWithAttribute(ns, name, {}, {}, {});
+}
+
Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name,
const StringPiece& attr_ns, const StringPiece& attr_name,
const StringPiece& attr_value) {
- for (auto& child : children) {
- if (Element* el = NodeCast<Element>(child.get())) {
+ return const_cast<Element*>(static_cast<const Element*>(this)->FindChildWithAttribute(
+ ns, name, attr_ns, attr_name, attr_value));
+}
+
+const Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name,
+ const StringPiece& attr_ns,
+ const StringPiece& attr_name,
+ const StringPiece& attr_value) const {
+ for (const auto& child : children) {
+ if (const Element* el = NodeCast<Element>(child.get())) {
if (ns == el->namespace_uri && name == el->name) {
if (attr_ns.empty() && attr_name.empty()) {
return el;
}
- Attribute* attr = el->FindAttribute(attr_ns, attr_name);
+ const Attribute* attr = el->FindAttribute(attr_ns, attr_name);
if (attr && attr_value == attr->value) {
return el;
}
diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h
index 063d7b9210c1..cf06ba5cc50e 100644
--- a/tools/aapt2/xml/XmlDom.h
+++ b/tools/aapt2/xml/XmlDom.h
@@ -100,11 +100,21 @@ class Element : public Node {
Attribute* FindAttribute(const android::StringPiece& ns, const android::StringPiece& name);
const Attribute* FindAttribute(const android::StringPiece& ns,
const android::StringPiece& name) const;
+
Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name);
+ const Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name) const;
+
Element* FindChildWithAttribute(const android::StringPiece& ns, const android::StringPiece& name,
const android::StringPiece& attr_ns,
const android::StringPiece& attr_name,
const android::StringPiece& attr_value);
+
+ const Element* FindChildWithAttribute(const android::StringPiece& ns,
+ const android::StringPiece& name,
+ const android::StringPiece& attr_ns,
+ const android::StringPiece& attr_name,
+ const android::StringPiece& attr_value) const;
+
std::vector<Element*> GetChildElements();
// Due to overriding of subtypes not working with unique_ptr, define a convenience Clone method
@@ -139,16 +149,16 @@ class XmlResource {
StringPool string_pool;
std::unique_ptr<xml::Element> root;
+
+ std::unique_ptr<XmlResource> Clone() const;
};
// Inflates an XML DOM from an InputStream, logging errors to the logger.
-// Returns the root node on success, or nullptr on failure.
std::unique_ptr<XmlResource> Inflate(io::InputStream* in, IDiagnostics* diag, const Source& source);
-// Inflates an XML DOM from a binary ResXMLTree, logging errors to the logger.
-// Returns the root node on success, or nullptr on failure.
-std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnostics* diag,
- const Source& source);
+// Inflates an XML DOM from a binary ResXMLTree.
+std::unique_ptr<XmlResource> Inflate(const void* data, size_t len,
+ std::string* out_error = nullptr);
Element* FindRootElement(Node* node);
diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp
index 34e6d3f54f56..e5012d67163d 100644
--- a/tools/aapt2/xml/XmlDom_test.cpp
+++ b/tools/aapt2/xml/XmlDom_test.cpp
@@ -70,8 +70,7 @@ TEST(XmlDomTest, BinaryInflate) {
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"));
+ std::unique_ptr<XmlResource> new_doc = Inflate(block.get(), buffer.size(), nullptr);
ASSERT_THAT(new_doc, NotNull());
EXPECT_THAT(new_doc->root->name, StrEq("Layout"));
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 4f3acd6ac444..04efd569850d 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -3437,6 +3437,7 @@ public class WifiManager {
* Set wifi verbose log. Called from developer settings.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
public void enableVerboseLogging (int verbose) {
try {
mService.enableVerboseLogging(verbose);