Merge "Fix stale media metadata" into qt-dev
diff --git a/api/test-current.txt b/api/test-current.txt
index b1ffbad..1a912a1c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -645,6 +645,7 @@
method public void setAutofillOptions(@Nullable android.content.AutofillOptions);
method public void setContentCaptureOptions(@Nullable android.content.ContentCaptureOptions);
field public static final String BUGREPORT_SERVICE = "bugreport";
+ field public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
field public static final String ROLLBACK_SERVICE = "rollback";
field public static final String STATUS_BAR_SERVICE = "statusbar";
field public static final String TEST_NETWORK_SERVICE = "test_network";
@@ -1916,6 +1917,10 @@
method public boolean hasSingleFileDescriptor();
}
+ public final class Parcel {
+ method public int readExceptionCode();
+ }
+
public class ParcelFileDescriptor implements java.io.Closeable android.os.Parcelable {
method public static java.io.File getFile(java.io.FileDescriptor) throws java.io.IOException;
}
@@ -1985,6 +1990,7 @@
public class SystemProperties {
method @NonNull public static String get(@NonNull String);
method @NonNull public static String get(@NonNull String, @Nullable String);
+ method public static boolean getBoolean(@NonNull String, boolean);
}
public final class UserHandle implements android.os.Parcelable {
@@ -2139,6 +2145,36 @@
}
+package android.os.image {
+
+ public class DynamicSystemClient {
+ ctor public DynamicSystemClient(@NonNull android.content.Context);
+ method @RequiresPermission("android.permission.INSTALL_DYNAMIC_SYSTEM") public void bind();
+ method public void setOnStatusChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.image.DynamicSystemClient.OnStatusChangedListener);
+ method public void setOnStatusChangedListener(@NonNull android.os.image.DynamicSystemClient.OnStatusChangedListener);
+ method @RequiresPermission("android.permission.INSTALL_DYNAMIC_SYSTEM") public void start(@NonNull android.net.Uri, long);
+ method @RequiresPermission("android.permission.INSTALL_DYNAMIC_SYSTEM") public void start(@NonNull android.net.Uri, long, long);
+ method @RequiresPermission("android.permission.INSTALL_DYNAMIC_SYSTEM") public void unbind();
+ field public static final int CAUSE_ERROR_EXCEPTION = 6; // 0x6
+ field public static final int CAUSE_ERROR_INVALID_URL = 4; // 0x4
+ field public static final int CAUSE_ERROR_IO = 3; // 0x3
+ field public static final int CAUSE_ERROR_IPC = 5; // 0x5
+ field public static final int CAUSE_INSTALL_CANCELLED = 2; // 0x2
+ field public static final int CAUSE_INSTALL_COMPLETED = 1; // 0x1
+ field public static final int CAUSE_NOT_SPECIFIED = 0; // 0x0
+ field public static final int STATUS_IN_PROGRESS = 2; // 0x2
+ field public static final int STATUS_IN_USE = 4; // 0x4
+ field public static final int STATUS_NOT_STARTED = 1; // 0x1
+ field public static final int STATUS_READY = 3; // 0x3
+ field public static final int STATUS_UNKNOWN = 0; // 0x0
+ }
+
+ public static interface DynamicSystemClient.OnStatusChangedListener {
+ method public void onStatusChanged(int, int, long, @Nullable Throwable);
+ }
+
+}
+
package android.os.storage {
public class StorageManager {
@@ -2161,6 +2197,7 @@
package android.permission {
public final class PermissionControllerManager {
+ method @RequiresPermission("android.permission.GET_RUNTIME_PERMISSIONS") public void getAppPermissions(@NonNull String, @NonNull android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, @Nullable android.os.Handler);
method @RequiresPermission("android.permission.REVOKE_RUNTIME_PERMISSIONS") public void revokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull java.util.concurrent.Executor, @NonNull android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback);
field public static final int COUNT_ONLY_WHEN_GRANTED = 1; // 0x1
field public static final int COUNT_WHEN_SYSTEM = 2; // 0x2
@@ -2168,11 +2205,25 @@
field public static final int REASON_MALWARE = 1; // 0x1
}
+ public static interface PermissionControllerManager.OnGetAppPermissionResultCallback {
+ method public void onGetAppPermissions(@NonNull java.util.List<android.permission.RuntimePermissionPresentationInfo>);
+ }
+
public abstract static class PermissionControllerManager.OnRevokeRuntimePermissionsCallback {
ctor public PermissionControllerManager.OnRevokeRuntimePermissionsCallback();
method public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>);
}
+ public final class RuntimePermissionPresentationInfo implements android.os.Parcelable {
+ ctor public RuntimePermissionPresentationInfo(@NonNull CharSequence, boolean, boolean);
+ method public int describeContents();
+ method @NonNull public CharSequence getLabel();
+ method public boolean isGranted();
+ method public boolean isStandard();
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.permission.RuntimePermissionPresentationInfo> CREATOR;
+ }
+
}
package android.print {
@@ -2930,6 +2981,21 @@
method public E valueAtUnchecked(int);
}
+ public class FeatureFlagUtils {
+ ctor public FeatureFlagUtils();
+ method public static java.util.Map<java.lang.String,java.lang.String> getAllFeatureFlags();
+ method public static boolean isEnabled(android.content.Context, String);
+ method public static void setEnabled(android.content.Context, String, boolean);
+ field public static final String DYNAMIC_SYSTEM = "settings_dynamic_system";
+ field public static final String FFLAG_OVERRIDE_PREFIX = "sys.fflag.override.";
+ field public static final String FFLAG_PREFIX = "sys.fflag.";
+ field public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid";
+ field public static final String PERSIST_PREFIX = "persist.sys.fflag.override.";
+ field public static final String PIXEL_WALLPAPER_CATEGORY_SWITCH = "settings_pixel_wallpaper_category_switch";
+ field public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press";
+ field public static final String SEAMLESS_TRANSFER = "settings_seamless_transfer";
+ }
+
public class TimeUtils {
method public static String formatDuration(long);
}
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index fc0048c..2899d49 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -294,8 +294,6 @@
MediametricsMediadrmReported mediametrics_mediadrm_reported = 198;
MediametricsNuPlayerReported mediametrics_nuplayer_reported = 199;
MediametricsRecorderReported mediametrics_recorder_reported = 200;
- VehicleMapServicePacketReported vms_packet_reported = 201;
- VehicleMapServicePacketFailureReported vms_packet_failure_reported = 202;
CarPowerStateChanged car_power_state_changed = 203;
GarageModeInfo garage_mode_info = 204;
TestAtomReported test_atom_reported = 205 [(log_from_module) = "cts"];
@@ -6421,42 +6419,6 @@
}
/**
- * Logs information about Vehicle Map Service packets. It specifies the layer
- * type, subtype, data version and size of the packet.
- */
-message VehicleMapServicePacketReported {
- // Identifies the type of data being transported by a Vehicle Maps Service
- // packet.
- optional int32 layer_type = 1;
- // Identifies the subtype of the layer.
- optional int32 layer_subtype = 2;
- // Identifies the version of the data being transported by a Vehicle Maps
- // Service packet.
- optional int32 version = 3;
- // Size in bytes of the packet.
- optional int64 size_in_bytes = 4;
-}
-
-/**
- * Logs that a Vehicle Map Service packet failed to be delivered.
- */
-message VehicleMapServicePacketFailureReported {
- // Identifies the type of data being transported by a Vehicle Map Service
- // packet.
- optional int32 layer_type = 1;
- // Identifies the subtype of the layer.
- optional int32 layer_subtype = 2;
- // Identifies the version of the data being transported by a Vehicle Map
- // Service packet.
- optional int32 version = 3;
- // Identifies the package name of the publisher of the data.
- optional string publisher_name = 4;
- // Identifies the package name of the subscriber of the data. An empty string
- // signifies that there are zero subscribers for the packet.
- optional string subscriber_name = 5;
-}
-
-/**
* Logs when Car Power state changed.
*
* Logged from:
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 654e698..941eda8 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4090,6 +4090,7 @@
* @hide
* @see #getSystemService(String)
*/
+ @TestApi
public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
/**
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b09eada..cb939f0 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -807,7 +807,7 @@
*
* @hide
*/
- public static final int INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS = 0x00000200;
+ public static final int INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS = 0x00400000;
/** {@hide} */
public static final int INSTALL_FORCE_VOLUME_UUID = 0x00000200;
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 7522bfb..89be000 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -48,6 +48,7 @@
import android.annotation.StringRes;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
+import android.apex.ApexInfo;
import android.app.ActivityTaskManager;
import android.app.ActivityThread;
import android.app.ResourcesManager;
@@ -8382,20 +8383,29 @@
* PackageInfo parser specifically for apex files.
* NOTE: It will collect certificates
*
- * @param apexFile
+ * @param apexInfo
* @return PackageInfo
* @throws PackageParserException
*/
- public static PackageInfo generatePackageInfoFromApex(File apexFile, int flags)
+ public static PackageInfo generatePackageInfoFromApex(ApexInfo apexInfo, int flags)
throws PackageParserException {
PackageParser pp = new PackageParser();
+ File apexFile = new File(apexInfo.packagePath);
final Package p = pp.parsePackage(apexFile, flags, false);
PackageUserState state = new PackageUserState();
PackageInfo pi = generatePackageInfo(p, EmptyArray.INT, flags, 0, 0,
Collections.emptySet(), state);
-
pi.applicationInfo.sourceDir = apexFile.getPath();
- pi.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
+ if (apexInfo.isFactory) {
+ pi.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+ } else {
+ pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM;
+ }
+ if (apexInfo.isActive) {
+ pi.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED;
+ } else {
+ pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED;
+ }
pi.isApex = true;
// Collect certificates
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 41be38a..58aacc2 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -153,14 +153,21 @@
public static final int CLONE_REMOVE_RES_NAMES = 1 << 3;
/** @hide */
+ public static final int CLONE_REMOVE_PERSON = 1 << 4;
+
+ /** @hide */
public static final int CLONE_REMOVE_FOR_CREATOR = CLONE_REMOVE_ICON | CLONE_REMOVE_RES_NAMES;
/** @hide */
public static final int CLONE_REMOVE_FOR_LAUNCHER = CLONE_REMOVE_ICON | CLONE_REMOVE_INTENT
- | CLONE_REMOVE_RES_NAMES;
+ | CLONE_REMOVE_RES_NAMES | CLONE_REMOVE_PERSON;
/** @hide */
public static final int CLONE_REMOVE_FOR_LAUNCHER_APPROVAL = CLONE_REMOVE_INTENT
+ | CLONE_REMOVE_RES_NAMES | CLONE_REMOVE_PERSON;
+
+ /** @hide */
+ public static final int CLONE_REMOVE_FOR_APP_PREDICTION = CLONE_REMOVE_ICON
| CLONE_REMOVE_RES_NAMES;
/** @hide */
@@ -169,8 +176,11 @@
CLONE_REMOVE_INTENT,
CLONE_REMOVE_NON_KEY_INFO,
CLONE_REMOVE_RES_NAMES,
+ CLONE_REMOVE_PERSON,
CLONE_REMOVE_FOR_CREATOR,
- CLONE_REMOVE_FOR_LAUNCHER
+ CLONE_REMOVE_FOR_LAUNCHER,
+ CLONE_REMOVE_FOR_LAUNCHER_APPROVAL,
+ CLONE_REMOVE_FOR_APP_PREDICTION
})
@Retention(RetentionPolicy.SOURCE)
public @interface CloneFlags {}
@@ -548,7 +558,9 @@
mDisabledMessage = source.mDisabledMessage;
mDisabledMessageResId = source.mDisabledMessageResId;
mCategories = cloneCategories(source.mCategories);
- mPersons = clonePersons(source.mPersons);
+ if ((cloneFlags & CLONE_REMOVE_PERSON) == 0) {
+ mPersons = clonePersons(source.mPersons);
+ }
if ((cloneFlags & CLONE_REMOVE_INTENT) == 0) {
mIntents = cloneIntents(source.mIntents);
mIntentPersistableExtrases =
diff --git a/core/java/android/net/DnsResolver.java b/core/java/android/net/DnsResolver.java
index 68826cb..4b2b4c3 100644
--- a/core/java/android/net/DnsResolver.java
+++ b/core/java/android/net/DnsResolver.java
@@ -16,16 +16,17 @@
package android.net;
+import static android.net.NetworkUtils.getDnsNetId;
import static android.net.NetworkUtils.resNetworkCancel;
import static android.net.NetworkUtils.resNetworkQuery;
import static android.net.NetworkUtils.resNetworkResult;
import static android.net.NetworkUtils.resNetworkSend;
+import static android.net.util.DnsUtils.haveIpv4;
+import static android.net.util.DnsUtils.haveIpv6;
+import static android.net.util.DnsUtils.rfc6724Sort;
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
-import static android.system.OsConstants.AF_INET;
-import static android.system.OsConstants.AF_INET6;
-import static android.system.OsConstants.IPPROTO_UDP;
-import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.ENONET;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
@@ -34,18 +35,12 @@
import android.os.CancellationSignal;
import android.os.Looper;
import android.system.ErrnoException;
-import android.system.Os;
import android.util.Log;
-import libcore.io.IoUtils;
-
import java.io.FileDescriptor;
-import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
@@ -196,8 +191,8 @@
final Object lock = new Object();
final FileDescriptor queryfd;
try {
- queryfd = resNetworkSend((network != null
- ? network.getNetIdForResolv() : NETID_UNSET), query, query.length, flags);
+ queryfd = resNetworkSend((network != null)
+ ? network.getNetIdForResolv() : NETID_UNSET, query, query.length, flags);
} catch (ErrnoException e) {
executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
return;
@@ -237,8 +232,8 @@
final Object lock = new Object();
final FileDescriptor queryfd;
try {
- queryfd = resNetworkQuery((network != null
- ? network.getNetIdForResolv() : NETID_UNSET), domain, nsClass, nsType, flags);
+ queryfd = resNetworkQuery((network != null)
+ ? network.getNetIdForResolv() : NETID_UNSET, domain, nsClass, nsType, flags);
} catch (ErrnoException e) {
executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
return;
@@ -252,14 +247,16 @@
private class InetAddressAnswerAccumulator implements Callback<byte[]> {
private final List<InetAddress> mAllAnswers;
+ private final Network mNetwork;
private int mRcode;
private DnsException mDnsException;
private final Callback<? super List<InetAddress>> mUserCallback;
private final int mTargetAnswerCount;
private int mReceivedAnswerCount = 0;
- InetAddressAnswerAccumulator(int size,
+ InetAddressAnswerAccumulator(@NonNull Network network, int size,
@NonNull Callback<? super List<InetAddress>> callback) {
+ mNetwork = network;
mTargetAnswerCount = size;
mAllAnswers = new ArrayList<>();
mUserCallback = callback;
@@ -280,8 +277,7 @@
private void maybeReportAnswer() {
if (++mReceivedAnswerCount != mTargetAnswerCount) return;
if (mAllAnswers.isEmpty() && maybeReportError()) return;
- // TODO: Do RFC6724 sort.
- mUserCallback.onAnswer(mAllAnswers, mRcode);
+ mUserCallback.onAnswer(rfc6724Sort(mNetwork, mAllAnswers), mRcode);
}
@Override
@@ -308,7 +304,7 @@
/**
* Send a DNS query with the specified name on a network with both IPv4 and IPv6,
- * get back a set of InetAddresses asynchronously.
+ * get back a set of InetAddresses with rfc6724 sorting style asynchronously.
*
* This method will examine the connection ability on given network, and query IPv4
* and IPv6 if connection is available.
@@ -335,8 +331,23 @@
return;
}
final Object lock = new Object();
- final boolean queryIpv6 = haveIpv6(network);
- final boolean queryIpv4 = haveIpv4(network);
+ final Network queryNetwork;
+ try {
+ queryNetwork = (network != null) ? network : new Network(getDnsNetId());
+ } catch (ErrnoException e) {
+ executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
+ return;
+ }
+ final boolean queryIpv6 = haveIpv6(queryNetwork);
+ final boolean queryIpv4 = haveIpv4(queryNetwork);
+
+ // This can only happen if queryIpv4 and queryIpv6 are both false.
+ // This almost certainly means that queryNetwork does not exist or no longer exists.
+ if (!queryIpv6 && !queryIpv4) {
+ executor.execute(() -> callback.onError(
+ new DnsException(ERROR_SYSTEM, new ErrnoException("resNetworkQuery", ENONET))));
+ return;
+ }
final FileDescriptor v4fd;
final FileDescriptor v6fd;
@@ -345,9 +356,8 @@
if (queryIpv6) {
try {
- v6fd = resNetworkQuery((network != null
- ? network.getNetIdForResolv() : NETID_UNSET),
- domain, CLASS_IN, TYPE_AAAA, flags);
+ v6fd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN,
+ TYPE_AAAA, flags);
} catch (ErrnoException e) {
executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
return;
@@ -355,7 +365,6 @@
queryCount++;
} else v6fd = null;
- // TODO: Use device flag to control the sleep time.
// Avoiding gateways drop packets if queries are sent too close together
try {
Thread.sleep(SLEEP_TIME_MS);
@@ -365,9 +374,8 @@
if (queryIpv4) {
try {
- v4fd = resNetworkQuery((network != null
- ? network.getNetIdForResolv() : NETID_UNSET),
- domain, CLASS_IN, TYPE_A, flags);
+ v4fd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN, TYPE_A,
+ flags);
} catch (ErrnoException e) {
if (queryIpv6) resNetworkCancel(v6fd); // Closes fd, marks it invalid.
executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
@@ -377,7 +385,7 @@
} else v4fd = null;
final InetAddressAnswerAccumulator accumulator =
- new InetAddressAnswerAccumulator(queryCount, callback);
+ new InetAddressAnswerAccumulator(queryNetwork, queryCount, callback);
synchronized (lock) {
if (queryIpv6) {
@@ -398,7 +406,7 @@
/**
* Send a DNS query with the specified name and query type, get back a set of
- * InetAddresses asynchronously.
+ * InetAddresses with rfc6724 sorting style asynchronously.
*
* The answer will be provided asynchronously through the provided {@link Callback}.
*
@@ -423,15 +431,17 @@
}
final Object lock = new Object();
final FileDescriptor queryfd;
+ final Network queryNetwork;
try {
- queryfd = resNetworkQuery((network != null
- ? network.getNetIdForResolv() : NETID_UNSET), domain, CLASS_IN, nsType, flags);
+ queryNetwork = (network != null) ? network : new Network(getDnsNetId());
+ queryfd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN, nsType,
+ flags);
} catch (ErrnoException e) {
executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
return;
}
final InetAddressAnswerAccumulator accumulator =
- new InetAddressAnswerAccumulator(1, callback);
+ new InetAddressAnswerAccumulator(queryNetwork, 1, callback);
synchronized (lock) {
registerFDListener(executor, queryfd, accumulator, cancellationSignal, lock);
if (cancellationSignal == null) return;
@@ -500,38 +510,6 @@
});
}
- // These two functions match the behaviour of have_ipv4 and have_ipv6 in the native resolver.
- private boolean haveIpv4(@Nullable Network network) {
- final SocketAddress addrIpv4 =
- new InetSocketAddress(InetAddresses.parseNumericAddress("8.8.8.8"), 0);
- return checkConnectivity(network, AF_INET, addrIpv4);
- }
-
- private boolean haveIpv6(@Nullable Network network) {
- final SocketAddress addrIpv6 =
- new InetSocketAddress(InetAddresses.parseNumericAddress("2000::"), 0);
- return checkConnectivity(network, AF_INET6, addrIpv6);
- }
-
- private boolean checkConnectivity(@Nullable Network network,
- int domain, @NonNull SocketAddress addr) {
- final FileDescriptor socket;
- try {
- socket = Os.socket(domain, SOCK_DGRAM, IPPROTO_UDP);
- } catch (ErrnoException e) {
- return false;
- }
- try {
- if (network != null) network.bindSocket(socket);
- Os.connect(socket, addr);
- } catch (IOException | ErrnoException e) {
- return false;
- } finally {
- IoUtils.closeQuietly(socket);
- }
- return true;
- }
-
private static class DnsAddressAnswer extends DnsPacket {
private static final String TAG = "DnsResolver.DnsAddressAnswer";
private static final boolean DBG = false;
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index c06a132..a640f83 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -157,6 +157,13 @@
public static native void resNetworkCancel(FileDescriptor fd);
/**
+ * DNS resolver series jni method.
+ * Attempts to get netid of network which resolver will
+ * use if no network is explicitly selected.
+ */
+ public static native int getDnsNetId() throws ErrnoException;
+
+ /**
* Get the tcp repair window associated with the {@code fd}.
*
* @param fd the tcp socket's {@link FileDescriptor}.
diff --git a/core/java/android/net/StaticIpConfiguration.java b/core/java/android/net/StaticIpConfiguration.java
index fe42111..050fcfa 100644
--- a/core/java/android/net/StaticIpConfiguration.java
+++ b/core/java/android/net/StaticIpConfiguration.java
@@ -22,7 +22,6 @@
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.net.shared.InetAddressUtils;
-import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -54,7 +53,7 @@
@TestApi
public final class StaticIpConfiguration implements Parcelable {
/** @hide */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ @UnsupportedAppUsage
@Nullable
public LinkAddress ipAddress;
/** @hide */
diff --git a/core/java/android/net/util/DnsUtils.java b/core/java/android/net/util/DnsUtils.java
new file mode 100644
index 0000000..e6abd50
--- /dev/null
+++ b/core/java/android/net/util/DnsUtils.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.util;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.InetAddresses;
+import android.net.Network;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import com.android.internal.util.BitUtils;
+
+import libcore.io.IoUtils;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * @hide
+ */
+public class DnsUtils {
+ private static final String TAG = "DnsUtils";
+ private static final int CHAR_BIT = 8;
+ public static final int IPV6_ADDR_SCOPE_NODELOCAL = 0x01;
+ public static final int IPV6_ADDR_SCOPE_LINKLOCAL = 0x02;
+ public static final int IPV6_ADDR_SCOPE_SITELOCAL = 0x05;
+ public static final int IPV6_ADDR_SCOPE_GLOBAL = 0x0e;
+ private static final Comparator<SortableAddress> sRfc6724Comparator = new Rfc6724Comparator();
+
+ /**
+ * Comparator to sort SortableAddress in Rfc6724 style.
+ */
+ public static class Rfc6724Comparator implements Comparator<SortableAddress> {
+ // This function matches the behaviour of _rfc6724_compare in the native resolver.
+ @Override
+ public int compare(SortableAddress span1, SortableAddress span2) {
+ // Rule 1: Avoid unusable destinations.
+ if (span1.hasSrcAddr != span2.hasSrcAddr) {
+ return span2.hasSrcAddr - span1.hasSrcAddr;
+ }
+
+ // Rule 2: Prefer matching scope.
+ if (span1.scopeMatch != span2.scopeMatch) {
+ return span2.scopeMatch - span1.scopeMatch;
+ }
+
+ // TODO: Implement rule 3: Avoid deprecated addresses.
+ // TODO: Implement rule 4: Prefer home addresses.
+
+ // Rule 5: Prefer matching label.
+ if (span1.labelMatch != span2.labelMatch) {
+ return span2.labelMatch - span1.labelMatch;
+ }
+
+ // Rule 6: Prefer higher precedence.
+ if (span1.precedence != span2.precedence) {
+ return span2.precedence - span1.precedence;
+ }
+
+ // TODO: Implement rule 7: Prefer native transport.
+
+ // Rule 8: Prefer smaller scope.
+ if (span1.scope != span2.scope) {
+ return span1.scope - span2.scope;
+ }
+
+ // Rule 9: Use longest matching prefix. IPv6 only.
+ if (span1.prefixMatchLen != span2.prefixMatchLen) {
+ return span2.prefixMatchLen - span1.prefixMatchLen;
+ }
+
+ // Rule 10: Leave the order unchanged. Collections.sort is a stable sort.
+ return 0;
+ }
+ }
+
+ /**
+ * Class used to sort with RFC 6724
+ */
+ public static class SortableAddress {
+ public final int label;
+ public final int labelMatch;
+ public final int scope;
+ public final int scopeMatch;
+ public final int precedence;
+ public final int prefixMatchLen;
+ public final int hasSrcAddr;
+ public final InetAddress address;
+
+ public SortableAddress(@NonNull InetAddress addr, @Nullable InetAddress srcAddr) {
+ address = addr;
+ hasSrcAddr = (srcAddr != null) ? 1 : 0;
+ label = findLabel(addr);
+ scope = findScope(addr);
+ precedence = findPrecedence(addr);
+ labelMatch = ((srcAddr != null) && (label == findLabel(srcAddr))) ? 1 : 0;
+ scopeMatch = ((srcAddr != null) && (scope == findScope(srcAddr))) ? 1 : 0;
+ if (isIpv6Address(addr) && isIpv6Address(srcAddr)) {
+ prefixMatchLen = compareIpv6PrefixMatchLen(srcAddr, addr);
+ } else {
+ prefixMatchLen = 0;
+ }
+ }
+ }
+
+ /**
+ * Sort the given address list in RFC6724 order.
+ * Will leave the list unchanged if an error occurs.
+ *
+ * This function matches the behaviour of _rfc6724_sort in the native resolver.
+ */
+ public static @NonNull List<InetAddress> rfc6724Sort(@Nullable Network network,
+ @NonNull List<InetAddress> answers) {
+ List<SortableAddress> sortableAnswerList = new ArrayList<>();
+ answers.forEach(addr -> sortableAnswerList.add(
+ new SortableAddress(addr, findSrcAddress(network, addr))));
+
+ Collections.sort(sortableAnswerList, sRfc6724Comparator);
+
+ final List<InetAddress> sortedAnswers = new ArrayList<>();
+ sortableAnswerList.forEach(ans -> sortedAnswers.add(ans.address));
+
+ return sortedAnswers;
+ }
+
+ private static @Nullable InetAddress findSrcAddress(@Nullable Network network,
+ @NonNull InetAddress addr) {
+ final int domain;
+ if (isIpv4Address(addr)) {
+ domain = AF_INET;
+ } else if (isIpv6Address(addr)) {
+ domain = AF_INET6;
+ } else {
+ return null;
+ }
+ final FileDescriptor socket;
+ try {
+ socket = Os.socket(domain, SOCK_DGRAM, IPPROTO_UDP);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "findSrcAddress:" + e.toString());
+ return null;
+ }
+ try {
+ if (network != null) network.bindSocket(socket);
+ Os.connect(socket, new InetSocketAddress(addr, 0));
+ return ((InetSocketAddress) Os.getsockname(socket)).getAddress();
+ } catch (IOException | ErrnoException e) {
+ return null;
+ } finally {
+ IoUtils.closeQuietly(socket);
+ }
+ }
+
+ /**
+ * Get the label for a given IPv4/IPv6 address.
+ * RFC 6724, section 2.1.
+ *
+ * Note that Java will return an IPv4-mapped address as an IPv4 address.
+ */
+ private static int findLabel(@NonNull InetAddress addr) {
+ if (isIpv4Address(addr)) {
+ return 4;
+ } else if (isIpv6Address(addr)) {
+ if (addr.isLoopbackAddress()) {
+ return 0;
+ } else if (isIpv6Address6To4(addr)) {
+ return 2;
+ } else if (isIpv6AddressTeredo(addr)) {
+ return 5;
+ } else if (isIpv6AddressULA(addr)) {
+ return 13;
+ } else if (((Inet6Address) addr).isIPv4CompatibleAddress()) {
+ return 3;
+ } else if (addr.isSiteLocalAddress()) {
+ return 11;
+ } else if (isIpv6Address6Bone(addr)) {
+ return 12;
+ } else {
+ // All other IPv6 addresses, including global unicast addresses.
+ return 1;
+ }
+ } else {
+ // This should never happen.
+ return 1;
+ }
+ }
+
+ private static boolean isIpv6Address(@Nullable InetAddress addr) {
+ return addr instanceof Inet6Address;
+ }
+
+ private static boolean isIpv4Address(@Nullable InetAddress addr) {
+ return addr instanceof Inet4Address;
+ }
+
+ private static boolean isIpv6Address6To4(@NonNull InetAddress addr) {
+ if (!isIpv6Address(addr)) return false;
+ final byte[] byteAddr = addr.getAddress();
+ return byteAddr[0] == 0x20 && byteAddr[1] == 0x02;
+ }
+
+ private static boolean isIpv6AddressTeredo(@NonNull InetAddress addr) {
+ if (!isIpv6Address(addr)) return false;
+ final byte[] byteAddr = addr.getAddress();
+ return byteAddr[0] == 0x20 && byteAddr[1] == 0x01 && byteAddr[2] == 0x00
+ && byteAddr[3] == 0x00;
+ }
+
+ private static boolean isIpv6AddressULA(@NonNull InetAddress addr) {
+ return isIpv6Address(addr) && (addr.getAddress()[0] & 0xfe) == 0xfc;
+ }
+
+ private static boolean isIpv6Address6Bone(@NonNull InetAddress addr) {
+ if (!isIpv6Address(addr)) return false;
+ final byte[] byteAddr = addr.getAddress();
+ return byteAddr[0] == 0x3f && byteAddr[1] == (byte) 0xfe;
+ }
+
+ private static int getIpv6MulticastScope(@NonNull InetAddress addr) {
+ return !isIpv6Address(addr) ? 0 : (addr.getAddress()[1] & 0x0f);
+ }
+
+ private static int findScope(@NonNull InetAddress addr) {
+ if (isIpv6Address(addr)) {
+ if (addr.isMulticastAddress()) {
+ return getIpv6MulticastScope(addr);
+ } else if (addr.isLoopbackAddress() || addr.isLinkLocalAddress()) {
+ /**
+ * RFC 4291 section 2.5.3 says loopback is to be treated as having
+ * link-local scope.
+ */
+ return IPV6_ADDR_SCOPE_LINKLOCAL;
+ } else if (addr.isSiteLocalAddress()) {
+ return IPV6_ADDR_SCOPE_SITELOCAL;
+ } else {
+ return IPV6_ADDR_SCOPE_GLOBAL;
+ }
+ } else if (isIpv4Address(addr)) {
+ if (addr.isLoopbackAddress() || addr.isLinkLocalAddress()) {
+ return IPV6_ADDR_SCOPE_LINKLOCAL;
+ } else {
+ /**
+ * RFC 6724 section 3.2. Other IPv4 addresses, including private addresses
+ * and shared addresses (100.64.0.0/10), are assigned global scope.
+ */
+ return IPV6_ADDR_SCOPE_GLOBAL;
+ }
+ } else {
+ /**
+ * This should never happen.
+ * Return a scope with low priority as a last resort.
+ */
+ return IPV6_ADDR_SCOPE_NODELOCAL;
+ }
+ }
+
+ /**
+ * Get the precedence for a given IPv4/IPv6 address.
+ * RFC 6724, section 2.1.
+ *
+ * Note that Java will return an IPv4-mapped address as an IPv4 address.
+ */
+ private static int findPrecedence(@NonNull InetAddress addr) {
+ if (isIpv4Address(addr)) {
+ return 35;
+ } else if (isIpv6Address(addr)) {
+ if (addr.isLoopbackAddress()) {
+ return 50;
+ } else if (isIpv6Address6To4(addr)) {
+ return 30;
+ } else if (isIpv6AddressTeredo(addr)) {
+ return 5;
+ } else if (isIpv6AddressULA(addr)) {
+ return 3;
+ } else if (((Inet6Address) addr).isIPv4CompatibleAddress() || addr.isSiteLocalAddress()
+ || isIpv6Address6Bone(addr)) {
+ return 1;
+ } else {
+ // All other IPv6 addresses, including global unicast addresses.
+ return 40;
+ }
+ } else {
+ return 1;
+ }
+ }
+
+ /**
+ * Find number of matching initial bits between the two addresses.
+ */
+ private static int compareIpv6PrefixMatchLen(@NonNull InetAddress srcAddr,
+ @NonNull InetAddress dstAddr) {
+ final byte[] srcByte = srcAddr.getAddress();
+ final byte[] dstByte = dstAddr.getAddress();
+
+ // This should never happen.
+ if (srcByte.length != dstByte.length) return 0;
+
+ for (int i = 0; i < dstByte.length; ++i) {
+ if (srcByte[i] == dstByte[i]) {
+ continue;
+ }
+ int x = BitUtils.uint8(srcByte[i]) ^ BitUtils.uint8(dstByte[i]);
+ return i * CHAR_BIT + (Integer.numberOfLeadingZeros(x) - 24); // Java ints are 32 bits
+ }
+ return dstByte.length * CHAR_BIT;
+ }
+
+ /**
+ * Check if given network has Ipv4 capability
+ * This function matches the behaviour of have_ipv4 in the native resolver.
+ */
+ public static boolean haveIpv4(@Nullable Network network) {
+ final SocketAddress addrIpv4 =
+ new InetSocketAddress(InetAddresses.parseNumericAddress("8.8.8.8"), 0);
+ return checkConnectivity(network, AF_INET, addrIpv4);
+ }
+
+ /**
+ * Check if given network has Ipv6 capability
+ * This function matches the behaviour of have_ipv6 in the native resolver.
+ */
+ public static boolean haveIpv6(@Nullable Network network) {
+ final SocketAddress addrIpv6 =
+ new InetSocketAddress(InetAddresses.parseNumericAddress("2000::"), 0);
+ return checkConnectivity(network, AF_INET6, addrIpv6);
+ }
+
+ private static boolean checkConnectivity(@Nullable Network network,
+ int domain, @NonNull SocketAddress addr) {
+ final FileDescriptor socket;
+ try {
+ socket = Os.socket(domain, SOCK_DGRAM, IPPROTO_UDP);
+ } catch (ErrnoException e) {
+ return false;
+ }
+ try {
+ if (network != null) network.bindSocket(socket);
+ Os.connect(socket, addr);
+ } catch (IOException | ErrnoException e) {
+ return false;
+ } finally {
+ IoUtils.closeQuietly(socket);
+ }
+ return true;
+ }
+}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index de963c9..fe2e948 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -2001,6 +2002,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @TestApi
public final int readExceptionCode() {
int code = readInt();
if (code == EX_HAS_REPLY_HEADER) {
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index edfdda8bb..4538410 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -174,6 +174,7 @@
* @hide
*/
@SystemApi
+ @TestApi
public static boolean getBoolean(@NonNull String key, boolean def) {
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get_boolean(key, def);
diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java
index f1f24fb..921f0f2 100644
--- a/core/java/android/os/image/DynamicSystemClient.java
+++ b/core/java/android/os/image/DynamicSystemClient.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -67,6 +68,7 @@
* @hide
*/
@SystemApi
+@TestApi
public class DynamicSystemClient {
/** @hide */
@IntDef(prefix = { "STATUS_" }, value = {
@@ -283,6 +285,7 @@
* @hide
*/
@SystemApi
+ @TestApi
public DynamicSystemClient(@NonNull Context context) {
mContext = context;
mConnection = new DynSystemServiceConnection();
@@ -314,8 +317,11 @@
* Bind to {@code DynamicSystem} installation service. Binding to the installation service
* allows it to send status updates to {@link #OnStatusChangedListener}. It is recommanded
* to bind before calling {@link #start} and get status updates.
+ * @hide
*/
@RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM)
+ @SystemApi
+ @TestApi
public void bind() {
if (!featureFlagEnabled()) {
Slog.w(TAG, FeatureFlagUtils.DYNAMIC_SYSTEM + " not enabled; bind() aborted.");
@@ -334,8 +340,11 @@
/**
* Unbind from {@code DynamicSystem} installation service. Unbinding from the installation
* service stops it from sending following status updates.
+ * @hide
*/
@RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM)
+ @SystemApi
+ @TestApi
public void unbind() {
if (!mBound) {
return;
@@ -367,8 +376,11 @@
*
* @param systemUrl a network Uri, a file Uri or a content Uri pointing to a system image file.
* @param systemSize size of system image.
+ * @hide
*/
@RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM)
+ @SystemApi
+ @TestApi
public void start(@NonNull Uri systemUrl, @BytesLong long systemSize) {
start(systemUrl, systemSize, DEFAULT_USERDATA_SIZE);
}
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index 6e83e5a..3ab5c51 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -173,6 +173,7 @@
*
* @hide
*/
+ @TestApi
public interface OnGetAppPermissionResultCallback {
/**
* The result for {@link #getAppPermissions(String, OnGetAppPermissionResultCallback,
@@ -385,6 +386,7 @@
*
* @hide
*/
+ @TestApi
@RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
public void getAppPermissions(@NonNull String packageName,
@NonNull OnGetAppPermissionResultCallback callback, @Nullable Handler handler) {
diff --git a/core/java/android/permission/RuntimePermissionPresentationInfo.java b/core/java/android/permission/RuntimePermissionPresentationInfo.java
index 4fce14c..d696fea 100644
--- a/core/java/android/permission/RuntimePermissionPresentationInfo.java
+++ b/core/java/android/permission/RuntimePermissionPresentationInfo.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -32,6 +33,7 @@
*
* @hide
*/
+@TestApi
@SystemApi
public final class RuntimePermissionPresentationInfo implements Parcelable {
private static final int FLAG_GRANTED = 1 << 0;
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index e81ce7f..aa11445 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -16,6 +16,7 @@
package android.service.notification;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.StringDef;
import android.annotation.SystemApi;
import android.annotation.TestApi;
@@ -48,6 +49,7 @@
private final CharSequence mExplanation;
private final Bundle mSignals;
private final int mUser;
+ @Nullable private String mIssuer;
/** @hide */
@StringDef (prefix = { "KEY_" }, value = {
@@ -183,6 +185,7 @@
}
mSignals = in.readBundle();
mUser = in.readInt();
+ mIssuer = in.readString();
}
public static final @android.annotation.NonNull Creator<Adjustment> CREATOR = new Creator<Adjustment>() {
@@ -251,6 +254,7 @@
}
dest.writeBundle(mSignals);
dest.writeInt(mUser);
+ dest.writeString(mIssuer);
}
@Override
@@ -259,4 +263,14 @@
+ "mSignals=" + mSignals
+ '}';
}
+
+ /** @hide */
+ public void setIssuer(@Nullable String issuer) {
+ mIssuer = issuer;
+ }
+
+ /** @hide */
+ public @Nullable String getIssuer() {
+ return mIssuer;
+ }
}
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index cafeb87..12d3228 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -236,6 +236,7 @@
public final void adjustNotification(@NonNull Adjustment adjustment) {
if (!isBound()) return;
try {
+ setAdjustmentIssuer(adjustment);
getNotificationInterface().applyEnqueuedAdjustmentFromAssistant(mWrapper, adjustment);
} catch (android.os.RemoteException ex) {
Log.v(TAG, "Unable to contact notification manager", ex);
@@ -253,6 +254,9 @@
public final void adjustNotifications(@NonNull List<Adjustment> adjustments) {
if (!isBound()) return;
try {
+ for (Adjustment adjustment : adjustments) {
+ setAdjustmentIssuer(adjustment);
+ }
getNotificationInterface().applyAdjustmentsFromAssistant(mWrapper, adjustments);
} catch (android.os.RemoteException ex) {
Log.v(TAG, "Unable to contact notification manager", ex);
@@ -366,6 +370,12 @@
}
}
+ private void setAdjustmentIssuer(@Nullable Adjustment adjustment) {
+ if (adjustment != null) {
+ adjustment.setIssuer(getOpPackageName() + "/" + getClass().getName());
+ }
+ }
+
private final class MyHandler extends Handler {
public static final int MSG_ON_NOTIFICATION_ENQUEUED = 1;
public static final int MSG_ON_NOTIFICATION_SNOOZED = 2;
@@ -389,6 +399,7 @@
NotificationChannel channel = (NotificationChannel) args.arg2;
args.recycle();
Adjustment adjustment = onNotificationEnqueued(sbn, channel);
+ setAdjustmentIssuer(adjustment);
if (adjustment != null) {
if (!isBound()) {
Log.w(TAG, "MSG_ON_NOTIFICATION_ENQUEUED: service not bound, skip.");
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index c42dc81..324e02c 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -16,6 +16,7 @@
package android.util;
+import android.annotation.TestApi;
import android.content.Context;
import android.os.SystemProperties;
import android.provider.Settings;
@@ -29,6 +30,7 @@
*
* @hide
*/
+@TestApi
public class FeatureFlagUtils {
public static final String FFLAG_PREFIX = "sys.fflag.";
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index a88c51a..fca97fe 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -185,7 +185,7 @@
private static final int SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
- private static final int WATCHDOG_TIMEOUT_MILLIS = 3000;
+ private static final int WATCHDOG_TIMEOUT_MILLIS = 5000;
private static final int DEFAULT_SALT_EXPIRATION_DAYS = 7;
private int mMaxHashSaltDays = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
@@ -252,6 +252,123 @@
// Sorted list of DisplayResolveInfos for the alphabetical app section.
private List<ResolverActivity.DisplayResolveInfo> mSortedList = new ArrayList<>();
+ private ContentPreviewCoordinator mPreviewCoord;
+
+ private class ContentPreviewCoordinator {
+ private static final int IMAGE_LOAD_TIMEOUT_MILLIS = 300;
+ private static final int IMAGE_FADE_IN_MILLIS = 150;
+ private static final int IMAGE_LOAD_TIMEOUT = 1;
+ private static final int IMAGE_LOAD_INTO_VIEW = 2;
+
+ private final View mParentView;
+ private boolean mHideParentOnFail;
+ private boolean mAtLeastOneLoaded = false;
+
+ class LoadUriTask {
+ public final Uri mUri;
+ public final int mImageResourceId;
+ public final int mExtraCount;
+ public final Bitmap mBmp;
+
+ LoadUriTask(int imageResourceId, Uri uri, int extraCount, Bitmap bmp) {
+ this.mImageResourceId = imageResourceId;
+ this.mUri = uri;
+ this.mExtraCount = extraCount;
+ this.mBmp = bmp;
+ }
+ }
+
+ // If at least one image loads within the timeout period, allow other
+ // loads to continue. Otherwise terminate and optionally hide
+ // the parent area
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case IMAGE_LOAD_TIMEOUT:
+ maybeHideContentPreview();
+ break;
+
+ case IMAGE_LOAD_INTO_VIEW:
+ if (isFinishing()) break;
+
+ LoadUriTask task = (LoadUriTask) msg.obj;
+ RoundedRectImageView imageView = mParentView.findViewById(
+ task.mImageResourceId);
+ if (task.mBmp == null) {
+ imageView.setVisibility(View.GONE);
+ maybeHideContentPreview();
+ return;
+ }
+
+ mAtLeastOneLoaded = true;
+ imageView.setVisibility(View.VISIBLE);
+ imageView.setAlpha(0.0f);
+ imageView.setImageBitmap(task.mBmp);
+
+ ValueAnimator fadeAnim = ObjectAnimator.ofFloat(imageView, "alpha", 0.0f,
+ 1.0f);
+ fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
+ fadeAnim.setDuration(IMAGE_FADE_IN_MILLIS);
+ fadeAnim.start();
+
+ if (task.mExtraCount > 0) {
+ imageView.setExtraImageCount(task.mExtraCount);
+ }
+ }
+ }
+ };
+
+ ContentPreviewCoordinator(View parentView, boolean hideParentOnFail) {
+ super();
+
+ this.mParentView = parentView;
+ this.mHideParentOnFail = hideParentOnFail;
+ }
+
+ private void loadUriIntoView(final int imageResourceId, final Uri uri,
+ final int extraImages) {
+ mHandler.sendEmptyMessageDelayed(IMAGE_LOAD_TIMEOUT, IMAGE_LOAD_TIMEOUT_MILLIS);
+
+ AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
+ final Bitmap bmp = loadThumbnail(uri, new Size(200, 200));
+ final Message msg = Message.obtain();
+ msg.what = IMAGE_LOAD_INTO_VIEW;
+ msg.obj = new LoadUriTask(imageResourceId, uri, extraImages, bmp);
+ mHandler.sendMessage(msg);
+ });
+ }
+
+ private void cancelLoads() {
+ mHandler.removeMessages(IMAGE_LOAD_INTO_VIEW);
+ mHandler.removeMessages(IMAGE_LOAD_TIMEOUT);
+ }
+
+ private void maybeHideContentPreview() {
+ if (!mAtLeastOneLoaded && mHideParentOnFail) {
+ Log.i(TAG, "Hiding image preview area. Timed out waiting for preview to load"
+ + " within " + IMAGE_LOAD_TIMEOUT_MILLIS + "ms.");
+ collapseParentView();
+ if (mChooserRowAdapter != null) {
+ mChooserRowAdapter.hideContentPreview();
+ }
+ mHideParentOnFail = false;
+ }
+ }
+
+ private void collapseParentView() {
+ // This will effectively hide the content preview row by forcing the height
+ // to zero. It is faster than forcing a relayout of the listview
+ final View v = mParentView;
+ int widthSpec = MeasureSpec.makeMeasureSpec(v.getWidth(), MeasureSpec.EXACTLY);
+ int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
+ v.measure(widthSpec, heightSpec);
+ v.getLayoutParams().height = 0;
+ v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getTop());
+ v.invalidate();
+ }
+ }
+
private final Handler mChooserHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
@@ -621,14 +738,15 @@
private ViewGroup displayContentPreview(@ContentPreviewType int previewType,
Intent targetIntent, LayoutInflater layoutInflater, ViewGroup convertView,
ViewGroup parent) {
+ if (convertView != null) return convertView;
+
switch (previewType) {
case CONTENT_PREVIEW_TEXT:
- return displayTextContentPreview(targetIntent, layoutInflater, convertView, parent);
+ return displayTextContentPreview(targetIntent, layoutInflater, parent);
case CONTENT_PREVIEW_IMAGE:
- return displayImageContentPreview(targetIntent, layoutInflater, convertView,
- parent);
+ return displayImageContentPreview(targetIntent, layoutInflater, parent);
case CONTENT_PREVIEW_FILE:
- return displayFileContentPreview(targetIntent, layoutInflater, convertView, parent);
+ return displayFileContentPreview(targetIntent, layoutInflater, parent);
default:
Log.e(TAG, "Unexpected content preview type: " + previewType);
}
@@ -637,10 +755,9 @@
}
private ViewGroup displayTextContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
- ViewGroup convertView, ViewGroup parent) {
- ViewGroup contentPreviewLayout =
- convertView != null ? convertView : (ViewGroup) layoutInflater.inflate(
- R.layout.chooser_grid_preview_text, parent, false);
+ ViewGroup parent) {
+ ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
+ R.layout.chooser_grid_preview_text, parent, false);
contentPreviewLayout.findViewById(R.id.copy_button).setOnClickListener(
this::onCopyButtonClicked);
@@ -677,12 +794,8 @@
if (previewThumbnail == null) {
previewThumbnailView.setVisibility(View.GONE);
} else {
- Bitmap bmp = loadThumbnail(previewThumbnail, new Size(100, 100));
- if (bmp == null) {
- previewThumbnailView.setVisibility(View.GONE);
- } else {
- previewThumbnailView.setImageBitmap(bmp);
- }
+ mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, false);
+ mPreviewCoord.loadUriIntoView(R.id.content_preview_thumbnail, previewThumbnail, 0);
}
}
@@ -690,15 +803,15 @@
}
private ViewGroup displayImageContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
- ViewGroup convertView, ViewGroup parent) {
- ViewGroup contentPreviewLayout =
- convertView != null ? convertView : (ViewGroup) layoutInflater.inflate(
- R.layout.chooser_grid_preview_image, parent, false);
+ ViewGroup parent) {
+ ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
+ R.layout.chooser_grid_preview_image, parent, false);
+ mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, true);
String action = targetIntent.getAction();
if (Intent.ACTION_SEND.equals(action)) {
Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
- loadUriIntoView(R.id.content_preview_image_1_large, uri, contentPreviewLayout);
+ mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, uri, 0);
} else {
ContentResolver resolver = getContentResolver();
@@ -717,21 +830,16 @@
return contentPreviewLayout;
}
- loadUriIntoView(R.id.content_preview_image_1_large, imageUris.get(0),
- contentPreviewLayout);
+ mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, imageUris.get(0), 0);
if (imageUris.size() == 2) {
- loadUriIntoView(R.id.content_preview_image_2_large, imageUris.get(1),
- contentPreviewLayout);
+ mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_large,
+ imageUris.get(1), 0);
} else if (imageUris.size() > 2) {
- loadUriIntoView(R.id.content_preview_image_2_small, imageUris.get(1),
- contentPreviewLayout);
- RoundedRectImageView imageView = loadUriIntoView(
- R.id.content_preview_image_3_small, imageUris.get(2), contentPreviewLayout);
-
- if (imageUris.size() > 3) {
- imageView.setExtraImageCount(imageUris.size() - 3);
- }
+ mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_small,
+ imageUris.get(1), 0);
+ mPreviewCoord.loadUriIntoView(R.id.content_preview_image_3_small,
+ imageUris.get(2), imageUris.size() - 3);
}
}
@@ -803,11 +911,10 @@
}
private ViewGroup displayFileContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
- ViewGroup convertView, ViewGroup parent) {
+ ViewGroup parent) {
- ViewGroup contentPreviewLayout =
- convertView != null ? convertView : (ViewGroup) layoutInflater.inflate(
- R.layout.chooser_grid_preview_file, parent, false);
+ ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
+ R.layout.chooser_grid_preview_file, parent, false);
// TODO(b/120417119): Disable file copy until after moving to sysui,
// due to permissions issues
@@ -839,6 +946,10 @@
R.id.content_preview_filename);
fileNameView.setText(fileName);
+ View thumbnailView = contentPreviewLayout.findViewById(
+ R.id.content_preview_file_thumbnail);
+ thumbnailView.setVisibility(View.GONE);
+
ImageView fileIconView = contentPreviewLayout.findViewById(
R.id.content_preview_file_icon);
fileIconView.setVisibility(View.VISIBLE);
@@ -849,32 +960,25 @@
return contentPreviewLayout;
}
- private void loadFileUriIntoView(Uri uri, View parent) {
+ private void loadFileUriIntoView(final Uri uri, final View parent) {
FileInfo fileInfo = extractFileInfo(uri, getContentResolver());
TextView fileNameView = parent.findViewById(R.id.content_preview_filename);
fileNameView.setText(fileInfo.name);
if (fileInfo.hasThumbnail) {
- loadUriIntoView(R.id.content_preview_file_thumbnail, uri, parent);
+ mPreviewCoord = new ContentPreviewCoordinator(parent, false);
+ mPreviewCoord.loadUriIntoView(R.id.content_preview_file_thumbnail, uri, 0);
} else {
+ View thumbnailView = parent.findViewById(R.id.content_preview_file_thumbnail);
+ thumbnailView.setVisibility(View.GONE);
+
ImageView fileIconView = parent.findViewById(R.id.content_preview_file_icon);
fileIconView.setVisibility(View.VISIBLE);
fileIconView.setImageResource(R.drawable.chooser_file_generic);
}
}
- private RoundedRectImageView loadUriIntoView(int imageResourceId, Uri uri, View parent) {
- RoundedRectImageView imageView = parent.findViewById(imageResourceId);
- Bitmap bmp = loadThumbnail(uri, new Size(200, 200));
- if (bmp != null) {
- imageView.setVisibility(View.VISIBLE);
- imageView.setImageBitmap(bmp);
- }
-
- return imageView;
- }
-
@VisibleForTesting
protected boolean isImageType(String mimeType) {
return mimeType != null && mimeType.startsWith("image/");
@@ -944,6 +1048,9 @@
mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
mChooserHandler.removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT);
mChooserHandler.removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED);
+
+ if (mPreviewCoord != null) mPreviewCoord.cancelLoads();
+
if (mAppPredictor != null) {
mAppPredictor.unregisterPredictionUpdates(mAppPredictorCallback);
mAppPredictor.destroy();
@@ -2036,9 +2143,12 @@
}
int availableWidth = right - left - v.getPaddingLeft() - v.getPaddingRight();
- if (mChooserRowAdapter.calculateChooserTargetWidth(availableWidth)
+ if (mChooserRowAdapter.consumeLayoutRequest()
+ || mChooserRowAdapter.calculateChooserTargetWidth(availableWidth)
|| mAdapterView.getAdapter() == null) {
- mAdapterView.setAdapter(mChooserRowAdapter);
+ if (mAdapterView.getAdapter() == null) {
+ mAdapterView.setAdapter(mChooserRowAdapter);
+ }
getMainThreadHandler().post(() -> {
if (mResolverDrawerLayout == null || mChooserRowAdapter == null) {
@@ -2589,6 +2699,9 @@
private int mChooserTargetWidth = 0;
private boolean mShowAzLabelIfPoss;
+ private boolean mHideContentPreview = false;
+ private boolean mLayoutRequested = false;
+
private static final int VIEW_TYPE_DIRECT_SHARE = 0;
private static final int VIEW_TYPE_NORMAL = 1;
private static final int VIEW_TYPE_CONTENT_PREVIEW = 2;
@@ -2651,6 +2764,18 @@
return maxTargets;
}
+ public void hideContentPreview() {
+ mHideContentPreview = true;
+ mLayoutRequested = true;
+ notifyDataSetChanged();
+ }
+
+ public boolean consumeLayoutRequest() {
+ boolean oldValue = mLayoutRequested;
+ mLayoutRequested = false;
+ return oldValue;
+ }
+
@Override
public boolean areAllItemsEnabled() {
return false;
@@ -2684,7 +2809,8 @@
return 0;
}
- if (mChooserListAdapter == null || mChooserListAdapter.getCount() == 0) {
+ if (mHideContentPreview || mChooserListAdapter == null
+ || mChooserListAdapter.getCount() == 0) {
return 0;
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index a5daa0a..538c81d 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1319,7 +1319,8 @@
buttonLayout.setVisibility(View.VISIBLE);
int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(),
- buttonLayout.getPaddingRight(), buttonLayout.getPaddingBottom() + inset);
+ buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize(
+ R.dimen.resolver_button_bar_spacing) + inset);
mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
mSettingsButton = (Button) buttonLayout.findViewById(R.id.button_app_settings);
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index f9f28da..daa6347 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -1301,18 +1301,6 @@
lpTrack->setParameters(param.toString());
}
-static void android_media_AudioTrack_set_eos(JNIEnv *env, jobject thiz) {
- sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
- if (lpTrack == NULL) {
- jniThrowException(env, "java/lang/IllegalStateException",
- "AudioTrack not initialized");
- return;
- }
- AudioParameter param = AudioParameter();
- param.addInt(String8("EOS"), 1);
- lpTrack->setParameters(param.toString());
-}
-
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
static const JNINativeMethod gMethods[] = {
@@ -1389,7 +1377,6 @@
{"native_setPresentation", "(II)I", (void *)android_media_AudioTrack_setPresentation},
{"native_getPortId", "()I", (void *)android_media_AudioTrack_get_port_id},
{"native_set_delay_padding", "(II)V", (void *)android_media_AudioTrack_set_delay_padding},
- {"native_set_eos", "()V", (void *)android_media_AudioTrack_set_eos},
};
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index c5fc9b3..00e0e3a 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -18,26 +18,27 @@
#include <vector>
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include <nativehelper/ScopedLocalRef.h>
-#include "NetdClient.h"
-#include <utils/misc.h>
-#include <android_runtime/AndroidRuntime.h>
-#include <utils/Log.h>
#include <arpa/inet.h>
-#include <net/if.h>
#include <linux/filter.h>
#include <linux/if_arp.h>
#include <linux/tcp.h>
+#include <net/if.h>
#include <netinet/ether.h>
#include <netinet/icmp6.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <netinet/udp.h>
-#include <cutils/properties.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <cutils/properties.h>
+#include <utils/misc.h>
+#include <utils/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+
+#include "NetdClient.h"
#include "core_jni_helpers.h"
+#include "jni.h"
extern "C" {
int ifc_enable(const char *ifname);
@@ -303,6 +304,15 @@
jniSetFileDescriptorOfFD(env, javaFd, -1);
}
+static jint android_net_utils_getDnsNetId(JNIEnv *env, jobject thiz) {
+ int dnsNetId = getNetworkForDns();
+ if (dnsNetId < 0) {
+ throwErrnoException(env, "getDnsNetId", -dnsNetId);
+ }
+
+ return dnsNetId;
+}
+
static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) {
if (javaFd == NULL) {
jniThrowNullPointerException(env, NULL);
@@ -359,6 +369,7 @@
{ "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
{ "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult },
{ "resNetworkCancel", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_resNetworkCancel },
+ { "getDnsNetId", "()I", (void*) android_net_utils_getDnsNetId },
};
int register_android_net_NetworkUtils(JNIEnv* env)
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index bff954d..ab7aed3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1697,6 +1697,7 @@
@hide -->
<permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"
android:protectionLevel="signature|privileged" />
+ <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
<!-- @SystemApi Allows an internal user to set signal strength in NetworkRequest. This kind of
request will wake up device when signal strength meets the given value.
diff --git a/core/res/res/layout/chooser_grid_preview_file.xml b/core/res/res/layout/chooser_grid_preview_file.xml
index 27c6041..f7d60c9 100644
--- a/core/res/res/layout/chooser_grid_preview_file.xml
+++ b/core/res/res/layout/chooser_grid_preview_file.xml
@@ -44,8 +44,7 @@
android:adjustViewBounds="true"
android:layout_gravity="center_vertical"
android:gravity="center"
- android:scaleType="centerCrop"
- android:visibility="gone"/>
+ android:scaleType="centerCrop"/>
<ImageView
android:id="@+id/content_preview_file_icon"
android:layout_width="36dp"
diff --git a/core/res/res/layout/chooser_grid_preview_image.xml b/core/res/res/layout/chooser_grid_preview_image.xml
index ad31e0d..79a0de4 100644
--- a/core/res/res/layout/chooser_grid_preview_image.xml
+++ b/core/res/res/layout/chooser_grid_preview_image.xml
@@ -33,7 +33,6 @@
<view class="com.android.internal.app.ChooserActivity$RoundedRectImageView"
android:id="@+id/content_preview_image_1_large"
- android:visibility="gone"
android:layout_width="120dp"
android:layout_height="140dp"
android:layout_alignParentTop="true"
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index 0697b0e..aeaccfd 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -100,8 +100,8 @@
android:layoutDirection="locale"
android:measureWithLargestChild="true"
android:background="?attr/colorBackgroundFloating"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
+ android:paddingTop="@dimen/resolver_button_bar_spacing"
+ android:paddingBottom="@dimen/resolver_button_bar_spacing"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:elevation="8dp">
diff --git a/packages/SystemUI/res/values-mcc310-mnc030/config.xml b/core/res/res/values-mcc310-mnc030/config.xml
similarity index 100%
rename from packages/SystemUI/res/values-mcc310-mnc030/config.xml
rename to core/res/res/values-mcc310-mnc030/config.xml
diff --git a/packages/SystemUI/res/values-mcc310-mnc070/config.xml b/core/res/res/values-mcc310-mnc070/config.xml
similarity index 100%
rename from packages/SystemUI/res/values-mcc310-mnc070/config.xml
rename to core/res/res/values-mcc310-mnc070/config.xml
diff --git a/packages/SystemUI/res/values-mcc310-mnc170/config.xml b/core/res/res/values-mcc310-mnc170/config.xml
similarity index 100%
rename from packages/SystemUI/res/values-mcc310-mnc170/config.xml
rename to core/res/res/values-mcc310-mnc170/config.xml
diff --git a/packages/SystemUI/res/values-mcc310-mnc280/config.xml b/core/res/res/values-mcc310-mnc280/config.xml
similarity index 100%
rename from packages/SystemUI/res/values-mcc310-mnc280/config.xml
rename to core/res/res/values-mcc310-mnc280/config.xml
diff --git a/packages/SystemUI/res/values-mcc310-mnc380/config.xml b/core/res/res/values-mcc310-mnc380/config.xml
similarity index 100%
rename from packages/SystemUI/res/values-mcc310-mnc380/config.xml
rename to core/res/res/values-mcc310-mnc380/config.xml
diff --git a/core/res/res/values-mcc310-mnc410/config.xml b/core/res/res/values-mcc310-mnc410/config.xml
index 00ab712..3fb3f0f 100644
--- a/core/res/res/values-mcc310-mnc410/config.xml
+++ b/core/res/res/values-mcc310-mnc410/config.xml
@@ -48,4 +48,8 @@
<item>"#8"</item>
<item>"#9"</item>
</string-array>
+
+ <!-- Enable 5 bar signal strength icon -->
+ <bool name="config_inflateSignalStrength">true</bool>
+
</resources>
diff --git a/packages/SystemUI/res/values-mcc310-mnc560/config.xml b/core/res/res/values-mcc310-mnc560/config.xml
similarity index 100%
rename from packages/SystemUI/res/values-mcc310-mnc560/config.xml
rename to core/res/res/values-mcc310-mnc560/config.xml
diff --git a/packages/SystemUI/res/values-mcc310-mnc950/config.xml b/core/res/res/values-mcc310-mnc950/config.xml
similarity index 100%
rename from packages/SystemUI/res/values-mcc310-mnc950/config.xml
rename to core/res/res/values-mcc310-mnc950/config.xml
diff --git a/packages/SystemUI/res/values-mcc311-mnc180/config.xml b/core/res/res/values-mcc311-mnc180/config.xml
similarity index 100%
rename from packages/SystemUI/res/values-mcc311-mnc180/config.xml
rename to core/res/res/values-mcc311-mnc180/config.xml
diff --git a/core/res/res/values-mcc311-mnc480/config.xml b/core/res/res/values-mcc311-mnc480/config.xml
index db2f8d0..336e30e 100755
--- a/core/res/res/values-mcc311-mnc480/config.xml
+++ b/core/res/res/values-mcc311-mnc480/config.xml
@@ -40,4 +40,7 @@
<bool name="config_use_sim_language_file">true</bool>
+ <!-- Enable 5 bar signal strength icon -->
+ <bool name="config_inflateSignalStrength">true</bool>
+
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2de5397..fb58569 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4121,4 +4121,9 @@
even after user setup is complete. The defined component should be used for supervision purposes
only. The component must be part of a system app. -->
<string name="config_defaultSupervisionProfileOwnerComponent" translatable="false"></string>
+
+ <!-- Whether to artificially interpret all signal strengths as
+ one bar higher than they actually are -->
+ <bool name="config_inflateSignalStrength">false</bool>
+
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 6f11432..e0ab6c8 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -742,6 +742,7 @@
<dimen name="chooser_preview_image_border">1dp</dimen>
<dimen name="chooser_preview_width">-1px</dimen>
<dimen name="resolver_icon_size">42dp</dimen>
+ <dimen name="resolver_button_bar_spacing">8dp</dimen>
<dimen name="resolver_badge_size">18dp</dimen>
<dimen name="chooser_target_width">90dp</dimen>
<dimen name="chooser_header_scroll_elevation">4dp</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 3a348f0..3d2d969 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3775,6 +3775,7 @@
<java-symbol type="drawable" name="iconfactory_adaptive_icon_drawable_wrapper"/>
<java-symbol type="dimen" name="resolver_icon_size"/>
<java-symbol type="dimen" name="resolver_badge_size"/>
+ <java-symbol type="dimen" name="resolver_button_bar_spacing"/>
<!-- For DropBox -->
<java-symbol type="integer" name="config_dropboxLowPriorityBroadcastRateLimitPeriod" />
@@ -3796,4 +3797,5 @@
<java-symbol type="style" name="Animation.DeviceDefault.Activity.Resolver" />
<java-symbol type="string" name="config_defaultSupervisionProfileOwnerComponent" />
+ <java-symbol type="bool" name="config_inflateSignalStrength" />
</resources>
diff --git a/core/tests/coretests/src/android/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
index 50e915d..71d9a46 100644
--- a/core/tests/coretests/src/android/content/pm/PackageParserTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import android.apex.ApexInfo;
import android.content.Context;
import android.content.pm.PackageParser.Component;
import android.content.pm.PackageParser.Package;
@@ -497,10 +498,17 @@
@Test
public void testApexPackageInfoGeneration() throws Exception {
- File apexFile = copyRawResourceToFile("com.android.tzdata.apex",
+ String apexPackageName = "com.android.tzdata.apex";
+ File apexFile = copyRawResourceToFile(apexPackageName,
R.raw.com_android_tzdata);
+ ApexInfo apexInfo = new ApexInfo();
+ apexInfo.isActive = true;
+ apexInfo.isFactory = false;
+ apexInfo.packageName = apexPackageName;
+ apexInfo.packagePath = apexFile.getPath();
+ apexInfo.versionCode = 191000070;
int flags = PackageManager.GET_META_DATA | PackageManager.GET_SIGNING_CERTIFICATES;
- PackageInfo pi = PackageParser.generatePackageInfoFromApex(apexFile, flags);
+ PackageInfo pi = PackageParser.generatePackageInfoFromApex(apexInfo, flags);
assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName);
assertTrue(pi.applicationInfo.enabled);
assertEquals(28, pi.applicationInfo.targetSdkVersion);
@@ -515,5 +523,7 @@
assertNotNull(pi.signingInfo);
assertTrue(pi.signingInfo.getApkContentsSigners().length > 0);
assertTrue(pi.isApex);
+ assertTrue((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0);
+ assertTrue((pi.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0);
}
}
diff --git a/data/etc/car/com.android.car.xml b/data/etc/car/com.android.car.xml
index f1797de..19548bc 100644
--- a/data/etc/car/com.android.car.xml
+++ b/data/etc/car/com.android.car.xml
@@ -24,6 +24,7 @@
<permission name="android.permission.PROVIDE_TRUST_AGENT"/>
<permission name="android.permission.REAL_GET_TASKS"/>
<permission name="android.permission.REBOOT"/>
+ <permission name="android.permission.READ_LOGS"/>
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
</privapp-permissions>
</permissions>
diff --git a/data/etc/car/com.google.android.car.kitchensink.xml b/data/etc/car/com.google.android.car.kitchensink.xml
index 6b26e8f..d36a826 100644
--- a/data/etc/car/com.google.android.car.kitchensink.xml
+++ b/data/etc/car/com.google.android.car.kitchensink.xml
@@ -25,6 +25,7 @@
<permission name="android.permission.MODIFY_PHONE_STATE"/>
<permission name="android.permission.PROVIDE_TRUST_AGENT"/>
<permission name="android.permission.REAL_GET_TASKS"/>
+ <permission name="android.permission.READ_LOGS"/>
<permission name="android.permission.REBOOT"/>
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
</privapp-permissions>
diff --git a/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java
index dbb79bc..e030478 100644
--- a/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java
+++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java
@@ -162,15 +162,15 @@
}
if ((opResult.output != null) && (opResult.output.length > 0)) {
- if (inputLength > 0) {
+ if (inputLength + mBufferedLength > 0) {
// More output might be produced in this loop -- buffer the current output
if (bufferedOutput == null) {
bufferedOutput = new ByteArrayOutputStream();
- try {
- bufferedOutput.write(opResult.output);
- } catch (IOException e) {
- throw new ProviderException("Failed to buffer output", e);
- }
+ }
+ try {
+ bufferedOutput.write(opResult.output);
+ } catch (IOException e) {
+ throw new ProviderException("Failed to buffer output", e);
}
} else {
// No more output will be produced in this loop
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index d9d614f..e29e569 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -101,6 +101,20 @@
public static final int PLAYSTATE_PAUSED = 2; // matches SL_PLAYSTATE_PAUSED
/** indicates AudioTrack state is playing */
public static final int PLAYSTATE_PLAYING = 3; // matches SL_PLAYSTATE_PLAYING
+ /**
+ * @hide
+ * indicates AudioTrack state is stopping waiting for NATIVE_EVENT_STREAM_END to
+ * transition to PLAYSTATE_STOPPED.
+ * Only valid for offload mode.
+ */
+ private static final int PLAYSTATE_STOPPING = 4;
+ /**
+ * @hide
+ * indicates AudioTrack state is paused from stopping state. Will transition to
+ * PLAYSTATE_STOPPING if play() is called.
+ * Only valid for offload mode.
+ */
+ private static final int PLAYSTATE_PAUSED_STOPPING = 5;
// keep these values in sync with android_media_AudioTrack.cpp
/**
@@ -303,6 +317,14 @@
* One of PLAYSTATE_STOPPED, PLAYSTATE_PAUSED, or PLAYSTATE_PLAYING.
*/
private int mPlayState = PLAYSTATE_STOPPED;
+
+ /**
+ * Indicates that we are expecting an end of stream callback following a call
+ * to setOffloadEndOfStream() in a gapless track transition context. The native track
+ * will be restarted automatically.
+ */
+ private boolean mOffloadEosPending = false;
+
/**
* Lock to ensure mPlayState updates reflect the actual state of the object.
*/
@@ -1073,6 +1095,10 @@
* Declares that the last write() operation on this track provided the last buffer of this
* stream.
* After the end of stream, previously set padding and delay values are ignored.
+ * Can only be called only if the AudioTrack is opened in offload mode
+ * {@see Builder#setOffloadedPlayback(boolean)}.
+ * Can only be called only if the AudioTrack is in state {@link #PLAYSTATE_PLAYING}
+ * {@see #getPlaystate()}.
* Use this method in the same thread as any write() operation.
*/
public void setOffloadEndOfStream() {
@@ -1082,7 +1108,20 @@
if (mState == STATE_UNINITIALIZED) {
throw new IllegalStateException("Uninitialized track");
}
- native_set_eos();
+ if (mPlayState != PLAYSTATE_PLAYING) {
+ throw new IllegalStateException("EOS not supported if not playing");
+ }
+ synchronized (mStreamEventCbLock) {
+ if (mStreamEventCbInfoList.size() == 0) {
+ throw new IllegalStateException("EOS not supported without StreamEventCallback");
+ }
+ }
+
+ synchronized (mPlayStateLock) {
+ native_stop();
+ mOffloadEosPending = true;
+ mPlayState = PLAYSTATE_STOPPING;
+ }
}
/**
@@ -1366,7 +1405,11 @@
}
baseRelease();
native_release();
- mState = STATE_UNINITIALIZED;
+ synchronized (mPlayStateLock) {
+ mState = STATE_UNINITIALIZED;
+ mPlayState = PLAYSTATE_STOPPED;
+ mPlayStateLock.notify();
+ }
}
@Override
@@ -1525,7 +1568,14 @@
*/
public int getPlayState() {
synchronized (mPlayStateLock) {
- return mPlayState;
+ switch (mPlayState) {
+ case PLAYSTATE_STOPPING:
+ return PLAYSTATE_PLAYING;
+ case PLAYSTATE_PAUSED_STOPPING:
+ return PLAYSTATE_PAUSED;
+ default:
+ return mPlayState;
+ }
}
}
@@ -2260,7 +2310,12 @@
synchronized(mPlayStateLock) {
baseStart();
native_start();
- mPlayState = PLAYSTATE_PLAYING;
+ if (mPlayState == PLAYSTATE_PAUSED_STOPPING) {
+ mPlayState = PLAYSTATE_STOPPING;
+ } else {
+ mPlayState = PLAYSTATE_PLAYING;
+ mOffloadEosPending = false;
+ }
}
}
@@ -2282,9 +2337,15 @@
synchronized(mPlayStateLock) {
native_stop();
baseStop();
- mPlayState = PLAYSTATE_STOPPED;
- mAvSyncHeader = null;
- mAvSyncBytesRemaining = 0;
+ if (mOffloaded && mPlayState != PLAYSTATE_PAUSED_STOPPING) {
+ mPlayState = PLAYSTATE_STOPPING;
+ } else {
+ mPlayState = PLAYSTATE_STOPPED;
+ mOffloadEosPending = false;
+ mAvSyncHeader = null;
+ mAvSyncBytesRemaining = 0;
+ mPlayStateLock.notify();
+ }
}
}
@@ -2305,7 +2366,11 @@
synchronized(mPlayStateLock) {
native_pause();
basePause();
- mPlayState = PLAYSTATE_PAUSED;
+ if (mPlayState == PLAYSTATE_STOPPING) {
+ mPlayState = PLAYSTATE_PAUSED_STOPPING;
+ } else {
+ mPlayState = PLAYSTATE_PAUSED;
+ }
}
}
@@ -2434,6 +2499,9 @@
return ERROR_BAD_VALUE;
}
+ if (!blockUntilOffloadDrain(writeMode)) {
+ return 0;
+ }
final int ret = native_write_byte(audioData, offsetInBytes, sizeInBytes, mAudioFormat,
writeMode == WRITE_BLOCKING);
@@ -2544,6 +2612,10 @@
return ERROR_BAD_VALUE;
}
+ if (!blockUntilOffloadDrain(writeMode)) {
+ return 0;
+ }
+
final int ret = native_write_short(audioData, offsetInShorts, sizeInShorts, mAudioFormat,
writeMode == WRITE_BLOCKING);
@@ -2632,6 +2704,10 @@
return ERROR_BAD_VALUE;
}
+ if (!blockUntilOffloadDrain(writeMode)) {
+ return 0;
+ }
+
final int ret = native_write_float(audioData, offsetInFloats, sizeInFloats, mAudioFormat,
writeMode == WRITE_BLOCKING);
@@ -2706,6 +2782,10 @@
return ERROR_BAD_VALUE;
}
+ if (!blockUntilOffloadDrain(writeMode)) {
+ return 0;
+ }
+
int ret = 0;
if (audioData.isDirect()) {
ret = native_write_native_bytes(audioData,
@@ -2790,6 +2870,10 @@
return ERROR_BAD_VALUE;
}
+ if (!blockUntilOffloadDrain(writeMode)) {
+ return 0;
+ }
+
// create timestamp header if none exists
if (mAvSyncHeader == null) {
mAvSyncHeader = ByteBuffer.allocate(mOffset);
@@ -2859,6 +2943,25 @@
return native_reload_static();
}
+ /**
+ * When an AudioTrack in offload mode is in STOPPING play state, wait until event STREAM_END is
+ * received if blocking write or return with 0 frames written if non blocking mode.
+ */
+ private boolean blockUntilOffloadDrain(int writeMode) {
+ synchronized (mPlayStateLock) {
+ while (mPlayState == PLAYSTATE_STOPPING || mPlayState == PLAYSTATE_PAUSED_STOPPING) {
+ if (writeMode == WRITE_NON_BLOCKING) {
+ return false;
+ }
+ try {
+ mPlayStateLock.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ return true;
+ }
+ }
+
//--------------------------------------------------------------------------
// Audio effects management
//--------------------
@@ -3293,6 +3396,22 @@
public void handleMessage(Message msg) {
final LinkedList<StreamEventCbInfo> cbInfoList;
synchronized (mStreamEventCbLock) {
+ if (msg.what == NATIVE_EVENT_STREAM_END) {
+ synchronized (mPlayStateLock) {
+ if (mPlayState == PLAYSTATE_STOPPING) {
+ if (mOffloadEosPending) {
+ native_start();
+ mPlayState = PLAYSTATE_PLAYING;
+ } else {
+ mAvSyncHeader = null;
+ mAvSyncBytesRemaining = 0;
+ mPlayState = PLAYSTATE_STOPPED;
+ }
+ mOffloadEosPending = false;
+ mPlayStateLock.notify();
+ }
+ }
+ }
if (mStreamEventCbInfoList.size() == 0) {
return;
}
@@ -3560,7 +3679,6 @@
private native int native_getPortId();
private native void native_set_delay_padding(int delayInFrames, int paddingInFrames);
- private native void native_set_eos();
//---------------------------------------------------------
// Utility methods
diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification_selected_unseen.xml b/packages/CarSystemUI/res/drawable/car_ic_notification_selected_unseen.xml
index 1c25866..c5d7728 100644
--- a/packages/CarSystemUI/res/drawable/car_ic_notification_selected_unseen.xml
+++ b/packages/CarSystemUI/res/drawable/car_ic_notification_selected_unseen.xml
@@ -30,4 +30,5 @@
android:strokeWidth="1"
android:pathData="M 6 0 C 9.31370849898 0 12 2.68629150102 12 6 C 12 9.31370849898 9.31370849898 12 6 12 C 2.68629150102 12 0 9.31370849898 0 6 C 0 2.68629150102 2.68629150102 0 6 0 Z" />
</group>
-</vector>
\ No newline at end of file
+</vector>
+
diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification_unseen.xml b/packages/CarSystemUI/res/drawable/car_ic_notification_unseen.xml
index e2fd862..25d1af3 100644
--- a/packages/CarSystemUI/res/drawable/car_ic_notification_unseen.xml
+++ b/packages/CarSystemUI/res/drawable/car_ic_notification_unseen.xml
@@ -30,4 +30,4 @@
android:strokeWidth="1"
android:pathData="M 6 0 C 9.31370849898 0 12 2.68629150102 12 6 C 12 9.31370849898 9.31370849898 12 6 12 C 2.68629150102 12 0 9.31370849898 0 6 C 0 2.68629150102 2.68629150102 0 6 0 Z" />
</group>
-</vector>
\ No newline at end of file
+</vector>
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
index 6e7be06..095e2e9 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
@@ -35,7 +35,7 @@
*/
class CarNavigationBarView extends LinearLayout {
private View mNavButtons;
- private View mNotificationsButton;
+ private CarNavigationButton mNotificationsButton;
private CarStatusBar mCarStatusBar;
private Context mContext;
private View mLockScreenButtons;
@@ -151,10 +151,20 @@
* Nav buttons will be shown.
*/
public void hideKeyguardButtons() {
- if (mLockScreenButtons == null) {
- return;
- }
+ if (mLockScreenButtons == null) return;
+
mNavButtons.setVisibility(View.VISIBLE);
mLockScreenButtons.setVisibility(View.GONE);
}
+
+ /**
+ * Toggles the notification unseen indicator on/off.
+ *
+ * @param hasUnseen true if the unseen notification count is great than 0.
+ */
+ void toggleNotificationUnseenIndicator(Boolean hasUnseen) {
+ if (mNotificationsButton == null) return;
+
+ mNotificationsButton.setUnseen(hasUnseen);
+ }
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
index e640baa..8879742 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
@@ -34,12 +34,17 @@
* code.
*/
public class CarNavigationButton extends com.android.keyguard.AlphaOptimizedImageButton {
-
private static final String TAG = "CarNavigationButton";
+
+ private static final int UNSEEN_ICON_RESOURCE_ID = R.drawable.car_ic_notification_unseen;
+ private static final int UNSEEN_SELECTED_ICON_RESOURCE_ID =
+ R.drawable.car_ic_notification_selected_unseen;
+
private Context mContext;
private String mIntent;
private String mLongIntent;
private boolean mBroadcastIntent;
+ private boolean mHasUnseen = false;
private boolean mSelected = false;
private float mSelectedAlpha = 1f;
private float mUnselectedAlpha = 1f;
@@ -50,6 +55,8 @@
public CarNavigationButton(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
+
+ // CarNavigationButton attrs
TypedArray typedArray = context.obtainStyledAttributes(
attrs, R.styleable.CarNavigationButton);
mIntent = typedArray.getString(R.styleable.CarNavigationButton_intent);
@@ -59,10 +66,15 @@
R.styleable.CarNavigationButton_selectedAlpha, mSelectedAlpha);
mUnselectedAlpha = typedArray.getFloat(
R.styleable.CarNavigationButton_unselectedAlpha, mUnselectedAlpha);
- mIconResourceId = typedArray.getResourceId(
- com.android.internal.R.styleable.ImageView_src, 0);
mSelectedIconResourceId = typedArray.getResourceId(
R.styleable.CarNavigationButton_selectedIcon, mIconResourceId);
+ typedArray.recycle();
+
+ // ImageView attrs
+ TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.ImageView);
+ mIconResourceId = a.getResourceId(com.android.internal.R.styleable.ImageView_src, 0);
+ a.recycle();
}
@@ -119,6 +131,23 @@
super.setSelected(selected);
mSelected = selected;
setAlpha(mSelected ? mSelectedAlpha : mUnselectedAlpha);
- setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId);
+ updateImage();
+ }
+
+ /**
+ * @param hasUnseen true if should indicate if this is a Unseen state, false otherwise.
+ */
+ public void setUnseen(boolean hasUnseen) {
+ mHasUnseen = hasUnseen;
+ updateImage();
+ }
+
+ private void updateImage() {
+ if (mHasUnseen) {
+ setImageResource(mSelected ? UNSEEN_SELECTED_ICON_RESOURCE_ID
+ : UNSEEN_ICON_RESOURCE_ID);
+ } else {
+ setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId);
+ }
}
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 54e468ee..b6b34c7 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -311,7 +311,6 @@
return result;
}
-
@Override
public void showKeyguard() {
super.showKeyguard();
@@ -451,9 +450,21 @@
mNotificationDataManager = new NotificationDataManager();
mNotificationDataManager.setOnUnseenCountUpdateListener(
() -> {
- // TODO: Update Notification Icon based on unseen count
- Log.d(TAG, "unseen count: " +
- mNotificationDataManager.getUnseenNotificationCount());
+ if (mNavigationBarView != null && mNotificationDataManager != null) {
+ Boolean hasUnseen =
+ mNotificationDataManager.getUnseenNotificationCount() > 0;
+ if (mNavigationBarView != null) {
+ mNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
+ }
+
+ if (mLeftNavigationBarView != null) {
+ mLeftNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
+ }
+
+ if (mRightNavigationBarView != null) {
+ mRightNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
+ }
+ }
});
CarHeadsUpNotificationManager carHeadsUpNotificationManager =
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
index 8090d14..4e40ba4 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
@@ -683,9 +683,9 @@
public void enter() {
maybeLogEvaluationResult(
networkEventType(validationStage(), EvaluationResult.VALIDATED));
- // If the user has accepted that and HTTPS probing is disabled, then mark the network
- // as validated and partial so that settings can keep informing the user that the
- // connection is limited.
+ // If the user has accepted partial connectivity and HTTPS probing is disabled, then
+ // mark the network as validated and partial so that settings can keep informing the
+ // user that the connection is limited.
int result = NETWORK_VALIDATION_RESULT_VALID;
if (!mUseHttps && mAcceptPartialConnectivity) {
result |= NETWORK_VALIDATION_RESULT_PARTIAL;
@@ -1054,6 +1054,11 @@
// TODO: Consider abandoning this state after a few attempts and
// transitioning back to EvaluatingState, to perhaps give ourselves
// the opportunity to (re)detect a captive portal or something.
+ //
+ // TODO: distinguish between CMD_EVALUATE_PRIVATE_DNS messages that are caused by server
+ // lookup failures (which should continue to do exponential backoff) and
+ // CMD_EVALUATE_PRIVATE_DNS messages that are caused by user reconfiguration (which
+ // should be processed immediately.
sendMessageDelayed(CMD_EVALUATE_PRIVATE_DNS, mPrivateDnsReevalDelayMs);
mPrivateDnsReevalDelayMs *= 2;
if (mPrivateDnsReevalDelayMs > MAX_REEVALUATE_DELAY_MS) {
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
index 764e2d0..a538a5b 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
@@ -410,6 +410,7 @@
private static final String[] DATA_COLUMN = new String[] {
PrivateDataContract.COLNAME_DATA
};
+
@Nullable
static byte[] retrieveBlob(@NonNull final SQLiteDatabase db, @NonNull final String key,
@NonNull final String clientId, @NonNull final String name) {
@@ -432,6 +433,57 @@
}
/**
+ * Wipe all data in tables when network factory reset occurs.
+ */
+ static void wipeDataUponNetworkReset(@NonNull final SQLiteDatabase db) {
+ for (int remainingRetries = 3; remainingRetries > 0; --remainingRetries) {
+ db.beginTransaction();
+ try {
+ db.delete(NetworkAttributesContract.TABLENAME, null, null);
+ db.delete(PrivateDataContract.TABLENAME, null, null);
+ final Cursor cursorNetworkAttributes = db.query(
+ // table name
+ NetworkAttributesContract.TABLENAME,
+ // column name
+ new String[] { NetworkAttributesContract.COLNAME_L2KEY },
+ null, // selection
+ null, // selectionArgs
+ null, // groupBy
+ null, // having
+ null, // orderBy
+ "1"); // limit
+ if (0 != cursorNetworkAttributes.getCount()) {
+ cursorNetworkAttributes.close();
+ continue;
+ }
+ cursorNetworkAttributes.close();
+ final Cursor cursorPrivateData = db.query(
+ // table name
+ PrivateDataContract.TABLENAME,
+ // column name
+ new String[] { PrivateDataContract.COLNAME_L2KEY },
+ null, // selection
+ null, // selectionArgs
+ null, // groupBy
+ null, // having
+ null, // orderBy
+ "1"); // limit
+ if (0 != cursorPrivateData.getCount()) {
+ cursorPrivateData.close();
+ continue;
+ }
+ cursorPrivateData.close();
+ db.setTransactionSuccessful();
+ return;
+ } catch (SQLiteException e) {
+ Log.e(TAG, "Could not wipe the data in database", e);
+ } finally {
+ db.endTransaction();
+ }
+ }
+ }
+
+ /**
* The following is a horrible hack that is necessary because the Android SQLite API does not
* have a way to query a binary blob. This, almost certainly, is an overlook.
*
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
index 8312dfe..55ab8d4 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
@@ -60,7 +60,6 @@
*/
public class IpMemoryStoreService extends IIpMemoryStore.Stub {
private static final String TAG = IpMemoryStoreService.class.getSimpleName();
- private static final int MAX_CONCURRENT_THREADS = 4;
private static final int DATABASE_SIZE_THRESHOLD = 10 * 1024 * 1024; //10MB
private static final int MAX_DROP_RECORD_TIMES = 500;
private static final int MIN_DELETE_NUM = 5;
@@ -107,23 +106,17 @@
db = null;
}
mDb = db;
- // The work-stealing thread pool executor will spawn threads as needed up to
- // the max only when there is no free thread available. This generally behaves
- // exactly like one would expect it intuitively :
- // - When work arrives, it will spawn a new thread iff there are no available threads
- // - When there is no work to do it will shutdown threads after a while (the while
- // being equal to 2 seconds (not configurable) when max threads are spun up and
- // twice as much for every one less thread)
- // - When all threads are busy the work is enqueued and waits for any worker
- // to become available.
- // Because the stealing pool is made for very heavily parallel execution of
- // small tasks that spawn others, it creates a queue per thread that in this
- // case is overhead. However, the three behaviors above make it a superior
- // choice to cached or fixedThreadPoolExecutor, neither of which can actually
- // enqueue a task waiting for a thread to be free. This can probably be solved
- // with judicious subclassing of ThreadPoolExecutor, but that's a lot of dangerous
- // complexity for little benefit in this case.
- mExecutor = Executors.newWorkStealingPool(MAX_CONCURRENT_THREADS);
+ // The single thread executor guarantees that all work is executed sequentially on the
+ // same thread, and no two tasks can be active at the same time. This is required to
+ // ensure operations from multiple clients don't interfere with each other (in particular,
+ // operations involving a transaction must not run concurrently with other operations
+ // as the other operations might be taken as part of the transaction). By default, the
+ // single thread executor runs off an unbounded queue.
+ // TODO : investigate replacing this scheme with a scheme where each thread has its own
+ // instance of the database, as it may be faster. It is likely however that IpMemoryStore
+ // operations are mostly IO-bound anyway, and additional contention is unlikely to bring
+ // benefits. Alternatively, a read-write lock might increase throughput.
+ mExecutor = Executors.newSingleThreadExecutor();
RegularMaintenanceJobService.schedule(mContext, this);
}
@@ -410,8 +403,12 @@
});
}
+ /**
+ * Wipe the data in IpMemoryStore database upon network factory reset.
+ */
@Override
public void factoryReset() {
+ mExecutor.execute(() -> IpMemoryStoreDatabase.wipeDataUponNetworkReset(mDb));
}
/** Get db size threshold. */
diff --git a/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
index cc8f2c0..262641d 100644
--- a/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -941,6 +941,10 @@
.notifyNetworkTested(intCaptor.capture(), any());
List<Integer> intArgs = intCaptor.getAllValues();
+ // None of these exact values can be known in advance except for intArgs.get(0) because the
+ // HTTP and HTTPS probes race and the order in which they complete is non-deterministic.
+ // Thus, check only exact value for intArgs.get(0) and only check the validation result for
+ // the rest ones.
assertEquals(Integer.valueOf(NETWORK_VALIDATION_PROBE_DNS
| NETWORK_VALIDATION_PROBE_FALLBACK | NETWORK_VALIDATION_RESULT_VALID),
intArgs.get(0));
diff --git a/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java b/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
index 87346e5..64fe3a6 100644
--- a/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -62,6 +62,7 @@
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -77,7 +78,11 @@
private static final int DEFAULT_TIMEOUT_MS = 5000;
private static final int LONG_TIMEOUT_MS = 30000;
private static final int FAKE_KEY_COUNT = 20;
+ private static final long LEASE_EXPIRY_NULL = -1L;
+ private static final int MTU_NULL = -1;
private static final String[] FAKE_KEYS;
+ private static final byte[] TEST_BLOB_DATA = new byte[] { -3, 6, 8, -9, 12,
+ -128, 0, 89, 112, 91, -34 };
static {
FAKE_KEYS = new String[FAKE_KEY_COUNT];
for (int i = 0; i < FAKE_KEYS.length; ++i) {
@@ -124,6 +129,29 @@
mDbFile.delete();
}
+ /** Helper method to build test network attributes */
+ private static NetworkAttributes.Builder buildTestNetworkAttributes(
+ final Inet4Address ipAddress, final long expiry, final String hint,
+ final List<InetAddress> dnsServers, final int mtu) {
+ final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
+ if (null != ipAddress) {
+ na.setAssignedV4Address(ipAddress);
+ }
+ if (LEASE_EXPIRY_NULL != expiry) {
+ na.setAssignedV4AddressExpiry(expiry);
+ }
+ if (null != hint) {
+ na.setGroupHint(hint);
+ }
+ if (null != dnsServers) {
+ na.setDnsAddresses(dnsServers);
+ }
+ if (MTU_NULL != mtu) {
+ na.setMtu(mtu);
+ }
+ return na;
+ }
+
/** Helper method to make a vanilla IOnStatusListener */
private IOnStatusListener onStatus(Consumer<Status> functor) {
return new IOnStatusListener() {
@@ -265,7 +293,7 @@
}
}
- // Helper methods to factorize more boilerplate
+ // Helper method to store network attributes to database
private void storeAttributes(final String l2Key, final NetworkAttributes na) {
storeAttributes("Did not complete storing attributes", l2Key, na);
}
@@ -278,15 +306,28 @@
})));
}
+ // Helper method to store blob data to database
+ private void storeBlobOrFail(final String l2Key, final Blob b, final byte[] data) {
+ storeBlobOrFail("Did not complete storing private data", l2Key, b, data);
+ }
+ private void storeBlobOrFail(final String timeoutMessage, final String l2Key, final Blob b,
+ final byte[] data) {
+ b.data = data;
+ doLatched(timeoutMessage, latch -> mService.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME,
+ b, onStatus(status -> {
+ assertTrue("Store status not successful : " + status.resultCode,
+ status.isSuccess());
+ latch.countDown();
+ })));
+ }
+
/** Insert large data that db size will be over threshold for maintenance test usage. */
private void insertFakeDataAndOverThreshold() {
try {
- final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
- na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
- na.setGroupHint("hint1");
- na.setMtu(219);
- na.setDnsAddresses(Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6")));
- final byte[] data = new byte[]{-3, 6, 8, -9, 12, -128, 0, 89, 112, 91, -34};
+ final NetworkAttributes.Builder na = buildTestNetworkAttributes(
+ (Inet4Address) Inet4Address.getByName("1.2.3.4"), LEASE_EXPIRY_NULL,
+ "hint1", Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6")),
+ 219);
final long time = System.currentTimeMillis() - 1;
for (int i = 0; i < 1000; i++) {
int errorCode = IpMemoryStoreDatabase.storeNetworkAttributes(
@@ -298,7 +339,8 @@
assertEquals(errorCode, Status.SUCCESS);
errorCode = IpMemoryStoreDatabase.storeBlob(
- mService.mDb, "fakeKey" + i, TEST_CLIENT_ID, TEST_DATA_NAME, data);
+ mService.mDb, "fakeKey" + i, TEST_CLIENT_ID, TEST_DATA_NAME,
+ TEST_BLOB_DATA);
assertEquals(errorCode, Status.SUCCESS);
}
@@ -320,12 +362,10 @@
@Test
public void testNetworkAttributes() throws UnknownHostException {
- final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
- na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
- na.setAssignedV4AddressExpiry(System.currentTimeMillis() + 7_200_000);
- na.setGroupHint("hint1");
- na.setMtu(219);
final String l2Key = FAKE_KEYS[0];
+ final NetworkAttributes.Builder na = buildTestNetworkAttributes(
+ (Inet4Address) Inet4Address.getByName("1.2.3.4"),
+ System.currentTimeMillis() + 7_200_000, "hint1", null, 219);
NetworkAttributes attributes = na.build();
storeAttributes(l2Key, attributes);
@@ -420,16 +460,9 @@
@Test
public void testPrivateData() {
- final Blob b = new Blob();
- b.data = new byte[] { -3, 6, 8, -9, 12, -128, 0, 89, 112, 91, -34 };
final String l2Key = FAKE_KEYS[0];
- doLatched("Did not complete storing private data", latch ->
- mService.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
- onStatus(status -> {
- assertTrue("Store status not successful : " + status.resultCode,
- status.isSuccess());
- latch.countDown();
- })));
+ final Blob b = new Blob();
+ storeBlobOrFail(l2Key, b, TEST_BLOB_DATA);
doLatched("Did not complete retrieving private data", latch ->
mService.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, onBlobRetrieved(
@@ -564,11 +597,10 @@
@Test
public void testIsSameNetwork() throws UnknownHostException {
- final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
- na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
- na.setGroupHint("hint1");
- na.setMtu(219);
- na.setDnsAddresses(Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6")));
+ final NetworkAttributes.Builder na = buildTestNetworkAttributes(
+ (Inet4Address) Inet4Address.getByName("1.2.3.4"), LEASE_EXPIRY_NULL,
+ "hint1", Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6")),
+ 219);
storeAttributes(FAKE_KEYS[0], na.build());
// 0 and 1 have identical attributes
@@ -601,7 +633,6 @@
})));
}
-
@Test
public void testFullMaintenance() {
insertFakeDataAndOverThreshold();
@@ -660,4 +691,66 @@
// still be over the threshold.
assertTrue(mService.isDbSizeOverThreshold());
}
+
+ @Test
+ public void testFactoryReset() throws UnknownHostException {
+ final String l2Key = FAKE_KEYS[0];
+
+ // store network attributes
+ final NetworkAttributes.Builder na = buildTestNetworkAttributes(
+ (Inet4Address) Inet4Address.getByName("1.2.3.4"),
+ System.currentTimeMillis() + 7_200_000, "hint1", null, 219);
+ storeAttributes(l2Key, na.build());
+
+ // store private data blob
+ final Blob b = new Blob();
+ storeBlobOrFail(l2Key, b, TEST_BLOB_DATA);
+
+ // wipe all data in Database
+ mService.factoryReset();
+
+ // retrieved network attributes should be null
+ doLatched("Did not complete retrieving attributes", latch ->
+ mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved(
+ (status, key, attr) -> {
+ assertTrue("Retrieve network attributes not successful : "
+ + status.resultCode, status.isSuccess());
+ assertEquals(l2Key, key);
+ assertNull(attr);
+ latch.countDown();
+ })));
+
+ // retrieved private data blob should be null
+ doLatched("Did not complete retrieving private data", latch ->
+ mService.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, onBlobRetrieved(
+ (status, key, name, data) -> {
+ assertTrue("Retrieve blob status not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(l2Key, key);
+ assertEquals(name, TEST_DATA_NAME);
+ assertNull(data);
+ latch.countDown();
+ })));
+ }
+
+ public void testTasksAreSerial() {
+ final long sleepTimeMs = 1000;
+ final long startTime = System.currentTimeMillis();
+ mService.retrieveNetworkAttributes("somekey", onNetworkAttributesRetrieved(
+ (status, key, attr) -> {
+ assertTrue("Unexpected status : " + status.resultCode, status.isSuccess());
+ try {
+ Thread.sleep(sleepTimeMs);
+ } catch (InterruptedException e) {
+ fail("InterruptedException");
+ }
+ }));
+ doLatched("Serial tasks timing out", latch ->
+ mService.retrieveNetworkAttributes("somekey", onNetworkAttributesRetrieved(
+ (status, key, attr) -> {
+ assertTrue("Unexpected status : " + status.resultCode,
+ status.isSuccess());
+ assertTrue(System.currentTimeMillis() >= startTime + sleepTimeMs);
+ })), DEFAULT_TIMEOUT_MS);
+ }
}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 4c72f48..0e91839 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -578,6 +578,8 @@
<string name="wifi_display_certification">Wireless display certification</string>
<!-- Setting Checkbox title whether to enable WiFi Verbose Logging. [CHAR LIMIT=40] -->
<string name="wifi_verbose_logging">Enable Wi\u2011Fi Verbose Logging</string>
+ <!-- Setting Checkbox title whether to disable WiFi Scan Throttling. [CHAR LIMIT=40] -->
+ <string name="wifi_scan_throttling">Wi\u2011Fi scan throttling</string>
<!-- Setting Checkbox title whether to always keep mobile data active. [CHAR LIMIT=80] -->
<string name="mobile_data_always_on">Mobile data always active</string>
<!-- Setting Checkbox title whether to enable hardware acceleration for tethering. [CHAR LIMIT=80] -->
@@ -633,6 +635,8 @@
<string name="wifi_display_certification_summary">Show options for wireless display certification</string>
<!-- Setting Checkbox summary whether to enable Wifi verbose Logging [CHAR LIMIT=80] -->
<string name="wifi_verbose_logging_summary">Increase Wi\u2011Fi logging level, show per SSID RSSI in Wi\u2011Fi Picker</string>
+ <!-- Setting Checkbox summary whether to disable Wifi scan throttling [CHAR LIMIT=NONE] -->
+ <string name="wifi_scan_throttling_summary">Reduces battery drain & improves network performance</string>
<!-- Label indicating network has been manually marked as metered -->
<string name="wifi_metered_label">Metered</string>
<!-- Label indicating network has been manually marked as unmetered -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/SignalStrengthUtil.java b/packages/SettingsLib/src/com/android/settingslib/net/SignalStrengthUtil.java
new file mode 100644
index 0000000..246f2ce
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/net/SignalStrengthUtil.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.net;
+
+import android.content.Context;
+import android.telephony.SubscriptionManager;
+
+/**
+ * Utilities for dealing with signal strength.
+ */
+public class SignalStrengthUtil {
+ /**
+ * @return whether we should artificially inflate the signal strength and number of levels by 1
+ * bar for the subscription with the given id
+ */
+ public static boolean shouldInflateSignalStrength(Context context, int subscriptionId) {
+ return SubscriptionManager.getResourcesForSubId(context, subscriptionId)
+ .getBoolean(com.android.internal.R.bool.config_inflateSignalStrength);
+ }
+}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index e00f204..a245086 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -22,7 +22,8 @@
android:sharedUserId="android.uid.shell"
>
- <!-- Standard permissions granted to the shell. -->
+ <!-- Standard permissions granted to the shell. -->
+ <uses-permission android:name="android.permission.GET_RUNTIME_PERMISSIONS" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.CALL_PHONE" />
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index bcdcb3b..9a04222 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -103,9 +103,12 @@
<item name="android:colorControlActivated">?attr/wallpaperTextColor</item>
</style>
- <style name="keyguard_presentation_theme" parent="@android:style/Theme.Material.NoActionBar.Fullscreen">
- <item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
- <item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_dark</item>
+ <style name="Theme.SystemUI.KeyguardPresentation">
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowFullscreen">true</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:colorBackground">@*android:color/background_material_dark</item>
</style>
<style name="TextAppearance.Keyguard" parent="Theme.SystemUI">
diff --git a/packages/SystemUI/res/values-sw320dp-land/dimens.xml b/packages/SystemUI/res/color/notification_guts_priority_button_bg_fill.xml
similarity index 62%
copy from packages/SystemUI/res/values-sw320dp-land/dimens.xml
copy to packages/SystemUI/res/color/notification_guts_priority_button_bg_fill.xml
index 2ec5abd..8ef1bf9c 100644
--- a/packages/SystemUI/res/values-sw320dp-land/dimens.xml
+++ b/packages/SystemUI/res/color/notification_guts_priority_button_bg_fill.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2019 The Android Open Source Project
~
@@ -14,14 +13,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<resources>
- <!-- Global actions grid -->
- <dimen name="global_actions_grid_vertical_padding">3dp</dimen>
- <dimen name="global_actions_grid_horizontal_padding">0dp</dimen>
-
- <dimen name="global_actions_grid_item_side_margin">4dp</dimen>
- <dimen name="global_actions_grid_item_vertical_margin">5dp</dimen>
-
-</resources>
-
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_selected="true"
+ android:color="@color/notification_guts_priority_button_bg_fill_color_selected" />
+ <item android:color="@color/notification_guts_priority_button_bg_fill_color" />
+</selector>
diff --git a/packages/SystemUI/res/values-sw320dp-land/dimens.xml b/packages/SystemUI/res/color/notification_guts_priority_button_bg_stroke.xml
similarity index 62%
copy from packages/SystemUI/res/values-sw320dp-land/dimens.xml
copy to packages/SystemUI/res/color/notification_guts_priority_button_bg_stroke.xml
index 2ec5abd..7964609 100644
--- a/packages/SystemUI/res/values-sw320dp-land/dimens.xml
+++ b/packages/SystemUI/res/color/notification_guts_priority_button_bg_stroke.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2019 The Android Open Source Project
~
@@ -14,14 +13,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<resources>
- <!-- Global actions grid -->
- <dimen name="global_actions_grid_vertical_padding">3dp</dimen>
- <dimen name="global_actions_grid_horizontal_padding">0dp</dimen>
-
- <dimen name="global_actions_grid_item_side_margin">4dp</dimen>
- <dimen name="global_actions_grid_item_vertical_margin">5dp</dimen>
-
-</resources>
-
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_selected="true"
+ android:color="@color/notification_guts_priority_button_bg_stroke_color_selected" />
+ <item android:color="@color/notification_guts_priority_button_bg_stroke_color" />
+</selector>
diff --git a/packages/SystemUI/res/color/notification_guts_priority_contents.xml b/packages/SystemUI/res/color/notification_guts_priority_contents.xml
new file mode 100644
index 0000000..56c43f0
--- /dev/null
+++ b/packages/SystemUI/res/color/notification_guts_priority_contents.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_selected="true"
+ android:color="@color/notification_guts_priority_button_content_color_selected" />
+ <item android:color="@color/notification_guts_priority_button_content_color" />
+</selector>
diff --git a/packages/SystemUI/res/drawable/button_border_selected.xml b/packages/SystemUI/res/drawable/button_border_selected.xml
deleted file mode 100644
index 01e7099..0000000
--- a/packages/SystemUI/res/drawable/button_border_selected.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2019 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid
- android:color="@color/notification_guts_selection_bg" />
- <stroke
- android:width="1dp"
- android:color="@color/GM2_grey_300"/>
- <corners android:radius="@dimen/rect_button_radius" />
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/button_border_unselected.xml b/packages/SystemUI/res/drawable/button_border_unselected.xml
deleted file mode 100644
index b9c4ced..0000000
--- a/packages/SystemUI/res/drawable/button_border_unselected.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2019 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle"
- android:color="@color/notification_guts_selection_bg">
- <stroke
- android:width="1dp"
- android:color="@color/GM2_grey_300"/>
-
- <corners android:radius="@dimen/rect_button_radius" />
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/button_ripple_radius.xml b/packages/SystemUI/res/drawable/button_ripple_radius.xml
deleted file mode 100644
index 5c2857a..0000000
--- a/packages/SystemUI/res/drawable/button_ripple_radius.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2019 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-<ripple
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="?android:attr/colorControlHighlight">
- <item android:id="@android:id/mask">
- <shape android:shape="rectangle">
- <solid android:color="@color/notification_guts_selection_bg" />
- <corners android:radius="@dimen/rect_button_radius" />
- </shape>
- </item>
-</ripple>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_volume_media.xml b/packages/SystemUI/res/drawable/ic_volume_media.xml
index c8fa3fb..99be7b6 100644
--- a/packages/SystemUI/res/drawable/ic_volume_media.xml
+++ b/packages/SystemUI/res/drawable/ic_volume_media.xml
@@ -22,6 +22,6 @@
<path
android:fillColor="#FFFFFFFF"
- android:pathData="M12,3l0.01,10.55c-0.59,-0.34 -1.27,-0.55 -2,-0.55C7.79,13 6,14.79 6,17c0,2.21 1.79,4 4.01,4S14,19.21 14,17V7h4V3H12zM10.01,19c-1.1,0 -2,-0.9 -2,-2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2C12.01,18.1 11.11,19 10.01,19z"/>
+ android:pathData="M12 3l0.01 10.55c-0.59-0.34-1.27-0.55-2-0.55C7.79 13 6 14.79 6 17s1.79 4 4.01 4S14 19.21 14 17V7h4V3h-6z" />
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_volume_media_mute.xml b/packages/SystemUI/res/drawable/ic_volume_media_mute.xml
index 45b5b87..3a495963 100644
--- a/packages/SystemUI/res/drawable/ic_volume_media_mute.xml
+++ b/packages/SystemUI/res/drawable/ic_volume_media_mute.xml
@@ -22,9 +22,6 @@
<path
android:fillColor="#FFFFFFFF"
- android:pathData="M21.19,21.19L14,14l-2,-2l-9.2,-9.2L1.39,4.22l8.79,8.79c-0.06,0 -0.12,-0.01 -0.18,-0.01C7.79,13 6,14.79 6,17c0,2.21 1.79,4 4.01,4S14,19.21 14,17v-0.17l5.78,5.78L21.19,21.19zM10.01,19c-1.1,0 -2,-0.9 -2,-2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2C12.01,18.1 11.11,19 10.01,19z"/>
- <path
- android:fillColor="#FFffffff"
- android:pathData="M14,11.17l0,-4.17l4,0l0,-4l-6,0l0,6.17z"/>
+ android:pathData="M21.19 21.19L14 14l-2-2-9.2-9.2-1.41 1.42 8.79 8.79c-0.06 0-0.12-0.01-0.18-0.01-2.21 0-4 1.79-4 4s1.79 4 4.01 4S14 19.21 14 17v-0.17l5.78 5.78 1.41-1.42zM14 11.17V7h4V3h-6v6.17z" />
</vector>
diff --git a/packages/SystemUI/res/values-sw320dp-land/dimens.xml b/packages/SystemUI/res/drawable/notification_guts_priority_button_bg.xml
similarity index 63%
rename from packages/SystemUI/res/values-sw320dp-land/dimens.xml
rename to packages/SystemUI/res/drawable/notification_guts_priority_button_bg.xml
index 2ec5abd..a0e025f 100644
--- a/packages/SystemUI/res/values-sw320dp-land/dimens.xml
+++ b/packages/SystemUI/res/drawable/notification_guts_priority_button_bg.xml
@@ -14,14 +14,14 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<resources>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle" >
+ <solid
+ android:color="@color/notification_guts_priority_button_bg_fill" />
- <!-- Global actions grid -->
- <dimen name="global_actions_grid_vertical_padding">3dp</dimen>
- <dimen name="global_actions_grid_horizontal_padding">0dp</dimen>
+ <stroke
+ android:width="1dp"
+ android:color="@color/notification_guts_priority_button_bg_stroke" />
- <dimen name="global_actions_grid_item_side_margin">4dp</dimen>
- <dimen name="global_actions_grid_item_vertical_margin">5dp</dimen>
-
-</resources>
-
+ <corners android:radius="@dimen/rect_button_radius" />
+</shape>
diff --git a/packages/SystemUI/res/layout-land/global_actions_grid.xml b/packages/SystemUI/res/layout-land/global_actions_grid.xml
index 4619430..6a10812 100644
--- a/packages/SystemUI/res/layout-land/global_actions_grid.xml
+++ b/packages/SystemUI/res/layout-land/global_actions_grid.xml
@@ -28,10 +28,10 @@
android:orientation="vertical"
android:layout_marginTop="@dimen/global_actions_grid_side_margin"
android:translationZ="@dimen/global_actions_translate"
- android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
- android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
- android:paddingTop="@dimen/global_actions_grid_vertical_padding"
- android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
+ android:paddingLeft="@dimen/global_actions_grid_vertical_padding"
+ android:paddingRight="@dimen/global_actions_grid_vertical_padding"
+ android:paddingTop="@dimen/global_actions_grid_horizontal_padding"
+ android:paddingBottom="@dimen/global_actions_grid_horizontal_padding"
android:background="?android:attr/colorBackgroundFloating"
>
<LinearLayout
@@ -60,10 +60,10 @@
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/global_actions_grid_side_margin"
android:layout_marginBottom="@dimen/global_actions_grid_side_margin"
- android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
- android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
- android:paddingTop="@dimen/global_actions_grid_vertical_padding"
- android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
+ android:paddingLeft="@dimen/global_actions_grid_vertical_padding"
+ android:paddingRight="@dimen/global_actions_grid_vertical_padding"
+ android:paddingTop="@dimen/global_actions_grid_horizontal_padding"
+ android:paddingBottom="@dimen/global_actions_grid_horizontal_padding"
android:orientation="horizontal"
android:layoutDirection="ltr"
android:background="?android:attr/colorBackgroundFloating"
diff --git a/packages/SystemUI/res/layout-land/global_actions_grid_item.xml b/packages/SystemUI/res/layout-land/global_actions_grid_item.xml
new file mode 100644
index 0000000..bc12338
--- /dev/null
+++ b/packages/SystemUI/res/layout-land/global_actions_grid_item.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<!-- RelativeLayouts have an issue enforcing minimum heights, so just
+ work around this for now with LinearLayouts. -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:layout_marginTop="@dimen/global_actions_grid_item_side_margin"
+ android:layout_marginBottom="@dimen/global_actions_grid_item_side_margin"
+ android:layout_marginLeft="@dimen/global_actions_grid_item_vertical_margin"
+ android:layout_marginRight="@dimen/global_actions_grid_item_vertical_margin"
+>
+ <LinearLayout
+ android:layout_width="@dimen/global_actions_grid_item_height"
+ android:layout_height="@dimen/global_actions_grid_item_width"
+ android:gravity="top|center_horizontal"
+ android:orientation="vertical"
+ >
+ <ImageView
+ android:id="@*android:id/icon"
+ android:layout_width="@dimen/global_actions_grid_item_icon_width"
+ android:layout_height="@dimen/global_actions_grid_item_icon_height"
+ android:layout_marginTop="@dimen/global_actions_grid_item_icon_top_margin"
+ android:layout_marginBottom="@dimen/global_actions_grid_item_icon_bottom_margin"
+ android:layout_marginLeft="@dimen/global_actions_grid_item_icon_side_margin"
+ android:layout_marginRight="@dimen/global_actions_grid_item_icon_side_margin"
+ android:scaleType="centerInside"
+ android:tint="@color/global_actions_text"
+ />
+
+ <TextView
+ android:id="@*android:id/message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:singleLine="true"
+ android:gravity="center"
+ android:textSize="12dp"
+ android:textColor="@color/global_actions_text"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ />
+
+ <TextView
+ android:visibility="gone"
+ android:id="@*android:id/status"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textColor="@color/global_actions_text"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ />
+ </LinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml b/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml
index 4ece03b..79c51d9 100644
--- a/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml
+++ b/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml
@@ -29,10 +29,10 @@
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/global_actions_grid_side_margin"
android:layout_marginBottom="@dimen/global_actions_grid_side_margin"
- android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
- android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
- android:paddingTop="@dimen/global_actions_grid_vertical_padding"
- android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
+ android:paddingLeft="@dimen/global_actions_grid_vertical_padding"
+ android:paddingRight="@dimen/global_actions_grid_vertical_padding"
+ android:paddingTop="@dimen/global_actions_grid_horizontal_padding"
+ android:paddingBottom="@dimen/global_actions_grid_horizontal_padding"
android:orientation="horizontal"
android:layoutDirection="rtl"
android:background="?android:attr/colorBackgroundFloating"
@@ -49,10 +49,10 @@
android:orientation="vertical"
android:layout_marginBottom="@dimen/global_actions_grid_side_margin"
android:translationZ="@dimen/global_actions_translate"
- android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
- android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
- android:paddingTop="@dimen/global_actions_grid_vertical_padding"
- android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
+ android:paddingLeft="@dimen/global_actions_grid_vertical_padding"
+ android:paddingRight="@dimen/global_actions_grid_vertical_padding"
+ android:paddingTop="@dimen/global_actions_grid_horizontal_padding"
+ android:paddingBottom="@dimen/global_actions_grid_horizontal_padding"
android:background="?android:attr/colorBackgroundFloating"
>
<LinearLayout
diff --git a/packages/SystemUI/res/layout/global_actions_grid_item.xml b/packages/SystemUI/res/layout/global_actions_grid_item.xml
index 3cefce0..4404c874 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_item.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_item.xml
@@ -18,8 +18,8 @@
work around this for now with LinearLayouts. -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/global_actions_grid_item_width"
- android:layout_height="@dimen/global_actions_grid_item_height"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="@dimen/global_actions_grid_item_vertical_margin"
android:layout_marginBottom="@dimen/global_actions_grid_item_vertical_margin"
@@ -41,7 +41,7 @@
android:layout_marginLeft="@dimen/global_actions_grid_item_icon_side_margin"
android:layout_marginRight="@dimen/global_actions_grid_item_icon_side_margin"
android:scaleType="centerInside"
- android:alpha="?android:attr/primaryContentAlpha"
+ android:tint="@color/global_actions_text"
/>
<TextView
@@ -53,6 +53,7 @@
android:singleLine="true"
android:gravity="center"
android:textSize="12dp"
+ android:textColor="@color/global_actions_text"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
@@ -62,7 +63,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
- android:textColor="?android:attr/textColorTertiary"
+ android:textColor="@color/global_actions_text"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 4b672ee0..8ffa2d8 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -219,106 +219,127 @@
android:gravity="center"
android:orientation="vertical">
- <RelativeLayout
+ <LinearLayout
android:id="@+id/alert"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/notification_importance_button_padding"
android:clickable="true"
- android:focusable="true">
- <ImageView
- android:id="@+id/alert_icon"
- android:src="@drawable/ic_notifications_alert"
- android:background="@android:color/transparent"
- android:layout_gravity="center"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:clickable="false"
- android:focusable="false"/>
- <TextView
- android:id="@+id/alert_label"
+ android:focusable="true"
+ android:background="@drawable/notification_guts_priority_button_bg"
+ android:orientation="vertical">
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:ellipsize="end"
- android:maxLines="1"
- android:clickable="false"
- android:focusable="false"
- android:layout_toEndOf="@id/alert_icon"
- android:layout_marginStart="@dimen/notification_importance_drawable_padding"
- android:textAppearance="@style/TextAppearance.NotificationImportanceButton.Unselected"
- android:text="@string/notification_alert_title"/>
+ android:orientation="horizontal"
+ android:gravity="center"
+ >
+ <ImageView
+ android:id="@+id/alert_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_notifications_alert"
+ android:background="@android:color/transparent"
+ android:tint="@color/notification_guts_priority_contents"
+ android:clickable="false"
+ android:focusable="false"/>
+ <TextView
+ android:id="@+id/alert_label"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/notification_importance_drawable_padding"
+ android:layout_weight="1"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:clickable="false"
+ android:focusable="false"
+ android:textAppearance="@style/TextAppearance.NotificationImportanceButton"
+ android:text="@string/notification_alert_title"/>
+ </LinearLayout>
<TextView
android:id="@+id/alert_summary"
- android:visibility="gone"
- android:paddingTop="@dimen/notification_importance_button_padding"
- android:text="@string/notification_channel_summary_default"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/notification_importance_button_description_top_margin"
+ android:visibility="gone"
+ android:text="@string/notification_channel_summary_default"
android:clickable="false"
android:focusable="false"
android:ellipsize="end"
android:maxLines="2"
- android:layout_below="@id/alert_icon"
android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/>
- </RelativeLayout>
+ </LinearLayout>
- <RelativeLayout
+ <LinearLayout
android:id="@+id/silence"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:padding="@dimen/notification_importance_button_padding"
android:layout_marginTop="@dimen/notification_importance_button_separation"
+ android:padding="@dimen/notification_importance_button_padding"
android:clickable="true"
- android:focusable="true">
- <ImageView
- android:id="@+id/silence_icon"
- android:src="@drawable/ic_notifications_silence"
- android:background="@android:color/transparent"
- android:layout_gravity="center"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:clickable="false"
- android:focusable="false"/>
- <TextView
- android:id="@+id/silence_label"
+ android:focusable="true"
+ android:background="@drawable/notification_guts_priority_button_bg"
+ android:orientation="vertical">
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:ellipsize="end"
- android:maxLines="1"
- android:clickable="false"
- android:focusable="false"
- android:layout_toEndOf="@id/silence_icon"
- android:layout_marginStart="@dimen/notification_importance_drawable_padding"
- android:textAppearance="@style/TextAppearance.NotificationImportanceButton.Unselected"
- android:text="@string/notification_silence_title"/>
+ android:orientation="horizontal"
+ android:gravity="center"
+ >
+ <ImageView
+ android:id="@+id/silence_icon"
+ android:src="@drawable/ic_notifications_silence"
+ android:background="@android:color/transparent"
+ android:tint="@color/notification_guts_priority_contents"
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clickable="false"
+ android:focusable="false"/>
+ <TextView
+ android:id="@+id/silence_label"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:clickable="false"
+ android:focusable="false"
+ android:layout_toEndOf="@id/silence_icon"
+ android:layout_marginStart="@dimen/notification_importance_drawable_padding"
+ android:textAppearance="@style/TextAppearance.NotificationImportanceButton"
+ android:text="@string/notification_silence_title"/>
+ </LinearLayout>
<TextView
android:id="@+id/silence_summary"
- android:visibility="gone"
- android:paddingTop="@dimen/notification_importance_button_padding"
- android:text="@string/notification_channel_summary_default"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/notification_importance_button_description_top_margin"
+ android:visibility="gone"
+ android:text="@string/notification_channel_summary_low"
android:clickable="false"
android:focusable="false"
android:ellipsize="end"
android:maxLines="2"
- android:layout_below="@id/silence_icon"
android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/>
- </RelativeLayout>
+ </LinearLayout>
</LinearLayout>
<RelativeLayout
android:id="@+id/bottom_buttons"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/notification_guts_button_spacing" >
+ android:layout_height="60dp"
+ android:gravity="center_vertical"
+ android:paddingStart="4dp"
+ android:paddingEnd="4dp"
+ >
<TextView
android:id="@+id/turn_off_notifications"
android:text="@string/inline_turn_off_notifications"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
+ android:gravity="start|center_vertical"
android:minWidth="@dimen/notification_importance_toggle_size"
android:minHeight="@dimen/notification_importance_toggle_size"
android:maxWidth="200dp"
@@ -329,10 +350,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
- android:gravity="center_vertical|end"
- android:maxWidth="125dp"
+ android:gravity="end|center_vertical"
android:minWidth="@dimen/notification_importance_toggle_size"
android:minHeight="@dimen/notification_importance_toggle_size"
+ android:maxWidth="125dp"
style="@style/TextAppearance.NotificationInfo.Button"/>
</RelativeLayout>
diff --git a/packages/SystemUI/res/layout/qs_carrier.xml b/packages/SystemUI/res/layout/qs_carrier.xml
index 8dd06f0..28b2d21 100644
--- a/packages/SystemUI/res/layout/qs_carrier.xml
+++ b/packages/SystemUI/res/layout/qs_carrier.xml
@@ -35,7 +35,7 @@
android:layout_marginEnd="@dimen/qs_carrier_margin_width"
android:visibility="gone" />
- <view class="com.android.systemui.qs.QSCarrier$QSCarrierText"
+ <com.android.systemui.util.AutoMarqueeTextView
android:id="@+id/qs_carrier_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/quick_settings_header_info.xml b/packages/SystemUI/res/layout/quick_settings_header_info.xml
index 1e16e5d1..c812489 100644
--- a/packages/SystemUI/res/layout/quick_settings_header_info.xml
+++ b/packages/SystemUI/res/layout/quick_settings_header_info.xml
@@ -45,7 +45,7 @@
android:contentDescription="@string/accessibility_quick_settings_alarm_set"
android:visibility="gone"/>
- <TextView
+ <com.android.systemui.util.AutoMarqueeTextView
android:id="@+id/next_alarm_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -77,7 +77,7 @@
android:layout_height="@dimen/qs_header_alarm_icon_size"
android:visibility="gone"/>
- <TextView
+ <com.android.systemui.util.AutoMarqueeTextView
android:id="@+id/ringer_mode_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/start_contextual.xml b/packages/SystemUI/res/layout/start_contextual.xml
deleted file mode 100644
index e022c73..0000000
--- a/packages/SystemUI/res/layout/start_contextual.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:systemui="http://schemas.android.com/apk/res-auto"
- android:id="@+id/start_menu_container"
- android:layout_width="@dimen/navigation_key_width"
- android:layout_height="match_parent"
- android:importantForAccessibility="no"
- android:focusable="false"
- android:clipChildren="false"
- android:clipToPadding="false"
- >
- <include layout="@layout/rotate_suggestion"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="invisible"
- />
- <include layout="@layout/back"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="invisible"
- />
-</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-mcc310-mnc410/config.xml b/packages/SystemUI/res/values-mcc310-mnc410/config.xml
deleted file mode 100644
index 26b9192..0000000
--- a/packages/SystemUI/res/values-mcc310-mnc410/config.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2019, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources>
- <!-- Enable 5 bar signal strength icon -->
- <bool name="config_inflateSignalStrength">true</bool>
-</resources>
-
diff --git a/packages/SystemUI/res/values-mcc311-mnc480/config.xml b/packages/SystemUI/res/values-mcc311-mnc480/config.xml
deleted file mode 100644
index 7dadae7..0000000
--- a/packages/SystemUI/res/values-mcc311-mnc480/config.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources>
- <!-- Enable 5 bar signal strength icon -->
- <bool name="config_inflateSignalStrength">true</bool>
-</resources>
-
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index addc10a..dd3073f 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -42,12 +42,16 @@
<!-- The color of the text inside a notification -->
<color name="notification_primary_text_color">@*android:color/notification_primary_text_color_dark</color>
- <color name="notification_guts_selection_bg">@color/GM2_grey_800</color>
- <color name="notification_guts_selection_border">@color/GM2_grey_700</color>
<color name="notification_guts_link_icon_tint">@color/GM2_grey_500</color>
- <color name="notification_guts_sub_text_color">@color/GM2_grey_200</color>
+ <color name="notification_guts_sub_text_color">@color/GM2_grey_500</color>
<color name="notification_guts_header_text_color">@color/GM2_grey_200</color>
- <color name="notification_guts_button_color">@color/GM2_blue_200</color>
+ <color name="notification_guts_info_button_color">@color/GM2_blue_300</color>
+ <color name="notification_guts_priority_button_content_color">@color/GM2_grey_500</color>
+ <color name="notification_guts_priority_button_content_color_selected">@color/GM2_blue_300</color>
+ <color name="notification_guts_priority_button_bg_fill_color">@color/transparent</color>
+ <color name="notification_guts_priority_button_bg_fill_color_selected">@color/GM2_grey_800</color>
+ <color name="notification_guts_priority_button_bg_stroke_color">@color/GM2_grey_700</color>
+ <color name="notification_guts_priority_button_bg_stroke_color_selected">@color/GM2_grey_700</color>
<color name="notification_section_header_label_color">@color/GM2_grey_200</color>
<color name="notification_section_clear_all_btn_color">@color/GM2_grey_500</color>
diff --git a/packages/SystemUI/res/values-sw320dp/dimens.xml b/packages/SystemUI/res/values-sw320dp/dimens.xml
index 4390d35..2774554 100644
--- a/packages/SystemUI/res/values-sw320dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw320dp/dimens.xml
@@ -16,7 +16,7 @@
-->
<resources>
<!-- Global actions grid -->
- <dimen name="global_actions_grid_vertical_padding">0dp</dimen>
+ <dimen name="global_actions_grid_vertical_padding">3dp</dimen>
<dimen name="global_actions_grid_horizontal_padding">3dp</dimen>
<dimen name="global_actions_grid_item_side_margin">5dp</dimen>
@@ -24,9 +24,9 @@
<dimen name="global_actions_grid_item_width">64dp</dimen>
<dimen name="global_actions_grid_item_height">64dp</dimen>
- <dimen name="global_actions_grid_item_icon_width">18dp</dimen>
- <dimen name="global_actions_grid_item_icon_height">18dp</dimen>
- <dimen name="global_actions_grid_item_icon_top_margin">10dp</dimen>
+ <dimen name="global_actions_grid_item_icon_width">20dp</dimen>
+ <dimen name="global_actions_grid_item_icon_height">20dp</dimen>
+ <dimen name="global_actions_grid_item_icon_top_margin">12dp</dimen>
<dimen name="global_actions_grid_item_icon_side_margin">22dp</dimen>
<dimen name="global_actions_grid_item_icon_bottom_margin">4dp</dimen>
diff --git a/packages/SystemUI/res/values-sw392dp-land/dimens.xml b/packages/SystemUI/res/values-sw392dp-land/dimens.xml
deleted file mode 100644
index c718614..0000000
--- a/packages/SystemUI/res/values-sw392dp-land/dimens.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2019 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<resources>
-
- <!-- Global actions grid -->
- <dimen name="global_actions_grid_vertical_padding">3dp</dimen>
- <dimen name="global_actions_grid_horizontal_padding">0dp</dimen>
-
- <dimen name="global_actions_grid_item_side_margin">6dp</dimen>
- <dimen name="global_actions_grid_item_vertical_margin">10dp</dimen>
-
-</resources>
-
diff --git a/packages/SystemUI/res/values-sw392dp/dimens.xml b/packages/SystemUI/res/values-sw392dp/dimens.xml
index 2557ff4..be1d952 100644
--- a/packages/SystemUI/res/values-sw392dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw392dp/dimens.xml
@@ -16,7 +16,7 @@
-->
<resources>
<!-- Global actions grid -->
- <dimen name="global_actions_grid_vertical_padding">0dp</dimen>
+ <dimen name="global_actions_grid_vertical_padding">6dp</dimen>
<dimen name="global_actions_grid_horizontal_padding">3dp</dimen>
<dimen name="global_actions_grid_item_side_margin">10dp</dimen>
@@ -26,7 +26,7 @@
<dimen name="global_actions_grid_item_icon_width">22dp</dimen>
<dimen name="global_actions_grid_item_icon_height">22dp</dimen>
- <dimen name="global_actions_grid_item_icon_top_margin">12dp</dimen>
+ <dimen name="global_actions_grid_item_icon_top_margin">14dp</dimen>
<dimen name="global_actions_grid_item_icon_side_margin">22dp</dimen>
<dimen name="global_actions_grid_item_icon_bottom_margin">4dp</dimen>
diff --git a/packages/SystemUI/res/values-sw410dp-land/dimens.xml b/packages/SystemUI/res/values-sw410dp-land/dimens.xml
deleted file mode 100644
index 61ba2d0..0000000
--- a/packages/SystemUI/res/values-sw410dp-land/dimens.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2019 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<resources>
-
- <!-- Global actions grid -->
- <dimen name="global_actions_grid_vertical_padding">4dp</dimen>
- <dimen name="global_actions_grid_horizontal_padding">8dp</dimen>
-
- <dimen name="global_actions_grid_item_side_margin">8dp</dimen>
- <dimen name="global_actions_grid_item_vertical_margin">12dp</dimen>
-
-</resources>
-
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 811f1a3..3f84b32 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -39,16 +39,16 @@
<color name="qs_customize_decoration">@color/GM2_grey_100</color>
<!-- The color of the background in the separated list of the Global Actions menu -->
- <color name="global_actions_separated_background">@color/GM2_grey_300</color>
+ <color name="global_actions_separated_background">#F5F5F5</color>
<!-- The color of the background in the grid of the Global Actions menu -->
- <color name="global_actions_grid_background">@color/GM2_grey_200</color>
+ <color name="global_actions_grid_background">#F1F3F4</color>
<!-- The color of the text in the Global Actions menu -->
- <color name="global_actions_text">@color/GM2_grey_900</color>
+ <color name="global_actions_text">@color/GM2_grey_700</color>
<!-- The color of the text in the Global Actions menu -->
- <color name="global_actions_alert_text">@color/GM2_red_500</color>
+ <color name="global_actions_alert_text">@color/GM2_red_700</color>
<!-- Tint color for the content on the notification overflow card. -->
<color name="keyguard_overflow_content_color">#ff686868</color>
@@ -91,14 +91,18 @@
<!-- The "inside" of a notification, reached via longpress -->
<color name="notification_guts_bg_color">@color/GM2_grey_50</color>
- <color name="notification_guts_selection_bg">#FFFFFF</color>
- <color name="notification_guts_selection_border">#4285F4</color>
<color name="notification_guts_link_icon_tint">@color/GM2_grey_700</color>
<color name="notification_guts_sub_text_color">@color/GM2_grey_700</color>
<color name="notification_guts_header_text_color">@color/GM2_grey_900</color>
<color name="notification_silence_color">#FF32c1de</color>
<color name="notification_alert_color">#FFF87B2B</color>
- <color name="notification_guts_button_color">@color/GM2_blue_700</color>
+ <color name="notification_guts_info_button_color">@color/GM2_blue_700</color>
+ <color name="notification_guts_priority_button_content_color">@color/GM2_grey_700</color>
+ <color name="notification_guts_priority_button_content_color_selected">@color/GM2_blue_700</color>
+ <color name="notification_guts_priority_button_bg_fill_color">@color/transparent</color>
+ <color name="notification_guts_priority_button_bg_fill_color_selected">#FFFFFF</color>
+ <color name="notification_guts_priority_button_bg_stroke_color">@color/GM2_grey_300</color>
+ <color name="notification_guts_priority_button_bg_stroke_color_selected">@color/GM2_blue_600</color>
<color name="notification_section_header_label_color">@color/GM2_grey_900</color>
<color name="notification_section_clear_all_btn_color">@color/GM2_grey_700</color>
@@ -185,8 +189,11 @@
<color name="GM2_red_300">#F28B82</color>
<color name="GM2_red_500">#B71C1C</color>
+ <color name="GM2_red_700">#C5221F</color>
<color name="GM2_blue_200">#AECBFA</color>
+ <color name="GM2_blue_300">#8AB4F8</color>
+ <color name="GM2_blue_600">#1A73E8</color>
<color name="GM2_blue_700">#1967D2</color>
<color name="GM2_yellow_500">#FFFBBC04</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index d774c55..19e7b73 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -331,7 +331,7 @@
<!-- Nav bar button default ordering/layout -->
<string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string>
<string name="config_navBarLayoutQuickstep" translatable="false">back[1.7WC];home;contextual[1.7WC]</string>
- <string name="config_navBarLayoutHandle" translatable="false">start_contextual[40AC];home_handle;ime_switcher[40AC]</string>
+ <string name="config_navBarLayoutHandle" translatable="false">back[40AC];home_handle;ime_switcher[40AC]</string>
<bool name="quick_settings_show_full_alarm">false</bool>
@@ -417,10 +417,6 @@
it has been expanded to reveal its children. -->
<bool name="config_showGroupNotificationBgWhenExpanded">false</bool>
- <!-- Whether to artificially interpret all signal strengths as
- one bar higher than they actually are -->
- <bool name="config_inflateSignalStrength">false</bool>
-
<!-- Should we vibrate on an icon animation of the shelf. This should only be active if the
vibrator is capable of subtle vibrations -->
<bool name="config_vibrateOnIconAnimation">false</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ec538ff..c04d28a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -38,7 +38,6 @@
<dimen name="navigation_handle_sample_horizontal_margin">10dp</dimen>
<dimen name="navigation_home_handle_width">72dp</dimen>
-
<!-- Size of the nav bar edge panels, should be greater to the
edge sensitivity + the drag threshold -->
<dimen name="navigation_edge_panel_width">70dp</dimen>
@@ -57,6 +56,9 @@
<!-- Luminance change threshold that allows applying new value if difference was exceeded -->
<item name="navigation_luminance_change_threshold" type="dimen" format="float">0.05</item>
+ <dimen name="floating_rotation_button_diameter">40dp</dimen>
+ <dimen name="floating_rotation_button_margin">4dp</dimen>
+
<!-- Height of notification icons in the status bar -->
<dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen>
@@ -218,7 +220,7 @@
<dimen name="notification_guts_option_horizontal_padding">15dp</dimen>
<!-- The vertical space between items in the alert selections in the inline settings -->
- <dimen name="notification_guts_option_vertical_padding">20dp</dimen>
+ <dimen name="notification_guts_option_vertical_padding">16dp</dimen>
<!-- The vertical space between the alert selections in the inline settings -->
<dimen name="notification_guts_option_vertical_margin">6dp</dimen>
@@ -227,7 +229,7 @@
<dimen name="notification_importance_toggle_marginTop">28dp</dimen>
<dimen name="notification_importance_toggle_marginBottom">28dp</dimen>
<dimen name="notification_importance_text_marginTop">20dp</dimen>
- <dimen name="notification_importance_button_separation">16dp</dimen>
+ <dimen name="notification_importance_button_separation">8dp</dimen>
<dimen name="notification_importance_button_width">178dp</dimen>
<dimen name="notification_importance_button_horiz_padding">28dp</dimen>
<dimen name="notification_importance_drawable_padding">8dp</dimen>
@@ -236,8 +238,9 @@
<dimen name="notification_importance_description_text">14sp</dimen>
<dimen name="notification_importance_channel_text">16sp</dimen>
<dimen name="notification_importance_channel_group_text">14sp</dimen>
- <dimen name="notification_importance_button_text">16sp</dimen>
- <dimen name="notification_importance_button_padding">14dp</dimen>
+ <dimen name="notification_importance_button_text">14sp</dimen>
+ <dimen name="notification_importance_button_padding">16dp</dimen>
+ <dimen name="notification_importance_button_description_top_margin">12dp</dimen>
<dimen name="rect_button_radius">8dp</dimen>
<!-- The minimum height for the snackbar shown after the snooze option has been chosen. -->
@@ -950,7 +953,7 @@
<!-- Global actions grid layout -->
<dimen name="global_actions_grid_side_margin">4dp</dimen>
- <dimen name="global_actions_grid_container_bottom_margin">4dp</dimen>
+ <dimen name="global_actions_grid_container_bottom_margin">8dp</dimen>
<!-- Used to workaround a bug where shadows are clipped during animations by expanding
the bounds of the parent view. -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 9831250..8bc84c6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1677,25 +1677,16 @@
<string name="inline_keep_showing_app">Keep showing notifications from this app?</string>
<!-- [CHAR LIMIT=100] Notification Importance title -->
- <string name="notification_silence_title">Gentle</string>
+ <string name="notification_silence_title">Silent</string>
<!-- [CHAR LIMIT=100] Notification Importance title -->
- <string name="notification_alert_title">Prioritized</string>
+ <string name="notification_alert_title">Alerting</string>
<!-- [CHAR LIMIT=150] Notification Importance title: low importance level summary -->
- <string name="notification_channel_summary_low">Helps you focus with notifications only in the pull-down shade. Always silent.</string>
-
- <!-- [CHAR LIMIT=150] Notification Importance title: low importance level summary -->
- <string name="notification_channel_summary_low_status">Displays below priority notifications. Always silent.</string>
-
- <!-- [CHAR LIMIT=150] Notification Importance title: low importance level summary -->
- <string name="notification_channel_summary_low_lock">Displays below priority notifications. Always silent.</string>
-
- <!-- [CHAR LIMIT=150] Notification Importance title: low importance level summary -->
- <string name="notification_channel_summary_low_status_lock">Displays below priority notifications. Always silent.</string>
+ <string name="notification_channel_summary_low">Helps you focus without sound or vibration.</string>
<!-- [CHAR LIMIT=150] Notification Importance title: normal importance level summary -->
- <string name="notification_channel_summary_default">Gets your attention with sound & a status bar icon. Shows on lock screen.</string>
+ <string name="notification_channel_summary_default">Gets your attention with sound or vibration.</string>
<!-- Notification: Control panel: Label that displays when the app's notifications cannot be blocked. -->
<string name="notification_unblockable_desc">These notifications can\'t be modified.</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 9861750..b387793 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -314,11 +314,12 @@
<item name="*android:errorColor">?android:attr/colorError</item>
</style>
- <style name="qs_theme" parent="@style/Theme.SystemUI">
+ <style name="qs_theme" parent="@*android:style/Theme.DeviceDefault.QuickSettings">
<item name="lightIconTheme">@style/QSIconTheme</item>
<item name="darkIconTheme">@style/QSIconTheme</item>
<item name="android:colorError">@*android:color/error_color_material_dark</item>
<item name="android:windowIsFloating">true</item>
+ <item name="android:homeAsUpIndicator">@drawable/ic_arrow_back</item>
</style>
<style name="systemui_theme_remote_input" parent="@android:style/Theme.DeviceDefault.Light">
@@ -459,9 +460,9 @@
</style>
<style name="TextAppearance.NotificationInfo.Button">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
- <item name="android:textSize">16sp</item>
- <item name="android:textColor">@color/notification_guts_button_color</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:textColor">@color/notification_guts_info_button_color</item>
<item name="android:background">@drawable/btn_borderless_rect</item>
<item name="android:gravity">center_vertical</item>
<item name="android:focusable">true</item>
@@ -469,21 +470,21 @@
<style name="TextAppearance.NotificationImportanceChannel">
<item name="android:textSize">@dimen/notification_importance_channel_text</item>
- <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
<item name="android:textColor">@color/notification_guts_header_text_color</item>
<item name="android:textSize">@dimen/notification_importance_channel_text</item>
</style>
<style name="TextAppearance.NotificationImportanceChannelGroup">
<item name="android:textSize">@dimen/notification_importance_channel_group_text</item>
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:textColor">@color/notification_guts_sub_text_color</item>
<item name="android:textSize">@dimen/notification_importance_channel_group_text</item>
</style>
<style name="TextAppearance.NotificationImportanceHeader">
<item name="android:textSize">@dimen/notification_importance_header_text</item>
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:textColor">@color/notification_guts_header_text_color</item>
</style>
@@ -496,18 +497,11 @@
<style name="TextAppearance.NotificationImportanceButton">
<item name="android:textSize">@dimen/notification_importance_button_text</item>
- <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+ <item name="android:textColor">@color/notification_guts_priority_contents</item>
<item name="android:gravity">center</item>
</style>
- <style name="TextAppearance.NotificationImportanceButton.Selected" parent="TextAppearance.NotificationImportanceButton">
- <item name="android:textColor">?android:attr/colorAccent</item>
- </style>
-
- <style name="TextAppearance.NotificationImportanceButton.Unselected" parent="TextAppearance.NotificationImportanceButton">
- <item name="android:textColor">?android:attr/textColorPrimary</item>
- </style>
-
<style name="TextAppearance.HeadsUpStatusBarText"
parent="@*android:style/TextAppearance.DeviceDefault.Notification.Info">
</style>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 050655c..0ec60e5df 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -33,6 +33,7 @@
import android.view.View;
import android.view.WindowManager;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.NavigationBarController;
import com.android.systemui.statusbar.phone.NavigationBarView;
@@ -234,7 +235,8 @@
}
- private final static class KeyguardPresentation extends Presentation {
+ @VisibleForTesting
+ static final class KeyguardPresentation extends Presentation {
private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height
private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s
private final InjectionInflationController mInjectableInflater;
@@ -256,7 +258,7 @@
KeyguardPresentation(Context context, Display display,
InjectionInflationController injectionInflater) {
- super(context, display, R.style.keyguard_presentation_theme);
+ super(context, display, R.style.Theme_SystemUI_KeyguardPresentation);
mInjectableInflater = injectionInflater;
getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
setCancelable(false);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 4ec79a6..392183b 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -16,6 +16,7 @@
package com.android.systemui.bubbles;
+import static android.app.Notification.FLAG_BUBBLE;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
@@ -40,8 +41,6 @@
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityTaskManager;
-import android.app.IActivityTaskManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -53,6 +52,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.Settings;
+import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.service.notification.ZenModeConfig;
import android.util.Log;
@@ -138,7 +138,6 @@
private final Context mContext;
private final NotificationEntryManager mNotificationEntryManager;
- private final IActivityTaskManager mActivityTaskManager;
private final BubbleTaskStackListener mTaskStackListener;
private BubbleStateChangeListener mStateChangeListener;
private BubbleExpandListener mExpandListener;
@@ -250,7 +249,6 @@
mStatusBarStateListener = new StatusBarStateListener();
Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener);
- mActivityTaskManager = ActivityTaskManager.getService();
mTaskStackListener = new BubbleTaskStackListener();
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
@@ -509,6 +507,12 @@
updateBubble(entry);
}
}
+
+ @Override
+ public void onNotificationRankingUpdated(RankingMap rankingMap) {
+ // Forward to BubbleData to block any bubbles which should no longer be shown
+ mBubbleData.notificationRankingUpdated(rankingMap);
+ }
};
@SuppressWarnings("FieldCanBeLocal")
@@ -547,8 +551,11 @@
mNotificationEntryManager.performRemoveNotification(bubble.entry.notification,
UNDEFINED_DISMISS_REASON);
} else {
- // The notification is still in the shade but we've removed the bubble so
- // lets make sure NoMan knows it's not a bubble anymore
+ // Update the flag for SysUI
+ bubble.entry.notification.getNotification().flags &= ~FLAG_BUBBLE;
+
+ // Make sure NoMan knows it's not a bubble anymore so anyone querying it will
+ // get right result back
try {
mBarService.onNotificationBubbleChanged(bubble.getKey(),
false /* isBubble */);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 6ab973e..5575b35 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -22,6 +22,8 @@
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationListenerService.RankingMap;
import android.util.Log;
import android.util.Pair;
@@ -114,6 +116,8 @@
// State tracked during an operation -- keeps track of what listener events to dispatch.
private Update mStateChange;
+ private NotificationListenerService.Ranking mTmpRanking;
+
private TimeSource mTimeSource = System::currentTimeMillis;
@Nullable
@@ -193,6 +197,31 @@
dispatchPendingChanges();
}
+ /**
+ * Called when NotificationListener has received adjusted notification rank and reapplied
+ * filtering and sorting. This is used to dismiss any bubbles which should no longer be shown
+ * due to changes in permissions on the notification channel or the global setting.
+ *
+ * @param rankingMap the updated ranking map from NotificationListenerService
+ */
+ public void notificationRankingUpdated(RankingMap rankingMap) {
+ if (mTmpRanking == null) {
+ mTmpRanking = new NotificationListenerService.Ranking();
+ }
+
+ String[] orderedKeys = rankingMap.getOrderedKeys();
+ for (int i = 0; i < orderedKeys.length; i++) {
+ String key = orderedKeys[i];
+ if (hasBubbleWithKey(key)) {
+ rankingMap.getRanking(key, mTmpRanking);
+ if (!mTmpRanking.canBubble()) {
+ doRemove(key, BubbleController.DISMISS_BLOCKED);
+ }
+ }
+ }
+ dispatchPendingChanges();
+ }
+
private void doAdd(Bubble bubble) {
if (DEBUG) {
Log.d(TAG, "doAdd: " + bubble);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 0abd25d..c63389a 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -237,16 +237,16 @@
private ViewClippingUtil.ClippingParameters mClippingParameters =
new ViewClippingUtil.ClippingParameters() {
- @Override
- public boolean shouldFinish(View view) {
- return false;
- }
+ @Override
+ public boolean shouldFinish(View view) {
+ return false;
+ }
- @Override
- public boolean isClippingEnablingAllowed(View view) {
- return !mIsExpanded;
- }
- };
+ @Override
+ public boolean isClippingEnablingAllowed(View view) {
+ return !mIsExpanded;
+ }
+ };
/** Float property that 'drags' the flyout. */
private final FloatPropertyCompat mFlyoutCollapseProperty =
@@ -291,7 +291,7 @@
private boolean mSuppressFlyout = false;
public BubbleStackView(Context context, BubbleData data,
- @Nullable SurfaceSynchronizer synchronizer) {
+ @Nullable SurfaceSynchronizer synchronizer) {
super(context);
mBubbleData = data;
@@ -699,6 +699,7 @@
animateInFlyoutForBubble(bubble);
requestUpdate();
logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
+ updatePointerPosition();
}
// via BubbleData.Listener
@@ -714,6 +715,7 @@
} else {
Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
}
+ updatePointerPosition();
}
// via BubbleData.Listener
@@ -730,7 +732,6 @@
}
}
-
/**
* Changes the currently selected bubble. If the stack is already expanded, the newly selected
* bubble will be shown immediately. This does not change the expanded state or change the
@@ -1073,8 +1074,8 @@
(overscrollingPastDot ? collapsePercent - 1f : collapsePercent * -1)
* (overscrollingLeft ? -1 : 1)
* (mFlyout.getWidth() / (FLYOUT_OVERSCROLL_ATTENUATION_FACTOR
- // Attenuate the smaller dot less than the larger flyout.
- / (overscrollingPastDot ? 2 : 1)));
+ // Attenuate the smaller dot less than the larger flyout.
+ / (overscrollingPastDot ? 2 : 1)));
}
mFlyout.setTranslationX(mFlyout.getRestingTranslationX() + overscrollTranslation);
@@ -1530,18 +1531,22 @@
if (DEBUG) {
Log.d(TAG, "updatePointerPosition()");
}
- Bubble expandedBubble = getExpandedBubble();
- if (expandedBubble != null) {
- BubbleView iconView = expandedBubble.iconView;
- float bubbleLeft = iconView.getTranslationX();
- float halfBubbleWidth = (iconView.getWidth() / 2f);
- // Bubbles live in expanded view container (x includes expanded view padding).
- // Pointer lives in expanded view, which has padding (x does not include padding).
- // Remove padding when deriving pointer location from bubbles.
- float pointerX = bubbleLeft - mExpandedViewPadding + halfBubbleWidth;
- expandedBubble.expandedView.setPointerPosition((int) pointerX);
+ Bubble expandedBubble = getExpandedBubble();
+ if (expandedBubble == null) {
+ return;
}
+
+ int index = getBubbleIndex(expandedBubble);
+ float bubbleLeftFromScreenLeft = mExpandedAnimationController.getBubbleLeft(index);
+ float halfBubble = mBubbleSize / 2f;
+
+ // Bubbles live in expanded view container (x includes expanded view padding).
+ // Pointer lives in expanded view, which has padding (x does not include padding).
+ // Remove padding when deriving pointer location from bubbles.
+ float bubbleCenter = bubbleLeftFromScreenLeft + halfBubble - mExpandedViewPadding;
+
+ expandedBubble.expandedView.setPointerPosition(bubbleCenter);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index ae8043f..24337a3 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -63,6 +63,8 @@
private Point mDisplaySize;
/** Size of dismiss target at bottom of screen. */
private float mPipDismissHeight;
+ /** Max number of bubbles shown in row above expanded view.*/
+ private int mBubblesMaxRendered;
/** Whether the dragged-out bubble is in the dismiss target. */
private boolean mIndividualBubbleWithinDismissTarget = false;
@@ -112,32 +114,25 @@
mStatusBarHeight =
res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
mPipDismissHeight = res.getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height);
+ mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered);
}
/**
* Animates expanding the bubbles into a row along the top of the screen.
- *
- * @return The y-value to which the bubbles were expanded, in case that's useful.
*/
- public float expandFromStack(PointF collapseTo, Runnable after) {
+ public void expandFromStack(PointF collapseTo, Runnable after) {
animationsForChildrenFromIndex(
0, /* startIndex */
new ChildAnimationConfigurator() {
- // How much to translate the next bubble, so that it is not overlapping the
- // previous one.
- float mTranslateNextBubbleXBy = mExpandedViewPadding + mBubblePaddingPx;
-
@Override
public void configureAnimationForChildAtIndex(
int index, PhysicsAnimationLayout.PhysicsPropertyAnimator animation) {
- animation.position(mTranslateNextBubbleXBy, getExpandedY());
- mTranslateNextBubbleXBy += mBubbleSizePx + mBubblePaddingPx;
+ animation.position(getBubbleLeft(index), getExpandedY());
}
})
.startAll(after);
mCollapseToPoint = collapseTo;
- return getExpandedY();
}
/** Animate collapsing the bubbles back to their stacked position. */
@@ -189,9 +184,7 @@
final boolean draggedOutEnough =
y > getExpandedY() + mBubbleSizePx || y < getExpandedY() - mBubbleSizePx;
if (draggedOutEnough != mBubbleDraggedOutEnough) {
- animateStackByBubbleWidthsStartingFrom(
- /* numBubbleWidths */ draggedOutEnough ? -1 : 0,
- /* startIndex */ mLayout.indexOfChild(bubbleView) + 1);
+ updateBubblePositions();
mBubbleDraggedOutEnough = draggedOutEnough;
}
}
@@ -200,17 +193,14 @@
public void dismissDraggedOutBubble(Runnable after) {
mIndividualBubbleWithinDismissTarget = false;
- // Fill the space from the soon to be dismissed bubble.
- animateStackByBubbleWidthsStartingFrom(
- /* numBubbleWidths */ -1,
- /* startIndex */ mLayout.indexOfChild(mBubbleDraggingOut) + 1);
-
animationForChild(mBubbleDraggingOut)
.withStiffness(SpringForce.STIFFNESS_HIGH)
.scaleX(1.1f)
.scaleY(1.1f)
.alpha(0f, after)
.start();
+
+ updateBubblePositions();
}
/** Magnets the given bubble to the dismiss target. */
@@ -251,15 +241,13 @@
final int index = mLayout.indexOfChild(bubbleView);
animationForChildAtIndex(index)
- .position(getXForChildAtIndex(index), getExpandedY())
- .withPositionStartVelocities(velX, velY)
- .start(() -> bubbleView.setTranslationZ(0f) /* after */);
-
- animateStackByBubbleWidthsStartingFrom(
- /* numBubbleWidths */ 0, /* startIndex */ index + 1);
+ .position(getBubbleLeft(index), getExpandedY())
+ .withPositionStartVelocities(velX, velY)
+ .start(() -> bubbleView.setTranslationZ(0f) /* after */);
mBubbleDraggingOut = null;
mBubbleDraggedOutEnough = false;
+ updateBubblePositions();
}
/**
@@ -337,8 +325,6 @@
@Override
void onChildAdded(View child, int index) {
- // Pop in from the top.
- // TODO: Reverse this when bubbles are at the bottom.
child.setTranslationX(getXForChildAtIndex(index));
animationForChild(child)
@@ -346,7 +332,7 @@
getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */
getExpandedY() /* to */)
.start();
- animateBubblesAfterIndexToCorrectX(index);
+ updateBubblePositions();
}
@Override
@@ -367,7 +353,7 @@
}
// Animate all the other bubbles to their new positions sans this bubble.
- animateBubblesAfterIndexToCorrectX(index);
+ updateBubblePositions();
}
@Override
@@ -383,21 +369,18 @@
.start(() -> super.setChildVisibility(child, index, visibility) /* after */);
}
- /**
- * Animates the bubbles after the given index to the X position they should be in according to
- * {@link #getXForChildAtIndex}.
- */
- private void animateBubblesAfterIndexToCorrectX(int start) {
- for (int i = start; i < mLayout.getChildCount(); i++) {
+ private void updateBubblePositions() {
+ for (int i = 0; i < mLayout.getChildCount(); i++) {
final View bubble = mLayout.getChildAt(i);
// Don't animate the dragging out bubble, or it'll jump around while being dragged. It
// will be snapped to the correct X value after the drag (if it's not dismissed).
- if (!bubble.equals(mBubbleDraggingOut)) {
- animationForChild(bubble)
- .translationX(getXForChildAtIndex(i))
- .start();
+ if (bubble.equals(mBubbleDraggingOut)) {
+ return;
}
+ animationForChild(bubble)
+ .translationX(getBubbleLeft(i))
+ .start();
}
}
@@ -405,4 +388,34 @@
private float getXForChildAtIndex(int index) {
return mBubblePaddingPx + (mBubbleSizePx + mBubblePaddingPx) * index;
}
+
+ /**
+ * @param index Bubble index in row.
+ * @return Bubble left x from left edge of screen.
+ */
+ public float getBubbleLeft(int index) {
+ float bubbleLeftFromRowLeft = index * (mBubbleSizePx + mBubblePaddingPx);
+ return getRowLeft() + bubbleLeftFromRowLeft;
+ }
+
+ private float getRowLeft() {
+ if (mLayout == null) {
+ return 0;
+ }
+ int bubbleCount = mLayout.getChildCount();
+ if (bubbleCount > mBubblesMaxRendered) {
+ // Only shown bubbles are relevant for calculating position.
+ bubbleCount = mBubblesMaxRendered;
+ }
+ // Width calculations.
+ double bubble = bubbleCount * mBubbleSizePx;
+ float gap = (bubbleCount - 1) * mBubblePaddingPx;
+ float row = gap + (float) bubble;
+
+ float halfRow = row / 2f;
+ float centerScreen = mDisplaySize.x / 2;
+ float rowLeftFromScreenLeft = centerScreen - halfRow;
+
+ return rowLeftFromScreenLeft;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/QSCarrier.java
index c5bf402..4d18312 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSCarrier.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSCarrier.java
@@ -33,7 +33,7 @@
public class QSCarrier extends LinearLayout {
private View mMobileGroup;
- private QSCarrierText mCarrierText;
+ private TextView mCarrierText;
private ImageView mMobileSignal;
private ImageView mMobileRoaming;
private DualToneHandler mDualToneHandler;
@@ -111,43 +111,4 @@
public void setCarrierText(CharSequence text) {
mCarrierText.setText(text);
}
-
- /**
- * TextView that changes its ellipsize value with its visibility.
- */
- public static class QSCarrierText extends TextView {
-
- public QSCarrierText(Context context) {
- super(context);
- }
-
- public QSCarrierText(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public QSCarrierText(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public QSCarrierText(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
- @Override
- protected void onFinishInflate() {
- setSelected(true);
- }
-
- @Override
- protected void onVisibilityChanged(View changedView, int visibility) {
- super.onVisibilityChanged(changedView, visibility);
- // Only show marquee when visible
- if (visibility == VISIBLE) {
- setEllipsize(TextUtils.TruncateAt.MARQUEE);
- } else {
- setEllipsize(TextUtils.TruncateAt.END);
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 9d5871e..c375574 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -169,6 +169,5 @@
public interface NotificationSettingsListener {
default void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) { }
-
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index fe8c6b7..1440803 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -194,7 +194,7 @@
}
final Notification.Action action =
statusBarNotification.getNotification().actions[actionIndex];
- if (Objects.equals(action.actionIntent, actionIntent)) {
+ if (!Objects.equals(action.actionIntent, actionIntent)) {
Log.w(TAG, "actionIntent does not match");
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
index a5a6d87..1aa6bc9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification;
import android.annotation.Nullable;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import com.android.internal.statusbar.NotificationVisibility;
@@ -98,4 +100,14 @@
@Nullable NotificationVisibility visibility,
boolean removedByUser) {
}
+
+ /**
+ * Called whenever notification ranking changes, in response to
+ * {@link NotificationListenerService#onNotificationRankingUpdate}. This is called after
+ * NotificationData has processed the update and notifications have been re-sorted and filtered.
+ *
+ * @param rankingMap provides access to ranking information on currently active notifications
+ */
+ default void onNotificationRankingUpdated(RankingMap rankingMap) {
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 3ac5768..e8388ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -483,6 +483,10 @@
}
updateNotifications();
+
+ for (NotificationEntryListener listener : mNotificationEntryListeners) {
+ listener.onNotificationRankingUpdated(rankingMap);
+ }
}
private void updateRankingOfPendingNotifications(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index c65e90e..35cc960 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -65,7 +65,7 @@
private val mDozeParameters: DozeParameters;
var willWakeUp = false
set(value) {
- if (value && mDozeAmount != 0.0f) {
+ if (!value || mDozeAmount != 0.0f) {
field = value
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index e4e8c80..1dc96b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -20,6 +20,10 @@
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -40,11 +44,12 @@
import android.metrics.LogMaker;
import android.os.Handler;
import android.os.RemoteException;
-import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
-import android.transition.AutoTransition;
+import android.transition.ChangeBounds;
+import android.transition.Fade;
import android.transition.TransitionManager;
+import android.transition.TransitionSet;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
@@ -62,6 +67,7 @@
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.logging.NotificationCounters;
+import java.lang.annotation.Retention;
import java.util.List;
import java.util.Set;
@@ -92,9 +98,8 @@
// standard controls
private static final int ACTION_ALERT = 5;
- private static final int BUTTON_ANIM_TIME_MS = 200;
-
- private static final boolean SHOW_BUTTON_SUMMARY = false;
+ private TextView mPriorityDescriptionView;
+ private TextView mSilentDescriptionView;
private INotificationManager mINotificationManager;
private PackageManager mPm;
@@ -110,8 +115,6 @@
private NotificationChannel mSingleNotificationChannel;
private int mStartingChannelImportance;
private boolean mWasShownHighPriority;
- private boolean mShowOnLockscreen;
- private boolean mShowInStatusBar;
private boolean mPressedApply;
/**
@@ -129,8 +132,6 @@
private OnSettingsClickListener mOnSettingsClickListener;
private OnAppSettingsClickListener mAppSettingsClickListener;
private NotificationGuts mGutsContainer;
- private Drawable mSelectedBackground;
- private Drawable mUnselectedBackground;
private Drawable mPkgIcon;
/** Whether this view is being shown as part of the blocking helper. */
@@ -145,16 +146,14 @@
private OnClickListener mOnAlert = v -> {
mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING;
mChosenImportance = IMPORTANCE_DEFAULT;
- setImportanceSummary(ACTION_ALERT, true);
- updateButtons(ACTION_ALERT);
+ applyAlertingBehavior(BEHAVIOR_ALERTING, true /* userTriggered */);
};
// used by standard ui
private OnClickListener mOnSilent = v -> {
mExitReason = NotificationCounters.BLOCKING_HELPER_DELIVER_SILENTLY;
mChosenImportance = IMPORTANCE_LOW;
- setImportanceSummary(ACTION_TOGGLE_SILENT, true);
- updateButtons(ACTION_TOGGLE_SILENT);
+ applyAlertingBehavior(BEHAVIOR_SILENT, true /* userTriggered */);
};
// used by standard ui
@@ -218,6 +217,14 @@
super(context, attrs);
}
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mPriorityDescriptionView = findViewById(R.id.alert_summary);
+ mSilentDescriptionView = findViewById(R.id.silence_summary);
+ }
+
// Specify a CheckSaveListener to override when/if the user's changes are committed.
public interface CheckSaveListener {
// Invoked when importance has changed and the NotificationInfo wants to try to save it.
@@ -293,9 +300,6 @@
mDelegatePkg = mSbn.getOpPkg();
mIsDeviceProvisioned = isDeviceProvisioned;
- mSelectedBackground = mContext.getDrawable(R.drawable.button_border_selected);
- mUnselectedBackground = mContext.getDrawable(R.drawable.button_border_unselected);
-
int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage(
pkg, mAppUid, false /* includeDeleted */);
if (mNumUniqueChannelsInRow == 0) {
@@ -308,11 +312,6 @@
&& numTotalChannels == 1;
}
- mShowInStatusBar = !mINotificationManager.shouldHideSilentStatusIcons(
- mContext.getPackageName());
- mShowOnLockscreen = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 0) == 1;
-
bindHeader();
bindChannelDetails();
@@ -376,13 +375,9 @@
silent.setOnClickListener(mOnSilent);
alert.setOnClickListener(mOnAlert);
- if (mWasShownHighPriority) {
- updateButtons(ACTION_ALERT);
- setImportanceSummary(ACTION_ALERT, false);
- } else {
- updateButtons(ACTION_TOGGLE_SILENT);
- setImportanceSummary(ACTION_TOGGLE_SILENT, false);
- }
+ applyAlertingBehavior(
+ mWasShownHighPriority ? BEHAVIOR_ALERTING : BEHAVIOR_SILENT,
+ false /* userTriggered */);
}
private void bindHeader() {
@@ -545,66 +540,45 @@
}
}
- private void updateButtons(int blockState) {
- View silence = findViewById(R.id.silence);
- View alert = findViewById(R.id.alert);
- TextView done = findViewById(R.id.done);
- switch (blockState) {
- case ACTION_TOGGLE_SILENT:
- updateButtons(silence, alert);
- if (mWasShownHighPriority) {
- done.setText(R.string.inline_ok_button);
- } else {
- done.setText(R.string.inline_done_button);
- }
- break;
- case ACTION_ALERT:
- updateButtons(alert, silence);
- if (mWasShownHighPriority) {
- done.setText(R.string.inline_done_button);
- } else {
- done.setText(R.string.inline_ok_button);
- }
- break;
- }
- }
-
- private void updateButtons(View selected, View unselected) {
- selected.setBackground(mSelectedBackground);
- selected.setSelected(true);
- unselected.setBackground(mUnselectedBackground);
- unselected.setSelected(false);
- }
-
- void setImportanceSummary(int blockState, boolean userTriggered) {
+ private void applyAlertingBehavior(@AlertingBehavior int behavior, boolean userTriggered) {
if (userTriggered) {
- AutoTransition transition = new AutoTransition();
- transition.setDuration(BUTTON_ANIM_TIME_MS);
+ TransitionSet transition = new TransitionSet();
+ transition.setOrdering(TransitionSet.ORDERING_TOGETHER);
+ transition.addTransition(new Fade(Fade.OUT))
+ .addTransition(new ChangeBounds())
+ .addTransition(
+ new Fade(Fade.IN)
+ .setStartDelay(150)
+ .setDuration(200)
+ .setInterpolator(FAST_OUT_SLOW_IN));
+ transition.setDuration(350);
+ transition.setInterpolator(FAST_OUT_SLOW_IN);
TransitionManager.beginDelayedTransition(this, transition);
}
- if (SHOW_BUTTON_SUMMARY) {
- if (blockState == ACTION_ALERT) {
- TextView view = findViewById(R.id.alert_summary);
- view.setVisibility(VISIBLE);
- findViewById(R.id.silence_summary).setVisibility(GONE);
- view.setText(R.string.notification_channel_summary_default);
- } else {
- TextView view = findViewById(R.id.silence_summary);
- view.setVisibility(VISIBLE);
- findViewById(R.id.alert_summary).setVisibility(GONE);
- if (mShowInStatusBar) {
- if (mShowOnLockscreen) {
- view.setText(R.string.notification_channel_summary_low_status_lock);
- } else {
- view.setText(R.string.notification_channel_summary_low_status);
- }
- } else if (mShowOnLockscreen) {
- view.setText(R.string.notification_channel_summary_low_lock);
- } else {
- view.setText(R.string.notification_channel_summary_low);
- }
- }
+
+ View alert = findViewById(R.id.alert);
+ View silence = findViewById(R.id.silence);
+
+ switch (behavior) {
+ case BEHAVIOR_ALERTING:
+ alert.setSelected(true);
+ silence.setSelected(false);
+ mPriorityDescriptionView.setVisibility(VISIBLE);
+ mSilentDescriptionView.setVisibility(GONE);
+ break;
+ case BEHAVIOR_SILENT:
+ alert.setSelected(false);
+ silence.setSelected(true);
+ mSilentDescriptionView.setVisibility(VISIBLE);
+ mPriorityDescriptionView.setVisibility(GONE);
+ break;
+ default:
+ throw new IllegalArgumentException("Unrecognized alerting behavior: " + behavior);
}
+
+ boolean isAChange = mWasShownHighPriority != (behavior == BEHAVIOR_ALERTING);
+ TextView done = findViewById(R.id.done);
+ done.setText(isAChange ? R.string.inline_ok_button : R.string.inline_done_button);
}
private void saveImportanceAndExitReason(@NotificationInfoAction int action) {
@@ -883,4 +857,10 @@
.setSubtype(mIsForBlockingHelper ? MetricsEvent.BLOCKING_HELPER_DISPLAY
: MetricsEvent.BLOCKING_HELPER_UNKNOWN);
}
+
+ @Retention(SOURCE)
+ @IntDef({BEHAVIOR_ALERTING, BEHAVIOR_SILENT})
+ private @interface AlertingBehavior {}
+ private static final int BEHAVIOR_ALERTING = 0;
+ private static final int BEHAVIOR_SILENT = 1;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
new file mode 100644
index 0000000..6286d4d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.Surface;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.KeyButtonDrawable;
+import com.android.systemui.statusbar.policy.KeyButtonView;
+
+/** Containing logic for the rotation button on the physical left bottom corner of the screen. */
+public class FloatingRotationButton implements RotationButton {
+
+ private final Context mContext;
+ private final WindowManager mWindowManager;
+ private final KeyButtonView mKeyButtonView;
+ private final int mDiameter;
+ private final int mMargin;
+ private KeyButtonDrawable mKeyButtonDrawable;
+ private boolean mIsShowing;
+ private boolean mCanShow = true;
+
+ private RotationButtonController mRotationButtonController;
+
+ FloatingRotationButton(Context context) {
+ mContext = context;
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ mKeyButtonView = (KeyButtonView) LayoutInflater.from(mContext).inflate(
+ R.layout.rotate_suggestion, null);
+ mKeyButtonView.setVisibility(View.VISIBLE);
+
+ Resources resources = mContext.getResources();
+ mDiameter = resources.getDimensionPixelSize(R.dimen.floating_rotation_button_diameter);
+ mMargin = resources.getDimensionPixelSize(R.dimen.floating_rotation_button_margin);
+ }
+
+ @Override
+ public void setRotationButtonController(RotationButtonController rotationButtonController) {
+ mRotationButtonController = rotationButtonController;
+ }
+
+ @Override
+ public View getCurrentView() {
+ return mKeyButtonView;
+ }
+
+ @Override
+ public boolean show() {
+ if (!mCanShow || mIsShowing) {
+ return false;
+ }
+ mIsShowing = true;
+ int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(mDiameter, mDiameter,
+ mMargin, mMargin, WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, flags,
+ PixelFormat.TRANSLUCENT);
+ lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.setTitle("FloatingRotationButton");
+ switch (mWindowManager.getDefaultDisplay().getRotation()) {
+ case Surface.ROTATION_0:
+ lp.gravity = Gravity.BOTTOM | Gravity.LEFT;
+ break;
+ case Surface.ROTATION_90:
+ lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
+ break;
+ case Surface.ROTATION_180:
+ lp.gravity = Gravity.TOP | Gravity.RIGHT;
+ break;
+ case Surface.ROTATION_270:
+ lp.gravity = Gravity.TOP | Gravity.LEFT;
+ break;
+ default:
+ break;
+ }
+ updateIcon();
+ mWindowManager.addView(mKeyButtonView, lp);
+ if (mKeyButtonDrawable != null && mKeyButtonDrawable.canAnimate()) {
+ mKeyButtonDrawable.resetAnimation();
+ mKeyButtonDrawable.startAnimation();
+ }
+ return true;
+ }
+
+ @Override
+ public boolean hide() {
+ if (!mIsShowing) {
+ return false;
+ }
+ mWindowManager.removeViewImmediate(mKeyButtonView);
+ mRotationButtonController.cleanUp();
+ mIsShowing = false;
+ return true;
+ }
+
+ @Override
+ public boolean isVisible() {
+ return mIsShowing;
+ }
+
+ @Override
+ public void updateIcon() {
+ if (!mIsShowing) {
+ return;
+ }
+ mKeyButtonDrawable = getImageDrawable();
+ mKeyButtonView.setImageDrawable(mKeyButtonDrawable);
+ mKeyButtonDrawable.setCallback(mKeyButtonView);
+ if (mKeyButtonDrawable != null && mKeyButtonDrawable.canAnimate()) {
+ mKeyButtonDrawable.resetAnimation();
+ mKeyButtonDrawable.startAnimation();
+ }
+ }
+
+ @Override
+ public void setOnClickListener(View.OnClickListener onClickListener) {
+ mKeyButtonView.setOnClickListener(onClickListener);
+ }
+
+ @Override
+ public void setOnHoverListener(View.OnHoverListener onHoverListener) {
+ mKeyButtonView.setOnHoverListener(onHoverListener);
+ }
+
+ @Override
+ public KeyButtonDrawable getImageDrawable() {
+ Context context = new ContextThemeWrapper(mContext.getApplicationContext(),
+ mRotationButtonController.getStyleRes());
+ return KeyButtonDrawable.create(context, R.drawable.ic_sysbar_rotate_button,
+ false /* shadow */, true /* hasOvalBg */);
+ }
+
+ @Override
+ public void setDarkIntensity(float darkIntensity) {
+ mKeyButtonView.setDarkIntensity(darkIntensity);
+ }
+
+ @Override
+ public void setCanShowRotationButton(boolean canShow) {
+ mCanShow = canShow;
+ if (!mCanShow) {
+ hide();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index c9ba76c..337c6b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -188,7 +188,7 @@
@Override
public void onQuickStepStarted() {
// Use navbar dragging as a signal to hide the rotate button
- mNavigationBarView.getRotateSuggestionButton().setRotateSuggestionButtonState(false);
+ mNavigationBarView.getRotationButtonController().setRotateSuggestionButtonState(false);
// Hide the notifications panel when quick step starts
mStatusBar.collapsePanel(true /* animate */);
@@ -333,16 +333,16 @@
// Currently there is no accelerometer sensor on non-default display.
if (mIsOnDefaultDisplay) {
- final RotationContextButton rotationButton =
- mNavigationBarView.getRotateSuggestionButton();
- rotationButton.setListener(mRotationButtonListener);
- rotationButton.addRotationCallback(mRotationWatcher);
+ mNavigationBarView.getRotateSuggestionButton().setListener(mRotationButtonListener);
+
+ final RotationButtonController rotationButtonController =
+ mNavigationBarView.getRotationButtonController();
+ rotationButtonController.addRotationCallback(mRotationWatcher);
// Reset user rotation pref to match that of the WindowManager if starting in locked
// mode. This will automatically happen when switching from auto-rotate to locked mode.
- if (display != null && rotationButton.isRotationLocked()) {
- final int winRotation = display.getRotation();
- rotationButton.setRotationLockedAtAngle(winRotation);
+ if (display != null && rotationButtonController.isRotationLocked()) {
+ rotationButtonController.setRotationLockedAtAngle(display.getRotation());
}
} else {
mDisabledFlags2 |= StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS;
@@ -458,34 +458,34 @@
if (DEBUG_WINDOW_STATE) Log.d(TAG, "Navigation bar " + windowStateToString(state));
updateSystemUiStateFlags(-1);
- mNavigationBarView.getRotateSuggestionButton()
- .onNavigationBarWindowVisibilityChange(isNavBarWindowVisible());
+ mNavigationBarView.getRotationButtonController().onNavigationBarWindowVisibilityChange(
+ isNavBarWindowVisible());
}
}
@Override
public void onRotationProposal(final int rotation, boolean isValid) {
final int winRotation = mNavigationBarView.getDisplay().getRotation();
- final boolean rotateSuggestionsDisabled = RotationContextButton
+ final boolean rotateSuggestionsDisabled = RotationButtonController
.hasDisable2RotateSuggestionFlag(mDisabledFlags2);
+ final RotationButtonController rotationButtonController =
+ mNavigationBarView.getRotationButtonController();
+ final RotationButton rotationButton = rotationButtonController.getRotationButton();
+
if (RotationContextButton.DEBUG_ROTATION) {
Log.v(TAG, "onRotationProposal proposedRotation=" + Surface.rotationToString(rotation)
+ ", winRotation=" + Surface.rotationToString(winRotation)
+ ", isValid=" + isValid + ", mNavBarWindowState="
+ StatusBarManager.windowStateToString(mNavigationBarWindowState)
+ ", rotateSuggestionsDisabled=" + rotateSuggestionsDisabled
- + ", isRotateButtonVisible=" + (mNavigationBarView == null ? "null" :
- mNavigationBarView.getRotateSuggestionButton().isVisible()));
+ + ", isRotateButtonVisible=" + (mNavigationBarView == null ? "null"
+ : rotationButton.isVisible()));
}
// Respect the disabled flag, no need for action as flag change callback will handle hiding
if (rotateSuggestionsDisabled) return;
- View rotationButton = mNavigationBarView.getRotateSuggestionButton().getCurrentView();
- if (rotationButton != null && rotationButton.isAttachedToWindow()) {
- mNavigationBarView.getRotateSuggestionButton()
- .onRotationProposal(rotation, winRotation, isValid);
- }
+ rotationButtonController.onRotationProposal(rotation, winRotation, isValid);
}
/** Restores the System UI flags saved state to {@link NavigationBarFragment}. */
@@ -593,7 +593,7 @@
private void setDisabled2Flags(int state2) {
// Method only called on change of disable2 flags
if (mNavigationBarView != null) {
- mNavigationBarView.getRotateSuggestionButton().onDisable2FlagChanged(state2);
+ mNavigationBarView.getRotationButtonController().onDisable2FlagChanged(state2);
}
}
@@ -862,8 +862,8 @@
boolean[] feedbackEnabled = new boolean[1];
int a11yFlags = getA11yButtonState(feedbackEnabled);
- mNavigationBarView.getRotateSuggestionButton()
- .setAccessibilityFeedbackEnabled(feedbackEnabled[0]);
+ mNavigationBarView.getRotationButtonController().setAccessibilityFeedbackEnabled(
+ feedbackEnabled[0]);
boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index a12ae96..662c744 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -63,7 +63,6 @@
public static final String RIGHT = "right";
public static final String CONTEXTUAL = "contextual";
public static final String IME_SWITCHER = "ime_switcher";
- public static final String START_CONTEXTUAL = "start_contextual";
public static final String GRAVITY_SEPARATOR = ";";
public static final String BUTTON_SEPARATOR = ",";
@@ -395,8 +394,6 @@
v = inflater.inflate(R.layout.home_handle, parent, false);
} else if (IME_SWITCHER.equals(button)) {
v = inflater.inflate(R.layout.ime_switcher, parent, false);
- } else if (START_CONTEXTUAL.equals(button)) {
- v = inflater.inflate(R.layout.start_contextual, parent, false);
} else if (button.startsWith(KEY)) {
String uri = extractImage(button);
int code = extractKeycode(button);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index 3b3336b..8a28c6f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -173,6 +173,7 @@
for (int i = buttonDispatchers.size() - 1; i >= 0; i--) {
buttonDispatchers.valueAt(i).setDarkIntensity(darkIntensity);
}
+ mView.getRotationButtonController().setDarkIntensity(darkIntensity);
if (mAutoDim) {
applyLightsOut(false, true);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 5f61975..7682e8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -22,6 +22,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
+import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
import android.animation.LayoutTransition;
@@ -130,7 +131,6 @@
private boolean mImeVisible;
private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>();
- private final ContextualButtonGroup mStartContextualButtonGroup;
private final ContextualButtonGroup mContextualButtonGroup;
private Configuration mConfiguration;
private Configuration mTmpLastConfiguration;
@@ -138,6 +138,8 @@
private NavigationBarInflaterView mNavigationInflaterView;
private RecentsOnboarding mRecentsOnboarding;
private NotificationPanelView mPanelView;
+ private FloatingRotationButton mFloatingRotationButton;
+ private RotationButtonController mRotationButtonController;
private NavBarTintController mTintController;
@@ -232,24 +234,12 @@
private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = info -> {
// When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully
// gestural mode, the entire nav bar should be touchable.
- if (!QuickStepContract.isGesturalMode(mNavBarMode) || mImeVisible) {
+ if (!isGesturalMode(mNavBarMode) || mImeVisible) {
info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
return;
}
info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- RotationContextButton rotationContextButton = getRotateSuggestionButton();
- // If the rotate suggestion button is not visible in fully gestural mode, the entire nav bar
- // is not touchable so that the app underneath can be clicked through.
- if (rotationContextButton.getVisibility() != VISIBLE) {
- info.touchableRegion.setEmpty();
- } else {
- // Set the rotate suggestion button area to be touchable.
- rotationContextButton.getCurrentView().getLocationInWindow(mTmpPosition);
- Rect rect = new Rect(mTmpPosition[0], mTmpPosition[1],
- mTmpPosition[0] + mRotationButtonBounds.width(),
- mTmpPosition[1] + mRotationButtonBounds.height());
- info.touchableRegion.union(rect);
- }
+ info.touchableRegion.setEmpty();
};
public NavigationBarView(Context context, AttributeSet attrs) {
@@ -258,15 +248,14 @@
mIsVertical = false;
mLongClickableAccessibilityButton = false;
mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
- boolean isGesturalMode = QuickStepContract.isGesturalMode(mNavBarMode);
+ boolean isGesturalMode = isGesturalMode(mNavBarMode);
// Set up the context group of buttons
mContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container);
final ContextualButton imeSwitcherButton = new ContextualButton(R.id.ime_switcher,
R.drawable.ic_ime_switcher_default);
final RotationContextButton rotateSuggestionButton = new RotationContextButton(
- R.id.rotate_suggestion, R.drawable.ic_sysbar_rotate_button, getContext(),
- R.style.RotateButtonCCWStart90);
+ R.id.rotate_suggestion, R.drawable.ic_sysbar_rotate_button);
final ContextualButton accessibilityButton =
new ContextualButton(R.id.accessibility_button,
R.drawable.ic_sysbar_accessibility_button);
@@ -278,13 +267,12 @@
mOverviewProxyService = Dependency.get(OverviewProxyService.class);
mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);
+ mFloatingRotationButton = new FloatingRotationButton(context);
+ mRotationButtonController = new RotationButtonController(context,
+ R.style.RotateButtonCCWStart90,
+ isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton);
final ContextualButton backButton = new ContextualButton(R.id.back, 0);
- mStartContextualButtonGroup = new ContextualButtonGroup(R.id.start_menu_container);
- if (isGesturalMode) {
- mStartContextualButtonGroup.addButton(rotateSuggestionButton);
- }
- mStartContextualButtonGroup.addButton(backButton);
mConfiguration = new Configuration();
mTmpLastConfiguration = new Configuration();
@@ -301,7 +289,6 @@
mButtonDispatchers.put(R.id.accessibility_button, accessibilityButton);
mButtonDispatchers.put(R.id.rotate_suggestion, rotateSuggestionButton);
mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup);
- mButtonDispatchers.put(R.id.start_menu_container, mStartContextualButtonGroup);
mDeadZone = new DeadZone(this);
mEdgeBackGestureHandler = new EdgeBackGestureHandler(context, mOverviewProxyService);
@@ -390,6 +377,14 @@
return mCurrentView;
}
+ public RotationButtonController getRotationButtonController() {
+ return mRotationButtonController;
+ }
+
+ public FloatingRotationButton getFloatingRotationButton() {
+ return mFloatingRotationButton;
+ }
+
public ButtonDispatcher getRecentsButton() {
return mButtonDispatchers.get(R.id.recent_apps);
}
@@ -414,10 +409,6 @@
return (RotationContextButton) mButtonDispatchers.get(R.id.rotate_suggestion);
}
- public ContextualButtonGroup getStartContextualButtonGroup() {
- return mStartContextualButtonGroup;
- }
-
public ButtonDispatcher getHomeHandle() {
return mButtonDispatchers.get(R.id.home_handle);
}
@@ -454,7 +445,6 @@
if (densityChange || dirChange) {
mRecentIcon = getDrawable(R.drawable.ic_sysbar_recent);
mContextualButtonGroup.updateIcons();
- mStartContextualButtonGroup.updateIcons();
}
if (orientationChange || densityChange || dirChange) {
mBackIcon = getBackDrawable();
@@ -490,7 +480,7 @@
return;
}
- if (QuickStepContract.isGesturalMode(mNavBarMode)) {
+ if (isGesturalMode(mNavBarMode)) {
drawable.setRotation(degrees);
return;
}
@@ -561,6 +551,7 @@
mTransitionListener.onBackAltCleared();
}
mImeVisible = visible;
+ mRotationButtonController.getRotationButton().setCanShowRotationButton(!mImeVisible);
}
public void setDisabledFlags(int disabledFlags) {
@@ -603,7 +594,7 @@
mBarTransitions.reapplyDarkIntensity();
- boolean disableHome = QuickStepContract.isGesturalMode(mNavBarMode)
+ boolean disableHome = isGesturalMode(mNavBarMode)
|| ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
// TODO(b/113914868): investigation log for disappearing home button
@@ -613,7 +604,7 @@
// Always disable recents when alternate car mode UI is active and for secondary displays.
boolean disableRecent = isRecentsButtonDisabled();
- boolean disableBack = !useAltBack && (QuickStepContract.isGesturalMode(mNavBarMode)
+ boolean disableBack = !useAltBack && (isGesturalMode(mNavBarMode)
|| ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0));
// When screen pinning, don't hide back and home when connected service or back and
@@ -641,7 +632,6 @@
}
getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
- mStartContextualButtonGroup.setButtonVisibility(R.id.back, !disableBack);
getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
}
@@ -782,7 +772,7 @@
// Color adaption is tied with showing home handle, only avaliable if visible
mTintController.onNavigationModeChanged(mNavBarMode);
- if (QuickStepContract.isGesturalMode(mNavBarMode)) {
+ if (isGesturalMode(mNavBarMode)) {
mTintController.start();
} else {
mTintController.stop();
@@ -939,7 +929,7 @@
"onMeasure: (%dx%d) old: (%dx%d)", w, h, getMeasuredWidth(), getMeasuredHeight()));
final boolean newVertical = w > 0 && h > w
- && !QuickStepContract.isGesturalMode(mNavBarMode);
+ && !isGesturalMode(mNavBarMode);
if (newVertical != mIsVertical) {
mIsVertical = newVertical;
if (DEBUG) {
@@ -950,7 +940,7 @@
notifyVerticalChangedListener(newVertical);
}
- if (QuickStepContract.isGesturalMode(mNavBarMode)) {
+ if (isGesturalMode(mNavBarMode)) {
// Update the nav bar background to match the height of the visible nav bar
int height = mIsVertical
? getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 9da75b6..7623dee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -1440,10 +1440,8 @@
} else if (statusBarState == StatusBarState.KEYGUARD
|| statusBarState == StatusBarState.SHADE_LOCKED) {
mKeyguardBottomArea.setVisibility(View.VISIBLE);
- mKeyguardBottomArea.setAlpha(1f);
} else {
mKeyguardBottomArea.setVisibility(View.GONE);
- mKeyguardBottomArea.setAlpha(1f);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButton.java
new file mode 100644
index 0000000..2580c0e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButton.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.view.View;
+
+import com.android.systemui.statusbar.policy.KeyButtonDrawable;
+
+/** Interface of a rotation button that interacts {@link RotationButtonController}. */
+interface RotationButton {
+ void setRotationButtonController(RotationButtonController rotationButtonController);
+ View getCurrentView();
+ boolean show();
+ boolean hide();
+ boolean isVisible();
+ void updateIcon();
+ void setOnClickListener(View.OnClickListener onClickListener);
+ void setOnHoverListener(View.OnHoverListener onHoverListener);
+ KeyButtonDrawable getImageDrawable();
+ void setDarkIntensity(float darkIntensity);
+ default void setCanShowRotationButton(boolean canShow) {}
+ default boolean acceptRotationProposal() {
+ return getCurrentView() != null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java
new file mode 100644
index 0000000..1e5406f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.annotation.StyleRes;
+import android.app.StatusBarManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.view.IRotationWatcher.Stub;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.View;
+import android.view.WindowManagerGlobal;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.statusbar.policy.KeyButtonDrawable;
+import com.android.systemui.statusbar.policy.RotationLockController;
+
+import java.util.Optional;
+import java.util.function.Consumer;
+
+/** Contains logic that deals with showing a rotate suggestion button with animation. */
+public class RotationButtonController {
+
+ private static final int BUTTON_FADE_IN_OUT_DURATION_MS = 100;
+ private static final int NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS = 20000;
+
+ private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3;
+
+ private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+ private final ViewRippler mViewRippler = new ViewRippler();
+
+ private @StyleRes int mStyleRes;
+ private int mLastRotationSuggestion;
+ private boolean mPendingRotationSuggestion;
+ private boolean mHoveringRotationSuggestion;
+ private RotationLockController mRotationLockController;
+ private TaskStackListenerImpl mTaskStackListener;
+ private Consumer<Integer> mRotWatcherListener;
+ private boolean mIsNavigationBarShowing;
+
+ private final Runnable mRemoveRotationProposal =
+ () -> setRotateSuggestionButtonState(false /* visible */);
+ private final Runnable mCancelPendingRotationProposal =
+ () -> mPendingRotationSuggestion = false;
+ private Animator mRotateHideAnimator;
+ private boolean mAccessibilityFeedbackEnabled;
+
+ private final Context mContext;
+ private final RotationButton mRotationButton;
+
+ private final Stub mRotationWatcher = new Stub() {
+ @Override
+ public void onRotationChanged(final int rotation) throws RemoteException {
+ if (mRotationButton.getCurrentView() == null) {
+ return;
+ }
+
+ // We need this to be scheduled as early as possible to beat the redrawing of
+ // window in response to the orientation change.
+ Handler h = mRotationButton.getCurrentView().getHandler();
+ Message msg = Message.obtain(h, () -> {
+ // If the screen rotation changes while locked, potentially update lock to flow with
+ // new screen rotation and hide any showing suggestions.
+ if (mRotationLockController.isRotationLocked()) {
+ if (shouldOverrideUserLockPrefs(rotation)) {
+ setRotationLockedAtAngle(rotation);
+ }
+ setRotateSuggestionButtonState(false /* visible */, true /* forced */);
+ }
+
+ if (mRotWatcherListener != null) {
+ mRotWatcherListener.accept(rotation);
+ }
+ });
+ msg.setAsynchronous(true);
+ h.sendMessageAtFrontOfQueue(msg);
+ }
+ };
+
+ /**
+ * Determines if rotation suggestions disabled2 flag exists in flag
+ * @param disable2Flags see if rotation suggestion flag exists in this flag
+ * @return whether flag exists
+ */
+ static boolean hasDisable2RotateSuggestionFlag(int disable2Flags) {
+ return (disable2Flags & StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS) != 0;
+ }
+
+ RotationButtonController(Context context, @StyleRes int style, RotationButton rotationButton) {
+ mContext = context;
+ mRotationButton = rotationButton;
+ mRotationButton.setRotationButtonController(this);
+
+ mStyleRes = style;
+ mIsNavigationBarShowing = true;
+ mRotationLockController = Dependency.get(RotationLockController.class);
+
+ // Register the task stack listener
+ mTaskStackListener = new TaskStackListenerImpl();
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
+ mRotationButton.setOnClickListener(this::onRotateSuggestionClick);
+ mRotationButton.setOnHoverListener(this::onRotateSuggestionHover);
+
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .watchRotation(mRotationWatcher, mContext.getDisplay().getDisplayId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void cleanUp() {
+ // Unregister the task stack listener
+ ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
+
+ try {
+ WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(mRotationWatcher);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void addRotationCallback(Consumer<Integer> watcher) {
+ mRotWatcherListener = watcher;
+ }
+
+ void setAccessibilityFeedbackEnabled(boolean flag) {
+ mAccessibilityFeedbackEnabled = flag;
+ }
+
+ void setRotationLockedAtAngle(int rotationSuggestion) {
+ mRotationLockController.setRotationLockedAtAngle(true /* locked */, rotationSuggestion);
+ }
+
+ public boolean isRotationLocked() {
+ return mRotationLockController.isRotationLocked();
+ }
+
+ void setRotateSuggestionButtonState(boolean visible) {
+ setRotateSuggestionButtonState(visible, false /* force */);
+ }
+
+ void setRotateSuggestionButtonState(final boolean visible, final boolean force) {
+ // At any point the the button can become invisible because an a11y service became active.
+ // Similarly, a call to make the button visible may be rejected because an a11y service is
+ // active. Must account for this.
+ // Rerun a show animation to indicate change but don't rerun a hide animation
+ if (!visible && !mRotationButton.isVisible()) return;
+
+ final View view = mRotationButton.getCurrentView();
+ if (view == null) return;
+
+ final KeyButtonDrawable currentDrawable = mRotationButton.getImageDrawable();
+ if (currentDrawable == null) return;
+
+ // Clear any pending suggestion flag as it has either been nullified or is being shown
+ mPendingRotationSuggestion = false;
+ view.removeCallbacks(mCancelPendingRotationProposal);
+
+ // Handle the visibility change and animation
+ if (visible) { // Appear and change (cannot force)
+ // Stop and clear any currently running hide animations
+ if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
+ mRotateHideAnimator.cancel();
+ }
+ mRotateHideAnimator = null;
+
+ // Reset the alpha if any has changed due to hide animation
+ view.setAlpha(1f);
+
+ // Run the rotate icon's animation if it has one
+ if (currentDrawable.canAnimate()) {
+ currentDrawable.resetAnimation();
+ currentDrawable.startAnimation();
+ }
+
+ if (!isRotateSuggestionIntroduced()) mViewRippler.start(view);
+
+ // Set visibility unless a11y service is active.
+ mRotationButton.show();
+ } else { // Hide
+ mViewRippler.stop(); // Prevent any pending ripples, force hide or not
+
+ if (force) {
+ // If a hide animator is running stop it and make invisible
+ if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
+ mRotateHideAnimator.pause();
+ }
+ mRotationButton.hide();
+ return;
+ }
+
+ // Don't start any new hide animations if one is running
+ if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
+
+ ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", 0f);
+ fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS);
+ fadeOut.setInterpolator(Interpolators.LINEAR);
+ fadeOut.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mRotationButton.hide();
+ }
+ });
+
+ mRotateHideAnimator = fadeOut;
+ fadeOut.start();
+ }
+ }
+
+ void setDarkIntensity(float darkIntensity) {
+ mRotationButton.setDarkIntensity(darkIntensity);
+ }
+
+ void onRotationProposal(int rotation, int windowRotation, boolean isValid) {
+ if (!mRotationButton.acceptRotationProposal()) {
+ return;
+ }
+
+ // This method will be called on rotation suggestion changes even if the proposed rotation
+ // is not valid for the top app. Use invalid rotation choices as a signal to remove the
+ // rotate button if shown.
+ if (!isValid) {
+ setRotateSuggestionButtonState(false /* visible */);
+ return;
+ }
+
+ final View currentView = mRotationButton.getCurrentView();
+
+ // If window rotation matches suggested rotation, remove any current suggestions
+ if (rotation == windowRotation) {
+ if (currentView != null) {
+ currentView.removeCallbacks(mRemoveRotationProposal);
+ }
+ setRotateSuggestionButtonState(false /* visible */);
+ return;
+ }
+
+ // Prepare to show the navbar icon by updating the icon style to change anim params
+ mLastRotationSuggestion = rotation; // Remember rotation for click
+ final boolean rotationCCW = isRotationAnimationCCW(windowRotation, rotation);
+ int style;
+ if (windowRotation == Surface.ROTATION_0 || windowRotation == Surface.ROTATION_180) {
+ style = rotationCCW ? R.style.RotateButtonCCWStart90 : R.style.RotateButtonCWStart90;
+ } else { // 90 or 270
+ style = rotationCCW ? R.style.RotateButtonCCWStart0 : R.style.RotateButtonCWStart0;
+ }
+ mStyleRes = style;
+ mRotationButton.updateIcon();
+
+ if (mIsNavigationBarShowing) {
+ // The navbar is visible so show the icon right away
+ showAndLogRotationSuggestion();
+ } else {
+ // If the navbar isn't shown, flag the rotate icon to be shown should the navbar become
+ // visible given some time limit.
+ mPendingRotationSuggestion = true;
+ if (currentView != null) {
+ currentView.removeCallbacks(mCancelPendingRotationProposal);
+ currentView.postDelayed(mCancelPendingRotationProposal,
+ NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS);
+ }
+ }
+ }
+
+ void onDisable2FlagChanged(int state2) {
+ final boolean rotateSuggestionsDisabled = hasDisable2RotateSuggestionFlag(state2);
+ if (rotateSuggestionsDisabled) onRotationSuggestionsDisabled();
+ }
+
+ void onNavigationBarWindowVisibilityChange(boolean showing) {
+ if (mIsNavigationBarShowing != showing) {
+ mIsNavigationBarShowing = showing;
+
+ // If the navbar is visible, show the rotate button if there's a pending suggestion
+ if (showing && mPendingRotationSuggestion) {
+ showAndLogRotationSuggestion();
+ }
+ }
+ }
+
+ @StyleRes int getStyleRes() {
+ return mStyleRes;
+ }
+
+ RotationButton getRotationButton() {
+ return mRotationButton;
+ }
+
+ private void onRotateSuggestionClick(View v) {
+ mMetricsLogger.action(MetricsEvent.ACTION_ROTATION_SUGGESTION_ACCEPTED);
+ incrementNumAcceptedRotationSuggestionsIfNeeded();
+ setRotationLockedAtAngle(mLastRotationSuggestion);
+ }
+
+ private boolean onRotateSuggestionHover(View v, MotionEvent event) {
+ final int action = event.getActionMasked();
+ mHoveringRotationSuggestion = (action == MotionEvent.ACTION_HOVER_ENTER)
+ || (action == MotionEvent.ACTION_HOVER_MOVE);
+ rescheduleRotationTimeout(true /* reasonHover */);
+ return false; // Must return false so a11y hover events are dispatched correctly.
+ }
+
+ private void onRotationSuggestionsDisabled() {
+ // Immediately hide the rotate button and clear any planned removal
+ setRotateSuggestionButtonState(false /* visible */, true /* force */);
+ if (mRotationButton.getCurrentView() != null) {
+ mRotationButton.getCurrentView().removeCallbacks(mRemoveRotationProposal);
+ }
+ }
+
+ private void showAndLogRotationSuggestion() {
+ setRotateSuggestionButtonState(true /* visible */);
+ rescheduleRotationTimeout(false /* reasonHover */);
+ mMetricsLogger.visible(MetricsEvent.ROTATION_SUGGESTION_SHOWN);
+ }
+
+ private boolean shouldOverrideUserLockPrefs(final int rotation) {
+ // Only override user prefs when returning to the natural rotation (normally portrait).
+ // Don't let apps that force landscape or 180 alter user lock.
+ return rotation == NATURAL_ROTATION;
+ }
+
+ private boolean isRotationAnimationCCW(int from, int to) {
+ // All 180deg WM rotation animations are CCW, match that
+ if (from == Surface.ROTATION_0 && to == Surface.ROTATION_90) return false;
+ if (from == Surface.ROTATION_0 && to == Surface.ROTATION_180) return true; //180d so CCW
+ if (from == Surface.ROTATION_0 && to == Surface.ROTATION_270) return true;
+ if (from == Surface.ROTATION_90 && to == Surface.ROTATION_0) return true;
+ if (from == Surface.ROTATION_90 && to == Surface.ROTATION_180) return false;
+ if (from == Surface.ROTATION_90 && to == Surface.ROTATION_270) return true; //180d so CCW
+ if (from == Surface.ROTATION_180 && to == Surface.ROTATION_0) return true; //180d so CCW
+ if (from == Surface.ROTATION_180 && to == Surface.ROTATION_90) return true;
+ if (from == Surface.ROTATION_180 && to == Surface.ROTATION_270) return false;
+ if (from == Surface.ROTATION_270 && to == Surface.ROTATION_0) return false;
+ if (from == Surface.ROTATION_270 && to == Surface.ROTATION_90) return true; //180d so CCW
+ if (from == Surface.ROTATION_270 && to == Surface.ROTATION_180) return true;
+ return false; // Default
+ }
+
+ private void rescheduleRotationTimeout(final boolean reasonHover) {
+ if (mRotationButton.getCurrentView() == null) {
+ return;
+ }
+
+ // May be called due to a new rotation proposal or a change in hover state
+ if (reasonHover) {
+ // Don't reschedule if a hide animator is running
+ if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
+ // Don't reschedule if not visible
+ if (!mRotationButton.isVisible()) return;
+ }
+
+ // Stop any pending removal
+ mRotationButton.getCurrentView().removeCallbacks(mRemoveRotationProposal);
+ // Schedule timeout
+ mRotationButton.getCurrentView().postDelayed(mRemoveRotationProposal,
+ computeRotationProposalTimeout());
+ }
+
+ private int computeRotationProposalTimeout() {
+ if (mAccessibilityFeedbackEnabled) return 10000;
+ if (mHoveringRotationSuggestion) return 8000;
+ return 5000;
+ }
+
+ private boolean isRotateSuggestionIntroduced() {
+ ContentResolver cr = mContext.getContentResolver();
+ return Settings.Secure.getInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0)
+ >= NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION;
+ }
+
+ private void incrementNumAcceptedRotationSuggestionsIfNeeded() {
+ // Get the number of accepted suggestions
+ ContentResolver cr = mContext.getContentResolver();
+ final int numSuggestions = Settings.Secure.getInt(cr,
+ Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0);
+
+ // Increment the number of accepted suggestions only if it would change intro mode
+ if (numSuggestions < NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION) {
+ Settings.Secure.putInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED,
+ numSuggestions + 1);
+ }
+ }
+
+ private class TaskStackListenerImpl extends TaskStackChangeListener {
+ // Invalidate any rotation suggestion on task change or activity orientation change
+ // Note: all callbacks happen on main thread
+
+ @Override
+ public void onTaskStackChanged() {
+ setRotateSuggestionButtonState(false /* visible */);
+ }
+
+ @Override
+ public void onTaskRemoved(int taskId) {
+ setRotateSuggestionButtonState(false /* visible */);
+ }
+
+ @Override
+ public void onTaskMovedToFront(int taskId) {
+ setRotateSuggestionButtonState(false /* visible */);
+ }
+
+ @Override
+ public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
+ // Only hide the icon if the top task changes its requestedOrientation
+ // Launcher can alter its requestedOrientation while it's not on top, don't hide on this
+ Optional.ofNullable(ActivityManagerWrapper.getInstance())
+ .map(ActivityManagerWrapper::getRunningTask)
+ .ifPresent(a -> {
+ if (a.id == taskId) setRotateSuggestionButtonState(false /* visible */);
+ });
+ }
+ }
+
+ private class ViewRippler {
+ private static final int RIPPLE_OFFSET_MS = 50;
+ private static final int RIPPLE_INTERVAL_MS = 2000;
+ private View mRoot;
+
+ public void start(View root) {
+ stop(); // Stop any pending ripple animations
+
+ mRoot = root;
+
+ // Schedule pending ripples, offset the 1st to avoid problems with visibility change
+ mRoot.postOnAnimationDelayed(mRipple, RIPPLE_OFFSET_MS);
+ mRoot.postOnAnimationDelayed(mRipple, RIPPLE_INTERVAL_MS);
+ mRoot.postOnAnimationDelayed(mRipple, 2 * RIPPLE_INTERVAL_MS);
+ mRoot.postOnAnimationDelayed(mRipple, 3 * RIPPLE_INTERVAL_MS);
+ mRoot.postOnAnimationDelayed(mRipple, 4 * RIPPLE_INTERVAL_MS);
+ }
+
+ public void stop() {
+ if (mRoot != null) mRoot.removeCallbacks(mRipple);
+ }
+
+ private final Runnable mRipple = new Runnable() {
+ @Override
+ public void run() { // Cause the ripple to fire via false presses
+ if (!mRoot.isAttachedToWindow()) return;
+ mRoot.setPressed(true /* pressed */);
+ mRoot.setPressed(false /* pressed */);
+ }
+ };
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
index 7203e57..b117dec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
@@ -18,281 +18,30 @@
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
-import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
import android.annotation.DrawableRes;
import android.annotation.IdRes;
-import android.annotation.NonNull;
-import android.annotation.StyleRes;
-import android.app.StatusBarManager;
-import android.content.ContentResolver;
import android.content.Context;
-import android.os.Handler;
-import android.os.Message;
-import android.os.RemoteException;
-import android.provider.Settings;
import android.view.ContextThemeWrapper;
-import android.view.IRotationWatcher.Stub;
-import android.view.MotionEvent;
-import android.view.Surface;
import android.view.View;
-import android.view.WindowManagerGlobal;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.Dependency;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.statusbar.policy.KeyButtonDrawable;
-import com.android.systemui.statusbar.policy.RotationLockController;
-
-import java.util.Optional;
-import java.util.function.Consumer;
/** Containing logic for the rotation button in nav bar. */
public class RotationContextButton extends ContextualButton implements
- NavigationModeController.ModeChangedListener {
+ NavigationModeController.ModeChangedListener, RotationButton {
public static final boolean DEBUG_ROTATION = false;
- private static final int BUTTON_FADE_IN_OUT_DURATION_MS = 100;
- private static final int NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS = 20000;
-
- private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3;
-
- private @StyleRes int mStyleRes;
-
- private int mLastRotationSuggestion;
- private boolean mPendingRotationSuggestion;
- private boolean mHoveringRotationSuggestion;
- private RotationLockController mRotationLockController;
- private TaskStackListenerImpl mTaskStackListener;
- private Consumer<Integer> mRotWatcherListener;
- private boolean mIsNavigationBarShowing;
-
- private final Runnable mRemoveRotationProposal =
- () -> setRotateSuggestionButtonState(false /* visible */);
- private final Runnable mCancelPendingRotationProposal =
- () -> mPendingRotationSuggestion = false;
- private Animator mRotateHideAnimator;
- private boolean mAccessibilityFeedbackEnabled;
private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
+ private RotationButtonController mRotationButtonController;
- private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
- private final ViewRippler mViewRippler = new ViewRippler();
-
- private final Stub mRotationWatcher = new Stub() {
- @Override
- public void onRotationChanged(final int rotation) throws RemoteException {
- if (getCurrentView() == null) {
- return;
- }
-
- // We need this to be scheduled as early as possible to beat the redrawing of
- // window in response to the orientation change.
- Handler h = getCurrentView().getHandler();
- Message msg = Message.obtain(h, () -> {
- // If the screen rotation changes while locked, potentially update lock to flow with
- // new screen rotation and hide any showing suggestions.
- if (mRotationLockController.isRotationLocked()) {
- if (shouldOverrideUserLockPrefs(rotation)) {
- setRotationLockedAtAngle(rotation);
- }
- setRotateSuggestionButtonState(false /* visible */, true /* forced */);
- }
-
- if (mRotWatcherListener != null) {
- mRotWatcherListener.accept(rotation);
- }
- });
- msg.setAsynchronous(true);
- h.sendMessageAtFrontOfQueue(msg);
- }
- };
-
- /**
- * Determines if rotation suggestions disabled2 flag exists in flag
- * @param disable2Flags see if rotation suggestion flag exists in this flag
- * @return whether flag exists
- */
- static boolean hasDisable2RotateSuggestionFlag(int disable2Flags) {
- return (disable2Flags & StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS) != 0;
- }
-
- public RotationContextButton(@IdRes int buttonResId, @DrawableRes int iconResId,
- @NonNull Context context, @StyleRes int style) {
+ public RotationContextButton(@IdRes int buttonResId, @DrawableRes int iconResId) {
super(buttonResId, iconResId);
-
- mStyleRes = style;
- mIsNavigationBarShowing = true;
- mRotationLockController = Dependency.get(RotationLockController.class);
-
- // Register the task stack listener
- mTaskStackListener = new TaskStackListenerImpl();
- ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
- setOnClickListener(this::onRotateSuggestionClick);
- setOnHoverListener(this::onRotateSuggestionHover);
-
- try {
- WindowManagerGlobal.getWindowManagerService()
- .watchRotation(mRotationWatcher, context.getDisplay().getDisplayId());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
}
- public void addRotationCallback(Consumer<Integer> watcher) {
- mRotWatcherListener = watcher;
- }
-
- public void setRotateSuggestionButtonState(boolean visible) {
- setRotateSuggestionButtonState(visible, false /* force */);
- }
-
- public void setRotateSuggestionButtonState(final boolean visible, final boolean force) {
- // At any point the the button can become invisible because an a11y service became active.
- // Similarly, a call to make the button visible may be rejected because an a11y service is
- // active. Must account for this.
- // Rerun a show animation to indicate change but don't rerun a hide animation
- if (!visible && !isVisible()) return;
-
- final View view = getCurrentView();
- if (view == null) return;
-
- final KeyButtonDrawable currentDrawable = getImageDrawable();
- if (currentDrawable == null) return;
-
- // Clear any pending suggestion flag as it has either been nullified or is being shown
- mPendingRotationSuggestion = false;
- view.removeCallbacks(mCancelPendingRotationProposal);
-
- // Handle the visibility change and animation
- if (visible) { // Appear and change (cannot force)
- // Stop and clear any currently running hide animations
- if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
- mRotateHideAnimator.cancel();
- }
- mRotateHideAnimator = null;
-
- // Reset the alpha if any has changed due to hide animation
- view.setAlpha(1f);
-
- // Run the rotate icon's animation if it has one
- if (currentDrawable.canAnimate()) {
- currentDrawable.resetAnimation();
- currentDrawable.startAnimation();
- }
-
- if (!isRotateSuggestionIntroduced()) mViewRippler.start(view);
-
- // Set visibility unless a11y service is active.
- show();
- } else { // Hide
- mViewRippler.stop(); // Prevent any pending ripples, force hide or not
-
- if (force) {
- // If a hide animator is running stop it and make invisible
- if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
- mRotateHideAnimator.pause();
- }
- hide();
- return;
- }
-
- // Don't start any new hide animations if one is running
- if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
-
- ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", 0f);
- fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS);
- fadeOut.setInterpolator(Interpolators.LINEAR);
- fadeOut.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- hide();
- }
- });
-
- mRotateHideAnimator = fadeOut;
- fadeOut.start();
- }
- }
-
- public void setAccessibilityFeedbackEnabled(boolean flag) {
- mAccessibilityFeedbackEnabled = flag;
- }
-
- public void setRotationLockedAtAngle(int rotationSuggestion) {
- mRotationLockController.setRotationLockedAtAngle(true /* locked */, rotationSuggestion);
- }
-
- public boolean isRotationLocked() {
- return mRotationLockController.isRotationLocked();
- }
-
- public void onRotationProposal(int rotation, int windowRotation, boolean isValid) {
- // This method will be called on rotation suggestion changes even if the proposed rotation
- // is not valid for the top app. Use invalid rotation choices as a signal to remove the
- // rotate button if shown.
- if (!isValid) {
- setRotateSuggestionButtonState(false /* visible */);
- return;
- }
-
- // If window rotation matches suggested rotation, remove any current suggestions
- if (rotation == windowRotation) {
- if (getCurrentView() != null) {
- getCurrentView().removeCallbacks(mRemoveRotationProposal);
- }
- setRotateSuggestionButtonState(false /* visible */);
- return;
- }
-
- // Prepare to show the navbar icon by updating the icon style to change anim params
- mLastRotationSuggestion = rotation; // Remember rotation for click
- final boolean rotationCCW = isRotationAnimationCCW(windowRotation, rotation);
- int style;
- if (windowRotation == Surface.ROTATION_0 || windowRotation == Surface.ROTATION_180) {
- style = rotationCCW ? R.style.RotateButtonCCWStart90 : R.style.RotateButtonCWStart90;
- } else { // 90 or 270
- style = rotationCCW ? R.style.RotateButtonCCWStart0 : R.style.RotateButtonCWStart0;
- }
- mStyleRes = style;
- updateIcon();
-
- if (mIsNavigationBarShowing) {
- // The navbar is visible so show the icon right away
- showAndLogRotationSuggestion();
- } else {
- // If the navbar isn't shown, flag the rotate icon to be shown should the navbar become
- // visible given some time limit.
- mPendingRotationSuggestion = true;
- if (getCurrentView() != null) {
- getCurrentView().removeCallbacks(mCancelPendingRotationProposal);
- getCurrentView().postDelayed(mCancelPendingRotationProposal,
- NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS);
- }
- }
- }
-
- public void onDisable2FlagChanged(int state2) {
- final boolean rotateSuggestionsDisabled = hasDisable2RotateSuggestionFlag(state2);
- if (rotateSuggestionsDisabled) onRotationSuggestionsDisabled();
- }
-
- public void onNavigationBarWindowVisibilityChange(boolean showing) {
- if (mIsNavigationBarShowing != showing) {
- mIsNavigationBarShowing = showing;
-
- // If the navbar is visible, show the rotate button if there's a pending suggestion
- if (showing && mPendingRotationSuggestion) {
- showAndLogRotationSuggestion();
- }
- }
+ @Override
+ public void setRotationButtonController(RotationButtonController rotationButtonController) {
+ mRotationButtonController = rotationButtonController;
}
@Override
@@ -309,115 +58,16 @@
@Override
protected KeyButtonDrawable getNewDrawable() {
- Context context = new ContextThemeWrapper(getContext().getApplicationContext(), mStyleRes);
+ Context context = new ContextThemeWrapper(getContext().getApplicationContext(),
+ mRotationButtonController.getStyleRes());
return KeyButtonDrawable.create(context, mIconResId, false /* shadow */,
QuickStepContract.isGesturalMode(mNavBarMode));
}
@Override
public void onDestroy() {
- // Unregister the task stack listener
- ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
-
- try {
- WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(mRotationWatcher);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- private void onRotateSuggestionClick(View v) {
- mMetricsLogger.action(MetricsEvent.ACTION_ROTATION_SUGGESTION_ACCEPTED);
- incrementNumAcceptedRotationSuggestionsIfNeeded();
- setRotationLockedAtAngle(mLastRotationSuggestion);
- }
-
- private boolean onRotateSuggestionHover(View v, MotionEvent event) {
- final int action = event.getActionMasked();
- mHoveringRotationSuggestion = (action == MotionEvent.ACTION_HOVER_ENTER)
- || (action == MotionEvent.ACTION_HOVER_MOVE);
- rescheduleRotationTimeout(true /* reasonHover */);
- return false; // Must return false so a11y hover events are dispatched correctly.
- }
-
- private void onRotationSuggestionsDisabled() {
- // Immediately hide the rotate button and clear any planned removal
- setRotateSuggestionButtonState(false /* visible */, true /* force */);
- if (getCurrentView() != null) {
- getCurrentView().removeCallbacks(mRemoveRotationProposal);
- }
- }
-
- private void showAndLogRotationSuggestion() {
- setRotateSuggestionButtonState(true /* visible */);
- rescheduleRotationTimeout(false /* reasonHover */);
- mMetricsLogger.visible(MetricsEvent.ROTATION_SUGGESTION_SHOWN);
- }
-
- private boolean shouldOverrideUserLockPrefs(final int rotation) {
- // Only override user prefs when returning to the natural rotation (normally portrait).
- // Don't let apps that force landscape or 180 alter user lock.
- return rotation == NATURAL_ROTATION;
- }
-
- private boolean isRotationAnimationCCW(int from, int to) {
- // All 180deg WM rotation animations are CCW, match that
- if (from == Surface.ROTATION_0 && to == Surface.ROTATION_90) return false;
- if (from == Surface.ROTATION_0 && to == Surface.ROTATION_180) return true; //180d so CCW
- if (from == Surface.ROTATION_0 && to == Surface.ROTATION_270) return true;
- if (from == Surface.ROTATION_90 && to == Surface.ROTATION_0) return true;
- if (from == Surface.ROTATION_90 && to == Surface.ROTATION_180) return false;
- if (from == Surface.ROTATION_90 && to == Surface.ROTATION_270) return true; //180d so CCW
- if (from == Surface.ROTATION_180 && to == Surface.ROTATION_0) return true; //180d so CCW
- if (from == Surface.ROTATION_180 && to == Surface.ROTATION_90) return true;
- if (from == Surface.ROTATION_180 && to == Surface.ROTATION_270) return false;
- if (from == Surface.ROTATION_270 && to == Surface.ROTATION_0) return false;
- if (from == Surface.ROTATION_270 && to == Surface.ROTATION_90) return true; //180d so CCW
- if (from == Surface.ROTATION_270 && to == Surface.ROTATION_180) return true;
- return false; // Default
- }
-
- private void rescheduleRotationTimeout(final boolean reasonHover) {
- if (getCurrentView() == null) {
- return;
- }
-
- // May be called due to a new rotation proposal or a change in hover state
- if (reasonHover) {
- // Don't reschedule if a hide animator is running
- if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
- // Don't reschedule if not visible
- if (!isVisible()) return;
- }
-
- // Stop any pending removal
- getCurrentView().removeCallbacks(mRemoveRotationProposal);
- // Schedule timeout
- getCurrentView().postDelayed(mRemoveRotationProposal, computeRotationProposalTimeout());
- }
-
- private int computeRotationProposalTimeout() {
- if (mAccessibilityFeedbackEnabled) return 10000;
- if (mHoveringRotationSuggestion) return 8000;
- return 5000;
- }
-
- private boolean isRotateSuggestionIntroduced() {
- ContentResolver cr = getContext().getContentResolver();
- return Settings.Secure.getInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0)
- >= NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION;
- }
-
- private void incrementNumAcceptedRotationSuggestionsIfNeeded() {
- // Get the number of accepted suggestions
- ContentResolver cr = getContext().getContentResolver();
- final int numSuggestions = Settings.Secure.getInt(cr,
- Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0);
-
- // Increment the number of accepted suggestions only if it would change intro mode
- if (numSuggestions < NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION) {
- Settings.Secure.putInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED,
- numSuggestions + 1);
+ if (mRotationButtonController != null) {
+ mRotationButtonController.cleanUp();
}
}
@@ -426,66 +76,9 @@
mNavBarMode = mode;
}
- private class TaskStackListenerImpl extends TaskStackChangeListener {
- // Invalidate any rotation suggestion on task change or activity orientation change
- // Note: all callbacks happen on main thread
-
- @Override
- public void onTaskStackChanged() {
- setRotateSuggestionButtonState(false /* visible */);
- }
-
- @Override
- public void onTaskRemoved(int taskId) {
- setRotateSuggestionButtonState(false /* visible */);
- }
-
- @Override
- public void onTaskMovedToFront(int taskId) {
- setRotateSuggestionButtonState(false /* visible */);
- }
-
- @Override
- public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
- // Only hide the icon if the top task changes its requestedOrientation
- // Launcher can alter its requestedOrientation while it's not on top, don't hide on this
- Optional.ofNullable(ActivityManagerWrapper.getInstance())
- .map(ActivityManagerWrapper::getRunningTask)
- .ifPresent(a -> {
- if (a.id == taskId) setRotateSuggestionButtonState(false /* visible */);
- });
- }
- }
-
- private class ViewRippler {
- private static final int RIPPLE_OFFSET_MS = 50;
- private static final int RIPPLE_INTERVAL_MS = 2000;
- private View mRoot;
-
- public void start(View root) {
- stop(); // Stop any pending ripple animations
-
- mRoot = root;
-
- // Schedule pending ripples, offset the 1st to avoid problems with visibility change
- mRoot.postOnAnimationDelayed(mRipple, RIPPLE_OFFSET_MS);
- mRoot.postOnAnimationDelayed(mRipple, RIPPLE_INTERVAL_MS);
- mRoot.postOnAnimationDelayed(mRipple, 2 * RIPPLE_INTERVAL_MS);
- mRoot.postOnAnimationDelayed(mRipple, 3 * RIPPLE_INTERVAL_MS);
- mRoot.postOnAnimationDelayed(mRipple, 4 * RIPPLE_INTERVAL_MS);
- }
-
- public void stop() {
- if (mRoot != null) mRoot.removeCallbacks(mRipple);
- }
-
- private final Runnable mRipple = new Runnable() {
- @Override
- public void run() { // Cause the ripple to fire via false presses
- if (!mRoot.isAttachedToWindow()) return;
- mRoot.setPressed(true /* pressed */);
- mRoot.setPressed(false /* pressed */);
- }
- };
+ @Override
+ public boolean acceptRotationProposal() {
+ View currentView = getCurrentView();
+ return currentView != null && currentView.isAttachedToWindow();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index a5bb92d..da1ef2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -71,6 +71,7 @@
public CastControllerImpl(Context context) {
mContext = context;
mMediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+ mMediaRouter.setRouterGroupId(MediaRouter.MIRRORING_GROUP_ID);
mProjectionManager = (MediaProjectionManager)
context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
mProjection = mProjectionManager.getActiveProjectionInfo();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index c08390f..2afe485 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -38,6 +38,7 @@
import com.android.internal.telephony.cdma.EriInfo;
import com.android.settingslib.Utils;
import com.android.settingslib.graph.SignalDrawable;
+import com.android.settingslib.net.SignalStrengthUtil;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
@@ -248,9 +249,8 @@
}
private void updateInflateSignalStrength() {
- mInflateSignalStrengths = SubscriptionManager.getResourcesForSubId(mContext,
- mSubscriptionInfo.getSubscriptionId())
- .getBoolean(R.bool.config_inflateSignalStrength);
+ mInflateSignalStrengths = SignalStrengthUtil.shouldInflateSignalStrength(mContext,
+ mSubscriptionInfo.getSubscriptionId());
}
private int getNumLevels() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index b8a14ef..b2972fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -1140,7 +1140,8 @@
res.getBoolean(com.android.internal.R.bool.config_alwaysUseCdmaRssi);
config.hspaDataDistinguishable =
res.getBoolean(R.bool.config_hspa_data_distinguishable);
- config.inflateSignalStrengths = res.getBoolean(R.bool.config_inflateSignalStrength);
+ config.inflateSignalStrengths = res.getBoolean(
+ com.android.internal.R.bool.config_inflateSignalStrength);
CarrierConfigManager configMgr = (CarrierConfigManager)
context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
diff --git a/packages/SystemUI/src/com/android/systemui/util/AutoMarqueeTextView.java b/packages/SystemUI/src/com/android/systemui/util/AutoMarqueeTextView.java
new file mode 100644
index 0000000..d9d410d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/AutoMarqueeTextView.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+/**
+ * TextView that changes its ellipsize value with its visibility.
+ *
+ * The View responds to changes in user-visibility to change its ellipsize from MARQUEE to END
+ * and back. Useful for TextView that need to marquee forever.
+ */
+public class AutoMarqueeTextView extends TextView {
+
+ private boolean mAggregatedVisible = false;
+
+ public AutoMarqueeTextView(Context context) {
+ super(context);
+ }
+
+ public AutoMarqueeTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AutoMarqueeTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public AutoMarqueeTextView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ setSelected(true);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ setSelected(false);
+ }
+
+ @Override
+ public void onVisibilityAggregated(boolean isVisible) {
+ super.onVisibilityAggregated(isVisible);
+ if (isVisible == mAggregatedVisible) return;
+
+ mAggregatedVisible = isVisible;
+ if (mAggregatedVisible) {
+ setEllipsize(TextUtils.TruncateAt.MARQUEE);
+ } else {
+ setEllipsize(TextUtils.TruncateAt.END);
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardPresentationTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
similarity index 73%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardPresentationTest.java
rename to packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
index dfe2913..8138420 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardPresentationTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -11,18 +11,18 @@
* 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
+ * limitations under the License.
*/
-package com.android.systemui.statusbar.phone;
+package com.android.keyguard;
+import android.content.Context;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.view.LayoutInflater;
import androidx.test.filters.SmallTest;
-import com.android.systemui.R;
+import com.android.keyguard.KeyguardDisplayManager.KeyguardPresentation;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.InjectionInflationController;
@@ -39,8 +39,9 @@
com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
InjectionInflationController inflationController = new InjectionInflationController(
SystemUIFactory.getInstance().getRootComponent());
- LayoutInflater inflater = inflationController
- .injectable(LayoutInflater.from(getContext()));
- inflater.inflate(R.layout.keyguard_presentation, null);
+ Context context = getContext();
+ KeyguardPresentation keyguardPresentation =
+ new KeyguardPresentation(context, context.getDisplay(), inflationController);
+ keyguardPresentation.onCreate(null /*savedInstanceState */);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
index 74b15fb..756cf3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -43,10 +43,14 @@
@RunWith(AndroidTestingRunner.class)
public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestCase {
+ private int mDisplayWidth = 500;
+ private int mDisplayHeight = 1000;
+
@Spy
private ExpandedAnimationController mExpandedController =
new ExpandedAnimationController(
- new Point(500, 1000) /* displaySize */, 0 /* expandedViewPadding */);
+ new Point(mDisplayWidth, mDisplayHeight) /* displaySize */,
+ 0 /* expandedViewPadding */);
private int mStackOffset;
private float mBubblePadding;
private float mBubbleSize;
@@ -58,11 +62,11 @@
super.setUp();
addOneMoreThanRenderLimitBubbles();
mLayout.setController(mExpandedController);
+
Resources res = mLayout.getResources();
mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
-
mExpansionPoint = new PointF(100, 100);
}
@@ -243,7 +247,7 @@
private void testBubblesInCorrectExpandedPositions() {
// Check all the visible bubbles to see if they're in the right place.
for (int i = 0; i < Math.min(mLayout.getChildCount(), mMaxRenderedBubbles); i++) {
- assertEquals(mBubblePadding + (i * (mBubbleSize + mBubblePadding)),
+ assertEquals(getBubbleLeft(i),
mLayout.getChildAt(i).getTranslationX(),
2f);
assertEquals(mExpandedController.getExpandedY(),
@@ -254,4 +258,33 @@
}
}
}
+
+ /**
+ * @param index Bubble index in row.
+ * @return Bubble left x from left edge of screen.
+ */
+ public float getBubbleLeft(int index) {
+ float bubbleLeftFromRowLeft = index * (mBubbleSize + mBubblePadding);
+ return getRowLeft() + bubbleLeftFromRowLeft;
+ }
+
+ private float getRowLeft() {
+ if (mLayout == null) {
+ return 0;
+ }
+ int bubbleCount = mLayout.getChildCount();
+ if (bubbleCount > mMaxRenderedBubbles) {
+ bubbleCount = mMaxRenderedBubbles;
+ }
+ // Width calculations.
+ double bubble = bubbleCount * mBubbleSize;
+ float gap = (bubbleCount - 1) * mBubblePadding;
+ float row = gap + (float) bubble;
+
+ float halfRow = row / 2f;
+ float centerScreen = mDisplayWidth / 2;
+ float rowLeftFromScreenLeft = centerScreen - halfRow;
+
+ return rowLeftFromScreenLeft;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 4d593c1..72f3a62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -402,6 +402,7 @@
verify(mRow).setEntry(eq(mEntry));
assertEquals(1, mEntry.systemGeneratedSmartActions.size());
assertEquals("action", mEntry.systemGeneratedSmartActions.get(0).title);
+ verify(mEntryListener).onNotificationRankingUpdated(mRankingMap);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
index ad9c729..4181d38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
@@ -271,6 +271,8 @@
when(view.getBarTransitions()).thenReturn(mock(BarTransitions.class));
when(view.getLightTransitionsController()).thenReturn(
mock(LightBarTransitionsController.class));
+ when(view.getRotationButtonController()).thenReturn(
+ mock(RotationButtonController.class));
return view;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarRotationContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarRotationContextTest.java
index 28e91ea..cf08428 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarRotationContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarRotationContextTest.java
@@ -51,7 +51,8 @@
public final SysuiTestableContext mContext = new SysuiTestableContext(
InstrumentationRegistry.getContext(), getLeakCheck());
private final TestableDependency mDependency = new TestableDependency(mContext);
- private RotationContextButton mButton;
+ private RotationButtonController mRotationButtonController;
+ private RotationButton mRotationButton;
@Before
public void setup() {
@@ -59,50 +60,66 @@
mDependency.injectMockDependency(RotationLockController.class);
final View view = new View(mContext);
- mButton = spy(new RotationContextButton(RES_UNDEF, RES_UNDEF, mContext, RES_UNDEF));
+ mRotationButton = mock(RotationButton.class);
+ mRotationButtonController = spy(
+ new RotationButtonController(mContext, RES_UNDEF, mRotationButton));
final KeyButtonDrawable kbd = mock(KeyButtonDrawable.class);
- doReturn(view).when(mButton).getCurrentView();
- doReturn(kbd).when(mButton).getNewDrawable();
+ doReturn(view).when(mRotationButton).getCurrentView();
+ doReturn(true).when(mRotationButton).acceptRotationProposal();
}
@Test
public void testOnInvalidRotationProposal() {
- mButton.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1, false /* isValid */);
- verify(mButton, times(1)).setRotateSuggestionButtonState(false /* visible */);
+ mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1,
+ false /* isValid */);
+ verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
+ false /* visible */);
}
@Test
public void testOnSameRotationProposal() {
- mButton.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE, true /* isValid */);
- verify(mButton, times(1)).setRotateSuggestionButtonState(false /* visible */);
+ mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE,
+ true /* isValid */);
+ verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
+ false /* visible */);
}
@Test
public void testOnRotationProposalShowButtonShowNav() {
// No navigation bar should not call to set visibility state
- mButton.onNavigationBarWindowVisibilityChange(false /* showing */);
- verify(mButton, times(0)).setRotateSuggestionButtonState(false /* visible */);
- verify(mButton, times(0)).setRotateSuggestionButtonState(true /* visible */);
+ mRotationButtonController.onNavigationBarWindowVisibilityChange(false /* showing */);
+ verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
+ false /* visible */);
+ verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
+ true /* visible */);
// No navigation bar with rotation change should not call to set visibility state
- mButton.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1, true /* isValid */);
- verify(mButton, times(0)).setRotateSuggestionButtonState(false /* visible */);
- verify(mButton, times(0)).setRotateSuggestionButtonState(true /* visible */);
+ mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1,
+ true /* isValid */);
+ verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
+ false /* visible */);
+ verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
+ true /* visible */);
// Since rotation has changed rotation should be pending, show mButton when showing nav bar
- mButton.onNavigationBarWindowVisibilityChange(true /* showing */);
- verify(mButton, times(1)).setRotateSuggestionButtonState(true /* visible */);
+ mRotationButtonController.onNavigationBarWindowVisibilityChange(true /* showing */);
+ verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
+ true /* visible */);
}
@Test
public void testOnRotationProposalShowButton() {
// Navigation bar being visible should not call to set visibility state
- mButton.onNavigationBarWindowVisibilityChange(true /* showing */);
- verify(mButton, times(0)).setRotateSuggestionButtonState(false /* visible */);
- verify(mButton, times(0)).setRotateSuggestionButtonState(true /* visible */);
+ mRotationButtonController.onNavigationBarWindowVisibilityChange(true /* showing */);
+ verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
+ false /* visible */);
+ verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
+ true /* visible */);
// Navigation bar is visible and rotation requested
- mButton.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1, true /* isValid */);
- verify(mButton, times(1)).setRotateSuggestionButtonState(true /* visible */);
+ mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1,
+ true /* isValid */);
+ verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
+ true /* visible */);
}
}
diff --git a/packages/overlays/Android.mk b/packages/overlays/Android.mk
index 2675e90..3f16c12 100644
--- a/packages/overlays/Android.mk
+++ b/packages/overlays/Android.mk
@@ -49,7 +49,10 @@
IconShapeTeardropOverlay \
NavigationBarMode3ButtonOverlay \
NavigationBarMode2ButtonOverlay \
- NavigationBarModeGesturalOverlay
+ NavigationBarModeGesturalOverlay \
+ NavigationBarModeGesturalOverlayNarrowBack \
+ NavigationBarModeGesturalOverlayWideBack \
+ NavigationBarModeGesturalOverlayExtraWideBack
include $(BUILD_PHONY_PACKAGE)
include $(CLEAR_VARS)
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.mk b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.mk
new file mode 100644
index 0000000..9a38efa
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.mk
@@ -0,0 +1,30 @@
+#
+# Copyright 2019, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_RRO_THEME := NavigationBarModeGesturalExtraWideBack
+
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := NavigationBarModeGesturalOverlayExtraWideBack
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_RRO_PACKAGE)
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/AndroidManifest.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/AndroidManifest.xml
new file mode 100644
index 0000000..ba7beba
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<!--
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.internal.systemui.navbar.gestural_extra_wide_back"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <overlay android:targetPackage="android"
+ android:category="com.android.internal.navigation_bar_mode"
+ android:priority="1"/>
+
+ <application android:label="@string/navigation_bar_mode_title" android:hasCode="false"/>
+</manifest>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/config.xml
new file mode 100644
index 0000000..c8f994c
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/config.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources>
+ <!-- Controls the navigation bar interaction mode:
+ 0: 3 button mode (back, home, overview buttons)
+ 1: 2 button mode (back, home buttons + swipe up for overview)
+ 2: gestures only for back, home and overview -->
+ <integer name="config_navBarInteractionMode">2</integer>
+
+ <!-- Controls whether the nav bar can move from the bottom to the side in landscape.
+ Only applies if the device display is not square. -->
+ <bool name="config_navBarCanMove">false</bool>
+
+ <!-- Controls whether the navigation bar lets through taps. -->
+ <bool name="config_navBarTapThrough">true</bool>
+
+ <!-- Controls the size of the back gesture inset. -->
+ <dimen name="config_backGestureInset">40dp</dimen>
+
+ <!-- Controls whether the navbar needs a scrim with
+ {@link Window#setEnsuringNavigationBarContrastWhenTransparent}. -->
+ <bool name="config_navBarNeedsScrim">false</bool>
+
+ <!-- Controls whether seamless rotation should be allowed even though the navbar can move
+ (which normally prevents seamless rotation). -->
+ <bool name="config_allowSeamlessRotationDespiteNavBarMoving">true</bool>
+
+ <!-- Controls whether the side edge gestures can always trigger the transient nav bar to
+ show. -->
+ <bool name="config_navBarAlwaysShowOnSideEdgeGesture">true</bool>
+
+</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml
new file mode 100644
index 0000000..987d203
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources>
+ <!-- Height of the bottom navigation / system bar. -->
+ <dimen name="navigation_bar_height">16dp</dimen>
+ <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
+ <dimen name="navigation_bar_height_landscape">16dp</dimen>
+ <!-- Width of the navigation bar when it is placed vertically on the screen -->
+ <dimen name="navigation_bar_width">16dp</dimen>
+ <!-- Height of the bottom navigation / system bar. -->
+ <dimen name="navigation_bar_frame_height">48dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/strings.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/strings.xml
new file mode 100644
index 0000000..bbab5e047
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Name of overlay [CHAR LIMIT=64] -->
+ <string name="navigation_bar_mode_title" translatable="false">Gestural Navigation Bar</string>
+</resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.mk b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.mk
new file mode 100644
index 0000000..1d004c8
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.mk
@@ -0,0 +1,30 @@
+#
+# Copyright 2019, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_RRO_THEME := NavigationBarModeGesturalNarrowBack
+
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := NavigationBarModeGesturalOverlayNarrowBack
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_RRO_PACKAGE)
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/AndroidManifest.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/AndroidManifest.xml
new file mode 100644
index 0000000..8de91c0
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<!--
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.internal.systemui.navbar.gestural_narrow_back"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <overlay android:targetPackage="android"
+ android:category="com.android.internal.navigation_bar_mode"
+ android:priority="1"/>
+
+ <application android:label="@string/navigation_bar_mode_title" android:hasCode="false"/>
+</manifest>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/config.xml
new file mode 100644
index 0000000..693110a
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/config.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources>
+ <!-- Controls the navigation bar interaction mode:
+ 0: 3 button mode (back, home, overview buttons)
+ 1: 2 button mode (back, home buttons + swipe up for overview)
+ 2: gestures only for back, home and overview -->
+ <integer name="config_navBarInteractionMode">2</integer>
+
+ <!-- Controls whether the nav bar can move from the bottom to the side in landscape.
+ Only applies if the device display is not square. -->
+ <bool name="config_navBarCanMove">false</bool>
+
+ <!-- Controls whether the navigation bar lets through taps. -->
+ <bool name="config_navBarTapThrough">true</bool>
+
+ <!-- Controls the size of the back gesture inset. -->
+ <dimen name="config_backGestureInset">18dp</dimen>
+
+ <!-- Controls whether the navbar needs a scrim with
+ {@link Window#setEnsuringNavigationBarContrastWhenTransparent}. -->
+ <bool name="config_navBarNeedsScrim">false</bool>
+
+ <!-- Controls whether seamless rotation should be allowed even though the navbar can move
+ (which normally prevents seamless rotation). -->
+ <bool name="config_allowSeamlessRotationDespiteNavBarMoving">true</bool>
+
+ <!-- Controls whether the side edge gestures can always trigger the transient nav bar to
+ show. -->
+ <bool name="config_navBarAlwaysShowOnSideEdgeGesture">true</bool>
+
+</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml
new file mode 100644
index 0000000..987d203
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources>
+ <!-- Height of the bottom navigation / system bar. -->
+ <dimen name="navigation_bar_height">16dp</dimen>
+ <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
+ <dimen name="navigation_bar_height_landscape">16dp</dimen>
+ <!-- Width of the navigation bar when it is placed vertically on the screen -->
+ <dimen name="navigation_bar_width">16dp</dimen>
+ <!-- Height of the bottom navigation / system bar. -->
+ <dimen name="navigation_bar_frame_height">48dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/strings.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/strings.xml
new file mode 100644
index 0000000..bbab5e047
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Name of overlay [CHAR LIMIT=64] -->
+ <string name="navigation_bar_mode_title" translatable="false">Gestural Navigation Bar</string>
+</resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.mk b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.mk
new file mode 100644
index 0000000..0ab463f
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.mk
@@ -0,0 +1,30 @@
+#
+# Copyright 2019, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_RRO_THEME := NavigationBarModeGesturalWideBack
+
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := NavigationBarModeGesturalOverlayWideBack
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_RRO_PACKAGE)
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/AndroidManifest.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/AndroidManifest.xml
new file mode 100644
index 0000000..daf4613
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<!--
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.internal.systemui.navbar.gestural_wide_back"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <overlay android:targetPackage="android"
+ android:category="com.android.internal.navigation_bar_mode"
+ android:priority="1"/>
+
+ <application android:label="@string/navigation_bar_mode_title" android:hasCode="false"/>
+</manifest>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/config.xml
new file mode 100644
index 0000000..5cd6ce3
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/config.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources>
+ <!-- Controls the navigation bar interaction mode:
+ 0: 3 button mode (back, home, overview buttons)
+ 1: 2 button mode (back, home buttons + swipe up for overview)
+ 2: gestures only for back, home and overview -->
+ <integer name="config_navBarInteractionMode">2</integer>
+
+ <!-- Controls whether the nav bar can move from the bottom to the side in landscape.
+ Only applies if the device display is not square. -->
+ <bool name="config_navBarCanMove">false</bool>
+
+ <!-- Controls whether the navigation bar lets through taps. -->
+ <bool name="config_navBarTapThrough">true</bool>
+
+ <!-- Controls the size of the back gesture inset. -->
+ <dimen name="config_backGestureInset">32dp</dimen>
+
+ <!-- Controls whether the navbar needs a scrim with
+ {@link Window#setEnsuringNavigationBarContrastWhenTransparent}. -->
+ <bool name="config_navBarNeedsScrim">false</bool>
+
+ <!-- Controls whether seamless rotation should be allowed even though the navbar can move
+ (which normally prevents seamless rotation). -->
+ <bool name="config_allowSeamlessRotationDespiteNavBarMoving">true</bool>
+
+ <!-- Controls whether the side edge gestures can always trigger the transient nav bar to
+ show. -->
+ <bool name="config_navBarAlwaysShowOnSideEdgeGesture">true</bool>
+
+</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml
new file mode 100644
index 0000000..987d203
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources>
+ <!-- Height of the bottom navigation / system bar. -->
+ <dimen name="navigation_bar_height">16dp</dimen>
+ <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
+ <dimen name="navigation_bar_height_landscape">16dp</dimen>
+ <!-- Width of the navigation bar when it is placed vertically on the screen -->
+ <dimen name="navigation_bar_width">16dp</dimen>
+ <!-- Height of the bottom navigation / system bar. -->
+ <dimen name="navigation_bar_frame_height">48dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/strings.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/strings.xml
new file mode 100644
index 0000000..bbab5e047
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Name of overlay [CHAR LIMIT=64] -->
+ <string name="navigation_bar_mode_title" translatable="false">Gestural Navigation Bar</string>
+</resources>
\ No newline at end of file
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index 71d03a8..3061981 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -7376,6 +7376,9 @@
// OS: Q
FIELD_BIOMETRIC_AUTH_ERROR = 1741;
+ // Custom tag for NotificationItem. Hash of the NAS that made adjustments.
+ FIELD_NOTIFICATION_ASSISTANT_SERVICE_HASH = 1742;
+
// ---- End Q Constants, all Q constants go above this line ----
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index dfbb55a..d0edaaa 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -1763,7 +1763,7 @@
+ ", callingPackage: " + callingPackage;
// STOPSHIP (b/128866264): Just to catch breakages. Remove before final release.
Slog.wtf(TAG, errorMsg);
- throw new UnsupportedOperationException(errorMsg);
+ throw new IllegalStateException(errorMsg);
}
setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
interval, operation, directReceiver, listenerTag, flags, true, workSource,
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 1816681..0a1dbff 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -77,6 +77,7 @@
import android.net.ISocketKeepaliveCallback;
import android.net.ITetheringEventCallback;
import android.net.InetAddresses;
+import android.net.IpMemoryStore;
import android.net.IpPrefix;
import android.net.LinkProperties;
import android.net.LinkProperties.CompareResult;
@@ -6888,6 +6889,9 @@
final int userId = UserHandle.getCallingUserId();
+ final IpMemoryStore ipMemoryStore = IpMemoryStore.getMemoryStore(mContext);
+ ipMemoryStore.factoryReset();
+
// Turn airplane mode off
setAirplaneMode(false);
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 64bcaa0..7a947f1 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -54,6 +54,7 @@
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.WorkSource;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.util.DebugUtils;
@@ -78,6 +79,7 @@
private static final boolean DEBUG = false;
private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
+ private static final String RAMPING_RINGER_ENABLED = "ramping_ringer_enabled";
private static final long[] DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS = { 0, 30, 100, 30 };
@@ -874,6 +876,11 @@
if (Settings.System.getInt(
mContext.getContentResolver(), Settings.System.VIBRATE_WHEN_RINGING, 0) != 0) {
return ringerMode != AudioManager.RINGER_MODE_SILENT;
+ } else if (Settings.Global.getInt(
+ mContext.getContentResolver(), Settings.Global.APPLY_RAMPING_RINGER, 0) != 0
+ && DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_TELEPHONY, RAMPING_RINGER_ENABLED, false)) {
+ return ringerMode != AudioManager.RINGER_MODE_SILENT;
} else {
return ringerMode == AudioManager.RINGER_MODE_VIBRATE;
}
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index bdbff3d..4b48ef9 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -661,12 +661,6 @@
return mTestUserKeyFile == null ? getAdbFile(ADB_KEYS_FILE) : mTestUserKeyFile;
}
- private void createKeyFile(File keyFile) throws IOException {
- keyFile.createNewFile();
- FileUtils.setPermissions(keyFile.toString(),
- FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1);
- }
-
private void writeKey(String key) {
try {
File keyFile = getUserKeyFile();
@@ -675,14 +669,13 @@
return;
}
- if (!keyFile.exists()) {
- createKeyFile(keyFile);
- }
-
FileOutputStream fo = new FileOutputStream(keyFile, true);
fo.write(key.getBytes());
fo.write('\n');
fo.close();
+
+ FileUtils.setPermissions(keyFile.toString(),
+ FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1);
} catch (IOException ex) {
Slog.e(TAG, "Error writing key:" + ex);
}
@@ -698,10 +691,6 @@
return;
}
- if (!keyFile.exists()) {
- createKeyFile(keyFile);
- }
-
atomicKeyFile = new AtomicFile(keyFile);
fo = atomicKeyFile.startWrite();
for (String key : keys) {
@@ -709,6 +698,9 @@
fo.write('\n');
}
atomicKeyFile.finishWrite(fo);
+
+ FileUtils.setPermissions(keyFile.toString(),
+ FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1);
} catch (IOException ex) {
Slog.e(TAG, "Error writing keys: " + ex);
if (atomicKeyFile != null) {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 017c1b3..107e1fbb 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -601,21 +601,6 @@
// Hide notification first, as tie managed profile lock takes time
hideEncryptionNotification(new UserHandle(userId));
- // Now we have unlocked the parent user we should show notifications
- // about any profiles that exist.
- List<UserInfo> profiles = mUserManager.getProfiles(userId);
- for (int i = 0; i < profiles.size(); i++) {
- UserInfo profile = profiles.get(i);
- final boolean isSecure = isUserSecure(profile.id);
- if (isSecure && profile.isManagedProfile()) {
- UserHandle userHandle = profile.getUserHandle();
- if (!mUserManager.isUserUnlockingOrUnlocked(userHandle) &&
- !mUserManager.isQuietModeEnabled(userHandle)) {
- showEncryptionNotificationForProfile(userHandle);
- }
- }
- }
-
if (mUserManager.getUserInfo(userId).isManagedProfile()) {
tieManagedProfileLockIfNecessary(userId, null);
}
@@ -1205,6 +1190,7 @@
*/
private void unlockUser(int userId, byte[] token, byte[] secret) {
// TODO: make this method fully async so we can update UI with progress strings
+ final boolean alreadyUnlocked = mUserManager.isUserUnlockingOrUnlocked(userId);
final CountDownLatch latch = new CountDownLatch(1);
final IProgressListener listener = new IProgressListener.Stub() {
@Override
@@ -1235,18 +1221,31 @@
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
- try {
- if (!mUserManager.getUserInfo(userId).isManagedProfile()) {
- final List<UserInfo> profiles = mUserManager.getProfiles(userId);
- for (UserInfo pi : profiles) {
- // Unlock managed profile with unified lock
- if (tiedManagedProfileReadyToUnlock(pi)) {
- unlockChildProfile(pi.id, false /* ignoreUserNotAuthenticated */);
- }
+
+ if (mUserManager.getUserInfo(userId).isManagedProfile()) {
+ return;
+ }
+
+ for (UserInfo profile : mUserManager.getProfiles(userId)) {
+ // Unlock managed profile with unified lock
+ if (tiedManagedProfileReadyToUnlock(profile)) {
+ try {
+ unlockChildProfile(profile.id, false /* ignoreUserNotAuthenticated */);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Failed to unlock child profile", e);
}
}
- } catch (RemoteException e) {
- Log.d(TAG, "Failed to unlock child profile", e);
+ // Now we have unlocked the parent user and attempted to unlock the profile we should
+ // show notifications if the profile is still locked.
+ if (!alreadyUnlocked) {
+ long ident = clearCallingIdentity();
+ try {
+ maybeShowEncryptionNotificationForUser(profile.id);
+ } finally {
+ restoreCallingIdentity(ident);
+ }
+ }
+
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 1160e33..c4eb661 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1036,19 +1036,12 @@
final StatusBarNotification n = r.sbn;
final int callingUid = n.getUid();
final String pkg = n.getPackageName();
- final boolean wasBubble = r.getNotification().isBubbleNotification();
if (isBubble && isNotificationAppropriateToBubble(r, pkg, callingUid,
null /* oldEntry */)) {
r.getNotification().flags |= FLAG_BUBBLE;
} else {
r.getNotification().flags &= ~FLAG_BUBBLE;
}
- if (wasBubble != r.getNotification().isBubbleNotification()) {
- // Add the "alert only once" flag so that the notification won't HUN
- // unnecessarily just because the bubble flag was changed.
- r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
- mListeners.notifyPostedLocked(r, r);
- }
}
}
}
@@ -2643,18 +2636,25 @@
ParceledListSlice channelsList) {
List<NotificationChannel> channels = channelsList.getList();
final int channelsSize = channels.size();
+ boolean needsPolicyFileChange = false;
for (int i = 0; i < channelsSize; i++) {
final NotificationChannel channel = channels.get(i);
Preconditions.checkNotNull(channel, "channel in list is null");
- mPreferencesHelper.createNotificationChannel(pkg, uid, channel,
- true /* fromTargetApp */, mConditionProviders.isPackageOrComponentAllowed(
+ needsPolicyFileChange = mPreferencesHelper.createNotificationChannel(pkg, uid,
+ channel, true /* fromTargetApp */,
+ mConditionProviders.isPackageOrComponentAllowed(
pkg, UserHandle.getUserId(uid)));
- mListeners.notifyNotificationChannelChanged(pkg,
- UserHandle.getUserHandleForUid(uid),
- mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false),
- NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
+ if (needsPolicyFileChange) {
+ mListeners.notifyNotificationChannelChanged(pkg,
+ UserHandle.getUserHandleForUid(uid),
+ mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(),
+ false),
+ NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
+ }
}
- handleSavePolicyFile();
+ if (needsPolicyFileChange) {
+ handleSavePolicyFile();
+ }
}
@Override
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 4ed24ec..c2e559a 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -174,6 +174,7 @@
private ArrayList<CharSequence> mSmartReplies;
private final List<Adjustment> mAdjustments;
+ private String mAdjustmentIssuer;
private final NotificationStats mStats;
private int mUserSentiment;
private boolean mIsInterruptive;
@@ -684,6 +685,9 @@
importance = Math.min(IMPORTANCE_HIGH, importance);
setAssistantImportance(importance);
}
+ if (!signals.isEmpty() && adjustment.getIssuer() != null) {
+ mAdjustmentIssuer = adjustment.getIssuer();
+ }
}
// We have now gotten all the information out of the adjustments and can forget them.
mAdjustments.clear();
@@ -1297,6 +1301,13 @@
lm.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_IMPORTANCE_ASST,
mAssistantImportance);
}
+ // Log the issuer of any adjustments that may have affected this notification. We only log
+ // the hash here as NotificationItem events are frequent, and the number of NAS
+ // implementations (and hence the chance of collisions) is low.
+ if (mAdjustmentIssuer != null) {
+ lm.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_ASSISTANT_SERVICE_HASH,
+ mAdjustmentIssuer.hashCode());
+ }
return lm;
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 9886d0a..1c0ac16 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -481,11 +481,16 @@
* @param allowed whether bubbles are allowed.
*/
public void setBubblesAllowed(String pkg, int uid, boolean allowed) {
+ boolean changed = false;
synchronized (mPackagePreferences) {
PackagePreferences p = getOrCreatePackagePreferencesLocked(pkg, uid);
+ changed = p.allowBubble != allowed;
p.allowBubble = allowed;
p.lockedAppFields = p.lockedAppFields | LockableAppFields.USER_LOCKED_BUBBLE;
}
+ if (changed) {
+ updateConfig();
+ }
}
/**
@@ -610,12 +615,13 @@
}
@Override
- public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
+ public boolean createNotificationChannel(String pkg, int uid, NotificationChannel channel,
boolean fromTargetApp, boolean hasDndAccess) {
Preconditions.checkNotNull(pkg);
Preconditions.checkNotNull(channel);
Preconditions.checkNotNull(channel.getId());
Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
+ boolean needsPolicyFileChange = false;
synchronized (mPackagePreferences) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
if (r == null) {
@@ -632,17 +638,28 @@
if (existing != null && fromTargetApp) {
if (existing.isDeleted()) {
existing.setDeleted(false);
+ needsPolicyFileChange = true;
// log a resurrected channel as if it's new again
MetricsLogger.action(getChannelLog(channel, pkg).setType(
com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
}
- existing.setName(channel.getName().toString());
- existing.setDescription(channel.getDescription());
- existing.setBlockableSystem(channel.isBlockableSystem());
- if (existing.getGroup() == null) {
+ if (!Objects.equals(channel.getName().toString(), existing.getName().toString())) {
+ existing.setName(channel.getName().toString());
+ needsPolicyFileChange = true;
+ }
+ if (!Objects.equals(channel.getDescription(), existing.getDescription())) {
+ existing.setDescription(channel.getDescription());
+ needsPolicyFileChange = true;
+ }
+ if (channel.isBlockableSystem() != existing.isBlockableSystem()) {
+ existing.setBlockableSystem(channel.isBlockableSystem());
+ needsPolicyFileChange = true;
+ }
+ if (channel.getGroup() != null && existing.getGroup() == null) {
existing.setGroup(channel.getGroup());
+ needsPolicyFileChange = true;
}
// Apps are allowed to downgrade channel importance if the user has not changed any
@@ -651,23 +668,30 @@
if (existing.getUserLockedFields() == 0 &&
channel.getImportance() < existing.getImportance()) {
existing.setImportance(channel.getImportance());
+ needsPolicyFileChange = true;
}
// system apps and dnd access apps can bypass dnd if the user hasn't changed any
// fields on the channel yet
if (existing.getUserLockedFields() == 0 && hasDndAccess) {
boolean bypassDnd = channel.canBypassDnd();
- existing.setBypassDnd(bypassDnd);
+ if (bypassDnd != existing.canBypassDnd()) {
+ existing.setBypassDnd(bypassDnd);
+ needsPolicyFileChange = true;
- if (bypassDnd != mAreChannelsBypassingDnd
- || previousExistingImportance != existing.getImportance()) {
- updateChannelsBypassingDnd(mContext.getUserId());
+ if (bypassDnd != mAreChannelsBypassingDnd
+ || previousExistingImportance != existing.getImportance()) {
+ updateChannelsBypassingDnd(mContext.getUserId());
+ }
}
}
updateConfig();
- return;
+ return needsPolicyFileChange;
}
+
+ needsPolicyFileChange = true;
+
if (channel.getImportance() < IMPORTANCE_NONE
|| channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
throw new IllegalArgumentException("Invalid importance level");
@@ -703,6 +727,8 @@
MetricsLogger.action(getChannelLog(channel, pkg).setType(
com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
}
+
+ return needsPolicyFileChange;
}
void clearLockedFieldsLocked(NotificationChannel channel) {
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 72502acd..5de00e4 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -39,7 +39,7 @@
boolean fromTargetApp);
ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty);
- void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
+ boolean createNotificationChannel(String pkg, int uid, NotificationChannel channel,
boolean fromTargetApp, boolean hasDndAccess);
void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser);
NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted);
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index dd2d300..6971ba3 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.apex.ApexInfo;
@@ -26,6 +27,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
@@ -42,8 +44,13 @@
import java.io.File;
import java.io.PrintWriter;
-import java.util.Collection;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.stream.Collectors;
/**
* ApexManager class handles communications with the apex service to perform operation and queries,
@@ -59,10 +66,10 @@
* AndroidManifest.xml}
*
* <p>Note that key of this map is {@code packageName} field of the corresponding {@code
- * AndroidManfiset.xml}.
+ * AndroidManifest.xml}.
*/
@GuardedBy("mLock")
- private ArrayMap<String, PackageInfo> mActivePackagesCache;
+ private List<PackageInfo> mAllPackagesCache;
/**
* A map from {@code apexName} to the {@Link PackageInfo} generated from the {@code
* AndroidManifest.xml}.
@@ -74,6 +81,7 @@
@GuardedBy("mLock")
private ArrayMap<String, PackageInfo> mApexNameToPackageInfoCache;
+
ApexManager(Context context) {
try {
mApexService = IApexService.Stub.asInterface(
@@ -84,6 +92,15 @@
mContext = context;
}
+ static final int MATCH_ACTIVE_PACKAGE = 1 << 0;
+ static final int MATCH_FACTORY_PACKAGE = 1 << 1;
+ @IntDef(
+ flag = true,
+ prefix = { "MATCH_"},
+ value = {MATCH_ACTIVE_PACKAGE, MATCH_FACTORY_PACKAGE})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface PackageInfoFlags{}
+
void systemReady() {
mContext.registerReceiver(new BroadcastReceiver() {
@Override
@@ -94,16 +111,18 @@
}, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
}
- private void populateActivePackagesCacheIfNeeded() {
+ private void populateAllPackagesCacheIfNeeded() {
synchronized (mLock) {
- if (mActivePackagesCache != null) {
+ if (mAllPackagesCache != null) {
return;
}
- mActivePackagesCache = new ArrayMap<>();
mApexNameToPackageInfoCache = new ArrayMap<>();
try {
- final ApexInfo[] activePkgs = mApexService.getActivePackages();
- for (ApexInfo ai : activePkgs) {
+ mAllPackagesCache = new ArrayList<>();
+ HashSet<String> activePackagesSet = new HashSet<>();
+ HashSet<String> factoryPackagesSet = new HashSet<>();
+ final ApexInfo[] allPkgs = mApexService.getAllPackages();
+ for (ApexInfo ai : allPkgs) {
// If the device is using flattened APEX, don't report any APEX
// packages since they won't be managed or updated by PackageManager.
if ((new File(ai.packagePath)).isDirectory()) {
@@ -111,11 +130,27 @@
}
try {
final PackageInfo pkg = PackageParser.generatePackageInfoFromApex(
- new File(ai.packagePath), PackageManager.GET_META_DATA
- | PackageManager.GET_SIGNING_CERTIFICATES);
- mActivePackagesCache.put(pkg.packageName, pkg);
- // TODO(b/132324953): remove.
- mApexNameToPackageInfoCache.put(ai.packageName, pkg);
+ ai, PackageManager.GET_META_DATA
+ | PackageManager.GET_SIGNING_CERTIFICATES);
+ mAllPackagesCache.add(pkg);
+ if (ai.isActive) {
+ if (activePackagesSet.contains(pkg.packageName)) {
+ throw new IllegalStateException(
+ "Two active packages have the same name: "
+ + pkg.packageName);
+ }
+ activePackagesSet.add(ai.packageName);
+ // TODO(b/132324953): remove.
+ mApexNameToPackageInfoCache.put(ai.packageName, pkg);
+ }
+ if (ai.isFactory) {
+ if (factoryPackagesSet.contains(pkg.packageName)) {
+ throw new IllegalStateException(
+ "Two factory packages have the same name: "
+ + pkg.packageName);
+ }
+ factoryPackagesSet.add(ai.packageName);
+ }
} catch (PackageParserException pe) {
throw new IllegalStateException("Unable to parse: " + ai, pe);
}
@@ -128,41 +163,85 @@
}
/**
- * Retrieves information about an active APEX package.
+ * Retrieves information about an APEX package.
*
* @param packageName the package name to look for. Note that this is the package name reported
* in the APK container manifest (i.e. AndroidManifest.xml), which might
* differ from the one reported in the APEX manifest (i.e.
* apex_manifest.json).
+ * @param flags the type of package to return. This may match to active packages
+ * and factory (pre-installed) packages.
* @return a PackageInfo object with the information about the package, or null if the package
* is not found.
*/
- @Nullable PackageInfo getActivePackage(String packageName) {
- populateActivePackagesCacheIfNeeded();
- return mActivePackagesCache.get(packageName);
+ @Nullable PackageInfo getPackageInfo(String packageName, @PackageInfoFlags int flags) {
+ populateAllPackagesCacheIfNeeded();
+ boolean matchActive = (flags & MATCH_ACTIVE_PACKAGE) != 0;
+ boolean matchFactory = (flags & MATCH_FACTORY_PACKAGE) != 0;
+ for (PackageInfo packageInfo: mAllPackagesCache) {
+ if (!packageInfo.packageName.equals(packageName)) {
+ continue;
+ }
+ if ((!matchActive || isActive(packageInfo))
+ && (!matchFactory || isFactory(packageInfo))) {
+ return packageInfo;
+ }
+ }
+ return null;
}
/**
- * Returns a {@link PackageInfo} for an APEX package keyed by it's {@code apexName}.
+ * Returns a {@link PackageInfo} for an active APEX package keyed by it's {@code apexName}.
*
* @deprecated this API will soon be deleted, please don't depend on it.
*/
// TODO(b/132324953): delete.
@Deprecated
@Nullable PackageInfo getPackageInfoForApexName(String apexName) {
- populateActivePackagesCacheIfNeeded();
+ populateAllPackagesCacheIfNeeded();
return mApexNameToPackageInfoCache.get(apexName);
}
/**
* Retrieves information about all active APEX packages.
*
- * @return a Collection of PackageInfo object, each one containing information about a different
+ * @return a List of PackageInfo object, each one containing information about a different
* active package.
*/
- Collection<PackageInfo> getActivePackages() {
- populateActivePackagesCacheIfNeeded();
- return mActivePackagesCache.values();
+ List<PackageInfo> getActivePackages() {
+ populateAllPackagesCacheIfNeeded();
+ return mAllPackagesCache
+ .stream()
+ .filter(item -> isActive(item))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Retrieves information about all active pre-installed APEX packages.
+ *
+ * @return a List of PackageInfo object, each one containing information about a different
+ * active pre-installed package.
+ */
+ List<PackageInfo> getFactoryPackages() {
+ populateAllPackagesCacheIfNeeded();
+ return mAllPackagesCache
+ .stream()
+ .filter(item -> isFactory(item))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Retrieves information about all inactive APEX packages.
+ *
+ * @return a List of PackageInfo object, each one containing information about a different
+ * inactive package.
+ */
+ List<PackageInfo> getInactivePackages() {
+ populateAllPackagesCacheIfNeeded();
+ return mAllPackagesCache
+ .stream()
+ .filter(item -> !isActive(item))
+ .collect(Collectors.toList());
}
/**
@@ -172,8 +251,13 @@
* @return {@code true} if {@code packageName} is an apex package.
*/
boolean isApexPackage(String packageName) {
- populateActivePackagesCacheIfNeeded();
- return mActivePackagesCache.containsKey(packageName);
+ populateAllPackagesCacheIfNeeded();
+ for (PackageInfo packageInfo : mAllPackagesCache) {
+ if (packageInfo.packageName.equals(packageName)) {
+ return true;
+ }
+ }
+ return false;
}
/**
@@ -301,6 +385,55 @@
}
/**
+ * Whether an APEX package is active or not.
+ *
+ * @param packageInfo the package to check
+ * @return {@code true} if this package is active, {@code false} otherwise.
+ */
+ private static boolean isActive(PackageInfo packageInfo) {
+ return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0;
+ }
+
+ /**
+ * Whether the APEX package is pre-installed or not.
+ *
+ * @param packageInfo the package to check
+ * @return {@code true} if this package is pre-installed, {@code false} otherwise.
+ */
+ private static boolean isFactory(PackageInfo packageInfo) {
+ return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ }
+
+ /**
+ * Dump information about the packages contained in a particular cache
+ * @param packagesCache the cache to print information about.
+ * @param packageName a {@link String} containing a package name, or {@code null}. If set, only
+ * information about that specific package will be dumped.
+ * @param ipw the {@link IndentingPrintWriter} object to send information to.
+ */
+ void dumpFromPackagesCache(
+ List<PackageInfo> packagesCache,
+ @Nullable String packageName,
+ IndentingPrintWriter ipw) {
+ ipw.println();
+ ipw.increaseIndent();
+ for (PackageInfo pi : packagesCache) {
+ if (packageName != null && !packageName.equals(pi.packageName)) {
+ continue;
+ }
+ ipw.println(pi.packageName);
+ ipw.increaseIndent();
+ ipw.println("Version: " + pi.versionCode);
+ ipw.println("Path: " + pi.applicationInfo.sourceDir);
+ ipw.println("IsActive: " + isActive(pi));
+ ipw.println("IsFactory: " + isFactory(pi));
+ ipw.decreaseIndent();
+ }
+ ipw.decreaseIndent();
+ ipw.println();
+ }
+
+ /**
* Dumps various state information to the provided {@link PrintWriter} object.
*
* @param pw the {@link PrintWriter} object to send information to.
@@ -309,23 +442,16 @@
*/
void dump(PrintWriter pw, @Nullable String packageName) {
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
- ipw.println();
- ipw.println("Active APEX packages:");
- ipw.increaseIndent();
try {
- populateActivePackagesCacheIfNeeded();
- for (PackageInfo pi : mActivePackagesCache.values()) {
- if (packageName != null && !packageName.equals(pi.packageName)) {
- continue;
- }
- ipw.println(pi.packageName);
- ipw.increaseIndent();
- ipw.println("Version: " + pi.versionCode);
- ipw.println("Path: " + pi.applicationInfo.sourceDir);
- ipw.decreaseIndent();
- }
- ipw.decreaseIndent();
+ populateAllPackagesCacheIfNeeded();
ipw.println();
+ ipw.println("Active APEX packages:");
+ dumpFromPackagesCache(getActivePackages(), packageName, ipw);
+ ipw.println("Inactive APEX packages:");
+ dumpFromPackagesCache(getInactivePackages(), packageName, ipw);
+ ipw.println("Factory APEX packages:");
+ dumpFromPackagesCache(getFactoryPackages(), packageName, ipw);
+ ipw.increaseIndent();
ipw.println("APEX session state:");
ipw.increaseIndent();
final ApexSessionInfo[] sessions = mApexService.getSessions();
@@ -360,6 +486,6 @@
}
public void onBootCompleted() {
- populateActivePackagesCacheIfNeeded();
+ populateAllPackagesCacheIfNeeded();
}
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 84470f9..2a61fee 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4220,6 +4220,11 @@
final boolean matchFactoryOnly = (flags & MATCH_FACTORY_ONLY) != 0;
if (matchFactoryOnly) {
+ // Instant app filtering for APEX modules is ignored
+ if ((flags & MATCH_APEX) != 0) {
+ return mApexManager.getPackageInfo(packageName,
+ ApexManager.MATCH_FACTORY_PACKAGE);
+ }
final PackageSetting ps = mSettings.getDisabledSystemPkgLPr(packageName);
if (ps != null) {
if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) {
@@ -4230,7 +4235,6 @@
}
return generatePackageInfo(ps, flags, userId);
}
- // TODO(b/123680735): support MATCH_APEX|MATCH_FACTORY_ONLY case
}
PackageParser.Package p = mPackages.get(packageName);
@@ -4260,9 +4264,8 @@
}
return generatePackageInfo(ps, flags, userId);
}
- //
if (!matchFactoryOnly && (flags & MATCH_APEX) != 0) {
- return mApexManager.getActivePackage(packageName);
+ return mApexManager.getPackageInfo(packageName, ApexManager.MATCH_ACTIVE_PACKAGE);
}
}
return null;
@@ -8558,6 +8561,7 @@
flags = updateFlagsForPackage(flags, userId, null);
final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
final boolean listApex = (flags & MATCH_APEX) != 0;
+ final boolean listFactory = (flags & MATCH_FACTORY_ONLY) != 0;
mPermissionManager.enforceCrossUserPermission(callingUid, userId,
false /* requireFullPermission */, false /* checkShell */,
@@ -8598,9 +8602,14 @@
}
}
if (listApex) {
- // TODO(b/119767311): include uninstalled/inactive APEX if
- // MATCH_UNINSTALLED_PACKAGES is set.
- list.addAll(mApexManager.getActivePackages());
+ if (listFactory) {
+ list.addAll(mApexManager.getFactoryPackages());
+ } else {
+ list.addAll(mApexManager.getActivePackages());
+ }
+ if (listUninstalled) {
+ list.addAll(mApexManager.getInactivePackages());
+ }
}
return new ParceledListSlice<>(list);
}
@@ -24890,7 +24899,8 @@
return;
}
final ApexManager am = PackageManagerService.this.mApexManager;
- PackageInfo activePackage = am.getActivePackage(packageName);
+ PackageInfo activePackage = am.getPackageInfo(packageName,
+ ApexManager.MATCH_ACTIVE_PACKAGE);
if (activePackage == null) {
adapter.onPackageDeleted(packageName, PackageManager.DELETE_FAILED_ABORTED,
packageName + " is not an apex package");
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index eec4b70..d6e87aa 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -674,9 +674,10 @@
return new ArrayList<>();
}
- // Get the list of all dynamic shortcuts in this package
+ // Get the list of all dynamic shortcuts in this package.
final ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
- findAll(shortcuts, ShortcutInfo::isDynamicVisible, ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
+ findAll(shortcuts, ShortcutInfo::isDynamicVisible,
+ ShortcutInfo.CLONE_REMOVE_FOR_APP_PREDICTION);
final List<ShortcutManager.ShareShortcutInfo> result = new ArrayList<>();
for (int i = 0; i < shortcuts.size(); i++) {
diff --git a/services/core/java/com/android/server/pm/permission/TEST_MAPPING b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
index 610c82f..3f9eb2d 100644
--- a/services/core/java/com/android/server/pm/permission/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
@@ -29,16 +29,10 @@
"name": "CtsPermission2TestCases",
"options": [
{
- "include-filter": "android.permission.cts.SharedUidPermissionsTest"
- },
- {
- "include-filter": "android.permission.cts.RestrictedPermissionsTest"
+ "include-filter": "android.permission2.cts.RestrictedPermissionsTest"
},
{
"include-filter": "android.permission.cts.PermissionMaxSdkVersionTest"
- },
- {
- "include-filter": "android.permission.cts.PrivappPermissionsTest"
}
]
},
diff --git a/services/core/java/com/android/server/telecom/TelecomLoaderService.java b/services/core/java/com/android/server/telecom/TelecomLoaderService.java
index e65eae0..54369ca 100644
--- a/services/core/java/com/android/server/telecom/TelecomLoaderService.java
+++ b/services/core/java/com/android/server/telecom/TelecomLoaderService.java
@@ -16,21 +16,17 @@
package com.android.server.telecom;
+import android.app.role.RoleManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
-import android.provider.Settings;
import android.telecom.DefaultDialerManager;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
@@ -221,19 +217,10 @@
private void registerDefaultAppNotifier() {
final DefaultPermissionGrantPolicy permissionPolicy = getDefaultPermissionGrantPolicy();
// Notify the package manager on default app changes
- final Uri defaultDialerAppUri = Settings.Secure.getUriFor(
- Settings.Secure.DIALER_DEFAULT_APPLICATION);
- ContentObserver contentObserver = new ContentObserver(
- new Handler(Looper.getMainLooper())) {
- @Override
- public void onChange(boolean selfChange, Uri uri, int userId) {
- if (defaultDialerAppUri.equals(uri)) {
- updateSimCallManagerPermissions(permissionPolicy, userId);
- }
- }
- };
- mContext.getContentResolver().registerContentObserver(defaultDialerAppUri,
- false, contentObserver, UserHandle.USER_ALL);
+ final RoleManager roleManager = mContext.getSystemService(RoleManager.class);
+ roleManager.addOnRoleHoldersChangedListenerAsUser(mContext.getMainExecutor(),
+ (roleName, user) -> updateSimCallManagerPermissions(permissionPolicy,
+ user.getIdentifier()), UserHandle.ALL);
}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 3a39053..a2eb40b 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -518,8 +518,11 @@
agentInfo = mActiveAgents.valueAt(index);
}
- boolean directUnlock = resolveInfo.serviceInfo.directBootAware
- && agentInfo.settings.canUnlockProfile;
+ boolean directUnlock = false;
+ if (agentInfo.settings != null) {
+ directUnlock = resolveInfo.serviceInfo.directBootAware
+ && agentInfo.settings.canUnlockProfile;
+ }
if (directUnlock) {
if (DEBUG) Slog.d(TAG, "refreshAgentList: trustagent " + name
diff --git a/services/core/java/com/android/server/twilight/TwilightService.java b/services/core/java/com/android/server/twilight/TwilightService.java
index bb4d67e..e4cb19e 100644
--- a/services/core/java/com/android/server/twilight/TwilightService.java
+++ b/services/core/java/com/android/server/twilight/TwilightService.java
@@ -162,9 +162,6 @@
if (mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
mLocationManager.requestSingleUpdate(
LocationManager.NETWORK_PROVIDER, this, Looper.getMainLooper());
- } else if (mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
- mLocationManager.requestSingleUpdate(
- LocationManager.GPS_PROVIDER, this, Looper.getMainLooper());
}
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index c5b2566..434239f 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -231,6 +231,11 @@
mService.mRootActivityContainer.sendPowerHintForLaunchEndIfNeeded();
}
+ // Once the target is shown, prevent spurious background app switches
+ if (reorderMode == REORDER_MOVE_TO_TOP) {
+ mService.stopAppSwitches();
+ }
+
mService.mH.post(
() -> mService.mAmInternal.setRunningRemoteAnimation(mCallingPid, false));
diff --git a/services/net/java/android/net/IpMemoryStoreClient.java b/services/net/java/android/net/IpMemoryStoreClient.java
index 3d56202..014b528 100644
--- a/services/net/java/android/net/IpMemoryStoreClient.java
+++ b/services/net/java/android/net/IpMemoryStoreClient.java
@@ -212,4 +212,16 @@
null, null, null));
}
}
+
+ /**
+ * Wipe the data in the database upon network factory reset.
+ */
+ public void factoryReset() {
+ try {
+ runWhenServiceReady(service -> ignoringRemoteException(
+ () -> service.factoryReset()));
+ } catch (ExecutionException m) {
+ Log.e(TAG, "Error executing factory reset", m);
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index fa1bcac..fd3678d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -423,9 +423,7 @@
assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
assertEquals(null, si.getIntent());
assertEquals(123, si.getRank());
- assertEquals("person", si.getPersons()[0].getName());
- assertEquals("personKey", si.getPersons()[0].getKey());
- assertEquals("personUri", si.getPersons()[0].getUri());
+ assertEquals(null, si.getPersons());
assertEquals(1, si.getExtras().getInt("k"));
assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_LONG_LIVED, si.getFlags());
@@ -455,6 +453,30 @@
assertEquals(456, si.getIconResourceId());
assertEquals(null, si.getIconResName());
+
+ si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_APP_PREDICTION);
+
+ assertEquals(mClientContext.getPackageName(), si.getPackage());
+ assertEquals("id", si.getId());
+ assertEquals(new ComponentName("a", "b"), si.getActivity());
+ assertEquals(null, si.getIcon());
+ assertEquals("title", si.getTitle());
+ assertEquals("text", si.getText());
+ assertEquals("dismes", si.getDisabledMessage());
+ assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getRank());
+ assertEquals("person", si.getPersons()[0].getName());
+ assertEquals("personKey", si.getPersons()[0].getKey());
+ assertEquals("personUri", si.getPersons()[0].getUri());
+ assertEquals(1, si.getExtras().getInt("k"));
+
+ assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_LONG_LIVED, si.getFlags());
+ assertEquals(null, si.getBitmapPath());
+
+ assertEquals(456, si.getIconResourceId());
+ assertEquals(null, si.getIconResName());
}
public void testShortcutInfoClone_resId() {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 87f221a..e75a30b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -22,7 +22,6 @@
import static android.app.Notification.CATEGORY_CALL;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
-import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
import static android.app.NotificationManager.EXTRA_BLOCKED_STATE;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
@@ -1660,11 +1659,14 @@
when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
eq(channel2.getId()), anyBoolean()))
.thenReturn(channel2);
+ when(mPreferencesHelper.createNotificationChannel(eq(PKG), anyInt(),
+ eq(channel2), anyBoolean(), anyBoolean()))
+ .thenReturn(true);
reset(mListeners);
mBinderService.createNotificationChannels(PKG,
new ParceledListSlice(Arrays.asList(mTestNotificationChannel, channel2)));
- verify(mListeners, times(1)).notifyNotificationChannelChanged(eq(PKG),
+ verify(mListeners, never()).notifyNotificationChannelChanged(eq(PKG),
eq(Process.myUserHandle()), eq(mTestNotificationChannel),
eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_ADDED));
verify(mListeners, times(1)).notifyNotificationChannelChanged(eq(PKG),
@@ -4963,13 +4965,10 @@
mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), false);
waitForIdle();
- // Make sure we are not a bubble / reported as such to listeners
- ArgumentCaptor<NotificationRecord> captor =
- ArgumentCaptor.forClass(NotificationRecord.class);
- verify(mListeners, times(1)).notifyPostedLocked(captor.capture(), any());
-
- assertEquals((captor.getValue().getNotification().flags & FLAG_BUBBLE), 0);
- assertTrue((captor.getValue().getNotification().flags & FLAG_ONLY_ALERT_ONCE) != 0);
+ // Make sure we are not a bubble
+ StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+ assertEquals(1, notifsAfter.length);
+ assertEquals((notifsAfter[0].getNotification().flags & FLAG_BUBBLE), 0);
}
@Test
@@ -5000,13 +4999,10 @@
mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), true);
waitForIdle();
- // Make sure we are a bubble / reported as such to listeners
- ArgumentCaptor<NotificationRecord> captor =
- ArgumentCaptor.forClass(NotificationRecord.class);
- verify(mListeners, times(1)).notifyPostedLocked(captor.capture(), any());
-
- assertTrue((captor.getValue().getNotification().flags & FLAG_BUBBLE) != 0);
- assertTrue((captor.getValue().getNotification().flags & FLAG_ONLY_ALERT_ONCE) != 0);
+ // Make sure we are a bubble
+ StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+ assertEquals(1, notifsAfter.length);
+ assertTrue((notifsAfter[0].getNotification().flags & FLAG_BUBBLE) != 0);
}
@Test
@@ -5037,7 +5033,6 @@
StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
assertEquals(1, notifsAfter.length);
assertEquals((notifsAfter[0].getNotification().flags & FLAG_BUBBLE), 0);
- verify(mListeners, times(0)).notifyPostedLocked(any(), any());
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index e22f827..8f8b746 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -262,13 +262,13 @@
int uid0 = 1001;
setUpPackageWithUid(package0, uid0);
NotificationChannel channel0 = new NotificationChannel("id0", "name0", IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(package0, uid0, channel0, true, false);
+ assertTrue(mHelper.createNotificationChannel(package0, uid0, channel0, true, false));
String package10 = "test.package.user10";
int uid10 = 1001001;
setUpPackageWithUid(package10, uid10);
NotificationChannel channel10 = new NotificationChannel("id10", "name10", IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(package10, uid10, channel10, true, false);
+ assertTrue(mHelper.createNotificationChannel(package10, uid10, channel10, true, false));
ByteArrayOutputStream baos = writeXmlAndPurge(package10, uid10, true, 10);
@@ -293,7 +293,7 @@
int uid0 = 1001;
setUpPackageWithUid(package0, uid0);
NotificationChannel channel0 = new NotificationChannel("id0", "name0", IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(package0, uid0, channel0, true, false);
+ assertTrue(mHelper.createNotificationChannel(package0, uid0, channel0, true, false));
ByteArrayOutputStream baos = writeXmlAndPurge(package0, uid0, true, 0);
@@ -334,8 +334,8 @@
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg2, true);
- mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false);
- mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, false, false);
+ assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false));
+ assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, false, false));
mHelper.setShowBadge(PKG_N_MR1, UID_N_MR1, true);
mHelper.setAppImportanceLocked(PKG_N_MR1, UID_N_MR1);
@@ -716,8 +716,8 @@
public void testCreateChannel_blocked() throws Exception {
mHelper.setImportance(PKG_N_MR1, UID_N_MR1, IMPORTANCE_NONE);
- mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1,
- new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false);
+ assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false));
}
@Test
@@ -746,10 +746,10 @@
} catch (IllegalArgumentException e) {
// yay
}
- mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1,
- new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE), true, false);
- mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1,
- new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX), true, false);
+ assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE), true, false));
+ assertFalse(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX), true, false));
}
@@ -763,7 +763,7 @@
channel.setBypassDnd(true);
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
- mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, false, false);
+ assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, false, false));
// same id, try to update all fields
final NotificationChannel channel2 =
@@ -776,7 +776,8 @@
mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, true);
// all fields should be changed
- assertEquals(channel2, mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), false));
+ assertEquals(channel2,
+ mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), false));
verify(mHandler, times(1)).requestSort();
}
@@ -894,7 +895,7 @@
}
channel.lockFields(lockMask);
- mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
+ assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false));
NotificationChannel savedChannel =
mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), false);
@@ -1469,13 +1470,18 @@
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
channel.setVibrationPattern(vibration);
- mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
+ assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false));
NotificationChannel newChannel = new NotificationChannel(
channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
newChannel.setVibrationPattern(new long[]{100});
+ newChannel.setAllowBubbles(!channel.canBubble());
+ newChannel.setLightColor(Color.BLUE);
+ newChannel.setSound(Uri.EMPTY, null);
+ newChannel.setShowBadge(!channel.canShowBadge());
- mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, newChannel, true, false);
+ assertFalse(mHelper.createNotificationChannel(
+ PKG_N_MR1, UID_N_MR1, newChannel, true, false));
// Old settings not overridden
compareChannels(channel,
@@ -1588,16 +1594,17 @@
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false);
- mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG_N_MR1}, new int[]{
- UID_N_MR1});
+ assertTrue(mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG_N_MR1},
+ new int[]{UID_N_MR1}));
- assertEquals(0, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, true).getList().size());
+ assertEquals(0, mHelper.getNotificationChannels(
+ PKG_N_MR1, UID_N_MR1, true).getList().size());
// Not deleted
mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false);
- mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{PKG_N_MR1}, new int[]{
- UID_N_MR1});
+ assertFalse(mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM,
+ new String[]{PKG_N_MR1}, new int[]{UID_N_MR1}));
assertEquals(2, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size());
}
@@ -1825,13 +1832,13 @@
@Test
public void testCreateChannel_updateName() {
NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
- mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false);
+ assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false));
NotificationChannel actual =
mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "id", false);
assertEquals("hello", actual.getName());
nc = new NotificationChannel("id", "goodbye", IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false);
+ assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false));
actual = mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "id", false);
assertEquals("goodbye", actual.getName());
@@ -1845,14 +1852,14 @@
NotificationChannelGroup group = new NotificationChannelGroup("group", "");
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true);
NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
- mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false);
+ assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false));
NotificationChannel actual =
mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "id", false);
assertNull(actual.getGroup());
nc = new NotificationChannel("id", "hello", IMPORTANCE_HIGH);
nc.setGroup(group.getId());
- mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false);
+ assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false));
actual = mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "id", false);
assertNotNull(actual.getGroup());
@@ -2109,7 +2116,7 @@
NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
update.setBypassDnd(true);
- mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, update, true, false);
+ assertFalse(mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, update, true, false));
assertFalse(mHelper.getNotificationChannel(SYSTEM_PKG, SYSTEM_UID, "A", false)
.canBypassDnd());
@@ -2122,7 +2129,7 @@
NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
update.setBypassDnd(true);
- mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, update, true, true);
+ assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, update, true, true));
assertTrue(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "A", false).canBypassDnd());
}
@@ -2676,4 +2683,11 @@
assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, channel1.getId(), false)
.isImportanceLockedByCriticalDeviceFunction());
}
+
+ @Test
+ public void testSetBubblesAllowed_false() {
+ mHelper.setBubblesAllowed(PKG_O, UID_O, false);
+ assertFalse(mHelper.areBubblesAllowed(PKG_O, UID_O));
+ verify(mHandler, times(1)).requestSort();
+ }
}
diff --git a/services/usb/java/com/android/server/usb/UsbSerialReader.java b/services/usb/java/com/android/server/usb/UsbSerialReader.java
index 32fc796..8ca77f0 100644
--- a/services/usb/java/com/android/server/usb/UsbSerialReader.java
+++ b/services/usb/java/com/android/server/usb/UsbSerialReader.java
@@ -85,22 +85,22 @@
throw new RemoteException("package " + packageName + " cannot be found");
}
packageTargetSdkVersion = pkg.applicationInfo.targetSdkVersion;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- if (packageTargetSdkVersion >= Build.VERSION_CODES.Q) {
- if (mContext.checkPermission(android.Manifest.permission.MANAGE_USB, pid, uid)
- == PackageManager.PERMISSION_DENIED) {
- UsbUserSettingsManager settings = mSettingsManager.getSettingsForUser(
- UserHandle.getUserId(uid));
+ if (packageTargetSdkVersion >= Build.VERSION_CODES.Q) {
+ if (mContext.checkPermission(android.Manifest.permission.MANAGE_USB, pid, uid)
+ == PackageManager.PERMISSION_DENIED) {
+ UsbUserSettingsManager settings = mSettingsManager.getSettingsForUser(
+ UserHandle.getUserId(uid));
- if (mDevice instanceof UsbDevice) {
- settings.checkPermission((UsbDevice) mDevice, packageName, uid);
- } else {
- settings.checkPermission((UsbAccessory) mDevice, uid);
+ if (mDevice instanceof UsbDevice) {
+ settings.checkPermission((UsbDevice) mDevice, packageName, uid);
+ } else {
+ settings.checkPermission((UsbAccessory) mDevice, uid);
+ }
}
}
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java
index 7b51457..511adf6 100644
--- a/telephony/java/android/telephony/emergency/EmergencyNumber.java
+++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java
@@ -299,7 +299,7 @@
* Get the dialing number of the emergency number.
*
* The character in the number string is only the dial pad
- * character('0'-'9', '*', or '#'). For example: 911.
+ * character('0'-'9', '*', '+', or '#'). For example: 911.
*
* @return the dialing number.
*/
diff --git a/tests/net/java/android/net/IpMemoryStoreTest.java b/tests/net/java/android/net/IpMemoryStoreTest.java
index 6e69b34..b81ca36 100644
--- a/tests/net/java/android/net/IpMemoryStoreTest.java
+++ b/tests/net/java/android/net/IpMemoryStoreTest.java
@@ -321,4 +321,11 @@
eq(TEST_OTHER_DATA_NAME), any());
assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
}
+
+ @Test
+ public void testFactoryReset() throws RemoteException {
+ startIpMemoryStore(true /* supplyService */);
+ mStore.factoryReset();
+ verify(mMockService, times(1)).factoryReset();
+ }
}
diff --git a/tests/net/java/android/net/util/DnsUtilsTest.java b/tests/net/java/android/net/util/DnsUtilsTest.java
new file mode 100644
index 0000000..42e340b
--- /dev/null
+++ b/tests/net/java/android/net/util/DnsUtilsTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.util;
+
+import static android.net.util.DnsUtils.IPV6_ADDR_SCOPE_GLOBAL;
+import static android.net.util.DnsUtils.IPV6_ADDR_SCOPE_LINKLOCAL;
+import static android.net.util.DnsUtils.IPV6_ADDR_SCOPE_SITELOCAL;
+
+import static org.junit.Assert.assertEquals;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.InetAddresses;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DnsUtilsTest {
+ private InetAddress stringToAddress(@NonNull String addr) {
+ return InetAddresses.parseNumericAddress(addr);
+ }
+
+ private DnsUtils.SortableAddress makeSortableAddress(@NonNull String addr) {
+ return makeSortableAddress(addr, null);
+ }
+
+ private DnsUtils.SortableAddress makeSortableAddress(@NonNull String addr,
+ @Nullable String srcAddr) {
+ return new DnsUtils.SortableAddress(stringToAddress(addr),
+ srcAddr != null ? stringToAddress(srcAddr) : null);
+ }
+
+ @Test
+ public void testRfc6724Comparator() {
+ final List<DnsUtils.SortableAddress> test = Arrays.asList(
+ makeSortableAddress("216.58.200.36"), // Ipv4
+ makeSortableAddress("2404:6800:4008:801::2004"), // global
+ makeSortableAddress("::1"), // loop back
+ makeSortableAddress("fe80::c46f:1cff:fe04:39b4"), // link local
+ makeSortableAddress("::ffff:192.168.95.3"), // IPv4-mapped IPv6
+ makeSortableAddress("2001::47c1"), // teredo tunneling
+ makeSortableAddress("::216.58.200.36"), // IPv4-compatible
+ makeSortableAddress("3ffe::1234:5678")); // 6bone
+
+ final List<InetAddress> expected = Arrays.asList(
+ stringToAddress("::1"), // loop back
+ stringToAddress("fe80::c46f:1cff:fe04:39b4"), // link local
+ stringToAddress("2404:6800:4008:801::2004"), // global
+ stringToAddress("216.58.200.36"), // Ipv4
+ stringToAddress("::ffff:192.168.95.3"), // IPv4-mapped IPv6
+ stringToAddress("2001::47c1"), // teredo tunneling
+ stringToAddress("::216.58.200.36"), // IPv4-compatible
+ stringToAddress("3ffe::1234:5678")); // 6bone
+
+ Collections.sort(test, new DnsUtils.Rfc6724Comparator());
+
+ for (int i = 0; i < test.size(); ++i) {
+ assertEquals(test.get(i).address, expected.get(i));
+ }
+
+ // TODO: add more combinations
+ }
+
+ @Test
+ public void testV4SortableAddress() {
+ // Test V4 address
+ DnsUtils.SortableAddress test = makeSortableAddress("216.58.200.36");
+ assertEquals(test.hasSrcAddr, 0);
+ assertEquals(test.prefixMatchLen, 0);
+ assertEquals(test.address, stringToAddress("216.58.200.36"));
+ assertEquals(test.labelMatch, 0);
+ assertEquals(test.scopeMatch, 0);
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.label, 4);
+ assertEquals(test.precedence, 35);
+
+ // Test V4 loopback address with the same source address
+ test = makeSortableAddress("127.1.2.3", "127.1.2.3");
+ assertEquals(test.hasSrcAddr, 1);
+ assertEquals(test.prefixMatchLen, 0);
+ assertEquals(test.address, stringToAddress("127.1.2.3"));
+ assertEquals(test.labelMatch, 1);
+ assertEquals(test.scopeMatch, 1);
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL);
+ assertEquals(test.label, 4);
+ assertEquals(test.precedence, 35);
+ }
+
+ @Test
+ public void testV6SortableAddress() {
+ // Test global address
+ DnsUtils.SortableAddress test = makeSortableAddress("2404:6800:4008:801::2004");
+ assertEquals(test.address, stringToAddress("2404:6800:4008:801::2004"));
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.label, 1);
+ assertEquals(test.precedence, 40);
+
+ // Test global address with global source address
+ test = makeSortableAddress("2404:6800:4008:801::2004",
+ "2401:fa00:fc:fd00:6d6c:7199:b8e7:41d6");
+ assertEquals(test.address, stringToAddress("2404:6800:4008:801::2004"));
+ assertEquals(test.hasSrcAddr, 1);
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.labelMatch, 1);
+ assertEquals(test.scopeMatch, 1);
+ assertEquals(test.label, 1);
+ assertEquals(test.precedence, 40);
+ assertEquals(test.prefixMatchLen, 13);
+
+ // Test global address with linklocal source address
+ test = makeSortableAddress("2404:6800:4008:801::2004", "fe80::c46f:1cff:fe04:39b4");
+ assertEquals(test.hasSrcAddr, 1);
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.labelMatch, 1);
+ assertEquals(test.scopeMatch, 0);
+ assertEquals(test.label, 1);
+ assertEquals(test.precedence, 40);
+ assertEquals(test.prefixMatchLen, 0);
+
+ // Test loopback address with the same source address
+ test = makeSortableAddress("::1", "::1");
+ assertEquals(test.hasSrcAddr, 1);
+ assertEquals(test.prefixMatchLen, 16 * 8);
+ assertEquals(test.labelMatch, 1);
+ assertEquals(test.scopeMatch, 1);
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL);
+ assertEquals(test.label, 0);
+ assertEquals(test.precedence, 50);
+
+ // Test linklocal address
+ test = makeSortableAddress("fe80::c46f:1cff:fe04:39b4");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL);
+ assertEquals(test.label, 1);
+ assertEquals(test.precedence, 40);
+
+ // Test linklocal address
+ test = makeSortableAddress("fe80::");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL);
+ assertEquals(test.label, 1);
+ assertEquals(test.precedence, 40);
+
+ // Test 6to4 address
+ test = makeSortableAddress("2002:c000:0204::");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.label, 2);
+ assertEquals(test.precedence, 30);
+
+ // Test unique local address
+ test = makeSortableAddress("fc00::c000:13ab");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.label, 13);
+ assertEquals(test.precedence, 3);
+
+ // Test teredo tunneling address
+ test = makeSortableAddress("2001::47c1");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.label, 5);
+ assertEquals(test.precedence, 5);
+
+ // Test IPv4-compatible addresses
+ test = makeSortableAddress("::216.58.200.36");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.label, 3);
+ assertEquals(test.precedence, 1);
+
+ // Test site-local address
+ test = makeSortableAddress("fec0::cafe:3ab2");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_SITELOCAL);
+ assertEquals(test.label, 11);
+ assertEquals(test.precedence, 1);
+
+ // Test 6bone address
+ test = makeSortableAddress("3ffe::1234:5678");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.label, 12);
+ assertEquals(test.precedence, 1);
+ }
+}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 6560f58..8c024a5 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -615,7 +615,7 @@
private void onValidationRequested() {
try {
if (mNmProvNotificationRequested
- && mNmValidationResult == VALIDATION_RESULT_VALID) {
+ && ((mNmValidationResult & NETWORK_VALIDATION_RESULT_VALID) != 0)) {
mNmCallbacks.hideProvisioningNotification();
mNmProvNotificationRequested = false;
}
@@ -2781,10 +2781,10 @@
// Bring up a network with a captive portal.
// Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
- String firstRedirectUrl = "http://example.com/firstPath";
- mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl);
+ String redirectUrl = "http://android.com/path";
+ mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl);
captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
- assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), firstRedirectUrl);
+ assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), redirectUrl);
// Check that startCaptivePortalApp sends the expected command to NetworkMonitor.
mCm.startCaptivePortalApp(mWiFiNetworkAgent.getNetwork());
@@ -2794,7 +2794,7 @@
// Report that the captive portal is dismissed with partial connectivity, and check that
// callbacks are fired.
mWiFiNetworkAgent.setNetworkPartial();
- mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+ mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
waitForIdle();
captivePortalCallback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY,
mWiFiNetworkAgent);
@@ -2805,6 +2805,7 @@
false /* always */);
waitForIdle();
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
+ captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
NetworkCapabilities nc =
validatedCallback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY,
diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
index df1f57f..cd2bd26 100644
--- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -65,6 +65,7 @@
import android.os.UserManager;
import android.util.SparseIntArray;
+import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -96,6 +97,7 @@
private static final int SYSTEM_UID1 = 1000;
private static final int SYSTEM_UID2 = 1008;
private static final int VPN_UID = 10002;
+ private static final String REAL_SYSTEM_PACKAGE_NAME = "android";
private static final String MOCK_PACKAGE1 = "appName1";
private static final String MOCK_PACKAGE2 = "appName2";
private static final String SYSTEM_PACKAGE1 = "sysName1";
@@ -188,8 +190,10 @@
private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid, int userId) {
final PackageInfo pkgInfo;
if (hasSystemPermission) {
- pkgInfo = packageInfoWithPermissions(new String[] {CHANGE_NETWORK_STATE, NETWORK_STACK},
- PARTITION_SYSTEM);
+ final String[] systemPermissions = new String[]{
+ CHANGE_NETWORK_STATE, NETWORK_STACK, CONNECTIVITY_USE_RESTRICTED_NETWORKS
+ };
+ pkgInfo = packageInfoWithPermissions(systemPermissions, PARTITION_SYSTEM);
} else {
pkgInfo = packageInfoWithPermissions(new String[] {}, "");
}
@@ -646,4 +650,16 @@
mObserver.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1});
}
+
+ @Test
+ public void testRealSystemPermission() throws Exception {
+ // Use the real context as this test must ensure the *real* system package holds the
+ // necessary permission.
+ final Context realContext = InstrumentationRegistry.getContext();
+ final PermissionMonitor monitor = new PermissionMonitor(realContext, mNetdService);
+ final PackageManager manager = realContext.getPackageManager();
+ final PackageInfo systemInfo = manager.getPackageInfo(REAL_SYSTEM_PACKAGE_NAME,
+ GET_PERMISSIONS | MATCH_ANY_USER);
+ assertTrue(monitor.hasPermission(systemInfo, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
+ }
}