Merge "Uncomment a test."
diff --git a/api/current.txt b/api/current.txt
index 8f969c1..369e571 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -21818,23 +21818,23 @@
}
public final class MediaCas {
- ctor public MediaCas(int) throws android.media.UnsupportedCasException;
+ ctor public MediaCas(int) throws android.media.MediaCasException.UnsupportedCasException;
method public void closeSession(byte[]);
method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
method public static boolean isSystemIdSupported(int);
- method public byte[] openSession(int);
- method public byte[] openSession(int, int);
- method public void processEcm(byte[], byte[], int, int);
- method public void processEcm(byte[], byte[]);
- method public void processEmm(byte[], int, int);
- method public void processEmm(byte[]);
- method public void provision(java.lang.String);
- method public void refreshEntitlements(int, byte[]);
+ method public byte[] openSession(int) throws android.media.MediaCasException;
+ method public byte[] openSession(int, int) throws android.media.MediaCasException;
+ method public void processEcm(byte[], byte[], int, int) throws android.media.MediaCasException;
+ method public void processEcm(byte[], byte[]) throws android.media.MediaCasException;
+ method public void processEmm(byte[], int, int) throws android.media.MediaCasException;
+ method public void processEmm(byte[]) throws android.media.MediaCasException;
+ method public void provision(java.lang.String) throws android.media.MediaCasException;
+ method public void refreshEntitlements(int, byte[]) throws android.media.MediaCasException;
method public void release();
- method public void sendEvent(int, int, byte[]);
+ method public void sendEvent(int, int, byte[]) throws android.media.MediaCasException;
method public void setEventListener(android.media.MediaCas.EventListener, android.os.Handler);
- method public void setPrivateData(byte[]);
- method public void setSessionPrivateData(byte[], byte[]);
+ method public void setPrivateData(byte[]) throws android.media.MediaCasException;
+ method public void setSessionPrivateData(byte[], byte[]) throws android.media.MediaCasException;
}
public static abstract interface MediaCas.EventListener {
@@ -21847,7 +21847,22 @@
}
public class MediaCasException extends java.lang.Exception {
- ctor public MediaCasException(java.lang.String);
+ }
+
+ public static final class MediaCasException.DeniedByServerException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.NotProvisionedException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.ResourceBusyException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.UnsupportedCasException extends android.media.MediaCasException {
+ }
+
+ public class MediaCasStateException extends java.lang.IllegalStateException {
+ method public java.lang.String getDiagnosticInfo();
}
public final class MediaCodec {
@@ -22275,7 +22290,7 @@
}
public final class MediaDescrambler {
- ctor public MediaDescrambler(int) throws android.media.UnsupportedCasException;
+ ctor public MediaDescrambler(int) throws android.media.MediaCasException.UnsupportedCasException;
method public final int descramble(java.nio.ByteBuffer, int, java.nio.ByteBuffer, int, android.media.MediaCodec.CryptoInfo);
method public final void release();
method public final boolean requiresSecureDecoderComponent(java.lang.String);
@@ -23586,10 +23601,6 @@
field public static final int TONE_SUP_RINGTONE = 23; // 0x17
}
- public final class UnsupportedCasException extends android.media.MediaCasException {
- ctor public UnsupportedCasException(java.lang.String);
- }
-
public final class UnsupportedSchemeException extends android.media.MediaDrmException {
ctor public UnsupportedSchemeException(java.lang.String);
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 48b878e..95d6897 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -93,6 +93,7 @@
field public static final java.lang.String CLEAR_APP_CACHE = "android.permission.CLEAR_APP_CACHE";
field public static final java.lang.String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA";
field public static final java.lang.String CONNECTIVITY_INTERNAL = "android.permission.CONNECTIVITY_INTERNAL";
+ field public static final java.lang.String CONNECTIVITY_USE_RESTRICTED_NETWORKS = "android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS";
field public static final java.lang.String CONTROL_INCALL_EXPERIENCE = "android.permission.CONTROL_INCALL_EXPERIENCE";
field public static final java.lang.String CONTROL_LOCATION_UPDATES = "android.permission.CONTROL_LOCATION_UPDATES";
field public static final java.lang.String CONTROL_VPN = "android.permission.CONTROL_VPN";
@@ -23604,23 +23605,23 @@
}
public final class MediaCas {
- ctor public MediaCas(int) throws android.media.UnsupportedCasException;
+ ctor public MediaCas(int) throws android.media.MediaCasException.UnsupportedCasException;
method public void closeSession(byte[]);
method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
method public static boolean isSystemIdSupported(int);
- method public byte[] openSession(int);
- method public byte[] openSession(int, int);
- method public void processEcm(byte[], byte[], int, int);
- method public void processEcm(byte[], byte[]);
- method public void processEmm(byte[], int, int);
- method public void processEmm(byte[]);
- method public void provision(java.lang.String);
- method public void refreshEntitlements(int, byte[]);
+ method public byte[] openSession(int) throws android.media.MediaCasException;
+ method public byte[] openSession(int, int) throws android.media.MediaCasException;
+ method public void processEcm(byte[], byte[], int, int) throws android.media.MediaCasException;
+ method public void processEcm(byte[], byte[]) throws android.media.MediaCasException;
+ method public void processEmm(byte[], int, int) throws android.media.MediaCasException;
+ method public void processEmm(byte[]) throws android.media.MediaCasException;
+ method public void provision(java.lang.String) throws android.media.MediaCasException;
+ method public void refreshEntitlements(int, byte[]) throws android.media.MediaCasException;
method public void release();
- method public void sendEvent(int, int, byte[]);
+ method public void sendEvent(int, int, byte[]) throws android.media.MediaCasException;
method public void setEventListener(android.media.MediaCas.EventListener, android.os.Handler);
- method public void setPrivateData(byte[]);
- method public void setSessionPrivateData(byte[], byte[]);
+ method public void setPrivateData(byte[]) throws android.media.MediaCasException;
+ method public void setSessionPrivateData(byte[], byte[]) throws android.media.MediaCasException;
}
public static abstract interface MediaCas.EventListener {
@@ -23633,7 +23634,22 @@
}
public class MediaCasException extends java.lang.Exception {
- ctor public MediaCasException(java.lang.String);
+ }
+
+ public static final class MediaCasException.DeniedByServerException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.NotProvisionedException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.ResourceBusyException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.UnsupportedCasException extends android.media.MediaCasException {
+ }
+
+ public class MediaCasStateException extends java.lang.IllegalStateException {
+ method public java.lang.String getDiagnosticInfo();
}
public final class MediaCodec {
@@ -24061,7 +24077,7 @@
}
public final class MediaDescrambler {
- ctor public MediaDescrambler(int) throws android.media.UnsupportedCasException;
+ ctor public MediaDescrambler(int) throws android.media.MediaCasException.UnsupportedCasException;
method public final int descramble(java.nio.ByteBuffer, int, java.nio.ByteBuffer, int, android.media.MediaCodec.CryptoInfo);
method public final void release();
method public final boolean requiresSecureDecoderComponent(java.lang.String);
@@ -25383,10 +25399,6 @@
field public static final int TONE_SUP_RINGTONE = 23; // 0x17
}
- public final class UnsupportedCasException extends android.media.MediaCasException {
- ctor public UnsupportedCasException(java.lang.String);
- }
-
public final class UnsupportedSchemeException extends android.media.MediaDrmException {
ctor public UnsupportedSchemeException(java.lang.String);
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 4d8d7f2..a436d1f 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -21929,23 +21929,23 @@
}
public final class MediaCas {
- ctor public MediaCas(int) throws android.media.UnsupportedCasException;
+ ctor public MediaCas(int) throws android.media.MediaCasException.UnsupportedCasException;
method public void closeSession(byte[]);
method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
method public static boolean isSystemIdSupported(int);
- method public byte[] openSession(int);
- method public byte[] openSession(int, int);
- method public void processEcm(byte[], byte[], int, int);
- method public void processEcm(byte[], byte[]);
- method public void processEmm(byte[], int, int);
- method public void processEmm(byte[]);
- method public void provision(java.lang.String);
- method public void refreshEntitlements(int, byte[]);
+ method public byte[] openSession(int) throws android.media.MediaCasException;
+ method public byte[] openSession(int, int) throws android.media.MediaCasException;
+ method public void processEcm(byte[], byte[], int, int) throws android.media.MediaCasException;
+ method public void processEcm(byte[], byte[]) throws android.media.MediaCasException;
+ method public void processEmm(byte[], int, int) throws android.media.MediaCasException;
+ method public void processEmm(byte[]) throws android.media.MediaCasException;
+ method public void provision(java.lang.String) throws android.media.MediaCasException;
+ method public void refreshEntitlements(int, byte[]) throws android.media.MediaCasException;
method public void release();
- method public void sendEvent(int, int, byte[]);
+ method public void sendEvent(int, int, byte[]) throws android.media.MediaCasException;
method public void setEventListener(android.media.MediaCas.EventListener, android.os.Handler);
- method public void setPrivateData(byte[]);
- method public void setSessionPrivateData(byte[], byte[]);
+ method public void setPrivateData(byte[]) throws android.media.MediaCasException;
+ method public void setSessionPrivateData(byte[], byte[]) throws android.media.MediaCasException;
}
public static abstract interface MediaCas.EventListener {
@@ -21958,7 +21958,22 @@
}
public class MediaCasException extends java.lang.Exception {
- ctor public MediaCasException(java.lang.String);
+ }
+
+ public static final class MediaCasException.DeniedByServerException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.NotProvisionedException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.ResourceBusyException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.UnsupportedCasException extends android.media.MediaCasException {
+ }
+
+ public class MediaCasStateException extends java.lang.IllegalStateException {
+ method public java.lang.String getDiagnosticInfo();
}
public final class MediaCodec {
@@ -22386,7 +22401,7 @@
}
public final class MediaDescrambler {
- ctor public MediaDescrambler(int) throws android.media.UnsupportedCasException;
+ ctor public MediaDescrambler(int) throws android.media.MediaCasException.UnsupportedCasException;
method public final int descramble(java.nio.ByteBuffer, int, java.nio.ByteBuffer, int, android.media.MediaCodec.CryptoInfo);
method public final void release();
method public final boolean requiresSecureDecoderComponent(java.lang.String);
@@ -23697,10 +23712,6 @@
field public static final int TONE_SUP_RINGTONE = 23; // 0x17
}
- public final class UnsupportedCasException extends android.media.MediaCasException {
- ctor public UnsupportedCasException(java.lang.String);
- }
-
public final class UnsupportedSchemeException extends android.media.MediaDrmException {
ctor public UnsupportedSchemeException(java.lang.String);
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 124749a..f719749 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -93,6 +93,7 @@
import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.Build;
+import android.os.Debug;
import android.os.DropBoxManager;
import android.os.HardwarePropertiesManager;
import android.os.IBatteryPropertiesRegistrar;
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index bb844a3..919f4ba 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -27,6 +27,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* A request for the user to select a companion device to associate with.
@@ -69,6 +70,20 @@
}
@Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ AssociationRequest that = (AssociationRequest) o;
+ return mSingleDevice == that.mSingleDevice &&
+ Objects.equals(mDeviceFilters, that.mDeviceFilters);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSingleDevice, mDeviceFilters);
+ }
+
+ @Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeByte((byte) (mSingleDevice ? 1 : 0));
dest.writeParcelableList(mDeviceFilters, flags);
diff --git a/core/java/android/companion/BluetoothDeviceFilter.java b/core/java/android/companion/BluetoothDeviceFilter.java
index 1d8df7f..84e1536 100644
--- a/core/java/android/companion/BluetoothDeviceFilter.java
+++ b/core/java/android/companion/BluetoothDeviceFilter.java
@@ -35,6 +35,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.regex.Pattern;
/**
@@ -123,6 +124,22 @@
}
@Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ BluetoothDeviceFilter that = (BluetoothDeviceFilter) o;
+ return Objects.equals(mNamePattern, that.mNamePattern) &&
+ Objects.equals(mAddress, that.mAddress) &&
+ Objects.equals(mServiceUuids, that.mServiceUuids) &&
+ Objects.equals(mServiceUuidMasks, that.mServiceUuidMasks);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mNamePattern, mAddress, mServiceUuids, mServiceUuidMasks);
+ }
+
+ @Override
public int describeContents() {
return 0;
}
diff --git a/core/java/android/companion/BluetoothLEDeviceFilter.java b/core/java/android/companion/BluetoothLEDeviceFilter.java
index e057fbc..0444775 100644
--- a/core/java/android/companion/BluetoothLEDeviceFilter.java
+++ b/core/java/android/companion/BluetoothLEDeviceFilter.java
@@ -36,6 +36,8 @@
import com.android.internal.util.ObjectUtils;
import com.android.internal.util.Preconditions;
+import java.util.Arrays;
+import java.util.Objects;
import java.util.regex.Pattern;
/**
@@ -160,9 +162,39 @@
}
@Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ BluetoothLEDeviceFilter that = (BluetoothLEDeviceFilter) o;
+ return mRenameBytesFrom == that.mRenameBytesFrom &&
+ mRenameBytesTo == that.mRenameBytesTo &&
+ mRenameBytesReverseOrder == that.mRenameBytesReverseOrder &&
+ Objects.equals(mNamePattern, that.mNamePattern) &&
+ Objects.equals(mScanFilter, that.mScanFilter) &&
+ Arrays.equals(mRawDataFilter, that.mRawDataFilter) &&
+ Arrays.equals(mRawDataFilterMask, that.mRawDataFilterMask) &&
+ Objects.equals(mRenamePrefix, that.mRenamePrefix) &&
+ Objects.equals(mRenameSuffix, that.mRenameSuffix);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mNamePattern, mScanFilter, mRawDataFilter, mRawDataFilterMask,
+ mRenamePrefix, mRenameSuffix, mRenameBytesFrom, mRenameBytesTo,
+ mRenameBytesReverseOrder);
+ }
+
+ @Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(patternToString(getNamePattern()));
dest.writeParcelable(mScanFilter, flags);
+ dest.writeByteArray(mRawDataFilter);
+ dest.writeByteArray(mRawDataFilterMask);
+ dest.writeString(mRenamePrefix);
+ dest.writeString(mRenameSuffix);
+ dest.writeInt(mRenameBytesFrom);
+ dest.writeInt(mRenameBytesTo);
+ dest.writeBoolean(mRenameBytesReverseOrder);
}
@Override
@@ -174,13 +206,23 @@
= new Creator<BluetoothLEDeviceFilter>() {
@Override
public BluetoothLEDeviceFilter createFromParcel(Parcel in) {
- return new BluetoothLEDeviceFilter.Builder()
+ Builder builder = new Builder()
.setNamePattern(patternFromString(in.readString()))
- .setScanFilter(in.readParcelable(null))
- .setRawDataFilter(in.readBlob(), in.readBlob())
- .setRename(in.readString(), in.readString(),
- in.readInt(), in.readInt(), in.readBoolean())
- .build();
+ .setScanFilter(in.readParcelable(null));
+ byte[] rawDataFilter = in.createByteArray();
+ byte[] rawDataFilterMask = in.createByteArray();
+ if (rawDataFilter != null) {
+ builder.setRawDataFilter(rawDataFilter, rawDataFilterMask);
+ }
+ String renamePrefix = in.readString();
+ String suffix = in.readString();
+ int bytesFrom = in.readInt();
+ int bytesTo = in.readInt();
+ boolean bytesReverseOrder = in.readBoolean();
+ if (renamePrefix != null) {
+ builder.setRename(renamePrefix, suffix, bytesFrom, bytesTo, bytesReverseOrder);
+ }
+ return builder.build();
}
@Override
@@ -240,12 +282,14 @@
*/
@NonNull
public Builder setRawDataFilter(@NonNull byte[] rawDataFilter,
- @NonNull byte[] rawDataFilterMask) {
+ @Nullable byte[] rawDataFilterMask) {
checkNotUsed();
- checkArgument(rawDataFilter.length == rawDataFilterMask.length,
+ Preconditions.checkNotNull(rawDataFilter);
+ checkArgument(rawDataFilterMask == null ||
+ rawDataFilter.length == rawDataFilterMask.length,
"Mask and filter should be the same length");
- mRawDataFilter = Preconditions.checkNotNull(rawDataFilter);
- mRawDataFilterMask = Preconditions.checkNotNull(rawDataFilterMask);
+ mRawDataFilter = rawDataFilter;
+ mRawDataFilterMask = rawDataFilterMask;
return this;
}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 78e3de4..7b38863 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -17,6 +17,8 @@
package android.companion;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
@@ -24,7 +26,6 @@
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.os.Handler;
-import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
@@ -43,7 +44,7 @@
*/
public final class CompanionDeviceManager {
- private static final boolean DEBUG = false; //TODO
+ private static final boolean DEBUG = false;
private static final String LOG_TAG = "CompanionDeviceManager";
/**
@@ -129,10 +130,9 @@
if (!checkFeaturePresent()) {
return;
}
-
- final Handler finalHandler = handler != null
- ? handler
- : new Handler(Looper.getMainLooper());
+ checkNotNull(request, "Request cannot be null");
+ checkNotNull(callback, "Callback cannot be null");
+ final Handler finalHandler = Handler.mainIfNull(handler);
try {
mService.associate(
request,
diff --git a/core/java/android/companion/WifiDeviceFilter.java b/core/java/android/companion/WifiDeviceFilter.java
index 1ab9ce1..b6e704c 100644
--- a/core/java/android/companion/WifiDeviceFilter.java
+++ b/core/java/android/companion/WifiDeviceFilter.java
@@ -29,6 +29,7 @@
import android.os.Parcel;
import android.provider.OneTimeUseBuilder;
+import java.util.Objects;
import java.util.regex.Pattern;
/**
@@ -75,6 +76,19 @@
}
@Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ WifiDeviceFilter that = (WifiDeviceFilter) o;
+ return Objects.equals(mNamePattern, that.mNamePattern);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mNamePattern);
+ }
+
+ @Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(patternToString(getNamePattern()));
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 1fafe65..d264e09 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -4400,8 +4400,12 @@
defaultMaxAspectRatio = owner.applicationInfo.maxAspectRatio;
}
- aInfo.maxAspectRatio = Math.max(1.0f, sa.getFloat(
- R.styleable.AndroidManifestActivity_maxAspectRatio, defaultMaxAspectRatio));
+ aInfo.maxAspectRatio = sa.getFloat(
+ R.styleable.AndroidManifestActivity_maxAspectRatio, defaultMaxAspectRatio);
+ if (aInfo.maxAspectRatio < 1.0f && aInfo.maxAspectRatio != 0) {
+ // Ignore any value lesser than 1.0.
+ aInfo.maxAspectRatio = 0;
+ }
}
/**
diff --git a/core/java/android/metrics/LogMaker.java b/core/java/android/metrics/LogMaker.java
index 60b27b4..0448221 100644
--- a/core/java/android/metrics/LogMaker.java
+++ b/core/java/android/metrics/LogMaker.java
@@ -16,6 +16,7 @@
package android.metrics;
import android.annotation.SystemApi;
+import android.content.ComponentName;
import android.util.Log;
import android.util.SparseArray;
@@ -118,6 +119,16 @@
return this;
}
+ /**
+ * @param component to replace the existing setting.
+ * @hide
+ */
+ public LogMaker setComponentName(ComponentName component) {
+ entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME, component.getPackageName());
+ entries.put(MetricsEvent.FIELD_CLASS_NAME, component.getClassName());
+ return this;
+ }
+
/** Remove the package name property. */
public LogMaker clearPackageName() {
entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME);
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 3c7c962..8678d95 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -16,6 +16,8 @@
package android.os;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.util.Log;
import android.util.Printer;
@@ -69,6 +71,7 @@
*/
private static final boolean FIND_POTENTIAL_LEAKS = false;
private static final String TAG = "Handler";
+ private static Handler MAIN_THREAD_HANDLER = null;
/**
* Callback interface you can use when instantiating a Handler to avoid
@@ -231,6 +234,21 @@
mAsynchronous = async;
}
+ /** @hide */
+ @NonNull
+ public static Handler getMain() {
+ if (MAIN_THREAD_HANDLER == null) {
+ MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper());
+ }
+ return MAIN_THREAD_HANDLER;
+ }
+
+ /** @hide */
+ @NonNull
+ public static Handler mainIfNull(@Nullable Handler handler) {
+ return handler == null ? getMain() : handler;
+ }
+
/** {@hide} */
public String getTraceName(Message message) {
final StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/util/ExceptionUtils.java b/core/java/android/util/ExceptionUtils.java
index 87231e1..44019c32 100644
--- a/core/java/android/util/ExceptionUtils.java
+++ b/core/java/android/util/ExceptionUtils.java
@@ -17,6 +17,7 @@
package android.util;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.ParcelableException;
import com.android.internal.util.Preconditions;
@@ -55,10 +56,26 @@
return getCompleteMessage(null, t);
}
+ public static <E extends Throwable> void propagateIfInstanceOf(
+ @Nullable Throwable t, Class<E> c) throws E {
+ if (t != null && c.isInstance(t)) {
+ throw c.cast(t);
+ }
+ }
+
+ /**
+ * @param <E> a checked exception that is ok to throw without wrapping
+ */
+ public static <E extends Exception> RuntimeException propagate(@NonNull Throwable t, Class<E> c)
+ throws E {
+ propagateIfInstanceOf(t, c);
+ return propagate(t);
+ }
+
public static RuntimeException propagate(@NonNull Throwable t) {
Preconditions.checkNotNull(t);
- if (t instanceof Error) throw (Error)t;
- if (t instanceof RuntimeException) throw (RuntimeException)t;
+ propagateIfInstanceOf(t, Error.class);
+ propagateIfInstanceOf(t, RuntimeException.class);
throw new RuntimeException(t);
}
}
diff --git a/core/java/android/view/textclassifier/SmartSelection.java b/core/java/android/view/textclassifier/SmartSelection.java
index 9397a41..c68c449 100644
--- a/core/java/android/view/textclassifier/SmartSelection.java
+++ b/core/java/android/view/textclassifier/SmartSelection.java
@@ -26,6 +26,11 @@
System.loadLibrary("textclassifier");
}
+ /** Hints the classifier that this may be a url. */
+ static final int HINT_FLAG_URL = 0x01;
+ /** Hints the classifier that this may be an email. */
+ static final int HINT_FLAG_EMAIL = 0x02;
+
private final long mCtx;
/**
@@ -59,7 +64,7 @@
* scores for different collections.
*/
public ClassificationResult[] classifyText(
- String context, int selectionBegin, int selectionEnd) {
+ String context, int selectionBegin, int selectionEnd, int hintFlags) {
return nativeClassifyText(mCtx, context, selectionBegin, selectionEnd);
}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index be12f57..66a62c3 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -35,6 +35,7 @@
import android.text.style.ClickableSpan;
import android.text.util.Linkify;
import android.util.Log;
+import android.util.Patterns;
import android.view.View;
import com.android.internal.util.Preconditions;
@@ -88,7 +89,9 @@
if (start >= 0 && end <= string.length() && start <= end) {
final TextSelection.Builder tsBuilder = new TextSelection.Builder(start, end);
final SmartSelection.ClassificationResult[] results =
- getSmartSelection().classifyText(string, start, end);
+ getSmartSelection().classifyText(
+ string, start, end,
+ getHintFlags(string, start, end));
final int size = results.length;
for (int i = 0; i < size; i++) {
tsBuilder.setEntityType(results[i].mCollection, results[i].mScore);
@@ -116,12 +119,14 @@
validateInput(text, startIndex, endIndex);
try {
if (text.length() > 0) {
- final CharSequence classified = text.subSequence(startIndex, endIndex);
+ final String string = text.toString();
SmartSelection.ClassificationResult[] results = getSmartSelection()
- .classifyText(text.toString(), startIndex, endIndex);
+ .classifyText(string, startIndex, endIndex,
+ getHintFlags(string, startIndex, endIndex));
if (results.length > 0) {
final TextClassificationResult classificationResult =
- createClassificationResult(results, classified);
+ createClassificationResult(
+ results, string.subSequence(startIndex, endIndex));
// TODO: Added this log for debug only. Remove before release.
Log.d(LOG_TAG, String.format(
"Classification type: %s", classificationResult));
@@ -208,6 +213,24 @@
return builder.build();
}
+ private static int getHintFlags(CharSequence text, int start, int end) {
+ int flag = 0;
+ final CharSequence subText = text.subSequence(start, end);
+ if (Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(subText).matches()) {
+ flag |= SmartSelection.HINT_FLAG_EMAIL;
+ }
+ if (Patterns.AUTOLINK_WEB_URL.matcher(subText).matches()
+ && Linkify.sUrlMatchFilter.acceptMatch(text, start, end)) {
+ flag |= SmartSelection.HINT_FLAG_URL;
+ }
+ // TODO: Added this log for debug only. Remove before release.
+ Log.d(LOG_TAG, String.format("Email hint: %b",
+ (flag & SmartSelection.HINT_FLAG_EMAIL) != 0));
+ Log.d(LOG_TAG, String.format("Url hint: %b",
+ (flag & SmartSelection.HINT_FLAG_URL) != 0));
+ return flag;
+ }
+
private static String getHighestScoringType(SmartSelection.ClassificationResult[] types) {
if (types.length < 1) {
return "";
@@ -262,7 +285,9 @@
if (selectionStart >= 0 && selectionEnd <= text.length()
&& selectionStart <= selectionEnd) {
final SmartSelection.ClassificationResult[] results =
- smartSelection.classifyText(text, selectionStart, selectionEnd);
+ smartSelection.classifyText(
+ text, selectionStart, selectionEnd,
+ getHintFlags(text, selectionStart, selectionEnd));
if (results.length > 0) {
final String type = getHighestScoringType(results);
if (matches(type, linkMask)) {
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index b63b899..7d243af 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -2178,9 +2178,14 @@
update = true;
}
- final View anchor = mAnchor.get();
- final int newAccessibilityIdOfAnchor = (anchor != null)
- ? anchor.getAccessibilityViewId() : -1;
+ View anchor = null;
+ int newAccessibilityIdOfAnchor = -1;
+
+ if (mAnchor != null && mAnchor.get() != null) {
+ anchor = mAnchor.get();
+ newAccessibilityIdOfAnchor = anchor.getAccessibilityViewId();
+ }
+
if (newAccessibilityIdOfAnchor != p.accessibilityIdOfAnchor) {
p.accessibilityIdOfAnchor = newAccessibilityIdOfAnchor;
update = true;
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index a032383..003db06 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -289,7 +289,7 @@
*/
private static final class TextClassificationHelper {
- private static final int TRIM_DELTA = 50; // characters
+ private static final int TRIM_DELTA = 120; // characters
private TextClassifier mTextClassifier;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8721f34..3d355ce 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1346,7 +1346,7 @@
<permission android:name="android.permission.CONNECTIVITY_INTERNAL"
android:protectionLevel="signature|privileged" />
- <!-- Allows an internal user to use restricted Networks.
+ <!-- @SystemApi Allows an internal user to use restricted Networks.
@hide -->
<permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"
android:protectionLevel="signature|privileged" />
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index e8972aa..d154730 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -363,6 +363,7 @@
state.computedState.transform.copyTo(&info.transform[0]);
mRenderState.invokeFunctor(op.functor, DrawGlInfo::kModeDraw, &info);
+ if (!mRenderTarget.frameBufferId) mHasDrawn = true;
}
void BakedOpRenderer::dirtyRenderTarget(const Rect& uiDirty) {
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index 2e22132..611fdd1 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.media.MediaCasException.*;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -28,6 +29,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
import android.util.Log;
import android.util.Singleton;
@@ -84,8 +86,6 @@
* sessionId of the descrambler can be retrieved by {@link MediaExtractor#getDrmInitData}
* and used to initialize a MediaDescrambler object for MediaCodec.
* <p>
- * TODO: determine exception handling schemes.
- * <p>
* <h3>Listeners</h3>
* <p>The app may register a listener to receive events from the CA system using
* method {@link #setEventListener}. The exact format of the event is scheme-specific
@@ -382,28 +382,22 @@
mEventHandler = new EventHandler(looper);
}
- /*
- * TODO: handle ServiceSpecificException from the IMediaCas
- * All Drm-specific failures will be thrown by mICas as
- * ServiceSpecificException exception with Drm error code.
- * These need to be re-thrown as crypto exceptions.
- */
-
/**
* Send the private data for the CA system.
*
* @param data byte array of the private data.
*
* @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- /*
- * TODO: need to re-throw DRM-specific exceptions
- */
- public void setPrivateData(@NonNull byte[] data) {
+ public void setPrivateData(@NonNull byte[] data) throws MediaCasException {
validateInternalStates();
try {
mICas.setPrivateData(data);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -416,14 +410,17 @@
*
* @return session id of the newly opened session.
*
- * @throws IllegalStateException if the MediaCas instance is not valid,
- * or IllegalArgumentException if a session for the program already exists.
+ * @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- public byte[] openSession(int programNumber) {
+ public byte[] openSession(int programNumber) throws MediaCasException {
validateInternalStates();
try {
return mICas.openSession(programNumber);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -438,14 +435,18 @@
*
* @return session id of the newly opened session.
*
- * @throws IllegalStateException if the MediaCas instance is not valid,
- * or IllegalArgumentException if a session for the stream already exists.
+ * @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- public byte[] openSession(int programNumber, int elementaryPID) {
+ public byte[] openSession(int programNumber, int elementaryPID)
+ throws MediaCasException {
validateInternalStates();
try {
return mICas.openSessionForStream(programNumber, elementaryPID);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -457,14 +458,16 @@
*
* @param sessionId the session to be closed.
*
- * @throws IllegalStateException if the MediaCas instance is not valid,
- * or IllegalArgumentException if the session is not valid.
+ * @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
public void closeSession(@NonNull byte[] sessionId) {
validateInternalStates();
try {
mICas.closeSession(sessionId);
+ } catch (ServiceSpecificException e) {
+ MediaCasStateException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -476,17 +479,18 @@
* @param sessionId the session for which the private data is intended.
* @param data byte array of the private data.
*
- * @throws IllegalStateException if the MediaCas instance is not valid,
- * or IllegalArgumentException if the session is not valid.
+ * @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- /*
- * TODO: need to re-throw DRM-specific exceptions
- */
- public void setSessionPrivateData(@NonNull byte[] sessionId, @NonNull byte[] data) {
+ public void setSessionPrivateData(@NonNull byte[] sessionId, @NonNull byte[] data)
+ throws MediaCasException {
validateInternalStates();
try {
mICas.setSessionPrivateData(sessionId, data);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -500,19 +504,19 @@
* @param offset position within data where the ECM data begins.
* @param length length of the data (starting from offset).
*
- * @throws IllegalStateException if the MediaCas instance is not valid,
- * or IllegalArgumentException if the session is not valid.
+ * @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- /*
- * TODO: need to re-throw DRM-specific exceptions
- */
- public void processEcm(
- @NonNull byte[] sessionId, @NonNull byte[] data, int offset, int length) {
+ public void processEcm(@NonNull byte[] sessionId, @NonNull byte[] data,
+ int offset, int length) throws MediaCasException {
validateInternalStates();
try {
mCasData.set(data, offset, length);
mICas.processEcm(sessionId, mCasData);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -526,13 +530,12 @@
* @param sessionId the session for which the ECM is intended.
* @param data byte array of the ECM data.
*
- * @throws IllegalStateException if the MediaCas instance is not valid,
- * or IllegalArgumentException if the session is not valid.
+ * @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- /*
- * TODO: need to re-throw DRM-specific exceptions
- */
- public void processEcm(@NonNull byte[] sessionId, @NonNull byte[] data) {
+ public void processEcm(@NonNull byte[] sessionId, @NonNull byte[] data)
+ throws MediaCasException {
processEcm(sessionId, data, 0, data.length);
}
@@ -544,16 +547,18 @@
* @param length length of the data (starting from offset).
*
* @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- /*
- * TODO: need to re-throw DRM-specific exceptions
- */
- public void processEmm(@NonNull byte[] data, int offset, int length) {
+ public void processEmm(@NonNull byte[] data, int offset, int length)
+ throws MediaCasException {
validateInternalStates();
try {
mCasData.set(data, offset, length);
mICas.processEmm(mCasData);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -567,11 +572,10 @@
* @param data byte array of the EMM data.
*
* @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- /*
- * TODO: need to re-throw DRM-specific exceptions
- */
- public void processEmm(@NonNull byte[] data) {
+ public void processEmm(@NonNull byte[] data) throws MediaCasException {
processEmm(data, 0, data.length);
}
@@ -584,12 +588,17 @@
* @param data a byte array containing scheme-specific data for the event.
*
* @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- public void sendEvent(int event, int arg, @Nullable byte[] data) {
+ public void sendEvent(int event, int arg, @Nullable byte[] data)
+ throws MediaCasException {
validateInternalStates();
try {
mICas.sendEvent(event, arg, data);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -603,12 +612,16 @@
* specific.
*
* @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- public void provision(@NonNull String provisionString) {
+ public void provision(@NonNull String provisionString) throws MediaCasException {
validateInternalStates();
try {
mICas.provision(provisionString);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -621,15 +634,17 @@
* @param refreshData private data associated with the refreshment.
*
* @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- /*
- * TODO: define enums for refreshType
- */
- public void refreshEntitlements(int refreshType, @Nullable byte[] refreshData) {
+ public void refreshEntitlements(int refreshType, @Nullable byte[] refreshData)
+ throws MediaCasException {
validateInternalStates();
try {
mICas.refreshEntitlements(refreshType, refreshData);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
diff --git a/media/java/android/media/MediaCasException.java b/media/java/android/media/MediaCasException.java
index 1d5d3cd..485f6ee 100644
--- a/media/java/android/media/MediaCasException.java
+++ b/media/java/android/media/MediaCasException.java
@@ -16,11 +16,104 @@
package android.media;
+import android.os.ServiceSpecificException;
+
/**
* Base class for MediaCas exceptions
*/
public class MediaCasException extends Exception {
+
+ /** @hide */
+ public static final int DRM_ERROR_BASE = -2000;
+ /** @hide */
+ public static final int ERROR_DRM_UNKNOWN = DRM_ERROR_BASE;
+ /** @hide */
+ public static final int ERROR_DRM_NO_LICENSE = DRM_ERROR_BASE - 1;
+ /** @hide */
+ public static final int ERROR_DRM_LICENSE_EXPIRED = DRM_ERROR_BASE - 2;
+ /** @hide */
+ public static final int ERROR_DRM_SESSION_NOT_OPENED = DRM_ERROR_BASE - 3;
+ /** @hide */
+ public static final int ERROR_DRM_DECRYPT_UNIT_NOT_INITIALIZED = DRM_ERROR_BASE - 4;
+ /** @hide */
+ public static final int ERROR_DRM_DECRYPT = DRM_ERROR_BASE - 5;
+ /** @hide */
+ public static final int ERROR_DRM_CANNOT_HANDLE = DRM_ERROR_BASE - 6;
+ /** @hide */
+ public static final int ERROR_DRM_TAMPER_DETECTED = DRM_ERROR_BASE - 7;
+ /** @hide */
+ public static final int ERROR_DRM_NOT_PROVISIONED = DRM_ERROR_BASE - 8;
+ /** @hide */
+ public static final int ERROR_DRM_DEVICE_REVOKED = DRM_ERROR_BASE - 9;
+ /** @hide */
+ public static final int ERROR_DRM_RESOURCE_BUSY = DRM_ERROR_BASE - 10;
+ /** @hide */
+ public static final int ERROR_DRM_INSUFFICIENT_OUTPUT_PROTECTION = DRM_ERROR_BASE - 11;
+ /** @hide */
+ public static final int ERROR_DRM_LAST_USED_ERRORCODE = DRM_ERROR_BASE - 11;
+ /** @hide */
+ public static final int ERROR_DRM_VENDOR_MAX = DRM_ERROR_BASE - 500;
+ /** @hide */
+ public static final int ERROR_DRM_VENDOR_MIN = DRM_ERROR_BASE - 999;
+
+ /** @hide */
public MediaCasException(String detailMessage) {
super(detailMessage);
}
+
+ static void throwExceptions(ServiceSpecificException e) throws MediaCasException {
+ if (e.errorCode == ERROR_DRM_NOT_PROVISIONED) {
+ throw new NotProvisionedException(e.getMessage());
+ } else if (e.errorCode == ERROR_DRM_RESOURCE_BUSY) {
+ throw new ResourceBusyException(e.getMessage());
+ } else if (e.errorCode == ERROR_DRM_DEVICE_REVOKED) {
+ throw new DeniedByServerException(e.getMessage());
+ } else {
+ MediaCasStateException.throwExceptions(e);
+ }
+ }
+
+ /**
+ * Exception thrown when an attempt is made to construct a MediaCas object
+ * using a CA_system_id that is not supported by the device
+ */
+ public static final class UnsupportedCasException extends MediaCasException {
+ /** @hide */
+ public UnsupportedCasException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+ /**
+ * Exception thrown when an operation on a MediaCas object is attempted
+ * before it's provisioned successfully.
+ */
+ public static final class NotProvisionedException extends MediaCasException {
+ /** @hide */
+ public NotProvisionedException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+ /**
+ * Exception thrown when the provisioning server or key server denies a
+ * license for a device.
+ */
+ public static final class DeniedByServerException extends MediaCasException {
+ /** @hide */
+ public DeniedByServerException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+ /**
+ * Exception thrown when an operation on a MediaCas object is attempted
+ * and hardware resources are not available, due to being in use.
+ */
+ public static final class ResourceBusyException extends MediaCasException {
+ /** @hide */
+ public ResourceBusyException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
}
diff --git a/media/java/android/media/MediaCasStateException.java b/media/java/android/media/MediaCasStateException.java
new file mode 100644
index 0000000..cf05c29
--- /dev/null
+++ b/media/java/android/media/MediaCasStateException.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.ServiceSpecificException;
+
+import static android.media.MediaCasException.*;
+
+/**
+ * Base class for MediaCas runtime exceptions
+ */
+public class MediaCasStateException extends IllegalStateException {
+ private final int mErrorCode;
+ private final String mDiagnosticInfo;
+
+ /** @hide */
+ public MediaCasStateException(int err, @Nullable String msg, @Nullable String diagnosticInfo) {
+ super(msg);
+ mErrorCode = err;
+ mDiagnosticInfo = diagnosticInfo;
+ }
+
+ static void throwExceptions(ServiceSpecificException e) {
+ String diagnosticInfo = "";
+ switch (e.errorCode) {
+ case ERROR_DRM_UNKNOWN:
+ diagnosticInfo = "General CAS error";
+ break;
+ case ERROR_DRM_NO_LICENSE:
+ diagnosticInfo = "No license";
+ break;
+ case ERROR_DRM_LICENSE_EXPIRED:
+ diagnosticInfo = "License expired";
+ break;
+ case ERROR_DRM_SESSION_NOT_OPENED:
+ diagnosticInfo = "Session not opened";
+ break;
+ case ERROR_DRM_DECRYPT_UNIT_NOT_INITIALIZED:
+ diagnosticInfo = "Not initialized";
+ break;
+ case ERROR_DRM_DECRYPT:
+ diagnosticInfo = "Decrypt error";
+ break;
+ case ERROR_DRM_CANNOT_HANDLE:
+ diagnosticInfo = "Unsupported scheme or data format";
+ break;
+ case ERROR_DRM_TAMPER_DETECTED:
+ diagnosticInfo = "Tamper detected";
+ break;
+ default:
+ diagnosticInfo = "Unknown CAS state exception";
+ break;
+ }
+ throw new MediaCasStateException(e.errorCode, e.getMessage(),
+ String.format("%s (err=%d)", diagnosticInfo, e.errorCode));
+ }
+
+ /**
+ * Retrieve the associated error code
+ *
+ * @hide
+ */
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ /**
+ * Retrieve a developer-readable diagnostic information string
+ * associated with the exception. Do not show this to end-users,
+ * since this string will not be localized or generally comprehensible
+ * to end-users.
+ */
+ @NonNull
+ public String getDiagnosticInfo() {
+ return mDiagnosticInfo;
+ }
+}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 13a22b4..e628d18 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -1878,9 +1878,7 @@
* @param flags Specify {@link #CONFIGURE_FLAG_ENCODE} to configure the
* component as an encoder.
* @param descrambler Specify a descrambler object to facilitate secure
- * descrambling of the media data. descrambler must not be
- * null if this method is used. For non-secure codecs, use
- * {@link #configure} and with null crypto parameter.
+ * descrambling of the media data, or null for non-secure codecs.
* @throws IllegalArgumentException if the surface has been released (or is invalid),
* or the format is unacceptable (e.g. missing a mandatory key),
* or the flags are not set properly
@@ -1891,8 +1889,9 @@
*/
public void configure(
@Nullable MediaFormat format, @Nullable Surface surface,
- @ConfigureFlag int flags, @NonNull MediaDescrambler descrambler) {
- configure(format, surface, null, descrambler.getBinder(), flags);
+ @ConfigureFlag int flags, @Nullable MediaDescrambler descrambler) {
+ configure(format, surface, null,
+ descrambler != null ? descrambler.getBinder() : null, flags);
}
private void configure(
diff --git a/media/java/android/media/MediaDescrambler.java b/media/java/android/media/MediaDescrambler.java
index f5eede8..2dd1097 100644
--- a/media/java/android/media/MediaDescrambler.java
+++ b/media/java/android/media/MediaDescrambler.java
@@ -17,10 +17,12 @@
package android.media;
import android.annotation.NonNull;
+import android.media.MediaCasException.UnsupportedCasException;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.util.Log;
import java.nio.ByteBuffer;
@@ -54,13 +56,13 @@
/**
* Class for parceling descrambling parameters over IDescrambler binder.
*/
+ // This class currently is not used by Java binder. descramble() goes through
+ // jni to use shared memory. However, the parcelable is still required for AIDL.
static class DescrambleInfo implements Parcelable {
private DescrambleInfo() {
- // TODO: implement
}
private DescrambleInfo(Parcel in) {
- // TODO: disable
}
@Override
@@ -70,7 +72,6 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- // TODO: implement
}
public static final Parcelable.Creator<DescrambleInfo> CREATOR
@@ -112,13 +113,6 @@
return mIDescrambler.asBinder();
}
- /*
- * TODO: handle ServiceSpecificException from the mIDescrambler
- * All Drm-specific failures will be thrown by mIDescrambler as
- * ServiceSpecificException exception with Drm error code.
- * These need to be re-thrown as crypto exceptions.
- */
-
/**
* Query if the scrambling scheme requires the use of a secure decoder
* to decode data of the given mime type.
@@ -150,14 +144,16 @@
* @param sessionId the MediaCas sessionId to associate with this
* MediaDescrambler instance.
*
- * @throws IllegalStateException if the descrambler instance is not valid,
- * or IllegalArgumentException if the sessionId is not valid.
+ * @throws IllegalStateException if the descrambler instance is not valid.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
public final void setMediaCasSession(@NonNull byte[] sessionId) {
validateInternalStates();
try {
mIDescrambler.setMediaCasSession(sessionId);
+ } catch (ServiceSpecificException e) {
+ MediaCasStateException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -179,9 +175,7 @@
* values indicating errors.
*
* @throws IllegalStateException if the descrambler instance is not valid.
- */
- /*
- * TODO: throw DRM-specific exception if decrambling is failing.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
public final int descramble(
@NonNull ByteBuffer srcBuf, int srcPos, ByteBuffer dstBuf, int dstPos,
@@ -208,12 +202,17 @@
"Invalid CryptoInfo: key array is invalid!");
}
- return native_descramble(
- cryptoInfo.key[0],
- cryptoInfo.numSubSamples,
- cryptoInfo.numBytesOfClearData,
- cryptoInfo.numBytesOfEncryptedData,
- srcBuf, srcPos, dstBuf, dstPos);
+ try {
+ return native_descramble(
+ cryptoInfo.key[0],
+ cryptoInfo.numSubSamples,
+ cryptoInfo.numBytesOfClearData,
+ cryptoInfo.numBytesOfEncryptedData,
+ srcBuf, srcPos, dstBuf, dstPos);
+ } catch (ServiceSpecificException e) {
+ MediaCasStateException.throwExceptions(e);
+ }
+ return -1;
}
public final void release() {
diff --git a/media/java/android/media/UnsupportedCasException.java b/media/java/android/media/UnsupportedCasException.java
deleted file mode 100644
index 3167637..0000000
--- a/media/java/android/media/UnsupportedCasException.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-/**
- * Exception thrown when an attempt is made to construct a MediaCas object
- * using a CA_system_id that is not supported by the device
- */
-public final class UnsupportedCasException extends MediaCasException {
- public UnsupportedCasException(String detailMessage) {
- super(detailMessage);
- }
-}
diff --git a/media/jni/android_media_MediaDescrambler.cpp b/media/jni/android_media_MediaDescrambler.cpp
index 7585664..f031dbb 100644
--- a/media/jni/android_media_MediaDescrambler.cpp
+++ b/media/jni/android_media_MediaDescrambler.cpp
@@ -129,7 +129,7 @@
mMem = mDealer->allocate(neededSize);
}
-ssize_t JDescrambler::descramble(
+Status JDescrambler::descramble(
jbyte key,
size_t numSubSamples,
ssize_t totalLength,
@@ -137,7 +137,8 @@
const void *srcPtr,
jint srcOffset,
void *dstPtr,
- jint dstOffset) {
+ jint dstOffset,
+ ssize_t *result) {
// TODO: IDescrambler::descramble() is re-entrant, however because we
// only have 1 shared mem buffer, we can only do 1 descramble at a time.
// Concurrency might be improved by allowing on-demand allocation of up
@@ -159,16 +160,16 @@
info.dstPtr = NULL;
info.dstOffset = 0;
- int32_t result;
- binder::Status status = mDescrambler->descramble(info, &result);
+ int32_t descrambleResult;
+ Status status = mDescrambler->descramble(info, &descrambleResult);
- if (!status.isOk() || result > totalLength) {
- return -1;
+ if (status.isOk()) {
+ *result = (descrambleResult <= totalLength) ? descrambleResult : -1;
+ if (*result > 0) {
+ memcpy((void*)((uint8_t*)dstPtr + dstOffset), mMem->pointer(), *result);
+ }
}
- if (result > 0) {
- memcpy((void*)((uint8_t*)dstPtr + dstOffset), mMem->pointer(), result);
- }
- return result;
+ return status;
}
} // namespace android
@@ -251,11 +252,45 @@
numBytesOfClearData = NULL;
}
+ if (totalSize < 0) {
+ delete[] subSamples;
+ return -1;
+ }
+
*outSubSamples = subSamples;
return totalSize;
}
+static jthrowable createServiceSpecificException(
+ JNIEnv *env, int serviceSpecificError, const char *msg) {
+ if (env->ExceptionCheck()) {
+ ALOGW("Discarding pending exception");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+
+ ScopedLocalRef<jclass> clazz(
+ env, env->FindClass("android/os/ServiceSpecificException"));
+ CHECK(clazz.get() != NULL);
+
+ const jmethodID ctor = env->GetMethodID(clazz.get(), "<init>", "(ILjava/lang/String;)V");
+ CHECK(ctor != NULL);
+
+ ScopedLocalRef<jstring> msgObj(
+ env, env->NewStringUTF(msg != NULL ?
+ msg : String8::format("Error %#x", serviceSpecificError)));
+
+ return (jthrowable)env->NewObject(
+ clazz.get(), ctor, serviceSpecificError, msgObj.get());
+}
+
+static void throwServiceSpecificException(
+ JNIEnv *env, int serviceSpecificError, const char *msg) {
+ jthrowable exception = createServiceSpecificException(env, serviceSpecificError, msg);
+ env->Throw(exception);
+}
+
static jint android_media_MediaDescrambler_native_descramble(
JNIEnv *env, jobject thiz, jbyte key, jint numSubSamples,
jintArray numBytesOfClearDataObj, jintArray numBytesOfEncryptedDataObj,
@@ -290,11 +325,11 @@
env, dstBuf, dstOffset, totalLength, &dstPtr, &dstArray);
}
}
-
+ Status status;
if (err == OK) {
- result = descrambler->descramble(
+ status = descrambler->descramble(
key, numSubSamples, totalLength, subSamples,
- srcPtr, srcOffset, dstPtr, dstOffset);
+ srcPtr, srcOffset, dstPtr, dstOffset, &result);
}
delete[] subSamples;
@@ -304,6 +339,51 @@
if (dstArray != NULL) {
env->ReleaseByteArrayElements(dstArray, (jbyte *)dstPtr, 0);
}
+
+ if (!status.isOk()) {
+ switch (status.exceptionCode()) {
+ case Status::EX_SECURITY:
+ jniThrowException(env, "java/lang/SecurityException",
+ status.exceptionMessage());
+ break;
+ case Status::EX_BAD_PARCELABLE:
+ jniThrowException(env, "java/lang/BadParcelableException",
+ status.exceptionMessage());
+ break;
+ case Status::EX_ILLEGAL_ARGUMENT:
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ status.exceptionMessage());
+ break;
+ case Status::EX_NULL_POINTER:
+ jniThrowException(env, "java/lang/NullPointerException",
+ status.exceptionMessage());
+ break;
+ case Status::EX_ILLEGAL_STATE:
+ jniThrowException(env, "java/lang/IllegalStateException",
+ status.exceptionMessage());
+ break;
+ case Status::EX_NETWORK_MAIN_THREAD:
+ jniThrowException(env, "java/lang/NetworkOnMainThreadException",
+ status.exceptionMessage());
+ break;
+ case Status::EX_UNSUPPORTED_OPERATION:
+ jniThrowException(env, "java/lang/UnsupportedOperationException",
+ status.exceptionMessage());
+ break;
+ case Status::EX_SERVICE_SPECIFIC:
+ throwServiceSpecificException(env, status.serviceSpecificErrorCode(),
+ status.exceptionMessage());
+ break;
+ default:
+ {
+ String8 msg;
+ msg.appendFormat("Unknown exception code: %d, msg: %s",
+ status.exceptionCode(), status.exceptionMessage().string());
+ jniThrowException(env, "java/lang/RuntimeException", msg.string());
+ break;
+ }
+ }
+ }
return result;
}
diff --git a/media/jni/android_media_MediaDescrambler.h b/media/jni/android_media_MediaDescrambler.h
index e944a90..aeef05e 100644
--- a/media/jni/android_media_MediaDescrambler.h
+++ b/media/jni/android_media_MediaDescrambler.h
@@ -19,6 +19,7 @@
#include "jni.h"
+#include <binder/Status.h>
#include <media/cas/DescramblerAPI.h>
#include <media/stagefright/foundation/ABase.h>
#include <utils/Mutex.h>
@@ -31,11 +32,12 @@
class IDescrambler;
};
using namespace media;
+using binder::Status;
struct JDescrambler : public RefBase {
JDescrambler(JNIEnv *env, jobject descramberBinderObj);
- ssize_t descramble(
+ Status descramble(
jbyte key,
size_t numSubSamples,
ssize_t totalLength,
@@ -43,7 +45,8 @@
const void *srcPtr,
jint srcOffset,
void *dstPtr,
- jint dstOffset);
+ jint dstOffset,
+ ssize_t *result);
protected:
virtual ~JDescrambler();
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index e49463f..1b6aca1 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -58,6 +58,7 @@
import android.widget.ArrayAdapter;
import android.widget.TextView;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.Preconditions;
@@ -180,15 +181,20 @@
}
private void startDiscovery(AssociationRequest request) {
- mRequest = request;
+ if (!request.equals(mRequest)) {
+ mRequest = request;
- mFilters = request.getDeviceFilters();
- mWifiFilters = CollectionUtils.filter(mFilters, WifiDeviceFilter.class);
- mBluetoothFilters = CollectionUtils.filter(mFilters, BluetoothDeviceFilter.class);
- mBLEFilters = CollectionUtils.filter(mFilters, BluetoothLEDeviceFilter.class);
- mBLEScanFilters = CollectionUtils.map(mBLEFilters, BluetoothLEDeviceFilter::getScanFilter);
+ mFilters = request.getDeviceFilters();
+ mWifiFilters = CollectionUtils.filter(mFilters, WifiDeviceFilter.class);
+ mBluetoothFilters = CollectionUtils.filter(mFilters, BluetoothDeviceFilter.class);
+ mBLEFilters = CollectionUtils.filter(mFilters, BluetoothLEDeviceFilter.class);
+ mBLEScanFilters = CollectionUtils.map(mBLEFilters, BluetoothLEDeviceFilter::getScanFilter);
- reset();
+ reset();
+ }
+ if (!ArrayUtils.isEmpty(mDevicesFound)) {
+ onReadyToShowUI();
+ }
if (shouldScan(mBluetoothFilters)) {
final IntentFilter intentFilter = new IntentFilter();
@@ -228,10 +234,18 @@
private void stopScan() {
if (DEBUG) Log.i(LOG_TAG, "stopScan() called");
- mBluetoothAdapter.cancelDiscovery();
- mBLEScanner.stopScan(mBLEScanCallback);
- unregisterReceiver(mBluetoothDeviceFoundBroadcastReceiver);
- unregisterReceiver(mWifiDeviceFoundBroadcastReceiver);
+
+ if (shouldScan(mBluetoothFilters)) {
+ mBluetoothAdapter.cancelDiscovery();
+ unregisterReceiver(mBluetoothDeviceFoundBroadcastReceiver);
+ }
+ if (shouldScan(mBLEFilters)) {
+ mBLEScanner.stopScan(mBLEScanCallback);
+ }
+ if (shouldScan(mWifiFilters)) {
+ unregisterReceiver(mWifiDeviceFoundBroadcastReceiver);
+ }
+
stopSelf();
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index d058e78..79190cb 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -24,6 +24,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.NightDisplayController;
+import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.Preconditions;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.assist.AssistManager;
@@ -257,6 +258,8 @@
mProviders.put(VolumeDialogController.class, () ->
new VolumeDialogControllerImpl(mContext));
+ mProviders.put(MetricsLogger.class, () -> new MetricsLogger());
+
// Put all dependencies above here so the factory can override them if it wants.
SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index f70d5b4..b689a850 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -104,6 +104,8 @@
private int mImeHeight;
private float mSavedSnapFraction = -1f;
private boolean mSendingHoverAccessibilityEvents;
+ private boolean mMovementWithinMinimize;
+ private boolean mMovementWithinDismiss;
// Touch state
private final PipTouchState mTouchState;
@@ -435,6 +437,8 @@
* Gesture controlling normal movement of the PIP.
*/
private PipTouchGesture mDefaultMovementGesture = new PipTouchGesture() {
+ // Whether the PiP was on the left side of the screen at the start of the gesture
+ private boolean mStartedOnLeft;
@Override
public void onDown(PipTouchState touchState) {
@@ -442,6 +446,10 @@
return;
}
+ mStartedOnLeft = mMotionHelper.getBounds().left < mMovementBounds.centerX();
+ mMovementWithinMinimize = true;
+ mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom;
+
// If the menu is still visible, and we aren't minimized, then just poke the menu
// so that it will timeout after the user stops touching it
if (mMenuController.isMenuVisible() && !mIsMinimized) {
@@ -493,6 +501,18 @@
if (ENABLE_DISMISS_DRAG_TO_EDGE) {
updateDismissFraction();
}
+
+ final PointF curPos = touchState.getLastTouchPosition();
+ if (mMovementWithinMinimize) {
+ // Track if movement remains near starting edge to identify swipes to minimize
+ mMovementWithinMinimize = mStartedOnLeft
+ ? curPos.x <= mMovementBounds.left + mTmpBounds.width()
+ : curPos.x >= mMovementBounds.right;
+ }
+ if (mMovementWithinDismiss) {
+ // Track if movement remains near the bottom edge to identify swipe to dismiss
+ mMovementWithinDismiss = curPos.y >= mMovementBounds.bottom;
+ }
return true;
}
return false;
@@ -526,8 +546,15 @@
}
if (touchState.isDragging()) {
+ final PointF vel = touchState.getVelocity();
+ final float velocity = PointF.length(vel.x, vel.y);
+ final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond();
+ final boolean isHorizontal = Math.abs(vel.x) > Math.abs(vel.y);
final boolean onLeft = mMotionHelper.getBounds().left < mMovementBounds.centerX();
- boolean isFlingToBot = isFlingTowardsEdge(touchState, 4 /* bottom */);
+ final boolean isFlingToBot = !isHorizontal && mMovementWithinDismiss && vel.y > 0;
+ final boolean isFlingToEdge = isHorizontal && mMovementWithinMinimize
+ && (onLeft ? vel.x < 0 : vel.x > 0);
+
if (ENABLE_DISMISS_DRAG_TO_EDGE
&& (mMotionHelper.shouldDismissPip() || isFlingToBot)) {
mMotionHelper.animateDragToEdgeDismiss(mMotionHelper.getBounds(),
@@ -536,8 +563,7 @@
MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED,
METRIC_VALUE_DISMISSED_BY_DRAG);
return true;
- } else if (!mIsMinimized && (mMotionHelper.shouldMinimizePip()
- || isFlingTowardsEdge(touchState, onLeft ? 2 : 3))) {
+ } else if (!mIsMinimized && (mMotionHelper.shouldMinimizePip() || isFlingToEdge)) {
// Pip should be minimized
setMinimizedStateInternal(true);
if (mMenuController.isMenuVisible()) {
@@ -563,9 +589,7 @@
mMenuController.showMenu(mMotionHelper.getBounds(), mMovementBounds);
}
- final PointF vel = mTouchState.getVelocity();
- final float velocity = PointF.length(vel.x, vel.y);
- if (velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+ if (isFling) {
mMotionHelper.flingToSnapTarget(velocity, vel.x, vel.y, mMovementBounds,
mUpdateScrimListener);
} else {
@@ -585,42 +609,6 @@
};
/**
- * @return whether the gesture ending in {@param vel} is fast enough to be a fling and towards
- * the provided {@param edge} where:
- *
- * 1 = top
- * 2 = left
- * 3 = right
- * 4 = bottom
- */
- private boolean isFlingTowardsEdge(PipTouchState touchState, int edge) {
- final PointF vel = touchState.getVelocity();
- final PointF downPos = touchState.getDownTouchPosition();
- final Rect bounds = mMotionHelper.getBounds();
- final boolean isHorizontal = Math.abs(vel.x) > Math.abs(vel.y);
- final boolean isFling =
- PointF.length(vel.x, vel.y) > mFlingAnimationUtils.getMinVelocityPxPerSecond();
- if (!isFling) {
- return false;
- }
- switch (edge) {
- case 1: // top
- return !isHorizontal && vel.y < 0
- && downPos.y <= mMovementBounds.top + bounds.height();
- case 2: // left
- return isHorizontal && vel.x < 0
- && downPos.x <= mMovementBounds.left + bounds.width();
- case 3: // right
- return isHorizontal && vel.x > 0
- && downPos.x >= mMovementBounds.right;
- case 4: // bottom
- return !isHorizontal && vel.y > 0
- && downPos.y >= mMovementBounds.bottom;
- }
- return false;
- }
-
- /**
* Updates the current movement bounds based on whether the menu is currently visible.
*/
private void updateMovementBounds(boolean isExpanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index 1709718..9efe224 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -14,6 +14,8 @@
package com.android.systemui.qs;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_MORE_SETTINGS;
+
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
@@ -197,7 +199,7 @@
mDetailContent.removeAllViews();
mDetailContent.addView(detailView);
mDetailViews.put(viewCacheIndex, detailView);
- MetricsLogger.visible(mContext, adapter.getMetricsCategory());
+ Dependency.get(MetricsLogger.class).visible(adapter.getMetricsCategory());
announceForAccessibility(mContext.getString(
R.string.accessibility_quick_settings_detail,
adapter.getTitle()));
@@ -206,7 +208,7 @@
setVisibility(View.VISIBLE);
} else {
if (mDetailAdapter != null) {
- MetricsLogger.hidden(mContext, mDetailAdapter.getMetricsCategory());
+ Dependency.get(MetricsLogger.class).hidden(mDetailAdapter.getMetricsCategory());
}
mClosingDetail = true;
mDetailAdapter = null;
@@ -238,8 +240,12 @@
protected void setupDetailFooter(DetailAdapter adapter) {
final Intent settingsIntent = adapter.getSettingsIntent();
mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
- mDetailSettingsButton.setOnClickListener(v -> Dependency.get(ActivityStarter.class)
- .postStartActivityDismissingKeyguard(settingsIntent, 0));
+ mDetailSettingsButton.setOnClickListener(v -> {
+ Dependency.get(MetricsLogger.class).action(ACTION_QS_MORE_SETTINGS,
+ mDetailAdapter.getMetricsCategory());
+ Dependency.get(ActivityStarter.class)
+ .postStartActivityDismissingKeyguard(settingsIntent, 0);
+ });
}
protected void setupDetailHeader(final DetailAdapter adapter) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index 2202b58..a84138d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -16,6 +16,8 @@
package com.android.systemui.qs;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_DATE;
+
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
@@ -358,6 +360,8 @@
startSettingsActivity();
}
} else if (v == mDateTimeGroup) {
+ Dependency.get(MetricsLogger.class).action(ACTION_QS_DATE,
+ mNextAlarm != null);
if (mNextAlarm != null) {
PendingIntent showIntent = mNextAlarm.getShowIntent();
mActivityStarter.startPendingIntentDismissingKeyguard(showIntent);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 29d547c..8596b57 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -32,6 +32,8 @@
TileServices getTileServices();
void removeTile(String tileSpec);
+ int indexOf(String tileSpec);
+
interface Callback {
void onTilesChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 8298cbb..2e6116d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -59,6 +59,7 @@
protected final View mBrightnessView;
private final H mHandler = new H();
private final View mPageIndicator;
+ private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
private int mPanelPaddingBottom;
private int mBrightnessPaddingTop;
@@ -259,7 +260,7 @@
if (!mExpanded && mTileLayout instanceof PagedTileLayout) {
((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
}
- MetricsLogger.visibility(mContext, MetricsEvent.QS_PANEL, mExpanded);
+ mMetricsLogger.visibility(MetricsEvent.QS_PANEL, mExpanded);
if (!mExpanded) {
closeDetail();
} else {
@@ -475,7 +476,7 @@
int newVis = visible ? VISIBLE : INVISIBLE;
setVisibility(newVis);
if (mGridContentVisible != visible) {
- MetricsLogger.visibility(mContext, MetricsEvent.QS_PANEL, newVis);
+ mMetricsLogger.visibility(MetricsEvent.QS_PANEL, newVis);
}
mGridContentVisible = visible;
}
@@ -483,7 +484,7 @@
private void logTiles() {
for (int i = 0; i < mRecords.size(); i++) {
TileRecord tileRecord = mRecords.get(i);
- MetricsLogger.visible(mContext, tileRecord.tile.getMetricsCategory());
+ mMetricsLogger.visible(tileRecord.tile.getMetricsCategory());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 0ca115e..9330541 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -157,6 +157,10 @@
return mServices;
}
+ public int indexOf(String spec) {
+ return mTileSpecs.indexOf(spec);
+ }
+
@Override
public void onTuningChanged(String key, String newValue) {
if (!TILES_SETTING.equals(key)) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 6f35017..b5c1bd9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -22,6 +22,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.graphics.drawable.Drawable;
+import android.metrics.LogMaker;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
@@ -155,6 +156,11 @@
return mComponent;
}
+ @Override
+ protected LogMaker populate(LogMaker logMaker) {
+ return super.populate(logMaker).setComponentName(mComponent);
+ }
+
public Tile getQsTile() {
return mTile;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 948954c2..1aa51b1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -14,12 +14,19 @@
package com.android.systemui.qs.tileimpl;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_CLICK;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_LONG_PRESS;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_SECONDARY_CLICK;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_POSITION;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_VALUE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
+import android.metrics.LogMaker;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -29,7 +36,6 @@
import android.util.SparseArray;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
@@ -58,6 +64,7 @@
protected final H mHandler = new H(Dependency.get(Dependency.BG_LOOPER));
protected final Handler mUiHandler = new Handler(Looper.getMainLooper());
private final ArraySet<Object> mListeners = new ArraySet<>();
+ private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
protected TState mState = newTileState();
@@ -76,7 +83,7 @@
/**
* Declare the category of this tile.
*
- * Categories are defined in {@link com.android.internal.logging.MetricsProto.MetricsEvent}
+ * Categories are defined in {@link com.android.internal.logging.nano.MetricsProto.MetricsEvent}
* by editing frameworks/base/proto/src/metrics_constants.proto.
*/
abstract public int getMetricsCategory();
@@ -152,17 +159,28 @@
}
public void click() {
+ mMetricsLogger.write(populate(new LogMaker(ACTION_QS_CLICK).setType(TYPE_ACTION)));
mHandler.sendEmptyMessage(H.CLICK);
}
public void secondaryClick() {
+ mMetricsLogger.write(populate(new LogMaker(ACTION_QS_SECONDARY_CLICK).setType(TYPE_ACTION)));
mHandler.sendEmptyMessage(H.SECONDARY_CLICK);
}
public void longClick() {
+ mMetricsLogger.write(populate(new LogMaker(ACTION_QS_LONG_PRESS).setType(TYPE_ACTION)));
mHandler.sendEmptyMessage(H.LONG_CLICK);
}
+ protected LogMaker populate(LogMaker logMaker) {
+ if (mState instanceof BooleanState) {
+ logMaker.addTaggedData(FIELD_QS_VALUE, ((BooleanState) mState).value ? 1 : 0);
+ }
+ return logMaker.setSubtype(getMetricsCategory())
+ .addTaggedData(FIELD_QS_POSITION, mHost.indexOf(mTileSpec));
+ }
+
public void showDetail(boolean show) {
mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
}
@@ -224,7 +242,6 @@
}
protected void handleLongClick() {
- MetricsLogger.action(mContext, MetricsEvent.ACTION_QS_LONG_PRESS, getTileSpec());
Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(
getLongClickIntent(), 0);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index ed6e6ef..4e4de15 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -83,7 +83,6 @@
protected void handleClick() {
// Secondary clicks are header clicks, just toggle.
final boolean isEnabled = (Boolean)mState.value;
- MetricsLogger.action(mContext, getMetricsCategory(), !isEnabled);
mController.setBluetoothEnabled(!isEnabled);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index a1d3d26..22b6a63 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -108,13 +108,11 @@
protected void handleClick() {
if (mKeyguard.isSecure() && !mKeyguard.canSkipBouncer()) {
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
- MetricsLogger.action(mContext, getMetricsCategory());
showDetail(true);
mHost.openPanels();
});
return;
}
- MetricsLogger.action(mContext, getMetricsCategory());
showDetail(true);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 4351b2c..04be7de 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -98,7 +98,6 @@
@Override
protected void handleSecondaryClick() {
- MetricsLogger.action(mContext, getMetricsCategory());
if (mDataController.isMobileDataSupported()) {
showDetail(true);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index e33b680..5b374b1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -84,7 +84,6 @@
@Override
protected void handleClick() {
- MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
mSetting.setValue(mState.value ? 0 : 1);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 7a25140..b796451 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -87,7 +87,6 @@
private void toggleDataSaver() {
mState.value = !mDataSaverController.isDataSaverEnabled();
- MetricsLogger.action(mContext, getMetricsCategory(), mState.value);
mDataSaverController.setDataSaverEnabled(mState.value);
refreshState(mState.value);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index f35de68..3c2e897 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -146,7 +146,6 @@
Toast.LENGTH_LONG).show();
return;
}
- MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
showDetail(true);
int zen = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, Global.ZEN_MODE_ALARMS);
mController.setZen(zen, null, TAG);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index 7b0fd73..6d2aa90 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -87,7 +87,6 @@
if (ActivityManager.isUserAMonkey()) {
return;
}
- MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
boolean newState = !mState.value;
refreshState(newState);
mFlashlightController.setFlashlight(newState);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 6662937..5c3f65c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -106,7 +106,6 @@
if (!isEnabled && mAirplaneMode.getValue() != 0) {
return;
}
- MetricsLogger.action(mContext, getMetricsCategory(), !isEnabled);
mController.setHotspotEnabled(!isEnabled);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
index c953363..00cfbfa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
@@ -92,7 +92,6 @@
@Override
protected void handleClick() {
- MetricsLogger.action(mContext, getMetricsCategory(), mIntentPackage);
sendIntent("click", mOnClick, mOnClickUri);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index b5c02cb..b11b15a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -81,13 +81,11 @@
Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> {
final boolean wasEnabled = mState.value;
mHost.openPanels();
- MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
mController.setLocationEnabled(!wasEnabled);
});
return;
}
final boolean wasEnabled = mState.value;
- MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
mController.setLocationEnabled(!wasEnabled);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
index 3299339..d147b69 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
@@ -85,7 +85,6 @@
@Override
protected void handleClick() {
if (mAdapter == null) return;
- MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
if (!mAdapter.isEnabled()) {
mAdapter.enable();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index 8b47216..8aa1e43 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -54,7 +54,6 @@
@Override
protected void handleClick() {
final boolean activated = !mState.value;
- MetricsLogger.action(mContext, getMetricsCategory(), activated);
mController.setActivated(activated);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 130304f..fb937bd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -79,7 +79,6 @@
@Override
protected void handleClick() {
if (mController == null) return;
- MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
final boolean newState = !mState.value;
mController.setRotationLocked(!newState);
refreshState(newState);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index fde2e04..79b4c4a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -114,7 +114,6 @@
protected void handleClick() {
// Secondary clicks are header clicks, just toggle.
mState.copyTo(mStateBeforeClick);
- MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
mController.setWifiEnabled(!mState.value);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 5086091..6c89241 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -68,7 +68,6 @@
@Override
public void handleClick() {
- MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
mProfileController.setWorkModeEnabled(!mState.value);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index aa0fcbd..be16266 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -82,6 +82,8 @@
mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
mSignalClusterView = reinflateSignalCluster(mStatusBar);
Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mSignalClusterView);
+ // Default to showing until we know otherwise.
+ showSystemIconArea(false);
}
@Override
@@ -119,6 +121,8 @@
.removeView(mNotificationIconAreaInner);
}
notificationIconArea.addView(mNotificationIconAreaInner);
+ // Default to showing until we know otherwise.
+ showNotificationIconArea(false);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
index b5f56c3..4d99a46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
@@ -22,6 +22,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
@@ -33,7 +34,7 @@
private ArrayMap<Integer, Integer> mLegacyMap;
private LogMaker mLogMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN)
.setType(MetricsEvent.TYPE_ACTION);
- private MetricsLogger mMetricsLogger = new MetricsLogger();
+ private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
public LockscreenGestureLogger() {
mLegacyMap = new ArrayMap<>(EventLogConstants.METRICS_GESTURE_TYPE_MAP.length);
@@ -58,9 +59,4 @@
}
return value;
}
-
- @VisibleForTesting
- void setMetricsLogger(MetricsLogger metricsLogger) {
- mMetricsLogger = metricsLogger;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index b82b113..5370ceb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -484,7 +484,7 @@
private ScreenPinningRequest mScreenPinningRequest;
- MetricsLogger mMetricsLogger = new MetricsLogger();
+ private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
// ensure quick settings is disabled until the current user makes it through the setup wizard
private boolean mUserSetup = false;
@@ -748,12 +748,6 @@
private NavigationBarFragment mNavigationBar;
private View mNavigationBarView;
- @VisibleForTesting
- void setMetricsLogger(MetricsLogger metricsLogger) {
- mMetricsLogger = metricsLogger;
- mLockscreenGestureLogger.setMetricsLogger(metricsLogger);
- }
-
@Override
public void start() {
mNetworkController = Dependency.get(NetworkController.class);
@@ -993,16 +987,19 @@
Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mNotificationIconAreaController);
FragmentHostManager.get(mStatusBarWindow)
.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
- CollapsedStatusBarFragment statusBarFragment = (CollapsedStatusBarFragment) fragment;
+ CollapsedStatusBarFragment statusBarFragment =
+ (CollapsedStatusBarFragment) fragment;
statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);
mStatusBarView = (PhoneStatusBarView) fragment.getView();
mStatusBarView.setBar(this);
mStatusBarView.setPanel(mNotificationPanel);
mStatusBarView.setScrimController(mScrimController);
setAreThereNotifications();
+ checkBarModes();
}).getFragmentManager()
.beginTransaction()
- .replace(R.id.status_bar_container, new CollapsedStatusBarFragment(), CollapsedStatusBarFragment.TAG)
+ .replace(R.id.status_bar_container, new CollapsedStatusBarFragment(),
+ CollapsedStatusBarFragment.TAG)
.commit();
Dependency.get(StatusBarIconController.class).addIconGroup(
new IconManager((ViewGroup) mKeyguardStatusBar.findViewById(R.id.statusIcons)));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
new file mode 100644
index 0000000..c67cccc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_MORE_SETTINGS;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.testing.ViewUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.qs.DetailAdapter;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class QSDetailTest extends SysuiTestCase {
+
+ private MetricsLogger mMetricsLogger;
+ private QSDetail mQsDetail;
+ private QSPanel mQsPanel;
+ private QuickStatusBarHeader mQuickHeader;
+ private ActivityStarter mActivityStarter;
+ private DetailAdapter mMockDetailAdapter;
+ private TestableLooper mTestableLooper;
+
+ @Before
+ public void setup() throws Exception {
+ mTestableLooper = TestableLooper.get(this);
+ mTestableLooper.runWithLooper(() -> {
+ mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
+ mActivityStarter = mDependency.injectMockDependency(ActivityStarter.class);
+ mQsDetail = (QSDetail) LayoutInflater.from(mContext).inflate(R.layout.qs_detail, null);
+ mQsPanel = mock(QSPanel.class);
+ mQuickHeader = mock(QuickStatusBarHeader.class);
+ mQsDetail.setQsPanel(mQsPanel, mQuickHeader);
+
+ mMockDetailAdapter = mock(DetailAdapter.class);
+ when(mMockDetailAdapter.createDetailView(any(), any(), any()))
+ .thenReturn(mock(View.class));
+ });
+ }
+
+ @Test
+ public void testShowDetail_Metrics() {
+ ViewUtils.attachView(mQsDetail);
+ mTestableLooper.processAllMessages();
+
+ mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
+ verify(mMetricsLogger).visible(eq(mMockDetailAdapter.getMetricsCategory()));
+ mQsDetail.handleShowingDetail(null, 0, 0, false);
+ verify(mMetricsLogger).hidden(eq(mMockDetailAdapter.getMetricsCategory()));
+
+ ViewUtils.detachView(mQsDetail);
+ mTestableLooper.processAllMessages();
+ }
+
+ @Test
+ public void testMoreSettingsButton() {
+ ViewUtils.attachView(mQsDetail);
+ mTestableLooper.processAllMessages();
+
+ mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
+ mQsDetail.findViewById(android.R.id.button2).performClick();
+
+ int metricsCategory = mMockDetailAdapter.getMetricsCategory();
+ verify(mMetricsLogger).action(eq(ACTION_QS_MORE_SETTINGS), eq(metricsCategory));
+
+ verify(mActivityStarter).postStartActivityDismissingKeyguard(any(), anyInt());
+
+ ViewUtils.detachView(mQsDetail);
+ mTestableLooper.processAllMessages();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index deb31da..d77ed3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -19,6 +19,7 @@
import android.os.Looper;
+import com.android.internal.logging.MetricsLogger;
import com.android.keyguard.CarrierText;
import com.android.systemui.Dependency;
import com.android.systemui.R;
@@ -44,12 +45,15 @@
@RunWithLooper(setAsMainLooper = true)
public class QSFragmentTest extends SysuiBaseFragmentTest {
+ private MetricsLogger mMockMetricsLogger;
+
public QSFragmentTest() {
super(QSFragment.class);
}
@Before
public void addLeakCheckDependencies() {
+ mMockMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE,
new LayoutInflaterBuilder(mContext)
.replace("com.android.systemui.statusbar.policy.SplitClockView",
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
new file mode 100644
index 0000000..4979684
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.qs.customize.QSCustomizer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class QSPanelTest extends SysuiTestCase {
+
+ private MetricsLogger mMetricsLogger;
+ private QSPanel mQsPanel;
+ private QSTileHost mHost;
+ private QSCustomizer mCustomizer;
+
+ @Before
+ public void setup() throws Exception {
+ TestableLooper.get(this).runWithLooper(() -> {
+ mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
+ mQsPanel = new QSPanel(mContext, null);
+ mHost = mock(QSTileHost.class);
+ when(mHost.getTiles()).thenReturn(Collections.emptyList());
+ mCustomizer = mock(QSCustomizer.class);
+ mQsPanel.setHost(mHost, mCustomizer);
+ });
+ }
+
+ @Test
+ public void testSetExpanded_Metrics() {
+ mQsPanel.setExpanded(true);
+ verify(mMetricsLogger).visibility(eq(MetricsEvent.QS_PANEL), eq(true));
+ mQsPanel.setExpanded(false);
+ verify(mMetricsLogger).visibility(eq(MetricsEvent.QS_PANEL), eq(false));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
new file mode 100644
index 0000000..9ed9d28c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs.tileimpl;
+
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_CLICK;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_LONG_PRESS;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_SECONDARY_CLICK;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_POSITION;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_VALUE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.metrics.LogMaker;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Dependency;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QSTileHost;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class QSTileImplTest extends SysuiTestCase {
+
+ public static final int POSITION = 14;
+ private TestableLooper mTestableLooper;
+ private TileImpl mTile;
+ private QSTileHost mHost;
+ private MetricsLogger mMetricsLogger;
+
+ @Before
+ public void setup() throws Exception {
+ String spec = "spec";
+ mTestableLooper = TestableLooper.get(this);
+ mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
+ mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
+ mHost = mock(QSTileHost.class);
+ when(mHost.indexOf(spec)).thenReturn(POSITION);
+ mTestableLooper.runWithLooper(() -> {
+ mTile = new TileImpl(mHost);
+ mTile.setTileSpec(spec);
+ });
+ }
+
+ @Test
+ public void testClick_Metrics() {
+ mTile.click();
+ verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_CLICK)));
+ }
+
+ @Test
+ public void testSecondaryClick_Metrics() {
+ mTile.secondaryClick();
+ verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_SECONDARY_CLICK)));
+ }
+
+ @Test
+ public void testLongClick_Metrics() {
+ mTile.longClick();
+ verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_LONG_PRESS)));
+ }
+
+ @Test
+ public void testPopulate() {
+ LogMaker maker = mock(LogMaker.class);
+ when(maker.setSubtype(anyInt())).thenReturn(maker);
+ mTile.getState().value = true;
+ mTile.populate(maker);
+ verify(maker).addTaggedData(eq(FIELD_QS_VALUE), eq(1));
+ verify(maker).addTaggedData(eq(FIELD_QS_POSITION), eq(POSITION));
+ }
+
+ private class TileLogMatcher implements ArgumentMatcher<LogMaker> {
+
+ private final int mCategory;
+ public String mInvalid;
+
+ public TileLogMatcher(int category) {
+ mCategory = category;
+ }
+
+ @Override
+ public boolean matches(LogMaker arg) {
+ if (arg.getCategory() != mCategory) {
+ mInvalid = "Expected category " + mCategory + " but was " + arg.getCategory();
+ return false;
+ }
+ if (arg.getType() != TYPE_ACTION) {
+ mInvalid = "Expected type " + TYPE_ACTION + " but was " + arg.getType();
+ return false;
+ }
+ if (arg.getSubtype() != mTile.getMetricsCategory()) {
+ mInvalid = "Expected subtype " + mTile.getMetricsCategory() + " but was "
+ + arg.getSubtype();
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return mInvalid;
+ }
+ }
+
+ private static class TileImpl extends QSTileImpl<QSTile.BooleanState> {
+ protected TileImpl(QSHost host) {
+ super(host);
+ }
+
+ @Override
+ public BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ protected void handleClick() {
+
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return 42;
+ }
+
+ @Override
+ public Intent getLongClickIntent() {
+ return null;
+ }
+
+ @Override
+ protected void setListening(boolean listening) {
+
+ }
+
+ @Override
+ public CharSequence getTileLabel() {
+ return null;
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
index a9acda3..6ddbffc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
@@ -16,6 +16,7 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -104,6 +105,6 @@
fragment.disable(0, 0, false);
- Mockito.verify(mNotificationAreaInner).setVisibility(eq(View.VISIBLE));
+ Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index f48af75..bf6b394 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -62,9 +62,9 @@
mKeyguardIndicationController = mock(KeyguardIndicationController.class);
mStackScroller = mock(NotificationStackScrollLayout.class);
mMetricsLogger = new FakeMetricsLogger();
+ mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache,
mKeyguardIndicationController, mStackScroller);
- mStatusBar.setMetricsLogger(mMetricsLogger);
doAnswer(invocation -> {
OnDismissAction onDismissAction = (OnDismissAction) invocation.getArguments()[0];
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 783aae7..da441f5 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3656,7 +3656,7 @@
ACTION_SETTINGS_MASTER_SWITCH_BLUETOOTH_TOGGLE = 870;
// The name of the activity being launched in an app transition event.
- APP_TRANSITION_ACTIVITY_NAME = 871;
+ FIELD_CLASS_NAME = 871;
// ACTION: Settings > App detail > Uninstall
ACTION_SETTINGS_UNINSTALL_APP = 872;
@@ -3874,6 +3874,24 @@
// OPEN: Settings -> System -> Reset options
RESET_DASHBOARD = 924;
+ // ACTION: QS -> Tile clicked
+ ACTION_QS_CLICK = 925;
+
+ // ACTION: QS -> Secondary click
+ ACTION_QS_SECONDARY_CLICK = 926;
+
+ // FIELD: Position info in QS clicks
+ FIELD_QS_POSITION = 927;
+
+ // FIELD: The value of a QS tile when clicked (if applicable)
+ FIELD_QS_VALUE = 928;
+
+ // ACTION: QS -> Detail panel -> more settings
+ ACTION_QS_MORE_SETTINGS = 929;
+
+ // ACTION: QS -> Click date
+ ACTION_QS_DATE = 930;
+
// ---- End O Constants, all O constants go above this line ----
// Add new aosp constants above this line.
diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
index 04a09fe..5edfb06 100644
--- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
@@ -10,8 +10,8 @@
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.app.ActivityManagerInternal.APP_TRANSITION_TIMEOUT;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_ACTIVITY_NAME;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_CALLING_PACKAGE_NAME;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_CLASS_NAME;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DELAY_MS;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_IS_EPHEMERAL;
@@ -313,7 +313,7 @@
final LogMaker builder = new LogMaker(APP_TRANSITION);
builder.setPackageName(info.launchedActivity.packageName);
builder.setType(type);
- builder.addTaggedData(APP_TRANSITION_ACTIVITY_NAME, info.launchedActivity.info.name);
+ builder.addTaggedData(FIELD_CLASS_NAME, info.launchedActivity.info.name);
if (info.launchedActivity.launchedFromPackage != null) {
builder.addTaggedData(APP_TRANSITION_CALLING_PACKAGE_NAME,
info.launchedActivity.launchedFromPackage);
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index fd65c10..bff5f51 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -617,15 +617,15 @@
boolean kept = true;
try {
final ActivityRecord r = topRunningActivityLocked();
- final boolean wasFocused = supervisor.isFocusedStack(sourceStack)
+ final boolean wasFocused = r != null && supervisor.isFocusedStack(sourceStack)
&& (topRunningActivityLocked() == r);
- final boolean wasResumed = sourceStack.mResumedActivity == r;
- final boolean wasPaused = sourceStack.mPausingActivity == r;
+ final boolean wasResumed = r != null && sourceStack.mResumedActivity == r;
+ final boolean wasPaused = r != null && sourceStack.mPausingActivity == r;
// In some cases the focused stack isn't the front stack. E.g. pinned stack.
// Whenever we are moving the top activity from the front stack we want to make sure to
// move the stack to the front.
- final boolean wasFront = supervisor.isFrontStackOnDisplay(sourceStack)
+ final boolean wasFront = r != null && supervisor.isFrontStackOnDisplay(sourceStack)
&& (sourceStack.topRunningActivityLocked() == r);
// Adjust the position for the new parent stack as needed.
@@ -667,8 +667,10 @@
// new stack by moving the stack to the front.
final boolean moveStackToFront = moveStackMode == REPARENT_MOVE_STACK_TO_FRONT
|| (moveStackMode == REPARENT_KEEP_STACK_AT_FRONT && (wasFocused || wasFront));
- toStack.moveToFrontAndResumeStateIfNeeded(r, moveStackToFront, wasResumed, wasPaused,
- reason);
+ if (r != null) {
+ toStack.moveToFrontAndResumeStateIfNeeded(r, moveStackToFront, wasResumed,
+ wasPaused, reason);
+ }
if (!animate) {
toStack.mNoAnimActivities.add(topActivity);
}
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 72ae90d..1decf4e 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -19,6 +19,7 @@
import static android.app.ActivityManager.StackId;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
@@ -1204,12 +1205,23 @@
* in anyway.
*/
@Override
- int getOrientation() {
+ int getOrientation(int candidate) {
+ if (!fillsParent()) {
+ // Can't specify orientation if app doesn't fill parent.
+ return SCREEN_ORIENTATION_UNSET;
+ }
+
+ if (candidate == SCREEN_ORIENTATION_BEHIND) {
+ // Allow app to specify orientation regardless of its visibility state if the current
+ // candidate want us to use orientation behind. I.e. the visible app on-top of this one
+ // wants us to use the orientation of the app behind it.
+ return mOrientation;
+ }
+
// The {@link AppWindowToken} should only specify an orientation when it is not closing or
// going to the bottom. Allowing closing {@link AppWindowToken} to participate can lead to
// an Activity in another task being started in the wrong orientation during the transition.
- if (fillsParent()
- && !(sendingToBottom || mService.mClosingApps.contains(this))
+ if (!(sendingToBottom || mService.mClosingApps.contains(this))
&& (isVisible() || mService.mOpeningApps.contains(this))) {
return mOrientation;
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 68d0f24..b0e3e32 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -152,8 +152,7 @@
}
WindowState computeFocusedWindow() {
- final int count = mChildren.size();
- for (int i = 0; i < count; i++) {
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
final DisplayContent dc = mChildren.get(i);
final WindowState win = dc.findFocusedWindow();
if (win != null) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 2a02359..84ba139 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -516,14 +516,22 @@
mOrientation = orientation;
}
+ int getOrientation() {
+ return getOrientation(mOrientation);
+ }
+
/**
* Returns the specified orientation for this window container or one of its children is there
* is one set, or {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSET} if no
* specification is set.
* NOTE: {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED} is a
* specification...
+ *
+ * @param candidate The current orientation candidate that will be returned if we don't find a
+ * better match.
+ * @return The orientation as specified by this branch or the window hierarchy.
*/
- int getOrientation() {
+ int getOrientation(int candidate) {
if (!fillsParent()) {
// Ignore containers that don't completely fill their parents.
return SCREEN_ORIENTATION_UNSET;
@@ -537,12 +545,14 @@
&& mOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
return mOrientation;
}
- int candidate = mOrientation;
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
- final int orientation = wc.getOrientation();
+ // TODO: Maybe mOrientation should default to SCREEN_ORIENTATION_UNSET vs.
+ // SCREEN_ORIENTATION_UNSPECIFIED?
+ final int orientation = wc.getOrientation(candidate == SCREEN_ORIENTATION_BEHIND
+ ? SCREEN_ORIENTATION_BEHIND : SCREEN_ORIENTATION_UNSET);
if (orientation == SCREEN_ORIENTATION_BEHIND) {
// container wants us to use the orientation of the container behind it. See if we
// can find one. Else return SCREEN_ORIENTATION_BEHIND so the caller can choose to
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index f0732dd..da49eb3 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -317,7 +317,7 @@
// In case the runtime switched since last boot (such as when
// the old runtime was removed in an OTA), set the system
- // property so that it is in sync. We can't do this in
+ // property so that it is in sync. We can | xq oqi't do this in
// libnativehelper's JniInvocation::Init code where we already
// had to fallback to a different runtime because it is
// running as root and we need to be the system user to set
diff --git a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
index 9356dac..7790698 100644
--- a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
+++ b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
@@ -20,6 +20,7 @@
import static com.android.internal.util.Preconditions.checkNotNull;
import android.Manifest;
+import android.annotation.CheckResult;
import android.annotation.Nullable;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
@@ -36,8 +37,11 @@
import android.net.NetworkPolicyManager;
import android.os.Binder;
import android.os.Environment;
+import android.os.Handler;
import android.os.IBinder;
import android.os.IDeviceIdleController;
+import android.os.IInterface;
+import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -68,11 +72,13 @@
import java.util.function.Function;
//TODO move to own package!
-//TODO un/linkToDeath & onBinderDied - unbind
//TODO onStop schedule unbind in 5 seconds
-//TODO Prune association on app uninstall
+//TODO make sure APIs are only callable from currently focused app
+//TODO schedule stopScan on activity destroy(except if configuration change)
+//TODO on associate called again after configuration change -> replace old callback with new
+//TODO avoid leaking calling activity in IFindDeviceCallback (see PrintManager#print for example)
/** @hide */
-public class CompanionDeviceManagerService extends SystemService {
+public class CompanionDeviceManagerService extends SystemService implements Binder.DeathRecipient {
private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative(
CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
@@ -90,6 +96,8 @@
private final CompanionDeviceManagerImpl mImpl;
private final ConcurrentMap<Integer, AtomicFile> mUidToStorage = new ConcurrentHashMap<>();
private IDeviceIdleController mIdleController;
+ private IFindDeviceCallback mFindDeviceCallback;
+ private ServiceConnection mServiceConnection;
public CompanionDeviceManagerService(Context context) {
super(context);
@@ -125,7 +133,51 @@
publishBinderService(Context.COMPANION_DEVICE_SERVICE, mImpl);
}
+ @Override
+ public void binderDied() {
+ Handler.getMain().post(this::handleBinderDied);
+ }
+
+ private void handleBinderDied() {
+ mServiceConnection = unbind(mServiceConnection);
+ mFindDeviceCallback = unlinkToDeath(mFindDeviceCallback, this, 0);
+ }
+
+ /**
+ * Usage: {@code a = unlinkToDeath(a, deathRecipient, flags); }
+ */
+ @Nullable
+ @CheckResult
+ private static <T extends IInterface> T unlinkToDeath(T iinterface,
+ IBinder.DeathRecipient deathRecipient, int flags) {
+ if (iinterface != null) {
+ iinterface.asBinder().unlinkToDeath(deathRecipient, flags);
+ }
+ return null;
+ }
+
+ @Nullable
+ @CheckResult
+ private ServiceConnection unbind(@Nullable ServiceConnection conn) {
+ if (conn != null) {
+ getContext().unbindService(conn);
+ }
+ return null;
+ }
+
class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
+
+ @Override
+ public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ try {
+ return super.onTransact(code, data, reply, flags);
+ } catch (Throwable e) {
+ Slog.e(LOG_TAG, "Error during IPC", e);
+ throw ExceptionUtils.propagate(e, RemoteException.class);
+ }
+ }
+
@Override
public void associate(
AssociationRequest request,
@@ -135,14 +187,14 @@
Slog.i(LOG_TAG, "associate(request = " + request + ", callback = " + callback
+ ", callingPackage = " + callingPackage + ")");
}
- checkNotNull(request);
- checkNotNull(callback);
+ checkNotNull(request, "Request cannot be null");
+ checkNotNull(callback, "Callback cannot be null");
final long callingIdentity = Binder.clearCallingIdentity();
try {
//TODO bindServiceAsUser
getContext().bindService(
new Intent().setComponent(SERVICE_TO_BIND_TO),
- getServiceConnection(request, callback, callingPackage),
+ createServiceConnection(request, callback, callingPackage),
Context.BIND_AUTO_CREATE);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
@@ -168,11 +220,11 @@
return UserHandle.getUserId(Binder.getCallingUid());
}
- private ServiceConnection getServiceConnection(
+ private ServiceConnection createServiceConnection(
final AssociationRequest request,
final IFindDeviceCallback findDeviceCallback,
final String callingPackage) {
- return new ServiceConnection() {
+ mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (DEBUG) {
@@ -180,6 +232,14 @@
"onServiceConnected(name = " + name + ", service = "
+ service + ")");
}
+ mFindDeviceCallback = findDeviceCallback;
+ try {
+ mFindDeviceCallback.asBinder().linkToDeath(
+ CompanionDeviceManagerService.this, 0);
+ } catch (RemoteException e) {
+ handleBinderDied();
+ return;
+ }
try {
ICompanionDeviceDiscoveryService.Stub
.asInterface(service)
@@ -198,6 +258,7 @@
if (DEBUG) Slog.i(LOG_TAG, "onServiceDisconnected(name = " + name + ")");
}
};
+ return mServiceConnection;
}
private ICompanionDeviceDiscoveryServiceCallback.Stub getServiceCallback() {
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
index b5826f0..2003b91 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -26,8 +26,10 @@
import android.view.Surface;
import android.view.WindowManager;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -160,4 +162,22 @@
sWm.mRoot.mOrientationChangeComplete = true;
sWm.mRoot.performSurfacePlacement(false /* recoveringMemory */);
}
+
+ @Test
+ public void testGetOrientation() throws Exception {
+ final TestAppWindowToken token = new TestAppWindowToken(sDisplayContent);
+ token.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+
+ token.setFillsParent(false);
+ // Can not specify orientation if app doesn't fill parent.
+ assertEquals(SCREEN_ORIENTATION_UNSET, token.getOrientation());
+
+ token.setFillsParent(true);
+ token.hidden = true;
+ token.sendingToBottom = true;
+ // Can not specify orientation if app isn't visible even though it fills parent.
+ assertEquals(SCREEN_ORIENTATION_UNSET, token.getOrientation());
+ // Can specify orientation if the current orientation candidate is orientation behind.
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, token.getOrientation(SCREEN_ORIENTATION_BEHIND));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index 3868242..dd94a21 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -21,6 +21,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -233,6 +234,26 @@
assertEquals(currentOverrideConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
}
+ @Test
+ public void testFocusedWindowMultipleDisplays() throws Exception {
+ // Create a focusable window and check that focus is calcualted correctly
+ final WindowState window1 =
+ createWindow(null, TYPE_BASE_APPLICATION, sDisplayContent, "window1");
+ assertEquals(window1, sWm.mRoot.computeFocusedWindow());
+
+ // Check that a new display doesn't affect focus
+ final DisplayContent dc = createNewDisplay();
+ assertEquals(window1, sWm.mRoot.computeFocusedWindow());
+
+ // Add a window to the second display, and it should be focused
+ final WindowState window2 = createWindow(null, TYPE_BASE_APPLICATION, dc, "window2");
+ assertEquals(window2, sWm.mRoot.computeFocusedWindow());
+
+ // Move the first window to the to including parents, and make sure focus is updated
+ window1.getParent().positionChildAt(POSITION_TOP, window1, true);
+ assertEquals(window1, sWm.mRoot.computeFocusedWindow());
+ }
+
private void assertForAllWindowsOrder(List<WindowState> expectedWindows) {
final LinkedList<WindowState> actualWindows = new LinkedList();
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
index 3ce3df1..9dbd8a6 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
@@ -76,9 +76,9 @@
task2.addChild(appWindowToken2, 0);
appWindowToken2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
- assertEquals(stack.getOrientation(), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, stack.getOrientation());
sWm.mClosingApps.add(appWindowToken2);
- assertEquals(stack.getOrientation(), SCREEN_ORIENTATION_LANDSCAPE);
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, stack.getOrientation());
}
@Test
@@ -94,9 +94,9 @@
task2.addChild(appWindowToken2, 0);
appWindowToken2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
- assertEquals(stack.getOrientation(), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, stack.getOrientation());
task2.setSendingToBottom(true);
- assertEquals(stack.getOrientation(), SCREEN_ORIENTATION_LANDSCAPE);
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, stack.getOrientation());
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
index 80b2e7d..a7d594c 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
@@ -422,7 +422,7 @@
final TestWindowContainer child1 = root.addChildWindow(builder);
child1.setFillsParent(true);
- assertTrue(root.getOrientation() == expectedOrientation);
+ assertEquals(expectedOrientation, root.getOrientation());
}
@Test
@@ -805,8 +805,13 @@
}
@Override
+ int getOrientation(int candidate) {
+ return mOrientation != null ? mOrientation : super.getOrientation(candidate);
+ }
+
+ @Override
int getOrientation() {
- return mOrientation != null ? mOrientation : super.getOrientation();
+ return getOrientation(super.mOrientation);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 48799d2..a9d930f 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -16,6 +16,8 @@
package com.android.server.wm;
+import static android.view.View.VISIBLE;
+
import android.app.ActivityManager.TaskDescription;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -233,7 +235,7 @@
attrs.setTitle(name);
final WindowState w = new WindowState(sWm, sMockSession, sIWindow, token, parent, OP_NONE,
- 0, attrs, 0, 0, ownerCanAddInternalSystemWindow);
+ 0, attrs, VISIBLE, 0, ownerCanAddInternalSystemWindow);
// TODO: Probably better to make this call in the WindowState ctor to avoid errors with
// adding it to the token...
token.addWindow(w);
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index c790902..92233b1 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -125,8 +125,9 @@
public static final String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts";
/**
- * Extra key used to indicate the time (in millis) when the last outgoing emergency call was
- * made. This is used to identify potential emergency callbacks.
+ * Extra key used to indicate the time (in milliseconds since midnight, January 1, 1970 UTC)
+ * when the last outgoing emergency call was made. This is used to identify potential emergency
+ * callbacks.
*/
public static final String EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS =
"android.telecom.extra.LAST_EMERGENCY_CALLBACK_TIME_MILLIS";