Merge "Add a privileged API for capturing and consuming bugreports"
diff --git a/Android.bp b/Android.bp
index 15befae..c5f415d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -731,6 +731,7 @@
"android.hardware.contexthub-V1.0-java",
"android.hardware.health-V1.0-java-constants",
"android.hardware.thermal-V1.0-java-constants",
+ "android.hardware.thermal-V1.0-java",
"android.hardware.thermal-V1.1-java",
"android.hardware.thermal-V2.0-java",
"android.hardware.tv.input-V1.0-java-constants",
@@ -745,6 +746,7 @@
"android.hardware.radio-V1.3-java",
"android.hardware.radio-V1.4-java",
"android.hardware.usb.gadget-V1.0-java",
+ "networkstack-aidl-interfaces-java",
"netd_aidl_interface-java",
"devicepolicyprotosnano",
],
@@ -872,11 +874,16 @@
name: "networkstack-aidl-interfaces",
local_include_dir: "core/java",
srcs: [
+ "core/java/android/net/INetworkMonitor.aidl",
+ "core/java/android/net/INetworkMonitorCallbacks.aidl",
+ "core/java/android/net/IIpMemoryStore.aidl",
"core/java/android/net/INetworkStackConnector.aidl",
"core/java/android/net/INetworkStackStatusCallback.aidl",
+ "core/java/android/net/PrivateDnsConfigParcel.aidl",
"core/java/android/net/dhcp/DhcpServingParamsParcel.aidl",
"core/java/android/net/dhcp/IDhcpServer.aidl",
"core/java/android/net/dhcp/IDhcpServerCallbacks.aidl",
+ "core/java/android/net/ipmemorystore/**/*.aidl",
],
api_dir: "aidl/networkstack",
}
diff --git a/api/current.txt b/api/current.txt
index 7e0c33c..8dad532 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -26076,6 +26076,27 @@
field public static final int URI_COLUMN_INDEX = 2; // 0x2
}
+ public final class Session2Command implements android.os.Parcelable {
+ ctor public Session2Command(int);
+ ctor public Session2Command(java.lang.String, android.os.Bundle);
+ method public int describeContents();
+ method public int getCommandCode();
+ method public java.lang.String getCustomCommand();
+ method public android.os.Bundle getExtras();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final int COMMAND_CODE_CUSTOM = 0; // 0x0
+ field public static final android.os.Parcelable.Creator<android.media.Session2Command> CREATOR;
+ field public static final int RESULT_ERROR_UNKNOWN_ERROR = -1; // 0xffffffff
+ field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
+ field public static final int RESULT_SUCCESS = 0; // 0x0
+ }
+
+ public static final class Session2Command.Result {
+ ctor public Session2Command.Result(int, android.os.Bundle);
+ method public int getResultCode();
+ method public android.os.Bundle getResultData();
+ }
+
public class SoundPool {
ctor public deprecated SoundPool(int, int, int);
method public final void autoPause();
@@ -27121,6 +27142,21 @@
package android.media.session {
+ public final class ControllerCallbackLink implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.os.IBinder getBinder();
+ method public void notifyEvent(java.lang.String, android.os.Bundle);
+ method public void notifyExtrasChanged(android.os.Bundle);
+ method public void notifyMetadataChanged(android.media.MediaMetadata);
+ method public void notifyPlaybackStateChanged(android.media.session.PlaybackState);
+ method public void notifyQueueChanged(java.util.List<android.media.session.MediaSession.QueueItem>);
+ method public void notifyQueueTitleChanged(java.lang.CharSequence);
+ method public void notifySessionDestroyed();
+ method public void notifyVolumeInfoChanged(android.media.session.MediaController.PlaybackInfo);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.media.session.ControllerCallbackLink> CREATOR;
+ }
+
public final class MediaController {
ctor public MediaController(android.content.Context, android.media.session.MediaSession.Token);
method public void adjustVolume(int, int);
@@ -27355,6 +27391,36 @@
method public android.media.session.PlaybackState.CustomAction.Builder setExtras(android.os.Bundle);
}
+ public final class SessionCallbackLink implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.os.IBinder getBinder();
+ method public void notifyAdjustVolume(java.lang.String, int, int, android.media.session.ControllerCallbackLink, int);
+ method public void notifyCommand(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle, android.os.ResultReceiver);
+ method public void notifyCustomAction(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle);
+ method public void notifyFastForward(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+ method public void notifyMediaButton(java.lang.String, int, int, android.content.Intent, int, android.os.ResultReceiver);
+ method public void notifyMediaButtonFromController(java.lang.String, int, int, android.media.session.ControllerCallbackLink, android.content.Intent);
+ method public void notifyNext(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+ method public void notifyPause(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+ method public void notifyPlay(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+ method public void notifyPlayFromMediaId(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle);
+ method public void notifyPlayFromSearch(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle);
+ method public void notifyPlayFromUri(java.lang.String, int, int, android.media.session.ControllerCallbackLink, android.net.Uri, android.os.Bundle);
+ method public void notifyPrepare(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+ method public void notifyPrepareFromMediaId(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle);
+ method public void notifyPrepareFromSearch(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle);
+ method public void notifyPrepareFromUri(java.lang.String, int, int, android.media.session.ControllerCallbackLink, android.net.Uri, android.os.Bundle);
+ method public void notifyPrevious(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+ method public void notifyRate(java.lang.String, int, int, android.media.session.ControllerCallbackLink, android.media.Rating);
+ method public void notifyRewind(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+ method public void notifySeekTo(java.lang.String, int, int, android.media.session.ControllerCallbackLink, long);
+ method public void notifySetVolumeTo(java.lang.String, int, int, android.media.session.ControllerCallbackLink, int);
+ method public void notifySkipToTrack(java.lang.String, int, int, android.media.session.ControllerCallbackLink, long);
+ method public void notifyStop(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.media.session.SessionCallbackLink> CREATOR;
+ }
+
}
package android.media.tv {
@@ -42662,6 +42728,7 @@
method public static java.lang.String capabilitiesToString(int);
method public android.telecom.PhoneAccountHandle getAccountHandle();
method public int getCallCapabilities();
+ method public int getCallDirection();
method public android.telecom.CallIdentification getCallIdentification();
method public int getCallProperties();
method public java.lang.String getCallerDisplayName();
@@ -42698,6 +42765,9 @@
field public static final int CAPABILITY_SUPPORT_DEFLECT = 16777216; // 0x1000000
field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2
field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8
+ field public static final int DIRECTION_INCOMING = 0; // 0x0
+ field public static final int DIRECTION_OUTGOING = 1; // 0x1
+ field public static final int DIRECTION_UNKNOWN = -1; // 0xffffffff
field public static final int PROPERTY_CONFERENCE = 1; // 0x1
field public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 4; // 0x4
field public static final int PROPERTY_ENTERPRISE_CALL = 32; // 0x20
@@ -44853,6 +44923,7 @@
method public java.util.List<java.lang.Integer> getEmergencyNumberSources();
method public java.util.List<java.lang.Integer> getEmergencyServiceCategories();
method public int getEmergencyServiceCategoryBitmask();
+ method public java.util.List<java.lang.String> getEmergencyUrns();
method public java.lang.String getMnc();
method public java.lang.String getNumber();
method public boolean isFromSources(int);
@@ -44906,6 +44977,7 @@
method public boolean isEnabled();
method public void startResolutionActivity(android.app.Activity, int, android.content.Intent, android.app.PendingIntent) throws android.content.IntentSender.SendIntentException;
method public void switchToSubscription(int, android.app.PendingIntent);
+ method public void updateSubscriptionNickname(int, java.lang.String, android.app.PendingIntent);
field public static final java.lang.String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.telephony.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
field public static final java.lang.String ACTION_NOTIFY_CARRIER_SETUP_INCOMPLETE = "android.telephony.euicc.action.NOTIFY_CARRIER_SETUP_INCOMPLETE";
field public static final int EMBEDDED_SUBSCRIPTION_RESULT_ERROR = 2; // 0x2
@@ -49118,6 +49190,7 @@
method public float getAxisValue(int);
method public float getAxisValue(int, int);
method public int getButtonState();
+ method public int getClassification();
method public int getDeviceId();
method public long getDownTime();
method public int getEdgeFlags();
@@ -49269,6 +49342,9 @@
field public static final int BUTTON_STYLUS_PRIMARY = 32; // 0x20
field public static final int BUTTON_STYLUS_SECONDARY = 64; // 0x40
field public static final int BUTTON_TERTIARY = 4; // 0x4
+ field public static final int CLASSIFICATION_AMBIGUOUS_GESTURE = 1; // 0x1
+ field public static final int CLASSIFICATION_DEEP_PRESS = 2; // 0x2
+ field public static final int CLASSIFICATION_NONE = 0; // 0x0
field public static final android.os.Parcelable.Creator<android.view.MotionEvent> CREATOR;
field public static final int EDGE_BOTTOM = 2; // 0x2
field public static final int EDGE_LEFT = 4; // 0x4
@@ -52319,6 +52395,8 @@
method public final android.view.contentcapture.ContentCaptureSession createContentCaptureSession(android.view.contentcapture.ContentCaptureContext);
method public final void destroy();
method public final android.view.contentcapture.ContentCaptureSessionId getContentCaptureSessionId();
+ method public android.view.autofill.AutofillId newAutofillId(android.view.autofill.AutofillId, int);
+ method public final android.view.ViewStructure newVirtualViewStructure(android.view.autofill.AutofillId, int);
method public final void notifyViewAppeared(android.view.ViewStructure);
method public final void notifyViewDisappeared(android.view.autofill.AutofillId);
method public final void notifyViewTextChanged(android.view.autofill.AutofillId, java.lang.CharSequence, int);
diff --git a/api/system-current.txt b/api/system-current.txt
index cb1e96a..320a794 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -537,6 +537,13 @@
method public void onVrStateChanged(boolean);
}
+ public final class WallpaperColors implements android.os.Parcelable {
+ ctor public WallpaperColors(android.graphics.Color, android.graphics.Color, android.graphics.Color, int);
+ method public int getColorHints();
+ field public static final int HINT_SUPPORTS_DARK_TEXT = 1; // 0x1
+ field public static final int HINT_SUPPORTS_DARK_THEME = 2; // 0x2
+ }
+
public final class WallpaperInfo implements android.os.Parcelable {
method public boolean supportsAmbientMode();
}
@@ -2947,7 +2954,7 @@
method public void setLocationControllerExtraPackage(java.lang.String);
method public void setLocationControllerExtraPackageEnabled(boolean);
method public void setLocationEnabledForUser(boolean, android.os.UserHandle);
- method public boolean setProviderEnabledForUser(java.lang.String, boolean, android.os.UserHandle);
+ method public deprecated boolean setProviderEnabledForUser(java.lang.String, boolean, android.os.UserHandle);
method public boolean unregisterGnssBatchedLocationCallback(android.location.BatchedLocationCallback);
}
@@ -3207,6 +3214,22 @@
package android.media.session {
+ public final class ControllerCallbackLink implements android.os.Parcelable {
+ ctor public ControllerCallbackLink(android.media.session.ControllerCallbackLink.CallbackStub);
+ }
+
+ public static abstract class ControllerCallbackLink.CallbackStub {
+ ctor public ControllerCallbackLink.CallbackStub();
+ method public void onEvent(java.lang.String, android.os.Bundle);
+ method public void onExtrasChanged(android.os.Bundle);
+ method public void onMetadataChanged(android.media.MediaMetadata);
+ method public void onPlaybackStateChanged(android.media.session.PlaybackState);
+ method public void onQueueChanged(java.util.List<android.media.session.MediaSession.QueueItem>);
+ method public void onQueueTitleChanged(java.lang.CharSequence);
+ method public void onSessionDestroyed();
+ method public void onVolumeInfoChanged(android.media.session.MediaController.PlaybackInfo);
+ }
+
public final class MediaSessionManager {
method public void setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, android.os.Handler);
method public void setOnVolumeKeyLongPressListener(android.media.session.MediaSessionManager.OnVolumeKeyLongPressListener, android.os.Handler);
@@ -5249,6 +5272,7 @@
method public void onCreateContentCaptureSession(android.view.contentcapture.ContentCaptureContext, android.view.contentcapture.ContentCaptureSessionId);
method public void onDestroyContentCaptureSession(android.view.contentcapture.ContentCaptureSessionId);
method public void onDisconnected();
+ method public void onUserDataRemovalRequest(android.view.contentcapture.UserDataRemovalRequest);
method public final void setActivityContentCaptureEnabled(android.content.ComponentName, boolean);
method public final void setContentCaptureWhitelist(java.util.List<java.lang.String>, java.util.List<android.content.ComponentName>);
method public final void setPackageContentCaptureEnabled(java.lang.String, boolean);
@@ -6376,6 +6400,7 @@
public class SubscriptionInfo implements android.os.Parcelable {
method public java.util.List<android.telephony.UiccAccessRule> getAccessRules();
method public int getCardId();
+ method public int getProfileClass();
}
public class SubscriptionManager {
@@ -6385,6 +6410,11 @@
method public void setDefaultDataSubId(int);
method public void setDefaultSmsSubId(int);
field public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI;
+ field public static final int PROFILE_CLASS_DEFAULT = -1; // 0xffffffff
+ field public static final int PROFILE_CLASS_OPERATIONAL = 2; // 0x2
+ field public static final int PROFILE_CLASS_PROVISIONING = 1; // 0x1
+ field public static final int PROFILE_CLASS_TESTING = 0; // 0x0
+ field public static final int PROFILE_CLASS_UNSET = -1; // 0xffffffff
field public static final android.net.Uri VT_ENABLED_CONTENT_URI;
field public static final android.net.Uri WFC_ENABLED_CONTENT_URI;
field public static final android.net.Uri WFC_MODE_CONTENT_URI;
@@ -6846,6 +6876,7 @@
method public static int getCallTypeFromVideoState(int);
method public int getEmergencyCallRouting();
method public int getEmergencyServiceCategories();
+ method public java.util.List<java.lang.String> getEmergencyUrns();
method public android.telephony.ims.ImsStreamMediaProfile getMediaProfile();
method public int getRestrictCause();
method public int getServiceType();
@@ -6860,6 +6891,7 @@
method public void setCallRestrictCause(int);
method public void setEmergencyCallRouting(int);
method public void setEmergencyServiceCategories(int);
+ method public void setEmergencyUrns(java.util.List<java.lang.String>);
method public void updateCallExtras(android.telephony.ims.ImsCallProfile);
method public void updateCallType(android.telephony.ims.ImsCallProfile);
method public void updateMediaProfile(android.telephony.ims.ImsCallProfile);
diff --git a/cmds/media/src/com/android/commands/media/Media.java b/cmds/media/src/com/android/commands/media/Media.java
index 6788f7d..d160b73 100644
--- a/cmds/media/src/com/android/commands/media/Media.java
+++ b/cmds/media/src/com/android/commands/media/Media.java
@@ -19,12 +19,12 @@
import android.app.ActivityManager;
import android.content.Context;
-import android.content.pm.ParceledListSlice;
import android.media.MediaMetadata;
+import android.media.session.ControllerCallbackLink;
import android.media.session.ISessionController;
-import android.media.session.ISessionControllerCallback;
import android.media.session.ISessionManager;
import android.media.session.MediaController.PlaybackInfo;
+import android.media.session.MediaSession.QueueItem;
import android.media.session.PlaybackState;
import android.os.Bundle;
import android.os.HandlerThread;
@@ -178,13 +178,7 @@
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD));
}
- class ControllerMonitor extends ISessionControllerCallback.Stub {
- private final ISessionController mController;
-
- public ControllerMonitor(ISessionController controller) {
- mController = controller;
- }
-
+ class ControllerCallbackStub extends ControllerCallbackLink.CallbackStub {
@Override
public void onSessionDestroyed() {
System.out.println("onSessionDestroyed. Enter q to quit.");
@@ -208,25 +202,35 @@
}
@Override
- public void onQueueChanged(ParceledListSlice queue) throws RemoteException {
+ public void onQueueChanged(List<QueueItem> queue) {
System.out.println("onQueueChanged, "
- + (queue == null ? "null queue" : " size=" + queue.getList().size()));
+ + (queue == null ? "null queue" : " size=" + queue.size()));
}
@Override
- public void onQueueTitleChanged(CharSequence title) throws RemoteException {
+ public void onQueueTitleChanged(CharSequence title) {
System.out.println("onQueueTitleChange " + title);
}
@Override
- public void onExtrasChanged(Bundle extras) throws RemoteException {
+ public void onExtrasChanged(Bundle extras) {
System.out.println("onExtrasChanged " + extras);
}
@Override
- public void onVolumeInfoChanged(PlaybackInfo info) throws RemoteException {
+ public void onVolumeInfoChanged(PlaybackInfo info) {
System.out.println("onVolumeInfoChanged " + info);
}
+ }
+
+ private class ControllerMonitor {
+ private final ISessionController mController;
+ private final ControllerCallbackLink mControllerCallbackLink;
+
+ ControllerMonitor(ISessionController controller) {
+ mController = controller;
+ mControllerCallbackLink = new ControllerCallbackLink(new ControllerCallbackStub());
+ }
void printUsageMessage() {
try {
@@ -244,7 +248,7 @@
@Override
protected void onLooperPrepared() {
try {
- mController.registerCallbackListener(PACKAGE_NAME, ControllerMonitor.this);
+ mController.registerCallbackListener(PACKAGE_NAME, mControllerCallbackLink);
} catch (RemoteException e) {
System.out.println("Error registering monitor callback");
}
@@ -287,7 +291,7 @@
} finally {
cbThread.getLooper().quit();
try {
- mController.unregisterCallbackListener(this);
+ mController.unregisterCallbackListener(mControllerCallbackLink);
} catch (Exception e) {
// ignoring
}
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index e0e1b72..ed22df5 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -490,7 +490,6 @@
Landroid/location/ILocationManager$Stub;->TRANSACTION_getAllProviders:I
Landroid/location/ILocationManager;->getAllProviders()Ljava/util/List;
Landroid/location/ILocationManager;->getNetworkProviderPackage()Ljava/lang/String;
-Landroid/location/ILocationManager;->reportLocation(Landroid/location/Location;Z)V
Landroid/location/INetInitiatedListener$Stub;-><init>()V
Landroid/location/INetInitiatedListener;->sendNiResponse(II)Z
Landroid/location/LocationManager$ListenerTransport;-><init>(Landroid/location/LocationManager;Landroid/location/LocationListener;Landroid/os/Looper;)V
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index fdb71bb..c89848e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -121,6 +121,7 @@
import android.view.autofill.AutofillManager.AutofillClient;
import android.view.autofill.AutofillPopupWindow;
import android.view.autofill.IAutofillWindowPresenter;
+import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.ContentCaptureManager;
import android.widget.AdapterView;
import android.widget.Toast;
@@ -1045,14 +1046,19 @@
private void notifyContentCaptureManagerIfNeeded(@ContentCaptureNotificationType int type) {
final ContentCaptureManager cm = getContentCaptureManager();
- if (cm == null || !cm.isContentCaptureEnabled()) {
+ if (cm == null) {
return;
}
switch (type) {
case CONTENT_CAPTURE_START:
//TODO(b/111276913): decide whether the InteractionSessionId should be
// saved / restored in the activity bundle - probably not
- cm.onActivityStarted(mToken, getComponentName());
+ int flags = 0;
+ if ((getWindow().getAttributes().flags
+ & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
+ flags |= ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE;
+ }
+ cm.onActivityStarted(mToken, getComponentName(), flags);
break;
case CONTENT_CAPTURE_FLUSH:
cm.flush();
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index c021b72..83c6fac 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -95,8 +95,10 @@
import android.net.EthernetManager;
import android.net.IConnectivityManager;
import android.net.IEthernetManager;
+import android.net.IIpMemoryStore;
import android.net.IIpSecService;
import android.net.INetworkPolicyManager;
+import android.net.IpMemoryStore;
import android.net.IpSecManager;
import android.net.NetworkPolicyManager;
import android.net.NetworkScoreManager;
@@ -323,10 +325,21 @@
registerService(Context.NETWORK_STACK_SERVICE, NetworkStack.class,
new StaticServiceFetcher<NetworkStack>() {
- @Override
- public NetworkStack createService() {
- return new NetworkStack();
- }});
+ @Override
+ public NetworkStack createService() {
+ return new NetworkStack();
+ }});
+
+ registerService(Context.IP_MEMORY_STORE_SERVICE, IpMemoryStore.class,
+ new CachedServiceFetcher<IpMemoryStore>() {
+ @Override
+ public IpMemoryStore createService(final ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(
+ Context.IP_MEMORY_STORE_SERVICE);
+ IIpMemoryStore service = IIpMemoryStore.Stub.asInterface(b);
+ return new IpMemoryStore(ctx, service);
+ }});
registerService(Context.IPSEC_SERVICE, IpSecManager.class,
new CachedServiceFetcher<IpSecManager>() {
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index ace814a..38a98d3c8 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.SystemApi;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -56,6 +56,7 @@
* eg. A launcher may set its text color to black if this flag is specified.
* @hide
*/
+ @SystemApi
public static final int HINT_SUPPORTS_DARK_TEXT = 1 << 0;
/**
@@ -64,6 +65,7 @@
* eg. A launcher may set its drawer color to black if this flag is specified.
* @hide
*/
+ @SystemApi
public static final int HINT_SUPPORTS_DARK_THEME = 1 << 1;
/**
@@ -234,7 +236,7 @@
* @see WallpaperColors#fromDrawable(Drawable)
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
public WallpaperColors(@NonNull Color primaryColor, @Nullable Color secondaryColor,
@Nullable Color tertiaryColor, int colorHints) {
@@ -349,7 +351,7 @@
* @return True if dark text is supported.
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
public int getColorHints() {
return mColorHints;
}
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 2990b57..e0a15a5 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -19,6 +19,7 @@
import static android.app.ActivityThread.isSystem;
import static android.app.WindowConfigurationProto.ACTIVITY_TYPE;
import static android.app.WindowConfigurationProto.APP_BOUNDS;
+import static android.app.WindowConfigurationProto.BOUNDS;
import static android.app.WindowConfigurationProto.WINDOWING_MODE;
import static android.view.Surface.rotationToString;
@@ -585,6 +586,9 @@
}
protoOutputStream.write(WINDOWING_MODE, mWindowingMode);
protoOutputStream.write(ACTIVITY_TYPE, mActivityType);
+ if (mBounds != null) {
+ mBounds.writeToProto(protoOutputStream, BOUNDS);
+ }
protoOutputStream.end(token);
}
@@ -606,6 +610,10 @@
mAppBounds = new Rect();
mAppBounds.readFromProto(proto, APP_BOUNDS);
break;
+ case (int) BOUNDS:
+ mBounds = new Rect();
+ mBounds.readFromProto(proto, BOUNDS);
+ break;
case (int) WINDOWING_MODE:
mWindowingMode = proto.readInt(WINDOWING_MODE);
break;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 7da67d9..9247486 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6815,8 +6815,12 @@
}
/**
- * Returns the list of input methods permitted by the device or profiles
- * owners of the current user. (*Not* calling user, due to a limitation in InputMethodManager.)
+ * Returns the list of input methods permitted by the device or profiles owners.
+ *
+ * <p>On {@link android.os.Build.VERSION_CODES#Q} and later devices, this method returns the
+ * result for the calling user.</p>
+ *
+ * <p>On Android P and prior devices, this method returns the result for the current user.</p>
*
* <p>Null means all input methods are allowed, if a non-null list is returned
* it will contain the intersection of the permitted lists for any device or profile
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index cc4d4b1a..7d03f00 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -1699,7 +1699,8 @@
@Override
public void setVisibility(int visibility) {
- mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_VISIBILITY_MASK) | visibility;
+ mNode.mFlags = (mNode.mFlags & ~ViewNode.FLAGS_VISIBILITY_MASK)
+ | (visibility & ViewNode.FLAGS_VISIBILITY_MASK);
}
@Override
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index 2d630a6..d73e73f 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -67,6 +67,56 @@
private static final String LOG_TAG = RoleManager.class.getSimpleName();
/**
+ * The name of the proxy calling role.
+ * <p>
+ * A proxy calling app implements the {@link android.telecom.CallRedirectionService} API and
+ * provides a means to re-write the phone number for an outgoing call to place the call through
+ * a proxy calling service.
+ * <p>
+ * A single app may fill this role at any one time.
+ * @hide
+ */
+ public static final String ROLE_PROXY_CALLING_APP = "android.app.role.PROXY_CALLING_APP";
+
+ /**
+ * The name of the call screening and caller id role.
+ * <p>
+ * A call screening and caller id app implements the
+ * {@link android.telecom.CallScreeningService} API.
+ * <p>
+ * A single app may fill this role at any one time.
+ * @hide
+ */
+ public static final String ROLE_CALL_SCREENING_APP = "android.app.role.CALL_SCREENING_APP";
+
+ /**
+ * The name of the call companion app role.
+ * <p>
+ * A call companion app provides no user interface for calls, but will be bound to by Telecom
+ * when there are active calls on the device. Companion apps for wearable devices are an
+ * acceptable use-case. A call companion app implements the
+ * {@link android.telecom.InCallService} API.
+ * <p>
+ * Multiple apps app may fill this role at any one time.
+ * @hide
+ */
+ public static final String ROLE_CALL_COMPANION_APP = "android.app.role.CALL_COMPANION_APP";
+
+ /**
+ * The name of the car mode dialer app role.
+ * <p>
+ * Similar to the {@link #ROLE_DIALER} role, this role determines which app is responsible for
+ * showing the user interface for ongoing calls on the device. This app filling this role is
+ * only used when the device is in car mode (see
+ * {@link android.app.UiModeManager#ACTION_ENTER_CAR_MODE} for more information). An app
+ * filling this role must implement the {@link android.telecom.InCallService} API.
+ * <p>
+ * A single app may fill this role at any one time.
+ * @hide
+ */
+ public static final String ROLE_CAR_MODE_DIALER_APP = "android.app.role.CAR_MODE_DIALER_APP";
+
+ /**
* The name of the dialer role.
*/
public static final String ROLE_DIALER = "android.app.role.DIALER";
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 5e1be83..6f866ea 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3093,6 +3093,7 @@
VIBRATOR_SERVICE,
//@hide: STATUS_BAR_SERVICE,
CONNECTIVITY_SERVICE,
+ //@hide: IP_MEMORY_STORE_SERVICE,
IPSEC_SERVICE,
//@hide: UPDATE_LOCK_SERVICE,
//@hide: NETWORKMANAGEMENT_SERVICE,
@@ -3630,6 +3631,14 @@
/**
* Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.net.IpMemoryStore} to store and read information about
+ * known networks.
+ * @hide
+ */
+ public static final String IP_MEMORY_STORE_SERVICE = "ipmemorystore";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
* {@link android.net.IpSecManager} for encrypting Sockets or Networks with
* IPSec.
*
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 2b266b7..c984651 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1621,7 +1621,7 @@
}
final AttributeSet attrs = parser;
- return parseApkLite(apkPath, parser, attrs, signingDetails);
+ return parseApkLite(apkPath, parser, attrs, signingDetails, flags);
} catch (XmlPullParserException | IOException | RuntimeException e) {
Slog.w(TAG, "Failed to parse " + apkPath, e);
@@ -1708,7 +1708,7 @@
}
private static ApkLite parseApkLite(String codePath, XmlPullParser parser, AttributeSet attrs,
- SigningDetails signingDetails)
+ SigningDetails signingDetails, int flags)
throws IOException, XmlPullParserException, PackageParserException {
final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs);
@@ -1716,11 +1716,12 @@
int versionCode = 0;
int versionCodeMajor = 0;
int revisionCode = 0;
+ int targetSdkVersion = 0;
boolean coreApp = false;
boolean debuggable = false;
boolean multiArch = false;
boolean use32bitAbi = false;
- boolean extractNativeLibs = true;
+ Boolean extractNativeLibsProvided = null;
boolean isolatedSplits = false;
boolean isFeatureSplit = false;
boolean isSplitRequired = false;
@@ -1785,7 +1786,8 @@
use32bitAbi = attrs.getAttributeBooleanValue(i, false);
}
if ("extractNativeLibs".equals(attr)) {
- extractNativeLibs = attrs.getAttributeBooleanValue(i, true);
+ extractNativeLibsProvided = Boolean.valueOf(
+ attrs.getAttributeBooleanValue(i, true));
}
if ("preferCodeIntegrity".equals(attr)) {
preferCodeIntegrity = attrs.getAttributeBooleanValue(i, false);
@@ -1803,9 +1805,51 @@
PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"<uses-split> tag requires 'android:name' attribute");
}
+ } else if (TAG_USES_SDK.equals(parser.getName())) {
+ final String[] errorMsg = new String[1];
+ Pair<Integer, Integer> versions = deriveSdkVersions(new AbstractVersionsAccessor() {
+ @Override public String getMinSdkVersionCode() {
+ return getAttributeAsString("minSdkVersion");
+ }
+
+ @Override public int getMinSdkVersion() {
+ return getAttributeAsInt("minSdkVersion");
+ }
+
+ @Override public String getTargetSdkVersionCode() {
+ return getAttributeAsString("targetSdkVersion");
+ }
+
+ @Override public int getTargetSdkVersion() {
+ return getAttributeAsInt("targetSdkVersion");
+ }
+
+ private String getAttributeAsString(String name) {
+ return attrs.getAttributeValue(ANDROID_RESOURCES, name);
+ }
+
+ private int getAttributeAsInt(String name) {
+ try {
+ return attrs.getAttributeIntValue(ANDROID_RESOURCES, name, -1);
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ }
+ }, flags, errorMsg);
+
+ if (versions == null) {
+ throw new PackageParserException(
+ PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, errorMsg[0]);
+ }
+
+ targetSdkVersion = versions.second;
}
}
+ final boolean extractNativeLibsDefault = targetSdkVersion < Build.VERSION_CODES.Q;
+ final boolean extractNativeLibs = (extractNativeLibsProvided != null)
+ ? extractNativeLibsProvided : extractNativeLibsDefault;
+
if (preferCodeIntegrity && extractNativeLibs) {
throw new PackageParserException(
PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
@@ -2215,65 +2259,60 @@
} else if (tagName.equals(TAG_USES_SDK)) {
if (SDK_VERSION > 0) {
- sa = res.obtainAttributes(parser,
- com.android.internal.R.styleable.AndroidManifestUsesSdk);
+ sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesSdk);
+ final TypedArray saFinal = sa;
+ Pair<Integer, Integer> versions = deriveSdkVersions(
+ new AbstractVersionsAccessor() {
+ @Override public String getMinSdkVersionCode() {
+ return getAttributeAsString(
+ R.styleable.AndroidManifestUsesSdk_minSdkVersion);
+ }
- int minVers = 1;
- String minCode = null;
- int targetVers = 0;
- String targetCode = null;
+ @Override public int getMinSdkVersion() {
+ return getAttributeAsInt(
+ R.styleable.AndroidManifestUsesSdk_minSdkVersion);
+ }
- TypedValue val = sa.peekValue(
- com.android.internal.R.styleable.AndroidManifestUsesSdk_minSdkVersion);
- if (val != null) {
- if (val.type == TypedValue.TYPE_STRING && val.string != null) {
- minCode = val.string.toString();
- } else {
- // If it's not a string, it's an integer.
- minVers = val.data;
- }
+ @Override public String getTargetSdkVersionCode() {
+ return getAttributeAsString(
+ R.styleable.AndroidManifestUsesSdk_targetSdkVersion);
+ }
+
+ @Override public int getTargetSdkVersion() {
+ return getAttributeAsInt(
+ R.styleable.AndroidManifestUsesSdk_targetSdkVersion);
+ }
+
+ private String getAttributeAsString(int index) {
+ TypedValue val = saFinal.peekValue(index);
+ if (val != null && val.type == TypedValue.TYPE_STRING
+ && val.string != null) {
+ return val.string.toString();
+ }
+ return null;
+ }
+
+ private int getAttributeAsInt(int index) {
+ TypedValue val = saFinal.peekValue(index);
+ if (val != null && val.type != TypedValue.TYPE_STRING) {
+ // If it's not a string, it's an integer.
+ return val.data;
+ }
+ return -1;
+ }
+ }, flags, outError);
+
+ if (versions == null) {
+ mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
+ return null;
}
- val = sa.peekValue(
- com.android.internal.R.styleable.AndroidManifestUsesSdk_targetSdkVersion);
- if (val != null) {
- if (val.type == TypedValue.TYPE_STRING && val.string != null) {
- targetCode = val.string.toString();
- if (minCode == null) {
- minCode = targetCode;
- }
- } else {
- // If it's not a string, it's an integer.
- targetVers = val.data;
- }
- } else {
- targetVers = minVers;
- targetCode = minCode;
- }
+ pkg.applicationInfo.minSdkVersion = versions.first;
+ pkg.applicationInfo.targetSdkVersion = versions.second;
sa.recycle();
-
- final int minSdkVersion = PackageParser.computeMinSdkVersion(minVers, minCode,
- SDK_VERSION, SDK_CODENAMES, outError);
- if (minSdkVersion < 0) {
- mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
- return null;
- }
-
- boolean defaultToCurrentDevBranch = (flags & PARSE_FORCE_SDK) != 0;
- final int targetSdkVersion = PackageParser.computeTargetSdkVersion(targetVers,
- targetCode, SDK_CODENAMES, outError, defaultToCurrentDevBranch);
- if (targetSdkVersion < 0) {
- mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
- return null;
- }
-
- pkg.applicationInfo.minSdkVersion = minSdkVersion;
- pkg.applicationInfo.targetSdkVersion = targetSdkVersion;
}
-
XmlUtils.skipCurrentTag(parser);
-
} else if (tagName.equals(TAG_SUPPORT_SCREENS)) {
sa = res.obtainAttributes(parser,
com.android.internal.R.styleable.AndroidManifestSupportsScreens);
@@ -2675,6 +2714,67 @@
return -1;
}
+ private interface AbstractVersionsAccessor {
+ /** Returns minimum SDK version code string, or null if absent. */
+ String getMinSdkVersionCode();
+
+ /** Returns minimum SDK version code, or -1 if absent. */
+ int getMinSdkVersion();
+
+ /** Returns target SDK version code string, or null if absent. */
+ String getTargetSdkVersionCode();
+
+ /** Returns target SDK version code, or -1 if absent. */
+ int getTargetSdkVersion();
+ }
+
+ private static @Nullable Pair<Integer, Integer> deriveSdkVersions(
+ @NonNull AbstractVersionsAccessor accessor, int flags, String[] outError) {
+ int minVers = 1;
+ String minCode = null;
+ int targetVers = 0;
+ String targetCode = null;
+
+ String code = accessor.getMinSdkVersionCode();
+ int version = accessor.getMinSdkVersion();
+ // Check integer first since code is almost never a null string (e.g. "28").
+ if (version >= 0) {
+ minVers = version;
+ } else if (code != null) {
+ minCode = code;
+ }
+
+ code = accessor.getTargetSdkVersionCode();
+ version = accessor.getTargetSdkVersion();
+ // Check integer first since code is almost never a null string (e.g. "28").
+ if (version >= 0) {
+ targetVers = version;
+ } else if (code != null) {
+ targetCode = code;
+ if (minCode == null) {
+ minCode = targetCode;
+ }
+ } else {
+ targetVers = minVers;
+ targetCode = minCode;
+ }
+
+ final int minSdkVersion = computeMinSdkVersion(minVers, minCode,
+ SDK_VERSION, SDK_CODENAMES, outError);
+ if (minSdkVersion < 0) {
+ return null;
+ }
+
+ boolean defaultToCurrentDevBranch = (flags & PARSE_FORCE_SDK) != 0;
+ final int targetSdkVersion = computeTargetSdkVersion(targetVers,
+ targetCode, SDK_CODENAMES, outError, defaultToCurrentDevBranch);
+ if (targetSdkVersion < 0) {
+ return null;
+ }
+
+ return Pair.create(minSdkVersion, targetSdkVersion);
+ }
+
/**
* Computes the minSdkVersion to use at runtime. If the package is not
* compatible with this platform, populates {@code outError[0]} with an
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index b238d77..f652f85 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -73,6 +73,10 @@
* @hide
*/
public static final String KEY_NEGATIVE_TEXT = "negative_text";
+ /**
+ * @hide
+ */
+ public static final String KEY_REQUIRE_CONFIRMATION = "require_confirmation";
/**
* Error/help message will show for this amount of time.
@@ -215,6 +219,30 @@
}
/**
+ * Optional: A hint to the system to require user confirmation after a biometric has been
+ * authenticated. For example, implicit modalities like Face and Iris authentication are
+ * passive, meaning they don't require an explicit user action to complete. When set to
+ * 'false', the user action (e.g. pressing a button) will not be required. BiometricPrompt
+ * will require confirmation by default.
+ *
+ * A typical use case for not requiring confirmation would be for low-risk transactions,
+ * such as re-authenticating a recently authenticated application. A typical use case for
+ * requiring confirmation would be for authorizing a purchase.
+ *
+ * Note that this is a hint to the system. The system may choose to ignore the flag. For
+ * example, if the user disables implicit authentication in Settings, or if it does not
+ * apply to a modality (e.g. Fingerprint). When ignored, the system will default to
+ * requiring confirmation.
+ *
+ * @param requireConfirmation
+ * @hide
+ */
+ public Builder setRequireConfirmation(boolean requireConfirmation) {
+ mBundle.putBoolean(KEY_REQUIRE_CONFIRMATION, requireConfirmation);
+ return this;
+ }
+
+ /**
* Creates a {@link BiometricPrompt}.
* @return a {@link BiometricPrompt}
* @throws IllegalArgumentException if any of the required fields are not set.
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 436b4a1..abc00fe 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -2051,6 +2051,16 @@
return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
}
+ /** @hide */
+ public NetworkRequest getDefaultRequest() {
+ try {
+ // This is not racy as the default request is final in ConnectivityService.
+ return mService.getDefaultRequest();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/* TODO: These permissions checks don't belong in client-side code. Move them to
* services.jar, possibly in com.android.server.net. */
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index e7d441d..da5d96e 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -167,6 +167,8 @@
int getMultipathPreference(in Network Network);
+ NetworkRequest getDefaultRequest();
+
int getRestoreDefaultNetworkDelay(int networkType);
boolean addVpnAddress(String address, int prefixLength);
diff --git a/core/java/android/net/IIpMemoryStore.aidl b/core/java/android/net/IIpMemoryStore.aidl
new file mode 100644
index 0000000..6f88dec
--- /dev/null
+++ b/core/java/android/net/IIpMemoryStore.aidl
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.IOnBlobRetrievedListener;
+import android.net.ipmemorystore.IOnL2KeyResponseListener;
+import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
+import android.net.ipmemorystore.IOnSameNetworkResponseListener;
+import android.net.ipmemorystore.IOnStatusListener;
+
+/** {@hide} */
+oneway interface IIpMemoryStore {
+ /**
+ * Store network attributes for a given L2 key.
+ * If L2Key is null, choose automatically from the attributes ; passing null is equivalent to
+ * calling findL2Key with the attributes and storing in the returned value.
+ *
+ * @param l2Key The L2 key for the L2 network. Clients that don't know or care about the L2
+ * key and only care about grouping can pass a unique ID here like the ones
+ * generated by {@code java.util.UUID.randomUUID()}, but keep in mind the low
+ * relevance of such a network will lead to it being evicted soon if it's not
+ * refreshed. Use findL2Key to try and find a similar L2Key to these attributes.
+ * @param attributes The attributes for this network.
+ * @param listener A listener that will be invoked to inform of the completion of this call,
+ * or null if the client is not interested in learning about success/failure.
+ * @return (through the listener) The L2 key. This is useful if the L2 key was not specified.
+ * If the call failed, the L2 key will be null.
+ */
+ void storeNetworkAttributes(String l2Key, in NetworkAttributesParcelable attributes,
+ IOnStatusListener listener);
+
+ /**
+ * Store a binary blob associated with an L2 key and a name.
+ *
+ * @param l2Key The L2 key for this network.
+ * @param clientId The ID of the client.
+ * @param name The name of this data.
+ * @param data The data to store.
+ * @param listener A listener to inform of the completion of this call, or null if the client
+ * is not interested in learning about success/failure.
+ * @return (through the listener) A status to indicate success or failure.
+ */
+ void storeBlob(String l2Key, String clientId, String name, in Blob data,
+ IOnStatusListener listener);
+
+ /**
+ * Returns the best L2 key associated with the attributes.
+ *
+ * This will find a record that would be in the same group as the passed attributes. This is
+ * useful to choose the key for storing a sample or private data when the L2 key is not known.
+ * If multiple records are group-close to these attributes, the closest match is returned.
+ * If multiple records have the same closeness, the one with the smaller (unicode codepoint
+ * order) L2 key is returned.
+ * If no record matches these attributes, null is returned.
+ *
+ * @param attributes The attributes of the network to find.
+ * @param listener The listener that will be invoked to return the answer.
+ * @return (through the listener) The L2 key if one matched, or null.
+ */
+ void findL2Key(in NetworkAttributesParcelable attributes, IOnL2KeyResponseListener listener);
+
+ /**
+ * Returns whether, to the best of the store's ability to tell, the two specified L2 keys point
+ * to the same L3 network. Group-closeness is used to determine this.
+ *
+ * @param l2Key1 The key for the first network.
+ * @param l2Key2 The key for the second network.
+ * @param listener The listener that will be invoked to return the answer.
+ * @return (through the listener) A SameL3NetworkResponse containing the answer and confidence.
+ */
+ void isSameNetwork(String l2Key1, String l2Key2, IOnSameNetworkResponseListener listener);
+
+ /**
+ * Retrieve the network attributes for a key.
+ * If no record is present for this key, this will return null attributes.
+ *
+ * @param l2Key The key of the network to query.
+ * @param listener The listener that will be invoked to return the answer.
+ * @return (through the listener) The network attributes and the L2 key associated with
+ * the query.
+ */
+ void retrieveNetworkAttributes(String l2Key, IOnNetworkAttributesRetrieved listener);
+
+ /**
+ * Retrieve previously stored private data.
+ * If no data was stored for this L2 key and name this will return null.
+ *
+ * @param l2Key The L2 key.
+ * @param clientId The id of the client that stored this data.
+ * @param name The name of the data.
+ * @param listener The listener that will be invoked to return the answer.
+ * @return (through the listener) The private data (or null), with the L2 key
+ * and the name of the data associated with the query.
+ */
+ void retrieveBlob(String l2Key, String clientId, String name,
+ IOnBlobRetrievedListener listener);
+}
diff --git a/core/java/android/net/INetworkMonitor.aidl b/core/java/android/net/INetworkMonitor.aidl
new file mode 100644
index 0000000..41f969a
--- /dev/null
+++ b/core/java/android/net/INetworkMonitor.aidl
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing perNmissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import android.net.PrivateDnsConfigParcel;
+
+/** @hide */
+oneway interface INetworkMonitor {
+ // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
+ // The network should be used as a default internet connection. It was found to be:
+ // 1. a functioning network providing internet access, or
+ // 2. a captive portal and the user decided to use it as is.
+ const int NETWORK_TEST_RESULT_VALID = 0;
+
+ // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
+ // The network should not be used as a default internet connection. It was found to be:
+ // 1. a captive portal and the user is prompted to sign-in, or
+ // 2. a captive portal and the user did not want to use it, or
+ // 3. a broken network (e.g. DNS failed, connect failed, HTTP request failed).
+ const int NETWORK_TEST_RESULT_INVALID = 1;
+
+ void start();
+ void launchCaptivePortalApp();
+ void forceReevaluation(int uid);
+ void notifyPrivateDnsChanged(in PrivateDnsConfigParcel config);
+ void notifyDnsResponse(int returnCode);
+ void notifySystemReady();
+ void notifyNetworkConnected();
+ void notifyNetworkDisconnected();
+ void notifyLinkPropertiesChanged();
+ void notifyNetworkCapabilitiesChanged();
+}
\ No newline at end of file
diff --git a/core/java/android/net/INetworkMonitorCallbacks.aidl b/core/java/android/net/INetworkMonitorCallbacks.aidl
new file mode 100644
index 0000000..0bc2575
--- /dev/null
+++ b/core/java/android/net/INetworkMonitorCallbacks.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.INetworkMonitor;
+import android.net.PrivateDnsConfigParcel;
+
+/** @hide */
+oneway interface INetworkMonitorCallbacks {
+ void onNetworkMonitorCreated(in INetworkMonitor networkMonitor);
+ void notifyNetworkTested(int testResult, @nullable String redirectUrl);
+ void notifyPrivateDnsConfigResolved(in PrivateDnsConfigParcel config);
+ void showProvisioningNotification(String action);
+ void hideProvisioningNotification();
+}
\ No newline at end of file
diff --git a/core/java/android/net/INetworkStackConnector.aidl b/core/java/android/net/INetworkStackConnector.aidl
index be0dc07..2df8ab7 100644
--- a/core/java/android/net/INetworkStackConnector.aidl
+++ b/core/java/android/net/INetworkStackConnector.aidl
@@ -15,6 +15,7 @@
*/
package android.net;
+import android.net.INetworkMonitorCallbacks;
import android.net.dhcp.DhcpServingParamsParcel;
import android.net.dhcp.IDhcpServerCallbacks;
@@ -22,4 +23,5 @@
oneway interface INetworkStackConnector {
void makeDhcpServer(in String ifName, in DhcpServingParamsParcel params,
in IDhcpServerCallbacks cb);
+ void makeNetworkMonitor(int netId, String name, in INetworkMonitorCallbacks cb);
}
\ No newline at end of file
diff --git a/core/java/android/net/IpMemoryStore.java b/core/java/android/net/IpMemoryStore.java
new file mode 100644
index 0000000..b35f097
--- /dev/null
+++ b/core/java/android/net/IpMemoryStore.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.IOnBlobRetrievedListener;
+import android.net.ipmemorystore.IOnL2KeyResponseListener;
+import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
+import android.net.ipmemorystore.IOnSameNetworkResponseListener;
+import android.net.ipmemorystore.IOnStatusListener;
+import android.net.ipmemorystore.NetworkAttributes;
+import android.os.RemoteException;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * The interface for system components to access the IP memory store.
+ * @see com.android.server.net.ipmemorystore.IpMemoryStoreService
+ * @hide
+ */
+@SystemService(Context.IP_MEMORY_STORE_SERVICE)
+public class IpMemoryStore {
+ @NonNull final Context mContext;
+ @NonNull final IIpMemoryStore mService;
+
+ public IpMemoryStore(@NonNull final Context context, @NonNull final IIpMemoryStore service) {
+ mContext = Preconditions.checkNotNull(context, "missing context");
+ mService = Preconditions.checkNotNull(service, "missing IIpMemoryStore");
+ }
+
+ /**
+ * Store network attributes for a given L2 key.
+ * If L2Key is null, choose automatically from the attributes ; passing null is equivalent to
+ * calling findL2Key with the attributes and storing in the returned value.
+ *
+ * @param l2Key The L2 key for the L2 network. Clients that don't know or care about the L2
+ * key and only care about grouping can pass a unique ID here like the ones
+ * generated by {@code java.util.UUID.randomUUID()}, but keep in mind the low
+ * relevance of such a network will lead to it being evicted soon if it's not
+ * refreshed. Use findL2Key to try and find a similar L2Key to these attributes.
+ * @param attributes The attributes for this network.
+ * @param listener A listener that will be invoked to inform of the completion of this call,
+ * or null if the client is not interested in learning about success/failure.
+ * Through the listener, returns the L2 key. This is useful if the L2 key was not specified.
+ * If the call failed, the L2 key will be null.
+ */
+ public void storeNetworkAttributes(@NonNull final String l2Key,
+ @NonNull final NetworkAttributes attributes,
+ @Nullable final IOnStatusListener listener) {
+ try {
+ mService.storeNetworkAttributes(l2Key, attributes.toParcelable(), listener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Store a binary blob associated with an L2 key and a name.
+ *
+ * @param l2Key The L2 key for this network.
+ * @param clientId The ID of the client.
+ * @param name The name of this data.
+ * @param data The data to store.
+ * @param listener A listener to inform of the completion of this call, or null if the client
+ * is not interested in learning about success/failure.
+ * Through the listener, returns a status to indicate success or failure.
+ */
+ public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId,
+ @NonNull final String name, @NonNull final Blob data,
+ @Nullable final IOnStatusListener listener) {
+ try {
+ mService.storeBlob(l2Key, clientId, name, data, listener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the best L2 key associated with the attributes.
+ *
+ * This will find a record that would be in the same group as the passed attributes. This is
+ * useful to choose the key for storing a sample or private data when the L2 key is not known.
+ * If multiple records are group-close to these attributes, the closest match is returned.
+ * If multiple records have the same closeness, the one with the smaller (unicode codepoint
+ * order) L2 key is returned.
+ * If no record matches these attributes, null is returned.
+ *
+ * @param attributes The attributes of the network to find.
+ * @param listener The listener that will be invoked to return the answer.
+ * Through the listener, returns the L2 key if one matched, or null.
+ */
+ public void findL2Key(@NonNull final NetworkAttributes attributes,
+ @NonNull final IOnL2KeyResponseListener listener) {
+ try {
+ mService.findL2Key(attributes.toParcelable(), listener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether, to the best of the store's ability to tell, the two specified L2 keys point
+ * to the same L3 network. Group-closeness is used to determine this.
+ *
+ * @param l2Key1 The key for the first network.
+ * @param l2Key2 The key for the second network.
+ * @param listener The listener that will be invoked to return the answer.
+ * Through the listener, a SameL3NetworkResponse containing the answer and confidence.
+ */
+ public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2,
+ @NonNull final IOnSameNetworkResponseListener listener) {
+ try {
+ mService.isSameNetwork(l2Key1, l2Key2, listener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Retrieve the network attributes for a key.
+ * If no record is present for this key, this will return null attributes.
+ *
+ * @param l2Key The key of the network to query.
+ * @param listener The listener that will be invoked to return the answer.
+ * Through the listener, returns the network attributes and the L2 key associated with
+ * the query.
+ */
+ public void retrieveNetworkAttributes(@NonNull final String l2Key,
+ @NonNull final IOnNetworkAttributesRetrieved listener) {
+ try {
+ mService.retrieveNetworkAttributes(l2Key, listener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Retrieve previously stored private data.
+ * If no data was stored for this L2 key and name this will return null.
+ *
+ * @param l2Key The L2 key.
+ * @param clientId The id of the client that stored this data.
+ * @param name The name of the data.
+ * @param listener The listener that will be invoked to return the answer.
+ * Through the listener, returns the private data (or null), with the L2 key
+ * and the name of the data associated with the query.
+ */
+ public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId,
+ @NonNull final String name, @NonNull final IOnBlobRetrievedListener listener) {
+ try {
+ mService.retrieveBlob(l2Key, clientId, name, listener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java
index d4a0ec63..2eac6de 100644
--- a/core/java/android/net/NetworkStack.java
+++ b/core/java/android/net/NetworkStack.java
@@ -48,14 +48,16 @@
public class NetworkStack {
private static final String TAG = NetworkStack.class.getSimpleName();
+ public static final String NETWORKSTACK_PACKAGE_NAME = "com.android.mainline.networkstack";
+
@NonNull
@GuardedBy("mPendingNetStackRequests")
- private final ArrayList<NetworkStackRequest> mPendingNetStackRequests = new ArrayList<>();
+ private final ArrayList<NetworkStackCallback> mPendingNetStackRequests = new ArrayList<>();
@Nullable
@GuardedBy("mPendingNetStackRequests")
private INetworkStackConnector mConnector;
- private interface NetworkStackRequest {
+ private interface NetworkStackCallback {
void onNetworkStackConnected(INetworkStackConnector connector);
}
@@ -77,6 +79,21 @@
});
}
+ /**
+ * Create a NetworkMonitor.
+ *
+ * <p>The INetworkMonitor will be returned asynchronously through the provided callbacks.
+ */
+ public void makeNetworkMonitor(Network network, String name, INetworkMonitorCallbacks cb) {
+ requestConnector(connector -> {
+ try {
+ connector.makeNetworkMonitor(network.netId, name, cb);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ });
+ }
+
private class NetworkStackConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
@@ -96,14 +113,14 @@
ServiceManager.addService(Context.NETWORK_STACK_SERVICE, service, false /* allowIsolated */,
DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
- final ArrayList<NetworkStackRequest> requests;
+ final ArrayList<NetworkStackCallback> requests;
synchronized (mPendingNetStackRequests) {
requests = new ArrayList<>(mPendingNetStackRequests);
mPendingNetStackRequests.clear();
mConnector = connector;
}
- for (NetworkStackRequest r : requests) {
+ for (NetworkStackCallback r : requests) {
r.onNetworkStackConnected(connector);
}
}
@@ -124,7 +141,8 @@
"com.android.server.NetworkStackService",
true /* initialize */,
context.getClassLoader());
- connector = (IBinder) service.getMethod("makeConnector").invoke(null);
+ connector = (IBinder) service.getMethod("makeConnector", Context.class)
+ .invoke(null, context);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
Slog.wtf(TAG, "Could not create network stack connector from NetworkStackService");
// TODO: crash/reboot system here ?
@@ -153,7 +171,7 @@
}
// TODO: use this method to obtain the connector when implementing network stack operations
- private void requestConnector(@NonNull NetworkStackRequest request) {
+ private void requestConnector(@NonNull NetworkStackCallback request) {
// TODO: PID check.
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
// Don't even attempt to obtain the connector and give a nice error message
diff --git a/core/java/android/net/PrivateDnsConfigParcel.aidl b/core/java/android/net/PrivateDnsConfigParcel.aidl
new file mode 100644
index 0000000..b52fce6
--- /dev/null
+++ b/core/java/android/net/PrivateDnsConfigParcel.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+parcelable PrivateDnsConfigParcel {
+ String hostname;
+ String[] ips;
+}
diff --git a/core/java/android/net/ipmemorystore/Blob.aidl b/core/java/android/net/ipmemorystore/Blob.aidl
new file mode 100644
index 0000000..9dbef11
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/Blob.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+/**
+ * A blob of data opaque to the memory store. The client mutates this at its own risk,
+ * and it is strongly suggested to never do it at all and treat this as immutable.
+ * {@hide}
+ */
+parcelable Blob {
+ byte[] data;
+}
diff --git a/core/java/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl b/core/java/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
new file mode 100644
index 0000000..4926feb
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnBlobRetrievedListener {
+ /**
+ * Private data was retrieved for the L2 key and name specified.
+ * Note this does not return the client ID, as clients are expected to only ever use one ID.
+ */
+ void onBlobRetrieved(in StatusParcelable status, in String l2Key, in String name,
+ in Blob data);
+}
diff --git a/core/java/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl b/core/java/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
new file mode 100644
index 0000000..dea0cc4
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnL2KeyResponseListener {
+ /**
+ * The operation completed with the specified L2 key.
+ */
+ void onL2KeyResponse(in StatusParcelable status, in String l2Key);
+}
diff --git a/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl b/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl
new file mode 100644
index 0000000..57f59a1
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnNetworkAttributesRetrieved {
+ /**
+ * Network attributes were fetched for the specified L2 key. While the L2 key will never
+ * be null, the attributes may be if no data is stored about this L2 key.
+ */
+ void onL2KeyResponse(in StatusParcelable status, in String l2Key,
+ in NetworkAttributesParcelable attributes);
+}
diff --git a/core/java/android/net/ipmemorystore/IOnSameNetworkResponseListener.aidl b/core/java/android/net/ipmemorystore/IOnSameNetworkResponseListener.aidl
new file mode 100644
index 0000000..294bd3b
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/IOnSameNetworkResponseListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+import android.net.ipmemorystore.SameL3NetworkResponseParcelable;
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnSameNetworkResponseListener {
+ /**
+ * The memory store has come up with the answer to a query that was sent.
+ */
+ void onSameNetworkResponse(in StatusParcelable status,
+ in SameL3NetworkResponseParcelable response);
+}
diff --git a/core/java/android/net/ipmemorystore/IOnStatusListener.aidl b/core/java/android/net/ipmemorystore/IOnStatusListener.aidl
new file mode 100644
index 0000000..5d07504
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/IOnStatusListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnStatusListener {
+ /**
+ * The operation has completed with the specified status.
+ */
+ void onComplete(in StatusParcelable status);
+}
diff --git a/core/java/android/net/ipmemorystore/NetworkAttributes.java b/core/java/android/net/ipmemorystore/NetworkAttributes.java
new file mode 100644
index 0000000..d7e5b27
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/NetworkAttributes.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A POD object to represent attributes of a single L2 network entry.
+ * @hide
+ */
+public class NetworkAttributes {
+ private static final boolean DBG = true;
+
+ // The v4 address that was assigned to this device the last time it joined this network.
+ // This typically comes from DHCP but could be something else like static configuration.
+ // This does not apply to IPv6.
+ // TODO : add a list of v6 prefixes for the v6 case.
+ @Nullable
+ public final Inet4Address assignedV4Address;
+
+ // Optionally supplied by the client if it has an opinion on L3 network. For example, this
+ // could be a hash of the SSID + security type on WiFi.
+ @Nullable
+ public final String groupHint;
+
+ // The list of DNS server addresses.
+ @Nullable
+ public final List<InetAddress> dnsAddresses;
+
+ // The mtu on this network.
+ @Nullable
+ public final Integer mtu;
+
+ NetworkAttributes(
+ @Nullable final Inet4Address assignedV4Address,
+ @Nullable final String groupHint,
+ @Nullable final List<InetAddress> dnsAddresses,
+ @Nullable final Integer mtu) {
+ if (mtu != null && mtu < 0) throw new IllegalArgumentException("MTU can't be negative");
+ this.assignedV4Address = assignedV4Address;
+ this.groupHint = groupHint;
+ this.dnsAddresses = null == dnsAddresses ? null :
+ Collections.unmodifiableList(new ArrayList<>(dnsAddresses));
+ this.mtu = mtu;
+ }
+
+ @VisibleForTesting
+ public NetworkAttributes(@NonNull final NetworkAttributesParcelable parcelable) {
+ // The call to the other constructor must be the first statement of this constructor,
+ // so everything has to be inline
+ this((Inet4Address) getByAddressOrNull(parcelable.assignedV4Address),
+ parcelable.groupHint,
+ blobArrayToInetAddressList(parcelable.dnsAddresses),
+ parcelable.mtu >= 0 ? parcelable.mtu : null);
+ }
+
+ @Nullable
+ private static InetAddress getByAddressOrNull(@Nullable final byte[] address) {
+ try {
+ return InetAddress.getByAddress(address);
+ } catch (UnknownHostException e) {
+ return null;
+ }
+ }
+
+ @Nullable
+ private static List<InetAddress> blobArrayToInetAddressList(@Nullable final Blob[] blobs) {
+ if (null == blobs) return null;
+ final ArrayList<InetAddress> list = new ArrayList<>(blobs.length);
+ for (final Blob b : blobs) {
+ final InetAddress addr = getByAddressOrNull(b.data);
+ if (null != addr) list.add(addr);
+ }
+ return list;
+ }
+
+ @Nullable
+ private static Blob[] inetAddressListToBlobArray(@Nullable final List<InetAddress> addresses) {
+ if (null == addresses) return null;
+ final ArrayList<Blob> blobs = new ArrayList<>();
+ for (int i = 0; i < addresses.size(); ++i) {
+ final InetAddress addr = addresses.get(i);
+ if (null == addr) continue;
+ final Blob b = new Blob();
+ b.data = addr.getAddress();
+ blobs.add(b);
+ }
+ return blobs.toArray(new Blob[0]);
+ }
+
+ /** Converts this NetworkAttributes to a parcelable object */
+ @NonNull
+ public NetworkAttributesParcelable toParcelable() {
+ final NetworkAttributesParcelable parcelable = new NetworkAttributesParcelable();
+ parcelable.assignedV4Address =
+ (null == assignedV4Address) ? null : assignedV4Address.getAddress();
+ parcelable.groupHint = groupHint;
+ parcelable.dnsAddresses = inetAddressListToBlobArray(dnsAddresses);
+ parcelable.mtu = (null == mtu) ? -1 : mtu;
+ return parcelable;
+ }
+
+ /** @hide */
+ public static class Builder {
+ @Nullable
+ private Inet4Address mAssignedAddress;
+ @Nullable
+ private String mGroupHint;
+ @Nullable
+ private List<InetAddress> mDnsAddresses;
+ @Nullable
+ private Integer mMtu;
+
+ /**
+ * Set the assigned address.
+ * @param assignedV4Address The assigned address.
+ * @return This builder.
+ */
+ public Builder setAssignedV4Address(@Nullable final Inet4Address assignedV4Address) {
+ mAssignedAddress = assignedV4Address;
+ return this;
+ }
+
+ /**
+ * Set the group hint.
+ * @param groupHint The group hint.
+ * @return This builder.
+ */
+ public Builder setGroupHint(@Nullable final String groupHint) {
+ mGroupHint = groupHint;
+ return this;
+ }
+
+ /**
+ * Set the DNS addresses.
+ * @param dnsAddresses The DNS addresses.
+ * @return This builder.
+ */
+ public Builder setDnsAddresses(@Nullable final List<InetAddress> dnsAddresses) {
+ if (DBG && null != dnsAddresses) {
+ // Parceling code crashes if one of the addresses is null, therefore validate
+ // them when running in debug.
+ for (final InetAddress address : dnsAddresses) {
+ if (null == address) throw new IllegalArgumentException("Null DNS address");
+ }
+ }
+ this.mDnsAddresses = dnsAddresses;
+ return this;
+ }
+
+ /**
+ * Set the MTU.
+ * @param mtu The MTU.
+ * @return This builder.
+ */
+ public Builder setMtu(@Nullable final Integer mtu) {
+ if (null != mtu && mtu < 0) throw new IllegalArgumentException("MTU can't be negative");
+ mMtu = mtu;
+ return this;
+ }
+
+ /**
+ * Return the built NetworkAttributes object.
+ * @return The built NetworkAttributes object.
+ */
+ public NetworkAttributes build() {
+ return new NetworkAttributes(mAssignedAddress, mGroupHint, mDnsAddresses, mMtu);
+ }
+ }
+
+ @Override
+ public boolean equals(@Nullable final Object o) {
+ if (!(o instanceof NetworkAttributes)) return false;
+ final NetworkAttributes other = (NetworkAttributes) o;
+ return Objects.equals(assignedV4Address, other.assignedV4Address)
+ && Objects.equals(groupHint, other.groupHint)
+ && Objects.equals(dnsAddresses, other.dnsAddresses)
+ && Objects.equals(mtu, other.mtu);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(assignedV4Address, groupHint, dnsAddresses, mtu);
+ }
+}
diff --git a/core/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl b/core/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
new file mode 100644
index 0000000..0894d72
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+// Blob[] is used to represent an array of byte[], as structured AIDL does not support arrays
+// of arrays.
+import android.net.ipmemorystore.Blob;
+
+/**
+ * An object to represent attributes of a single L2 network entry.
+ * See NetworkAttributes.java for a description of each field. The types used in this class
+ * are structured parcelable types instead of the richer types of the NetworkAttributes object,
+ * but they have the same purpose. The NetworkAttributes.java file also contains the code
+ * to convert the richer types to the parcelable types and back.
+ * @hide
+ */
+parcelable NetworkAttributesParcelable {
+ byte[] assignedV4Address;
+ String groupHint;
+ Blob[] dnsAddresses;
+ int mtu;
+}
diff --git a/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
new file mode 100644
index 0000000..0cb37e9
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * An object representing the answer to a query whether two given L2 networks represent the
+ * same L3 network. Parcels as a SameL3NetworkResponseParceled object.
+ * @hide
+ */
+public class SameL3NetworkResponse {
+ @IntDef(prefix = "NETWORK_",
+ value = {NETWORK_SAME, NETWORK_DIFFERENT, NETWORK_NEVER_CONNECTED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface NetworkSameness {}
+
+ /**
+ * Both L2 networks represent the same L3 network.
+ */
+ public static final int NETWORK_SAME = 1;
+
+ /**
+ * The two L2 networks represent a different L3 network.
+ */
+ public static final int NETWORK_DIFFERENT = 2;
+
+ /**
+ * The device has never connected to at least one of these two L2 networks, or data
+ * has been wiped. Therefore the device has never seen the L3 network behind at least
+ * one of these two L2 networks, and can't evaluate whether it's the same as the other.
+ */
+ public static final int NETWORK_NEVER_CONNECTED = 3;
+
+ /**
+ * The first L2 key specified in the query.
+ */
+ @NonNull
+ public final String l2Key1;
+
+ /**
+ * The second L2 key specified in the query.
+ */
+ @NonNull
+ public final String l2Key2;
+
+ /**
+ * A confidence value indicating whether the two L2 networks represent the same L3 network.
+ *
+ * If both L2 networks were known, this value will be between 0.0 and 1.0, with 0.0
+ * representing complete confidence that the given L2 networks represent a different
+ * L3 network, and 1.0 representing complete confidence that the given L2 networks
+ * represent the same L3 network.
+ * If at least one of the L2 networks was not known, this value will be outside of the
+ * 0.0~1.0 range.
+ *
+ * Most apps should not be interested in this, and are encouraged to use the collapsing
+ * {@link #getNetworkSameness()} function below.
+ */
+ public final float confidence;
+
+ /**
+ * @return whether the two L2 networks represent the same L3 network. Either
+ * {@code NETWORK_SAME}, {@code NETWORK_DIFFERENT} or {@code NETWORK_NEVER_CONNECTED}.
+ */
+ @NetworkSameness
+ public final int getNetworkSameness() {
+ if (confidence > 1.0 || confidence < 0.0) return NETWORK_NEVER_CONNECTED;
+ return confidence > 0.5 ? NETWORK_SAME : NETWORK_DIFFERENT;
+ }
+
+ SameL3NetworkResponse(@NonNull final String l2Key1, @NonNull final String l2Key2,
+ final float confidence) {
+ this.l2Key1 = l2Key1;
+ this.l2Key2 = l2Key2;
+ this.confidence = confidence;
+ }
+
+ /** Builds a SameL3NetworkResponse from a parcelable object */
+ @VisibleForTesting
+ public SameL3NetworkResponse(@NonNull final SameL3NetworkResponseParcelable parceled) {
+ this(parceled.l2Key1, parceled.l2Key2, parceled.confidence);
+ }
+
+ /** Converts this SameL3NetworkResponse to a parcelable object */
+ @NonNull
+ public SameL3NetworkResponseParcelable toParcelable() {
+ final SameL3NetworkResponseParcelable parcelable = new SameL3NetworkResponseParcelable();
+ parcelable.l2Key1 = l2Key1;
+ parcelable.l2Key2 = l2Key2;
+ parcelable.confidence = confidence;
+ return parcelable;
+ }
+
+ // Note key1 and key2 have to match each other for this to return true. If
+ // key1 matches o.key2 and the other way around this returns false.
+ @Override
+ public boolean equals(@Nullable final Object o) {
+ if (!(o instanceof SameL3NetworkResponse)) return false;
+ final SameL3NetworkResponse other = (SameL3NetworkResponse) o;
+ return l2Key1.equals(other.l2Key1) && l2Key2.equals(other.l2Key2)
+ && confidence == other.confidence;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(l2Key1, l2Key2, confidence);
+ }
+}
diff --git a/core/java/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl b/core/java/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
new file mode 100644
index 0000000..7196699
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+/** {@hide} */
+parcelable SameL3NetworkResponseParcelable {
+ String l2Key1;
+ String l2Key2;
+ float confidence;
+}
diff --git a/core/java/android/net/ipmemorystore/Status.java b/core/java/android/net/ipmemorystore/Status.java
new file mode 100644
index 0000000..5b016ec
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/Status.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+import android.annotation.NonNull;
+
+/**
+ * A parcelable status representing the result of an operation.
+ * Parcels as StatusParceled.
+ * @hide
+ */
+public class Status {
+ public static final int SUCCESS = 0;
+
+ public final int resultCode;
+
+ public Status(final int resultCode) {
+ this.resultCode = resultCode;
+ }
+
+ Status(@NonNull final StatusParcelable parcelable) {
+ this(parcelable.resultCode);
+ }
+
+ /** Converts this Status to a parcelable object */
+ @NonNull
+ public StatusParcelable toParcelable() {
+ final StatusParcelable parcelable = new StatusParcelable();
+ parcelable.resultCode = resultCode;
+ return parcelable;
+ }
+
+ public boolean isSuccess() {
+ return SUCCESS == resultCode;
+ }
+}
diff --git a/core/java/android/net/ipmemorystore/StatusParcelable.aidl b/core/java/android/net/ipmemorystore/StatusParcelable.aidl
new file mode 100644
index 0000000..fb36ef4
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/StatusParcelable.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+/** {@hide} */
+parcelable StatusParcelable {
+ int resultCode;
+}
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 9801406..68d6d85 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -16,6 +16,8 @@
package android.os;
+import android.annotation.NonNull;
+
import com.android.internal.os.Zygote;
import dalvik.annotation.optimization.FastNative;
@@ -313,7 +315,7 @@
* @param sectionName The name of the code section to appear in the trace. This may be at
* most 127 Unicode code units long.
*/
- public static void beginSection(String sectionName) {
+ public static void beginSection(@NonNull String sectionName) {
if (isTagEnabled(TRACE_TAG_APP)) {
if (sectionName.length() > MAX_SECTION_NAME_LEN) {
throw new IllegalArgumentException("sectionName is too long");
@@ -345,7 +347,7 @@
* @param methodName The method name to appear in the trace.
* @param cookie Unique identifier for distinguishing simultaneous events
*/
- public static void beginAsyncSection(String methodName, int cookie) {
+ public static void beginAsyncSection(@NonNull String methodName, int cookie) {
asyncTraceBegin(TRACE_TAG_APP, methodName, cookie);
}
@@ -357,7 +359,7 @@
* @param methodName The method name to appear in the trace.
* @param cookie Unique identifier for distinguishing simultaneous events
*/
- public static void endAsyncSection(String methodName, int cookie) {
+ public static void endAsyncSection(@NonNull String methodName, int cookie) {
asyncTraceEnd(TRACE_TAG_APP, methodName, cookie);
}
@@ -367,7 +369,7 @@
* @param counterName The counter name to appear in the trace.
* @param counterValue The counter value.
*/
- public static void setCounter(String counterName, long counterValue) {
+ public static void setCounter(@NonNull String counterName, long counterValue) {
if (isTagEnabled(TRACE_TAG_APP)) {
nativeTraceCounter(TRACE_TAG_APP, counterName, counterValue);
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 66e1c80..101252b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -86,7 +86,7 @@
import android.util.ArraySet;
import android.util.Log;
import android.util.MemoryIntArray;
-import android.view.textservice.TextServicesManager;
+import android.view.inputmethod.InputMethodSystemProperty;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.ColorDisplayController;
@@ -5030,10 +5030,6 @@
public static boolean putStringForUser(@NonNull ContentResolver resolver,
@NonNull String name, @Nullable String value, @Nullable String tag,
boolean makeDefault, @UserIdInt int userHandle) {
- if (LOCATION_MODE.equals(name)) {
- // Map LOCATION_MODE to underlying location provider storage API
- return setLocationModeForUser(resolver, Integer.parseInt(value), userHandle);
- }
if (MOVED_TO_GLOBAL.contains(name)) {
Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Secure"
+ " to android.provider.Settings.Global");
@@ -5186,10 +5182,6 @@
/** @hide */
@UnsupportedAppUsage
public static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) {
- if (LOCATION_MODE.equals(name)) {
- // Map from to underlying location provider storage API to location mode
- return getLocationModeForUser(cr, userHandle);
- }
String v = getStringForUser(cr, name, userHandle);
try {
return v != null ? Integer.parseInt(v) : def;
@@ -5224,10 +5216,6 @@
/** @hide */
public static int getIntForUser(ContentResolver cr, String name, int userHandle)
throws SettingNotFoundException {
- if (LOCATION_MODE.equals(name)) {
- // Map from to underlying location provider storage API to location mode
- return getLocationModeForUser(cr, userHandle);
- }
String v = getStringForUser(cr, name, userHandle);
try {
return Integer.parseInt(v);
@@ -5810,9 +5798,8 @@
* this value being present in settings.db or on ContentObserver notifications on the
* corresponding Uri.
*
- * @deprecated use {@link #LOCATION_MODE} and
- * {@link LocationManager#MODE_CHANGED_ACTION} (or
- * {@link LocationManager#PROVIDERS_CHANGED_ACTION})
+ * @deprecated Providers should not be controlled individually. See {@link #LOCATION_MODE}
+ * documentation for information on reading/writing location information.
*/
@Deprecated
public static final String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed";
@@ -5830,9 +5817,7 @@
* notifications for the corresponding Uri. Use {@link LocationManager#MODE_CHANGED_ACTION}
* to receive changes in this value.
*
- * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To
- * get the status of a location provider, use
- * {@link LocationManager#isProviderEnabled(String)}.
+ * @deprecated To check location mode, use {@link LocationManager#isLocationEnabled()}.
*/
@Deprecated
public static final String LOCATION_MODE = "location_mode";
@@ -5861,9 +5846,7 @@
/**
* Location access disabled.
*
- * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To
- * get the status of a location provider, use
- * {@link LocationManager#isProviderEnabled(String)}.
+ * @deprecated See {@link #LOCATION_MODE}.
*/
@Deprecated
public static final int LOCATION_MODE_OFF = 0;
@@ -5883,9 +5866,7 @@
* with {@link android.location.Criteria#POWER_HIGH} may be downgraded to
* {@link android.location.Criteria#POWER_MEDIUM}.
*
- * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To
- * get the status of a location provider, use
- * {@link LocationManager#isProviderEnabled(String)}.
+ * @deprecated See {@link #LOCATION_MODE}.
*/
@Deprecated
public static final int LOCATION_MODE_BATTERY_SAVING = 2;
@@ -5893,9 +5874,7 @@
/**
* Best-effort location computation allowed.
*
- * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To
- * get the status of a location provider, use
- * {@link LocationManager#isProviderEnabled(String)}.
+ * @deprecated See {@link #LOCATION_MODE}.
*/
@Deprecated
public static final int LOCATION_MODE_HIGH_ACCURACY = 3;
@@ -7835,6 +7814,19 @@
BOOLEAN_VALIDATOR;
/**
+ * Whether or not face unlock always requires user confirmation, meaning {@link
+ * android.hardware.biometrics.BiometricPrompt.Builder#setRequireConfirmation(boolean)}
+ * is always 'true'. This overrides the behavior that apps choose in the
+ * setRequireConfirmation API.
+ * @hide
+ */
+ public static final String FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION =
+ "face_unlock_always_require_confirmation";
+
+ private static final Validator FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION_VALIDATOR =
+ BOOLEAN_VALIDATOR;
+
+ /**
* Whether the assist gesture should be enabled.
*
* @hide
@@ -8431,6 +8423,7 @@
AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN,
FACE_UNLOCK_KEYGUARD_ENABLED,
FACE_UNLOCK_APP_ENABLED,
+ FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
ASSIST_GESTURE_ENABLED,
ASSIST_GESTURE_SILENCE_ALERTS_ENABLED,
ASSIST_GESTURE_WAKE_ENABLED,
@@ -8584,6 +8577,8 @@
AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN_VALIDATOR);
VALIDATORS.put(FACE_UNLOCK_KEYGUARD_ENABLED, FACE_UNLOCK_KEYGUARD_ENABLED_VALIDATOR);
VALIDATORS.put(FACE_UNLOCK_APP_ENABLED, FACE_UNLOCK_APP_ENABLED_VALIDATOR);
+ VALIDATORS.put(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
+ FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION_VALIDATOR);
VALIDATORS.put(ASSIST_GESTURE_ENABLED, ASSIST_GESTURE_ENABLED_VALIDATOR);
VALIDATORS.put(ASSIST_GESTURE_SILENCE_ALERTS_ENABLED,
ASSIST_GESTURE_SILENCE_ALERTS_ENABLED_VALIDATOR);
@@ -8652,14 +8647,14 @@
CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_ENABLED);
CLONE_TO_MANAGED_PROFILE.add(ALLOW_MOCK_LOCATION);
CLONE_TO_MANAGED_PROFILE.add(ALLOWED_GEOLOCATION_ORIGINS);
- CLONE_TO_MANAGED_PROFILE.add(DEFAULT_INPUT_METHOD);
CLONE_TO_MANAGED_PROFILE.add(ENABLED_ACCESSIBILITY_SERVICES);
- CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS);
CLONE_TO_MANAGED_PROFILE.add(LOCATION_CHANGER);
CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE);
CLONE_TO_MANAGED_PROFILE.add(LOCATION_PROVIDERS_ALLOWED);
CLONE_TO_MANAGED_PROFILE.add(SELECTED_INPUT_METHOD_SUBTYPE);
- if (TextServicesManager.DISABLE_PER_PROFILE_SPELL_CHECKER) {
+ if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
+ CLONE_TO_MANAGED_PROFILE.add(DEFAULT_INPUT_METHOD);
+ CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS);
CLONE_TO_MANAGED_PROFILE.add(SELECTED_SPELL_CHECKER);
CLONE_TO_MANAGED_PROFILE.add(SELECTED_SPELL_CHECKER_SUBTYPE);
}
@@ -8778,84 +8773,6 @@
userId);
}
}
-
- /**
- * Thread-safe method for setting the location mode to one of
- * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY},
- * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}.
- * Necessary because the mode is a composite of the underlying location provider
- * settings.
- *
- * @param cr the content resolver to use
- * @param mode such as {@link #LOCATION_MODE_HIGH_ACCURACY}
- * @param userId the userId for which to change mode
- * @return true if the value was set, false on database errors
- *
- * @throws IllegalArgumentException if mode is not one of the supported values
- *
- * @deprecated To enable/disable location, use
- * {@link LocationManager#setLocationEnabledForUser(boolean, int)}.
- * To enable/disable a specific location provider, use
- * {@link LocationManager#setProviderEnabledForUser(String, boolean, int)}.
- */
- @Deprecated
- private static boolean setLocationModeForUser(
- ContentResolver cr, int mode, int userId) {
- synchronized (mLocationSettingsLock) {
- boolean gps = false;
- boolean network = false;
- switch (mode) {
- case LOCATION_MODE_OFF:
- break;
- case LOCATION_MODE_SENSORS_ONLY:
- gps = true;
- break;
- case LOCATION_MODE_BATTERY_SAVING:
- network = true;
- break;
- case LOCATION_MODE_HIGH_ACCURACY:
- gps = true;
- network = true;
- break;
- default:
- throw new IllegalArgumentException("Invalid location mode: " + mode);
- }
-
- boolean nlpSuccess = Settings.Secure.setLocationProviderEnabledForUser(
- cr, LocationManager.NETWORK_PROVIDER, network, userId);
- boolean gpsSuccess = Settings.Secure.setLocationProviderEnabledForUser(
- cr, LocationManager.GPS_PROVIDER, gps, userId);
- return gpsSuccess && nlpSuccess;
- }
- }
-
- /**
- * Thread-safe method for reading the location mode, returns one of
- * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY},
- * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}. Necessary
- * because the mode is a composite of the underlying location provider settings.
- *
- * @param cr the content resolver to use
- * @param userId the userId for which to read the mode
- * @return the location mode
- */
- private static final int getLocationModeForUser(ContentResolver cr, int userId) {
- synchronized (mLocationSettingsLock) {
- boolean gpsEnabled = Settings.Secure.isLocationProviderEnabledForUser(
- cr, LocationManager.GPS_PROVIDER, userId);
- boolean networkEnabled = Settings.Secure.isLocationProviderEnabledForUser(
- cr, LocationManager.NETWORK_PROVIDER, userId);
- if (gpsEnabled && networkEnabled) {
- return LOCATION_MODE_HIGH_ACCURACY;
- } else if (gpsEnabled) {
- return LOCATION_MODE_SENSORS_ONLY;
- } else if (networkEnabled) {
- return LOCATION_MODE_BATTERY_SAVING;
- } else {
- return LOCATION_MODE_OFF;
- }
- }
- }
}
/**
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index 26bf361..e5e028d 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -42,6 +42,7 @@
import android.view.contentcapture.ContentCaptureSessionId;
import android.view.contentcapture.IContentCaptureDirectManager;
import android.view.contentcapture.MainContentCaptureSession;
+import android.view.contentcapture.UserDataRemovalRequest;
import com.android.internal.os.IResultReceiver;
@@ -165,7 +166,7 @@
*/
public final void setContentCaptureWhitelist(@Nullable List<String> packages,
@Nullable List<ComponentName> activities) {
- //TODO(b/111276913): implement
+ //TODO(b/122595322): implement
}
/**
@@ -176,7 +177,7 @@
*/
public final void setActivityContentCaptureEnabled(@NonNull ComponentName activity,
boolean enabled) {
- //TODO(b/111276913): implement
+ //TODO(b/122595322): implement
}
/**
@@ -187,7 +188,7 @@
*/
public final void setPackageContentCaptureEnabled(@NonNull String packageName,
boolean enabled) {
- //TODO(b/111276913): implement
+ //TODO(b/122595322): implement
}
/**
@@ -196,7 +197,7 @@
*/
@NonNull
public final Set<ComponentName> getContentCaptureDisabledActivities() {
- //TODO(b/111276913): implement
+ //TODO(b/122595322): implement
return null;
}
@@ -206,7 +207,7 @@
*/
@NonNull
public final Set<String> getContentCaptureDisabledPackages() {
- //TODO(b/111276913): implement
+ //TODO(b/122595322): implement
return null;
}
@@ -255,6 +256,16 @@
if (VERBOSE) Log.v(TAG, "onContentCaptureEventsRequest(id=" + sessionId + ")");
onContentCaptureEventsRequest(sessionId, new ContentCaptureEventsRequest(event));
}
+
+ /**
+ * Notifies the service that the app requested to remove data associated with the user.
+ *
+ * @param request the user data requested to be removed
+ */
+ public void onUserDataRemovalRequest(@NonNull UserDataRemovalRequest request) {
+ if (VERBOSE) Log.v(TAG, "onUserDataRemovalRequest()");
+ }
+
/**
* Notifies the service of {@link SnapshotData snapshot data} associated with a session.
*
@@ -311,6 +322,14 @@
@NonNull String sessionId, int uid, IResultReceiver clientReceiver) {
mSessionUids.put(sessionId, uid);
onCreateContentCaptureSession(context, new ContentCaptureSessionId(sessionId));
+
+ final int flags = context.getFlags();
+ if ((flags & ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE) != 0) {
+ setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED_BY_FLAG_SECURE,
+ mClientInterface.asBinder());
+ return;
+ }
+
setClientState(clientReceiver, ContentCaptureSession.STATE_ACTIVE,
mClientInterface.asBinder());
}
@@ -392,15 +411,16 @@
}
/**
- * Sends the state of the {@link ContentCaptureManager} in the cleint app.
+ * Sends the state of the {@link ContentCaptureManager} in the client app.
*
* @param clientReceiver receiver in the client app.
+ * @param sessionState state of the session
* @param binder handle to the {@code IContentCaptureDirectManager} object that resides in the
* service.
* @hide
*/
public static void setClientState(@NonNull IResultReceiver clientReceiver,
- int sessionStatus, @Nullable IBinder binder) {
+ int sessionState, @Nullable IBinder binder) {
try {
final Bundle extras;
if (binder != null) {
@@ -409,7 +429,7 @@
} else {
extras = null;
}
- clientReceiver.send(sessionStatus, extras);
+ clientReceiver.send(sessionState, extras);
} catch (RemoteException e) {
Slog.w(TAG, "Error async reporting result to client: " + e);
}
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index 433483f7..7a58681 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -16,6 +16,7 @@
package android.text.style;
+import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
@@ -370,6 +371,7 @@
/**
* @return The color of the underline for that span, or 0 if there is no underline
*/
+ @ColorInt
public int getUnderlineColor() {
// The order here should match what is used in updateDrawState
final boolean misspelled = (mFlags & FLAG_MISSPELLED) != 0;
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 8b97e0e..0edcb3d 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -22,7 +22,9 @@
import android.text.TextUtils;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
/**
* Util class to get feature flag information.
@@ -37,8 +39,11 @@
public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid";
public static final String SAFETY_HUB = "settings_safety_hub";
public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press";
+ public static final String AOD_IMAGEWALLPAPER_ENABLED = "settings_aod_imagewallpaper_enabled";
private static final Map<String, String> DEFAULT_FLAGS;
+ private static final Set<String> OBSERVABLE_FLAGS;
+
static {
DEFAULT_FLAGS = new HashMap<>();
DEFAULT_FLAGS.put("settings_audio_switcher", "true");
@@ -54,6 +59,10 @@
DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false");
DEFAULT_FLAGS.put(SAFETY_HUB, "false");
DEFAULT_FLAGS.put(SCREENRECORD_LONG_PRESS, "false");
+ DEFAULT_FLAGS.put(AOD_IMAGEWALLPAPER_ENABLED, "false");
+
+ OBSERVABLE_FLAGS = new HashSet<>();
+ OBSERVABLE_FLAGS.add(AOD_IMAGEWALLPAPER_ENABLED);
}
/**
@@ -90,6 +99,16 @@
*/
public static void setEnabled(Context context, String feature, boolean enabled) {
SystemProperties.set(FFLAG_OVERRIDE_PREFIX + feature, enabled ? "true" : "false");
+
+ // Also update Settings.Global if needed so that we can observe it via observer.
+ if (OBSERVABLE_FLAGS.contains(feature)) {
+ setObservableFlag(context, feature, enabled);
+ }
+ }
+
+ private static void setObservableFlag(Context context, String feature, boolean enabled) {
+ Settings.Global.putString(
+ context.getContentResolver(), feature, enabled ? "true" : "false");
}
/**
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index a86abe5..9d3552f 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -18,6 +18,9 @@
import static android.view.Display.DEFAULT_DISPLAY;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.graphics.Matrix;
@@ -30,6 +33,7 @@
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
+import java.lang.annotation.Retention;
import java.util.Objects;
/**
@@ -462,7 +466,7 @@
/**
* This flag indicates that the event has been generated by a gesture generator. It
- * provides a hint to the GestureDector to not apply any touch slop.
+ * provides a hint to the GestureDetector to not apply any touch slop.
*
* @hide
*/
@@ -1391,6 +1395,42 @@
};
/**
+ * Classification constant: None.
+ *
+ * No additional information is available about the current motion event stream.
+ *
+ * @see #getClassification
+ */
+ public static final int CLASSIFICATION_NONE = 0;
+
+ /**
+ * Classification constant: Ambiguous gesture.
+ *
+ * The user's intent with respect to the current event stream is not yet determined.
+ * Gestural actions, such as scrolling, should be inhibited until the classification resolves
+ * to another value or the event stream ends.
+ *
+ * @see #getClassification
+ */
+ public static final int CLASSIFICATION_AMBIGUOUS_GESTURE = 1;
+
+ /**
+ * Classification constant: Deep press.
+ *
+ * The current event stream represents the user intentionally pressing harder on the screen.
+ * This classification type should be used to accelerate the long press behaviour.
+ *
+ * @see #getClassification
+ */
+ public static final int CLASSIFICATION_DEEP_PRESS = 2;
+
+ /** @hide */
+ @Retention(SOURCE)
+ @IntDef(prefix = { "CLASSIFICATION" }, value = {
+ CLASSIFICATION_NONE, CLASSIFICATION_AMBIGUOUS_GESTURE, CLASSIFICATION_DEEP_PRESS})
+ public @interface Classification {};
+
+ /**
* Tool type constant: Unknown tool type.
* This constant is used when the tool type is not known or is not relevant,
* such as for a trackball or other non-pointing device.
@@ -1478,7 +1518,7 @@
private static native long nativeInitialize(long nativePtr,
int deviceId, int source, int displayId, int action, int flags, int edgeFlags,
- int metaState, int buttonState,
+ int metaState, int buttonState, @Classification int classification,
float xOffset, float yOffset, float xPrecision, float yPrecision,
long downTimeNanos, long eventTimeNanos,
int pointerCount, PointerProperties[] pointerIds, PointerCoords[] pointerCoords);
@@ -1548,6 +1588,8 @@
@CriticalNative
private static native void nativeSetButtonState(long nativePtr, int buttonState);
@CriticalNative
+ private static native int nativeGetClassification(long nativePtr);
+ @CriticalNative
private static native int nativeGetActionButton(long nativePtr);
@CriticalNative
private static native void nativeSetActionButton(long nativePtr, int actionButton);
@@ -1648,7 +1690,7 @@
MotionEvent ev = obtain();
ev.mNativePtr = nativeInitialize(ev.mNativePtr,
deviceId, source, displayId, action, flags, edgeFlags, metaState, buttonState,
- 0, 0, xPrecision, yPrecision,
+ CLASSIFICATION_NONE, 0, 0, xPrecision, yPrecision,
downTime * NS_PER_MS, eventTime * NS_PER_MS,
pointerCount, pointerProperties, pointerCoords);
if (ev.mNativePtr == 0) {
@@ -1833,7 +1875,7 @@
ev.mNativePtr = nativeInitialize(ev.mNativePtr,
deviceId, source, displayId,
- action, 0, edgeFlags, metaState, 0,
+ action, 0, edgeFlags, metaState, 0 /*buttonState*/, CLASSIFICATION_NONE,
0, 0, xPrecision, yPrecision,
downTime * NS_PER_MS, eventTime * NS_PER_MS,
1, pp, pc);
@@ -2539,6 +2581,18 @@
}
/**
+ * Returns the classification for the current gesture.
+ * The classification may change as more events become available for the same gesture.
+ *
+ * @see #CLASSIFICATION_NONE
+ * @see #CLASSIFICATION_AMBIGUOUS_GESTURE
+ * @see #CLASSIFICATION_DEEP_PRESS
+ */
+ public @Classification int getClassification() {
+ return nativeGetClassification(mNativePtr);
+ }
+
+ /**
* Gets which button has been modified during a press or release action.
*
* For actions other than {@link #ACTION_BUTTON_PRESS} and {@link #ACTION_BUTTON_RELEASE}
@@ -3172,7 +3226,7 @@
/**
* Adds all of the movement samples of the specified event to this one if
* it is compatible. To be compatible, the event must have the same device id,
- * source, display id, action, flags, pointer count, pointer properties.
+ * source, display id, action, flags, classification, pointer count, pointer properties.
*
* Only applies to {@link #ACTION_MOVE} or {@link #ACTION_HOVER_MOVE} events.
*
@@ -3194,7 +3248,9 @@
if (nativeGetDeviceId(mNativePtr) != nativeGetDeviceId(event.mNativePtr)
|| nativeGetSource(mNativePtr) != nativeGetSource(event.mNativePtr)
|| nativeGetDisplayId(mNativePtr) != nativeGetDisplayId(event.mNativePtr)
- || nativeGetFlags(mNativePtr) != nativeGetFlags(event.mNativePtr)) {
+ || nativeGetFlags(mNativePtr) != nativeGetFlags(event.mNativePtr)
+ || nativeGetClassification(mNativePtr)
+ != nativeGetClassification(event.mNativePtr)) {
return false;
}
@@ -3282,7 +3338,7 @@
nativeGetDisplayId(mNativePtr),
nativeGetAction(mNativePtr), nativeGetFlags(mNativePtr),
nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
- nativeGetButtonState(mNativePtr),
+ nativeGetButtonState(mNativePtr), nativeGetClassification(mNativePtr),
nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
nativeGetDownTimeNanos(mNativePtr),
@@ -3376,7 +3432,7 @@
nativeGetDisplayId(mNativePtr),
newAction, nativeGetFlags(mNativePtr),
nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
- nativeGetButtonState(mNativePtr),
+ nativeGetButtonState(mNativePtr), nativeGetClassification(mNativePtr),
nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
nativeGetDownTimeNanos(mNativePtr), eventTimeNanos,
@@ -3409,6 +3465,8 @@
}
appendUnless("0", msg, ", buttonState=", MotionEvent.buttonStateToString(getButtonState()));
+ appendUnless(classificationToString(CLASSIFICATION_NONE), msg, ", classification=",
+ classificationToString(getClassification()));
appendUnless("0", msg, ", metaState=", KeyEvent.metaStateToString(getMetaState()));
appendUnless("0", msg, ", flags=0x", Integer.toHexString(getFlags()));
appendUnless("0", msg, ", edgeFlags=0x", Integer.toHexString(getEdgeFlags()));
@@ -3547,6 +3605,26 @@
}
/**
+ * Returns a string that represents the symbolic name of the specified classification.
+ *
+ * @param classification The classification type.
+ * @return The symbolic name of this classification.
+ * @hide
+ */
+ public static String classificationToString(@Classification int classification) {
+ switch (classification) {
+ case CLASSIFICATION_NONE:
+ return "NONE";
+ case CLASSIFICATION_AMBIGUOUS_GESTURE:
+ return "AMBIGUOUS_GESTURE";
+ case CLASSIFICATION_DEEP_PRESS:
+ return "DEEP_PRESS";
+
+ }
+ return "NONE";
+ }
+
+ /**
* Returns a string that represents the symbolic name of the specified tool type
* such as "TOOL_TYPE_FINGER" or an equivalent numeric constant such as "42" if unknown.
*
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index cb2c40e..cd0e579 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8199,6 +8199,19 @@
* <p>The populated structure is then passed to the service through
* {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)}.
*
+ * <p><b>Note: </b>views that manage a virtual structure under this view must populate just
+ * the node representing this view and return right away, then asynchronously report (not
+ * necessarily in the UI thread) when the children nodes appear, disappear or have their text
+ * changed by calling
+ * {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)},
+ * {@link ContentCaptureSession#notifyViewDisappeared(AutofillId)}, and
+ * {@link ContentCaptureSession#notifyViewTextChanged(AutofillId, CharSequence, int)}
+ * respectively. The structure for the a child must be created using
+ * {@link ContentCaptureSession#newVirtualViewStructure(AutofillId, int)}, and the
+ * {@code autofillId} for a child can be obtained either through
+ * {@code childStructure.getAutofillId()} or
+ * {@link ContentCaptureSession#newAutofillId(AutofillId, int)}.
+ *
* <p><b>Note: </b>the following methods of the {@code structure} will be ignored:
* <ul>
* <li>{@link ViewStructure#setChildCount(int)}
@@ -8235,10 +8248,6 @@
} else {
structure.setId(id, null, null, null);
}
- if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
- //TODO(b/111276913): STOPSHIP - don't set it if not needed
- structure.setDataIsSensitive(false);
- }
if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
|| viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index 95a346f..c630177 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -794,7 +794,7 @@
* @deprecated All window animations are running with detached wallpaper.
*/
public boolean getDetachWallpaper() {
- return false;
+ return true;
}
/**
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index e962e7c..ff45efd 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -15,6 +15,8 @@
*/
package android.view.contentcapture;
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
@@ -24,9 +26,12 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.os.IResultReceiver;
import com.android.internal.util.Preconditions;
+import com.android.internal.util.SyncResultReceiver;
import java.io.PrintWriter;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -48,6 +53,11 @@
private static final String BG_THREAD_NAME = "intel_svc_streamer_thread";
+ /**
+ * Timeout for calls to system_server.
+ */
+ private static final int SYNC_CALLS_TIMEOUT_MS = 5000;
+
// TODO(b/121044306): define a way to dynamically set them(for example, using settings?)
static final boolean VERBOSE = false;
static final boolean DEBUG = true; // STOPSHIP if not set to false
@@ -116,8 +126,8 @@
/** @hide */
public void onActivityStarted(@NonNull IBinder applicationToken,
- @NonNull ComponentName activityComponent) {
- getMainContentCaptureSession().start(applicationToken, activityComponent);
+ @NonNull ComponentName activityComponent, int flags) {
+ getMainContentCaptureSession().start(applicationToken, activityComponent, flags);
}
/** @hide */
@@ -142,8 +152,21 @@
*/
@Nullable
public ComponentName getServiceComponentName() {
- //TODO(b/121047489): implement
- return null;
+ if (!isContentCaptureEnabled()) {
+ return null;
+ }
+ // Wait for system server to return the component name.
+ final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+ mHandler.sendMessage(obtainMessage(
+ ContentCaptureManager::handleReceiverServiceComponentName,
+ this, mContext.getUserId(), resultReceiver));
+
+ try {
+ return resultReceiver.getParcelableResult();
+ } catch (RemoteException e) {
+ // Unable to retrieve component name in a reasonable amount of time.
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -191,4 +214,14 @@
pw.print(prefix); pw.println("No sessions");
}
}
+
+
+ /** Retrieves the component name of the target content capture service through system_server. */
+ private void handleReceiverServiceComponentName(int userId, IResultReceiver resultReceiver) {
+ try {
+ mService.getReceiverServiceComponentName(userId, resultReceiver);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to retrieve service component name: " + e);
+ }
+ }
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 344b9973..6890beaf 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -46,8 +46,7 @@
/**
* Used on {@link #notifyViewTextChanged(AutofillId, CharSequence, int)} to indicate that the
- *
- * thext change was caused by user input (for example, through IME).
+ * text change was caused by user input (for example, through IME).
*/
public static final int FLAG_USER_INPUT = 0x1;
@@ -86,6 +85,13 @@
*/
public static final int STATE_DISABLED_DUPLICATED_ID = 4;
+ /**
+ * Session is disabled by FLAG_SECURE
+ *
+ * @hide
+ */
+ public static final int STATE_DISABLED_BY_FLAG_SECURE = 5;
+
private static final int INITIAL_CHILDREN_CAPACITY = 5;
private final CloseGuard mCloseGuard = CloseGuard.get();
@@ -178,7 +184,7 @@
mCloseGuard.close();
- //TODO(b/111276913): check state (for example, how to handle if it's waiting for remote
+ // TODO(b/111276913): check state (for example, how to handle if it's waiting for remote
// id) and send it to the cache of batched commands
if (VERBOSE) {
Log.v(TAG, "destroy(): state=" + getStateAsString(mState) + ", mId=" + mId);
@@ -295,6 +301,26 @@
}
/**
+ * Creates a new {@link AutofillId} for a virtual child, so it can be used to uniquely identify
+ * the children in the session.
+ *
+ * @param parentId id of the virtual view parent (it can be obtained by calling
+ * {@link ViewStructure#getAutofillId()} on the parent).
+ * @param virtualChildId id of the virtual child, relative to the parent.
+ *
+ * @return if for the virtual child
+ *
+ * @throws IllegalArgumentException if the {@code parentId} is a virtual child id.
+ */
+ public @NonNull AutofillId newAutofillId(@NonNull AutofillId parentId, int virtualChildId) {
+ Preconditions.checkNotNull(parentId);
+ Preconditions.checkArgument(!parentId.isVirtual(), "virtual ids cannot have children");
+ // TODO(b/121197119): we need to add the session id to the AutofillId to make them unique
+ // per session
+ return new AutofillId(parentId, virtualChildId);
+ }
+
+ /**
* Creates a {@link ViewStructure} for a "virtual" view, so it can be passed to
* {@link #notifyViewAppeared(ViewStructure)} by the view managing the virtual view hierarchy.
*
@@ -303,12 +329,11 @@
* @param virtualId id of the virtual child, relative to the parent.
*
* @return a new {@link ViewStructure} that can be used for Content Capture purposes.
- *
- * @hide
*/
@NonNull
public final ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId,
int virtualId) {
+ // TODO(b/121197119): use the constructor that takes a session id / assert on unit test.
return new ViewNode.ViewStructureImpl(parentId, virtualId);
}
@@ -356,6 +381,8 @@
return "DISABLED_NO_SERVICE";
case STATE_DISABLED_DUPLICATED_ID:
return "DISABLED_DUPLICATED_ID";
+ case STATE_DISABLED_BY_FLAG_SECURE:
+ return "DISABLED_FLAG_SECURE";
default:
return "INVALID:" + state;
}
diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
index 01776f8..be9c00f 100644
--- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl
+++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
@@ -32,7 +32,28 @@
* @hide
*/
oneway interface IContentCaptureManager {
+ /**
+ * Starts a new session for the provided {@code userId} running as part of the
+ * app's activity identified by {@code activityToken}/{@code componentName}.
+ *
+ * @param sessionId Unique session id as provided by the app.
+ * @param flags Meta flags that enable or disable content capture (see
+ * {@link IContentCaptureContext#flags}).
+ */
void startSession(int userId, IBinder activityToken, in ComponentName componentName,
String sessionId, int flags, in IResultReceiver result);
+
+ /**
+ * Marks the end of a session for the provided {@code userId} identified by
+ * the corresponding {@code startSession}'s {@code sessionId}.
+ */
void finishSession(int userId, String sessionId);
+
+ /**
+ * Returns the content capture service's component name (if enabled and
+ * connected).
+ * @param Receiver of the content capture service's @{code ComponentName}
+ * provided {@code Bundle} with key "{@code EXTRA}".
+ */
+ void getReceiverServiceComponentName(int userId, in IResultReceiver result);
}
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 8be887b..a29aaf0 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -157,7 +157,8 @@
*
* @hide
*/
- void start(@NonNull IBinder applicationToken, @NonNull ComponentName activityComponent) {
+ void start(@NonNull IBinder applicationToken, @NonNull ComponentName activityComponent,
+ int flags) {
if (!isContentCaptureEnabled()) return;
if (VERBOSE) {
@@ -166,7 +167,7 @@
}
mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleStartSession, this,
- applicationToken, activityComponent));
+ applicationToken, activityComponent, flags));
}
@Override
@@ -181,7 +182,8 @@
obtainMessage(MainContentCaptureSession::handleDestroySession, this));
}
- private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName) {
+ private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName,
+ int flags) {
if (mState != STATE_UNKNOWN) {
// TODO(b/111276913): revisit this scenario
Log.w(TAG, "ignoring handleStartSession(" + token + ") while on state "
@@ -196,7 +198,6 @@
Log.v(TAG, "handleStartSession(): token=" + token + ", act="
+ getActivityDebugName() + ", id=" + mId);
}
- final int flags = 0; // TODO(b/111276913): get proper flags
try {
if (mSystemServerInterface == null) return;
@@ -245,7 +246,11 @@
Log.w(TAG, "Failed to link to death on " + binder + ": " + e);
}
}
- if (resultCode == STATE_DISABLED_NO_SERVICE || resultCode == STATE_DISABLED_DUPLICATED_ID) {
+
+ // TODO(b/111276913): change the resultCode to use flags so there's just one flag for
+ // disabled stuff
+ if (resultCode == STATE_DISABLED_NO_SERVICE || resultCode == STATE_DISABLED_DUPLICATED_ID
+ || resultCode == STATE_DISABLED_BY_FLAG_SECURE) {
mDisabled.set(true);
handleResetSession(/* resetState= */ false);
} else {
diff --git a/core/java/android/view/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java
index 86b89adb..b7a486a 100644
--- a/core/java/android/view/contentcapture/ViewNode.java
+++ b/core/java/android/view/contentcapture/ViewNode.java
@@ -24,6 +24,7 @@
import android.os.Bundle;
import android.os.LocaleList;
import android.os.Parcel;
+import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewParent;
@@ -32,30 +33,188 @@
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
-//TODO(b/111276913): add javadocs / implement Parcelable / implement
-//TODO(b/111276913): for now it's extending ViewNode directly as it needs most of its properties,
+//TODO(b/122484602): add javadocs / implement Parcelable / implement
+//TODO(b/122484602): for now it's extending ViewNode directly as it needs most of its properties,
// but it might be better to create a common, abstract android.view.ViewNode class that both extend
// instead
/** @hide */
@SystemApi
public final class ViewNode extends AssistStructure.ViewNode {
- private static final String TAG = "ViewNode";
+ private static final String TAG = ViewNode.class.getSimpleName();
+ private static final boolean VERBOSE = false;
+
+ private static final long FLAGS_HAS_TEXT = 1L << 0;
+ private static final long FLAGS_HAS_COMPLEX_TEXT = 1L << 1;
+ private static final long FLAGS_VISIBILITY_MASK = View.VISIBLE | View.INVISIBLE | View.GONE;
+ private static final long FLAGS_HAS_CLASSNAME = 1L << 4;
+ private static final long FLAGS_HAS_AUTOFILL_ID = 1L << 5;
+ private static final long FLAGS_HAS_AUTOFILL_PARENT_ID = 1L << 6;
+ private static final long FLAGS_HAS_ID = 1L << 7;
+ private static final long FLAGS_HAS_LARGE_COORDS = 1L << 8;
+ private static final long FLAGS_HAS_SCROLL = 1L << 9;
+ private static final long FLAGS_ASSIST_BLOCKED = 1L << 10;
+ private static final long FLAGS_DISABLED = 1L << 11;
+ private static final long FLAGS_CLICKABLE = 1L << 12;
+ private static final long FLAGS_LONG_CLICKABLE = 1L << 13;
+ private static final long FLAGS_CONTEXT_CLICKABLE = 1L << 14;
+ private static final long FLAGS_FOCUSABLE = 1L << 15;
+ private static final long FLAGS_FOCUSED = 1L << 16;
+ private static final long FLAGS_ACCESSIBILITY_FOCUSED = 1L << 17;
+ private static final long FLAGS_CHECKABLE = 1L << 18;
+ private static final long FLAGS_CHECKED = 1L << 19;
+ private static final long FLAGS_SELECTED = 1L << 20;
+ private static final long FLAGS_ACTIVATED = 1L << 21;
+ private static final long FLAGS_OPAQUE = 1L << 22;
+ private static final long FLAGS_HAS_MATRIX = 1L << 23;
+ private static final long FLAGS_HAS_ELEVATION = 1L << 24;
+ private static final long FLAGS_HAS_ALPHA = 1L << 25;
+ private static final long FLAGS_HAS_CONTENT_DESCRIPTION = 1L << 26;
+ private static final long FLAGS_HAS_EXTRAS = 1L << 27;
+ private static final long FLAGS_HAS_LOCALE_LIST = 1L << 28;
+ private static final long FLAGS_HAS_INPUT_TYPE = 1L << 29;
+ private static final long FLAGS_HAS_MIN_TEXT_EMS = 1L << 30;
+ private static final long FLAGS_HAS_MAX_TEXT_EMS = 1L << 31;
+ private static final long FLAGS_HAS_MAX_TEXT_LENGTH = 1L << 32;
+ private static final long FLAGS_HAS_TEXT_ID_ENTRY = 1L << 33;
+ private static final long FLAGS_HAS_AUTOFILL_TYPE = 1L << 34;
+ private static final long FLAGS_HAS_AUTOFILL_VALUE = 1L << 35;
+ private static final long FLAGS_HAS_AUTOFILL_HINTS = 1L << 36;
+ private static final long FLAGS_HAS_AUTOFILL_OPTIONS = 1L << 37;
+
+ /** Flags used to optimize what's written to the parcel */
+ private long mFlags;
private AutofillId mParentAutofillId;
- // TODO(b/111276913): temporarily setting some fields here while they're not accessible from the
- // superclass
private AutofillId mAutofillId;
- private CharSequence mText;
+ private ViewNodeText mText;
private String mClassName;
+ private int mId = View.NO_ID;
+ private String mIdPackage;
+ private String mIdType;
+ private String mIdEntry;
+ private int mX;
+ private int mY;
+ private int mScrollX;
+ private int mScrollY;
+ private int mWidth;
+ private int mHeight;
+ private Matrix mMatrix;
+ private float mElevation;
+ private float mAlpha = 1.0f;
+ private CharSequence mContentDescription;
+ private Bundle mExtras;
+ private LocaleList mLocaleList;
+ private int mInputType;
+ private int mMinEms = -1;
+ private int mMaxEms = -1;
+ private int mMaxLength = -1;
+ private String mTextIdEntry;
+ private @View.AutofillType int mAutofillType = View.AUTOFILL_TYPE_NONE;
+ private String[] mAutofillHints;
+ private AutofillValue mAutofillValue;
+ private CharSequence[] mAutofillOptions;
/** @hide */
public ViewNode() {
}
+ private ViewNode(long nodeFlags, @NonNull Parcel parcel) {
+ mFlags = nodeFlags;
+
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_ID) != 0) {
+ mAutofillId = parcel.readParcelable(null);
+ }
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_PARENT_ID) != 0) {
+ mParentAutofillId = parcel.readParcelable(null);
+ }
+ if ((nodeFlags & FLAGS_HAS_TEXT) != 0) {
+ mText = new ViewNodeText(parcel, (nodeFlags & FLAGS_HAS_COMPLEX_TEXT) == 0);
+ }
+ if ((nodeFlags & FLAGS_HAS_CLASSNAME) != 0) {
+ mClassName = parcel.readString();
+ }
+ if ((nodeFlags & FLAGS_HAS_ID) != 0) {
+ mId = parcel.readInt();
+ if (mId != View.NO_ID) {
+ mIdEntry = parcel.readString();
+ if (mIdEntry != null) {
+ mIdType = parcel.readString();
+ mIdPackage = parcel.readString();
+ }
+ }
+ }
+ if ((nodeFlags & FLAGS_HAS_LARGE_COORDS) != 0) {
+ mX = parcel.readInt();
+ mY = parcel.readInt();
+ mWidth = parcel.readInt();
+ mHeight = parcel.readInt();
+ } else {
+ int val = parcel.readInt();
+ mX = val & 0x7fff;
+ mY = (val >> 16) & 0x7fff;
+ val = parcel.readInt();
+ mWidth = val & 0x7fff;
+ mHeight = (val >> 16) & 0x7fff;
+ }
+ if ((nodeFlags & FLAGS_HAS_SCROLL) != 0) {
+ mScrollX = parcel.readInt();
+ mScrollY = parcel.readInt();
+ }
+ if ((nodeFlags & FLAGS_HAS_MATRIX) != 0) {
+ mMatrix = new Matrix();
+ final float[] tmpMatrix = new float[9];
+ parcel.readFloatArray(tmpMatrix);
+ mMatrix.setValues(tmpMatrix);
+ }
+ if ((nodeFlags & FLAGS_HAS_ELEVATION) != 0) {
+ mElevation = parcel.readFloat();
+ }
+ if ((nodeFlags & FLAGS_HAS_ALPHA) != 0) {
+ mAlpha = parcel.readFloat();
+ }
+ if ((nodeFlags & FLAGS_HAS_CONTENT_DESCRIPTION) != 0) {
+ mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ }
+ if ((nodeFlags & FLAGS_HAS_EXTRAS) != 0) {
+ mExtras = parcel.readBundle();
+ }
+ if ((nodeFlags & FLAGS_HAS_LOCALE_LIST) != 0) {
+ mLocaleList = parcel.readParcelable(null);
+ }
+ if ((nodeFlags & FLAGS_HAS_INPUT_TYPE) != 0) {
+ mInputType = parcel.readInt();
+ }
+ if ((nodeFlags & FLAGS_HAS_MIN_TEXT_EMS) != 0) {
+ mMinEms = parcel.readInt();
+ }
+ if ((nodeFlags & FLAGS_HAS_MAX_TEXT_EMS) != 0) {
+ mMaxEms = parcel.readInt();
+ }
+ if ((nodeFlags & FLAGS_HAS_MAX_TEXT_LENGTH) != 0) {
+ mMaxLength = parcel.readInt();
+ }
+ if ((nodeFlags & FLAGS_HAS_TEXT_ID_ENTRY) != 0) {
+ mTextIdEntry = parcel.readString();
+ }
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_TYPE) != 0) {
+ mAutofillType = parcel.readInt();
+ }
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_HINTS) != 0) {
+ mAutofillHints = parcel.readStringArray();
+ }
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_VALUE) != 0) {
+ mAutofillValue = parcel.readParcelable(null);
+ }
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_OPTIONS) != 0) {
+ mAutofillOptions = parcel.readCharSequenceArray();
+ }
+ }
+
/**
* Returns the {@link AutofillId} of this view's parent, if the parent is also part of the
* screen observation tree.
@@ -65,53 +224,446 @@
return mParentAutofillId;
}
- // TODO(b/111276913): temporarily overwriting some methods
@Override
public AutofillId getAutofillId() {
return mAutofillId;
}
+
@Override
public CharSequence getText() {
- return mText;
+ return mText != null ? mText.mText : null;
}
+
@Override
public String getClassName() {
return mClassName;
}
+ @Override
+ public int getId() {
+ return mId;
+ }
+
+ @Override
+ public String getIdPackage() {
+ return mIdPackage;
+ }
+
+ @Override
+ public String getIdType() {
+ return mIdType;
+ }
+
+ @Override
+ public String getIdEntry() {
+ return mIdEntry;
+ }
+
+ @Override
+ public int getLeft() {
+ return mX;
+ }
+
+ @Override
+ public int getTop() {
+ return mY;
+ }
+
+ @Override
+ public int getScrollX() {
+ return mScrollX;
+ }
+
+ @Override
+ public int getScrollY() {
+ return mScrollY;
+ }
+
+ @Override
+ public int getWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getHeight() {
+ return mHeight;
+ }
+
+ @Override
+ public boolean isAssistBlocked() {
+ return (mFlags & FLAGS_ASSIST_BLOCKED) != 0;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return (mFlags & FLAGS_DISABLED) == 0;
+ }
+
+ @Override
+ public boolean isClickable() {
+ return (mFlags & FLAGS_CLICKABLE) != 0;
+ }
+
+ @Override
+ public boolean isLongClickable() {
+ return (mFlags & FLAGS_LONG_CLICKABLE) != 0;
+ }
+
+ @Override
+ public boolean isContextClickable() {
+ return (mFlags & FLAGS_CONTEXT_CLICKABLE) != 0;
+ }
+
+ @Override
+ public boolean isFocusable() {
+ return (mFlags & FLAGS_FOCUSABLE) != 0;
+ }
+
+ @Override
+ public boolean isFocused() {
+ return (mFlags & FLAGS_FOCUSED) != 0;
+ }
+
+ @Override
+ public boolean isAccessibilityFocused() {
+ return (mFlags & FLAGS_ACCESSIBILITY_FOCUSED) != 0;
+ }
+
+ @Override
+ public boolean isCheckable() {
+ return (mFlags & FLAGS_CHECKABLE) != 0;
+ }
+
+ @Override
+ public boolean isChecked() {
+ return (mFlags & FLAGS_CHECKED) != 0;
+ }
+
+ @Override
+ public boolean isSelected() {
+ return (mFlags & FLAGS_SELECTED) != 0;
+ }
+
+ @Override
+ public boolean isActivated() {
+ return (mFlags & FLAGS_ACTIVATED) != 0;
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return (mFlags & FLAGS_OPAQUE) != 0;
+ }
+
+ @Override
+ public Matrix getTransformation() {
+ return mMatrix;
+ }
+
+ @Override
+ public float getElevation() {
+ return mElevation;
+ }
+
+ @Override
+ public float getAlpha() {
+ return mAlpha;
+ }
+
+ @Override
+ public CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ @Override
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public String getHint() {
+ return mText != null ? mText.mHint : null;
+ }
+
+ @Override
+ public int getTextSelectionStart() {
+ return mText != null ? mText.mTextSelectionStart : -1;
+ }
+
+ @Override
+ public int getTextSelectionEnd() {
+ return mText != null ? mText.mTextSelectionEnd : -1;
+ }
+
+ @Override
+ public int getTextColor() {
+ return mText != null ? mText.mTextColor : TEXT_COLOR_UNDEFINED;
+ }
+
+ @Override
+ public int getTextBackgroundColor() {
+ return mText != null ? mText.mTextBackgroundColor : TEXT_COLOR_UNDEFINED;
+ }
+
+ @Override
+ public float getTextSize() {
+ return mText != null ? mText.mTextSize : 0;
+ }
+
+ @Override
+ public int getTextStyle() {
+ return mText != null ? mText.mTextStyle : 0;
+ }
+
+ @Override
+ public int[] getTextLineCharOffsets() {
+ return mText != null ? mText.mLineCharOffsets : null;
+ }
+
+ @Override
+ public int[] getTextLineBaselines() {
+ return mText != null ? mText.mLineBaselines : null;
+ }
+
+ @Override
+ public int getVisibility() {
+ return (int) (mFlags & FLAGS_VISIBILITY_MASK);
+ }
+
+ @Override
+ public int getInputType() {
+ return mInputType;
+ }
+
+ @Override
+ public int getMinTextEms() {
+ return mMinEms;
+ }
+
+ @Override
+ public int getMaxTextEms() {
+ return mMaxEms;
+ }
+
+ @Override
+ public int getMaxTextLength() {
+ return mMaxLength;
+ }
+
+ @Override
+ public String getTextIdEntry() {
+ return mTextIdEntry;
+ }
+
+ @Override
+ public @View.AutofillType int getAutofillType() {
+ return mAutofillType;
+ }
+
+ @Override
+ @Nullable public String[] getAutofillHints() {
+ return mAutofillHints;
+ }
+
+ @Override
+ @Nullable public AutofillValue getAutofillValue() {
+ return mAutofillValue;
+ }
+
+ @Override
+ @Nullable public CharSequence[] getAutofillOptions() {
+ return mAutofillOptions;
+ }
+
+ @Override
+ public LocaleList getLocaleList() {
+ return mLocaleList;
+ }
+
+ private void writeSelfToParcel(@NonNull Parcel parcel, int parcelFlags) {
+ long nodeFlags = mFlags;
+
+ if (mAutofillId != null) {
+ nodeFlags |= FLAGS_HAS_AUTOFILL_ID;
+ }
+
+ if (mParentAutofillId != null) {
+ nodeFlags |= FLAGS_HAS_AUTOFILL_PARENT_ID;
+ }
+
+ if (mText != null) {
+ nodeFlags |= FLAGS_HAS_TEXT;
+ if (!mText.isSimple()) {
+ nodeFlags |= FLAGS_HAS_COMPLEX_TEXT;
+ }
+ }
+ if (mClassName != null) {
+ nodeFlags |= FLAGS_HAS_CLASSNAME;
+ }
+ if (mId != View.NO_ID) {
+ nodeFlags |= FLAGS_HAS_ID;
+ }
+ if ((mX & ~0x7fff) != 0 || (mY & ~0x7fff) != 0
+ || (mWidth & ~0x7fff) != 0 | (mHeight & ~0x7fff) != 0) {
+ nodeFlags |= FLAGS_HAS_LARGE_COORDS;
+ }
+ if (mScrollX != 0 || mScrollY != 0) {
+ nodeFlags |= FLAGS_HAS_SCROLL;
+ }
+ if (mMatrix != null) {
+ nodeFlags |= FLAGS_HAS_MATRIX;
+ }
+ if (mElevation != 0) {
+ nodeFlags |= FLAGS_HAS_ELEVATION;
+ }
+ if (mAlpha != 1.0f) {
+ nodeFlags |= FLAGS_HAS_ALPHA;
+ }
+ if (mContentDescription != null) {
+ nodeFlags |= FLAGS_HAS_CONTENT_DESCRIPTION;
+ }
+ if (mExtras != null) {
+ nodeFlags |= FLAGS_HAS_EXTRAS;
+ }
+ if (mLocaleList != null) {
+ nodeFlags |= FLAGS_HAS_LOCALE_LIST;
+ }
+ if (mInputType != 0) {
+ nodeFlags |= FLAGS_HAS_INPUT_TYPE;
+ }
+ if (mMinEms > -1) {
+ nodeFlags |= FLAGS_HAS_MIN_TEXT_EMS;
+ }
+ if (mMaxEms > -1) {
+ nodeFlags |= FLAGS_HAS_MAX_TEXT_EMS;
+ }
+ if (mMaxLength > -1) {
+ nodeFlags |= FLAGS_HAS_MAX_TEXT_LENGTH;
+ }
+ if (mTextIdEntry != null) {
+ nodeFlags |= FLAGS_HAS_TEXT_ID_ENTRY;
+ }
+ if (mAutofillValue != null) {
+ nodeFlags |= FLAGS_HAS_AUTOFILL_VALUE;
+ }
+ if (mAutofillType != View.AUTOFILL_TYPE_NONE) {
+ nodeFlags |= FLAGS_HAS_AUTOFILL_TYPE;
+ }
+ if (mAutofillHints != null) {
+ nodeFlags |= FLAGS_HAS_AUTOFILL_HINTS;
+ }
+ if (mAutofillOptions != null) {
+ nodeFlags |= FLAGS_HAS_AUTOFILL_OPTIONS;
+ }
+ parcel.writeLong(nodeFlags);
+
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_ID) != 0) {
+ parcel.writeParcelable(mAutofillId, parcelFlags);
+ }
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_PARENT_ID) != 0) {
+ parcel.writeParcelable(mParentAutofillId, parcelFlags);
+ }
+ if ((nodeFlags & FLAGS_HAS_TEXT) != 0) {
+ mText.writeToParcel(parcel, (nodeFlags & FLAGS_HAS_COMPLEX_TEXT) == 0);
+ }
+ if ((nodeFlags & FLAGS_HAS_CLASSNAME) != 0) {
+ parcel.writeString(mClassName);
+ }
+ if ((nodeFlags & FLAGS_HAS_ID) != 0) {
+ parcel.writeInt(mId);
+ if (mId != View.NO_ID) {
+ parcel.writeString(mIdEntry);
+ if (mIdEntry != null) {
+ parcel.writeString(mIdType);
+ parcel.writeString(mIdPackage);
+ }
+ }
+ }
+ if ((nodeFlags & FLAGS_HAS_LARGE_COORDS) != 0) {
+ parcel.writeInt(mX);
+ parcel.writeInt(mY);
+ parcel.writeInt(mWidth);
+ parcel.writeInt(mHeight);
+ } else {
+ parcel.writeInt((mY << 16) | mX);
+ parcel.writeInt((mHeight << 16) | mWidth);
+ }
+ if ((nodeFlags & FLAGS_HAS_SCROLL) != 0) {
+ parcel.writeInt(mScrollX);
+ parcel.writeInt(mScrollY);
+ }
+ if ((nodeFlags & FLAGS_HAS_MATRIX) != 0) {
+ //TODO(b/122484602): use a singleton tmpMatrix (if logic is not moved to superclass)
+ final float[] tmpMatrix = new float[9];
+ mMatrix.getValues(tmpMatrix);
+ parcel.writeFloatArray(tmpMatrix);
+ }
+ if ((nodeFlags & FLAGS_HAS_ELEVATION) != 0) {
+ parcel.writeFloat(mElevation);
+ }
+ if ((nodeFlags & FLAGS_HAS_ALPHA) != 0) {
+ parcel.writeFloat(mAlpha);
+ }
+ if ((nodeFlags & FLAGS_HAS_CONTENT_DESCRIPTION) != 0) {
+ TextUtils.writeToParcel(mContentDescription, parcel, 0);
+ }
+ if ((nodeFlags & FLAGS_HAS_EXTRAS) != 0) {
+ parcel.writeBundle(mExtras);
+ }
+ if ((nodeFlags & FLAGS_HAS_LOCALE_LIST) != 0) {
+ parcel.writeParcelable(mLocaleList, 0);
+ }
+ if ((nodeFlags & FLAGS_HAS_INPUT_TYPE) != 0) {
+ parcel.writeInt(mInputType);
+ }
+ if ((nodeFlags & FLAGS_HAS_MIN_TEXT_EMS) != 0) {
+ parcel.writeInt(mMinEms);
+ }
+ if ((nodeFlags & FLAGS_HAS_MAX_TEXT_EMS) != 0) {
+ parcel.writeInt(mMaxEms);
+ }
+ if ((nodeFlags & FLAGS_HAS_MAX_TEXT_LENGTH) != 0) {
+ parcel.writeInt(mMaxLength);
+ }
+ if ((nodeFlags & FLAGS_HAS_TEXT_ID_ENTRY) != 0) {
+ parcel.writeString(mTextIdEntry);
+ }
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_TYPE) != 0) {
+ parcel.writeInt(mAutofillType);
+ }
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_HINTS) != 0) {
+ parcel.writeStringArray(mAutofillHints);
+ }
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_VALUE) != 0) {
+ parcel.writeParcelable(mAutofillValue, 0);
+ }
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_OPTIONS) != 0) {
+ parcel.writeCharSequenceArray(mAutofillOptions);
+ }
+ }
+
/** @hide */
public static void writeToParcel(@NonNull Parcel parcel, @Nullable ViewNode node, int flags) {
if (node == null) {
- parcel.writeParcelable(null, flags);
- return;
+ parcel.writeLong(0);
+ } else {
+ node.writeSelfToParcel(parcel, flags);
}
- parcel.writeParcelable(node.mAutofillId, flags);
- parcel.writeParcelable(node.mParentAutofillId, flags);
- parcel.writeCharSequence(node.mText);
- parcel.writeString(node.mClassName);
}
/** @hide */
public static @Nullable ViewNode readFromParcel(@NonNull Parcel parcel) {
- final AutofillId id = parcel.readParcelable(null);
- if (id == null) return null;
-
- final ViewNode node = new ViewNode();
-
- node.mAutofillId = id;
- node.mParentAutofillId = parcel.readParcelable(null);
- node.mText = parcel.readCharSequence();
- node.mClassName = parcel.readString();
-
- return node;
+ final long nodeFlags = parcel.readLong();
+ return nodeFlags == 0 ? new ViewNode() : new ViewNode(nodeFlags, parcel);
}
/** @hide */
- static final class ViewStructureImpl extends ViewStructure {
+ @VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk.
+ public static final class ViewStructureImpl extends ViewStructure {
final ViewNode mNode = new ViewNode();
- ViewStructureImpl(@NonNull View view) {
+ @VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk.
+ public ViewStructureImpl(@NonNull View view) {
mNode.mAutofillId = Preconditions.checkNotNull(view).getAutofillId();
final ViewParent parent = view.getParent();
if (parent instanceof View) {
@@ -119,179 +671,212 @@
}
}
- ViewStructureImpl(@NonNull AutofillId parentId, int virtualId) {
+ @VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk.
+ public ViewStructureImpl(@NonNull AutofillId parentId, int virtualId) {
mNode.mParentAutofillId = Preconditions.checkNotNull(parentId);
mNode.mAutofillId = new AutofillId(parentId, virtualId);
}
+ @VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk.
+ public ViewNode getNode() {
+ return mNode;
+ }
+
@Override
public void setId(int id, String packageName, String typeName, String entryName) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mId = id;
+ mNode.mIdPackage = packageName;
+ mNode.mIdType = typeName;
+ mNode.mIdEntry = entryName;
}
@Override
public void setDimens(int left, int top, int scrollX, int scrollY, int width, int height) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mX = left;
+ mNode.mY = top;
+ mNode.mScrollX = scrollX;
+ mNode.mScrollY = scrollY;
+ mNode.mWidth = width;
+ mNode.mHeight = height;
}
@Override
public void setTransformation(Matrix matrix) {
- // TODO(b/111276913): implement or move to superclass
+ if (matrix == null) {
+ mNode.mMatrix = null;
+ } else {
+ mNode.mMatrix = new Matrix(matrix);
+ }
}
@Override
public void setElevation(float elevation) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mElevation = elevation;
}
@Override
public void setAlpha(float alpha) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mAlpha = alpha;
}
@Override
public void setVisibility(int visibility) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_VISIBILITY_MASK)
+ | (visibility & FLAGS_VISIBILITY_MASK);
}
@Override
public void setAssistBlocked(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_ASSIST_BLOCKED)
+ | (state ? FLAGS_ASSIST_BLOCKED : 0);
}
@Override
public void setEnabled(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_DISABLED) | (state ? 0 : FLAGS_DISABLED);
}
@Override
public void setClickable(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_CLICKABLE) | (state ? FLAGS_CLICKABLE : 0);
}
@Override
public void setLongClickable(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_LONG_CLICKABLE)
+ | (state ? FLAGS_LONG_CLICKABLE : 0);
}
@Override
public void setContextClickable(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_CONTEXT_CLICKABLE)
+ | (state ? FLAGS_CONTEXT_CLICKABLE : 0);
}
@Override
public void setFocusable(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_FOCUSABLE) | (state ? FLAGS_FOCUSABLE : 0);
}
@Override
public void setFocused(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_FOCUSED) | (state ? FLAGS_FOCUSED : 0);
}
@Override
public void setAccessibilityFocused(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_ACCESSIBILITY_FOCUSED)
+ | (state ? FLAGS_ACCESSIBILITY_FOCUSED : 0);
}
@Override
public void setCheckable(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_CHECKABLE) | (state ? FLAGS_CHECKABLE : 0);
}
@Override
public void setChecked(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_CHECKED) | (state ? FLAGS_CHECKED : 0);
}
@Override
public void setSelected(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_SELECTED) | (state ? FLAGS_SELECTED : 0);
}
@Override
public void setActivated(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_ACTIVATED) | (state ? FLAGS_ACTIVATED : 0);
}
@Override
public void setOpaque(boolean opaque) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_OPAQUE) | (opaque ? FLAGS_OPAQUE : 0);
}
@Override
public void setClassName(String className) {
- // TODO(b/111276913): temporarily setting directly; should be done on superclass instead
mNode.mClassName = className;
}
@Override
public void setContentDescription(CharSequence contentDescription) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mContentDescription = contentDescription;
}
@Override
public void setText(CharSequence text) {
- // TODO(b/111276913): temporarily setting directly; should be done on superclass instead
- mNode.mText = text;
+ final ViewNodeText t = getNodeText();
+ t.mText = TextUtils.trimNoCopySpans(text);
+ t.mTextSelectionStart = t.mTextSelectionEnd = -1;
}
@Override
public void setText(CharSequence text, int selectionStart, int selectionEnd) {
- // TODO(b/111276913): temporarily setting directly; should be done on superclass instead
- mNode.mText = text;
- // TODO(b/111276913): implement or move to superclass
+ final ViewNodeText t = getNodeText();
+ t.mText = TextUtils.trimNoCopySpans(text);
+ t.mTextSelectionStart = selectionStart;
+ t.mTextSelectionEnd = selectionEnd;
}
@Override
public void setTextStyle(float size, int fgColor, int bgColor, int style) {
- // TODO(b/111276913): implement or move to superclass
+ final ViewNodeText t = getNodeText();
+ t.mTextColor = fgColor;
+ t.mTextBackgroundColor = bgColor;
+ t.mTextSize = size;
+ t.mTextStyle = style;
}
@Override
public void setTextLines(int[] charOffsets, int[] baselines) {
- // TODO(b/111276913): implement or move to superclass
+ final ViewNodeText t = getNodeText();
+ t.mLineCharOffsets = charOffsets;
+ t.mLineBaselines = baselines;
+ }
+
+ @Override
+ public void setTextIdEntry(String entryName) {
+ mNode.mTextIdEntry = Preconditions.checkNotNull(entryName);
}
@Override
public void setHint(CharSequence hint) {
- // TODO(b/111276913): implement or move to superclass
+ getNodeText().mHint = hint != null ? hint.toString() : null;
}
@Override
public CharSequence getText() {
- // TODO(b/111276913): temporarily getting directly; should be done on superclass instead
- return mNode.mText;
+ return mNode.getText();
}
@Override
public int getTextSelectionStart() {
- // TODO(b/111276913): implement or move to superclass
- return 0;
+ return mNode.getTextSelectionStart();
}
@Override
public int getTextSelectionEnd() {
- // TODO(b/111276913): implement or move to superclass
- return 0;
+ return mNode.getTextSelectionEnd();
}
@Override
public CharSequence getHint() {
- // TODO(b/111276913): implement or move to superclass
- return null;
+ return mNode.getHint();
}
@Override
public Bundle getExtras() {
- // TODO(b/111276913): implement or move to superclass
- return null;
+ if (mNode.mExtras != null) {
+ return mNode.mExtras;
+ }
+ mNode.mExtras = new Bundle();
+ return mNode.mExtras;
}
@Override
public boolean hasExtras() {
- // TODO(b/111276913): implement or move to superclass
- return false;
+ return mNode.mExtras != null;
}
@Override
@@ -325,50 +910,64 @@
@Override
public AutofillId getAutofillId() {
- // TODO(b/111276913): temporarily getting directly; should be done on superclass instead
return mNode.mAutofillId;
}
@Override
public void setAutofillId(AutofillId id) {
- // TODO(b/111276913): temporarily setting directly; should be done on superclass instead
- mNode.mAutofillId = id;
+ mNode.mAutofillId = Preconditions.checkNotNull(id);
}
+
@Override
public void setAutofillId(AutofillId parentId, int virtualId) {
- // TODO(b/111276913): temporarily setting directly; should be done on superclass instead
+ mNode.mParentAutofillId = Preconditions.checkNotNull(parentId);
mNode.mAutofillId = new AutofillId(parentId, virtualId);
}
@Override
- public void setAutofillType(int type) {
- // TODO(b/111276913): implement or move to superclass
+ public void setAutofillType(@View.AutofillType int type) {
+ mNode.mAutofillType = type;
}
@Override
- public void setAutofillHints(String[] hint) {
- // TODO(b/111276913): implement or move to superclass
+ public void setAutofillHints(String[] hints) {
+ mNode.mAutofillHints = hints;
}
@Override
public void setAutofillValue(AutofillValue value) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mAutofillValue = value;
}
@Override
public void setAutofillOptions(CharSequence[] options) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mAutofillOptions = options;
}
@Override
public void setInputType(int inputType) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mInputType = inputType;
+ }
+
+ @Override
+ public void setMinTextEms(int minEms) {
+ mNode.mMinEms = minEms;
+ }
+
+ @Override
+ public void setMaxTextEms(int maxEms) {
+ mNode.mMaxEms = maxEms;
+ }
+
+ @Override
+ public void setMaxTextLength(int maxLength) {
+ mNode.mMaxLength = maxLength;
}
@Override
public void setDataIsSensitive(boolean sensitive) {
- // TODO(b/111276913): implement or move to superclass
+ Log.w(TAG, "setDataIsSensitive() is not supported");
}
@Override
@@ -378,7 +977,7 @@
@Override
public Rect getTempRect() {
- // TODO(b/111276913): implement or move to superclass
+ Log.w(TAG, "getTempRect() is not supported");
return null;
}
@@ -389,7 +988,7 @@
@Override
public void setLocaleList(LocaleList localeList) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mLocaleList = localeList;
}
@Override
@@ -402,5 +1001,66 @@
public void setHtmlInfo(HtmlInfo htmlInfo) {
Log.w(TAG, "setHtmlInfo() is not supported");
}
+
+ private ViewNodeText getNodeText() {
+ if (mNode.mText != null) {
+ return mNode.mText;
+ }
+ mNode.mText = new ViewNodeText();
+ return mNode.mText;
+ }
+ }
+
+ //TODO(b/122484602): copied 'as-is' from AssistStructure, except for writeSensitive
+ static final class ViewNodeText {
+ CharSequence mText;
+ float mTextSize;
+ int mTextStyle;
+ int mTextColor = ViewNode.TEXT_COLOR_UNDEFINED;
+ int mTextBackgroundColor = ViewNode.TEXT_COLOR_UNDEFINED;
+ int mTextSelectionStart;
+ int mTextSelectionEnd;
+ int[] mLineCharOffsets;
+ int[] mLineBaselines;
+ String mHint;
+
+ ViewNodeText() {
+ }
+
+ boolean isSimple() {
+ return mTextBackgroundColor == ViewNode.TEXT_COLOR_UNDEFINED
+ && mTextSelectionStart == 0 && mTextSelectionEnd == 0
+ && mLineCharOffsets == null && mLineBaselines == null && mHint == null;
+ }
+
+ ViewNodeText(Parcel in, boolean simple) {
+ mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mTextSize = in.readFloat();
+ mTextStyle = in.readInt();
+ mTextColor = in.readInt();
+ if (!simple) {
+ mTextBackgroundColor = in.readInt();
+ mTextSelectionStart = in.readInt();
+ mTextSelectionEnd = in.readInt();
+ mLineCharOffsets = in.createIntArray();
+ mLineBaselines = in.createIntArray();
+ mHint = in.readString();
+ }
+ }
+
+ void writeToParcel(Parcel out, boolean simple) {
+ TextUtils.writeToParcel(mText, out, 0);
+ out.writeFloat(mTextSize);
+ out.writeInt(mTextStyle);
+ out.writeInt(mTextColor);
+ if (!simple) {
+ out.writeInt(mTextBackgroundColor);
+ out.writeInt(mTextSelectionStart);
+ out.writeInt(mTextSelectionEnd);
+ out.writeIntArray(mLineCharOffsets);
+ out.writeIntArray(mLineBaselines);
+ out.writeString(mHint);
+ }
+ }
}
}
diff --git a/core/java/android/view/inputmethod/InputMethodSystemProperty.java b/core/java/android/view/inputmethod/InputMethodSystemProperty.java
index b233b75..57ed7f9 100644
--- a/core/java/android/view/inputmethod/InputMethodSystemProperty.java
+++ b/core/java/android/view/inputmethod/InputMethodSystemProperty.java
@@ -41,6 +41,17 @@
*/
private static final String PROP_DEBUG_MULTI_CLIENT_IME = "persist.debug.multi_client_ime";
+ /**
+ * System property key for debugging purpose. The value must be empty, "1", or "0".
+ *
+ * <p>Values 'y', 'yes', '1', 'true' or 'on' are considered true.</p>
+ *
+ * <p>To set, run "adb root && adb shell setprop persist.debug.per_profile_ime 1".</p>
+ *
+ * <p>This value will be ignored when {@link Build#IS_DEBUGGABLE} returns {@code false}.</p>
+ */
+ private static final String PROP_DEBUG_PER_PROFILE_IME = "persist.debug.per_profile_ime";
+
@Nullable
private static ComponentName getMultiClientImeComponentName() {
if (Build.IS_DEBUGGABLE) {
@@ -59,6 +70,9 @@
/**
* {@link ComponentName} of multi-client IME to be used.
*
+ * <p>TODO: Move this back to MultiClientInputMethodManagerService once
+ * {@link #PER_PROFILE_IME_ENABLED} always becomes {@code true}.</p>
+ *
* @hide
*/
@Nullable
@@ -68,7 +82,19 @@
/**
* {@code true} when multi-client IME is enabled.
*
+ * <p>TODO: Move this back to MultiClientInputMethodManagerService once
+ * {@link #PER_PROFILE_IME_ENABLED} always becomes {@code true}.</p>
+ *
* @hide
*/
public static final boolean MULTI_CLIENT_IME_ENABLED = (sMultiClientImeComponentName != null);
+
+ /**
+ * {@code true} when per-profile IME is enabled.
+ * @hide
+ */
+ public static final boolean PER_PROFILE_IME_ENABLED = MULTI_CLIENT_IME_ENABLED
+ || Build.IS_DEBUGGABLE && SystemProperties.getBoolean(
+ PROP_DEBUG_PER_PROFILE_IME, false);
+
}
diff --git a/core/java/android/view/textservice/TextServicesManager.java b/core/java/android/view/textservice/TextServicesManager.java
index 4c6862c..5dc8b19 100644
--- a/core/java/android/view/textservice/TextServicesManager.java
+++ b/core/java/android/view/textservice/TextServicesManager.java
@@ -67,12 +67,6 @@
private static final String TAG = TextServicesManager.class.getSimpleName();
private static final boolean DBG = false;
- /**
- * A compile time switch to control per-profile spell checker, which is not yet ready.
- * @hide
- */
- public static final boolean DISABLE_PER_PROFILE_SPELL_CHECKER = true;
-
private static TextServicesManager sInstance;
private final ITextServicesManager mService;
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 20febc2..afe46701 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -837,8 +837,8 @@
mContentWidth = width;
mContentHeight = height;
- mOffsetX = (int) (0.1f * width);
- mOffsetY = (int) (0.1f * height);
+ mOffsetX = (int) (1.05f * elevation);
+ mOffsetY = (int) (1.05f * elevation);
// Setup the surface we will use for drawing the content and shadow.
mSurfaceWidth = mContentWidth + 2 * mOffsetX;
mSurfaceHeight = mContentHeight + 2 * mOffsetY;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 0ea8579..51b8734 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3520,7 +3520,7 @@
*/
@android.view.RemotableViewMethod
public void setTextSelectHandle(@DrawableRes int textSelectHandle) {
- Preconditions.checkArgumentPositive(textSelectHandle,
+ Preconditions.checkArgument(textSelectHandle != 0,
"The text select handle should be a valid drawable resource id.");
setTextSelectHandle(mContext.getDrawable(textSelectHandle));
}
@@ -3577,7 +3577,7 @@
*/
@android.view.RemotableViewMethod
public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) {
- Preconditions.checkArgumentPositive(textSelectHandleLeft,
+ Preconditions.checkArgument(textSelectHandleLeft != 0,
"The text select left handle should be a valid drawable resource id.");
setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft));
}
@@ -3634,7 +3634,7 @@
*/
@android.view.RemotableViewMethod
public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) {
- Preconditions.checkArgumentPositive(textSelectHandleRight,
+ Preconditions.checkArgument(textSelectHandleRight != 0,
"The text select right handle should be a valid drawable resource id.");
setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight));
}
@@ -3667,9 +3667,7 @@
* @see #setTextCursorDrawable(int)
* @attr ref android.R.styleable#TextView_textCursorDrawable
*/
- public void setTextCursorDrawable(@NonNull Drawable textCursorDrawable) {
- Preconditions.checkNotNull(textCursorDrawable,
- "The cursor drawable should not be null.");
+ public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) {
mCursorDrawable = textCursorDrawable;
mCursorDrawableRes = 0;
if (mEditor != null) {
@@ -3687,8 +3685,6 @@
* @attr ref android.R.styleable#TextView_textCursorDrawable
*/
public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) {
- Preconditions.checkArgumentPositive(textCursorDrawable,
- "The cursor drawable should be a valid drawable resource id.");
setTextCursorDrawable(mContext.getDrawable(textCursorDrawable));
}
@@ -8061,6 +8057,26 @@
}
}
break;
+
+ case KeyEvent.KEYCODE_FORWARD_DEL:
+ if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) {
+ if (onTextContextMenuItem(ID_CUT)) {
+ return KEY_EVENT_HANDLED;
+ }
+ }
+ break;
+
+ case KeyEvent.KEYCODE_INSERT:
+ if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) {
+ if (onTextContextMenuItem(ID_COPY)) {
+ return KEY_EVENT_HANDLED;
+ }
+ } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) {
+ if (onTextContextMenuItem(ID_PASTE)) {
+ return KEY_EVENT_HANDLED;
+ }
+ }
+ break;
}
if (mEditor != null && mEditor.mKeyListener != null) {
@@ -10831,25 +10847,6 @@
return onTextContextMenuItem(ID_PASTE);
}
break;
- case KeyEvent.KEYCODE_INSERT:
- if (canCopy()) {
- return onTextContextMenuItem(ID_COPY);
- }
- break;
- }
- } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
- // Handle Shift-only shortcuts.
- switch (keyCode) {
- case KeyEvent.KEYCODE_FORWARD_DEL:
- if (canCut()) {
- return onTextContextMenuItem(ID_CUT);
- }
- break;
- case KeyEvent.KEYCODE_INSERT:
- if (canPaste()) {
- return onTextContextMenuItem(ID_PASTE);
- }
- break;
}
} else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
// Handle Ctrl-Shift shortcuts.
diff --git a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java
index 100c6ee..adf7692 100644
--- a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java
+++ b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java
@@ -16,10 +16,10 @@
package com.android.internal.policy;
-import com.android.internal.R;
-
import android.content.res.Resources;
+import com.android.internal.R;
+
/**
* Utility functions for screen decorations used by both window manager and System UI.
*/
@@ -31,15 +31,19 @@
* scaling, this means that we don't have to reload them on config changes.
*/
public static float getWindowCornerRadius(Resources resources) {
+ if (!supportsRoundedCornersOnWindows(resources)) {
+ return 0f;
+ }
+
// Radius that should be used in case top or bottom aren't defined.
float defaultRadius = resources.getDimension(R.dimen.rounded_corner_radius);
float topRadius = resources.getDimension(R.dimen.rounded_corner_radius_top);
- if (topRadius == 0) {
+ if (topRadius == 0f) {
topRadius = defaultRadius;
}
float bottomRadius = resources.getDimension(R.dimen.rounded_corner_radius_bottom);
- if (bottomRadius == 0) {
+ if (bottomRadius == 0f) {
bottomRadius = defaultRadius;
}
@@ -47,4 +51,11 @@
// completely cover the display.
return Math.min(topRadius, bottomRadius);
}
+
+ /**
+ * If live rounded corners are supported on windows.
+ */
+ public static boolean supportsRoundedCornersOnWindows(Resources resources) {
+ return resources.getBoolean(R.bool.config_supportsRoundedCornersOnWindows);
+ }
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 53b56f2..6a28059 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -153,13 +153,11 @@
void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type,
boolean requireConfirmation, int userId);
// Used to hide the dialog when a biometric is authenticated
- void onBiometricAuthenticated();
+ void onBiometricAuthenticated(boolean authenticated);
// Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
void onBiometricHelp(String message);
// Used to set a message - the dialog will dismiss after a certain amount of time
void onBiometricError(String error);
// Used to hide the biometric dialog when the AuthenticationClient is stopped
void hideBiometricDialog();
- // Used to request the "try again" button for authentications which requireConfirmation=true
- void showBiometricTryAgain();
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 9087dd2..197e873 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -97,13 +97,11 @@
void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type,
boolean requireConfirmation, int userId);
// Used to hide the dialog when a biometric is authenticated
- void onBiometricAuthenticated();
+ void onBiometricAuthenticated(boolean authenticated);
// Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
void onBiometricHelp(String message);
// Used to set a message - the dialog will dismiss after a certain amount of time
void onBiometricError(String error);
// Used to hide the biometric dialog when the AuthenticationClient is stopped
void hideBiometricDialog();
- // Used to request the "try again" button for authentications which requireConfirmation=true
- void showBiometricTryAgain();
}
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index ecf8119..50cff5c 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -334,7 +334,7 @@
static jlong android_view_MotionEvent_nativeInitialize(JNIEnv* env, jclass clazz,
jlong nativePtr,
jint deviceId, jint source, jint displayId, jint action, jint flags, jint edgeFlags,
- jint metaState, jint buttonState,
+ jint metaState, jint buttonState, jint classification,
jfloat xOffset, jfloat yOffset, jfloat xPrecision, jfloat yPrecision,
jlong downTimeNanos, jlong eventTimeNanos,
jint pointerCount, jobjectArray pointerPropertiesObjArray,
@@ -373,7 +373,8 @@
}
event->initialize(deviceId, source, displayId, action, 0, flags, edgeFlags, metaState,
- buttonState, xOffset, yOffset, xPrecision, yPrecision,
+ buttonState, static_cast<MotionClassification>(classification),
+ xOffset, yOffset, xPrecision, yPrecision,
downTimeNanos, eventTimeNanos, pointerCount, pointerProperties, rawPointerCoords);
return reinterpret_cast<jlong>(event);
@@ -668,6 +669,11 @@
event->setButtonState(buttonState);
}
+static jint android_view_MotionEvent_nativeGetClassification(jlong nativePtr) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ return static_cast<jint>(event->getClassification());
+}
+
static void android_view_MotionEvent_nativeOffsetLocation(jlong nativePtr, jfloat deltaX,
jfloat deltaY) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
@@ -747,7 +753,7 @@
static const JNINativeMethod gMotionEventMethods[] = {
/* name, signature, funcPtr */
{ "nativeInitialize",
- "(JIIIIIIIIFFFFJJI[Landroid/view/MotionEvent$PointerProperties;"
+ "(JIIIIIIIIIFFFFJJI[Landroid/view/MotionEvent$PointerProperties;"
"[Landroid/view/MotionEvent$PointerCoords;)J",
(void*)android_view_MotionEvent_nativeInitialize },
{ "nativeDispose",
@@ -846,6 +852,9 @@
{ "nativeSetButtonState",
"(JI)V",
(void*)android_view_MotionEvent_nativeSetButtonState },
+ { "nativeGetClassification",
+ "(J)I",
+ (void*)android_view_MotionEvent_nativeGetClassification },
{ "nativeOffsetLocation",
"(JFF)V",
(void*)android_view_MotionEvent_nativeOffsetLocation },
diff --git a/core/proto/android/app/window_configuration.proto b/core/proto/android/app/window_configuration.proto
index 2d15552..6cc1a40 100644
--- a/core/proto/android/app/window_configuration.proto
+++ b/core/proto/android/app/window_configuration.proto
@@ -29,4 +29,5 @@
optional .android.graphics.RectProto app_bounds = 1;
optional int32 windowing_mode = 2;
optional int32 activity_type = 3;
+ optional .android.graphics.RectProto bounds = 4;
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1c98c66..140225e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3648,4 +3648,8 @@
set in AndroidManifest.
{@see android.view.Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS} -->
<string name="config_secondaryHomeComponent" translatable="false">com.android.launcher3/com.android.launcher3.SecondaryDisplayLauncher</string>
+
+ <!-- If device supports corner radius on windows.
+ This should be turned off on low-end devices to improve animation performance. -->
+ <bool name="config_supportsRoundedCornersOnWindows">true</bool>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 01fbf80..f8c9037 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3515,6 +3515,7 @@
<java-symbol type="dimen" name="rounded_corner_radius" />
<java-symbol type="dimen" name="rounded_corner_radius_top" />
<java-symbol type="dimen" name="rounded_corner_radius_bottom" />
+ <java-symbol type="bool" name="config_supportsRoundedCornersOnWindows" />
<java-symbol type="string" name="config_defaultModuleMetadataProvider" />
diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk
index 041fb7e..74943c7 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -43,7 +43,8 @@
platform-test-annotations \
compatibility-device-util \
truth-prebuilt \
- print-test-util-lib
+ print-test-util-lib \
+ testng # TODO: remove once Android migrates to JUnit 4.12, which provide assertThrows
LOCAL_JAVA_LIBRARIES := \
android.test.runner \
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
new file mode 100644
index 0000000..59f3a4c
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.contentcapture;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.view.autofill.AutofillId;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+/**
+ * Unit test for {@link ContentCaptureSessionTest}.
+ *
+ * <p>To run it:
+ * {@code atest FrameworksCoreTests:android.view.contentcapture.ContentCaptureSessionTest}
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ContentCaptureSessionTest {
+
+ /**
+ * Uses a spy as ContentCaptureSession is abstract but (so far) we're testing its concrete
+ * methods.
+ */
+ @Spy
+ private ContentCaptureSession mMockSession;
+
+ @Test
+ public void testNewAutofillId_invalid() {
+ assertThrows(NullPointerException.class, () -> mMockSession.newAutofillId(null, 42));
+ assertThrows(IllegalArgumentException.class,
+ () -> mMockSession.newAutofillId(new AutofillId(42, 42), 42));
+ }
+
+ @Test
+ public void testNewAutofillId_valid() {
+ final AutofillId parentId = new AutofillId(42);
+ final AutofillId childId = mMockSession.newAutofillId(parentId, 108);
+ assertThat(childId.getViewId()).isEqualTo(42);
+ assertThat(childId.getVirtualChildId()).isEqualTo(108);
+ // TODO(b/121197119): assert session id
+ }
+
+ @Test
+ public void testNotifyXXX_null() {
+ assertThrows(NullPointerException.class, () -> mMockSession.notifyViewAppeared(null));
+ assertThrows(NullPointerException.class, () -> mMockSession.notifyViewDisappeared(null));
+ assertThrows(NullPointerException.class,
+ () -> mMockSession.notifyViewTextChanged(null, "whatever", 0));
+ }
+}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
new file mode 100644
index 0000000..995946b
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
@@ -0,0 +1,462 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.contentcapture;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.os.Bundle;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.support.test.InstrumentationRegistry;
+import android.view.View;
+import android.view.ViewStructure.HtmlInfo;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.view.contentcapture.ViewNode.ViewStructureImpl;
+import android.widget.FrameLayout;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Locale;
+
+/**
+ * Unit test for {@link ViewNode}.
+ *
+ * <p>To run it: {@code atest FrameworksCoreTests:android.view.contentcapture.ViewNodeTest}
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ViewNodeTest {
+
+ private final Context mContext = InstrumentationRegistry.getTargetContext();
+
+ @Mock
+ private HtmlInfo mHtmlInfoMock;
+
+ @Test
+ public void testAutofillIdMethods_orphanView() {
+ View view = new View(mContext);
+ AutofillId initialId = new AutofillId(42);
+ view.setAutofillId(initialId);
+
+ ViewStructureImpl structure = new ViewStructureImpl(view);
+ ViewNode node = structure.getNode();
+
+ assertThat(node.getAutofillId()).isEqualTo(initialId);
+ assertThat(node.getParentAutofillId()).isNull();
+
+ AutofillId newId = new AutofillId(108);
+ structure.setAutofillId(newId);
+ assertThat(node.getAutofillId()).isEqualTo(newId);
+ assertThat(node.getParentAutofillId()).isNull();
+
+ structure.setAutofillId(new AutofillId(66), 6);
+ assertThat(node.getAutofillId()).isEqualTo(new AutofillId(66, 6));
+ assertThat(node.getParentAutofillId()).isEqualTo(new AutofillId(66));
+ }
+
+ @Test
+ public void testAutofillIdMethods_parentedView() {
+ FrameLayout parent = new FrameLayout(mContext);
+ AutofillId initialParentId = new AutofillId(48);
+ parent.setAutofillId(initialParentId);
+
+ View child = new View(mContext);
+ AutofillId initialChildId = new AutofillId(42);
+ child.setAutofillId(initialChildId);
+
+ parent.addView(child);
+
+ ViewStructureImpl structure = new ViewStructureImpl(child);
+ ViewNode node = structure.getNode();
+
+ assertThat(node.getAutofillId()).isEqualTo(initialChildId);
+ assertThat(node.getParentAutofillId()).isEqualTo(initialParentId);
+
+ AutofillId newChildId = new AutofillId(108);
+ structure.setAutofillId(newChildId);
+ assertThat(node.getAutofillId()).isEqualTo(newChildId);
+ assertThat(node.getParentAutofillId()).isEqualTo(initialParentId);
+
+ AutofillId newParentId = new AutofillId(15162342);
+ parent.setAutofillId(newParentId);
+ assertThat(node.getAutofillId()).isEqualTo(newChildId);
+ assertThat(node.getParentAutofillId()).isEqualTo(initialParentId);
+
+ structure.setAutofillId(new AutofillId(66), 6);
+ assertThat(node.getAutofillId()).isEqualTo(new AutofillId(66, 6));
+ assertThat(node.getParentAutofillId()).isEqualTo(new AutofillId(66));
+ }
+
+ @Test
+ public void testAutofillIdMethods_explicitIdsConstructor() {
+ AutofillId initialParentId = new AutofillId(42);
+ ViewStructureImpl structure = new ViewStructureImpl(initialParentId, 108);
+ ViewNode node = structure.getNode();
+
+ assertThat(node.getAutofillId()).isEqualTo(new AutofillId(initialParentId, 108));
+ assertThat(node.getParentAutofillId()).isEqualTo(initialParentId);
+
+ AutofillId newChildId = new AutofillId(108);
+ structure.setAutofillId(newChildId);
+ assertThat(node.getAutofillId()).isEqualTo(newChildId);
+ assertThat(node.getParentAutofillId()).isEqualTo(initialParentId);
+
+ structure.setAutofillId(new AutofillId(66), 6);
+ assertThat(node.getAutofillId()).isEqualTo(new AutofillId(66, 6));
+ assertThat(node.getParentAutofillId()).isEqualTo(new AutofillId(66));
+ }
+
+ @Test
+ public void testInvalidSetters() {
+ View view = new View(mContext);
+ AutofillId initialId = new AutofillId(42);
+ view.setAutofillId(initialId);
+
+ ViewStructureImpl structure = new ViewStructureImpl(view);
+ ViewNode node = structure.getNode();
+ assertThat(node.getAutofillId()).isEqualTo(initialId); // sanity check
+
+ assertThrows(NullPointerException.class, () -> structure.setAutofillId(null));
+ assertThat(node.getAutofillId()).isEqualTo(initialId); // invariant
+
+ assertThrows(NullPointerException.class, () -> structure.setAutofillId(null, 666));
+ assertThat(node.getAutofillId()).isEqualTo(initialId); // invariant
+
+ assertThrows(NullPointerException.class, () -> structure.setTextIdEntry(null));
+ assertThat(node.getTextIdEntry()).isNull();
+ }
+
+ @Test
+ public void testUnsupportedProperties() {
+ View view = new View(mContext);
+
+ ViewStructureImpl structure = new ViewStructureImpl(view);
+ ViewNode node = structure.getNode();
+
+ structure.setChildCount(1);
+ assertThat(node.getChildCount()).isEqualTo(0);
+
+ structure.addChildCount(1);
+ assertThat(node.getChildCount()).isEqualTo(0);
+
+ assertThat(structure.newChild(0)).isNull();
+ assertThat(node.getChildCount()).isEqualTo(0);
+
+ assertThat(structure.asyncNewChild(0)).isNull();
+ assertThat(node.getChildCount()).isEqualTo(0);
+
+ structure.asyncCommit();
+ assertThat(node.getChildCount()).isEqualTo(0);
+
+ structure.setWebDomain("Y U NO SET?");
+ assertThat(node.getWebDomain()).isNull();
+
+ assertThat(structure.newHtmlInfoBuilder("WHATEVER")).isNull();
+
+ structure.setHtmlInfo(mHtmlInfoMock);
+ assertThat(node.getHtmlInfo()).isNull();
+
+ structure.setDataIsSensitive(true);
+
+ assertThat(structure.getTempRect()).isNull();
+ }
+
+ @Test
+ public void testValidProperties_directly() {
+ ViewStructureImpl structure = newSimpleStructure();
+ assertSimpleStructure(structure);
+ assertSimpleNode(structure.getNode());
+ }
+
+ @Test
+ public void testValidProperties_throughParcel() {
+ ViewStructureImpl structure = newSimpleStructure();
+ final ViewNode node = structure.getNode();
+ assertSimpleNode(node); // sanity check
+
+ final ViewNode clone = cloneThroughParcel(node);
+ assertSimpleNode(clone);
+ }
+
+ @Test
+ public void testComplexText_directly() {
+ ViewStructureImpl structure = newStructureWithComplexText();
+ assertStructureWithComplexText(structure);
+ assertNodeWithComplexText(structure.getNode());
+ }
+
+ @Test
+ public void testComplexText_throughParcel() {
+ ViewStructureImpl structure = newStructureWithComplexText();
+ final ViewNode node = structure.getNode();
+ assertNodeWithComplexText(node); // sanity check
+
+ ViewNode clone = cloneThroughParcel(node);
+ assertNodeWithComplexText(clone);
+ }
+
+ @Test
+ public void testVisibility() {
+ // Visibility is a special case becase it use flag masks, so we want to make sure it works
+ // fine
+ View view = new View(mContext);
+ ViewStructureImpl structure = new ViewStructureImpl(view);
+ ViewNode node = structure.getNode();
+
+ structure.setVisibility(View.VISIBLE);
+ assertThat(node.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.VISIBLE);
+
+ structure.setVisibility(View.GONE);
+ assertThat(node.getVisibility()).isEqualTo(View.GONE);
+ assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.GONE);
+
+ structure.setVisibility(View.VISIBLE);
+ assertThat(node.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.VISIBLE);
+
+ structure.setVisibility(View.INVISIBLE);
+ assertThat(node.getVisibility()).isEqualTo(View.INVISIBLE);
+ assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.INVISIBLE);
+
+ structure.setVisibility(View.INVISIBLE | View.GONE);
+ assertThat(node.getVisibility()).isEqualTo(View.INVISIBLE | View.GONE);
+ assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.INVISIBLE | View.GONE);
+
+
+ final int invalidValue = Math.max(Math.max(View.VISIBLE, View.INVISIBLE), View.GONE) * 2;
+ structure.setVisibility(View.VISIBLE);
+ structure.setVisibility(invalidValue); // should be ignored
+ assertThat(node.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.VISIBLE);
+
+ structure.setVisibility(View.GONE | invalidValue);
+ assertThat(node.getVisibility()).isEqualTo(View.GONE);
+ assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.GONE);
+ }
+
+ /**
+ * Creates a {@link ViewStructureImpl} that can be asserted through
+ * {@link #assertSimpleNode(ViewNode)}.
+ */
+ private ViewStructureImpl newSimpleStructure() {
+ View view = new View(mContext);
+ view.setAutofillId(new AutofillId(42));
+
+ ViewStructureImpl structure = new ViewStructureImpl(view);
+
+ // Basic properties
+ structure.setText("Text is set!");
+ structure.setClassName("Classy!");
+ structure.setContentDescription("Described I am!");
+ structure.setVisibility(View.INVISIBLE);
+
+ // Autofill properties
+ structure.setAutofillType(View.AUTOFILL_TYPE_TEXT);
+ structure.setAutofillHints(new String[] { "Auto", "Man" });
+ structure.setAutofillOptions(new String[] { "Maybe" });
+ structure.setAutofillValue(AutofillValue.forText("Malkovich"));
+
+ // Graphic properties
+ structure.setElevation(6.66f);
+ structure.setAlpha(66.6f);
+ structure.setTransformation(Matrix.IDENTITY_MATRIX);
+
+ // Extra text properties
+ structure.setMinTextEms(6);
+ structure.setMaxTextLength(66);
+ structure.setMaxTextEms(666);
+ structure.setInputType(42);
+ structure.setTextIdEntry("TEXT, Y U NO ENTRY?");
+ structure.setLocaleList(new LocaleList(Locale.US, Locale.ENGLISH));
+
+ // Resource id
+ structure.setId(16, "package.name", "type.name", "entry.name");
+
+ // Dimensions
+ structure.setDimens(4, 8, 15, 16, 23, 42);
+
+ // Boolean properties
+ structure.setAssistBlocked(true);
+ structure.setEnabled(true);
+ structure.setClickable(true);
+ structure.setLongClickable(true);
+ structure.setContextClickable(true);
+ structure.setFocusable(true);
+ structure.setFocused(true);
+ structure.setAccessibilityFocused(true);
+ structure.setChecked(true);
+ structure.setActivated(true);
+ structure.setOpaque(true);
+
+ // Bundle
+ assertThat(structure.hasExtras()).isFalse();
+ final Bundle bundle = structure.getExtras();
+ assertThat(bundle).isNotNull();
+ bundle.putString("Marlon", "Bundle");
+ assertThat(structure.hasExtras()).isTrue();
+ return structure;
+ }
+
+ /**
+ * Asserts the properties of a {@link ViewNode} that was created by
+ * {@link #newSimpleStructure()}.
+ */
+ private void assertSimpleNode(ViewNode node) {
+
+ // Basic properties
+ assertThat(node.getAutofillId()).isEqualTo(new AutofillId(42));
+ assertThat(node.getParentAutofillId()).isNull();
+ assertThat(node.getText()).isEqualTo("Text is set!");
+ assertThat(node.getClassName()).isEqualTo("Classy!");
+ assertThat(node.getContentDescription().toString()).isEqualTo("Described I am!");
+ assertThat(node.getVisibility()).isEqualTo(View.INVISIBLE);
+
+ // Autofill properties
+ assertThat(node.getAutofillType()).isEqualTo(View.AUTOFILL_TYPE_TEXT);
+ assertThat(node.getAutofillHints()).asList().containsExactly("Auto", "Man").inOrder();
+ assertThat(node.getAutofillOptions()).asList().containsExactly("Maybe").inOrder();
+ assertThat(node.getAutofillValue().getTextValue()).isEqualTo("Malkovich");
+
+ // Graphic properties
+ assertThat(node.getElevation()).isWithin(1.0e-10f).of(6.66f);
+ assertThat(node.getAlpha()).isWithin(1.0e-10f).of(66.6f);
+ assertThat(node.getTransformation()).isEqualTo(Matrix.IDENTITY_MATRIX);
+
+ // Extra text properties
+ assertThat(node.getMinTextEms()).isEqualTo(6);
+ assertThat(node.getMaxTextLength()).isEqualTo(66);
+ assertThat(node.getMaxTextEms()).isEqualTo(666);
+ assertThat(node.getInputType()).isEqualTo(42);
+ assertThat(node.getTextIdEntry()).isEqualTo("TEXT, Y U NO ENTRY?");
+ assertThat(node.getLocaleList()).isEqualTo(new LocaleList(Locale.US, Locale.ENGLISH));
+
+ // Resource id
+ assertThat(node.getId()).isEqualTo(16);
+ assertThat(node.getIdPackage()).isEqualTo("package.name");
+ assertThat(node.getIdType()).isEqualTo("type.name");
+ assertThat(node.getIdEntry()).isEqualTo("entry.name");
+
+ // Dimensions
+ assertThat(node.getLeft()).isEqualTo(4);
+ assertThat(node.getTop()).isEqualTo(8);
+ assertThat(node.getScrollX()).isEqualTo(15);
+ assertThat(node.getScrollY()).isEqualTo(16);
+ assertThat(node.getWidth()).isEqualTo(23);
+ assertThat(node.getHeight()).isEqualTo(42);
+
+ // Boolean properties
+ assertThat(node.isAssistBlocked()).isTrue();
+ assertThat(node.isEnabled()).isTrue();
+ assertThat(node.isClickable()).isTrue();
+ assertThat(node.isLongClickable()).isTrue();
+ assertThat(node.isContextClickable()).isTrue();
+ assertThat(node.isFocusable()).isTrue();
+ assertThat(node.isFocused()).isTrue();
+ assertThat(node.isAccessibilityFocused()).isTrue();
+ assertThat(node.isChecked()).isTrue();
+ assertThat(node.isActivated()).isTrue();
+ assertThat(node.isOpaque()).isTrue();
+
+ // Bundle
+ final Bundle bundle = node.getExtras();
+ assertThat(bundle).isNotNull();
+ assertThat(bundle.size()).isEqualTo(1);
+ assertThat(bundle.getString("Marlon")).isEqualTo("Bundle");
+ }
+
+ /**
+ * Asserts the properties of a {@link ViewStructureImpl} that was created by
+ * {@link #newSimpleStructure()}.
+ */
+ private void assertSimpleStructure(ViewStructureImpl structure) {
+ assertThat(structure.getAutofillId()).isEqualTo(new AutofillId(42));
+ assertThat(structure.getText()).isEqualTo("Text is set!");
+
+ // Bundle
+ final Bundle bundle = structure.getExtras();
+ assertThat(bundle.size()).isEqualTo(1);
+ assertThat(bundle.getString("Marlon")).isEqualTo("Bundle");
+ }
+
+ /**
+ * Creates a {@link ViewStructureImpl} with "complex" text properties (such as selection); it
+ * can be asserted through {@link #assertNodeWithComplexText(ViewNode)}.
+ */
+ private ViewStructureImpl newStructureWithComplexText() {
+ View view = new View(mContext);
+ ViewStructureImpl structure = new ViewStructureImpl(view);
+ structure.setText("IGNORE ME!");
+ structure.setText("Now we're talking!", 4, 8);
+ structure.setHint("Soylent Green is SPOILER ALERT");
+ structure.setTextStyle(15.0f, 16, 23, 42);
+ structure.setTextLines(new int[] {4, 8, 15} , new int[] {16, 23, 42});
+ return structure;
+ }
+
+ /**
+ * Asserts the properties of a {@link ViewNode} that was created by
+ * {@link #newStructureWithComplexText()}.
+ */
+ private void assertNodeWithComplexText(ViewNode node) {
+ assertThat(node.getText()).isEqualTo("Now we're talking!");
+ assertThat(node.getTextSelectionStart()).isEqualTo(4);
+ assertThat(node.getTextSelectionEnd()).isEqualTo(8);
+ assertThat(node.getHint()).isEqualTo("Soylent Green is SPOILER ALERT");
+ assertThat(node.getTextSize()).isWithin(1.0e-10f).of(15.0f);
+ assertThat(node.getTextColor()).isEqualTo(16);
+ assertThat(node.getTextBackgroundColor()).isEqualTo(23);
+ assertThat(node.getTextStyle()).isEqualTo(42);
+ assertThat(node.getTextLineCharOffsets()).asList().containsExactly(4, 8, 15).inOrder();
+ assertThat(node.getTextLineBaselines()).asList().containsExactly(16, 23, 42).inOrder();
+ }
+
+ /**
+ * Asserts the properties of a {@link ViewStructureImpl} that was created by
+ * {@link #newStructureWithComplexText()}.
+ */
+ private void assertStructureWithComplexText(ViewStructureImpl structure) {
+ assertThat(structure.getText()).isEqualTo("Now we're talking!");
+ assertThat(structure.getTextSelectionStart()).isEqualTo(4);
+ assertThat(structure.getTextSelectionEnd()).isEqualTo(8);
+ assertThat(structure.getHint()).isEqualTo("Soylent Green is SPOILER ALERT");
+ }
+
+ private ViewNode cloneThroughParcel(ViewNode node) {
+ Parcel parcel = Parcel.obtain();
+
+ try {
+ // Write to parcel
+ parcel.setDataPosition(0); // Sanity / paranoid check
+ ViewNode.writeToParcel(parcel, node, 0);
+
+ // Read from parcel
+ parcel.setDataPosition(0);
+ ViewNode clone = ViewNode.readFromParcel(parcel);
+ assertThat(clone).isNotNull();
+ return clone;
+ } finally {
+ parcel.recycle();
+ }
+ }
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk
index f2bd353..2dc30ea 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk
@@ -18,7 +18,7 @@
## The tests with only one dex
include $(CLEAR_VARS)
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex-instrumentation android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex-instrumentation androidx.test.rules
LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -41,7 +41,7 @@
## The tests with a minimal main dex
include $(CLEAR_VARS)
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex-instrumentation android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex-instrumentation androidx.test.rules
LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/InstrumentationTest.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/InstrumentationTest.java
index 4e6ec14..7812c58 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/InstrumentationTest.java
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/InstrumentationTest.java
@@ -15,10 +15,13 @@
*/
package com.android.multidexlegacytestapp.test2;
-import android.support.test.runner.AndroidJUnit4;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.multidexlegacytestapp.manymethods.Big001;
import com.android.multidexlegacytestapp.manymethods.Big079;
+
import junit.framework.Assert;
+
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/MultiDexAndroidJUnitRunner.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/MultiDexAndroidJUnitRunner.java
index 9e41a92..b3044dc 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/MultiDexAndroidJUnitRunner.java
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/MultiDexAndroidJUnitRunner.java
@@ -1,8 +1,9 @@
package com.android.multidexlegacytestapp.test2;
import android.os.Bundle;
+
import androidx.multidex.MultiDex;
-import android.support.test.runner.AndroidJUnitRunner;
+import androidx.test.runner.AndroidJUnitRunner;
public class MultiDexAndroidJUnitRunner extends AndroidJUnitRunner {
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 9b86b77..25f6775 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1075,6 +1075,11 @@
continue; // If alias and named family are conflict, use named family.
}
final Typeface base = systemFontMap.get(alias.getToName());
+ if (base == null) {
+ // The missing target is a valid thing, some configuration don't have font files,
+ // e.g. wear devices. Just skip this alias.
+ continue;
+ }
final int weight = alias.getWeight();
final Typeface newFace = weight == 400 ? base :
new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index b9860ad..635d0ec 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -34,7 +34,7 @@
namespace android::uirenderer {
static std::mutex sLock{};
-static ThreadBase* sUploadThread = nullptr;
+static sp<ThreadBase> sUploadThread = nullptr;
static renderthread::EglManager sEglManager;
static int sPendingUploads = 0;
static nsecs_t sLastUpload = 0;
@@ -257,4 +257,15 @@
Bitmap::computePalette(bitmap));
}
+void HardwareBitmapUploader::terminate() {
+ std::lock_guard _lock{sLock};
+ LOG_ALWAYS_FATAL_IF(sPendingUploads, "terminate called while uploads in progress");
+ if (sUploadThread) {
+ sUploadThread->requestExit();
+ sUploadThread->join();
+ sUploadThread = nullptr;
+ }
+ sEglManager.destroy();
+}
+
} // namespace android::uirenderer
diff --git a/libs/hwui/HardwareBitmapUploader.h b/libs/hwui/HardwareBitmapUploader.h
index 6298013..40f2b0c 100644
--- a/libs/hwui/HardwareBitmapUploader.h
+++ b/libs/hwui/HardwareBitmapUploader.h
@@ -23,6 +23,7 @@
class HardwareBitmapUploader {
public:
static sk_sp<Bitmap> allocateHardwareBitmap(const SkBitmap& sourceBitmap);
+ static void terminate();
};
} // namespace android::uirenderer
diff --git a/libs/hwui/private/hwui/DrawVkInfo.h b/libs/hwui/private/hwui/DrawVkInfo.h
index 019950f..b2351fc 100644
--- a/libs/hwui/private/hwui/DrawVkInfo.h
+++ b/libs/hwui/private/hwui/DrawVkInfo.h
@@ -53,16 +53,16 @@
VkFormat format;
// Input: Color space transfer params
- float G;
- float A;
- float B;
- float C;
- float D;
- float E;
- float F;
+ float g;
+ float a;
+ float b;
+ float c;
+ float d;
+ float e;
+ float f;
// Input: Color space transformation from linear RGB to D50-adapted XYZ
- float matrix[9];
+ float colorSpaceTransform[9];
// Input: current clip rect
int clipLeft;
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 56eedff..8cd97ed 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -93,7 +93,9 @@
, mHasWideColorGamutSupport(false) {}
EglManager::~EglManager() {
- destroy();
+ if (hasEglContext()) {
+ ALOGW("~EglManager() leaked an EGL context");
+ }
}
void EglManager::initialize() {
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index 524dfb0..174a140 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -19,6 +19,8 @@
#include "Properties.h"
#include "hwui/Typeface.h"
+#include "HardwareBitmapUploader.h"
+#include "renderthread/RenderProxy.h"
#include <benchmark/benchmark.h>
#include <getopt.h>
@@ -353,6 +355,9 @@
gBenchmarkReporter->Finalize();
}
+ renderthread::RenderProxy::trimMemory(100);
+ HardwareBitmapUploader::terminate();
+
LeakChecker::checkForLeaks();
return 0;
}
diff --git a/libs/hwui/thread/ThreadBase.h b/libs/hwui/thread/ThreadBase.h
index f9de8a5..8cdcc46 100644
--- a/libs/hwui/thread/ThreadBase.h
+++ b/libs/hwui/thread/ThreadBase.h
@@ -27,7 +27,7 @@
namespace android::uirenderer {
-class ThreadBase : protected Thread {
+class ThreadBase : public Thread {
PREVENT_COPY_AND_ASSIGN(ThreadBase);
public:
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index bdc84da..e795b81 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -89,7 +89,6 @@
List<String> getAllProviders();
List<String> getProviders(in Criteria criteria, boolean enabledOnly);
String getBestProvider(in Criteria criteria, boolean enabledOnly);
- boolean providerMeetsCriteria(String provider, in Criteria criteria);
ProviderProperties getProviderProperties(String provider);
String getNetworkProviderPackage();
void setLocationControllerExtraPackage(String packageName);
@@ -98,9 +97,7 @@
boolean isLocationControllerExtraPackageEnabled();
boolean isProviderEnabledForUser(String provider, int userId);
- boolean setProviderEnabledForUser(String provider, boolean enabled, int userId);
boolean isLocationEnabledForUser(int userId);
- void setLocationEnabledForUser(boolean enabled, int userId);
void addTestProvider(String name, in ProviderProperties properties, String opPackageName);
void removeTestProvider(String provider, String opPackageName);
void setTestProviderLocation(String provider, in Location loc, String opPackageName);
@@ -114,10 +111,6 @@
// --- internal ---
- // --- deprecated ---
- void reportLocation(in Location location, boolean passive);
- void reportLocationBatch(in List<Location> locations);
-
// for reporting callback completion
void locationCallbackFinished(ILocationListener listener);
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index d96597b..59c6a0a 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -43,6 +43,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Log;
import com.android.internal.location.ProviderProperties;
@@ -1282,11 +1283,13 @@
@SystemApi
@RequiresPermission(WRITE_SECURE_SETTINGS)
public void setLocationEnabledForUser(boolean enabled, UserHandle userHandle) {
- try {
- mService.setLocationEnabledForUser(enabled, userHandle.getIdentifier());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ Settings.Secure.putIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCATION_MODE,
+ enabled
+ ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY
+ : Settings.Secure.LOCATION_MODE_OFF,
+ userHandle.getIdentifier());
}
/**
@@ -1372,20 +1375,22 @@
* @return true if the value was set, false on database errors
*
* @throws IllegalArgumentException if provider is null
+ * @deprecated Do not manipulate providers individually, use
+ * {@link #setLocationEnabledForUser(boolean, UserHandle)} instead.
* @hide
*/
+ @Deprecated
@SystemApi
@RequiresPermission(WRITE_SECURE_SETTINGS)
public boolean setProviderEnabledForUser(
String provider, boolean enabled, UserHandle userHandle) {
checkProvider(provider);
- try {
- return mService.setProviderEnabledForUser(
- provider, enabled, userHandle.getIdentifier());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return Settings.Secure.setLocationProviderEnabledForUser(
+ mContext.getContentResolver(),
+ provider,
+ enabled,
+ userHandle.getIdentifier());
}
/**
@@ -1578,7 +1583,7 @@
*/
@Deprecated
public void clearTestProviderEnabled(String provider) {
- setTestProviderEnabled(provider, true);
+ setTestProviderEnabled(provider, false);
}
/**
diff --git a/location/lib/Android.bp b/location/lib/Android.bp
index b09335c..35f2877 100644
--- a/location/lib/Android.bp
+++ b/location/lib/Android.bp
@@ -18,5 +18,7 @@
name: "com.android.location.provider",
srcs: ["java/**/*.java"],
api_packages: ["com.android.location.provider"],
- metalava_enabled: false,
+ srcs_lib: "framework",
+ srcs_lib_whitelist_dirs: ["location/java"],
+ srcs_lib_whitelist_pkgs: ["com.android.internal.location"],
}
diff --git a/media/java/android/media/Session2Command.java b/media/java/android/media/Session2Command.java
index d2a5aae..8b285f2 100644
--- a/media/java/android/media/Session2Command.java
+++ b/media/java/android/media/Session2Command.java
@@ -36,8 +36,6 @@
* Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
* <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
* for consistent behavior across all devices.
- * </p>
- * @hide
*/
public final class Session2Command implements Parcelable {
/**
@@ -151,14 +149,17 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ if (dest == null) {
+ throw new IllegalArgumentException("parcel shouldn't be null");
+ }
dest.writeInt(mCommandCode);
dest.writeString(mCustomCommand);
dest.writeBundle(mExtras);
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (!(obj instanceof Session2Command)) {
return false;
}
@@ -185,7 +186,7 @@
* @param resultCode result code
* @param resultData result data
*/
- public Result(int resultCode, Bundle resultData) {
+ public Result(int resultCode, @Nullable Bundle resultData) {
mResultCode = resultCode;
mResultData = resultData;
}
@@ -200,6 +201,7 @@
/**
* Returns the result data.
*/
+ @Nullable
public Bundle getResultData() {
return mResultData;
}
diff --git a/media/java/android/media/Session2CommandGroup.java b/media/java/android/media/Session2CommandGroup.java
index 122dfb1..4668ec4 100644
--- a/media/java/android/media/Session2CommandGroup.java
+++ b/media/java/android/media/Session2CommandGroup.java
@@ -130,7 +130,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeParcelableArray((Session2Command[]) mCommands.toArray(), 0);
+ dest.writeParcelableArray(mCommands.toArray(new Session2Command[0]), 0);
}
/**
diff --git a/media/java/android/media/Session2Token.java b/media/java/android/media/Session2Token.java
index 4634c69..e1fff38 100644
--- a/media/java/android/media/Session2Token.java
+++ b/media/java/android/media/Session2Token.java
@@ -171,7 +171,7 @@
dest.writeString(mPackageName);
dest.writeString(mServiceName);
// TODO: Uncomment below
- //dest.writeStrongBinder(mSessionLink.asBinder());
+ //dest.writeStrongBinder(mSessionLink.getBinder());
dest.writeString(mComponentName == null ? "" : mComponentName.flattenToString());
}
diff --git a/media/java/android/media/session/ControllerCallbackLink.aidl b/media/java/android/media/session/ControllerCallbackLink.aidl
new file mode 100644
index 0000000..8ee8c7d
--- /dev/null
+++ b/media/java/android/media/session/ControllerCallbackLink.aidl
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.session;
+
+parcelable ControllerCallbackLink;
diff --git a/media/java/android/media/session/ControllerCallbackLink.java b/media/java/android/media/session/ControllerCallbackLink.java
new file mode 100644
index 0000000..a143c9b
--- /dev/null
+++ b/media/java/android/media/session/ControllerCallbackLink.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.session;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.media.MediaMetadata;
+import android.media.session.MediaController.PlaybackInfo;
+import android.media.session.MediaSession.QueueItem;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import java.util.List;
+
+/**
+ * Handles incoming commands to {@link MediaController.Callback}.
+ * <p>
+ * This API is not generally intended for third party application developers.
+ */
+public final class ControllerCallbackLink implements Parcelable {
+ final CallbackStub mCallbackStub;
+ final ISessionControllerCallback mIControllerCallback;
+
+ /**
+ * Constructor for stub (Callee)
+ * @hide
+ */
+ @SystemApi
+ public ControllerCallbackLink(@NonNull CallbackStub callbackStub) {
+ mCallbackStub = callbackStub;
+ mIControllerCallback = new CallbackStubProxy();
+ }
+
+ /**
+ * Constructor for interface (Caller)
+ */
+ ControllerCallbackLink(Parcel in) {
+ mCallbackStub = null;
+ mIControllerCallback = ISessionControllerCallback.Stub.asInterface(in.readStrongBinder());
+ }
+
+ /**
+ * Notify controller that the connected session is destroyed.
+ */
+ public void notifySessionDestroyed() {
+ try {
+ mIControllerCallback.notifySessionDestroyed();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify controller that the connected session sends an event.
+ *
+ * @param event the name of the event
+ * @param extras the extras included with the event
+ */
+ public void notifyEvent(@NonNull String event, @Nullable Bundle extras) {
+ try {
+ mIControllerCallback.notifyEvent(event, extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify controller that the current playback state is changed.
+ *
+ * @param state the new playback state
+ */
+ public void notifyPlaybackStateChanged(@Nullable PlaybackState state) {
+ try {
+ mIControllerCallback.notifyPlaybackStateChanged(state);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify controller that the current metadata is changed.
+ *
+ * @param metadata the new metadata
+ */
+ public void notifyMetadataChanged(@Nullable MediaMetadata metadata) {
+ try {
+ mIControllerCallback.notifyMetadataChanged(metadata);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify controller that the current queue is changed.
+ *
+ * @param queue the new queue
+ */
+ public void notifyQueueChanged(@Nullable List<QueueItem> queue) {
+ try {
+ mIControllerCallback.notifyQueueChanged(queue);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify controller that the current queue title is changed.
+ *
+ * @param title the new queue title
+ */
+ public void notifyQueueTitleChanged(@Nullable CharSequence title) {
+ try {
+ mIControllerCallback.notifyQueueTitleChanged(title);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify controller that the extras are changed.
+ *
+ * @param extras the new extras
+ */
+ public void notifyExtrasChanged(@Nullable Bundle extras) {
+ try {
+ mIControllerCallback.notifyExtrasChanged(extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify controller that the playback info is changed.
+ *
+ * @param info the new playback info
+ */
+ public void notifyVolumeInfoChanged(@NonNull PlaybackInfo info) {
+ try {
+ mIControllerCallback.notifyVolumeInfoChanged(info);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** Gets the binder */
+ @NonNull
+ public IBinder getBinder() {
+ return mIControllerCallback.asBinder();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mIControllerCallback.asBinder());
+ }
+
+ public static final Parcelable.Creator<ControllerCallbackLink> CREATOR =
+ new Parcelable.Creator<ControllerCallbackLink>() {
+ @Override
+ public ControllerCallbackLink createFromParcel(Parcel in) {
+ return new ControllerCallbackLink(in);
+ }
+
+ @Override
+ public ControllerCallbackLink[] newArray(int size) {
+ return new ControllerCallbackLink[size];
+ }
+ };
+
+ /**
+ * Class for Stub implementation
+ * @hide
+ */
+ @SystemApi
+ public abstract static class CallbackStub {
+ /** Stub method for ISessionControllerCallback.notifySessionDestroyed */
+ public void onSessionDestroyed() {
+ }
+
+ /** Stub method for ISessionControllerCallback.notifyEvent */
+ public void onEvent(@NonNull String event, @Nullable Bundle extras) {
+ }
+
+ /** Stub method for ISessionControllerCallback.notifyPlaybackStateChanged */
+ public void onPlaybackStateChanged(@Nullable PlaybackState state) {
+ }
+
+ /** Stub method for ISessionControllerCallback.notifyMetadataChanged */
+ public void onMetadataChanged(@Nullable MediaMetadata metadata) {
+ }
+
+ /** Stub method for ISessionControllerCallback.notifyQueueChanged */
+ public void onQueueChanged(@Nullable List<QueueItem> queue) {
+ }
+
+ /** Stub method for ISessionControllerCallback.notifyQueueTitleChanged */
+ public void onQueueTitleChanged(@Nullable CharSequence title) {
+ }
+
+ /** Stub method for ISessionControllerCallback.notifyExtrasChanged */
+ public void onExtrasChanged(@Nullable Bundle extras) {
+ }
+
+ /** Stub method for ISessionControllerCallback.notifyVolumeInfoChanged */
+ public void onVolumeInfoChanged(@NonNull PlaybackInfo info) {
+ }
+ }
+
+ private class CallbackStubProxy extends ISessionControllerCallback.Stub {
+ @Override
+ public void notifyEvent(String event, Bundle extras) {
+ mCallbackStub.onEvent(event, extras);
+ }
+
+ @Override
+ public void notifySessionDestroyed() {
+ mCallbackStub.onSessionDestroyed();
+ }
+
+ @Override
+ public void notifyPlaybackStateChanged(PlaybackState state) {
+ mCallbackStub.onPlaybackStateChanged(state);
+ }
+
+ @Override
+ public void notifyMetadataChanged(MediaMetadata metadata) {
+ mCallbackStub.onMetadataChanged(metadata);
+ }
+
+ @Override
+ public void notifyQueueChanged(List<QueueItem> queue) {
+ mCallbackStub.onQueueChanged(queue);
+ }
+
+ @Override
+ public void notifyQueueTitleChanged(CharSequence title) {
+ mCallbackStub.onQueueTitleChanged(title);
+ }
+
+ @Override
+ public void notifyExtrasChanged(Bundle extras) {
+ mCallbackStub.onExtrasChanged(extras);
+ }
+
+ @Override
+ public void notifyVolumeInfoChanged(PlaybackInfo info) {
+ mCallbackStub.onVolumeInfoChanged(info);
+ }
+ }
+}
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index bfc05fa..1524ad9 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -16,7 +16,6 @@
package android.media.session;
import android.app.PendingIntent;
-import android.content.pm.ParceledListSlice;
import android.media.AudioAttributes;
import android.media.MediaMetadata;
import android.media.session.ISessionController;
@@ -41,7 +40,8 @@
// These commands are for the TransportPerformer
void setMetadata(in MediaMetadata metadata, long duration, String metadataDescription);
void setPlaybackState(in PlaybackState state);
- void setQueue(in ParceledListSlice queue);
+ // TODO(b/122432476): Replace List with MediaParceledListSlice
+ void setQueue(in List<MediaSession.QueueItem> queue);
void setQueueTitle(CharSequence title);
void setExtras(in Bundle extras);
void setRatingType(int type);
diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl
index 626338d..9b86bfc 100644
--- a/media/java/android/media/session/ISessionCallback.aidl
+++ b/media/java/android/media/session/ISessionCallback.aidl
@@ -17,7 +17,7 @@
import android.content.Intent;
import android.media.Rating;
-import android.media.session.ISessionControllerCallback;
+import android.media.session.ControllerCallbackLink;
import android.net.Uri;
import android.os.Bundle;
import android.os.ResultReceiver;
@@ -26,46 +26,46 @@
* @hide
*/
oneway interface ISessionCallback {
- void onCommand(String packageName, int pid, int uid, ISessionControllerCallback caller,
+ void notifyCommand(String packageName, int pid, int uid, in ControllerCallbackLink caller,
String command, in Bundle args, in ResultReceiver cb);
- void onMediaButton(String packageName, int pid, int uid, in Intent mediaButtonIntent,
+ void notifyMediaButton(String packageName, int pid, int uid, in Intent mediaButtonIntent,
int sequenceNumber, in ResultReceiver cb);
- void onMediaButtonFromController(String packageName, int pid, int uid,
- ISessionControllerCallback caller, in Intent mediaButtonIntent);
+ void notifyMediaButtonFromController(String packageName, int pid, int uid,
+ in ControllerCallbackLink caller, in Intent mediaButtonIntent);
// These callbacks are for the TransportPerformer
- void onPrepare(String packageName, int pid, int uid, ISessionControllerCallback caller);
- void onPrepareFromMediaId(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String mediaId, in Bundle extras);
- void onPrepareFromSearch(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String query, in Bundle extras);
- void onPrepareFromUri(String packageName, int pid, int uid, ISessionControllerCallback caller,
+ void notifyPrepare(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+ void notifyPrepareFromMediaId(String packageName, int pid, int uid,
+ in ControllerCallbackLink caller, String mediaId, in Bundle extras);
+ void notifyPrepareFromSearch(String packageName, int pid, int uid,
+ in ControllerCallbackLink caller, String query, in Bundle extras);
+ void notifyPrepareFromUri(String packageName, int pid, int uid,
+ in ControllerCallbackLink caller, in Uri uri, in Bundle extras);
+ void notifyPlay(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+ void notifyPlayFromMediaId(String packageName, int pid, int uid,
+ in ControllerCallbackLink caller, String mediaId, in Bundle extras);
+ void notifyPlayFromSearch(String packageName, int pid, int uid,
+ in ControllerCallbackLink caller, String query, in Bundle extras);
+ void notifyPlayFromUri(String packageName, int pid, int uid, in ControllerCallbackLink caller,
in Uri uri, in Bundle extras);
- void onPlay(String packageName, int pid, int uid, ISessionControllerCallback caller);
- void onPlayFromMediaId(String packageName, int pid, int uid, ISessionControllerCallback caller,
- String mediaId, in Bundle extras);
- void onPlayFromSearch(String packageName, int pid, int uid, ISessionControllerCallback caller,
- String query, in Bundle extras);
- void onPlayFromUri(String packageName, int pid, int uid, ISessionControllerCallback caller,
- in Uri uri, in Bundle extras);
- void onSkipToTrack(String packageName, int pid, int uid, ISessionControllerCallback caller,
+ void notifySkipToTrack(String packageName, int pid, int uid, in ControllerCallbackLink caller,
long id);
- void onPause(String packageName, int pid, int uid, ISessionControllerCallback caller);
- void onStop(String packageName, int pid, int uid, ISessionControllerCallback caller);
- void onNext(String packageName, int pid, int uid, ISessionControllerCallback caller);
- void onPrevious(String packageName, int pid, int uid, ISessionControllerCallback caller);
- void onFastForward(String packageName, int pid, int uid, ISessionControllerCallback caller);
- void onRewind(String packageName, int pid, int uid, ISessionControllerCallback caller);
- void onSeekTo(String packageName, int pid, int uid, ISessionControllerCallback caller,
+ void notifyPause(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+ void notifyStop(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+ void notifyNext(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+ void notifyPrevious(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+ void notifyFastForward(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+ void notifyRewind(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+ void notifySeekTo(String packageName, int pid, int uid, in ControllerCallbackLink caller,
long pos);
- void onRate(String packageName, int pid, int uid, ISessionControllerCallback caller,
+ void notifyRate(String packageName, int pid, int uid, in ControllerCallbackLink caller,
in Rating rating);
- void onCustomAction(String packageName, int pid, int uid, ISessionControllerCallback caller,
+ void notifyCustomAction(String packageName, int pid, int uid, in ControllerCallbackLink caller,
String action, in Bundle args);
// These callbacks are for volume handling
- void onAdjustVolume(String packageName, int pid, int uid, ISessionControllerCallback caller,
+ void notifyAdjustVolume(String packageName, int pid, int uid, in ControllerCallbackLink caller,
int direction);
- void onSetVolumeTo(String packageName, int pid, int uid,
- ISessionControllerCallback caller, int value);
+ void notifySetVolumeTo(String packageName, int pid, int uid,
+ in ControllerCallbackLink caller, int value);
}
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl
index a843881..2ba09fd 100644
--- a/media/java/android/media/session/ISessionController.aidl
+++ b/media/java/android/media/session/ISessionController.aidl
@@ -17,10 +17,9 @@
import android.app.PendingIntent;
import android.content.Intent;
-import android.content.pm.ParceledListSlice;
import android.media.MediaMetadata;
import android.media.Rating;
-import android.media.session.ISessionControllerCallback;
+import android.media.session.ControllerCallbackLink;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
@@ -36,12 +35,12 @@
* @hide
*/
interface ISessionController {
- void sendCommand(String packageName, ISessionControllerCallback caller,
+ void sendCommand(String packageName, in ControllerCallbackLink caller,
String command, in Bundle args, in ResultReceiver cb);
- boolean sendMediaButton(String packageName, ISessionControllerCallback caller,
+ boolean sendMediaButton(String packageName, in ControllerCallbackLink caller,
boolean asSystemService, in KeyEvent mediaButton);
- void registerCallbackListener(String packageName, ISessionControllerCallback cb);
- void unregisterCallbackListener(ISessionControllerCallback cb);
+ void registerCallbackListener(String packageName, in ControllerCallbackLink cb);
+ void unregisterCallbackListener(in ControllerCallbackLink cb);
boolean isTransportControlEnabled();
String getPackageName();
String getTag();
@@ -49,40 +48,40 @@
long getFlags();
MediaController.PlaybackInfo getVolumeAttributes();
void adjustVolume(String packageName, String opPackageName,
- in ISessionControllerCallback caller, boolean asSystemService, int direction,
+ in ControllerCallbackLink caller, boolean asSystemService, int direction,
int flags);
- void setVolumeTo(String packageName, String opPackageName, in ISessionControllerCallback caller,
+ void setVolumeTo(String packageName, String opPackageName, in ControllerCallbackLink caller,
int value, int flags);
// These commands are for the TransportControls
- void prepare(String packageName, ISessionControllerCallback caller);
- void prepareFromMediaId(String packageName, ISessionControllerCallback caller,
+ void prepare(String packageName, in ControllerCallbackLink caller);
+ void prepareFromMediaId(String packageName, in ControllerCallbackLink caller,
String mediaId, in Bundle extras);
- void prepareFromSearch(String packageName, ISessionControllerCallback caller,
+ void prepareFromSearch(String packageName, in ControllerCallbackLink caller,
String string, in Bundle extras);
- void prepareFromUri(String packageName, ISessionControllerCallback caller,
+ void prepareFromUri(String packageName, in ControllerCallbackLink caller,
in Uri uri, in Bundle extras);
- void play(String packageName, ISessionControllerCallback caller);
- void playFromMediaId(String packageName, ISessionControllerCallback caller,
+ void play(String packageName, in ControllerCallbackLink caller);
+ void playFromMediaId(String packageName, in ControllerCallbackLink caller,
String mediaId, in Bundle extras);
- void playFromSearch(String packageName, ISessionControllerCallback caller,
+ void playFromSearch(String packageName, in ControllerCallbackLink caller,
String string, in Bundle extras);
- void playFromUri(String packageName, ISessionControllerCallback caller,
+ void playFromUri(String packageName, in ControllerCallbackLink caller,
in Uri uri, in Bundle extras);
- void skipToQueueItem(String packageName, ISessionControllerCallback caller, long id);
- void pause(String packageName, ISessionControllerCallback caller);
- void stop(String packageName, ISessionControllerCallback caller);
- void next(String packageName, ISessionControllerCallback caller);
- void previous(String packageName, ISessionControllerCallback caller);
- void fastForward(String packageName, ISessionControllerCallback caller);
- void rewind(String packageName, ISessionControllerCallback caller);
- void seekTo(String packageName, ISessionControllerCallback caller, long pos);
- void rate(String packageName, ISessionControllerCallback caller, in Rating rating);
- void sendCustomAction(String packageName, ISessionControllerCallback caller,
+ void skipToQueueItem(String packageName, in ControllerCallbackLink caller, long id);
+ void pause(String packageName, in ControllerCallbackLink caller);
+ void stop(String packageName, in ControllerCallbackLink caller);
+ void next(String packageName, in ControllerCallbackLink caller);
+ void previous(String packageName, in ControllerCallbackLink caller);
+ void fastForward(String packageName, in ControllerCallbackLink caller);
+ void rewind(String packageName, in ControllerCallbackLink caller);
+ void seekTo(String packageName, in ControllerCallbackLink caller, long pos);
+ void rate(String packageName, in ControllerCallbackLink caller, in Rating rating);
+ void sendCustomAction(String packageName, in ControllerCallbackLink caller,
String action, in Bundle args);
MediaMetadata getMetadata();
PlaybackState getPlaybackState();
- ParceledListSlice getQueue();
+ List<MediaSession.QueueItem> getQueue();
CharSequence getQueueTitle();
Bundle getExtras();
int getRatingType();
diff --git a/media/java/android/media/session/ISessionControllerCallback.aidl b/media/java/android/media/session/ISessionControllerCallback.aidl
index fac8897..5c02e7c 100644
--- a/media/java/android/media/session/ISessionControllerCallback.aidl
+++ b/media/java/android/media/session/ISessionControllerCallback.aidl
@@ -15,25 +15,24 @@
package android.media.session;
-import android.content.pm.ParceledListSlice;
import android.media.MediaMetadata;
-import android.media.session.PlaybackState;
import android.media.session.MediaController;
import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
import android.os.Bundle;
/**
* @hide
*/
oneway interface ISessionControllerCallback {
- void onEvent(String event, in Bundle extras);
- void onSessionDestroyed();
+ void notifyEvent(String event, in Bundle extras);
+ void notifySessionDestroyed();
// These callbacks are for the TransportController
- void onPlaybackStateChanged(in PlaybackState state);
- void onMetadataChanged(in MediaMetadata metadata);
- void onQueueChanged(in ParceledListSlice queue);
- void onQueueTitleChanged(CharSequence title);
- void onExtrasChanged(in Bundle extras);
- void onVolumeInfoChanged(in MediaController.PlaybackInfo info);
+ void notifyPlaybackStateChanged(in PlaybackState state);
+ void notifyMetadataChanged(in MediaMetadata metadata);
+ void notifyQueueChanged(in List<MediaSession.QueueItem> queue);
+ void notifyQueueTitleChanged(CharSequence title);
+ void notifyExtrasChanged(in Bundle extras);
+ void notifyVolumeInfoChanged(in MediaController.PlaybackInfo info);
}
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 51148e2..7ac3ef2 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -23,7 +23,7 @@
import android.media.session.IOnMediaKeyListener;
import android.media.session.IOnVolumeKeyLongPressListener;
import android.media.session.ISession;
-import android.media.session.ISessionCallback;
+import android.media.session.SessionCallbackLink;
import android.os.Bundle;
import android.view.KeyEvent;
@@ -32,9 +32,10 @@
* @hide
*/
interface ISessionManager {
- ISession createSession(String packageName, in ISessionCallback cb, String tag, int userId);
+ ISession createSession(String packageName, in SessionCallbackLink cb, String tag, int userId);
void notifySession2Created(in Session2Token sessionToken);
List<IBinder> getSessions(in ComponentName compName, int userId);
+ List<Session2Token> getSession2Tokens(int userId);
void dispatchMediaKeyEvent(String packageName, boolean asSystemService, in KeyEvent keyEvent,
boolean needWakeLock);
void dispatchVolumeKeyEvent(String packageName, String opPackageName, boolean asSystemService,
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index ef2df15..a1b8170 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -21,12 +21,12 @@
import android.annotation.UnsupportedAppUsage;
import android.app.PendingIntent;
import android.content.Context;
-import android.content.pm.ParceledListSlice;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.MediaMetadata;
import android.media.Rating;
import android.media.VolumeProvider;
+import android.media.session.MediaSession.QueueItem;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -72,7 +72,8 @@
private final MediaSession.Token mToken;
private final Context mContext;
- private final CallbackStub mCbStub = new CallbackStub(this);
+ private final ControllerCallbackLink mCbStub =
+ new ControllerCallbackLink(new CallbackStub(this));
private final ArrayList<MessageHandler> mCallbacks = new ArrayList<MessageHandler>();
private final Object mLock = new Object();
@@ -251,10 +252,7 @@
*/
public @Nullable List<MediaSession.QueueItem> getQueue() {
try {
- ParceledListSlice queue = mSessionBinder.getQueue();
- if (queue != null) {
- return queue.getList();
- }
+ return mSessionBinder.getQueue();
} catch (RemoteException e) {
Log.wtf(TAG, "Error calling getQueue.", e);
}
@@ -1113,10 +1111,10 @@
};
}
- private final static class CallbackStub extends ISessionControllerCallback.Stub {
+ private static final class CallbackStub extends ControllerCallbackLink.CallbackStub {
private final WeakReference<MediaController> mController;
- public CallbackStub(MediaController controller) {
+ CallbackStub(MediaController controller) {
mController = new WeakReference<MediaController>(controller);
}
@@ -1153,9 +1151,7 @@
}
@Override
- public void onQueueChanged(ParceledListSlice parceledQueue) {
- List<MediaSession.QueueItem> queue = parceledQueue == null ? null : parceledQueue
- .getList();
+ public void onQueueChanged(List<QueueItem> queue) {
MediaController controller = mController.get();
if (controller != null) {
controller.postMessage(MSG_UPDATE_QUEUE, queue, null);
@@ -1185,7 +1181,6 @@
controller.postMessage(MSG_UPDATE_VOLUME, info, null);
}
}
-
}
private final static class MessageHandler extends Handler {
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index df8cc35..e07cf15 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -24,7 +24,6 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ParceledListSlice;
import android.media.AudioAttributes;
import android.media.MediaDescription;
import android.media.MediaMetadata;
@@ -130,7 +129,7 @@
private final MediaSession.Token mSessionToken;
private final MediaController mController;
private final ISession mBinder;
- private final CallbackStub mCbStub;
+ private final SessionCallbackLink mCbStub;
// Do not change the name of mCallback. Support lib accesses this by using reflection.
@UnsupportedAppUsage
@@ -173,7 +172,7 @@
}
mMaxBitmapSize = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize);
- mCbStub = new CallbackStub(this);
+ mCbStub = new SessionCallbackLink(new CallbackStub(this));
MediaSessionManager manager = (MediaSessionManager) context
.getSystemService(Context.MEDIA_SESSION_SERVICE);
try {
@@ -467,7 +466,7 @@
*/
public void setQueue(@Nullable List<QueueItem> queue) {
try {
- mBinder.setQueue(queue == null ? null : new ParceledListSlice<QueueItem>(queue));
+ mBinder.setQueue(queue);
} catch (RemoteException e) {
Log.wtf("Dead object in setQueue.", e);
}
@@ -1063,7 +1062,7 @@
/**
* @hide
*/
- public static class CallbackStub extends ISessionCallback.Stub {
+ public static final class CallbackStub extends SessionCallbackLink.CallbackStub {
private WeakReference<MediaSession> mMediaSession;
public CallbackStub(MediaSession session) {
@@ -1071,14 +1070,14 @@
}
private static RemoteUserInfo createRemoteUserInfo(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
return new RemoteUserInfo(packageName, pid, uid,
- caller != null ? caller.asBinder() : null);
+ caller != null ? caller.getBinder() : null);
}
@Override
public void onCommand(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String command, Bundle args, ResultReceiver cb) {
+ ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchCommand(createRemoteUserInfo(packageName, pid, uid, caller),
@@ -1104,7 +1103,7 @@
@Override
public void onMediaButtonFromController(String packageName, int pid, int uid,
- ISessionControllerCallback caller, Intent mediaButtonIntent) {
+ ControllerCallbackLink caller, Intent mediaButtonIntent) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid, caller),
@@ -1114,7 +1113,7 @@
@Override
public void onPrepare(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchPrepare(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1123,7 +1122,7 @@
@Override
public void onPrepareFromMediaId(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String mediaId,
+ ControllerCallbackLink caller, String mediaId,
Bundle extras) {
MediaSession session = mMediaSession.get();
if (session != null) {
@@ -1134,7 +1133,7 @@
@Override
public void onPrepareFromSearch(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String query,
+ ControllerCallbackLink caller, String query,
Bundle extras) {
MediaSession session = mMediaSession.get();
if (session != null) {
@@ -1145,7 +1144,7 @@
@Override
public void onPrepareFromUri(String packageName, int pid, int uid,
- ISessionControllerCallback caller, Uri uri, Bundle extras) {
+ ControllerCallbackLink caller, Uri uri, Bundle extras) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchPrepareFromUri(createRemoteUserInfo(packageName, pid, uid, caller),
@@ -1155,7 +1154,7 @@
@Override
public void onPlay(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchPlay(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1164,7 +1163,7 @@
@Override
public void onPlayFromMediaId(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String mediaId,
+ ControllerCallbackLink caller, String mediaId,
Bundle extras) {
MediaSession session = mMediaSession.get();
if (session != null) {
@@ -1175,7 +1174,7 @@
@Override
public void onPlayFromSearch(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String query,
+ ControllerCallbackLink caller, String query,
Bundle extras) {
MediaSession session = mMediaSession.get();
if (session != null) {
@@ -1186,7 +1185,7 @@
@Override
public void onPlayFromUri(String packageName, int pid, int uid,
- ISessionControllerCallback caller, Uri uri, Bundle extras) {
+ ControllerCallbackLink caller, Uri uri, Bundle extras) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchPlayFromUri(createRemoteUserInfo(packageName, pid, uid, caller),
@@ -1196,7 +1195,7 @@
@Override
public void onSkipToTrack(String packageName, int pid, int uid,
- ISessionControllerCallback caller, long id) {
+ ControllerCallbackLink caller, long id) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchSkipToItem(createRemoteUserInfo(packageName, pid, uid, caller), id);
@@ -1205,7 +1204,7 @@
@Override
public void onPause(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchPause(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1214,7 +1213,7 @@
@Override
public void onStop(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchStop(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1223,7 +1222,7 @@
@Override
public void onNext(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchNext(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1232,7 +1231,7 @@
@Override
public void onPrevious(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchPrevious(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1241,7 +1240,7 @@
@Override
public void onFastForward(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchFastForward(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1250,7 +1249,7 @@
@Override
public void onRewind(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchRewind(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1259,7 +1258,7 @@
@Override
public void onSeekTo(String packageName, int pid, int uid,
- ISessionControllerCallback caller, long pos) {
+ ControllerCallbackLink caller, long pos) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchSeekTo(createRemoteUserInfo(packageName, pid, uid, caller), pos);
@@ -1267,7 +1266,7 @@
}
@Override
- public void onRate(String packageName, int pid, int uid, ISessionControllerCallback caller,
+ public void onRate(String packageName, int pid, int uid, ControllerCallbackLink caller,
Rating rating) {
MediaSession session = mMediaSession.get();
if (session != null) {
@@ -1277,7 +1276,7 @@
@Override
public void onCustomAction(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String action, Bundle args) {
+ ControllerCallbackLink caller, String action, Bundle args) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchCustomAction(createRemoteUserInfo(packageName, pid, uid, caller),
@@ -1287,7 +1286,7 @@
@Override
public void onAdjustVolume(String packageName, int pid, int uid,
- ISessionControllerCallback caller, int direction) {
+ ControllerCallbackLink caller, int direction) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchAdjustVolume(createRemoteUserInfo(packageName, pid, uid, caller),
@@ -1297,7 +1296,7 @@
@Override
public void onSetVolumeTo(String packageName, int pid, int uid,
- ISessionControllerCallback caller, int value) {
+ ControllerCallbackLink caller, int value) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchSetVolumeTo(createRemoteUserInfo(packageName, pid, uid, caller),
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index ef5cf00..56ea484 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -98,7 +98,7 @@
* @return The binder object from the system
* @hide
*/
- public @NonNull ISession createSession(@NonNull MediaSession.CallbackStub cbStub,
+ public @NonNull ISession createSession(@NonNull SessionCallbackLink cbStub,
@NonNull String tag, int userId) throws RemoteException {
return mService.createSession(mContext.getPackageName(), cbStub, tag, userId);
}
@@ -179,6 +179,44 @@
}
/**
+ * Gets a list of {@link Session2Token} with type {@link Session2Token#TYPE_SESSION} for the
+ * current user.
+ * <p>
+ * Although this API can be used without any restriction, each session owners can accept or
+ * reject your uses of {@link MediaSession2}.
+ *
+ * @return A list of {@link Session2Token}.
+ * @hide
+ */
+ // TODO: unhide
+ @NonNull
+ public List<Session2Token> getSession2Tokens() {
+ return getSession2Tokens(UserHandle.myUserId());
+ }
+
+ /**
+ * Gets a list of {@link Session2Token} with type {@link Session2Token#TYPE_SESSION} for the
+ * given user.
+ * <p>
+ * If you want to get tokens for another user, you must hold the
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission.
+ *
+ * @param userId The user id to fetch sessions for.
+ * @return A list of {@link Session2Token}
+ * @hide
+ */
+ // TODO: unhide
+ @NonNull
+ public List<Session2Token> getSession2Tokens(int userId) {
+ try {
+ return mService.getSession2Tokens(userId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to get session tokens", e);
+ }
+ return new ArrayList<>();
+ }
+
+ /**
* Add a listener to be notified when the list of active sessions
* changes.This requires the
* android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
diff --git a/media/java/android/media/session/SessionCallbackLink.aidl b/media/java/android/media/session/SessionCallbackLink.aidl
new file mode 100644
index 0000000..c489e5b
--- /dev/null
+++ b/media/java/android/media/session/SessionCallbackLink.aidl
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.session;
+
+parcelable SessionCallbackLink;
diff --git a/media/java/android/media/session/SessionCallbackLink.java b/media/java/android/media/session/SessionCallbackLink.java
new file mode 100644
index 0000000..7547bff
--- /dev/null
+++ b/media/java/android/media/session/SessionCallbackLink.java
@@ -0,0 +1,776 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.session;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.media.Rating;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+
+/**
+ * Handles incoming commands to {@link MediaSession.Callback}.
+ * <p>
+ * This API is not generally intended for third party application developers.
+ */
+public final class SessionCallbackLink implements Parcelable {
+ final CallbackStub mCallbackStub;
+ final ISessionCallback mISessionCallback;
+
+ /**
+ * Constructor for stub (Callee)
+ */
+ SessionCallbackLink(@NonNull CallbackStub callbackStub) {
+ mCallbackStub = callbackStub;
+ mISessionCallback = new CallbackStubProxy();
+ }
+
+ /**
+ * Constructor for interface (Caller)
+ */
+ SessionCallbackLink(Parcel in) {
+ mCallbackStub = null;
+ mISessionCallback = ISessionCallback.Stub.asInterface(in.readStrongBinder());
+ }
+
+ /**
+ * Notify session that a controller sends a command.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param command the name of the command
+ * @param args the arguments included with the command
+ * @param cb the result receiver for getting the result of the command
+ */
+ public void notifyCommand(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull String command,
+ @Nullable Bundle args, @Nullable ResultReceiver cb) {
+ try {
+ mISessionCallback.notifyCommand(packageName, pid, uid, caller, command, args, cb);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that the android system sends a media button event.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param mediaButtonIntent the media button intent
+ * @param sequenceNumber the sequence number of this call
+ * @param cb the result receiver for getting the result of the command
+ */
+ public void notifyMediaButton(@NonNull String packageName, int pid, int uid,
+ @NonNull Intent mediaButtonIntent, int sequenceNumber,
+ @Nullable ResultReceiver cb) {
+ try {
+ mISessionCallback.notifyMediaButton(packageName, pid, uid, mediaButtonIntent,
+ sequenceNumber, cb);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller sends a media button event.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param mediaButtonIntent the media button intent
+ */
+ public void notifyMediaButtonFromController(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull Intent mediaButtonIntent) {
+ try {
+ mISessionCallback.notifyMediaButtonFromController(packageName, pid, uid, caller,
+ mediaButtonIntent);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests preparing media.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ public void notifyPrepare(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionCallback.notifyPrepare(packageName, pid, uid, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests preparing media from given media ID.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param mediaId the ID of the media
+ * @param extras the extras included with this request.
+ */
+ public void notifyPrepareFromMediaId(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull String mediaId,
+ @Nullable Bundle extras) {
+ try {
+ mISessionCallback.notifyPrepareFromMediaId(packageName, pid, uid, caller, mediaId,
+ extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests preparing media from given search query.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param query the search query
+ * @param extras the extras included with this request.
+ */
+ public void notifyPrepareFromSearch(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull String query,
+ @Nullable Bundle extras) {
+ try {
+ mISessionCallback.notifyPrepareFromSearch(packageName, pid, uid, caller, query, extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests preparing media from given uri.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param uri the uri of the media
+ * @param extras the extras included with this request.
+ */
+ public void notifyPrepareFromUri(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull Uri uri, @Nullable Bundle extras) {
+ try {
+ mISessionCallback.notifyPrepareFromUri(packageName, pid, uid, caller, uri, extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests playing media.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ public void notifyPlay(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionCallback.notifyPlay(packageName, pid, uid, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests playing media from given media ID.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param mediaId the ID of the media
+ * @param extras the extras included with this request.
+ */
+ public void notifyPlayFromMediaId(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull String mediaId,
+ @Nullable Bundle extras) {
+ try {
+ mISessionCallback.notifyPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests playing media from given search query.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param query the search query
+ * @param extras the extras included with this request.
+ */
+ public void notifyPlayFromSearch(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull String query,
+ @Nullable Bundle extras) {
+ try {
+ mISessionCallback.notifyPlayFromSearch(packageName, pid, uid, caller, query, extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests playing media from given uri.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param uri the uri of the media
+ * @param extras the extras included with this request.
+ */
+ public void notifyPlayFromUri(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull Uri uri, @Nullable Bundle extras) {
+ try {
+ mISessionCallback.notifyPlayFromUri(packageName, pid, uid, caller, uri, extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests skipping to the queue item with given ID.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param id the queue id of the item
+ */
+ public void notifySkipToTrack(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, long id) {
+ try {
+ mISessionCallback.notifySkipToTrack(packageName, pid, uid, caller, id);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests pausing media.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ public void notifyPause(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionCallback.notifyPause(packageName, pid, uid, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests stopping media.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ public void notifyStop(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionCallback.notifyStop(packageName, pid, uid, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests skipping to the next queue item.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ public void notifyNext(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionCallback.notifyNext(packageName, pid, uid, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests skipping to the previous queue item.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ public void notifyPrevious(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionCallback.notifyPrevious(packageName, pid, uid, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests fast-forwarding.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ public void notifyFastForward(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionCallback.notifyFastForward(packageName, pid, uid, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests rewinding.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ public void notifyRewind(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionCallback.notifyRewind(packageName, pid, uid, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests seeking to the specific position.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param pos the position to move to, in milliseconds
+ */
+ public void notifySeekTo(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, long pos) {
+ try {
+ mISessionCallback.notifySeekTo(packageName, pid, uid, caller, pos);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests rating of the current media.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param rating the rating of the current media
+ */
+ public void notifyRate(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull Rating rating) {
+ try {
+ mISessionCallback.notifyRate(packageName, pid, uid, caller, rating);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller sends a custom action.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param action the name of the action
+ * @param args the arguments included with this action
+ */
+ public void notifyCustomAction(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull String action, @Nullable Bundle args) {
+ try {
+ mISessionCallback.notifyCustomAction(packageName, pid, uid, caller, action, args);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests adjusting volume.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param direction the direction of the volume change.
+ */
+ public void notifyAdjustVolume(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, int direction) {
+ try {
+ mISessionCallback.notifyAdjustVolume(packageName, pid, uid, caller, direction);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests setting volume.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param value the volume value to set
+ */
+ public void notifySetVolumeTo(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, int value) {
+ try {
+ mISessionCallback.notifySetVolumeTo(packageName, pid, uid, caller, value);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** Gets the binder */
+ @NonNull
+ public IBinder getBinder() {
+ return mISessionCallback.asBinder();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mISessionCallback.asBinder());
+ }
+
+ public static final Parcelable.Creator<SessionCallbackLink> CREATOR =
+ new Parcelable.Creator<SessionCallbackLink>() {
+ @Override
+ public SessionCallbackLink createFromParcel(Parcel in) {
+ return new SessionCallbackLink(in);
+ }
+
+ @Override
+ public SessionCallbackLink[] newArray(int size) {
+ return new SessionCallbackLink[size];
+ }
+ };
+
+ /**
+ * Class for Stub implementation
+ */
+ abstract static class CallbackStub {
+ /** Stub method for ISessionCallback.notifyCommand */
+ public void onCommand(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull String command,
+ @Nullable Bundle args, @Nullable ResultReceiver cb) {
+ }
+
+ /** Stub method for ISessionCallback.notifyMediaButton */
+ public void onMediaButton(@NonNull String packageName, int pid, int uid,
+ @NonNull Intent mediaButtonIntent, int sequenceNumber,
+ @Nullable ResultReceiver cb) {
+ }
+
+ /** Stub method for ISessionCallback.notifyMediaButtonFromController */
+ public void onMediaButtonFromController(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull Intent mediaButtonIntent) {
+ }
+
+ /** Stub method for ISessionCallback.notifyPrepare */
+ public void onPrepare(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionCallback.notifyPrepareFromMediaId */
+ public void onPrepareFromMediaId(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull String mediaId,
+ @Nullable Bundle extras) {
+ }
+
+ /** Stub method for ISessionCallback.notifyPrepareFromSearch */
+ public void onPrepareFromSearch(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, String query, @Nullable Bundle extras) {
+ }
+
+ /** Stub method for ISessionCallback.notifyPrepareFromUri */
+ public void onPrepareFromUri(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull Uri uri, @Nullable Bundle extras) {
+ }
+
+ /** Stub method for ISessionCallback.notifyPlay */
+ public void onPlay(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionCallback.notifyPlayFromMediaId */
+ public void onPlayFromMediaId(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull String mediaId,
+ @Nullable Bundle extras) {
+ }
+
+ /** Stub method for ISessionCallback.notifyPlayFromSearch */
+ public void onPlayFromSearch(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, String query, @Nullable Bundle extras) {
+ }
+
+ /** Stub method for ISessionCallback.notifyPlayFromUri */
+ public void onPlayFromUri(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull Uri uri, @Nullable Bundle extras) {
+ }
+
+ /** Stub method for ISessionCallback.notifySkipToTrack */
+ public void onSkipToTrack(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, long id) {
+ }
+
+ /** Stub method for ISessionCallback.notifyPause */
+ public void onPause(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionCallback.notifyStop */
+ public void onStop(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionCallback.notifyNext */
+ public void onNext(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionCallback.notifyPrevious */
+ public void onPrevious(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionCallback.notifyFastForward */
+ public void onFastForward(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionCallback.notifyRewind */
+ public void onRewind(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionCallback.notifySeekTo */
+ public void onSeekTo(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, long pos) {
+ }
+
+ /** Stub method for ISessionCallback.notifyRate */
+ public void onRate(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull Rating rating) {
+ }
+
+ /** Stub method for ISessionCallback.notifyCustomAction */
+ public void onCustomAction(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull String action,
+ @Nullable Bundle args) {
+ }
+
+ /** Stub method for ISessionCallback.notifyAdjustVolume */
+ public void onAdjustVolume(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, int direction) {
+ }
+
+ /** Stub method for ISessionCallback.notifySetVolumeTo */
+ public void onSetVolumeTo(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, int value) {
+ }
+ }
+
+ private class CallbackStubProxy extends ISessionCallback.Stub {
+ @Override
+ public void notifyCommand(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) {
+ mCallbackStub.onCommand(packageName, pid, uid, caller, command, args, cb);
+ }
+
+ @Override
+ public void notifyMediaButton(String packageName, int pid, int uid,
+ Intent mediaButtonIntent, int sequenceNumber, ResultReceiver cb) {
+ mCallbackStub.onMediaButton(packageName, pid, uid, mediaButtonIntent, sequenceNumber,
+ cb);
+ }
+
+ @Override
+ public void notifyMediaButtonFromController(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, Intent mediaButtonIntent) {
+ mCallbackStub.onMediaButtonFromController(packageName, pid, uid, caller,
+ mediaButtonIntent);
+ }
+
+ @Override
+ public void notifyPrepare(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ mCallbackStub.onPrepare(packageName, pid, uid, caller);
+ }
+
+ @Override
+ public void notifyPrepareFromMediaId(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String mediaId, Bundle extras) {
+ mCallbackStub.onPrepareFromMediaId(packageName, pid, uid, caller, mediaId, extras);
+ }
+
+ @Override
+ public void notifyPrepareFromSearch(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String query, Bundle extras) {
+ mCallbackStub.onPrepareFromSearch(packageName, pid, uid, caller, query, extras);
+ }
+
+ @Override
+ public void notifyPrepareFromUri(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, Uri uri, Bundle extras) {
+ mCallbackStub.onPrepareFromUri(packageName, pid, uid, caller, uri, extras);
+ }
+
+ @Override
+ public void notifyPlay(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ mCallbackStub.onPlay(packageName, pid, uid, caller);
+ }
+
+ @Override
+ public void notifyPlayFromMediaId(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String mediaId, Bundle extras) {
+ mCallbackStub.onPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras);
+ }
+
+ @Override
+ public void notifyPlayFromSearch(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String query, Bundle extras) {
+ mCallbackStub.onPlayFromSearch(packageName, pid, uid, caller, query, extras);
+ }
+
+ @Override
+ public void notifyPlayFromUri(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, Uri uri, Bundle extras) {
+ mCallbackStub.onPlayFromUri(packageName, pid, uid, caller, uri, extras);
+ }
+
+ @Override
+ public void notifySkipToTrack(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, long id) {
+ mCallbackStub.onSkipToTrack(packageName, pid, uid, caller, id);
+ }
+
+ @Override
+ public void notifyPause(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ mCallbackStub.onPause(packageName, pid, uid, caller);
+ }
+
+ @Override
+ public void notifyStop(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ mCallbackStub.onStop(packageName, pid, uid, caller);
+ }
+
+ @Override
+ public void notifyNext(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ mCallbackStub.onNext(packageName, pid, uid, caller);
+ }
+
+ @Override
+ public void notifyPrevious(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ mCallbackStub.onPrevious(packageName, pid, uid, caller);
+ }
+
+ @Override
+ public void notifyFastForward(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ mCallbackStub.onFastForward(packageName, pid, uid, caller);
+ }
+
+ @Override
+ public void notifyRewind(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ mCallbackStub.onRewind(packageName, pid, uid, caller);
+ }
+
+ @Override
+ public void notifySeekTo(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, long pos) {
+ mCallbackStub.onSeekTo(packageName, pid, uid, caller, pos);
+ }
+
+ @Override
+ public void notifyRate(String packageName, int pid, int uid, ControllerCallbackLink caller,
+ Rating rating) {
+ mCallbackStub.onRate(packageName, pid, uid, caller, rating);
+ }
+
+ @Override
+ public void notifyCustomAction(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String action, Bundle args) {
+ mCallbackStub.onCustomAction(packageName, pid, uid, caller, action, args);
+ }
+
+ @Override
+ public void notifyAdjustVolume(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, int direction) {
+ mCallbackStub.onAdjustVolume(packageName, pid, uid, caller, direction);
+ }
+
+ @Override
+ public void notifySetVolumeTo(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, int value) {
+ mCallbackStub.onSetVolumeTo(packageName, pid, uid, caller, value);
+ }
+ }
+}
diff --git a/media/lib/signer/Android.bp b/media/lib/signer/Android.bp
index 8c43683..44f8725 100644
--- a/media/lib/signer/Android.bp
+++ b/media/lib/signer/Android.bp
@@ -18,5 +18,7 @@
name: "com.android.mediadrm.signer",
srcs: ["java/**/*.java"],
api_packages: ["com.android.mediadrm.signer"],
- metalava_enabled: false,
+ srcs_lib: "framework",
+ srcs_lib_whitelist_dirs: ["media/java"],
+ srcs_lib_whitelist_pkgs: ["android.media"],
}
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 5cfb09b..fdcfc44 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -83,6 +83,10 @@
include_dirs: ["bionic/libc/dns/include"],
version_script: "libandroid.map.txt",
+ stubs: {
+ symbol_file: "libandroid.map.txt",
+ versions: ["29"],
+ },
}
// Network library.
diff --git a/native/webview/plat_support/Android.bp b/native/webview/plat_support/Android.bp
index 0936256..88decc8 100644
--- a/native/webview/plat_support/Android.bp
+++ b/native/webview/plat_support/Android.bp
@@ -24,7 +24,6 @@
srcs: [
"draw_functor.cpp",
"draw_gl_functor.cpp",
- "draw_vk_functor.cpp",
"functor_utils.cpp",
"jni_entry_point.cpp",
"graphics_utils.cpp",
diff --git a/native/webview/plat_support/draw_vk.h b/native/webview/plat_support/draw_vk.h
deleted file mode 100644
index 6b7d8d0..0000000
--- a/native/webview/plat_support/draw_vk.h
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-//******************************************************************************
-// This is a copy of the coresponding android_webview/public/browser header.
-// Any changes to the interface should be made there.
-//
-// The purpose of having the copy is twofold:
-// - it removes the need to have Chromium sources present in the tree in order
-// to build the plat_support library,
-// - it captures API that the corresponding Android release supports.
-//******************************************************************************
-
-#ifndef ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_VK_H_
-#define ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_VK_H_
-
-#include <vulkan/vulkan.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-static const int kAwDrawVKInfoVersion = 1;
-
-// Holds the information required to trigger initialization of the Vulkan
-// functor.
-struct InitParams {
- // All params are input
- VkInstance instance;
- VkPhysicalDevice physical_device;
- VkDevice device;
- VkQueue queue;
- uint32_t graphics_queue_index;
- uint32_t instance_version;
- const char* const* enabled_extension_names;
- // Only one of device_features and device_features_2 should be non-null.
- // If both are null then no features are enabled.
- VkPhysicalDeviceFeatures* device_features;
- VkPhysicalDeviceFeatures2* device_features_2;
-};
-
-// Holds the information required to trigger an Vulkan composite operation.
-struct CompositeParams {
- // Input: current width/height of destination surface.
- int width;
- int height;
-
- // Input: is the render target a FBO
- bool is_layer;
-
- // Input: current transform matrix
- float transform[16];
-
- // Input WebView should do its main compositing draws into this. It cannot do
- // anything that would require stopping the render pass.
- VkCommandBuffer secondary_command_buffer;
-
- // Input: The main color attachment index where secondary_command_buffer will
- // eventually be submitted.
- uint32_t color_attachment_index;
-
- // Input: A render pass which will be compatible to the one which the
- // secondary_command_buffer will be submitted into.
- VkRenderPass compatible_render_pass;
-
- // Input: Format of the destination surface.
- VkFormat format;
-
- // Input: Color space transfer params
- float G;
- float A;
- float B;
- float C;
- float D;
- float E;
- float F;
-
- // Input: Color space transformation from linear RGB to D50-adapted XYZ
- float matrix[9];
-
- // Input: current clip rect
- int clip_left;
- int clip_top;
- int clip_right;
- int clip_bottom;
-};
-
-// Holds the information for the post-submission callback of main composite
-// draw.
-struct PostCompositeParams {
- // Input: Fence for the composite command buffer to signal it has finished its
- // work on the GPU.
- int fd;
-};
-
-// Holds the information required to trigger an Vulkan operation.
-struct AwDrawVKInfo {
- int version; // The AwDrawVKInfo this struct was built with.
-
- // Input: tells the draw function what action to perform.
- enum Mode {
- kModeInit = 0,
- kModeReInit = 1,
- kModePreComposite = 2,
- kModeComposite = 3,
- kModePostComposite = 4,
- kModeSync = 5,
- } mode;
-
- // Input: The parameters for the functor being called
- union ParamUnion {
- struct InitParams init_params;
- struct CompositeParams composite_params;
- struct PostCompositeParams post_composite_params;
- } info;
-};
-
-typedef void(AwDrawVKFunction)(long view_context, AwDrawVKInfo* draw_info);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif
-
-#endif // ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_VK_H_
diff --git a/native/webview/plat_support/draw_vk_functor.cpp b/native/webview/plat_support/draw_vk_functor.cpp
deleted file mode 100644
index eab1340..0000000
--- a/native/webview/plat_support/draw_vk_functor.cpp
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// Provides a webviewchromium glue layer adapter from the internal Android
-// Vulkan Functor data types into the types the chromium stack expects, and
-// back.
-
-#define LOG_TAG "webviewchromium_plat_support"
-
-#include "draw_fn.h"
-#include "draw_vk.h"
-
-#include <jni.h>
-#include <private/hwui/DrawVkInfo.h>
-#include <utils/Functor.h>
-#include <utils/Log.h>
-
-#include "functor_utils.h"
-
-#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
-
-namespace android {
-namespace {
-
-AwDrawVKFunction* g_aw_drawvk_function = NULL;
-
-class DrawVKFunctor : public Functor {
- public:
- explicit DrawVKFunctor(jlong view_context) : view_context_(view_context) {}
- ~DrawVKFunctor() override {}
-
- // Functor
- status_t operator ()(int what, void* data) override {
- using uirenderer::DrawVkInfo;
- if (!g_aw_drawvk_function) {
- ALOGE("Cannot draw: no DrawVK Function installed");
- return DrawVkInfo::kStatusDone;
- }
-
- AwDrawVKInfo aw_info;
- aw_info.version = kAwDrawVKInfoVersion;
- switch (what) {
- case DrawVkInfo::kModeComposite: {
- aw_info.mode = AwDrawVKInfo::kModeComposite;
- DrawVkInfo* vk_info = reinterpret_cast<DrawVkInfo*>(data);
-
- // Map across the input values.
- CompositeParams& params = aw_info.info.composite_params;
- params.width = vk_info->width;
- params.height = vk_info->height;
- params.is_layer = vk_info->isLayer;
- for (size_t i = 0; i < 16; i++) {
- params.transform[i] = vk_info->transform[i];
- }
- params.secondary_command_buffer = vk_info->secondaryCommandBuffer;
- params.color_attachment_index = vk_info->colorAttachmentIndex;
- params.compatible_render_pass = vk_info->compatibleRenderPass;
- params.format = vk_info->format;
- params.G = vk_info->G;
- params.A = vk_info->A;
- params.B = vk_info->B;
- params.C = vk_info->C;
- params.D = vk_info->D;
- params.E = vk_info->E;
- params.F = vk_info->F;
- for (size_t i = 0; i < 9; i++) {
- params.matrix[i] = vk_info->matrix[i];
- }
- params.clip_left = vk_info->clipLeft;
- params.clip_top = vk_info->clipTop;
- params.clip_right = vk_info->clipRight;
- params.clip_bottom = vk_info->clipBottom;
-
- break;
- }
- case DrawVkInfo::kModePostComposite:
- break;
- case DrawVkInfo::kModeSync:
- aw_info.mode = AwDrawVKInfo::kModeSync;
- break;
- default:
- ALOGE("Unexpected DrawVKInfo type %d", what);
- return DrawVkInfo::kStatusDone;
- }
-
- // Invoke the DrawVK method.
- g_aw_drawvk_function(view_context_, &aw_info);
-
- return DrawVkInfo::kStatusDone;
- }
-
- private:
- intptr_t view_context_;
-};
-
-jlong CreateVKFunctor(JNIEnv*, jclass, jlong view_context) {
- RaiseFileNumberLimit();
- return reinterpret_cast<jlong>(new DrawVKFunctor(view_context));
-}
-
-void DestroyVKFunctor(JNIEnv*, jclass, jlong functor) {
- delete reinterpret_cast<DrawVKFunctor*>(functor);
-}
-
-void SetChromiumAwDrawVKFunction(JNIEnv*, jclass, jlong draw_function) {
- g_aw_drawvk_function = reinterpret_cast<AwDrawVKFunction*>(draw_function);
-}
-
-const char kClassName[] = "com/android/webview/chromium/DrawVKFunctor";
-const JNINativeMethod kJniMethods[] = {
- { "nativeCreateVKFunctor", "(J)J",
- reinterpret_cast<void*>(CreateVKFunctor) },
- { "nativeDestroyVKFunctor", "(J)V",
- reinterpret_cast<void*>(DestroyVKFunctor) },
- { "nativeSetChromiumAwDrawVKFunction", "(J)V",
- reinterpret_cast<void*>(SetChromiumAwDrawVKFunction) },
-};
-
-} // namespace
-
-void RegisterDrawVKFunctor(JNIEnv* env) {
- jclass clazz = env->FindClass(kClassName);
- LOG_ALWAYS_FATAL_IF(!clazz, "Unable to find class '%s'", kClassName);
-
- int res = env->RegisterNatives(clazz, kJniMethods, NELEM(kJniMethods));
- LOG_ALWAYS_FATAL_IF(res < 0, "register native methods failed: res=%d", res);
-}
-
-} // namespace android
diff --git a/packages/CarrierDefaultApp/tests/unit/Android.mk b/packages/CarrierDefaultApp/tests/unit/Android.mk
index 8e3785e..4c638811 100644
--- a/packages/CarrierDefaultApp/tests/unit/Android.mk
+++ b/packages/CarrierDefaultApp/tests/unit/Android.mk
@@ -21,7 +21,7 @@
LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common android.test.base
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test mockito-target-minus-junit4
+LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules mockito-target-minus-junit4
# Include all test java files.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml b/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml
index 3a06a09..7a26d95 100644
--- a/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml
@@ -21,7 +21,7 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.carrierdefaultapp"
android:label="CarrierDefaultApp Unit Test Cases">
</instrumentation>
diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp
index 4688848..2f7d599 100644
--- a/packages/NetworkStack/Android.bp
+++ b/packages/NetworkStack/Android.bp
@@ -21,10 +21,10 @@
installable: true,
srcs: [
"src/**/*.java",
+ ":services-networkstack-shared-srcs",
],
static_libs: [
"dhcp-packet-lib",
- "frameworks-net-shared-utils",
]
}
diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml
index 8516d94..0b0f1ec 100644
--- a/packages/NetworkStack/AndroidManifest.xml
+++ b/packages/NetworkStack/AndroidManifest.xml
@@ -22,8 +22,11 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+ <uses-permission android:name="android.permission.NETWORK_SETTINGS" />
<!-- Launch captive portal app as specific user -->
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <uses-permission android:name="android.permission.NETWORK_STACK" />
<application
android:label="NetworkStack"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/NetworkStack/src/android/net/util/SharedLog.java b/packages/NetworkStack/src/android/net/util/SharedLog.java
index 74bc147..4fabf10 100644
--- a/packages/NetworkStack/src/android/net/util/SharedLog.java
+++ b/packages/NetworkStack/src/android/net/util/SharedLog.java
@@ -69,6 +69,10 @@
mComponent = component;
}
+ public String getTag() {
+ return mTag;
+ }
+
/**
* Create a SharedLog based on this log with an additional component prefix on each logged line.
*/
diff --git a/services/net/java/android/net/util/Stopwatch.java b/packages/NetworkStack/src/android/net/util/Stopwatch.java
similarity index 78%
rename from services/net/java/android/net/util/Stopwatch.java
rename to packages/NetworkStack/src/android/net/util/Stopwatch.java
index cb15ee5..c316699 100644
--- a/services/net/java/android/net/util/Stopwatch.java
+++ b/packages/NetworkStack/src/android/net/util/Stopwatch.java
@@ -38,9 +38,9 @@
return (isStarted() && !isStopped());
}
- // Returning |this| makes possible the following usage pattern:
- //
- // Stopwatch s = new Stopwatch().start();
+ /**
+ * Start the Stopwatch.
+ */
public Stopwatch start() {
if (!isStarted()) {
mStartTimeMs = SystemClock.elapsedRealtime();
@@ -48,7 +48,10 @@
return this;
}
- // Returns the total time recorded, in milliseconds, or 0 if not started.
+ /**
+ * Stop the Stopwatch.
+ * @return the total time recorded, in milliseconds, or 0 if not started.
+ */
public long stop() {
if (isRunning()) {
mStopTimeMs = SystemClock.elapsedRealtime();
@@ -57,9 +60,11 @@
return (mStopTimeMs - mStartTimeMs);
}
- // Returns the total time recorded to date, in milliseconds.
- // If the Stopwatch is not running, returns the same value as stop(),
- // i.e. either the total time recorded before stopping or 0.
+ /**
+ * Return the total time recorded to date, in milliseconds.
+ * If the Stopwatch is not running, returns the same value as stop(),
+ * i.e. either the total time recorded before stopping or 0.
+ */
public long lap() {
if (isRunning()) {
return (SystemClock.elapsedRealtime() - mStartTimeMs);
@@ -68,6 +73,9 @@
}
}
+ /**
+ * Reset the Stopwatch. It will be stopped when this method returns.
+ */
public void reset() {
mStartTimeMs = 0;
mStopTimeMs = 0;
diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
index 7fea1e0..057012d 100644
--- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java
+++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
@@ -25,18 +25,31 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Service;
+import android.content.Context;
import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.INetworkMonitor;
+import android.net.INetworkMonitorCallbacks;
import android.net.INetworkStackConnector;
+import android.net.Network;
+import android.net.NetworkRequest;
+import android.net.PrivateDnsConfigParcel;
import android.net.dhcp.DhcpServer;
import android.net.dhcp.DhcpServingParams;
import android.net.dhcp.DhcpServingParamsParcel;
import android.net.dhcp.IDhcpServerCallbacks;
+import android.net.shared.PrivateDnsConfig;
import android.net.util.SharedLog;
import android.os.IBinder;
import android.os.RemoteException;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.connectivity.NetworkMonitor;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayDeque;
/**
* Android service used to start the network stack when bound to via an intent.
@@ -52,17 +65,41 @@
* <p>On platforms where the network stack runs in the system server process, this method may
* be called directly instead of obtaining the connector by binding to the service.
*/
- public static IBinder makeConnector() {
- return new NetworkStackConnector();
+ public static IBinder makeConnector(Context context) {
+ return new NetworkStackConnector(context);
}
@NonNull
@Override
public IBinder onBind(Intent intent) {
- return makeConnector();
+ return makeConnector(this);
}
private static class NetworkStackConnector extends INetworkStackConnector.Stub {
+ private static final int NUM_VALIDATION_LOG_LINES = 20;
+ private final Context mContext;
+ private final ConnectivityManager mCm;
+
+ private static final int MAX_VALIDATION_LOGS = 10;
+ @GuardedBy("mValidationLogs")
+ private final ArrayDeque<SharedLog> mValidationLogs = new ArrayDeque<>(MAX_VALIDATION_LOGS);
+
+ private SharedLog addValidationLogs(Network network, String name) {
+ final SharedLog log = new SharedLog(NUM_VALIDATION_LOG_LINES, network + " - " + name);
+ synchronized (mValidationLogs) {
+ while (mValidationLogs.size() >= MAX_VALIDATION_LOGS) {
+ mValidationLogs.removeLast();
+ }
+ mValidationLogs.addFirst(log);
+ }
+ return log;
+ }
+
+ NetworkStackConnector(Context context) {
+ mContext = context;
+ mCm = context.getSystemService(ConnectivityManager.class);
+ }
+
@NonNull
private final SharedLog mLog = new SharedLog(TAG);
@@ -89,11 +126,102 @@
}
@Override
+ public void makeNetworkMonitor(int netId, String name, INetworkMonitorCallbacks cb)
+ throws RemoteException {
+ final Network network = new Network(netId, false /* privateDnsBypass */);
+ final NetworkRequest defaultRequest = mCm.getDefaultRequest();
+ final SharedLog log = addValidationLogs(network, name);
+ final NetworkMonitor nm = new NetworkMonitor(
+ mContext, cb, network, defaultRequest, log);
+ cb.onNetworkMonitorCreated(new NetworkMonitorImpl(nm));
+ }
+
+ @Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {
checkNetworkStackCallingPermission();
- fout.println("NetworkStack logs:");
- mLog.dump(fd, fout, args);
+ final IndentingPrintWriter pw = new IndentingPrintWriter(fout, " ");
+ pw.println("NetworkStack logs:");
+ mLog.dump(fd, pw, args);
+
+ pw.println();
+ pw.println("Validation logs (most recent first):");
+ synchronized (mValidationLogs) {
+ for (SharedLog p : mValidationLogs) {
+ pw.println(p.getTag());
+ pw.increaseIndent();
+ p.dump(fd, pw, args);
+ pw.decreaseIndent();
+ }
+ }
+ }
+ }
+
+ private static class NetworkMonitorImpl extends INetworkMonitor.Stub {
+ private final NetworkMonitor mNm;
+
+ NetworkMonitorImpl(NetworkMonitor nm) {
+ mNm = nm;
+ }
+
+ @Override
+ public void start() {
+ checkNetworkStackCallingPermission();
+ mNm.start();
+ }
+
+ @Override
+ public void launchCaptivePortalApp() {
+ checkNetworkStackCallingPermission();
+ mNm.launchCaptivePortalApp();
+ }
+
+ @Override
+ public void forceReevaluation(int uid) {
+ checkNetworkStackCallingPermission();
+ mNm.forceReevaluation(uid);
+ }
+
+ @Override
+ public void notifyPrivateDnsChanged(PrivateDnsConfigParcel config) {
+ checkNetworkStackCallingPermission();
+ mNm.notifyPrivateDnsSettingsChanged(PrivateDnsConfig.fromParcel(config));
+ }
+
+ @Override
+ public void notifyDnsResponse(int returnCode) {
+ checkNetworkStackCallingPermission();
+ mNm.notifyDnsResponse(returnCode);
+ }
+
+ @Override
+ public void notifySystemReady() {
+ checkNetworkStackCallingPermission();
+ mNm.notifySystemReady();
+ }
+
+ @Override
+ public void notifyNetworkConnected() {
+ checkNetworkStackCallingPermission();
+ mNm.notifyNetworkConnected();
+ }
+
+ @Override
+ public void notifyNetworkDisconnected() {
+ checkNetworkStackCallingPermission();
+ mNm.notifyNetworkDisconnected();
+ }
+
+ @Override
+ public void notifyLinkPropertiesChanged() {
+ checkNetworkStackCallingPermission();
+ mNm.notifyLinkPropertiesChanged();
+ }
+
+ @Override
+ public void notifyNetworkCapabilitiesChanged() {
+ checkNetworkStackCallingPermission();
+ mNm.notifyNetworkCapabilitiesChanged();
}
}
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
similarity index 84%
rename from services/core/java/com/android/server/connectivity/NetworkMonitor.java
rename to packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
index 2a00025..94ea1b9 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
@@ -21,6 +21,11 @@
import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS;
import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC;
import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.metrics.ValidationProbeEvent.DNS_FAILURE;
import static android.net.metrics.ValidationProbeEvent.DNS_SUCCESS;
import static android.net.metrics.ValidationProbeEvent.PROBE_FALLBACK;
@@ -35,6 +40,9 @@
import android.net.CaptivePortal;
import android.net.ConnectivityManager;
import android.net.ICaptivePortal;
+import android.net.INetworkMonitor;
+import android.net.INetworkMonitorCallbacks;
+import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
@@ -46,11 +54,14 @@
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
import android.net.metrics.ValidationProbeEvent;
+import android.net.shared.NetworkMonitorUtils;
+import android.net.shared.PrivateDnsConfig;
+import android.net.util.SharedLog;
import android.net.util.Stopwatch;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
-import android.os.Handler;
import android.os.Message;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
@@ -65,8 +76,6 @@
import android.telephony.CellInfoWcdma;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
-import android.util.LocalLog;
-import android.util.LocalLog.ReadOnlyLocalLog;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -75,7 +84,6 @@
import com.android.internal.util.RingBufferIndices;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
-import com.android.server.connectivity.DnsManager.PrivateDnsConfig;
import java.io.IOException;
import java.net.HttpURLConnection;
@@ -104,9 +112,7 @@
// Default configuration values for captive portal detection probes.
// TODO: append a random length parameter to the default HTTPS url.
// TODO: randomize browser version ids in the default User-Agent String.
- private static final String DEFAULT_HTTPS_URL = "https://www.google.com/generate_204";
- private static final String DEFAULT_HTTP_URL =
- "http://connectivitycheck.gstatic.com/generate_204";
+ private static final String DEFAULT_HTTPS_URL = "https://www.google.com/generate_204";
private static final String DEFAULT_FALLBACK_URL = "http://www.google.com/gen_204";
private static final String DEFAULT_OTHER_FALLBACK_URLS =
"http://play.googleapis.com/generate_204";
@@ -144,33 +150,12 @@
}
}
- // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
- // The network should be used as a default internet connection. It was found to be:
- // 1. a functioning network providing internet access, or
- // 2. a captive portal and the user decided to use it as is.
- public static final int NETWORK_TEST_RESULT_VALID = 0;
- // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
- // The network should not be used as a default internet connection. It was found to be:
- // 1. a captive portal and the user is prompted to sign-in, or
- // 2. a captive portal and the user did not want to use it, or
- // 3. a broken network (e.g. DNS failed, connect failed, HTTP request failed).
- public static final int NETWORK_TEST_RESULT_INVALID = 1;
-
private static final int BASE = Protocol.BASE_NETWORK_MONITOR;
-
/**
- * Inform NetworkMonitor that their network is connected.
+ * ConnectivityService has sent a notification to indicate that network has connected.
* Initiates Network Validation.
*/
- public static final int CMD_NETWORK_CONNECTED = BASE + 1;
-
- /**
- * Inform ConnectivityService that the network has been tested.
- * obj = String representing URL that Internet probe was redirect to, if it was redirected.
- * arg1 = One of the NETWORK_TESTED_RESULT_* constants.
- * arg2 = NetID.
- */
- public static final int EVENT_NETWORK_TESTED = BASE + 2;
+ private static final int CMD_NETWORK_CONNECTED = BASE + 1;
/**
* Message to self indicating it's time to evaluate a network's connectivity.
@@ -179,9 +164,9 @@
private static final int CMD_REEVALUATE = BASE + 6;
/**
- * Inform NetworkMonitor that the network has disconnected.
+ * ConnectivityService has sent a notification to indicate that network has disconnected.
*/
- public static final int CMD_NETWORK_DISCONNECTED = BASE + 7;
+ private static final int CMD_NETWORK_DISCONNECTED = BASE + 7;
/**
* Force evaluation even if it has succeeded in the past.
@@ -199,21 +184,13 @@
private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = BASE + 9;
/**
- * Request ConnectivityService display provisioning notification.
- * arg1 = Whether to make the notification visible.
- * arg2 = NetID.
- * obj = Intent to be launched when notification selected by user, null if !arg1.
- */
- public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 10;
-
- /**
* Message indicating sign-in app should be launched.
* Sent by mLaunchCaptivePortalAppBroadcastReceiver when the
* user touches the sign in notification, or sent by
* ConnectivityService when the user touches the "sign into
* network" button in the wifi access point detail page.
*/
- public static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11;
+ private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11;
/**
* Retest network to see if captive portal is still in place.
@@ -234,7 +211,6 @@
* validation phase is completed.
*/
private static final int CMD_PRIVATE_DNS_SETTINGS_CHANGED = BASE + 13;
- public static final int EVENT_PRIVATE_DNS_CONFIG_RESOLVED = BASE + 14;
private static final int CMD_EVALUATE_PRIVATE_DNS = BASE + 15;
/**
@@ -263,23 +239,16 @@
// Delay between reevaluations once a captive portal has been found.
private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 10 * 60 * 1000;
- private static final int NUM_VALIDATION_LOG_LINES = 20;
-
private String mPrivateDnsProviderHostname = "";
- public static boolean isValidationRequired(
- NetworkCapabilities dfltNetCap, NetworkCapabilities nc) {
- // TODO: Consider requiring validation for DUN networks.
- return dfltNetCap.satisfiedByNetworkCapabilities(nc);
- }
-
private final Context mContext;
- private final Handler mConnectivityServiceHandler;
- private final NetworkAgentInfo mNetworkAgentInfo;
+ private final INetworkMonitorCallbacks mCallback;
private final Network mNetwork;
+ private final Network mNonPrivateDnsBypassNetwork;
private final int mNetId;
private final TelephonyManager mTelephonyManager;
private final WifiManager mWifiManager;
+ private final ConnectivityManager mCm;
private final NetworkRequest mDefaultRequest;
private final IpConnectivityLog mMetricsLog;
private final Dependencies mDependencies;
@@ -292,6 +261,9 @@
@Nullable
private final CaptivePortalProbeSpec[] mCaptivePortalFallbackSpecs;
+ private NetworkCapabilities mNetworkCapabilities;
+ private LinkProperties mLinkProperties;
+
@VisibleForTesting
protected boolean mIsCaptivePortalCheckEnabled;
@@ -304,7 +276,7 @@
// Avoids surfacing "Sign in to network" notification.
private boolean mDontDisplaySigninNotification = false;
- public boolean systemReady = false;
+ private volatile boolean mSystemReady = false;
private final State mDefaultState = new DefaultState();
private final State mValidatedState = new ValidatedState();
@@ -317,7 +289,7 @@
private CustomIntentReceiver mLaunchCaptivePortalAppBroadcastReceiver = null;
- private final LocalLog validationLogs = new LocalLog(NUM_VALIDATION_LOG_LINES);
+ private final SharedLog mValidationLogs;
private final Stopwatch mEvaluationTimer = new Stopwatch();
@@ -328,6 +300,7 @@
private final Random mRandom;
private int mNextFallbackUrlIndex = 0;
+
private int mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS;
private int mEvaluateAttempts = 0;
private volatile int mProbeToken = 0;
@@ -338,17 +311,18 @@
private final DnsStallDetector mDnsStallDetector;
private long mLastProbeTime;
- public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo,
- NetworkRequest defaultRequest) {
- this(context, handler, networkAgentInfo, defaultRequest, new IpConnectivityLog(),
+ public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
+ NetworkRequest defaultRequest, SharedLog validationLog) {
+ this(context, cb, network, defaultRequest, new IpConnectivityLog(), validationLog,
Dependencies.DEFAULT);
}
@VisibleForTesting
- protected NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo,
- NetworkRequest defaultRequest, IpConnectivityLog logger, Dependencies deps) {
+ protected NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
+ NetworkRequest defaultRequest, IpConnectivityLog logger, SharedLog validationLogs,
+ Dependencies deps) {
// Add suffix indicating which NetworkMonitor we're talking about.
- super(TAG + networkAgentInfo.name());
+ super(TAG + "/" + network.netId);
// Logs with a tag of the form given just above, e.g.
// <timestamp> 862 2402 D NetworkMonitor/NetworkAgentInfo [WIFI () - 100]: ...
@@ -356,15 +330,18 @@
mContext = context;
mMetricsLog = logger;
- mConnectivityServiceHandler = handler;
+ mValidationLogs = validationLogs;
+ mCallback = cb;
mDependencies = deps;
- mNetworkAgentInfo = networkAgentInfo;
- mNetwork = deps.getNetwork(networkAgentInfo).getPrivateDnsBypassingCopy();
+ mNonPrivateDnsBypassNetwork = network;
+ mNetwork = deps.getPrivateDnsBypassNetwork(network);
mNetId = mNetwork.netId;
mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
mDefaultRequest = defaultRequest;
+ // CHECKSTYLE:OFF IndentationCheck
addState(mDefaultState);
addState(mMaybeNotifyState, mDefaultState);
addState(mEvaluatingState, mMaybeNotifyState);
@@ -374,12 +351,13 @@
addState(mEvaluatingPrivateDnsState, mDefaultState);
addState(mValidatedState, mDefaultState);
setInitialState(mDefaultState);
+ // CHECKSTYLE:ON IndentationCheck
mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled();
mUseHttps = getUseHttpsValidation();
mCaptivePortalUserAgent = getCaptivePortalUserAgent();
mCaptivePortalHttpsUrl = makeURL(getCaptivePortalServerHttpsUrl());
- mCaptivePortalHttpUrl = makeURL(getCaptivePortalServerHttpUrl(deps, context));
+ mCaptivePortalHttpUrl = makeURL(deps.getCaptivePortalServerHttpUrl(context));
mCaptivePortalFallbackUrls = makeCaptivePortalFallbackUrls();
mCaptivePortalFallbackSpecs = makeCaptivePortalFallbackProbeSpecs();
mRandom = deps.getRandom();
@@ -390,7 +368,13 @@
mDataStallValidDnsTimeThreshold = getDataStallValidDnsTimeThreshold();
mDataStallEvaluationType = getDataStallEvalutionType();
- start();
+ // mLinkProperties and mNetworkCapbilities must never be null or we will NPE.
+ // Provide empty objects in case we are started and the network disconnects before
+ // we can ever fetch them.
+ // TODO: Delete ASAP.
+ mLinkProperties = new LinkProperties();
+ mNetworkCapabilities = new NetworkCapabilities();
+ mNetworkCapabilities.clearAll();
}
/**
@@ -401,6 +385,14 @@
}
/**
+ * Send a notification to NetworkMonitor indicating that there was a DNS query response event.
+ * @param returnCode the DNS return code of the response.
+ */
+ public void notifyDnsResponse(int returnCode) {
+ sendMessage(EVENT_DNS_NOTIFICATION, returnCode);
+ }
+
+ /**
* Send a notification to NetworkMonitor indicating that private DNS settings have changed.
* @param newCfg The new private DNS configuration.
*/
@@ -411,9 +403,75 @@
sendMessage(CMD_PRIVATE_DNS_SETTINGS_CHANGED, newCfg);
}
+ /**
+ * Send a notification to NetworkMonitor indicating that the system is ready.
+ */
+ public void notifySystemReady() {
+ // No need to run on the handler thread: mSystemReady is volatile and read only once on the
+ // isCaptivePortal() thread.
+ mSystemReady = true;
+ }
+
+ /**
+ * Send a notification to NetworkMonitor indicating that the network is now connected.
+ */
+ public void notifyNetworkConnected() {
+ sendMessage(CMD_NETWORK_CONNECTED);
+ }
+
+ /**
+ * Send a notification to NetworkMonitor indicating that the network is now disconnected.
+ */
+ public void notifyNetworkDisconnected() {
+ sendMessage(CMD_NETWORK_DISCONNECTED);
+ }
+
+ /**
+ * Send a notification to NetworkMonitor indicating that link properties have changed.
+ */
+ public void notifyLinkPropertiesChanged() {
+ getHandler().post(() -> {
+ updateLinkProperties();
+ });
+ }
+
+ private void updateLinkProperties() {
+ final LinkProperties lp = mCm.getLinkProperties(mNetwork);
+ // If null, we should soon get a message that the network was disconnected, and will stop.
+ if (lp != null) {
+ // TODO: send LinkProperties parceled in notifyLinkPropertiesChanged() and start().
+ mLinkProperties = lp;
+ }
+ }
+
+ /**
+ * Send a notification to NetworkMonitor indicating that network capabilities have changed.
+ */
+ public void notifyNetworkCapabilitiesChanged() {
+ getHandler().post(() -> {
+ updateNetworkCapabilities();
+ });
+ }
+
+ private void updateNetworkCapabilities() {
+ final NetworkCapabilities nc = mCm.getNetworkCapabilities(mNetwork);
+ // If null, we should soon get a message that the network was disconnected, and will stop.
+ if (nc != null) {
+ // TODO: send NetworkCapabilities parceled in notifyNetworkCapsChanged() and start().
+ mNetworkCapabilities = nc;
+ }
+ }
+
+ /**
+ * Request the captive portal application to be launched.
+ */
+ public void launchCaptivePortalApp() {
+ sendMessage(CMD_LAUNCH_CAPTIVE_PORTAL_APP);
+ }
+
@Override
protected void log(String s) {
- if (DBG) Log.d(TAG + "/" + mNetworkAgentInfo.name(), s);
+ if (DBG) Log.d(TAG + "/" + mNetwork.netId, s);
}
private void validationLog(int probeType, Object url, String msg) {
@@ -423,11 +481,7 @@
private void validationLog(String s) {
if (DBG) log(s);
- validationLogs.log(s);
- }
-
- public ReadOnlyLocalLog getValidationLogs() {
- return validationLogs.readOnlyLocalLog();
+ mValidationLogs.log(s);
}
private ValidationStage validationStage() {
@@ -435,20 +489,46 @@
}
private boolean isValidationRequired() {
- return isValidationRequired(
- mDefaultRequest.networkCapabilities, mNetworkAgentInfo.networkCapabilities);
+ return NetworkMonitorUtils.isValidationRequired(
+ mDefaultRequest.networkCapabilities, mNetworkCapabilities);
}
- private void notifyNetworkTestResultInvalid(Object obj) {
- mConnectivityServiceHandler.sendMessage(obtainMessage(
- EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_INVALID, mNetId, obj));
+ private void notifyNetworkTested(int result, @Nullable String redirectUrl) {
+ try {
+ mCallback.notifyNetworkTested(result, redirectUrl);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending network test result", e);
+ }
+ }
+
+ private void showProvisioningNotification(String action) {
+ try {
+ mCallback.showProvisioningNotification(action);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error showing provisioning notification", e);
+ }
+ }
+
+ private void hideProvisioningNotification() {
+ try {
+ mCallback.hideProvisioningNotification();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error hiding provisioning notification", e);
+ }
}
// DefaultState is the parent of all States. It exists only to handle CMD_* messages but
// does not entail any real state (hence no enter() or exit() routines).
private class DefaultState extends State {
@Override
+ public void enter() {
+ // TODO: have those passed parceled in start() and remove this
+ updateLinkProperties();
+ updateNetworkCapabilities();
+ }
+
+ @Override
public boolean processMessage(Message message) {
switch (message.what) {
case CMD_NETWORK_CONNECTED:
@@ -499,7 +579,7 @@
case APP_RETURN_UNWANTED:
mDontDisplaySigninNotification = true;
mUserDoesNotWant = true;
- notifyNetworkTestResultInvalid(null);
+ notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null);
// TODO: Should teardown network.
mUidResponsibleForReeval = 0;
transitionTo(mEvaluatingState);
@@ -563,8 +643,7 @@
public void enter() {
maybeLogEvaluationResult(
networkEventType(validationStage(), EvaluationResult.VALIDATED));
- mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
- NETWORK_TEST_RESULT_VALID, mNetId, null));
+ notifyNetworkTested(INetworkMonitor.NETWORK_TEST_RESULT_VALID, null);
mValidations++;
}
@@ -633,8 +712,7 @@
@Override
public void exit() {
- Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0, mNetId, null);
- mConnectivityServiceHandler.sendMessage(message);
+ hideProvisioningNotification();
}
}
@@ -751,9 +829,7 @@
CMD_LAUNCH_CAPTIVE_PORTAL_APP);
}
// Display the sign in notification.
- Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1, mNetId,
- mLaunchCaptivePortalAppBroadcastReceiver.getPendingIntent());
- mConnectivityServiceHandler.sendMessage(message);
+ showProvisioningNotification(mLaunchCaptivePortalAppBroadcastReceiver.mAction);
// Retest for captive portal occasionally.
sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */,
CAPTIVE_PORTAL_REEVALUATE_DELAY_MS);
@@ -839,12 +915,15 @@
}
private void notifyPrivateDnsConfigResolved() {
- mConnectivityServiceHandler.sendMessage(obtainMessage(
- EVENT_PRIVATE_DNS_CONFIG_RESOLVED, 0, mNetId, mPrivateDnsConfig));
+ try {
+ mCallback.notifyPrivateDnsConfigResolved(mPrivateDnsConfig.toParcel());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending private DNS config resolved notification", e);
+ }
}
private void handlePrivateDnsEvaluationFailure() {
- notifyNetworkTestResultInvalid(null);
+ notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null);
// Queue up a re-evaluation with backoff.
//
@@ -865,7 +944,7 @@
+ oneTimeHostnameSuffix;
final Stopwatch watch = new Stopwatch().start();
try {
- final InetAddress[] ips = mNetworkAgentInfo.network().getAllByName(host);
+ final InetAddress[] ips = mNonPrivateDnsBypassNetwork.getAllByName(host);
final long time = watch.stop();
final String strIps = Arrays.toString(ips);
final boolean success = (ips != null && ips.length > 0);
@@ -915,12 +994,12 @@
// state (even if no Private DNS validation required).
transitionTo(mEvaluatingPrivateDnsState);
} else if (probeResult.isPortal()) {
- notifyNetworkTestResultInvalid(probeResult.redirectUrl);
+ notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, probeResult.redirectUrl);
mLastPortalProbeResult = probeResult;
transitionTo(mCaptivePortalState);
} else {
logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED);
- notifyNetworkTestResultInvalid(probeResult.redirectUrl);
+ notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, probeResult.redirectUrl);
transitionTo(mWaitingForNextProbeState);
}
return HANDLED;
@@ -996,18 +1075,18 @@
}
}
- public boolean getIsCaptivePortalCheckEnabled() {
+ private boolean getIsCaptivePortalCheckEnabled() {
String symbol = Settings.Global.CAPTIVE_PORTAL_MODE;
int defaultValue = Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT;
int mode = mDependencies.getSetting(mContext, symbol, defaultValue);
return mode != Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE;
}
- public boolean getUseHttpsValidation() {
+ private boolean getUseHttpsValidation() {
return mDependencies.getSetting(mContext, Settings.Global.CAPTIVE_PORTAL_USE_HTTPS, 1) == 1;
}
- public boolean getWifiScansAlwaysAvailableDisabled() {
+ private boolean getWifiScansAlwaysAvailableDisabled() {
return mDependencies.getSetting(
mContext, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 0;
}
@@ -1040,15 +1119,6 @@
DEFAULT_DATA_STALL_EVALUATION_TYPES);
}
- // Static for direct access by ConnectivityService
- public static String getCaptivePortalServerHttpUrl(Context context) {
- return getCaptivePortalServerHttpUrl(Dependencies.DEFAULT, context);
- }
-
- public static String getCaptivePortalServerHttpUrl(Dependencies deps, Context context) {
- return deps.getSetting(context, Settings.Global.CAPTIVE_PORTAL_HTTP_URL, DEFAULT_HTTP_URL);
- }
-
private URL[] makeCaptivePortalFallbackUrls() {
try {
String separator = ",";
@@ -1144,7 +1214,7 @@
// 3. PAC scripts are sometimes used to block or restrict Internet access and may in
// fact block fetching of the generate_204 URL which would lead to false negative
// results for network validation.
- final ProxyInfo proxyInfo = mNetworkAgentInfo.linkProperties.getHttpProxy();
+ final ProxyInfo proxyInfo = mLinkProperties.getHttpProxy();
if (proxyInfo != null && !Uri.EMPTY.equals(proxyInfo.getPacFileUrl())) {
pacUrl = makeURL(proxyInfo.getPacFileUrl().toString());
if (pacUrl == null) {
@@ -1416,89 +1486,86 @@
return;
}
- if (!systemReady) {
+ if (!mSystemReady) {
return;
}
Intent latencyBroadcast =
- new Intent(ConnectivityConstants.ACTION_NETWORK_CONDITIONS_MEASURED);
- switch (mNetworkAgentInfo.networkInfo.getType()) {
- case ConnectivityManager.TYPE_WIFI:
- WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo();
- if (currentWifiInfo != null) {
- // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not
- // surrounded by double quotation marks (thus violating the Javadoc), but this
- // was changed to match the Javadoc in API 17. Since clients may have started
- // sanitizing the output of this method since API 17 was released, we should
- // not change it here as it would become impossible to tell whether the SSID is
- // simply being surrounded by quotes due to the API, or whether those quotes
- // are actually part of the SSID.
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_SSID,
- currentWifiInfo.getSSID());
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_BSSID,
- currentWifiInfo.getBSSID());
- } else {
- if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found");
- return;
- }
- break;
- case ConnectivityManager.TYPE_MOBILE:
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_NETWORK_TYPE,
- mTelephonyManager.getNetworkType());
- List<CellInfo> info = mTelephonyManager.getAllCellInfo();
- if (info == null) return;
- int numRegisteredCellInfo = 0;
- for (CellInfo cellInfo : info) {
- if (cellInfo.isRegistered()) {
- numRegisteredCellInfo++;
- if (numRegisteredCellInfo > 1) {
- if (VDBG) {
- logw("more than one registered CellInfo."
- + " Can't tell which is active. Bailing.");
- }
- return;
+ new Intent(NetworkMonitorUtils.ACTION_NETWORK_CONDITIONS_MEASURED);
+ if (mNetworkCapabilities.hasTransport(TRANSPORT_WIFI)) {
+ WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo();
+ if (currentWifiInfo != null) {
+ // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not
+ // surrounded by double quotation marks (thus violating the Javadoc), but this
+ // was changed to match the Javadoc in API 17. Since clients may have started
+ // sanitizing the output of this method since API 17 was released, we should
+ // not change it here as it would become impossible to tell whether the SSID is
+ // simply being surrounded by quotes due to the API, or whether those quotes
+ // are actually part of the SSID.
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_SSID,
+ currentWifiInfo.getSSID());
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_BSSID,
+ currentWifiInfo.getBSSID());
+ } else {
+ if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found");
+ return;
+ }
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_WIFI);
+ } else if (mNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_NETWORK_TYPE,
+ mTelephonyManager.getNetworkType());
+ List<CellInfo> info = mTelephonyManager.getAllCellInfo();
+ if (info == null) return;
+ int numRegisteredCellInfo = 0;
+ for (CellInfo cellInfo : info) {
+ if (cellInfo.isRegistered()) {
+ numRegisteredCellInfo++;
+ if (numRegisteredCellInfo > 1) {
+ if (VDBG) {
+ logw("more than one registered CellInfo."
+ + " Can't tell which is active. Bailing.");
}
- if (cellInfo instanceof CellInfoCdma) {
- CellIdentityCdma cellId = ((CellInfoCdma) cellInfo).getCellIdentity();
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
- } else if (cellInfo instanceof CellInfoGsm) {
- CellIdentityGsm cellId = ((CellInfoGsm) cellInfo).getCellIdentity();
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
- } else if (cellInfo instanceof CellInfoLte) {
- CellIdentityLte cellId = ((CellInfoLte) cellInfo).getCellIdentity();
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
- } else if (cellInfo instanceof CellInfoWcdma) {
- CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity();
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
- } else {
- if (VDBG) logw("Registered cellinfo is unrecognized");
- return;
- }
+ return;
+ }
+ if (cellInfo instanceof CellInfoCdma) {
+ CellIdentityCdma cellId = ((CellInfoCdma) cellInfo).getCellIdentity();
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId);
+ } else if (cellInfo instanceof CellInfoGsm) {
+ CellIdentityGsm cellId = ((CellInfoGsm) cellInfo).getCellIdentity();
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId);
+ } else if (cellInfo instanceof CellInfoLte) {
+ CellIdentityLte cellId = ((CellInfoLte) cellInfo).getCellIdentity();
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId);
+ } else if (cellInfo instanceof CellInfoWcdma) {
+ CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity();
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId);
+ } else {
+ if (VDBG) logw("Registered cellinfo is unrecognized");
+ return;
}
}
- break;
- default:
- return;
+ }
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_MOBILE);
+ } else {
+ return;
}
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CONNECTIVITY_TYPE,
- mNetworkAgentInfo.networkInfo.getType());
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_RESPONSE_RECEIVED,
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_RECEIVED,
responseReceived);
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_REQUEST_TIMESTAMP_MS,
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_REQUEST_TIMESTAMP_MS,
requestTimestampMs);
if (responseReceived) {
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_IS_CAPTIVE_PORTAL,
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_IS_CAPTIVE_PORTAL,
isCaptivePortal);
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_RESPONSE_TIMESTAMP_MS,
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_TIMESTAMP_MS,
responseTimestampMs);
}
mContext.sendBroadcastAsUser(latencyBroadcast, UserHandle.CURRENT,
- ConnectivityConstants.PERMISSION_ACCESS_NETWORK_CONDITIONS);
+ NetworkMonitorUtils.PERMISSION_ACCESS_NETWORK_CONDITIONS);
}
private void logNetworkEvent(int evtype) {
- int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes();
+ int[] transports = mNetworkCapabilities.getTransportTypes();
mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype));
}
@@ -1520,14 +1587,14 @@
private void maybeLogEvaluationResult(int evtype) {
if (mEvaluationTimer.isRunning()) {
- int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes();
+ int[] transports = mNetworkCapabilities.getTransportTypes();
mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype, mEvaluationTimer.stop()));
mEvaluationTimer.reset();
}
}
private void logValidationProbe(long durationMs, int probeType, int probeResult) {
- int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes();
+ int[] transports = mNetworkCapabilities.getTransportTypes();
boolean isFirstValidation = validationStage().mIsFirstValidation;
ValidationProbeEvent ev = new ValidationProbeEvent();
ev.probeType = ValidationProbeEvent.makeProbeType(probeType, isFirstValidation);
@@ -1537,9 +1604,9 @@
}
@VisibleForTesting
- public static class Dependencies {
- public Network getNetwork(NetworkAgentInfo networkAgentInfo) {
- return new OneAddressPerFamilyNetwork(networkAgentInfo.network());
+ static class Dependencies {
+ public Network getPrivateDnsBypassNetwork(Network network) {
+ return new OneAddressPerFamilyNetwork(network);
}
public Random getRandom() {
@@ -1547,6 +1614,13 @@
}
/**
+ * Get the captive portal server HTTP URL that is configured on the device.
+ */
+ public String getCaptivePortalServerHttpUrl(Context context) {
+ return NetworkMonitorUtils.getCaptivePortalServerHttpUrl(context);
+ }
+
+ /**
* Get the value of a global integer setting.
* @param symbol Name of the setting
* @param defaultValue Value to return if the setting is not defined.
@@ -1666,7 +1740,7 @@
boolean result = false;
// Reevaluation will generate traffic. Thus, set a minimal reevaluation timer to limit the
// possible traffic cost in metered network.
- if (mNetworkAgentInfo.networkCapabilities.isMetered()
+ if (mNetworkCapabilities.isMetered()
&& (SystemClock.elapsedRealtime() - getLastProbeTime()
< mDataStallMinEvaluateTime)) {
return false;
diff --git a/tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java
similarity index 68%
rename from tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java
rename to packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java
index 6e07b26..d31fa77 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -16,6 +16,14 @@
package com.android.server.connectivity;
+import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN;
+import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -24,13 +32,21 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.Intent;
+import android.net.CaptivePortal;
import android.net.ConnectivityManager;
+import android.net.INetworkMonitorCallbacks;
+import android.net.InetAddresses;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
@@ -38,9 +54,12 @@
import android.net.NetworkRequest;
import android.net.captiveportal.CaptivePortalProbeResult;
import android.net.metrics.IpConnectivityLog;
+import android.net.util.SharedLog;
import android.net.wifi.WifiManager;
+import android.os.ConditionVariable;
import android.os.Handler;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.provider.Settings;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -50,8 +69,10 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
import java.io.IOException;
import java.net.HttpURLConnection;
@@ -68,21 +89,23 @@
private static final String LOCATION_HEADER = "location";
private @Mock Context mContext;
- private @Mock Handler mHandler;
private @Mock IpConnectivityLog mLogger;
- private @Mock NetworkAgentInfo mAgent;
- private @Mock NetworkAgentInfo mNotMeteredAgent;
+ private @Mock SharedLog mValidationLogger;
private @Mock NetworkInfo mNetworkInfo;
- private @Mock NetworkRequest mRequest;
+ private @Mock ConnectivityManager mCm;
private @Mock TelephonyManager mTelephony;
private @Mock WifiManager mWifi;
- private @Mock Network mNetwork;
private @Mock HttpURLConnection mHttpConnection;
private @Mock HttpURLConnection mHttpsConnection;
private @Mock HttpURLConnection mFallbackConnection;
private @Mock HttpURLConnection mOtherFallbackConnection;
private @Mock Random mRandom;
private @Mock NetworkMonitor.Dependencies mDependencies;
+ private @Mock INetworkMonitorCallbacks mCallbacks;
+ private @Spy Network mNetwork = new Network(TEST_NETID);
+ private NetworkRequest mRequest;
+
+ private static final int TEST_NETID = 4242;
private static final String TEST_HTTP_URL = "http://www.google.com/gen_204";
private static final String TEST_HTTPS_URL = "https://www.google.com/gen_204";
@@ -93,33 +116,37 @@
private static final int RETURN_CODE_DNS_SUCCESS = 0;
private static final int RETURN_CODE_DNS_TIMEOUT = 255;
+ private static final int HANDLER_TIMEOUT_MS = 1000;
+
+ private static final LinkProperties TEST_LINKPROPERTIES = new LinkProperties();
+
+ private static final NetworkCapabilities METERED_CAPABILITIES = new NetworkCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET);
+
+ private static final NetworkCapabilities NOT_METERED_CAPABILITIES = new NetworkCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+
+ private static final NetworkCapabilities NO_INTERNET_CAPABILITIES = new NetworkCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+
@Before
public void setUp() throws IOException {
MockitoAnnotations.initMocks(this);
- mAgent.linkProperties = new LinkProperties();
- mAgent.networkCapabilities = new NetworkCapabilities()
- .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
- mAgent.networkInfo = mNetworkInfo;
-
- mNotMeteredAgent.linkProperties = new LinkProperties();
- mNotMeteredAgent.networkCapabilities = new NetworkCapabilities()
- .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
- mNotMeteredAgent.networkInfo = mNetworkInfo;
-
- when(mAgent.network()).thenReturn(mNetwork);
- when(mDependencies.getNetwork(any())).thenReturn(mNetwork);
+ when(mDependencies.getPrivateDnsBypassNetwork(any())).thenReturn(mNetwork);
when(mDependencies.getRandom()).thenReturn(mRandom);
when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt()))
.thenReturn(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_USE_HTTPS),
anyInt())).thenReturn(1);
- when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTP_URL),
- anyString())).thenReturn(TEST_HTTP_URL);
+ when(mDependencies.getCaptivePortalServerHttpUrl(any())).thenReturn(TEST_HTTP_URL);
when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTPS_URL),
anyString())).thenReturn(TEST_HTTPS_URL);
- when(mNetwork.getPrivateDnsBypassingCopy()).thenReturn(mNetwork);
+ doReturn(mNetwork).when(mNetwork).getPrivateDnsBypassingCopy();
+ when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm);
when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephony);
when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifi);
@@ -129,7 +156,7 @@
setFallbackSpecs(null); // Test with no fallback spec by default
when(mRandom.nextInt()).thenReturn(0);
- when(mNetwork.openConnection(any())).then((invocation) -> {
+ doAnswer((invocation) -> {
URL url = invocation.getArgument(0);
switch(url.toString()) {
case TEST_HTTP_URL:
@@ -144,12 +171,20 @@
fail("URL not mocked: " + url.toString());
return null;
}
- });
+ }).when(mNetwork).openConnection(any());
when(mHttpConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
when(mHttpsConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
- when(mNetwork.getAllByName(any())).thenReturn(new InetAddress[] {
- InetAddress.parseNumericAddress("192.168.0.0")
- });
+ doReturn(new InetAddress[] {
+ InetAddresses.parseNumericAddress("192.168.0.0")
+ }).when(mNetwork).getAllByName(any());
+
+ mRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .build();
+ // Default values. Individual tests can override these.
+ when(mCm.getLinkProperties(any())).thenReturn(TEST_LINKPROPERTIES);
+ when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES);
setMinDataStallEvaluateInterval(500);
setDataStallEvaluationType(1 << DATA_STALL_EVALUATION_TYPE_DNS);
@@ -160,10 +195,10 @@
private class WrappedNetworkMonitor extends NetworkMonitor {
private long mProbeTime = 0;
- WrappedNetworkMonitor(Context context, Handler handler,
- NetworkAgentInfo networkAgentInfo, NetworkRequest defaultRequest,
+ WrappedNetworkMonitor(Context context, Network network, NetworkRequest defaultRequest,
IpConnectivityLog logger, Dependencies deps) {
- super(context, handler, networkAgentInfo, defaultRequest, logger, deps);
+ super(context, mCallbacks, network, defaultRequest, logger,
+ new SharedLog("test_nm"), deps);
}
@Override
@@ -176,19 +211,39 @@
}
}
- WrappedNetworkMonitor makeMeteredWrappedNetworkMonitor() {
- return new WrappedNetworkMonitor(
- mContext, mHandler, mAgent, mRequest, mLogger, mDependencies);
+ private WrappedNetworkMonitor makeMeteredWrappedNetworkMonitor() {
+ final WrappedNetworkMonitor nm = new WrappedNetworkMonitor(
+ mContext, mNetwork, mRequest, mLogger, mDependencies);
+ when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES);
+ nm.start();
+ waitForIdle(nm.getHandler());
+ return nm;
}
- WrappedNetworkMonitor makeNotMeteredWrappedNetworkMonitor() {
- return new WrappedNetworkMonitor(
- mContext, mHandler, mNotMeteredAgent, mRequest, mLogger, mDependencies);
+ private WrappedNetworkMonitor makeNotMeteredWrappedNetworkMonitor() {
+ final WrappedNetworkMonitor nm = new WrappedNetworkMonitor(
+ mContext, mNetwork, mRequest, mLogger, mDependencies);
+ when(mCm.getNetworkCapabilities(any())).thenReturn(NOT_METERED_CAPABILITIES);
+ nm.start();
+ waitForIdle(nm.getHandler());
+ return nm;
}
- NetworkMonitor makeMonitor() {
- return new NetworkMonitor(
- mContext, mHandler, mAgent, mRequest, mLogger, mDependencies);
+ private NetworkMonitor makeMonitor() {
+ final NetworkMonitor nm = new NetworkMonitor(
+ mContext, mCallbacks, mNetwork, mRequest, mLogger, mValidationLogger,
+ mDependencies);
+ nm.start();
+ waitForIdle(nm.getHandler());
+ return nm;
+ }
+
+ private void waitForIdle(Handler handler) {
+ final ConditionVariable cv = new ConditionVariable(false);
+ handler.post(cv::open);
+ if (!cv.block(HANDLER_TIMEOUT_MS)) {
+ fail("Timed out waiting for handler");
+ }
}
@Test
@@ -319,6 +374,15 @@
}
@Test
+ public void testIsCaptivePortal_IgnorePortals() throws IOException {
+ setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE);
+ setSslException(mHttpsConnection);
+ setPortal302(mHttpConnection);
+
+ assertNotPortal(makeMonitor().isCaptivePortal());
+ }
+
+ @Test
public void testIsDataStall_EvaluationDisabled() {
setDataStallEvaluationType(0);
WrappedNetworkMonitor wrappedMonitor = makeMeteredWrappedNetworkMonitor();
@@ -390,6 +454,63 @@
assertFalse(wrappedMonitor.isDataStall());
}
+ @Test
+ public void testBrokenNetworkNotValidated() throws Exception {
+ setSslException(mHttpsConnection);
+ setStatus(mHttpConnection, 500);
+ setStatus(mFallbackConnection, 404);
+ when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES);
+
+ final NetworkMonitor nm = makeMonitor();
+ nm.notifyNetworkConnected();
+
+ verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+ .notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null);
+ }
+
+ @Test
+ public void testNoInternetCapabilityValidated() throws Exception {
+ when(mCm.getNetworkCapabilities(any())).thenReturn(NO_INTERNET_CAPABILITIES);
+
+ final NetworkMonitor nm = makeMonitor();
+ nm.notifyNetworkConnected();
+
+ verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+ .notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null);
+ verify(mNetwork, never()).openConnection(any());
+ }
+
+ @Test
+ public void testLaunchCaptivePortalApp() throws Exception {
+ setSslException(mHttpsConnection);
+ setPortal302(mHttpConnection);
+
+ final NetworkMonitor nm = makeMonitor();
+ nm.notifyNetworkConnected();
+
+ verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+ .showProvisioningNotification(any());
+
+ // Check that startCaptivePortalApp sends the expected intent.
+ nm.launchCaptivePortalApp();
+
+ final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext, timeout(HANDLER_TIMEOUT_MS).times(1))
+ .startActivityAsUser(intentCaptor.capture(), eq(UserHandle.CURRENT));
+ final Intent intent = intentCaptor.getValue();
+ assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, intent.getAction());
+ final Network network = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK);
+ assertEquals(TEST_NETID, network.netId);
+
+ // Have the app report that the captive portal is dismissed, and check that we revalidate.
+ setStatus(mHttpsConnection, 204);
+ setStatus(mHttpConnection, 204);
+ final CaptivePortal captivePortal = intent.getParcelableExtra(EXTRA_CAPTIVE_PORTAL);
+ captivePortal.reportCaptivePortalDismissed();
+ verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+ .notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null);
+ }
+
private void makeDnsTimeoutEvent(WrappedNetworkMonitor wrappedMonitor, int count) {
for (int i = 0; i < count; i++) {
wrappedMonitor.getDnsStallDetector().accumulateConsecutiveDnsTimeoutCount(
@@ -440,6 +561,11 @@
eq(Settings.Global.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS), any())).thenReturn(specs);
}
+ private void setCaptivePortalMode(int mode) {
+ when(mDependencies.getSetting(any(),
+ eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt())).thenReturn(mode);
+ }
+
private void assertPortal(CaptivePortalProbeResult result) {
assertTrue(result.isPortal());
assertFalse(result.isFailed());
@@ -459,12 +585,12 @@
}
private void setSslException(HttpURLConnection connection) throws IOException {
- when(connection.getResponseCode()).thenThrow(new SSLHandshakeException("Invalid cert"));
+ doThrow(new SSLHandshakeException("Invalid cert")).when(connection).getResponseCode();
}
private void set302(HttpURLConnection connection, String location) throws IOException {
setStatus(connection, 302);
- when(connection.getHeaderField(LOCATION_HEADER)).thenReturn(location);
+ doReturn(location).when(connection).getHeaderField(LOCATION_HEADER);
}
private void setPortal302(HttpURLConnection connection) throws IOException {
@@ -472,7 +598,7 @@
}
private void setStatus(HttpURLConnection connection, int status) throws IOException {
- when(connection.getResponseCode()).thenReturn(status);
+ doReturn(status).when(connection).getResponseCode();
}
}
diff --git a/packages/SystemUI/res/drawable/biometric_dialog_bg.xml b/packages/SystemUI/res/drawable/biometric_dialog_bg.xml
index d041556..0c6d57d 100644
--- a/packages/SystemUI/res/drawable/biometric_dialog_bg.xml
+++ b/packages/SystemUI/res/drawable/biometric_dialog_bg.xml
@@ -18,7 +18,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?android:attr/colorBackgroundFloating" />
- <corners android:radius="1dp"
+ <corners
android:topLeftRadius="@dimen/biometric_dialog_corner_size"
android:topRightRadius="@dimen/biometric_dialog_corner_size"
android:bottomLeftRadius="@dimen/biometric_dialog_corner_size"
diff --git a/packages/SystemUI/res/drawable/bubble_expanded_header_bg.xml b/packages/SystemUI/res/drawable/bubble_expanded_header_bg.xml
new file mode 100644
index 0000000..26bf981
--- /dev/null
+++ b/packages/SystemUI/res/drawable/bubble_expanded_header_bg.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape android:shape="rectangle">
+ <solid android:color="?android:attr/colorBackgroundFloating"/>
+ <corners
+ android:topLeftRadius="@dimen/corner_size"
+ android:topRightRadius="@dimen/corner_size"/>
+ </shape>
+ </item>
+ <item android:gravity="bottom">
+ <shape>
+ <size android:height="1dp"/>
+ <solid android:color="?android:attr/textColorSecondary" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/battery_percentage_view.xml b/packages/SystemUI/res/layout/battery_percentage_view.xml
index e52aa14..b9b1bb1 100644
--- a/packages/SystemUI/res/layout/battery_percentage_view.xml
+++ b/packages/SystemUI/res/layout/battery_percentage_view.xml
@@ -22,7 +22,7 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:singleLine="true"
- android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
+ android:textAppearance="@style/TextAppearance.StatusBar.Clock"
android:textColor="?android:attr/textColorPrimary"
android:gravity="center_vertical|start"
android:paddingStart="@dimen/battery_level_padding_start"
diff --git a/packages/SystemUI/res/layout/bubble_expanded_view.xml b/packages/SystemUI/res/layout/bubble_expanded_view.xml
index b2307e7..1aeb52c 100644
--- a/packages/SystemUI/res/layout/bubble_expanded_view.xml
+++ b/packages/SystemUI/res/layout/bubble_expanded_view.xml
@@ -20,11 +20,23 @@
android:layout_width="match_parent"
android:id="@+id/bubble_expanded_view">
- <!-- TODO: header -->
-
<View
android:id="@+id/pointer_view"
android:layout_width="@dimen/bubble_pointer_width"
android:layout_height="@dimen/bubble_pointer_height"
/>
+
+ <TextView
+ android:id="@+id/bubble_content_header"
+ android:background="@drawable/bubble_expanded_header_bg"
+ android:textAppearance="@*android:style/TextAppearance.Material.Title"
+ android:textSize="18sp"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/bubble_expanded_header_height"
+ android:gravity="start|center_vertical"
+ android:singleLine="true"
+ android:paddingLeft="@dimen/bubble_expanded_header_horizontal_padding"
+ android:paddingRight="@dimen/bubble_expanded_header_horizontal_padding"
+ />
+
</com.android.systemui.bubbles.BubbleExpandedViewContainer>
diff --git a/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml
index 307b538..5bcc1b3 100644
--- a/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml
+++ b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml
@@ -39,7 +39,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/monitoring_title_device_owned"
- style="@android:style/TextAppearance.Material.Title"
+ style="@style/TextAppearance.DeviceManagementDialog.Title"
android:textColor="?android:attr/textColorPrimary"
android:paddingBottom="@dimen/qs_footer_dialog_subtitle_padding"
/>
@@ -64,7 +64,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/monitoring_subtitle_ca_certificate"
- style="@android:style/TextAppearance.Material.Title"
+ style="@style/TextAppearance.DeviceManagementDialog.Title"
android:textColor="?android:attr/textColorPrimary"
android:paddingBottom="@dimen/qs_footer_dialog_subtitle_padding"
/>
@@ -89,7 +89,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/monitoring_subtitle_network_logging"
- style="@android:style/TextAppearance.Material.Title"
+ style="@style/TextAppearance.DeviceManagementDialog.Title"
android:textColor="?android:attr/textColorPrimary"
android:paddingBottom="@dimen/qs_footer_dialog_subtitle_padding"
/>
@@ -114,7 +114,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/monitoring_subtitle_vpn"
- style="@android:style/TextAppearance.Material.Title"
+ style="@style/TextAppearance.DeviceManagementDialog.Title"
android:textColor="?android:attr/textColorPrimary"
android:paddingBottom="@dimen/qs_footer_dialog_subtitle_padding"
/>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
index 22b8d2f..4b65b6a 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
@@ -24,6 +24,7 @@
android:clipToPadding="false"
android:gravity="center"
android:orientation="horizontal"
+ android:clickable="true"
android:paddingStart="@dimen/status_bar_padding_start"
android:paddingEnd="@dimen/status_bar_padding_end" >
diff --git a/packages/SystemUI/res/layout/signal_cluster_view.xml b/packages/SystemUI/res/layout/signal_cluster_view.xml
deleted file mode 100644
index cfa372b..0000000
--- a/packages/SystemUI/res/layout/signal_cluster_view.xml
+++ /dev/null
@@ -1,129 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2011, 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.
-*/
--->
-<!-- extends LinearLayout -->
-<com.android.systemui.statusbar.SignalClusterView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/signal_cluster"
- android:layout_height="match_parent"
- android:layout_width="wrap_content"
- android:gravity="center_vertical"
- android:orientation="horizontal"
- android:paddingEnd="@dimen/signal_cluster_battery_padding"
- >
- <ImageView
- android:id="@+id/vpn"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:paddingEnd="6dp"
- android:src="@drawable/stat_sys_vpn_ic"
- android:tint="@color/background_protect_secondary"
- android:contentDescription="@string/accessibility_vpn_on"
- />
- <FrameLayout
- android:id="@+id/ethernet_combo"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- >
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:theme="?attr/lightIconTheme"
- android:id="@+id/ethernet"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- />
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:theme="?attr/darkIconTheme"
- android:id="@+id/ethernet_dark"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:alpha="0.0"
- />
- </FrameLayout>
- <FrameLayout
- android:layout_height="17dp"
- android:layout_width="wrap_content">
- <ImageView
- android:id="@+id/wifi_in"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:src="@drawable/ic_activity_down"
- android:visibility="gone"
- android:paddingEnd="2dp"
- />
- <ImageView
- android:id="@+id/wifi_out"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:src="@drawable/ic_activity_up"
- android:paddingEnd="2dp"
- android:visibility="gone"
- />
- </FrameLayout>
- <FrameLayout
- android:id="@+id/wifi_combo"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- >
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:theme="?attr/lightIconTheme"
- android:id="@+id/wifi_signal"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- />
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:theme="?attr/darkIconTheme"
- android:id="@+id/wifi_signal_dark"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:alpha="0.0"
- />
- <ImageView
- android:id="@+id/wifi_inout"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- />
- </FrameLayout>
- <View
- android:id="@+id/wifi_signal_spacer"
- android:layout_width="@dimen/status_bar_wifi_signal_spacer_width"
- android:layout_height="4dp"
- android:visibility="gone"
- />
- <ViewStub
- android:id="@+id/connected_device_signals_stub"
- android:layout="@layout/connected_device_signal"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
- <LinearLayout
- android:id="@+id/mobile_signal_group"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- >
- </LinearLayout>
- <View
- android:id="@+id/wifi_airplane_spacer"
- android:layout_width="@dimen/status_bar_airplane_spacer_width"
- android:layout_height="4dp"
- android:visibility="gone"
- />
- <ImageView
- android:id="@+id/airplane"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- />
-</com.android.systemui.statusbar.SignalClusterView>
diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml
index 34c208a..02062bb 100644
--- a/packages/SystemUI/res/layout/super_status_bar.xml
+++ b/packages/SystemUI/res/layout/super_status_bar.xml
@@ -43,6 +43,14 @@
android:visibility="invisible" />
</com.android.systemui.statusbar.BackDropView>
+ <com.android.systemui.wallpaper.AodMaskView
+ android:id="@+id/aod_mask"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:importantForAccessibility="no"
+ android:visibility="invisible"
+ sysui:ignoreRightInset="true" />
+
<com.android.systemui.statusbar.ScrimView
android:id="@+id/scrim_behind"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 06df0e7..10e5f74 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -997,4 +997,8 @@
<dimen name="bubble_pointer_width">6dp</dimen>
<!-- Extra padding around the dismiss target for bubbles -->
<dimen name="bubble_dismiss_slop">16dp</dimen>
+ <!-- Height of the header within the expanded view. -->
+ <dimen name="bubble_expanded_header_height">48dp</dimen>
+ <!-- Left and right padding applied to the header. -->
+ <dimen name="bubble_expanded_header_horizontal_padding">24dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index dac20b5..633f868 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -102,12 +102,17 @@
<item type="id" name="action_snooze_assistant_suggestion_1"/>
<item type="id" name="action_snooze"/>
- <!-- For StatusBarIconContainer to tag its icon views -->
+ <!-- For StatusIconContainer to tag its icon views -->
<item type="id" name="status_bar_view_state_tag" />
<item type="id" name="display_cutout" />
<!-- Optional cancel button on Keyguard -->
<item type="id" name="cancel_button"/>
+
+ <!-- AodMaskView transition tag -->
+ <item type="id" name="aod_mask_transition_progress_tag" />
+ <item type="id" name="aod_mask_transition_progress_end_tag" />
+ <item type="id" name="aod_mask_transition_progress_start_tag" />
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 8a5a69b..22a0b36 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -125,7 +125,7 @@
<style name="TextAppearance.StatusBar.Clock" parent="@*android:style/TextAppearance.StatusBar.Icon">
<item name="android:textSize">@dimen/status_bar_clock_size</item>
- <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
<item name="android:textColor">@color/status_bar_clock_color</item>
</style>
@@ -265,6 +265,10 @@
<item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
</style>
+ <style name="TextAppearance.DeviceManagementDialog.Title" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle">
+ <item name="android:gravity">center</item>
+ </style>
+
<style name="BaseBrightnessDialogContainer" parent="@style/Theme.SystemUI">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index f3bdbae..078947c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -76,4 +76,10 @@
*/
float getWindowCornerRadius() = 10;
+ /**
+ * If device supports live rounded corners on windows.
+ * This might be turned off for performance reasons
+ */
+ boolean supportsRoundedCornersOnWindows() = 11;
+
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
index c0a1d89..e6acfbe 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
@@ -16,14 +16,10 @@
package com.android.systemui.shared.system;
+import android.graphics.HardwareRenderer;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.view.Surface;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
-import android.view.SyncRtSurfaceTransactionApplier;
-import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
-import android.view.ThreadedRenderer;
import android.view.View;
import android.view.ViewRootImpl;
@@ -31,20 +27,21 @@
/**
* Helper class to apply surface transactions in sync with RenderThread.
+ *
+ * NOTE: This is a modification of {@link android.view.SyncRtSurfaceTransactionApplier}, we can't
+ * currently reference that class from the shared lib as it is hidden.
*/
public class SyncRtSurfaceTransactionApplierCompat {
- private final SyncRtSurfaceTransactionApplier mApplier;
+ private final Surface mTargetSurface;
+ private final ViewRootImpl mTargetViewRootImpl;
/**
* @param targetView The view in the surface that acts as synchronization anchor.
*/
public SyncRtSurfaceTransactionApplierCompat(View targetView) {
- mApplier = new SyncRtSurfaceTransactionApplier(targetView);
- }
-
- private SyncRtSurfaceTransactionApplierCompat(SyncRtSurfaceTransactionApplier applier) {
- mApplier = applier;
+ mTargetViewRootImpl = targetView != null ? targetView.getViewRootImpl() : null;
+ mTargetSurface = mTargetViewRootImpl != null ? mTargetViewRootImpl.mSurface : null;
}
/**
@@ -53,38 +50,74 @@
* @param params The surface parameters to apply. DO NOT MODIFY the list after passing into
* this method to avoid synchronization issues.
*/
- public void scheduleApply(final SurfaceParams... params) {
- mApplier.scheduleApply(convert(params));
- }
-
- private SyncRtSurfaceTransactionApplier.SurfaceParams[] convert(SurfaceParams[] params) {
- SyncRtSurfaceTransactionApplier.SurfaceParams[] result =
- new SyncRtSurfaceTransactionApplier.SurfaceParams[params.length];
- for (int i = 0; i < params.length; i++) {
- result[i] = params[i].mParams;
+ public void scheduleApply(final SyncRtSurfaceTransactionApplierCompat.SurfaceParams... params) {
+ if (mTargetViewRootImpl == null) {
+ return;
}
- return result;
+ mTargetViewRootImpl.registerRtFrameCallback(new HardwareRenderer.FrameDrawingCallback() {
+ @Override
+ public void onFrameDraw(long frame) {
+ if (mTargetSurface == null || !mTargetSurface.isValid()) {
+ return;
+ }
+ TransactionCompat t = new TransactionCompat();
+ for (int i = params.length - 1; i >= 0; i--) {
+ SyncRtSurfaceTransactionApplierCompat.SurfaceParams surfaceParams =
+ params[i];
+ SurfaceControlCompat surface = surfaceParams.surface;
+ t.deferTransactionUntil(surface, mTargetSurface, frame);
+ applyParams(t, surfaceParams);
+ }
+ t.setEarlyWakeup();
+ t.apply();
+ }
+ });
+
+ // Make sure a frame gets scheduled.
+ mTargetViewRootImpl.getView().invalidate();
}
- public static void applyParams(TransactionCompat t, SurfaceParams params) {
- SyncRtSurfaceTransactionApplier.applyParams(t.mTransaction, params.mParams, t.mTmpValues);
+ public static void applyParams(TransactionCompat t,
+ SyncRtSurfaceTransactionApplierCompat.SurfaceParams params) {
+ t.setMatrix(params.surface, params.matrix);
+ t.setWindowCrop(params.surface, params.windowCrop);
+ t.setAlpha(params.surface, params.alpha);
+ t.setLayer(params.surface, params.layer);
+ t.setCornerRadius(params.surface, params.cornerRadius);
+ t.show(params.surface);
}
+ /**
+ * Creates an instance of SyncRtSurfaceTransactionApplier, deferring until the target view is
+ * attached if necessary.
+ */
public static void create(final View targetView,
final Consumer<SyncRtSurfaceTransactionApplierCompat> callback) {
- SyncRtSurfaceTransactionApplier.create(targetView,
- new Consumer<SyncRtSurfaceTransactionApplier>() {
- @Override
- public void accept(SyncRtSurfaceTransactionApplier applier) {
- callback.accept(new SyncRtSurfaceTransactionApplierCompat(applier));
- }
- });
+ if (targetView == null) {
+ // No target view, no applier
+ callback.accept(null);
+ } else if (targetView.getViewRootImpl() != null) {
+ // Already attached, we're good to go
+ callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView));
+ } else {
+ // Haven't been attached before we can get the view root
+ targetView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ targetView.removeOnAttachStateChangeListener(this);
+ callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView));
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ // Do nothing
+ }
+ });
+ }
}
public static class SurfaceParams {
- private final SyncRtSurfaceTransactionApplier.SurfaceParams mParams;
-
/**
* Constructs surface parameters to be applied when the current view state gets pushed to
* RenderThread.
@@ -96,8 +129,19 @@
*/
public SurfaceParams(SurfaceControlCompat surface, float alpha, Matrix matrix,
Rect windowCrop, int layer, float cornerRadius) {
- mParams = new SyncRtSurfaceTransactionApplier.SurfaceParams(surface.mSurfaceControl,
- alpha, matrix, windowCrop, layer, cornerRadius);
+ this.surface = surface;
+ this.alpha = alpha;
+ this.matrix = new Matrix(matrix);
+ this.windowCrop = new Rect(windowCrop);
+ this.layer = layer;
+ this.cornerRadius = cornerRadius;
}
+
+ public final SurfaceControlCompat surface;
+ public final float alpha;
+ final float cornerRadius;
+ public final Matrix matrix;
+ public final Rect windowCrop;
+ public final int layer;
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
index 2aba3fa..af32f48 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
@@ -82,6 +82,11 @@
return this;
}
+ public TransactionCompat setCornerRadius(SurfaceControlCompat surfaceControl, float radius) {
+ mTransaction.setCornerRadius(surfaceControl.mSurfaceControl, radius);
+ return this;
+ }
+
public TransactionCompat deferTransactionUntil(SurfaceControlCompat surfaceControl,
IBinder handle, long frameNumber) {
mTransaction.deferTransactionUntil(surfaceControl.mSurfaceControl, handle, frameNumber);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 27d624a..1c8a672 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -14,13 +14,13 @@
import com.android.systemui.Dependency;
import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.policy.ExtensionController;
+import com.android.systemui.statusbar.policy.ExtensionController.Extension;
-import java.util.Objects;
import java.util.TimeZone;
+import java.util.function.Consumer;
/**
* Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
@@ -47,43 +47,15 @@
* or not to show it below the alternate clock.
*/
private View mKeyguardStatusArea;
+ /**
+ * Used to select between plugin or default implementations of ClockPlugin interface.
+ */
+ private Extension<ClockPlugin> mClockExtension;
+ /**
+ * Consumer that accepts the a new ClockPlugin implementation when the Extension reloads.
+ */
+ private final Consumer<ClockPlugin> mClockPluginConsumer = plugin -> setClockPlugin(plugin);
- private final PluginListener<ClockPlugin> mClockPluginListener =
- new PluginListener<ClockPlugin>() {
- @Override
- public void onPluginConnected(ClockPlugin plugin, Context pluginContext) {
- disconnectPlugin();
- View smallClockView = plugin.getView();
- if (smallClockView != null) {
- // For now, assume that the most recently connected plugin is the
- // selected clock face. In the future, the user should be able to
- // pick a clock face from the available plugins.
- mSmallClockFrame.addView(smallClockView, -1,
- new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
- initPluginParams();
- mClockView.setVisibility(View.GONE);
- }
- View bigClockView = plugin.getBigClockView();
- if (bigClockView != null && mBigClockContainer != null) {
- mBigClockContainer.addView(bigClockView);
- mBigClockContainer.setVisibility(View.VISIBLE);
- }
- if (!plugin.shouldShowStatusArea()) {
- mKeyguardStatusArea.setVisibility(View.GONE);
- }
- mClockPlugin = plugin;
- }
-
- @Override
- public void onPluginDisconnected(ClockPlugin plugin) {
- if (Objects.equals(plugin, mClockPlugin)) {
- disconnectPlugin();
- mClockView.setVisibility(View.VISIBLE);
- mKeyguardStatusArea.setVisibility(View.VISIBLE);
- }
- }
- };
private final StatusBarStateController.StateListener mStateListener =
new StatusBarStateController.StateListener() {
@Override
@@ -122,18 +94,61 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- Dependency.get(PluginManager.class).addPluginListener(mClockPluginListener,
- ClockPlugin.class);
+ mClockExtension = Dependency.get(ExtensionController.class).newExtension(ClockPlugin.class)
+ .withPlugin(ClockPlugin.class)
+ .withCallback(mClockPluginConsumer)
+ .build();
Dependency.get(StatusBarStateController.class).addCallback(mStateListener);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- Dependency.get(PluginManager.class).removePluginListener(mClockPluginListener);
+ mClockExtension.destroy();
Dependency.get(StatusBarStateController.class).removeCallback(mStateListener);
}
+ private void setClockPlugin(ClockPlugin plugin) {
+ // Disconnect from existing plugin.
+ if (mClockPlugin != null) {
+ View smallClockView = mClockPlugin.getView();
+ if (smallClockView != null && smallClockView.getParent() == mSmallClockFrame) {
+ mSmallClockFrame.removeView(smallClockView);
+ }
+ if (mBigClockContainer != null) {
+ mBigClockContainer.removeAllViews();
+ mBigClockContainer.setVisibility(View.GONE);
+ }
+ mClockPlugin = null;
+ }
+ if (plugin == null) {
+ mClockView.setVisibility(View.VISIBLE);
+ mKeyguardStatusArea.setVisibility(View.VISIBLE);
+ return;
+ }
+ // Attach small and big clock views to hierarchy.
+ View smallClockView = plugin.getView();
+ if (smallClockView != null) {
+ mSmallClockFrame.addView(smallClockView, -1,
+ new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ mClockView.setVisibility(View.GONE);
+ }
+ View bigClockView = plugin.getBigClockView();
+ if (bigClockView != null && mBigClockContainer != null) {
+ mBigClockContainer.addView(bigClockView);
+ mBigClockContainer.setVisibility(View.VISIBLE);
+ }
+ // Hide default clock.
+ if (!plugin.shouldShowStatusArea()) {
+ mKeyguardStatusArea.setVisibility(View.GONE);
+ }
+ // Initialize plugin parameters.
+ mClockPlugin = plugin;
+ mClockPlugin.setStyle(getPaint().getStyle());
+ mClockPlugin.setTextColor(getCurrentTextColor());
+ }
+
/**
* Set container for big clock face appearing behind NSSL and KeyguardStatusView.
*/
@@ -232,33 +247,9 @@
}
}
- /**
- * When plugin changes, set all kept parameters into newer plugin.
- */
- private void initPluginParams() {
- if (mClockPlugin != null) {
- mClockPlugin.setStyle(getPaint().getStyle());
- mClockPlugin.setTextColor(getCurrentTextColor());
- }
- }
-
- private void disconnectPlugin() {
- if (mClockPlugin != null) {
- View smallClockView = mClockPlugin.getView();
- if (smallClockView != null) {
- mSmallClockFrame.removeView(smallClockView);
- }
- if (mBigClockContainer != null) {
- mBigClockContainer.removeAllViews();
- mBigClockContainer.setVisibility(View.GONE);
- }
- mClockPlugin = null;
- }
- }
-
@VisibleForTesting (otherwise = VisibleForTesting.NONE)
- PluginListener getClockPluginListener() {
- return mClockPluginListener;
+ Consumer<ClockPlugin> getClockPluginConsumer() {
+ return mClockPluginConsumer;
}
@VisibleForTesting (otherwise = VisibleForTesting.NONE)
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
index 9f363f6..7e5b426 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
@@ -25,12 +25,20 @@
private int mUid;
private String mPackageName;
private long mTimeStarted;
+ private String mState;
public AppOpItem(int code, int uid, String packageName, long timeStarted) {
this.mCode = code;
this.mUid = uid;
this.mPackageName = packageName;
this.mTimeStarted = timeStarted;
+ mState = new StringBuilder()
+ .append("AppOpItem(")
+ .append("Op code=").append(code).append(", ")
+ .append("UID=").append(uid).append(", ")
+ .append("Package name=").append(packageName)
+ .append(")")
+ .toString();
}
public int getCode() {
@@ -48,4 +56,9 @@
public long getTimeStarted() {
return mTimeStarted;
}
+
+ @Override
+ public String toString() {
+ return mState;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index e3bcb37..c013df3 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -29,7 +29,10 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dumpable;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -47,7 +50,7 @@
@Singleton
public class AppOpsControllerImpl implements AppOpsController,
AppOpsManager.OnOpActiveChangedListener,
- AppOpsManager.OnOpNotedListener {
+ AppOpsManager.OnOpNotedListener, Dumpable {
private static final long NOTED_OP_TIME_DELAY_MS = 5000;
private static final String TAG = "AppOpsControllerImpl";
@@ -271,6 +274,22 @@
}
}
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("AppOpsController state:");
+ pw.println(" Active Items:");
+ for (int i = 0; i < mActiveItems.size(); i++) {
+ final AppOpItem item = mActiveItems.get(i);
+ pw.print(" "); pw.println(item.toString());
+ }
+ pw.println(" Noted Items:");
+ for (int i = 0; i < mNotedItems.size(); i++) {
+ final AppOpItem item = mNotedItems.get(i);
+ pw.print(" "); pw.println(item.toString());
+ }
+
+ }
+
protected final class H extends Handler {
H(Looper looper) {
super(looper);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
index 3167b9e..016b8fe 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
@@ -33,9 +33,6 @@
import com.android.systemui.SystemUI;
import com.android.systemui.statusbar.CommandQueue;
-import java.util.HashMap;
-import java.util.Map;
-
/**
* Receives messages sent from AuthenticationClient and shows the appropriate biometric UI (e.g.
* BiometricDialogView).
@@ -52,10 +49,8 @@
private static final int MSG_BUTTON_NEGATIVE = 6;
private static final int MSG_USER_CANCELED = 7;
private static final int MSG_BUTTON_POSITIVE = 8;
- private static final int MSG_BIOMETRIC_SHOW_TRY_AGAIN = 9;
- private static final int MSG_TRY_AGAIN_PRESSED = 10;
+ private static final int MSG_TRY_AGAIN_PRESSED = 9;
- private Map<Integer, BiometricDialogView> mDialogs; // BiometricAuthenticator type, view
private SomeArgs mCurrentDialogArgs;
private BiometricDialogView mCurrentDialog;
private WindowManager mWindowManager;
@@ -63,21 +58,22 @@
private boolean mDialogShowing;
private Callback mCallback = new Callback();
- private boolean mTryAgainShowing; // No good place to save state before config change :/
- private boolean mConfirmShowing; // No good place to save state before config change :/
-
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
case MSG_SHOW_DIALOG:
- handleShowDialog((SomeArgs) msg.obj, false /* skipAnimation */);
+ handleShowDialog((SomeArgs) msg.obj, false /* skipAnimation */,
+ null /* savedState */);
break;
case MSG_BIOMETRIC_AUTHENTICATED:
- handleBiometricAuthenticated();
+ handleBiometricAuthenticated((boolean) msg.obj);
break;
case MSG_BIOMETRIC_HELP:
- handleBiometricHelp((String) msg.obj);
+ SomeArgs args = (SomeArgs) msg.obj;
+ handleBiometricHelp((String) args.arg1 /* message */,
+ (boolean) args.arg2 /* requireTryAgain */);
+ args.recycle();
break;
case MSG_BIOMETRIC_ERROR:
handleBiometricError((String) msg.obj);
@@ -94,9 +90,6 @@
case MSG_BUTTON_POSITIVE:
handleButtonPositive();
break;
- case MSG_BIOMETRIC_SHOW_TRY_AGAIN:
- handleShowTryAgain();
- break;
case MSG_TRY_AGAIN_PRESSED:
handleTryAgainPressed();
break;
@@ -137,30 +130,22 @@
@Override
public void start() {
- createDialogs();
-
- if (!mDialogs.isEmpty()) {
+ final PackageManager pm = mContext.getPackageManager();
+ if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
+ || pm.hasSystemFeature(PackageManager.FEATURE_FACE)
+ || pm.hasSystemFeature(PackageManager.FEATURE_IRIS)) {
getComponent(CommandQueue.class).addCallback(this);
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
}
}
- private void createDialogs() {
- final PackageManager pm = mContext.getPackageManager();
- mDialogs = new HashMap<>();
- if (pm.hasSystemFeature(PackageManager.FEATURE_FACE)) {
- mDialogs.put(BiometricAuthenticator.TYPE_FACE, new FaceDialogView(mContext, mCallback));
- }
- if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
- mDialogs.put(BiometricAuthenticator.TYPE_FINGERPRINT,
- new FingerprintDialogView(mContext, mCallback));
- }
- }
-
@Override
public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
int type, boolean requireConfirmation, int userId) {
- if (DEBUG) Log.d(TAG, "showBiometricDialog, type: " + type);
+ if (DEBUG) {
+ Log.d(TAG, "showBiometricDialog, type: " + type
+ + ", requireConfirmation: " + requireConfirmation);
+ }
// Remove these messages as they are part of the previous client
mHandler.removeMessages(MSG_BIOMETRIC_ERROR);
mHandler.removeMessages(MSG_BIOMETRIC_HELP);
@@ -176,15 +161,18 @@
}
@Override
- public void onBiometricAuthenticated() {
- if (DEBUG) Log.d(TAG, "onBiometricAuthenticated");
- mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget();
+ public void onBiometricAuthenticated(boolean authenticated) {
+ if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated);
+ mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, authenticated).sendToTarget();
}
@Override
public void onBiometricHelp(String message) {
if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message);
- mHandler.obtainMessage(MSG_BIOMETRIC_HELP, message).sendToTarget();
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = message;
+ args.arg2 = false; // requireTryAgain
+ mHandler.obtainMessage(MSG_BIOMETRIC_HELP, args).sendToTarget();
}
@Override
@@ -199,16 +187,21 @@
mHandler.obtainMessage(MSG_HIDE_DIALOG, false /* userCanceled */).sendToTarget();
}
- @Override
- public void showBiometricTryAgain() {
- if (DEBUG) Log.d(TAG, "showBiometricTryAgain");
- mHandler.obtainMessage(MSG_BIOMETRIC_SHOW_TRY_AGAIN).sendToTarget();
- }
-
- private void handleShowDialog(SomeArgs args, boolean skipAnimation) {
+ private void handleShowDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
mCurrentDialogArgs = args;
final int type = args.argi1;
- mCurrentDialog = mDialogs.get(type);
+
+ if (type == BiometricAuthenticator.TYPE_FINGERPRINT) {
+ mCurrentDialog = new FingerprintDialogView(mContext, mCallback);
+ } else if (type == BiometricAuthenticator.TYPE_FACE) {
+ mCurrentDialog = new FaceDialogView(mContext, mCallback);
+ } else {
+ Log.e(TAG, "Unsupported type: " + type);
+ }
+
+ if (savedState != null) {
+ mCurrentDialog.restoreState(savedState);
+ }
if (DEBUG) Log.d(TAG, "handleShowDialog, isAnimatingAway: "
+ mCurrentDialog.isAnimatingAway() + " type: " + type);
@@ -224,32 +217,36 @@
mCurrentDialog.setRequireConfirmation((boolean) args.arg3);
mCurrentDialog.setUserId(args.argi2);
mCurrentDialog.setSkipIntro(skipAnimation);
- mCurrentDialog.setPendingTryAgain(mTryAgainShowing);
- mCurrentDialog.setPendingConfirm(mConfirmShowing);
mWindowManager.addView(mCurrentDialog, mCurrentDialog.getLayoutParams());
mDialogShowing = true;
}
- private void handleBiometricAuthenticated() {
- if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated");
+ private void handleBiometricAuthenticated(boolean authenticated) {
+ if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated: " + authenticated);
- mCurrentDialog.announceForAccessibility(
- mContext.getResources()
- .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId()));
- if (mCurrentDialog.requiresConfirmation()) {
- mConfirmShowing = true;
- mCurrentDialog.showConfirmationButton(true /* show */);
+ if (authenticated) {
+ mCurrentDialog.announceForAccessibility(
+ mContext.getResources()
+ .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId()));
+ if (mCurrentDialog.requiresConfirmation()) {
+ mCurrentDialog.showConfirmationButton(true /* show */);
+ } else {
+ mCurrentDialog.updateState(BiometricDialogView.STATE_AUTHENTICATED);
+ mHandler.postDelayed(() -> {
+ handleHideDialog(false /* userCanceled */);
+ }, mCurrentDialog.getDelayAfterAuthenticatedDurationMs());
+ }
} else {
- mCurrentDialog.updateState(BiometricDialogView.STATE_AUTHENTICATED);
- mHandler.postDelayed(() -> {
- handleHideDialog(false /* userCanceled */);
- }, mCurrentDialog.getDelayAfterAuthenticatedDurationMs());
+ handleBiometricHelp(mContext.getResources()
+ .getString(com.android.internal.R.string.biometric_not_recognized),
+ true /* requireTryAgain */);
+ mCurrentDialog.showTryAgainButton(true /* show */);
}
}
- private void handleBiometricHelp(String message) {
+ private void handleBiometricHelp(String message, boolean requireTryAgain) {
if (DEBUG) Log.d(TAG, "handleBiometricHelp: " + message);
- mCurrentDialog.showHelpMessage(message);
+ mCurrentDialog.showHelpMessage(message, requireTryAgain);
}
private void handleBiometricError(String error) {
@@ -258,7 +255,6 @@
if (DEBUG) Log.d(TAG, "Dialog already dismissed");
return;
}
- mTryAgainShowing = false;
mCurrentDialog.showErrorMessage(error);
}
@@ -279,8 +275,6 @@
}
mReceiver = null;
mDialogShowing = false;
- mConfirmShowing = false;
- mTryAgainShowing = false;
mCurrentDialog.startDismiss();
}
@@ -294,7 +288,6 @@
} catch (RemoteException e) {
Log.e(TAG, "Remote exception when handling negative button", e);
}
- mTryAgainShowing = false;
handleHideDialog(false /* userCanceled */);
}
@@ -308,25 +301,16 @@
} catch (RemoteException e) {
Log.e(TAG, "Remote exception when handling positive button", e);
}
- mConfirmShowing = false;
handleHideDialog(false /* userCanceled */);
}
private void handleUserCanceled() {
- mTryAgainShowing = false;
- mConfirmShowing = false;
handleHideDialog(true /* userCanceled */);
}
- private void handleShowTryAgain() {
- mCurrentDialog.showTryAgainButton(true /* show */);
- mTryAgainShowing = true;
- }
-
private void handleTryAgainPressed() {
try {
mCurrentDialog.clearTemporaryMessage();
- mTryAgainShowing = false;
mReceiver.onTryAgainPressed();
} catch (RemoteException e) {
Log.e(TAG, "RemoteException when handling try again", e);
@@ -337,13 +321,20 @@
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
final boolean wasShowing = mDialogShowing;
+
+ // Save the state of the current dialog (buttons showing, etc)
+ final Bundle savedState = new Bundle();
+ if (mCurrentDialog != null) {
+ mCurrentDialog.onSaveState(savedState);
+ }
+
if (mDialogShowing) {
mCurrentDialog.forceRemove();
mDialogShowing = false;
}
- createDialogs();
+
if (wasShowing) {
- handleShowDialog(mCurrentDialogArgs, true /* skipAnimation */);
+ handleShowDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
index 9934bfd..b8c69c80 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
@@ -56,12 +56,15 @@
private static final String TAG = "BiometricDialogView";
+ private static final String KEY_TRY_AGAIN_VISIBILITY = "key_try_again_visibility";
+ private static final String KEY_CONFIRM_VISIBILITY = "key_confirm_visibility";
+
private static final int ANIMATION_DURATION_SHOW = 250; // ms
private static final int ANIMATION_DURATION_AWAY = 350; // ms
private static final int MSG_CLEAR_MESSAGE = 1;
- protected static final int STATE_NONE = 0;
+ protected static final int STATE_IDLE = 0;
protected static final int STATE_AUTHENTICATING = 1;
protected static final int STATE_ERROR = 2;
protected static final int STATE_PENDING_CONFIRMATION = 3;
@@ -78,12 +81,19 @@
private final float mDialogWidth;
private final DialogViewCallback mCallback;
- private ViewGroup mLayout;
- private final Button mPositiveButton;
- private final Button mNegativeButton;
- private final TextView mErrorText;
+ protected final ViewGroup mLayout;
+ protected final LinearLayout mDialog;
+ protected final TextView mTitleText;
+ protected final TextView mSubtitleText;
+ protected final TextView mDescriptionText;
+ protected final ImageView mBiometricIcon;
+ protected final TextView mErrorText;
+ protected final Button mPositiveButton;
+ protected final Button mNegativeButton;
+ protected final Button mTryAgainButton;
+
private Bundle mBundle;
- private final LinearLayout mDialog;
+
private int mLastState;
private boolean mAnimatingAway;
private boolean mWasForceRemoved;
@@ -91,15 +101,13 @@
protected boolean mRequireConfirmation;
private int mUserId; // used to determine if we should show work background
- private boolean mPendingShowTryAgain;
- private boolean mPendingShowConfirm;
-
protected abstract int getHintStringResourceId();
protected abstract int getAuthenticatedAccessibilityResourceId();
protected abstract int getIconDescriptionResourceId();
protected abstract Drawable getAnimationForTransition(int oldState, int newState);
protected abstract boolean shouldAnimateForTransition(int oldState, int newState);
protected abstract int getDelayAfterAuthenticatedDurationMs();
+ protected abstract boolean shouldGrayAreaDismissDialog();
private final Runnable mShowAnimationRunnable = new Runnable() {
@Override
@@ -124,7 +132,7 @@
public void handleMessage(Message msg) {
switch(msg.what) {
case MSG_CLEAR_MESSAGE:
- handleClearMessage();
+ handleClearMessage((boolean) msg.obj /* requireTryAgain */);
break;
default:
Log.e(TAG, "Unhandled message: " + msg.what);
@@ -158,10 +166,6 @@
mLayout = (ViewGroup) factory.inflate(R.layout.biometric_dialog, this, false);
addView(mLayout);
- mDialog = mLayout.findViewById(R.id.dialog);
-
- mErrorText = mLayout.findViewById(R.id.error);
-
mLayout.setOnKeyListener(new View.OnKeyListener() {
boolean downPressed = false;
@Override
@@ -184,12 +188,19 @@
final View space = mLayout.findViewById(R.id.space);
final View leftSpace = mLayout.findViewById(R.id.left_space);
final View rightSpace = mLayout.findViewById(R.id.right_space);
- final ImageView icon = mLayout.findViewById(R.id.biometric_icon);
- final Button tryAgain = mLayout.findViewById(R.id.button_try_again);
+
+ mDialog = mLayout.findViewById(R.id.dialog);
+ mTitleText = mLayout.findViewById(R.id.title);
+ mSubtitleText = mLayout.findViewById(R.id.subtitle);
+ mDescriptionText = mLayout.findViewById(R.id.description);
+ mBiometricIcon = mLayout.findViewById(R.id.biometric_icon);
+ mErrorText = mLayout.findViewById(R.id.error);
mNegativeButton = mLayout.findViewById(R.id.button2);
mPositiveButton = mLayout.findViewById(R.id.button1);
+ mTryAgainButton = mLayout.findViewById(R.id.button_try_again);
- icon.setContentDescription(getResources().getString(getIconDescriptionResourceId()));
+ mBiometricIcon.setContentDescription(
+ getResources().getString(getIconDescriptionResourceId()));
setDismissesDialog(space);
setDismissesDialog(leftSpace);
@@ -206,8 +217,9 @@
}, getDelayAfterAuthenticatedDurationMs());
});
- tryAgain.setOnClickListener((View v) -> {
+ mTryAgainButton.setOnClickListener((View v) -> {
showTryAgainButton(false /* show */);
+ handleClearMessage(false /* requireTryAgain */);
mCallback.onTryAgainPressed();
});
@@ -215,15 +227,17 @@
mLayout.requestFocus();
}
+ public void onSaveState(Bundle bundle) {
+ bundle.putInt(KEY_TRY_AGAIN_VISIBILITY, mTryAgainButton.getVisibility());
+ bundle.putInt(KEY_CONFIRM_VISIBILITY, mPositiveButton.getVisibility());
+ }
+
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
mErrorText.setText(getHintStringResourceId());
- final TextView title = mLayout.findViewById(R.id.title);
- final TextView subtitle = mLayout.findViewById(R.id.subtitle);
- final TextView description = mLayout.findViewById(R.id.description);
final ImageView backgroundView = mLayout.findViewById(R.id.background);
if (mUserManager.isManagedProfile(mUserId)) {
@@ -244,36 +258,34 @@
mDialog.getLayoutParams().width = (int) mDialogWidth;
}
- mLastState = STATE_NONE;
+ mLastState = STATE_IDLE;
updateState(STATE_AUTHENTICATING);
CharSequence titleText = mBundle.getCharSequence(BiometricPrompt.KEY_TITLE);
- title.setText(titleText);
- title.setSelected(true);
+ mTitleText.setVisibility(View.VISIBLE);
+ mTitleText.setText(titleText);
+ mTitleText.setSelected(true);
final CharSequence subtitleText = mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE);
if (TextUtils.isEmpty(subtitleText)) {
- subtitle.setVisibility(View.GONE);
+ mSubtitleText.setVisibility(View.GONE);
} else {
- subtitle.setVisibility(View.VISIBLE);
- subtitle.setText(subtitleText);
+ mSubtitleText.setVisibility(View.VISIBLE);
+ mSubtitleText.setText(subtitleText);
}
final CharSequence descriptionText =
mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION);
if (TextUtils.isEmpty(descriptionText)) {
- description.setVisibility(View.GONE);
+ mDescriptionText.setVisibility(View.GONE);
} else {
- description.setVisibility(View.VISIBLE);
- description.setText(descriptionText);
+ mDescriptionText.setVisibility(View.VISIBLE);
+ mDescriptionText.setText(descriptionText);
}
mNegativeButton.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT));
- showTryAgainButton(mPendingShowTryAgain);
- showConfirmationButton(mPendingShowConfirm);
-
if (mWasForceRemoved || mSkipIntro) {
// Show the dialog immediately
mLayout.animate().cancel();
@@ -302,8 +314,7 @@
? (AnimatedVectorDrawable) icon
: null;
- final ImageView imageView = getLayout().findViewById(R.id.biometric_icon);
- imageView.setImageDrawable(icon);
+ mBiometricIcon.setImageDrawable(icon);
if (animation != null && shouldAnimateForTransition(lastState, newState)) {
animation.forceAnimationOnUI();
@@ -314,7 +325,7 @@
private void setDismissesDialog(View v) {
v.setClickable(true);
v.setOnTouchListener((View view, MotionEvent event) -> {
- if (mLastState != STATE_AUTHENTICATED) {
+ if (mLastState != STATE_AUTHENTICATED && shouldGrayAreaDismissDialog()) {
mCallback.onUserCanceled();
}
return true;
@@ -331,11 +342,9 @@
mWindowManager.removeView(BiometricDialogView.this);
mAnimatingAway = false;
// Set the icons / text back to normal state
- handleClearMessage();
+ handleClearMessage(false /* requireTryAgain */);
showTryAgainButton(false /* show */);
- mPendingShowTryAgain = false;
- mPendingShowConfirm = false;
- updateState(STATE_NONE);
+ updateState(STATE_IDLE);
}
};
@@ -412,35 +421,42 @@
return mLayout;
}
- // Clears the temporary message and shows the help message.
- private void handleClearMessage() {
- updateState(STATE_AUTHENTICATING);
- mErrorText.setText(getHintStringResourceId());
- mErrorText.setTextColor(mTextColor);
+ // Clears the temporary message and shows the help message. If requireTryAgain is true,
+ // we will start the authenticating state again.
+ private void handleClearMessage(boolean requireTryAgain) {
+ if (!requireTryAgain) {
+ updateState(STATE_AUTHENTICATING);
+ mErrorText.setText(getHintStringResourceId());
+ mErrorText.setTextColor(mTextColor);
+ mErrorText.setVisibility(View.VISIBLE);
+ } else {
+ updateState(STATE_IDLE);
+ mErrorText.setVisibility(View.INVISIBLE);
+ }
}
// Shows an error/help message
- private void showTemporaryMessage(String message) {
+ private void showTemporaryMessage(String message, boolean requireTryAgain) {
mHandler.removeMessages(MSG_CLEAR_MESSAGE);
updateState(STATE_ERROR);
mErrorText.setText(message);
mErrorText.setTextColor(mErrorColor);
mErrorText.setContentDescription(message);
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_MESSAGE),
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_MESSAGE, requireTryAgain),
BiometricPrompt.HIDE_DIALOG_DELAY);
}
public void clearTemporaryMessage() {
mHandler.removeMessages(MSG_CLEAR_MESSAGE);
- mHandler.obtainMessage(MSG_CLEAR_MESSAGE).sendToTarget();
+ mHandler.obtainMessage(MSG_CLEAR_MESSAGE, false /* requireTryAgain */).sendToTarget();
}
- public void showHelpMessage(String message) {
- showTemporaryMessage(message);
+ public void showHelpMessage(String message, boolean requireTryAgain) {
+ showTemporaryMessage(message, requireTryAgain);
}
public void showErrorMessage(String error) {
- showTemporaryMessage(error);
+ showTemporaryMessage(error, false /* requireTryAgain */);
showTryAgainButton(false /* show */);
mCallback.onErrorShown();
}
@@ -459,22 +475,16 @@
}
public void showTryAgainButton(boolean show) {
- final Button tryAgain = mLayout.findViewById(R.id.button_try_again);
if (show) {
- tryAgain.setVisibility(View.VISIBLE);
+ mTryAgainButton.setVisibility(View.VISIBLE);
} else {
- tryAgain.setVisibility(View.GONE);
+ mTryAgainButton.setVisibility(View.GONE);
}
}
- // Set the state before the window is attached, so we know if the dialog should be started
- // with or without the button. This is because there's no good onPause signal
- public void setPendingTryAgain(boolean show) {
- mPendingShowTryAgain = show;
- }
-
- public void setPendingConfirm(boolean show) {
- mPendingShowConfirm = show;
+ public void restoreState(Bundle bundle) {
+ mTryAgainButton.setVisibility(bundle.getInt(KEY_TRY_AGAIN_VISIBILITY));
+ mPositiveButton.setVisibility(bundle.getInt(KEY_CONFIRM_VISIBILITY));
}
public WindowManager.LayoutParams getLayoutParams() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
index de3f947..359cb04 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
@@ -16,8 +16,18 @@
package com.android.systemui.biometrics;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
import android.content.Context;
+import android.graphics.Outline;
import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.ViewOutlineProvider;
import com.android.systemui.R;
@@ -28,13 +38,243 @@
*/
public class FaceDialogView extends BiometricDialogView {
+ private static final String TAG = "FaceDialogView";
+ private static final String KEY_DIALOG_SIZE = "key_dialog_size";
+
private static final int HIDE_DIALOG_DELAY = 500; // ms
+ private static final int IMPLICIT_Y_PADDING = 16; // dp
+ private static final int GROW_DURATION = 150; // ms
+ private static final int TEXT_ANIMATE_DISTANCE = 32; // dp
+
+ private static final int SIZE_UNKNOWN = 0;
+ private static final int SIZE_SMALL = 1;
+ private static final int SIZE_GROWING = 2;
+ private static final int SIZE_BIG = 3;
+
+ private int mSize;
+ private float mIconOriginalY;
+ private DialogOutlineProvider mOutlineProvider = new DialogOutlineProvider();
+
+ private final class DialogOutlineProvider extends ViewOutlineProvider {
+
+ float mY;
+
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(
+ 0 /* left */,
+ (int) mY, /* top */
+ mDialog.getWidth() /* right */,
+ mDialog.getBottom(), /* bottom */
+ getResources().getDimension(R.dimen.biometric_dialog_corner_size));
+ }
+
+ int calculateSmall() {
+ final float padding = dpToPixels(IMPLICIT_Y_PADDING);
+ return mDialog.getHeight() - mBiometricIcon.getHeight() - 2 * (int) padding;
+ }
+
+ void setOutlineY(float y) {
+ mY = y;
+ }
+ }
public FaceDialogView(Context context,
DialogViewCallback callback) {
super(context, callback);
}
+ private void updateSize(int newSize) {
+ final float padding = dpToPixels(IMPLICIT_Y_PADDING);
+ final float iconSmallPositionY = mDialog.getHeight() - mBiometricIcon.getHeight() - padding;
+
+ if (newSize == SIZE_SMALL) {
+ // These fields are required and/or always hold a spot on the UI, so should be set to
+ // INVISIBLE so they keep their position
+ mTitleText.setVisibility(View.INVISIBLE);
+ mErrorText.setVisibility(View.INVISIBLE);
+ mNegativeButton.setVisibility(View.INVISIBLE);
+
+ // These fields are optional, so set them to gone or invisible depending on their
+ // usage. If they're empty, they're already set to GONE in BiometricDialogView.
+ if (!TextUtils.isEmpty(mSubtitleText.getText())) {
+ mSubtitleText.setVisibility(View.INVISIBLE);
+ }
+ if (!TextUtils.isEmpty(mDescriptionText.getText())) {
+ mDescriptionText.setVisibility(View.INVISIBLE);
+ }
+
+ // Move the biometric icon to the small spot
+ mBiometricIcon.setY(iconSmallPositionY);
+
+ // Clip the dialog to the small size
+ mDialog.setOutlineProvider(mOutlineProvider);
+ mOutlineProvider.setOutlineY(mOutlineProvider.calculateSmall());
+
+ mDialog.setClipToOutline(true);
+ mDialog.invalidateOutline();
+
+ mSize = newSize;
+ } else if (mSize == SIZE_SMALL && newSize == SIZE_BIG) {
+ mSize = SIZE_GROWING;
+
+ // Animate the outline
+ final ValueAnimator outlineAnimator =
+ ValueAnimator.ofFloat(mOutlineProvider.calculateSmall(), 0);
+ outlineAnimator.addUpdateListener((animation) -> {
+ final float y = (float) animation.getAnimatedValue();
+ mOutlineProvider.setOutlineY(y);
+ mDialog.invalidateOutline();
+ });
+
+ // Animate the icon back to original big position
+ final ValueAnimator iconAnimator =
+ ValueAnimator.ofFloat(iconSmallPositionY, mIconOriginalY);
+ iconAnimator.addUpdateListener((animation) -> {
+ final float y = (float) animation.getAnimatedValue();
+ mBiometricIcon.setY(y);
+ });
+
+ // Animate the error text so it slides up with the icon
+ final ValueAnimator textSlideAnimator =
+ ValueAnimator.ofFloat(dpToPixels(TEXT_ANIMATE_DISTANCE), 0);
+ textSlideAnimator.addUpdateListener((animation) -> {
+ final float y = (float) animation.getAnimatedValue();
+ mErrorText.setTranslationY(y);
+ });
+
+ // Opacity animator for things that should fade in (title, subtitle, details, negative
+ // button)
+ final ValueAnimator opacityAnimator = ValueAnimator.ofFloat(0, 1);
+ opacityAnimator.addUpdateListener((animation) -> {
+ final float opacity = (float) animation.getAnimatedValue();
+
+ // These fields are required and/or always hold a spot on the UI
+ mTitleText.setAlpha(opacity);
+ mErrorText.setAlpha(opacity);
+ mNegativeButton.setAlpha(opacity);
+ mTryAgainButton.setAlpha(opacity);
+
+ // These fields are optional, so only animate them if they're supposed to be showing
+ if (!TextUtils.isEmpty(mSubtitleText.getText())) {
+ mSubtitleText.setAlpha(opacity);
+ }
+ if (!TextUtils.isEmpty(mDescriptionText.getText())) {
+ mDescriptionText.setAlpha(opacity);
+ }
+ });
+
+ // Choreograph together
+ final AnimatorSet as = new AnimatorSet();
+ as.setDuration(GROW_DURATION);
+ as.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ // Set the visibility of opacity-animating views back to VISIBLE
+ mTitleText.setVisibility(View.VISIBLE);
+ mErrorText.setVisibility(View.VISIBLE);
+ mNegativeButton.setVisibility(View.VISIBLE);
+ mTryAgainButton.setVisibility(View.VISIBLE);
+
+ if (!TextUtils.isEmpty(mSubtitleText.getText())) {
+ mSubtitleText.setVisibility(View.VISIBLE);
+ }
+ if (!TextUtils.isEmpty(mDescriptionText.getText())) {
+ mDescriptionText.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mSize = SIZE_BIG;
+ }
+ });
+ as.play(outlineAnimator).with(iconAnimator).with(opacityAnimator)
+ .with(textSlideAnimator);
+ as.start();
+ } else if (mSize == SIZE_BIG) {
+ mDialog.setClipToOutline(false);
+ mDialog.invalidateOutline();
+
+ mBiometricIcon.setY(mIconOriginalY);
+
+ mSize = newSize;
+ }
+ }
+
+ @Override
+ public void onSaveState(Bundle bundle) {
+ super.onSaveState(bundle);
+ bundle.putInt(KEY_DIALOG_SIZE, mSize);
+ }
+
+ @Override
+ public void restoreState(Bundle bundle) {
+ super.restoreState(bundle);
+ // Keep in mind that this happens before onAttachedToWindow()
+ mSize = bundle.getInt(KEY_DIALOG_SIZE);
+ }
+
+ /**
+ * Do small/big layout here instead of onAttachedToWindow, since:
+ * 1) We need the big layout to be measured, etc for small -> big animation
+ * 2) We need the dialog measurements to know where to move the biometric icon to
+ *
+ * BiometricDialogView already sets the views to their default big state, so here we only
+ * need to hide the ones that are unnecessary.
+ */
+ @Override
+ public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ if (mIconOriginalY == 0) {
+ mIconOriginalY = mBiometricIcon.getY();
+ }
+
+ // UNKNOWN means size hasn't been set yet. First time we create the dialog.
+ // onLayout can happen when visibility of views change (during animation, etc).
+ if (mSize != SIZE_UNKNOWN) {
+ // Probably not the cleanest way to do this, but since dialog is big by default,
+ // and small dialogs can persist across orientation changes, we need to set it to
+ // small size here again.
+ if (mSize == SIZE_SMALL) {
+ updateSize(SIZE_SMALL);
+ }
+ return;
+ }
+
+ // If we don't require confirmation, show the small dialog first (until errors occur).
+ if (!requiresConfirmation()) {
+ updateSize(SIZE_SMALL);
+ } else {
+ updateSize(SIZE_BIG);
+ }
+ }
+
+ @Override
+ public void showErrorMessage(String error) {
+ super.showErrorMessage(error);
+
+ // All error messages will cause the dialog to go from small -> big. Error messages
+ // are messages such as lockout, auth failed, etc.
+ if (mSize == SIZE_SMALL) {
+ updateSize(SIZE_BIG);
+ }
+ }
+
+ @Override
+ public void showTryAgainButton(boolean show) {
+ if (show && mSize == SIZE_SMALL) {
+ // Do not call super, we will nicely animate the alpha together with the rest
+ // of the elements in here.
+ updateSize(SIZE_BIG);
+ } else {
+ super.showTryAgainButton(show);
+ }
+ }
+
@Override
protected int getHintStringResourceId() {
return R.string.face_dialog_looking_for_face;
@@ -56,7 +296,9 @@
@Override
protected boolean shouldAnimateForTransition(int oldState, int newState) {
- if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) {
+ if (oldState == STATE_ERROR && newState == STATE_IDLE) {
+ return true;
+ } else if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) {
return false;
} else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) {
return true;
@@ -78,9 +320,19 @@
}
@Override
+ protected boolean shouldGrayAreaDismissDialog() {
+ if (mSize == SIZE_SMALL) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
protected Drawable getAnimationForTransition(int oldState, int newState) {
int iconRes;
- if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) {
+ if (oldState == STATE_ERROR && newState == STATE_IDLE) {
+ iconRes = R.drawable.face_dialog_error_to_face;
+ } else if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) {
iconRes = R.drawable.face_dialog_face_to_error;
} else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) {
iconRes = R.drawable.face_dialog_face_to_error;
@@ -97,4 +349,14 @@
}
return mContext.getDrawable(iconRes);
}
+
+ private float dpToPixels(float dp) {
+ return dp * ((float) mContext.getResources().getDisplayMetrics().densityDpi
+ / DisplayMetrics.DENSITY_DEFAULT);
+ }
+
+ private float pixelsToDp(float pixels) {
+ return pixels / ((float) mContext.getResources().getDisplayMetrics().densityDpi
+ / DisplayMetrics.DENSITY_DEFAULT);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java
index 1a6cee2..d63836b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java
@@ -49,7 +49,7 @@
@Override
protected boolean shouldAnimateForTransition(int oldState, int newState) {
- if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) {
+ if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) {
return false;
} else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) {
return true;
@@ -68,9 +68,15 @@
}
@Override
+ protected boolean shouldGrayAreaDismissDialog() {
+ // Fingerprint dialog always dismisses when region outside the dialog is tapped
+ return true;
+ }
+
+ @Override
protected Drawable getAnimationForTransition(int oldState, int newState) {
int iconRes;
- if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) {
+ if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) {
iconRes = R.drawable.fingerprint_dialog_fp_to_error;
} else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) {
iconRes = R.drawable.fingerprint_dialog_fp_to_error;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 7577609..9f3ff78 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -23,15 +23,20 @@
import static com.android.systemui.bubbles.BubbleMovementHelper.EDGE_OVERLAP;
import android.app.Notification;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.graphics.Point;
import android.graphics.Rect;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
+import android.util.Log;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dependency;
import com.android.systemui.R;
@@ -68,6 +73,8 @@
private static final String ENABLE_AUTO_BUBBLE_MESSAGES = "experiment_autobubble_messaging";
private static final String ENABLE_AUTO_BUBBLE_ONGOING = "experiment_autobubble_ongoing";
private static final String ENABLE_AUTO_BUBBLE_ALL = "experiment_autobubble_all";
+ private static final String ENABLE_BUBBLE_ACTIVITY_VIEW = "experiment_bubble_activity_view";
+ private static final String ENABLE_BUBBLE_CONTENT_INTENT = "experiment_bubble_content_intent";
private final Context mContext;
private final NotificationEntryManager mNotificationEntryManager;
@@ -189,6 +196,9 @@
// It's new
BubbleView bubble = new BubbleView(mContext);
bubble.setNotif(notif);
+ if (shouldUseActivityView(mContext)) {
+ bubble.setAppOverlayIntent(getAppOverlayIntent(notif));
+ }
mBubbles.put(bubble.getKey(), bubble);
boolean setPosition = mStackView != null && mStackView.getVisibility() != VISIBLE;
@@ -216,6 +226,21 @@
}
}
+ @Nullable
+ private PendingIntent getAppOverlayIntent(NotificationEntry notif) {
+ Notification notification = notif.notification.getNotification();
+ if (canLaunchInActivityView(notification.getAppOverlayIntent())) {
+ return notification.getAppOverlayIntent();
+ } else if (shouldUseContentIntent(mContext)
+ && canLaunchInActivityView(notification.contentIntent)) {
+ Log.d(TAG, "[addBubble " + notif.key
+ + "]: No appOverlayIntent, using contentIntent.");
+ return notification.contentIntent;
+ }
+ Log.d(TAG, "[addBubble " + notif.key + "]: No supported intent for ActivityView.");
+ return null;
+ }
+
/**
* Removes the bubble associated with the {@param uri}.
*/
@@ -223,6 +248,7 @@
BubbleView bv = mBubbles.get(key);
if (mStackView != null && bv != null) {
mStackView.removeBubble(bv);
+ bv.destroyActivityView(mStackView);
bv.getEntry().setBubbleDismissed(true);
}
@@ -282,9 +308,10 @@
}
}
}
- for (BubbleView view : viewsToRemove) {
- mBubbles.remove(view.getKey());
- mStackView.removeBubble(view);
+ for (BubbleView bubbleView : viewsToRemove) {
+ mBubbles.remove(bubbleView.getKey());
+ mStackView.removeBubble(bubbleView);
+ bubbleView.destroyActivityView(mStackView);
}
if (mStackView != null) {
mStackView.setVisibility(visible ? VISIBLE : INVISIBLE);
@@ -306,6 +333,17 @@
return mTempRect;
}
+ private boolean canLaunchInActivityView(PendingIntent intent) {
+ if (intent == null) {
+ return false;
+ }
+ ActivityInfo info =
+ intent.getIntent().resolveActivityInfo(mContext.getPackageManager(), 0);
+ return info != null
+ && ActivityInfo.isResizeableMode(info.resizeMode)
+ && (info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) != 0;
+ }
+
@VisibleForTesting
BubbleStackView getStackView() {
return mStackView;
@@ -378,4 +416,14 @@
return Settings.Secure.getInt(context.getContentResolver(),
ENABLE_AUTO_BUBBLE_ALL, 0) != 0;
}
+
+ private static boolean shouldUseActivityView(Context context) {
+ return Settings.Secure.getInt(context.getContentResolver(),
+ ENABLE_BUBBLE_ACTIVITY_VIEW, 0) != 0;
+ }
+
+ private static boolean shouldUseContentIntent(Context context) {
+ return Settings.Secure.getInt(context.getContentResolver(),
+ ENABLE_BUBBLE_CONTENT_INTENT, 0) != 0;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
index e28d96b..badefe1 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
@@ -24,6 +24,7 @@
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
+import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.recents.TriangleShape;
@@ -35,6 +36,8 @@
// The triangle pointing to the expanded view
private View mPointerView;
+ // The view displayed between the pointer and the expanded view
+ private TextView mHeaderView;
// The view that is being displayed for the expanded state
private View mExpandedView;
@@ -68,6 +71,7 @@
TriangleShape.create(width, height, true /* pointUp */));
triangleDrawable.setTint(Color.WHITE); // TODO: dark mode
mPointerView.setBackground(triangleDrawable);
+ mHeaderView = findViewById(R.id.bubble_content_header);
}
/**
@@ -80,9 +84,19 @@
}
/**
+ * Set the text displayed within the header.
+ */
+ public void setHeaderText(CharSequence text) {
+ mHeaderView.setText(text);
+ }
+
+ /**
* Set the view to display for the expanded state. Passing null will clear the view.
*/
public void setExpandedView(View view) {
+ if (mExpandedView == view) {
+ return;
+ }
if (mExpandedView != null) {
removeView(mExpandedView);
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index d69e7b3..3280a33 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -21,10 +21,13 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
+import android.app.ActivityView;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.RectF;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -50,6 +53,7 @@
*/
public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.FloatingView {
+ private static final String TAG = "BubbleStackView";
private Point mDisplaySize;
private FrameLayout mBubbleContainer;
@@ -59,6 +63,7 @@
private int mBubblePadding;
private boolean mIsExpanded;
+ private int mExpandedBubbleHeight;
private BubbleView mExpandedBubble;
private Point mCollapsedPosition;
private BubbleTouchHandler mTouchHandler;
@@ -106,6 +111,7 @@
mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+ mExpandedBubbleHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
mDisplaySize = new Point();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getSize(mDisplaySize);
@@ -389,27 +395,63 @@
}
private void updateExpandedBubble() {
- if (mExpandedBubble != null) {
+ if (mExpandedBubble == null) {
+ return;
+ }
+
+ if (mExpandedBubble.hasAppOverlayIntent()) {
+ ActivityView expandedView = mExpandedBubble.getActivityView();
+ expandedView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, mExpandedBubbleHeight));
+
+ final PendingIntent intent = mExpandedBubble.getAppOverlayIntent();
+ mExpandedViewContainer.setHeaderText(intent.getIntent().getComponent().toShortString());
+ mExpandedViewContainer.setExpandedView(expandedView);
+ expandedView.setCallback(new ActivityView.StateCallback() {
+ @Override
+ public void onActivityViewReady(ActivityView view) {
+ Log.d(TAG, "onActivityViewReady("
+ + mExpandedBubble.getEntry().key + "): " + view);
+ view.startActivity(intent);
+ }
+
+ @Override
+ public void onActivityViewDestroyed(ActivityView view) {
+ NotificationEntry entry = mExpandedBubble.getEntry();
+ Log.d(TAG, "onActivityViewDestroyed(key="
+ + ((entry != null) ? entry.key : "(none)") + "): " + view);
+ }
+ });
+ } else {
ExpandableNotificationRow row = mExpandedBubble.getRowView();
- if (!row.equals(mExpandedViewContainer.getChildAt(0))) {
+ if (!row.equals(mExpandedViewContainer.getExpandedView())) {
// Different expanded view than what we have
mExpandedViewContainer.setExpandedView(null);
}
- int pointerPosition = mExpandedBubble.getPosition().x
- + (mExpandedBubble.getWidth() / 2);
- mExpandedViewContainer.setPointerPosition(pointerPosition);
mExpandedViewContainer.setExpandedView(row);
+ mExpandedViewContainer.setHeaderText(null);
}
+ int pointerPosition = mExpandedBubble.getPosition().x
+ + (mExpandedBubble.getWidth() / 2);
+ mExpandedViewContainer.setPointerPosition(pointerPosition);
}
private void applyCurrentState() {
+ Log.d(TAG, "applyCurrentState: mIsExpanded=" + mIsExpanded);
+
mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
if (!mIsExpanded) {
mExpandedViewContainer.setExpandedView(null);
} else {
mExpandedViewContainer.setTranslationY(mBubbleContainer.getHeight());
- ExpandableNotificationRow row = mExpandedBubble.getRowView();
- applyRowState(row);
+ View expandedView = mExpandedViewContainer.getExpandedView();
+ if (expandedView instanceof ActivityView) {
+ if (expandedView.isAttachedToWindow()) {
+ ((ActivityView) expandedView).onLocationChanged();
+ }
+ } else {
+ applyRowState(mExpandedBubble.getRowView());
+ }
}
int bubbsCount = mBubbleContainer.getChildCount();
for (int i = 0; i < bubbsCount; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index 88030ee..96b2dba 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -33,7 +33,7 @@
* Handles interpreting touches on a {@link BubbleStackView}. This includes expanding, collapsing,
* dismissing, and flings.
*/
-public class BubbleTouchHandler implements View.OnTouchListener {
+class BubbleTouchHandler implements View.OnTouchListener {
private BubbleController mController = Dependency.get(BubbleController.class);
private PipDismissViewController mDismissViewController;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index 3307992..c1bbb93 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -16,7 +16,9 @@
package com.android.systemui.bubbles;
+import android.app.ActivityView;
import android.app.Notification;
+import android.app.PendingIntent;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Point;
@@ -25,7 +27,9 @@
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -37,7 +41,7 @@
/**
* A floating object on the screen that has a collapsed and expanded state.
*/
-public class BubbleView extends LinearLayout implements BubbleTouchHandler.FloatingView {
+class BubbleView extends LinearLayout implements BubbleTouchHandler.FloatingView {
private static final String TAG = "BubbleView";
private Context mContext;
@@ -46,6 +50,8 @@
private NotificationEntry mEntry;
private int mBubbleSize;
private int mIconSize;
+ private PendingIntent mAppOverlayIntent;
+ private ActivityView mActivityView;
public BubbleView(Context context) {
this(context, null);
@@ -117,12 +123,51 @@
}
/**
- * @return the view to display when the bubble is expanded.
+ * @return the view to display notification content when the bubble is expanded.
*/
public ExpandableNotificationRow getRowView() {
return mEntry.getRow();
}
+ /**
+ * @return a view used to display app overlay content when expanded.
+ */
+ public ActivityView getActivityView() {
+ if (mActivityView == null) {
+ mActivityView = new ActivityView(mContext);
+ Log.d(TAG, "[getActivityView] created: " + mActivityView);
+ }
+ return mActivityView;
+ }
+
+ /**
+ * Removes and releases an ActivityView if one was previously created for this bubble.
+ */
+ public void destroyActivityView(ViewGroup tmpParent) {
+ if (mActivityView == null) {
+ return;
+ }
+ // HACK: Only release if initialized. There's no way to know if the ActivityView has
+ // been initialized. Calling release() if it hasn't been initialized will crash.
+
+ if (!mActivityView.isAttachedToWindow()) {
+ // HACK: release() will crash if the view is not attached.
+
+ mActivityView.setVisibility(View.GONE);
+ tmpParent.addView(mActivityView, new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ }
+ try {
+ mActivityView.release();
+ } catch (IllegalStateException ex) {
+ Log.e(TAG, "ActivityView either already released, or not yet initialized.", ex);
+ }
+
+ ((ViewGroup) mActivityView.getParent()).removeView(mActivityView);
+ mActivityView = null;
+ }
+
@Override
public void setPosition(int x, int y) {
setTranslationX(x);
@@ -162,4 +207,20 @@
lp.height = mBubbleSize;
v.setLayoutParams(lp);
}
+
+ /**
+ * @return whether an ActivityView should be used to display the content of this Bubble
+ */
+ public boolean hasAppOverlayIntent() {
+ return mAppOverlayIntent != null;
+ }
+
+ public PendingIntent getAppOverlayIntent() {
+ return mAppOverlayIntent;
+
+ }
+
+ public void setAppOverlayIntent(PendingIntent intent) {
+ mAppOverlayIntent = intent;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 323cf1f..f14495b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1843,6 +1843,13 @@
*/
private void handleHide() {
Trace.beginSection("KeyguardViewMediator#handleHide");
+
+ // It's possible that the device was unlocked in a dream state. It's time to wake up.
+ if (mAodShowing) {
+ PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ pm.wakeUp(SystemClock.uptimeMillis(), "com.android.systemui:BOUNCER_DOZING");
+ }
+
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleHide");
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
index 01ee5ca..4388200 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
@@ -52,7 +52,8 @@
@Suppress("DEPRECATION")
override fun onClick(dialog: DialogInterface?, which: Int) {
- Dependency.get(ActivityStarter::class.java).startActivity(intent, false)
+ Dependency.get(ActivityStarter::class.java)
+ .postStartActivityDismissingKeyguard(intent, 0)
}
})
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index e030e40..e63f88a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -284,10 +284,17 @@
public void updateEverything() {
post(() -> {
updateVisibilities();
+ updateClickabilities();
setClickable(false);
});
}
+ private void updateClickabilities() {
+ mMultiUserSwitch.setClickable(mMultiUserSwitch.getVisibility() == View.VISIBLE);
+ mEdit.setClickable(mEdit.getVisibility() == View.VISIBLE);
+ mSettingsButton.setClickable(mSettingsButton.getVisibility() == View.VISIBLE);
+ }
+
private void updateVisibilities() {
mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 40c6039..28285e14 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -699,7 +699,7 @@
}
public void updateEverything() {
- post(() -> setClickable(false));
+ post(() -> setClickable(!mExpanded));
}
public void setQSPanel(final QSPanel qsPanel) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 81757d0..c474faf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -101,6 +101,7 @@
private float mBackButtonAlpha;
private MotionEvent mStatusBarGestureDownEvent;
private float mWindowCornerRadius;
+ private boolean mSupportsRoundedCornersOnWindows;
private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
@@ -244,6 +245,18 @@
}
}
+ public boolean supportsRoundedCornersOnWindows() {
+ if (!verifyCaller("supportsRoundedCornersOnWindows")) {
+ return false;
+ }
+ long token = Binder.clearCallingIdentity();
+ try {
+ return mSupportsRoundedCornersOnWindows;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
private boolean verifyCaller(String reason) {
final int callerId = Binder.getCallingUserHandle().getIdentifier();
if (callerId != mCurrentBoundedUserId) {
@@ -353,6 +366,8 @@
mInteractionFlags = Prefs.getInt(mContext, Prefs.Key.QUICK_STEP_INTERACTION_FLAGS,
getDefaultInteractionFlags());
mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext.getResources());
+ mSupportsRoundedCornersOnWindows = ScreenDecorationsUtils
+ .supportsRoundedCornersOnWindows(mContext.getResources());
// Listen for the package update changes.
if (mDeviceProvisionedController.getCurrentUser() == UserHandle.USER_SYSTEM) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 6a01563..904478e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -111,7 +111,6 @@
private static final int MSG_SHOW_CHARGING_ANIMATION = 44 << MSG_SHIFT;
private static final int MSG_SHOW_PINNING_TOAST_ENTER_EXIT = 45 << MSG_SHIFT;
private static final int MSG_SHOW_PINNING_TOAST_ESCAPE = 46 << MSG_SHIFT;
- private static final int MSG_BIOMETRIC_TRY_AGAIN = 47 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -271,11 +270,10 @@
default void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
int type, boolean requireConfirmation, int userId) { }
- default void onBiometricAuthenticated() { }
+ default void onBiometricAuthenticated(boolean authenticated) { }
default void onBiometricHelp(String message) { }
default void onBiometricError(String error) { }
default void hideBiometricDialog() { }
- default void showBiometricTryAgain() { }
}
@VisibleForTesting
@@ -736,9 +734,9 @@
}
@Override
- public void onBiometricAuthenticated() {
+ public void onBiometricAuthenticated(boolean authenticated) {
synchronized (mLock) {
- mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget();
+ mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, authenticated).sendToTarget();
}
}
@@ -763,13 +761,6 @@
}
}
- @Override
- public void showBiometricTryAgain() {
- synchronized (mLock) {
- mHandler.obtainMessage(MSG_BIOMETRIC_TRY_AGAIN).sendToTarget();
- }
- }
-
private final class H extends Handler {
private H(Looper l) {
super(l);
@@ -991,7 +982,7 @@
break;
case MSG_BIOMETRIC_AUTHENTICATED:
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).onBiometricAuthenticated();
+ mCallbacks.get(i).onBiometricAuthenticated((boolean) msg.obj);
}
break;
case MSG_BIOMETRIC_HELP:
@@ -1024,11 +1015,6 @@
mCallbacks.get(i).showPinningEscapeToast();
}
break;
- case MSG_BIOMETRIC_TRY_AGAIN:
- for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).showBiometricTryAgain();
- }
- break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
deleted file mode 100644
index 6d2c001..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ /dev/null
@@ -1,692 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
-import static android.app.StatusBarManager.DISABLE_NONE;
-
-import android.annotation.DrawableRes;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.telephony.SubscriptionInfo;
-import android.util.ArraySet;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import com.android.settingslib.graph.SignalDrawable;
-import com.android.systemui.Dependency;
-import com.android.systemui.R;
-import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
-import com.android.systemui.statusbar.policy.IconLogger;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NetworkController.IconState;
-import com.android.systemui.statusbar.policy.NetworkControllerImpl;
-import com.android.systemui.statusbar.policy.SecurityController;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.tuner.TunerService.Tunable;
-import com.android.systemui.util.Utils.DisableStateTracker;
-
-import java.util.ArrayList;
-import java.util.List;
-
-// Intimately tied to the design of res/layout/signal_cluster_view.xml
-public class SignalClusterView extends LinearLayout implements NetworkControllerImpl.SignalCallback,
- SecurityController.SecurityControllerCallback, Tunable, DarkReceiver {
-
- static final String TAG = "SignalClusterView";
- static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- private static final String SLOT_AIRPLANE = "airplane";
- private static final String SLOT_MOBILE = "mobile";
- private static final String SLOT_WIFI = "wifi";
- private static final String SLOT_ETHERNET = "ethernet";
- private static final String SLOT_VPN = "vpn";
-
- private final NetworkController mNetworkController;
- private final SecurityController mSecurityController;
-
- private boolean mVpnVisible = false;
- private int mVpnIconId = 0;
- private int mLastVpnIconId = -1;
- private boolean mEthernetVisible = false;
- private int mEthernetIconId = 0;
- private int mLastEthernetIconId = -1;
- private boolean mWifiVisible = false;
- private int mWifiStrengthId = 0;
- private int mLastWifiStrengthId = -1;
- private boolean mWifiIn;
- private boolean mWifiOut;
- private boolean mIsAirplaneMode = false;
- private int mAirplaneIconId = 0;
- private int mLastAirplaneIconId = -1;
- private String mAirplaneContentDescription;
- private String mWifiDescription;
- private String mEthernetDescription;
- private ArrayList<PhoneState> mPhoneStates = new ArrayList<PhoneState>();
- private int mIconTint = Color.WHITE;
- private float mDarkIntensity;
- private final Rect mTintArea = new Rect();
-
- ViewGroup mEthernetGroup, mWifiGroup;
- ImageView mVpn, mEthernet, mWifi, mAirplane, mEthernetDark, mWifiDark;
- ImageView mWifiActivityIn;
- ImageView mWifiActivityOut;
- View mWifiAirplaneSpacer;
- View mWifiSignalSpacer;
- LinearLayout mMobileSignalGroup;
-
- private final int mMobileSignalGroupEndPadding;
- private final int mMobileDataIconStartPadding;
- private final int mSecondaryTelephonyPadding;
- private final int mEndPadding;
- private final int mEndPaddingNothingVisible;
- private final float mIconScaleFactor;
-
- private boolean mBlockAirplane;
- private boolean mBlockMobile;
- private boolean mBlockWifi;
- private boolean mBlockEthernet;
- private boolean mActivityEnabled;
- private boolean mForceBlockWifi;
-
- private final IconLogger mIconLogger = Dependency.get(IconLogger.class);
-
- public SignalClusterView(Context context) {
- this(context, null);
- }
-
- public SignalClusterView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SignalClusterView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- Resources res = getResources();
- mMobileSignalGroupEndPadding =
- res.getDimensionPixelSize(R.dimen.mobile_signal_group_end_padding);
- mMobileDataIconStartPadding =
- res.getDimensionPixelSize(R.dimen.mobile_data_icon_start_padding);
- mSecondaryTelephonyPadding = res.getDimensionPixelSize(R.dimen.secondary_telephony_padding);
- mEndPadding = res.getDimensionPixelSize(R.dimen.signal_cluster_battery_padding);
- mEndPaddingNothingVisible = res.getDimensionPixelSize(
- R.dimen.no_signal_cluster_battery_padding);
-
- TypedValue typedValue = new TypedValue();
- res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
- mIconScaleFactor = typedValue.getFloat();
- mNetworkController = Dependency.get(NetworkController.class);
- mSecurityController = Dependency.get(SecurityController.class);
- addOnAttachStateChangeListener(
- new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS));
- updateActivityEnabled();
- }
-
- public void setForceBlockWifi() {
- mForceBlockWifi = true;
- mBlockWifi = true;
- if (isAttachedToWindow()) {
- // Re-register to get new callbacks.
- mNetworkController.removeCallback(this);
- mNetworkController.addCallback(this);
- }
- }
-
- @Override
- public void onTuningChanged(String key, String newValue) {
- if (!StatusBarIconController.ICON_BLACKLIST.equals(key)) {
- return;
- }
- ArraySet<String> blockList = StatusBarIconController.getIconBlacklist(newValue);
- boolean blockAirplane = blockList.contains(SLOT_AIRPLANE);
- boolean blockMobile = blockList.contains(SLOT_MOBILE);
- boolean blockWifi = blockList.contains(SLOT_WIFI);
- boolean blockEthernet = blockList.contains(SLOT_ETHERNET);
-
- if (blockAirplane != mBlockAirplane || blockMobile != mBlockMobile
- || blockEthernet != mBlockEthernet || blockWifi != mBlockWifi) {
- mBlockAirplane = blockAirplane;
- mBlockMobile = blockMobile;
- mBlockEthernet = blockEthernet;
- mBlockWifi = blockWifi || mForceBlockWifi;
- // Re-register to get new callbacks.
- mNetworkController.removeCallback(this);
- mNetworkController.addCallback(this);
- }
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- mVpn = findViewById(R.id.vpn);
- mEthernetGroup = findViewById(R.id.ethernet_combo);
- mEthernet = findViewById(R.id.ethernet);
- mEthernetDark = findViewById(R.id.ethernet_dark);
- mWifiGroup = findViewById(R.id.wifi_combo);
- mWifi = findViewById(R.id.wifi_signal);
- mWifiDark = findViewById(R.id.wifi_signal_dark);
- mWifiActivityIn = findViewById(R.id.wifi_in);
- mWifiActivityOut= findViewById(R.id.wifi_out);
- mAirplane = findViewById(R.id.airplane);
- mWifiAirplaneSpacer = findViewById(R.id.wifi_airplane_spacer);
- mWifiSignalSpacer = findViewById(R.id.wifi_signal_spacer);
- mMobileSignalGroup = findViewById(R.id.mobile_signal_group);
-
- maybeScaleVpnAndNoSimsIcons();
- }
-
- /**
- * Extracts the icon off of the VPN and no sims views and maybe scale them by
- * {@link #mIconScaleFactor}. Note that the other icons are not scaled here because they are
- * dynamic. As such, they need to be scaled each time the icon changes in {@link #apply()}.
- */
- private void maybeScaleVpnAndNoSimsIcons() {
- if (mIconScaleFactor == 1.f) {
- return;
- }
-
- mVpn.setImageDrawable(new ScalingDrawableWrapper(mVpn.getDrawable(), mIconScaleFactor));
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mVpnVisible = mSecurityController.isVpnEnabled();
- mVpnIconId = currentVpnIconId(mSecurityController.isVpnBranded());
-
- for (PhoneState state : mPhoneStates) {
- if (state.mMobileGroup.getParent() == null) {
- mMobileSignalGroup.addView(state.mMobileGroup);
- }
- }
-
- int endPadding = mMobileSignalGroup.getChildCount() > 0 ? mMobileSignalGroupEndPadding : 0;
- mMobileSignalGroup.setPaddingRelative(0, 0, endPadding, 0);
-
- Dependency.get(TunerService.class).addTunable(this, StatusBarIconController.ICON_BLACKLIST);
-
- apply();
- applyIconTint();
- mNetworkController.addCallback(this);
- mSecurityController.addCallback(this);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- mMobileSignalGroup.removeAllViews();
- Dependency.get(TunerService.class).removeTunable(this);
- mSecurityController.removeCallback(this);
- mNetworkController.removeCallback(this);
-
- super.onDetachedFromWindow();
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
-
- // Re-run all checks against the tint area for all icons
- applyIconTint();
- }
-
- // From SecurityController.
- @Override
- public void onStateChanged() {
- post(new Runnable() {
- @Override
- public void run() {
- mVpnVisible = mSecurityController.isVpnEnabled();
- mVpnIconId = currentVpnIconId(mSecurityController.isVpnBranded());
- apply();
- }
- });
- }
-
- private void updateActivityEnabled() {
- mActivityEnabled = mContext.getResources().getBoolean(R.bool.config_showActivity);
- }
-
- @Override
- public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
- boolean activityIn, boolean activityOut, String description, boolean isTransient,
- String secondaryLabel) {
- mWifiVisible = statusIcon.visible && !mBlockWifi;
- mWifiStrengthId = statusIcon.icon;
- mWifiDescription = statusIcon.contentDescription;
- mWifiIn = activityIn && mActivityEnabled && mWifiVisible;
- mWifiOut = activityOut && mActivityEnabled && mWifiVisible;
-
- apply();
- }
-
- @Override
- public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
- int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
- String description, boolean isWide, int subId, boolean roaming) {
- PhoneState state = getState(subId);
- if (state == null) {
- return;
- }
- state.mMobileVisible = statusIcon.visible && !mBlockMobile;
- state.mMobileStrengthId = statusIcon.icon;
- state.mMobileTypeId = statusType;
- state.mMobileDescription = statusIcon.contentDescription;
- state.mMobileTypeDescription = typeContentDescription;
- state.mRoaming = roaming;
- state.mActivityIn = activityIn && mActivityEnabled;
- state.mActivityOut = activityOut && mActivityEnabled;
-
- apply();
- }
-
- @Override
- public void setEthernetIndicators(IconState state) {
- mEthernetVisible = state.visible && !mBlockEthernet;
- mEthernetIconId = state.icon;
- mEthernetDescription = state.contentDescription;
-
- apply();
- }
-
- @Override
- public void setNoSims(boolean show, boolean simDetected) {
- // Noop. Status bar no longer shows no sim icon.
- }
-
- @Override
- public void setSubs(List<SubscriptionInfo> subs) {
- if (hasCorrectSubs(subs)) {
- return;
- }
- mPhoneStates.clear();
- if (mMobileSignalGroup != null) {
- mMobileSignalGroup.removeAllViews();
- }
- final int n = subs.size();
- for (int i = 0; i < n; i++) {
- inflatePhoneState(subs.get(i).getSubscriptionId());
- }
- if (isAttachedToWindow()) {
- applyIconTint();
- }
- }
-
- private boolean hasCorrectSubs(List<SubscriptionInfo> subs) {
- final int N = subs.size();
- if (N != mPhoneStates.size()) {
- return false;
- }
- for (int i = 0; i < N; i++) {
- if (mPhoneStates.get(i).mSubId != subs.get(i).getSubscriptionId()) {
- return false;
- }
- }
- return true;
- }
-
- private PhoneState getState(int subId) {
- for (PhoneState state : mPhoneStates) {
- if (state.mSubId == subId) {
- return state;
- }
- }
- Log.e(TAG, "Unexpected subscription " + subId);
- return null;
- }
-
- private PhoneState inflatePhoneState(int subId) {
- PhoneState state = new PhoneState(subId, mContext);
- if (mMobileSignalGroup != null) {
- mMobileSignalGroup.addView(state.mMobileGroup);
- }
- mPhoneStates.add(state);
- return state;
- }
-
- @Override
- public void setIsAirplaneMode(IconState icon) {
- mIsAirplaneMode = icon.visible && !mBlockAirplane;
- mAirplaneIconId = icon.icon;
- mAirplaneContentDescription = icon.contentDescription;
-
- apply();
- }
-
- @Override
- public void setMobileDataEnabled(boolean enabled) {
- // Don't care.
- }
-
- @Override
- public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
- // Standard group layout onPopulateAccessibilityEvent() implementations
- // ignore content description, so populate manually
- if (mEthernetVisible && mEthernetGroup != null &&
- mEthernetGroup.getContentDescription() != null)
- event.getText().add(mEthernetGroup.getContentDescription());
- if (mWifiVisible && mWifiGroup != null && mWifiGroup.getContentDescription() != null)
- event.getText().add(mWifiGroup.getContentDescription());
- for (PhoneState state : mPhoneStates) {
- state.populateAccessibilityEvent(event);
- }
- return super.dispatchPopulateAccessibilityEventInternal(event);
- }
-
- @Override
- public void onRtlPropertiesChanged(int layoutDirection) {
- super.onRtlPropertiesChanged(layoutDirection);
-
- if (mEthernet != null) {
- mEthernet.setImageDrawable(null);
- mEthernetDark.setImageDrawable(null);
- mLastEthernetIconId = -1;
- }
-
- if (mWifi != null) {
- mWifi.setImageDrawable(null);
- mWifiDark.setImageDrawable(null);
- mLastWifiStrengthId = -1;
- }
-
- for (PhoneState state : mPhoneStates) {
- if (state.mMobileType != null) {
- state.mMobileType.setImageDrawable(null);
- state.mLastMobileTypeId = -1;
- }
- }
-
- if (mAirplane != null) {
- mAirplane.setImageDrawable(null);
- mLastAirplaneIconId = -1;
- }
-
- apply();
- }
-
- @Override
- public boolean hasOverlappingRendering() {
- return false;
- }
-
- // Run after each indicator change.
- private void apply() {
- if (mWifiGroup == null) return;
-
- if (mVpnVisible) {
- if (mLastVpnIconId != mVpnIconId) {
- setIconForView(mVpn, mVpnIconId);
- mLastVpnIconId = mVpnIconId;
- }
- mIconLogger.onIconShown(SLOT_VPN);
- mVpn.setVisibility(View.VISIBLE);
- } else {
- mIconLogger.onIconHidden(SLOT_VPN);
- mVpn.setVisibility(View.GONE);
- }
- if (DEBUG) Log.d(TAG, String.format("vpn: %s", mVpnVisible ? "VISIBLE" : "GONE"));
-
- if (mEthernetVisible) {
- if (mLastEthernetIconId != mEthernetIconId) {
- setIconForView(mEthernet, mEthernetIconId);
- setIconForView(mEthernetDark, mEthernetIconId);
- mLastEthernetIconId = mEthernetIconId;
- }
- mEthernetGroup.setContentDescription(mEthernetDescription);
- mIconLogger.onIconShown(SLOT_ETHERNET);
- mEthernetGroup.setVisibility(View.VISIBLE);
- } else {
- mIconLogger.onIconHidden(SLOT_ETHERNET);
- mEthernetGroup.setVisibility(View.GONE);
- }
-
- if (DEBUG) Log.d(TAG,
- String.format("ethernet: %s",
- (mEthernetVisible ? "VISIBLE" : "GONE")));
-
- if (mWifiVisible) {
- if (mWifiStrengthId != mLastWifiStrengthId) {
- setIconForView(mWifi, mWifiStrengthId);
- setIconForView(mWifiDark, mWifiStrengthId);
- mLastWifiStrengthId = mWifiStrengthId;
- }
- mIconLogger.onIconShown(SLOT_WIFI);
- mWifiGroup.setContentDescription(mWifiDescription);
- mWifiGroup.setVisibility(View.VISIBLE);
- } else {
- mIconLogger.onIconHidden(SLOT_WIFI);
- mWifiGroup.setVisibility(View.GONE);
- }
-
- if (DEBUG) Log.d(TAG,
- String.format("wifi: %s sig=%d",
- (mWifiVisible ? "VISIBLE" : "GONE"),
- mWifiStrengthId));
-
- mWifiActivityIn.setVisibility(mWifiIn ? View.VISIBLE : View.GONE);
- mWifiActivityOut.setVisibility(mWifiOut ? View.VISIBLE : View.GONE);
-
- boolean anyMobileVisible = false;
- int firstMobileTypeId = 0;
- for (PhoneState state : mPhoneStates) {
- if (state.apply(anyMobileVisible)) {
- if (!anyMobileVisible) {
- firstMobileTypeId = state.mMobileTypeId;
- anyMobileVisible = true;
- }
- }
- }
- if (anyMobileVisible) {
- mIconLogger.onIconShown(SLOT_MOBILE);
- } else {
- mIconLogger.onIconHidden(SLOT_MOBILE);
- }
-
- if (mIsAirplaneMode) {
- if (mLastAirplaneIconId != mAirplaneIconId) {
- setIconForView(mAirplane, mAirplaneIconId);
- mLastAirplaneIconId = mAirplaneIconId;
- }
- mAirplane.setContentDescription(mAirplaneContentDescription);
- mIconLogger.onIconShown(SLOT_AIRPLANE);
- mAirplane.setVisibility(View.VISIBLE);
- } else {
- mIconLogger.onIconHidden(SLOT_AIRPLANE);
- mAirplane.setVisibility(View.GONE);
- }
-
- if (mIsAirplaneMode && mWifiVisible) {
- mWifiAirplaneSpacer.setVisibility(View.VISIBLE);
- } else {
- mWifiAirplaneSpacer.setVisibility(View.GONE);
- }
-
- if ((anyMobileVisible && firstMobileTypeId != 0) && mWifiVisible) {
- mWifiSignalSpacer.setVisibility(View.VISIBLE);
- } else {
- mWifiSignalSpacer.setVisibility(View.GONE);
- }
-
- boolean anythingVisible = mWifiVisible || mIsAirplaneMode
- || anyMobileVisible || mVpnVisible || mEthernetVisible;
- setPaddingRelative(0, 0, anythingVisible ? mEndPadding : mEndPaddingNothingVisible, 0);
- }
-
- /**
- * Sets the given drawable id on the view. This method will also scale the icon by
- * {@link #mIconScaleFactor} if appropriate.
- */
- private void setIconForView(ImageView imageView, @DrawableRes int iconId) {
- // Using the imageView's context to retrieve the Drawable so that theme is preserved.
- Drawable icon = imageView.getContext().getDrawable(iconId);
-
- if (mIconScaleFactor == 1.f) {
- imageView.setImageDrawable(icon);
- } else {
- imageView.setImageDrawable(new ScalingDrawableWrapper(icon, mIconScaleFactor));
- }
- }
-
-
- @Override
- public void onDarkChanged(Rect tintArea, float darkIntensity, int tint) {
- boolean changed = tint != mIconTint || darkIntensity != mDarkIntensity
- || !mTintArea.equals(tintArea);
- mIconTint = tint;
- mDarkIntensity = darkIntensity;
- mTintArea.set(tintArea);
- if (changed && isAttachedToWindow()) {
- applyIconTint();
- }
- }
-
- private void applyIconTint() {
- setTint(mVpn, DarkIconDispatcher.getTint(mTintArea, mVpn, mIconTint));
- setTint(mAirplane, DarkIconDispatcher.getTint(mTintArea, mAirplane, mIconTint));
- applyDarkIntensity(
- DarkIconDispatcher.getDarkIntensity(mTintArea, mWifi, mDarkIntensity),
- mWifi, mWifiDark);
- setTint(mWifiActivityIn,
- DarkIconDispatcher.getTint(mTintArea, mWifiActivityIn, mIconTint));
- setTint(mWifiActivityOut,
- DarkIconDispatcher.getTint(mTintArea, mWifiActivityOut, mIconTint));
- applyDarkIntensity(
- DarkIconDispatcher.getDarkIntensity(mTintArea, mEthernet, mDarkIntensity),
- mEthernet, mEthernetDark);
- for (int i = 0; i < mPhoneStates.size(); i++) {
- mPhoneStates.get(i).setIconTint(mIconTint, mDarkIntensity, mTintArea);
- }
- }
-
- private void applyDarkIntensity(float darkIntensity, View lightIcon, View darkIcon) {
- lightIcon.setAlpha(1 - darkIntensity);
- darkIcon.setAlpha(darkIntensity);
- }
-
- private void setTint(ImageView v, int tint) {
- v.setImageTintList(ColorStateList.valueOf(tint));
- }
-
- private int currentVpnIconId(boolean isBranded) {
- return isBranded ? R.drawable.stat_sys_branded_vpn : R.drawable.stat_sys_vpn_ic;
- }
-
- private class PhoneState {
- private final int mSubId;
- private boolean mMobileVisible = false;
- private int mMobileStrengthId = 0, mMobileTypeId = 0;
- private int mLastMobileStrengthId = -1;
- private int mLastMobileTypeId = -1;
- private String mMobileDescription, mMobileTypeDescription;
-
- private ViewGroup mMobileGroup;
- private ImageView mMobile, mMobileType, mMobileRoaming;
- private View mMobileRoamingSpace;
- public boolean mRoaming;
- private ImageView mMobileActivityIn;
- private ImageView mMobileActivityOut;
- public boolean mActivityIn;
- public boolean mActivityOut;
- private SignalDrawable mMobileSignalDrawable;
-
- public PhoneState(int subId, Context context) {
- ViewGroup root = (ViewGroup) LayoutInflater.from(context)
- .inflate(R.layout.mobile_signal_group, null);
- setViews(root);
- mSubId = subId;
- }
-
- public void setViews(ViewGroup root) {
- mMobileGroup = root;
- mMobile = root.findViewById(R.id.mobile_signal);
- mMobileType = root.findViewById(R.id.mobile_type);
- mMobileRoaming = root.findViewById(R.id.mobile_roaming);
- mMobileRoamingSpace = root.findViewById(R.id.mobile_roaming_space);
- mMobileActivityIn = root.findViewById(R.id.mobile_in);
- mMobileActivityOut = root.findViewById(R.id.mobile_out);
- mMobileSignalDrawable = new SignalDrawable(mMobile.getContext());
- mMobile.setImageDrawable(mMobileSignalDrawable);
- }
-
- public boolean apply(boolean isSecondaryIcon) {
- if (mMobileVisible && !mIsAirplaneMode) {
- if (mLastMobileStrengthId != mMobileStrengthId) {
- mMobile.getDrawable().setLevel(mMobileStrengthId);
- mLastMobileStrengthId = mMobileStrengthId;
- }
-
- if (mLastMobileTypeId != mMobileTypeId) {
- mMobileType.setImageResource(mMobileTypeId);
- mLastMobileTypeId = mMobileTypeId;
- }
-
- mMobileGroup.setContentDescription(mMobileTypeDescription
- + " " + mMobileDescription);
- mMobileGroup.setVisibility(View.VISIBLE);
- } else {
- mMobileGroup.setVisibility(View.GONE);
- }
-
- // When this isn't next to wifi, give it some extra padding between the signals.
- mMobileGroup.setPaddingRelative(isSecondaryIcon ? mSecondaryTelephonyPadding : 0,
- 0, 0, 0);
- mMobile.setPaddingRelative(mMobileDataIconStartPadding, 0, 0, 0);
-
- if (DEBUG) Log.d(TAG, String.format("mobile: %s sig=%d typ=%d",
- (mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, mMobileTypeId));
-
- mMobileType.setVisibility(mMobileTypeId != 0 ? View.VISIBLE : View.GONE);
- mMobileRoaming.setVisibility(mRoaming ? View.VISIBLE : View.GONE);
- mMobileRoamingSpace.setVisibility(mRoaming ? View.VISIBLE : View.GONE);
- mMobileActivityIn.setVisibility(mActivityIn ? View.VISIBLE : View.GONE);
- mMobileActivityOut.setVisibility(mActivityOut ? View.VISIBLE : View.GONE);
-
- return mMobileVisible;
- }
-
- public void populateAccessibilityEvent(AccessibilityEvent event) {
- if (mMobileVisible && mMobileGroup != null
- && mMobileGroup.getContentDescription() != null) {
- event.getText().add(mMobileGroup.getContentDescription());
- }
- }
-
- public void setIconTint(int tint, float darkIntensity, Rect tintArea) {
- mMobileSignalDrawable.setDarkIntensity(darkIntensity);
- setTint(mMobileType, DarkIconDispatcher.getTint(tintArea, mMobileType, tint));
- setTint(mMobileRoaming, DarkIconDispatcher.getTint(tintArea, mMobileRoaming,
- tint));
- setTint(mMobileActivityIn,
- DarkIconDispatcher.getTint(tintArea, mMobileActivityIn, tint));
- setTint(mMobileActivityOut,
- DarkIconDispatcher.getTint(tintArea, mMobileActivityOut, tint));
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java
deleted file mode 100644
index 0652227..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import com.android.internal.statusbar.StatusBarIcon;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Holds an array of {@link com.android.internal.statusbar.StatusBarIcon}s and draws them
- * in a linear layout
- */
-public class StatusBarIconContainer {
- private final List<StatusBarIcon> mIcons = new ArrayList<>();
-
- public StatusBarIconContainer(List<StatusBarIcon> icons) {
- mIcons.addAll(icons);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index e698e64..45db002 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -18,7 +18,6 @@
import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
-import android.os.Handler;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
@@ -51,7 +50,6 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
-import java.util.concurrent.TimeUnit;
/**
* NotificationEntryManager is responsible for the adding, removing, and updating of notifications.
@@ -64,11 +62,10 @@
NotificationUpdateHandler,
VisualStabilityManager.Callback {
private static final String TAG = "NotificationEntryMgr";
- protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- public static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
-
- protected final Context mContext;
+ private final Context mContext;
+ @VisibleForTesting
protected final HashMap<String, NotificationEntry> mPendingNotifications = new HashMap<>();
private final DeviceProvisionedController mDeviceProvisionedController =
@@ -80,13 +77,11 @@
private NotificationRemoteInputManager mRemoteInputManager;
private NotificationRowBinder mNotificationRowBinder;
- private final Handler mDeferredNotificationViewUpdateHandler;
- private Runnable mUpdateNotificationViewsCallback;
-
private NotificationPresenter mPresenter;
private NotificationListenerService.RankingMap mLatestRankingMap;
+ @VisibleForTesting
protected NotificationData mNotificationData;
- protected NotificationListContainer mListContainer;
+ private NotificationListContainer mListContainer;
@VisibleForTesting
final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders
= new ArrayList<>();
@@ -121,7 +116,6 @@
public NotificationEntryManager(Context context) {
mContext = context;
mNotificationData = new NotificationData();
- mDeferredNotificationViewUpdateHandler = new Handler();
}
/** Adds a {@link NotificationEntryListener}. */
@@ -156,7 +150,6 @@
NotificationListContainer listContainer,
HeadsUpManager headsUpManager) {
mPresenter = presenter;
- mUpdateNotificationViewsCallback = mPresenter::updateNotificationViews;
mNotificationData.setHeadsUpManager(headsUpManager);
mListContainer = listContainer;
@@ -180,14 +173,6 @@
return mNotificationData;
}
- protected Context getContext() {
- return mContext;
- }
-
- protected NotificationPresenter getPresenter() {
- return mPresenter;
- }
-
@Override
public void onReorderingAllowed() {
updateNotifications();
@@ -229,15 +214,6 @@
}
}
- private void maybeScheduleUpdateNotificationViews(NotificationEntry entry) {
- long audibleAlertTimeout = RECENTLY_ALERTED_THRESHOLD_MS
- - (System.currentTimeMillis() - entry.lastAudiblyAlertedMs);
- if (audibleAlertTimeout > 0) {
- mDeferredNotificationViewUpdateHandler.postDelayed(
- mUpdateNotificationViewsCallback, audibleAlertTimeout);
- }
- }
-
@Override
public void onAsyncInflationFinished(NotificationEntry entry,
@InflationFlag int inflatedFlags) {
@@ -256,7 +232,6 @@
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onNotificationAdded(entry);
}
- maybeScheduleUpdateNotificationViews(entry);
} else {
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onEntryReinflated(entry);
@@ -463,8 +438,6 @@
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onPostEntryUpdated(entry);
}
-
- maybeScheduleUpdateNotificationViews(entry);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 9e0dd84..95bd1ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -86,7 +86,6 @@
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -106,6 +105,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
@@ -122,6 +122,7 @@
private static final int MENU_VIEW_INDEX = 0;
private static final String TAG = "ExpandableNotifRow";
public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f;
+ private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
private boolean mUpdateBackgroundOnUpdate;
/**
@@ -1693,17 +1694,31 @@
/** Sets the last time the notification being displayed audibly alerted the user. */
public void setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) {
if (NotificationUtils.useNewInterruptionModel(mContext)) {
- boolean recentlyAudiblyAlerted = System.currentTimeMillis() - lastAudiblyAlertedMs
- < NotificationEntryManager.RECENTLY_ALERTED_THRESHOLD_MS;
- if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) {
- mChildrenContainer.getHeaderView().setRecentlyAudiblyAlerted(
- recentlyAudiblyAlerted);
+ long timeSinceAlertedAudibly = System.currentTimeMillis() - lastAudiblyAlertedMs;
+ boolean alertedRecently =
+ timeSinceAlertedAudibly < RECENTLY_ALERTED_THRESHOLD_MS;
+
+ applyAudiblyAlertedRecently(alertedRecently);
+
+ removeCallbacks(mExpireRecentlyAlertedFlag);
+ if (alertedRecently) {
+ long timeUntilNoLongerRecent =
+ RECENTLY_ALERTED_THRESHOLD_MS - timeSinceAlertedAudibly;
+ postDelayed(mExpireRecentlyAlertedFlag, timeUntilNoLongerRecent);
}
- mPrivateLayout.setRecentlyAudiblyAlerted(recentlyAudiblyAlerted);
- mPublicLayout.setRecentlyAudiblyAlerted(recentlyAudiblyAlerted);
}
}
+ private final Runnable mExpireRecentlyAlertedFlag = () -> applyAudiblyAlertedRecently(false);
+
+ private void applyAudiblyAlertedRecently(boolean audiblyAlertedRecently) {
+ if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) {
+ mChildrenContainer.getHeaderView().setRecentlyAudiblyAlerted(audiblyAlertedRecently);
+ }
+ mPrivateLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
+ mPublicLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
+ }
+
public View.OnClickListener getAppOpsOnClickListener() {
return mOnAppOpsClickListener;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index 280dda0..577e8d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -84,6 +84,14 @@
public void onCancelled() {
pulseFinished();
}
+
+ /**
+ * Whether to fade out wallpaper.
+ */
+ @Override
+ public boolean isFadeOutWallpaper() {
+ return mPulseReason == DozeLog.PULSE_REASON_DOCKING;
+ }
};
public DozeScrimController(DozeParameters dozeParameters) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index d873b0c..75adf50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -956,7 +956,7 @@
handled = true;
}
handled |= super.onTouchEvent(event);
- return mDozing ? handled : true;
+ return !mDozing || mPulsing || handled;
}
private boolean handleQsTouch(MotionEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index e25c829..853d7ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -275,9 +275,12 @@
holdWakeLock();
}
- // AOD wallpapers should fade away after a while
- if (mWallpaperSupportsAmbientMode && mDozeParameters.getAlwaysOn()
- && mState == ScrimState.AOD) {
+ // AOD wallpapers should fade away after a while.
+ // Docking pulses may take a long time, wallpapers should also fade away after a while.
+ if (mWallpaperSupportsAmbientMode && (
+ mDozeParameters.getAlwaysOn() && mState == ScrimState.AOD
+ || mState == ScrimState.PULSING && mCallback != null
+ && mCallback.isFadeOutWallpaper())) {
if (!mWallpaperVisibilityTimedOut) {
mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(),
AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
@@ -329,7 +332,7 @@
@VisibleForTesting
protected void onHideWallpaperTimeout() {
- if (mState != ScrimState.AOD) {
+ if (mState != ScrimState.AOD && mState != ScrimState.PULSING) {
return;
}
@@ -364,7 +367,7 @@
mExpansionFraction = fraction;
final boolean keyguardOrUnlocked = mState == ScrimState.UNLOCKED
- || mState == ScrimState.KEYGUARD;
+ || mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING;
if (!keyguardOrUnlocked || !mExpansionAffectsAlpha) {
return;
}
@@ -409,7 +412,7 @@
behindFraction = (float) Math.pow(behindFraction, 0.8f);
mCurrentBehindAlpha = behindFraction * GRADIENT_SCRIM_ALPHA_BUSY;
mCurrentInFrontAlpha = 0;
- } else if (mState == ScrimState.KEYGUARD) {
+ } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING) {
// Either darken of make the scrim transparent when you
// pull down the shade
float interpolatedFract = getInterpolatedFraction();
@@ -504,7 +507,8 @@
// We want to override the back scrim opacity for the AOD state
// when it's time to fade the wallpaper away.
- boolean aodWallpaperTimeout = mState == ScrimState.AOD && mWallpaperVisibilityTimedOut;
+ boolean aodWallpaperTimeout = (mState == ScrimState.AOD || mState == ScrimState.PULSING)
+ && mWallpaperVisibilityTimedOut;
// We also want to hide FLAG_SHOW_WHEN_LOCKED activities under the scrim.
boolean occludedKeyguard = (mState == ScrimState.PULSING || mState == ScrimState.AOD)
&& mKeyguardOccluded;
@@ -562,8 +566,8 @@
if (alpha == 0f) {
scrim.setClickable(false);
} else {
- // Eat touch events (unless dozing or pulsing).
- scrim.setClickable(mState != ScrimState.AOD && mState != ScrimState.PULSING);
+ // Eat touch events (unless dozing).
+ scrim.setClickable(mState != ScrimState.AOD);
}
updateScrim(scrim, alpha);
}
@@ -917,6 +921,9 @@
}
default void onCancelled() {
}
+ default boolean isFadeOutWallpaper() {
+ return false;
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index fb3c4aa..72519ba3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -219,6 +219,14 @@
public void prepare(ScrimState previousState) {
}
+ /**
+ * Check if lockscreen wallpaper or music album art exists.
+ * @return true if lockscreen wallpaper or music album art exists.
+ */
+ public boolean hasBackdrop() {
+ return mHasBackdrop;
+ }
+
public int getIndex() {
return mIndex;
}
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 6d78f72..9abd86d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -70,6 +70,7 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -98,6 +99,7 @@
import android.service.notification.StatusBarNotification;
import android.util.DisplayMetrics;
import android.util.EventLog;
+import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.Slog;
import android.view.Display;
@@ -478,8 +480,13 @@
WallpaperInfo info = wallpaperManager.getWallpaperInfo(UserHandle.USER_CURRENT);
final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_dozeSupportsAodWallpaper);
+ final boolean aodImageWallpaperEnabled = FeatureFlagUtils.isEnabled(mContext,
+ FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED);
+ updateAodMaskVisibility(deviceSupportsAodWallpaper && aodImageWallpaperEnabled);
+ // If WallpaperInfo is null, it must be ImageWallpaper.
final boolean supportsAmbientMode = deviceSupportsAodWallpaper
- && info != null && info.supportsAmbientMode();
+ && (info == null && aodImageWallpaperEnabled
+ || info != null && info.supportsAmbientMode());
mStatusBarWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
@@ -581,6 +588,7 @@
protected NotificationPresenter mPresenter;
private NotificationActivityStarter mNotificationActivityStarter;
private boolean mPulsing;
+ private ContentObserver mFeatureFlagObserver;
@Override
public void onActiveStateChanged(int code, int uid, String packageName, boolean active) {
@@ -697,6 +705,9 @@
mContext.registerReceiverAsUser(mWallpaperChangedReceiver, UserHandle.ALL,
wallpaperChangedFilter, null /* broadcastPermission */, null /* scheduler */);
mWallpaperChangedReceiver.onReceive(mContext, null);
+ mFeatureFlagObserver = new FeatureFlagObserver(
+ FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED /* feature */,
+ () -> mWallpaperChangedReceiver.onReceive(mContext, null) /* callback */);
// Set up the initial notification state. This needs to happen before CommandQueue.disable()
setUpPresenter();
@@ -3573,10 +3584,7 @@
mVisualStabilityManager.setScreenOn(false);
updateVisibleToUser();
- // We need to disable touch events because these might
- // collapse the panel after we expanded it, and thus we would end up with a blank
- // Keyguard.
- mNotificationPanel.setTouchAndAnimationDisabled(true);
+ updateNotificationPanelTouchState();
mStatusBarWindow.cancelCurrentTouch();
if (mLaunchCameraOnFinishedGoingToSleep) {
mLaunchCameraOnFinishedGoingToSleep = false;
@@ -3599,13 +3607,22 @@
mDeviceInteractive = true;
mAmbientPulseManager.releaseAllImmediately();
mVisualStabilityManager.setScreenOn(true);
- mNotificationPanel.setTouchAndAnimationDisabled(false);
+ updateNotificationPanelTouchState();
updateVisibleToUser();
updateIsKeyguard();
mDozeServiceHost.stopDozing();
}
};
+ /**
+ * We need to disable touch events because these might
+ * collapse the panel after we expanded it, and thus we would end up with a blank
+ * Keyguard.
+ */
+ private void updateNotificationPanelTouchState() {
+ mNotificationPanel.setTouchAndAnimationDisabled(!mDeviceInteractive && !mPulsing);
+ }
+
final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
@Override
public void onScreenTurningOn() {
@@ -3871,17 +3888,15 @@
@Override
public void onPulseStarted() {
callback.onPulseStarted();
- if (mAmbientPulseManager.hasNotifications()) {
- // Only pulse the stack scroller if there's actually something to show.
- // Otherwise just show the always-on screen.
- setPulsing(true);
- }
+ updateNotificationPanelTouchState();
+ setPulsing(true);
}
@Override
public void onPulseFinished() {
mPulsing = false;
callback.onPulseFinished();
+ updateNotificationPanelTouchState();
setPulsing(false);
}
@@ -4411,4 +4426,33 @@
public @TransitionMode int getStatusBarMode() {
return mStatusBarMode;
}
+
+ private void updateAodMaskVisibility(boolean supportsAodWallpaper) {
+ View mask = mStatusBarWindow.findViewById(R.id.aod_mask);
+ if (mask != null) {
+ mask.setVisibility(supportsAodWallpaper ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+
+ private final class FeatureFlagObserver extends ContentObserver {
+ private final Runnable mCallback;
+
+ FeatureFlagObserver(String feature, Runnable callback) {
+ this(null, feature, callback);
+ }
+
+ private FeatureFlagObserver(Handler handler, String feature, Runnable callback) {
+ super(handler);
+ mCallback = callback;
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(feature), false, this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ if (mCallback != null) {
+ mStatusBarWindow.post(mCallback);
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 0f8970f..bb23608 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -187,7 +187,7 @@
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else if (bouncerNeedsScrimming()) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
- } else if (mShowing && !mDozing) {
+ } else if (mShowing) {
if (!isWakeAndUnlocking() && !mStatusBar.isInLaunchTransition()) {
mBouncer.setExpansion(expansion);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 53e461d..8b25c34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -339,7 +339,7 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
NotificationStackScrollLayout stackScrollLayout = getStackScrollLayout();
- if (mService.isDozing() && !stackScrollLayout.hasPulsingNotifications()) {
+ if (mService.isDozing() && !mService.isPulsing()) {
// Capture all touch events in always-on.
return true;
}
@@ -347,8 +347,7 @@
if (mNotificationPanel.isFullyExpanded()
&& stackScrollLayout.getVisibility() == View.VISIBLE
&& mStatusBarStateController.getState() == StatusBarState.KEYGUARD
- && !mService.isBouncerShowing()
- && !mService.isDozing()) {
+ && !mService.isBouncerShowing()) {
intercept = mDragDownHelper.onInterceptTouchEvent(ev);
}
if (!intercept) {
@@ -369,7 +368,7 @@
boolean handled = false;
if (mService.isDozing()) {
mDoubleTapHelper.onTouchEvent(ev);
- handled = true;
+ handled = !mService.isPulsing();
}
if ((mStatusBarStateController.getState() == StatusBarState.KEYGUARD && !handled)
|| mDragDownHelper.isDraggingDown()) {
diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java b/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java
new file mode 100644
index 0000000..52cabe2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallpaper;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.util.AttributeSet;
+import android.util.FeatureFlagUtils;
+import android.util.Log;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.widget.ImageView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
+import com.android.systemui.statusbar.phone.ScrimState;
+
+/**
+ * A view that draws mask upon either image wallpaper or music album art in AOD.
+ */
+public class AodMaskView extends ImageView implements StatusBarStateController.StateListener,
+ ImageWallpaperTransformer.TransformationListener {
+ private static final String TAG = AodMaskView.class.getSimpleName();
+ private static final int TRANSITION_DURATION = 1000;
+
+ private static final AnimatableProperty TRANSITION_PROGRESS = AnimatableProperty.from(
+ "transition_progress",
+ AodMaskView::setTransitionAmount,
+ AodMaskView::getTransitionAmount,
+ R.id.aod_mask_transition_progress_tag,
+ R.id.aod_mask_transition_progress_start_tag,
+ R.id.aod_mask_transition_progress_end_tag
+ );
+
+ private final AnimationProperties mTransitionProperties = new AnimationProperties();
+ private final ImageWallpaperTransformer mTransformer;
+ private final RectF mBounds = new RectF();
+ private boolean mChangingStates;
+ private boolean mNeedMask;
+ private float mTransitionAmount;
+ private final WallpaperManager mWallpaperManager;
+ private final DisplayManager mDisplayManager;
+ private DisplayListener mDisplayListener = new DisplayListener() {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ // We just support DEFAULT_DISPLAY currently.
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ mTransformer.updateDisplayInfo(getDisplayInfo(displayId));
+ }
+ }
+ };
+
+ public AodMaskView(Context context) {
+ this(context, null);
+ }
+
+ public AodMaskView(Context context, AttributeSet attrs) {
+ this(context, attrs, null);
+ }
+
+ @VisibleForTesting
+ public AodMaskView(Context context, AttributeSet attrs, ImageWallpaperTransformer transformer) {
+ super(context, attrs);
+ setClickable(false);
+
+ StatusBarStateController controller = Dependency.get(StatusBarStateController.class);
+ if (controller != null) {
+ controller.addCallback(this);
+ } else {
+ Log.d(TAG, "Can not get StatusBarStateController!");
+ }
+
+ mDisplayManager = (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE);
+ mDisplayManager.registerDisplayListener(mDisplayListener, null);
+ mWallpaperManager =
+ (WallpaperManager) getContext().getSystemService(Context.WALLPAPER_SERVICE);
+
+ if (transformer == null) {
+ mTransformer = new ImageWallpaperTransformer(this);
+ mTransformer.addFilter(new ScrimFilter());
+ mTransformer.addFilter(new VignetteFilter());
+ mTransformer.updateOffsets();
+ mTransformer.updateDisplayInfo(getDisplayInfo(Display.DEFAULT_DISPLAY));
+
+ mTransitionProperties.setDuration(TRANSITION_DURATION);
+ mTransitionProperties.setAnimationFinishListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mTransformer.setIsTransiting(false);
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mTransformer.setIsTransiting(true);
+ }
+ });
+ } else {
+ // This part should only be hit by test cases.
+ mTransformer = transformer;
+ }
+ }
+
+ private DisplayInfo getDisplayInfo(int displayId) {
+ DisplayInfo displayInfo = new DisplayInfo();
+ mDisplayManager.getDisplay(displayId).getDisplayInfo(displayInfo);
+ return displayInfo;
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ mBounds.set(0, 0, w, h);
+ mTransformer.updateOffsets();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (mNeedMask) {
+ mTransformer.drawTransformedImage(canvas, null /* target */, null /* src */, mBounds);
+ }
+ }
+
+ private boolean checkIfNeedMask() {
+ // We need mask for ImageWallpaper / LockScreen Wallpaper (Music album art).
+ return mWallpaperManager.getWallpaperInfo() == null || ScrimState.AOD.hasBackdrop();
+ }
+
+ @Override
+ public void onStatePreChange(int oldState, int newState) {
+ mChangingStates = oldState != newState;
+ mNeedMask = checkIfNeedMask();
+ }
+
+ @Override
+ public void onStatePostChange() {
+ mChangingStates = false;
+ }
+
+ @Override
+ public void onStateChanged(int newState) {
+ }
+
+ @Override
+ public void onDozingChanged(boolean isDozing) {
+ if (!mNeedMask) {
+ return;
+ }
+
+ boolean enabled = checkFeatureIsEnabled();
+ mTransformer.updateAmbientModeState(enabled && isDozing);
+
+ if (enabled && !mChangingStates) {
+ setAnimatorProperty(isDozing);
+ } else {
+ invalidate();
+ }
+ }
+
+ private boolean checkFeatureIsEnabled() {
+ return FeatureFlagUtils.isEnabled(
+ getContext(), FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED);
+ }
+
+ @VisibleForTesting
+ void setAnimatorProperty(boolean isDozing) {
+ PropertyAnimator.setProperty(
+ this,
+ TRANSITION_PROGRESS,
+ isDozing ? 1f : 0f /* newEndValue */,
+ mTransitionProperties,
+ true /* animated */);
+ }
+
+ @Override
+ public void onTransformationUpdated() {
+ invalidate();
+ }
+
+ private void setTransitionAmount(float amount) {
+ mTransitionAmount = amount;
+ mTransformer.updateTransitionAmount(amount);
+ }
+
+ private float getTransitionAmount() {
+ return mTransitionAmount;
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperFilter.java b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperFilter.java
new file mode 100644
index 0000000..d457dac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperFilter.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallpaper;
+
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+/**
+ * Abstract filter used by static image wallpaper.
+ */
+abstract class ImageWallpaperFilter {
+ protected static final boolean DEBUG = false;
+
+ private ImageWallpaperTransformer mTransformer;
+
+ /**
+ * Apply this filter to the bitmap before drawing on canvas.
+ * @param c The canvas that will draw to.
+ * @param bitmap The bitmap to apply this filter.
+ * @param src The subset of the bitmap to be drawn.
+ * @param dest The rectangle that the bitmap will be scaled/translated to fit into.
+ */
+ public abstract void apply(@NonNull Canvas c, @Nullable Bitmap bitmap,
+ @Nullable Rect src, @NonNull RectF dest);
+
+ /**
+ * Notifies the occurrence of built-in transition of the animation.
+ * @param animator The animator which was animated.
+ */
+ public abstract void onAnimatorUpdate(ValueAnimator animator);
+
+ /**
+ * Notifies the occurrence of another transition of the animation.
+ * @param amount The transition amount.
+ */
+ public abstract void onTransitionAmountUpdate(float amount);
+
+ /**
+ * To set the associated transformer.
+ * @param transformer The transformer that is associated with this filter.
+ */
+ public void setTransformer(ImageWallpaperTransformer transformer) {
+ if (transformer != null) {
+ mTransformer = transformer;
+ }
+ }
+
+ protected ImageWallpaperTransformer getTransformer() {
+ return mTransformer;
+ }
+
+ /**
+ * Notifies the changing of the offset value of the ImageWallpaper.
+ * @param force True to force re-evaluate offsets.
+ * @param xOffset X offset of the ImageWallpaper in percentage.
+ * @param yOffset Y offset of the ImageWallpaper in percentage.
+ */
+ public void onOffsetsUpdate(boolean force, float xOffset, float yOffset) {
+ // No-op
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperTransformer.java b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperTransformer.java
new file mode 100644
index 0000000..25b0b0a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperTransformer.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallpaper;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.DisplayInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class is used to manage the filters that will be applied.
+ */
+public class ImageWallpaperTransformer {
+ private static final String TAG = ImageWallpaperTransformer.class.getSimpleName();
+
+ private DisplayInfo mDisplayInfo;
+ private final List<ImageWallpaperFilter> mFilters;
+ private final TransformationListener mListener;
+ private boolean mIsInAmbientMode;
+ private boolean mIsTransiting;
+
+ /**
+ * Constructor.
+ * @param listener A listener to inform you the transformation has updated.
+ */
+ public ImageWallpaperTransformer(TransformationListener listener) {
+ mFilters = new ArrayList<>();
+ mListener = listener;
+ }
+
+ /**
+ * Claim that we want to use the specified filter.
+ * @param filter The filter will be used.
+ */
+ public void addFilter(ImageWallpaperFilter filter) {
+ if (filter != null) {
+ filter.setTransformer(this);
+ mFilters.add(filter);
+ }
+ }
+
+ /**
+ * Check if any transition is running.
+ * @return True if the transition is running, false otherwise.
+ */
+ boolean isTransiting() {
+ return mIsTransiting;
+ }
+
+ /**
+ * Indicate if any transition is running. <br/>
+ * @param isTransiting True if the transition is running.
+ */
+ void setIsTransiting(boolean isTransiting) {
+ mIsTransiting = isTransiting;
+ }
+
+ /**
+ * Check if the device is in ambient mode.
+ * @return True if the device is in ambient mode, false otherwise.
+ */
+ public boolean isInAmbientMode() {
+ return mIsInAmbientMode;
+ }
+
+ /**
+ * Update current state of ambient mode.
+ * @param isInAmbientMode Current ambient mode state.
+ */
+ public void updateAmbientModeState(boolean isInAmbientMode) {
+ mIsInAmbientMode = isInAmbientMode;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ int idx = 0;
+ for (ImageWallpaperFilter filter : mFilters) {
+ sb.append(idx++).append(": ").append(filter.getClass().getSimpleName()).append("\n");
+ }
+ if (sb.length() == 0) {
+ sb.append("No filters applied");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Set a new display info.
+ * @param displayInfo New display info.
+ */
+ public void updateDisplayInfo(DisplayInfo displayInfo) {
+ mDisplayInfo = displayInfo;
+ }
+
+ /**
+ * To get current display info.
+ * @return Current display info.
+ */
+ public DisplayInfo getDisplayInfo() {
+ return mDisplayInfo;
+ }
+
+ /**
+ * Update the offsets with default value.
+ */
+ public void updateOffsets() {
+ this.updateOffsets(true, 0f, .5f);
+ }
+
+ /**
+ * To notify the filters that the offset of the ImageWallpaper changes.
+ * @param force True to force re-evaluate offsets.
+ * @param offsetX X offset of the ImageWallpaper in percentage.
+ * @param offsetY Y offset of the ImageWallpaper in percentage.
+ */
+ public void updateOffsets(boolean force, float offsetX, float offsetY) {
+ mFilters.forEach(filter -> filter.onOffsetsUpdate(force, offsetX, offsetY));
+ }
+
+ /**
+ * Apply all specified filters to the bitmap then draw to the canvas.
+ * @param c The canvas that will draw to.
+ * @param target The bitmap to apply filters.
+ * @param src The subset of the bitmap to be drawn
+ * @param dest The rectangle that the bitmap will be scaled/translated to fit into.
+ */
+ void drawTransformedImage(@NonNull Canvas c, @Nullable Bitmap target,
+ @Nullable Rect src, @NonNull RectF dest) {
+ mFilters.forEach(filter -> filter.apply(c, target, src, dest));
+ }
+
+ /**
+ * Update the transition amount. <br/>
+ * Must invoke this to update transition amount if not running built-in transition.
+ * @param amount The transition amount.
+ */
+ void updateTransitionAmount(float amount) {
+ mFilters.forEach(filter -> filter.onTransitionAmountUpdate(amount));
+ if (mListener != null) {
+ mListener.onTransformationUpdated();
+ }
+ }
+
+ /**
+ * An interface that informs the transformation status.
+ */
+ public interface TransformationListener {
+ /**
+ * Notifies the update of the transformation.
+ */
+ void onTransformationUpdated();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/ScrimFilter.java b/packages/SystemUI/src/com/android/systemui/wallpaper/ScrimFilter.java
new file mode 100644
index 0000000..637e48e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpaper/ScrimFilter.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallpaper;
+
+import android.animation.ValueAnimator;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+/**
+ * A filter that implements 70% black scrim effect.
+ */
+public class ScrimFilter extends ImageWallpaperFilter {
+ private static final int MAX_ALPHA = (int) (255 * .7f);
+ private static final int MIN_ALPHA = 0;
+
+ private final Paint mPaint;
+
+ public ScrimFilter() {
+ mPaint = new Paint();
+ mPaint.setColor(Color.BLACK);
+ mPaint.setAlpha(MAX_ALPHA);
+ }
+
+ @Override
+ public void apply(Canvas c, Bitmap bitmap, Rect src, RectF dest) {
+ ImageWallpaperTransformer transformer = getTransformer();
+
+ // If it is not in the transition, we need to set the property according to aod state.
+ if (!transformer.isTransiting()) {
+ mPaint.setAlpha(transformer.isInAmbientMode() ? MAX_ALPHA : MIN_ALPHA);
+ }
+
+ c.drawRect(dest, mPaint);
+ }
+
+ @Override
+ public void onAnimatorUpdate(ValueAnimator animator) {
+ ImageWallpaperTransformer transformer = getTransformer();
+ float fraction = animator.getAnimatedFraction();
+ float factor = transformer.isInAmbientMode() ? fraction : 1f - fraction;
+ mPaint.setAlpha((int) (factor * MAX_ALPHA));
+ }
+
+ @Override
+ public void onTransitionAmountUpdate(float amount) {
+ mPaint.setAlpha((int) (amount * MAX_ALPHA));
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/VignetteFilter.java b/packages/SystemUI/src/com/android/systemui/wallpaper/VignetteFilter.java
new file mode 100644
index 0000000..ad0b98b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpaper/VignetteFilter.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallpaper;
+
+import android.animation.ValueAnimator;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.graphics.RadialGradient;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.util.Log;
+import android.view.DisplayInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * A filter that implements vignette effect.
+ */
+public class VignetteFilter extends ImageWallpaperFilter {
+ private static final String TAG = VignetteFilter.class.getSimpleName();
+ private static final int MAX_ALPHA = 255;
+ private static final int MIN_ALPHA = 0;
+
+ private final Paint mPaint;
+ private final Matrix mMatrix;
+ private final Shader mShader;
+
+ private float mXOffset;
+ private float mYOffset;
+ private float mCenterX;
+ private float mCenterY;
+ private float mStretchX;
+ private float mStretchY;
+ private boolean mCalculateOffsetNeeded;
+
+ public VignetteFilter() {
+ mPaint = new Paint();
+ mMatrix = new Matrix();
+ mShader = new RadialGradient(0, 0, 1,
+ Color.TRANSPARENT, Color.BLACK, Shader.TileMode.CLAMP);
+ }
+
+ @Override
+ public void apply(Canvas c, Bitmap bitmap, Rect src, RectF dest) {
+ DisplayInfo info = getTransformer().getDisplayInfo();
+
+ if (mCalculateOffsetNeeded) {
+ int lw = info.logicalWidth;
+ int lh = info.logicalHeight;
+ mCenterX = lw / 2 + (dest.width() - lw) * mXOffset;
+ mCenterY = lh / 2 + (dest.height() - lh) * mYOffset;
+ mStretchX = info.logicalWidth / 2;
+ mStretchY = info.logicalHeight / 2;
+ mCalculateOffsetNeeded = false;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "apply: lw=" + info.logicalWidth + ", lh=" + info.logicalHeight
+ + ", center=(" + mCenterX + "," + mCenterY + ")"
+ + ", stretch=(" + mStretchX + "," + mStretchY + ")");
+ }
+
+ mMatrix.reset();
+ mMatrix.postTranslate(mCenterX, mCenterY);
+ mMatrix.postScale(mStretchX, mStretchY, mCenterX, mCenterY);
+ mShader.setLocalMatrix(mMatrix);
+ mPaint.setShader(mShader);
+
+ ImageWallpaperTransformer transformer = getTransformer();
+
+ // If it is not in the transition, we need to set the property according to aod state.
+ if (!transformer.isTransiting()) {
+ mPaint.setAlpha(transformer.isInAmbientMode() ? MAX_ALPHA : MIN_ALPHA);
+ }
+
+ c.drawRect(dest, mPaint);
+ }
+
+ @Override
+ public void onAnimatorUpdate(ValueAnimator animator) {
+ ImageWallpaperTransformer transformer = getTransformer();
+ float fraction = animator.getAnimatedFraction();
+ float factor = transformer.isInAmbientMode() ? fraction : 1f - fraction;
+ mPaint.setAlpha((int) (factor * MAX_ALPHA));
+ }
+
+ @Override
+ public void onTransitionAmountUpdate(float amount) {
+ mPaint.setAlpha((int) (amount * MAX_ALPHA));
+ }
+
+ @Override
+ public void onOffsetsUpdate(boolean force, float xOffset, float yOffset) {
+ if (force || mXOffset != xOffset || mYOffset != yOffset) {
+ mXOffset = xOffset;
+ mYOffset = yOffset;
+ mCalculateOffsetNeeded = true;
+ }
+ }
+
+ @VisibleForTesting
+ public PointF getCenterPoint() {
+ return new PointF(mCenterX, mCenterY);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 1844df5..8e02f57 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -21,7 +21,6 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -42,19 +41,18 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.function.Consumer;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
// Need to run on the main thread because KeyguardSliceView$Row init checks for
@@ -62,7 +60,6 @@
// the keyguard_clcok_switch layout is inflated.
@RunWithLooper(setAsMainLooper = true)
public class KeyguardClockSwitchTest extends SysuiTestCase {
- private PluginManager mPluginManager;
private FrameLayout mClockContainer;
private StatusBarStateController.StateListener mStateListener;
@@ -73,7 +70,6 @@
@Before
public void setUp() {
- mPluginManager = mDependency.injectMockDependency(PluginManager.class);
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
mKeyguardClockSwitch =
(KeyguardClockSwitch) layoutInflater.inflate(R.layout.keyguard_clock_switch, null);
@@ -84,29 +80,12 @@
}
@Test
- public void onAttachToWindow_addPluginListener() {
- mKeyguardClockSwitch.onAttachedToWindow();
-
- ArgumentCaptor<PluginListener> listener = ArgumentCaptor.forClass(PluginListener.class);
- verify(mPluginManager).addPluginListener(listener.capture(), eq(ClockPlugin.class));
- }
-
- @Test
- public void onDetachToWindow_removePluginListener() {
- mKeyguardClockSwitch.onDetachedFromWindow();
-
- ArgumentCaptor<PluginListener> listener = ArgumentCaptor.forClass(PluginListener.class);
- verify(mPluginManager).removePluginListener(listener.capture());
- }
-
- @Test
public void onPluginConnected_showPluginClock() {
ClockPlugin plugin = mock(ClockPlugin.class);
TextClock pluginView = new TextClock(getContext());
when(plugin.getView()).thenReturn(pluginView);
- PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
- listener.onPluginConnected(plugin, null);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
verify(mClockView).setVisibility(GONE);
assertThat(plugin.getView().getParent()).isEqualTo(mClockContainer);
@@ -122,9 +101,8 @@
ClockPlugin plugin = mock(ClockPlugin.class);
TextClock pluginView = new TextClock(getContext());
when(plugin.getBigClockView()).thenReturn(pluginView);
- PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
// WHEN the plugin is connected
- listener.onPluginConnected(plugin, null);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
// THEN the big clock container is visible and it is the parent of the
// big clock view.
assertThat(bigClockContainer.getVisibility()).isEqualTo(VISIBLE);
@@ -134,8 +112,7 @@
@Test
public void onPluginConnected_nullView() {
ClockPlugin plugin = mock(ClockPlugin.class);
- PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
- listener.onPluginConnected(plugin, null);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
verify(mClockView, never()).setVisibility(GONE);
}
@@ -144,12 +121,11 @@
// GIVEN a plugin has already connected
ClockPlugin plugin1 = mock(ClockPlugin.class);
when(plugin1.getView()).thenReturn(new TextClock(getContext()));
- PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
- listener.onPluginConnected(plugin1, null);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin1);
// WHEN a second plugin is connected
ClockPlugin plugin2 = mock(ClockPlugin.class);
when(plugin2.getView()).thenReturn(new TextClock(getContext()));
- listener.onPluginConnected(plugin2, null);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin2);
// THEN only the view from the second plugin should be a child of KeyguardClockSwitch.
assertThat(plugin2.getView().getParent()).isEqualTo(mClockContainer);
assertThat(plugin1.getView().getParent()).isNull();
@@ -161,10 +137,9 @@
TextClock pluginView = new TextClock(getContext());
when(plugin.getView()).thenReturn(pluginView);
mClockView.setVisibility(GONE);
- PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
- listener.onPluginConnected(plugin, null);
- listener.onPluginDisconnected(plugin);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(null);
verify(mClockView).setVisibility(VISIBLE);
assertThat(plugin.getView().getParent()).isNull();
@@ -180,10 +155,9 @@
ClockPlugin plugin = mock(ClockPlugin.class);
TextClock pluginView = new TextClock(getContext());
when(plugin.getBigClockView()).thenReturn(pluginView);
- PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
- listener.onPluginConnected(plugin, null);
- // WHEN the plugin is disconnected
- listener.onPluginDisconnected(plugin);
+ // WHEN the plugin is connected and then disconnected
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(null);
// THEN the big lock container is GONE and the big clock view doesn't have
// a parent.
assertThat(bigClockContainer.getVisibility()).isEqualTo(GONE);
@@ -193,41 +167,23 @@
@Test
public void onPluginDisconnected_nullView() {
ClockPlugin plugin = mock(ClockPlugin.class);
- PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
- listener.onPluginConnected(plugin, null);
- listener.onPluginDisconnected(plugin);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(null);
verify(mClockView, never()).setVisibility(GONE);
}
@Test
- public void onPluginDisconnected_firstOfTwoDisconnected() {
- // GIVEN two plugins are connected
- ClockPlugin plugin1 = mock(ClockPlugin.class);
- when(plugin1.getView()).thenReturn(new TextClock(getContext()));
- PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
- listener.onPluginConnected(plugin1, null);
- ClockPlugin plugin2 = mock(ClockPlugin.class);
- when(plugin2.getView()).thenReturn(new TextClock(getContext()));
- listener.onPluginConnected(plugin2, null);
- // WHEN the first plugin is disconnected
- listener.onPluginDisconnected(plugin1);
- // THEN the view from the second plugin is still a child of KeyguardClockSwitch.
- assertThat(plugin2.getView().getParent()).isEqualTo(mClockContainer);
- assertThat(plugin1.getView().getParent()).isNull();
- }
-
- @Test
public void onPluginDisconnected_secondOfTwoDisconnected() {
// GIVEN two plugins are connected
ClockPlugin plugin1 = mock(ClockPlugin.class);
when(plugin1.getView()).thenReturn(new TextClock(getContext()));
- PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
- listener.onPluginConnected(plugin1, null);
+ Consumer<ClockPlugin> consumer = mKeyguardClockSwitch.getClockPluginConsumer();
+ consumer.accept(plugin1);
ClockPlugin plugin2 = mock(ClockPlugin.class);
when(plugin2.getView()).thenReturn(new TextClock(getContext()));
- listener.onPluginConnected(plugin2, null);
+ consumer.accept(plugin2);
// WHEN the second plugin is disconnected
- listener.onPluginDisconnected(plugin2);
+ consumer.accept(null);
// THEN the default clock should be shown.
verify(mClockView).setVisibility(VISIBLE);
assertThat(plugin1.getView().getParent()).isNull();
@@ -246,8 +202,7 @@
ClockPlugin plugin = mock(ClockPlugin.class);
TextClock pluginView = new TextClock(getContext());
when(plugin.getView()).thenReturn(pluginView);
- PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
- listener.onPluginConnected(plugin, null);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
mKeyguardClockSwitch.setTextColor(Color.WHITE);
@@ -271,8 +226,7 @@
TextClock pluginView = new TextClock(getContext());
when(plugin.getView()).thenReturn(pluginView);
Style style = mock(Style.class);
- PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
- listener.onPluginConnected(plugin, null);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
mKeyguardClockSwitch.setStyle(style);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
index 521d5d1..53ad0b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
@@ -16,17 +16,12 @@
package com.android.systemui;
-
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.graphics.Bitmap;
@@ -58,13 +53,15 @@
@Mock private SurfaceHolder mSurfaceHolder;
@Mock private DisplayInfo mDisplayInfo;
- CountDownLatch mEventCountdown;
+ private CountDownLatch mEventCountdown;
+ private CountDownLatch mAmbientEventCountdown;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mEventCountdown = new CountDownLatch(1);
+ mAmbientEventCountdown = new CountDownLatch(2);
mImageWallpaper = new ImageWallpaper() {
@Override
@@ -86,6 +83,11 @@
assertTrue("mFixedSizeAllowed should be true", allowed);
mEventCountdown.countDown();
}
+
+ @Override
+ public void onAmbientModeChanged(boolean inAmbientMode, long duration) {
+ mAmbientEventCountdown.countDown();
+ }
};
}
};
@@ -132,4 +134,23 @@
verify(mSurfaceHolder, times(1)).setFixedSize(ImageWallpaper.DrawableEngine.MIN_BACKGROUND_WIDTH, ImageWallpaper.DrawableEngine.MIN_BACKGROUND_HEIGHT);
}
+ @Test
+ public void testDeliversAmbientModeChanged() {
+ ImageWallpaper.DrawableEngine wallpaperEngine =
+ (ImageWallpaper.DrawableEngine) mImageWallpaper.onCreateEngine();
+
+ assertEquals("setFixedSizeAllowed should have been called.",
+ 0, mEventCountdown.getCount());
+
+ wallpaperEngine.setCreated(true);
+ wallpaperEngine.doAmbientModeChanged(false, 1000);
+ assertFalse("ambient mode should be false", wallpaperEngine.isInAmbientMode());
+ assertEquals("onAmbientModeChanged should have been called.",
+ 1, mAmbientEventCountdown.getCount());
+
+ wallpaperEngine.doAmbientModeChanged(true, 1000);
+ assertTrue("ambient mode should be true", wallpaperEngine.isInAmbientMode());
+ assertEquals("onAmbientModeChanged should have been called.",
+ 0, mAmbientEventCountdown.getCount());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 146c5d6..8eb42c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -54,7 +54,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.function.Consumer;
@@ -94,6 +94,7 @@
},
visible -> mScrimVisibility = visible, mDozeParamenters, mAlarmManager);
mScrimController.setHasBackdrop(false);
+ mScrimController.setWallpaperSupportsAmbientMode(false);
}
@Test
@@ -474,6 +475,26 @@
}
@Test
+ public void testHoldsPulsingWallpaperAnimationLock() {
+ // Pre-conditions
+ mScrimController.transitionTo(ScrimState.PULSING, new ScrimController.Callback() {
+ @Override
+ public boolean isFadeOutWallpaper() {
+ return true;
+ }
+ });
+ mScrimController.finishAnimationsImmediately();
+ reset(mWakeLock);
+
+ mScrimController.onHideWallpaperTimeout();
+ verify(mWakeLock).acquire();
+ verify(mWakeLock, never()).release();
+ mScrimController.finishAnimationsImmediately();
+ verify(mWakeLock).release();
+ assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+ }
+
+ @Test
public void testWillHideAodWallpaper() {
mScrimController.setWallpaperSupportsAmbientMode(true);
mScrimController.transitionTo(ScrimState.AOD);
@@ -483,6 +504,34 @@
}
@Test
+ public void testWillHidePulsingWallpaper_withRequestFadeOut() {
+ mScrimController.setWallpaperSupportsAmbientMode(true);
+ mScrimController.transitionTo(ScrimState.PULSING, new ScrimController.Callback() {
+ @Override
+ public boolean isFadeOutWallpaper() {
+ return true;
+ }
+ });
+ verify(mAlarmManager).setExact(anyInt(), anyLong(), any(), any(), any());
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ verify(mAlarmManager).cancel(any(AlarmManager.OnAlarmListener.class));
+ }
+
+ @Test
+ public void testDoesNotHidePulsingWallpaper_withoutRequestFadeOut() {
+ mScrimController.setWallpaperSupportsAmbientMode(true);
+ mScrimController.transitionTo(ScrimState.PULSING, new ScrimController.Callback() {});
+ verify(mAlarmManager, never()).setExact(anyInt(), anyLong(), any(), any(), any());
+ }
+
+ @Test
+ public void testDoesNotHidePulsingWallpaper_withoutCallback() {
+ mScrimController.setWallpaperSupportsAmbientMode(true);
+ mScrimController.transitionTo(ScrimState.PULSING);
+ verify(mAlarmManager, never()).setExact(anyInt(), anyLong(), any(), any(), any());
+ }
+
+ @Test
public void testConservesExpansionOpacityAfterTransition() {
mScrimController.transitionTo(ScrimState.UNLOCKED);
mScrimController.setPanelExpansion(0.5f);
@@ -578,7 +627,7 @@
@Test
public void testEatsTouchEvent() {
HashSet<ScrimState> eatsTouches =
- new HashSet<>(Arrays.asList(ScrimState.AOD, ScrimState.PULSING));
+ new HashSet<>(Collections.singletonList(ScrimState.AOD));
for (ScrimState state : ScrimState.values()) {
if (state == ScrimState.UNINITIALIZED) {
continue;
@@ -738,5 +787,4 @@
callback.run();
}
}
-
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpaper/AodMaskViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpaper/AodMaskViewTest.java
new file mode 100644
index 0000000..c44a366
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpaper/AodMaskViewTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallpaper;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import android.hardware.display.DisplayManager;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.FeatureFlagUtils;
+import android.view.DisplayInfo;
+import android.view.WindowManager;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AodMaskViewTest extends SysuiTestCase {
+ private AodMaskView mMaskView;
+ private DisplayInfo mDisplayInfo;
+ private ImageWallpaperTransformer mTransformer;
+
+ @Before
+ public void setUp() throws Exception {
+ DisplayManager displayManager =
+ spy((DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE));
+ doNothing().when(displayManager).registerDisplayListener(any(), any());
+ mContext.addMockSystemService(DisplayManager.class, displayManager);
+
+ WallpaperManager wallpaperManager =
+ spy((WallpaperManager) mContext.getSystemService(Context.WALLPAPER_SERVICE));
+ doReturn(null).when(wallpaperManager).getWallpaperInfo();
+ mContext.addMockSystemService(WallpaperManager.class, wallpaperManager);
+
+ mTransformer = spy(new ImageWallpaperTransformer(null /* listener */));
+ mMaskView = spy(new AodMaskView(getContext(), null /* attrs */, mTransformer));
+ mDisplayInfo = new DisplayInfo();
+
+ ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE))
+ .getDefaultDisplay().getDisplayInfo(mDisplayInfo);
+
+ FeatureFlagUtils.setEnabled(
+ mContext, FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED, true);
+ }
+
+ @After
+ public void tearDown() {
+ FeatureFlagUtils.setEnabled(
+ mContext, FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED, false);
+ }
+
+ @Test
+ public void testCreateMaskView_TransformerIsNotNull() {
+ assertNotNull("mTransformer should not be null", mTransformer);
+ }
+
+ @Test
+ public void testAodMaskView_ShouldNotClickable() {
+ assertFalse("MaskView should not be clickable", mMaskView.isClickable());
+ }
+
+ @Test
+ public void testAodMaskView_OnSizeChange_ShouldUpdateTransformerOffsets() {
+ mMaskView.onSizeChanged(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight, 0, 0);
+ verify(mTransformer, times(1)).updateOffsets();
+ }
+
+ @Test
+ public void testAodMaskView_OnDraw_ShouldDrawTransformedImage() {
+ Canvas c = new Canvas();
+ RectF bounds = new RectF(0, 0, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
+ mMaskView.onSizeChanged(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight, 0, 0);
+ mMaskView.onStatePreChange(0, 1);
+ mMaskView.onDraw(c);
+ verify(mTransformer, times(1)).drawTransformedImage(c, null, null, bounds);
+ }
+
+ @Test
+ public void testAodMaskView_IsDozing_ShouldUpdateAmbientModeState() {
+ doNothing().when(mMaskView).setAnimatorProperty(anyBoolean());
+ mMaskView.onStatePreChange(0, 1);
+ mMaskView.onDozingChanged(true);
+ verify(mTransformer, times(1)).updateAmbientModeState(true);
+ }
+
+ @Test
+ public void testAodMaskView_IsDozing_ShouldDoTransitionOrDrawFinalFrame() {
+ doNothing().when(mMaskView).setAnimatorProperty(anyBoolean());
+ mMaskView.onStatePreChange(0, 1);
+ mMaskView.onDozingChanged(true);
+ mMaskView.onStatePostChange();
+ mMaskView.onDozingChanged(false);
+ verify(mMaskView, times(1)).invalidate();
+ verify(mMaskView, times(1)).setAnimatorProperty(false);
+ }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpaper/ImageWallpaperTransformerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpaper/ImageWallpaperTransformerTest.java
new file mode 100644
index 0000000..55b0aae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpaper/ImageWallpaperTransformerTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2009 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.wallpaper;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.DisplayInfo;
+import android.view.WindowManager;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ImageWallpaperTransformerTest extends SysuiTestCase {
+ private DisplayInfo mDisplayInfo;
+ private Bitmap mBitmap;
+ private Canvas mCanvas;
+ private RectF mDestination;
+
+ @Before
+ public void setUp() throws Exception {
+ mDisplayInfo = new DisplayInfo();
+ ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE))
+ .getDefaultDisplay().getDisplayInfo(mDisplayInfo);
+ int dimension = Math.max(mDisplayInfo.logicalHeight, mDisplayInfo.logicalWidth);
+ mBitmap = Bitmap.createBitmap(dimension, dimension, Bitmap.Config.ARGB_8888);
+ mCanvas = new Canvas(mBitmap);
+ mCanvas.drawColor(Color.RED);
+ mDestination = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
+ }
+
+ @Test
+ public void testVignetteFilter() {
+ VignetteFilter vignette = new VignetteFilter();
+
+ ImageWallpaperTransformer transformer = getTransformer(vignette);
+ transformer.drawTransformedImage(mCanvas, mBitmap, null, mDestination);
+
+ PointF center = vignette.getCenterPoint();
+ int p1 = mBitmap.getPixel((int) center.x, (int) center.y);
+ int p2 = mBitmap.getPixel(0, 0);
+ int p3 = mBitmap.getPixel(mBitmap.getWidth() - 1, mBitmap.getHeight() - 1);
+
+ assertThat(p1).isEqualTo(Color.RED);
+ assertThat(p2 | p3).isEqualTo(Color.BLACK);
+ }
+
+ @Test
+ public void testScrimFilter() {
+ getTransformer(new ScrimFilter())
+ .drawTransformedImage(mCanvas, mBitmap, null, mDestination);
+
+ int pixel = mBitmap.getPixel(0, 0);
+
+ // 0xff4d0000 is the result of 70% alpha pre-multiplied which is 0.7*(0,0,0)+0.3*(255,0,0).
+ assertThat(pixel).isEqualTo(0xff4d0000);
+ }
+
+ private ImageWallpaperTransformer getTransformer(ImageWallpaperFilter filter) {
+ ImageWallpaperTransformer transformer = new ImageWallpaperTransformer(null);
+ transformer.addFilter(filter);
+ transformer.updateDisplayInfo(mDisplayInfo);
+ transformer.updateOffsets();
+ transformer.updateAmbientModeState(true);
+ return transformer;
+ }
+}
diff --git a/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp b/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp
index a40cf1c..d969c69 100644
--- a/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp
+++ b/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp
@@ -16,6 +16,9 @@
#define LOG_TAG "PacProcessor"
+#include <stdlib.h>
+#include <string>
+
#include <utils/Log.h>
#include <utils/Mutex.h>
#include "android_runtime/AndroidRuntime.h"
@@ -23,24 +26,24 @@
#include "jni.h"
#include <nativehelper/JNIHelp.h>
-#include "proxy_resolver_v8.h"
+#include "proxy_resolver_v8_wrapper.h"
namespace android {
-net::ProxyResolverV8* proxyResolver = NULL;
+ProxyResolverV8Handle* proxyResolver = NULL;
bool pacSet = false;
-String16 jstringToString16(JNIEnv* env, jstring jstr) {
+std::u16string jstringToString16(JNIEnv* env, jstring jstr) {
const jchar* str = env->GetStringCritical(jstr, 0);
- String16 str16(reinterpret_cast<const char16_t*>(str),
+ std::u16string str16(reinterpret_cast<const char16_t*>(str),
env->GetStringLength(jstr));
env->ReleaseStringCritical(jstr, str);
return str16;
}
-jstring string16ToJstring(JNIEnv* env, String16 string) {
- const char16_t* str = string.string();
- size_t len = string.size();
+jstring string16ToJstring(JNIEnv* env, std::u16string string) {
+ const char16_t* str = string.data();
+ size_t len = string.length();
return env->NewString(reinterpret_cast<const jchar*>(str), len);
}
@@ -48,7 +51,7 @@
static jboolean com_android_pacprocessor_PacNative_createV8ParserNativeLocked(JNIEnv* /* env */,
jobject) {
if (proxyResolver == NULL) {
- proxyResolver = new net::ProxyResolverV8(net::ProxyResolverJSBindings::CreateDefault());
+ proxyResolver = ProxyResolverV8Handle_new();
pacSet = false;
return JNI_FALSE;
}
@@ -58,7 +61,7 @@
static jboolean com_android_pacprocessor_PacNative_destroyV8ParserNativeLocked(JNIEnv* /* env */,
jobject) {
if (proxyResolver != NULL) {
- delete proxyResolver;
+ ProxyResolverV8Handle_delete(proxyResolver);
proxyResolver = NULL;
return JNI_FALSE;
}
@@ -67,14 +70,14 @@
static jboolean com_android_pacprocessor_PacNative_setProxyScriptNativeLocked(JNIEnv* env, jobject,
jstring script) {
- String16 script16 = jstringToString16(env, script);
+ std::u16string script16 = jstringToString16(env, script);
if (proxyResolver == NULL) {
ALOGE("V8 Parser not started when setting PAC script");
return JNI_TRUE;
}
- if (proxyResolver->SetPacScript(script16) != OK) {
+ if (ProxyResolverV8Handle_SetPacScript(proxyResolver, script16.data()) != OK) {
ALOGE("Unable to set PAC script");
return JNI_TRUE;
}
@@ -85,9 +88,8 @@
static jstring com_android_pacprocessor_PacNative_makeProxyRequestNativeLocked(JNIEnv* env, jobject,
jstring url, jstring host) {
- String16 url16 = jstringToString16(env, url);
- String16 host16 = jstringToString16(env, host);
- String16 ret;
+ std::u16string url16 = jstringToString16(env, url);
+ std::u16string host16 = jstringToString16(env, host);
if (proxyResolver == NULL) {
ALOGE("V8 Parser not initialized when running PAC script");
@@ -99,12 +101,14 @@
return NULL;
}
- if (proxyResolver->GetProxyForURL(url16, host16, &ret) != OK) {
- String8 ret8(ret);
- ALOGE("Error Running PAC: %s", ret8.string());
+ std::unique_ptr<char16_t, decltype(&free)> result = std::unique_ptr<char16_t, decltype(&free)>(
+ ProxyResolverV8Handle_GetProxyForURL(proxyResolver, url16.data(), host16.data()), &free);
+ if (result.get() == NULL) {
+ ALOGE("Error Running PAC");
return NULL;
}
+ std::u16string ret(result.get());
jstring jret = string16ToJstring(env, ret);
return jret;
diff --git a/services/Android.bp b/services/Android.bp
index 01734f4..0abe53d 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -24,6 +24,7 @@
"services.contentcapture",
"services.coverage",
"services.devicepolicy",
+ "services.ipmemorystore",
"services.midi",
"services.net",
"services.print",
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index d7a2365..6b0adfb 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -850,19 +850,29 @@
mFullBackupQueue = readFullBackupSchedule();
}
- // Register for broadcasts about package install, etc., so we can
- // update the provider list.
+ // Register for broadcasts about package changes.
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addDataScheme("package");
- mContext.registerReceiver(mBroadcastReceiver, filter);
+ mContext.registerReceiverAsUser(
+ mBroadcastReceiver,
+ UserHandle.of(mUserId),
+ filter,
+ /* broadcastPermission */ null,
+ /* scheduler */ null);
+
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
- mContext.registerReceiver(mBroadcastReceiver, sdFilter);
+ mContext.registerReceiverAsUser(
+ mBroadcastReceiver,
+ UserHandle.of(mUserId),
+ sdFilter,
+ /* broadcastPermission */ null,
+ /* scheduler */ null);
}
private ArrayList<FullBackupEntry> readFullBackupSchedule() {
@@ -1127,17 +1137,23 @@
}
}
- // ----- Track installation/removal of packages -----
+ /**
+ * A {@link BroadcastReceiver} tracking changes to packages and sd cards in order to update our
+ * internal bookkeeping.
+ */
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
- if (MORE_DEBUG) Slog.d(TAG, "Received broadcast " + intent);
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Received broadcast " + intent);
+ }
String action = intent.getAction();
boolean replacing = false;
boolean added = false;
boolean changed = false;
Bundle extras = intent.getExtras();
- String[] pkgList = null;
+ String[] packageList = null;
+
if (Intent.ACTION_PACKAGE_ADDED.equals(action)
|| Intent.ACTION_PACKAGE_REMOVED.equals(action)
|| Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
@@ -1145,69 +1161,70 @@
if (uri == null) {
return;
}
- final String pkgName = uri.getSchemeSpecificPart();
- if (pkgName != null) {
- pkgList = new String[]{pkgName};
- }
- changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
- // At package-changed we only care about looking at new transport states
+ String packageName = uri.getSchemeSpecificPart();
+ if (packageName != null) {
+ packageList = new String[]{packageName};
+ }
+
+ changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
if (changed) {
- final String[] components =
+ // Look at new transport states for package changed events.
+ String[] components =
intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
if (MORE_DEBUG) {
- Slog.i(TAG, "Package " + pkgName + " changed; rechecking");
+ Slog.i(TAG, "Package " + packageName + " changed");
for (int i = 0; i < components.length; i++) {
Slog.i(TAG, " * " + components[i]);
}
}
mBackupHandler.post(
- () -> mTransportManager.onPackageChanged(pkgName, components));
- return; // nothing more to do in the PACKAGE_CHANGED case
+ () -> mTransportManager.onPackageChanged(packageName, components));
+ return;
}
added = Intent.ACTION_PACKAGE_ADDED.equals(action);
replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false);
} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
added = true;
- pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ packageList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
added = false;
- pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ packageList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
}
- if (pkgList == null || pkgList.length == 0) {
+ if (packageList == null || packageList.length == 0) {
return;
}
- final int uid = extras.getInt(Intent.EXTRA_UID);
+ int uid = extras.getInt(Intent.EXTRA_UID);
if (added) {
synchronized (mBackupParticipants) {
if (replacing) {
- // This is the package-replaced case; we just remove the entry
- // under the old uid and fall through to re-add. If an app
- // just added key/value backup participation, this picks it up
- // as a known participant.
- removePackageParticipantsLocked(pkgList, uid);
+ // Remove the entry under the old uid and fall through to re-add. If an app
+ // just opted into key/value backup, add it as a known participant.
+ removePackageParticipantsLocked(packageList, uid);
}
- addPackageParticipantsLocked(pkgList);
+ addPackageParticipantsLocked(packageList);
}
- // If they're full-backup candidates, add them there instead
- final long now = System.currentTimeMillis();
- for (final String packageName : pkgList) {
+
+ long now = System.currentTimeMillis();
+ for (String packageName : packageList) {
try {
- PackageInfo app = mPackageManager.getPackageInfo(packageName, 0);
+ PackageInfo app =
+ mPackageManager.getPackageInfoAsUser(
+ packageName, /* flags */ 0, mUserId);
if (AppBackupUtils.appGetsFullBackup(app)
&& AppBackupUtils.appIsEligibleForBackup(
app.applicationInfo, mPackageManager)) {
enqueueFullBackup(packageName, now);
scheduleNextFullBackupJob(0);
} else {
- // The app might have just transitioned out of full-data into
- // doing key/value backups, or might have just disabled backups
- // entirely. Make sure it is no longer in the full-data queue.
+ // The app might have just transitioned out of full-data into doing
+ // key/value backups, or might have just disabled backups entirely. Make
+ // sure it is no longer in the full-data queue.
synchronized (mQueueLock) {
dequeueFullBackupLocked(packageName);
}
@@ -1216,32 +1233,28 @@
mBackupHandler.post(
() -> mTransportManager.onPackageAdded(packageName));
-
} catch (NameNotFoundException e) {
- // doesn't really exist; ignore it
if (DEBUG) {
Slog.w(TAG, "Can't resolve new app " + packageName);
}
}
}
- // Whenever a package is added or updated we need to update
- // the package metadata bookkeeping.
+ // Whenever a package is added or updated we need to update the package metadata
+ // bookkeeping.
dataChangedImpl(PACKAGE_MANAGER_SENTINEL);
} else {
- if (replacing) {
- // The package is being updated. We'll receive a PACKAGE_ADDED shortly.
- } else {
- // Outright removal. In the full-data case, the app will be dropped
- // from the queue when its (now obsolete) name comes up again for
- // backup.
+ if (!replacing) {
+ // Outright removal. In the full-data case, the app will be dropped from the
+ // queue when its (now obsolete) name comes up again for backup.
synchronized (mBackupParticipants) {
- removePackageParticipantsLocked(pkgList, uid);
+ removePackageParticipantsLocked(packageList, uid);
}
}
- for (final String pkgName : pkgList) {
+
+ for (String packageName : packageList) {
mBackupHandler.post(
- () -> mTransportManager.onPackageRemoved(pkgName));
+ () -> mTransportManager.onPackageRemoved(packageName));
}
}
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 4b24ef9..dc0f602 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -39,6 +39,7 @@
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
+import com.android.internal.util.SyncResultReceiver;
import com.android.server.LocalServices;
import com.android.server.infra.AbstractMasterSystemService;
import com.android.server.infra.FrameworkResourcesServiceNameResolver;
@@ -50,8 +51,9 @@
/**
* A service used to observe the contents of the screen.
*
- * <p>The data collected by this service can be analyzed and combined with other sources to provide
- * contextual data in other areas of the system such as Autofill.
+ * <p>The data collected by this service can be analyzed on-device and combined
+ * with other sources to provide contextual data in other areas of the system
+ * such as Autofill.
*/
public final class ContentCaptureManagerService extends
AbstractMasterSystemService<ContentCaptureManagerService, ContentCapturePerUserService> {
@@ -196,6 +198,22 @@
}
@Override
+ public void getReceiverServiceComponentName(@UserIdInt int userId,
+ IResultReceiver receiver) {
+ ComponentName connectedServiceComponentName;
+ synchronized (mLock) {
+ final ContentCapturePerUserService service = getServiceForUserLocked(userId);
+ connectedServiceComponentName = service.getServiceComponentName();
+ }
+ try {
+ receiver.send(0, SyncResultReceiver.bundleFor(connectedServiceComponentName));
+ } catch (RemoteException e) {
+ // Ignore exception as we need to be resilient against app behavior.
+ Slog.w(TAG, "Unable to send service component name: " + e);
+ }
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 3cdf09e..466fb4e 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -3391,6 +3391,8 @@
pw.println(" Limit output to data associated with the given app op mode.");
pw.println(" --package [PACKAGE]");
pw.println(" Limit output to data associated with the given package name.");
+ pw.println(" --watchers");
+ pw.println(" Only output the watcher sections.");
}
private void dumpTimesLocked(PrintWriter pw, String firstPrefix, String prefix, long[] times,
@@ -3429,6 +3431,7 @@
String dumpPackage = null;
int dumpUid = -1;
int dumpMode = -1;
+ boolean dumpWatchers = false;
if (args != null) {
for (int i=0; i<args.length; i++) {
@@ -3476,6 +3479,8 @@
if (dumpMode < 0) {
return;
}
+ } else if ("--watchers".equals(arg)) {
+ dumpWatchers = true;
} else if (arg.length() > 0 && arg.charAt(0) == '-'){
pw.println("Unknown option: " + arg);
return;
@@ -3496,7 +3501,8 @@
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
final Date date = new Date();
boolean needSep = false;
- if (dumpOp < 0 && dumpMode < 0 && dumpPackage == null && mProfileOwners != null) {
+ if (dumpOp < 0 && dumpMode < 0 && dumpPackage == null && mProfileOwners != null
+ && !dumpWatchers) {
pw.println(" Profile owners:");
for (int poi = 0; poi < mProfileOwners.size(); poi++) {
pw.print(" User #");
@@ -3517,7 +3523,7 @@
ArraySet<ModeCallback> callbacks = mOpModeWatchers.valueAt(i);
for (int j=0; j<callbacks.size(); j++) {
final ModeCallback cb = callbacks.valueAt(j);
- if (dumpPackage != null && cb.mWatchingUid >= 0
+ if (dumpPackage != null
&& dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
continue;
}
@@ -3561,7 +3567,7 @@
boolean printedHeader = false;
for (int i=0; i<mModeWatchers.size(); i++) {
final ModeCallback cb = mModeWatchers.valueAt(i);
- if (dumpPackage != null && cb.mWatchingUid >= 0
+ if (dumpPackage != null
&& dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
continue;
}
@@ -3587,7 +3593,7 @@
if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) {
continue;
}
- if (dumpPackage != null && cb.mWatchingUid >= 0
+ if (dumpPackage != null
&& dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
continue;
}
@@ -3627,7 +3633,7 @@
if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) {
continue;
}
- if (dumpPackage != null && cb.mWatchingUid >= 0
+ if (dumpPackage != null
&& dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
continue;
}
@@ -3655,7 +3661,7 @@
pw.println(cb);
}
}
- if (mClients.size() > 0 && dumpMode < 0) {
+ if (mClients.size() > 0 && dumpMode < 0 && !dumpWatchers) {
needSep = true;
boolean printedHeader = false;
for (int i=0; i<mClients.size(); i++) {
@@ -3692,7 +3698,7 @@
}
}
if (mAudioRestrictions.size() > 0 && dumpOp < 0 && dumpPackage != null
- && dumpMode < 0) {
+ && dumpMode < 0 && !dumpWatchers) {
boolean printedHeader = false;
for (int o=0; o<mAudioRestrictions.size(); o++) {
final String op = AppOpsManager.opToName(mAudioRestrictions.keyAt(o));
@@ -3725,6 +3731,9 @@
final SparseIntArray opModes = uidState.opModes;
final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
+ if (dumpWatchers) {
+ continue;
+ }
if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) {
boolean hasOp = dumpOp < 0 || (uidState.opModes != null
&& uidState.opModes.indexOfKey(dumpOp) >= 0);
@@ -3880,18 +3889,34 @@
for (int i = 0; i < userRestrictionCount; i++) {
IBinder token = mOpUserRestrictions.keyAt(i);
ClientRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
- pw.println(" User restrictions for token " + token + ":");
+ boolean printedTokenHeader = false;
+
+ if (dumpMode >= 0 || dumpWatchers) {
+ continue;
+ }
final int restrictionCount = restrictionState.perUserRestrictions != null
? restrictionState.perUserRestrictions.size() : 0;
- if (restrictionCount > 0) {
- pw.println(" Restricted ops:");
+ if (restrictionCount > 0 && dumpPackage == null) {
+ boolean printedOpsHeader = false;
for (int j = 0; j < restrictionCount; j++) {
int userId = restrictionState.perUserRestrictions.keyAt(j);
boolean[] restrictedOps = restrictionState.perUserRestrictions.valueAt(j);
if (restrictedOps == null) {
continue;
}
+ if (dumpOp >= 0 && (dumpOp >= restrictedOps.length
+ || !restrictedOps[dumpOp])) {
+ continue;
+ }
+ if (!printedTokenHeader) {
+ pw.println(" User restrictions for token " + token + ":");
+ printedTokenHeader = true;
+ }
+ if (!printedOpsHeader) {
+ pw.println(" Restricted ops:");
+ printedOpsHeader = true;
+ }
StringBuilder restrictedOpsValue = new StringBuilder();
restrictedOpsValue.append("[");
final int restrictedOpCount = restrictedOps.length;
@@ -3911,11 +3936,37 @@
final int excludedPackageCount = restrictionState.perUserExcludedPackages != null
? restrictionState.perUserExcludedPackages.size() : 0;
- if (excludedPackageCount > 0) {
- pw.println(" Excluded packages:");
+ if (excludedPackageCount > 0 && dumpOp < 0) {
+ boolean printedPackagesHeader = false;
for (int j = 0; j < excludedPackageCount; j++) {
int userId = restrictionState.perUserExcludedPackages.keyAt(j);
String[] packageNames = restrictionState.perUserExcludedPackages.valueAt(j);
+ if (packageNames == null) {
+ continue;
+ }
+ boolean hasPackage;
+ if (dumpPackage != null) {
+ hasPackage = false;
+ for (String pkg : packageNames) {
+ if (dumpPackage.equals(pkg)) {
+ hasPackage = true;
+ break;
+ }
+ }
+ } else {
+ hasPackage = true;
+ }
+ if (!hasPackage) {
+ continue;
+ }
+ if (!printedTokenHeader) {
+ pw.println(" User restrictions for token " + token + ":");
+ printedTokenHeader = true;
+ }
+ if (!printedPackagesHeader) {
+ pw.println(" Excluded packages:");
+ printedPackagesHeader = true;
+ }
pw.print(" "); pw.print("user: "); pw.print(userId);
pw.print(" packages: "); pw.println(Arrays.toString(packageNames));
}
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 7bbc543..a333381 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -279,7 +279,7 @@
Slog.d(TAG,
"Airplane Mode change - current state: " + BluetoothAdapter.nameForState(
- st));
+ st) + ", isAirplaneModeOn()=" + isAirplaneModeOn());
if (isAirplaneModeOn()) {
// Clear registered LE apps to force shut-off
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 66ceae4..d0666b9 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -25,6 +25,7 @@
import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.ConnectivityManager.isNetworkTypeValid;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
@@ -37,6 +38,8 @@
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkPolicyManager.RULE_NONE;
import static android.net.NetworkPolicyManager.uidRulesToString;
+import static android.net.NetworkStack.NETWORKSTACK_PACKAGE_NAME;
+import static android.net.shared.NetworkMonitorUtils.isValidationRequired;
import static android.os.Process.INVALID_UID;
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
@@ -62,6 +65,8 @@
import android.net.INetd;
import android.net.INetdEventCallback;
import android.net.INetworkManagementEventObserver;
+import android.net.INetworkMonitor;
+import android.net.INetworkMonitorCallbacks;
import android.net.INetworkPolicyListener;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
@@ -79,9 +84,11 @@
import android.net.NetworkQuotaInfo;
import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
+import android.net.NetworkStack;
import android.net.NetworkState;
import android.net.NetworkUtils;
import android.net.NetworkWatchlistManager;
+import android.net.PrivateDnsConfigParcel;
import android.net.ProxyInfo;
import android.net.RouteInfo;
import android.net.UidRange;
@@ -90,12 +97,13 @@
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
import android.net.netlink.InetDiagMessage;
+import android.net.shared.NetworkMonitorUtils;
+import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
import android.net.util.NetdService;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
-import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -123,8 +131,8 @@
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.LocalLog;
-import android.util.LocalLog.ReadOnlyLocalLog;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -149,7 +157,6 @@
import com.android.server.am.BatteryStatsService;
import com.android.server.connectivity.DataConnectionStats;
import com.android.server.connectivity.DnsManager;
-import com.android.server.connectivity.DnsManager.PrivateDnsConfig;
import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.connectivity.KeepaliveTracker;
@@ -158,7 +165,6 @@
import com.android.server.connectivity.MultipathPolicyTracker;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkDiagnostics;
-import com.android.server.connectivity.NetworkMonitor;
import com.android.server.connectivity.NetworkNotificationManager;
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
import com.android.server.connectivity.PermissionMonitor;
@@ -186,7 +192,6 @@
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
-import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -442,6 +447,43 @@
*/
private static final int EVENT_DATA_SAVER_CHANGED = 40;
+ /**
+ * Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the network has
+ * been tested.
+ * obj = String representing URL that Internet probe was redirect to, if it was redirected.
+ * arg1 = One of the NETWORK_TESTED_RESULT_* constants.
+ * arg2 = NetID.
+ */
+ public static final int EVENT_NETWORK_TESTED = 41;
+
+ /**
+ * Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the private DNS
+ * config was resolved.
+ * obj = PrivateDnsConfig
+ * arg2 = netid
+ */
+ public static final int EVENT_PRIVATE_DNS_CONFIG_RESOLVED = 42;
+
+ /**
+ * Request ConnectivityService display provisioning notification.
+ * arg1 = Whether to make the notification visible.
+ * arg2 = NetID.
+ * obj = Intent to be launched when notification selected by user, null if !arg1.
+ */
+ public static final int EVENT_PROVISIONING_NOTIFICATION = 43;
+
+ /**
+ * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
+ * should be shown.
+ */
+ public static final int PROVISIONING_NOTIFICATION_SHOW = 1;
+
+ /**
+ * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
+ * should be hidden.
+ */
+ public static final int PROVISIONING_NOTIFICATION_HIDE = 0;
+
private static String eventName(int what) {
return sMagicDecoderRing.get(what, Integer.toString(what));
}
@@ -506,30 +548,6 @@
private long mMaxWakelockDurationMs = 0;
private long mLastWakeLockAcquireTimestamp = 0;
- // Array of <Network,ReadOnlyLocalLogs> tracking network validation and results
- private static final int MAX_VALIDATION_LOGS = 10;
- private static class ValidationLog {
- final Network mNetwork;
- final String mName;
- final ReadOnlyLocalLog mLog;
-
- ValidationLog(Network network, String name, ReadOnlyLocalLog log) {
- mNetwork = network;
- mName = name;
- mLog = log;
- }
- }
- private final ArrayDeque<ValidationLog> mValidationLogs = new ArrayDeque<>(MAX_VALIDATION_LOGS);
-
- private void addValidationLogs(ReadOnlyLocalLog log, Network network, String name) {
- synchronized (mValidationLogs) {
- while (mValidationLogs.size() >= MAX_VALIDATION_LOGS) {
- mValidationLogs.removeLast();
- }
- mValidationLogs.addFirst(new ValidationLog(network, name, log));
- }
- }
-
private final IpConnectivityLog mMetricsLog;
@GuardedBy("mBandwidthRequests")
@@ -1684,7 +1702,11 @@
// caller type. Need to re-factor NetdEventListenerService to allow multiple
// NetworkMonitor registrants.
if (nai != null && nai.satisfies(mDefaultRequest)) {
- nai.networkMonitor.sendMessage(NetworkMonitor.EVENT_DNS_NOTIFICATION, returnCode);
+ try {
+ nai.networkMonitor().notifyDnsResponse(returnCode);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
}
}
};
@@ -2266,17 +2288,6 @@
if (ArrayUtils.contains(args, SHORT_ARG) == false) {
pw.println();
- synchronized (mValidationLogs) {
- pw.println("mValidationLogs (most recent first):");
- for (ValidationLog p : mValidationLogs) {
- pw.println(p.mNetwork + " - " + p.mName);
- pw.increaseIndent();
- p.mLog.dump(fd, pw, args);
- pw.decreaseIndent();
- }
- }
-
- pw.println();
pw.println("mNetworkRequestInfoLogs (most recent first):");
pw.increaseIndent();
mNetworkRequestInfoLogs.reverseDump(fd, pw, args);
@@ -2455,11 +2466,11 @@
switch (msg.what) {
default:
return false;
- case NetworkMonitor.EVENT_NETWORK_TESTED: {
+ case EVENT_NETWORK_TESTED: {
final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
if (nai == null) break;
- final boolean valid = (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID);
+ final boolean valid = (msg.arg1 == NETWORK_TEST_RESULT_VALID);
final boolean wasValidated = nai.lastValidated;
final boolean wasDefault = isDefaultNetwork(nai);
@@ -2497,7 +2508,7 @@
}
break;
}
- case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: {
+ case EVENT_PROVISIONING_NOTIFICATION: {
final int netId = msg.arg2;
final boolean visible = toBool(msg.arg1);
final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
@@ -2530,7 +2541,7 @@
}
break;
}
- case NetworkMonitor.EVENT_PRIVATE_DNS_CONFIG_RESOLVED: {
+ case EVENT_PRIVATE_DNS_CONFIG_RESOLVED: {
final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
if (nai == null) break;
@@ -2572,8 +2583,61 @@
}
}
+ private class NetworkMonitorCallbacks extends INetworkMonitorCallbacks.Stub {
+ private final NetworkAgentInfo mNai;
+
+ private NetworkMonitorCallbacks(NetworkAgentInfo nai) {
+ mNai = nai;
+ }
+
+ @Override
+ public void onNetworkMonitorCreated(INetworkMonitor networkMonitor) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT,
+ new Pair<>(mNai, networkMonitor)));
+ }
+
+ @Override
+ public void notifyNetworkTested(int testResult, @Nullable String redirectUrl) {
+ mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(EVENT_NETWORK_TESTED,
+ testResult, mNai.network.netId, redirectUrl));
+ }
+
+ @Override
+ public void notifyPrivateDnsConfigResolved(PrivateDnsConfigParcel config) {
+ mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
+ EVENT_PRIVATE_DNS_CONFIG_RESOLVED,
+ 0, mNai.network.netId, PrivateDnsConfig.fromParcel(config)));
+ }
+
+ @Override
+ public void showProvisioningNotification(String action) {
+ final Intent intent = new Intent(action);
+ intent.setPackage(NETWORKSTACK_PACKAGE_NAME);
+
+ final PendingIntent pendingIntent;
+ // Only the system server can register notifications with package "android"
+ final long token = Binder.clearCallingIdentity();
+ try {
+ pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
+ EVENT_PROVISIONING_NOTIFICATION, PROVISIONING_NOTIFICATION_SHOW,
+ mNai.network.netId,
+ pendingIntent));
+ }
+
+ @Override
+ public void hideProvisioningNotification() {
+ mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
+ EVENT_PROVISIONING_NOTIFICATION, PROVISIONING_NOTIFICATION_HIDE,
+ mNai.network.netId));
+ }
+ }
+
private boolean networkRequiresValidation(NetworkAgentInfo nai) {
- return NetworkMonitor.isValidationRequired(
+ return isValidationRequired(
mDefaultRequest.networkCapabilities, nai.networkCapabilities);
}
@@ -2603,10 +2667,14 @@
// Internet access and therefore also require validation.
if (!networkRequiresValidation(nai)) return;
- // Notify the NetworkMonitor thread in case it needs to cancel or
+ // Notify the NetworkAgentInfo/NetworkMonitor in case NetworkMonitor needs to cancel or
// schedule DNS resolutions. If a DNS resolution is required the
// result will be sent back to us.
- nai.networkMonitor.notifyPrivateDnsSettingsChanged(cfg);
+ try {
+ nai.networkMonitor().notifyPrivateDnsChanged(cfg.toParcel());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
// With Private DNS bypass support, we can proceed to update the
// Private DNS config immediately, even if we're in strict mode
@@ -2736,7 +2804,11 @@
// Disable wakeup packet monitoring for each interface.
wakeupModifyInterface(iface, nai.networkCapabilities, false);
}
- nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
+ try {
+ nai.networkMonitor().notifyNetworkDisconnected();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
mNetworkAgentInfos.remove(nai.messenger);
nai.maybeStopClat();
synchronized (mNetworkForNetId) {
@@ -3096,7 +3168,11 @@
NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
if (nai == null) return;
if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) return;
- nai.networkMonitor.sendMessage(NetworkMonitor.CMD_LAUNCH_CAPTIVE_PORTAL_APP);
+ try {
+ nai.networkMonitor().launchCaptivePortalApp();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
});
}
@@ -3217,6 +3293,11 @@
return mMultinetworkPolicyTracker.getMeteredMultipathPreference();
}
+ @Override
+ public NetworkRequest getDefaultRequest() {
+ return mDefaultRequest;
+ }
+
private class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
@@ -3247,7 +3328,9 @@
break;
}
case EVENT_REGISTER_NETWORK_AGENT: {
- handleRegisterNetworkAgent((NetworkAgentInfo)msg.obj);
+ final Pair<NetworkAgentInfo, INetworkMonitor> arg =
+ (Pair<NetworkAgentInfo, INetworkMonitor>) msg.obj;
+ handleRegisterNetworkAgent(arg.first, arg.second);
break;
}
case EVENT_REGISTER_NETWORK_REQUEST:
@@ -3305,7 +3388,14 @@
}
case EVENT_SYSTEM_READY: {
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
- nai.networkMonitor.systemReady = true;
+ // Might have been called already in handleRegisterNetworkAgent since
+ // mSystemReady is set before sending EVENT_SYSTEM_READY, but calling
+ // this several times is fine.
+ try {
+ nai.networkMonitor().notifySystemReady();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
}
mMultipathPolicyTracker.start();
break;
@@ -3577,7 +3667,11 @@
if (isNetworkWithLinkPropertiesBlocked(lp, uid, false)) {
return;
}
- nai.networkMonitor.forceReevaluation(uid);
+ try {
+ nai.networkMonitor().forceReevaluation(uid);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
}
@Override
@@ -4785,27 +4879,49 @@
final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
new Network(reserveNetId()), new NetworkInfo(networkInfo), lp, nc, currentScore,
- mContext, mTrackerHandler, new NetworkMisc(networkMisc), mDefaultRequest, this);
+ mContext, mTrackerHandler, new NetworkMisc(networkMisc), this);
// Make sure the network capabilities reflect what the agent info says.
nai.networkCapabilities = mixInCapabilities(nai, nc);
- synchronized (this) {
- nai.networkMonitor.systemReady = mSystemReady;
- }
final String extraInfo = networkInfo.getExtraInfo();
final String name = TextUtils.isEmpty(extraInfo)
? nai.networkCapabilities.getSSID() : extraInfo;
- addValidationLogs(nai.networkMonitor.getValidationLogs(), nai.network, name);
if (DBG) log("registerNetworkAgent " + nai);
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT, nai));
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mContext.getSystemService(NetworkStack.class)
+ .makeNetworkMonitor(nai.network, name, new NetworkMonitorCallbacks(nai));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ // NetworkAgentInfo registration will finish when the NetworkMonitor is created.
+ // If the network disconnects or sends any other event before that, messages are deferred by
+ // NetworkAgent until nai.asyncChannel.connect(), which will be called when finalizing the
+ // registration.
return nai.network.netId;
}
- private void handleRegisterNetworkAgent(NetworkAgentInfo nai) {
+ private void handleRegisterNetworkAgent(NetworkAgentInfo nai, INetworkMonitor networkMonitor) {
+ nai.onNetworkMonitorCreated(networkMonitor);
if (VDBG) log("Got NetworkAgent Messenger");
mNetworkAgentInfos.put(nai.messenger, nai);
synchronized (mNetworkForNetId) {
mNetworkForNetId.put(nai.network.netId, nai);
}
+ synchronized (this) {
+ if (mSystemReady) {
+ try {
+ networkMonitor.notifySystemReady();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ try {
+ networkMonitor.start();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
nai.asyncChannel.connect(mContext, mTrackerHandler, nai.messenger);
NetworkInfo networkInfo = nai.networkInfo;
nai.networkInfo = null;
@@ -4855,6 +4971,11 @@
networkAgent.updateClat(mNMS);
notifyIfacesChangedForNetworkStats();
if (networkAgent.everConnected) {
+ try {
+ networkAgent.networkMonitor().notifyLinkPropertiesChanged();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED);
}
}
@@ -5092,6 +5213,11 @@
// If the requestable capabilities have changed or the score changed, we can't have been
// called by rematchNetworkAndRequests, so it's safe to start a rematch.
rematchAllNetworksAndRequests(nai, oldScore);
+ try {
+ nai.networkMonitor().notifyNetworkCapabilitiesChanged();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
}
@@ -5339,6 +5465,11 @@
}
if (capabilitiesChanged) {
+ try {
+ nai.networkMonitor().notifyNetworkCapabilitiesChanged();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
}
@@ -5739,7 +5870,15 @@
updateLinkProperties(networkAgent, new LinkProperties(networkAgent.linkProperties),
null);
- networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
+ // Until parceled LinkProperties are sent directly to NetworkMonitor, the connect
+ // command must be sent after updating LinkProperties to maximize chances of
+ // NetworkMonitor seeing the correct LinkProperties when starting.
+ // TODO: pass LinkProperties to the NetworkMonitor in the notifyNetworkConnected call.
+ try {
+ networkAgent.networkMonitor().notifyNetworkConnected();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
scheduleUnvalidatedPrompt(networkAgent);
if (networkAgent.isVPN()) {
@@ -6020,7 +6159,7 @@
@Override
public String getCaptivePortalServerUrl() {
enforceConnectivityInternalPermission();
- return NetworkMonitor.getCaptivePortalServerHttpUrl(mContext);
+ return NetworkMonitorUtils.getCaptivePortalServerHttpUrl(mContext);
}
@Override
@@ -6113,12 +6252,6 @@
}
@VisibleForTesting
- public NetworkMonitor createNetworkMonitor(Context context, Handler handler,
- NetworkAgentInfo nai, NetworkRequest defaultRequest) {
- return new NetworkMonitor(context, handler, nai, defaultRequest);
- }
-
- @VisibleForTesting
MultinetworkPolicyTracker createMultinetworkPolicyTracker(Context c, Handler h, Runnable r) {
return new MultinetworkPolicyTracker(c, h, r);
}
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 2346cfc..869d564 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -17,9 +17,14 @@
package com.android.server;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.location.LocationManager.FUSED_PROVIDER;
+import static android.location.LocationManager.GPS_PROVIDER;
+import static android.location.LocationManager.NETWORK_PROVIDER;
+import static android.location.LocationManager.PASSIVE_PROVIDER;
import static android.location.LocationProvider.AVAILABLE;
import static android.provider.Settings.Global.LOCATION_DISABLE_STATUS_CALLBACKS;
+import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.internal.util.Preconditions.checkState;
import android.Manifest;
@@ -29,7 +34,6 @@
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -64,7 +68,6 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Message;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
@@ -81,12 +84,14 @@
import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
import com.android.server.location.AbstractLocationProvider;
import com.android.server.location.ActivityRecognitionProxy;
import com.android.server.location.GeocoderProxy;
@@ -116,7 +121,6 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
-import java.util.Set;
/**
* The service class that manages LocationProviders and issues location
@@ -137,16 +141,12 @@
private static final String ACCESS_LOCATION_EXTRA_COMMANDS =
android.Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS;
- private static final String INSTALL_LOCATION_PROVIDER =
- android.Manifest.permission.INSTALL_LOCATION_PROVIDER;
private static final String NETWORK_LOCATION_SERVICE_ACTION =
"com.android.location.service.v3.NetworkLocationProvider";
private static final String FUSED_LOCATION_SERVICE_ACTION =
"com.android.location.service.FusedLocationProvider";
- private static final int MSG_LOCATION_CHANGED = 1;
-
private static final long NANOS_PER_MILLI = 1000000L;
// The maximum interval a location request can have and still be considered "high power".
@@ -170,75 +170,62 @@
private static final LocationRequest DEFAULT_LOCATION_REQUEST = new LocationRequest();
- private final Context mContext;
- private final AppOpsManager mAppOps;
-
- // used internally for synchronization
private final Object mLock = new Object();
+ private final Context mContext;
+ private final Handler mHandler;
- // --- fields below are final after systemRunning() ---
- private LocationFudger mLocationFudger;
- private GeofenceManager mGeofenceManager;
+ private AppOpsManager mAppOps;
private PackageManager mPackageManager;
private PowerManager mPowerManager;
private ActivityManager mActivityManager;
private UserManager mUserManager;
+
+ private GeofenceManager mGeofenceManager;
+ private LocationFudger mLocationFudger;
private GeocoderProxy mGeocodeProvider;
private GnssStatusListenerHelper mGnssStatusProvider;
private INetInitiatedListener mNetInitiatedListener;
- private LocationWorkerHandler mLocationHandler;
private PassiveProvider mPassiveProvider; // track passive provider for special cases
private LocationBlacklist mBlacklist;
private GnssMeasurementsProvider mGnssMeasurementsProvider;
private GnssNavigationMessageProvider mGnssNavigationMessageProvider;
+ @GuardedBy("mLock")
private String mLocationControllerExtraPackage;
private boolean mLocationControllerExtraPackageEnabled;
private IGpsGeofenceHardware mGpsGeofenceProxy;
- // --- fields below are protected by mLock ---
+ // list of currently active providers
+ @GuardedBy("mLock")
+ private final ArrayList<LocationProvider> mProviders = new ArrayList<>();
- // Mock (test) providers
- private final HashMap<String, MockProvider> mMockProviders =
- new HashMap<>();
+ // list of non-mock providers, so that when mock providers replace real providers, they can be
+ // later re-replaced
+ @GuardedBy("mLock")
+ private final ArrayList<LocationProvider> mRealProviders = new ArrayList<>();
- // all receivers
+ @GuardedBy("mLock")
private final HashMap<Object, Receiver> mReceivers = new HashMap<>();
-
- // currently installed providers (with mocks replacing real providers)
- private final ArrayList<LocationProvider> mProviders =
- new ArrayList<>();
-
- // real providers, saved here when mocked out
- private final HashMap<String, LocationProvider> mRealProviders =
- new HashMap<>();
-
- // mapping from provider name to provider
- private final HashMap<String, LocationProvider> mProvidersByName =
- new HashMap<>();
-
- // mapping from provider name to all its UpdateRecords
private final HashMap<String, ArrayList<UpdateRecord>> mRecordsByProvider =
new HashMap<>();
private final LocationRequestStatistics mRequestStatistics = new LocationRequestStatistics();
// mapping from provider name to last known location
+ @GuardedBy("mLock")
private final HashMap<String, Location> mLastLocation = new HashMap<>();
// same as mLastLocation, but is not updated faster than LocationFudger.FASTEST_INTERVAL_MS.
// locations stored here are not fudged for coarse permissions.
+ @GuardedBy("mLock")
private final HashMap<String, Location> mLastLocationCoarseInterval =
new HashMap<>();
- // all providers that operate over proxy, for authorizing incoming location and whitelisting
- // throttling
- private final ArrayList<LocationProviderProxy> mProxyProviders =
- new ArrayList<>();
-
private final ArraySet<String> mBackgroundThrottlePackageWhitelist = new ArraySet<>();
+ @GuardedBy("mLock")
private final ArrayMap<IBinder, Identity> mGnssMeasurementsListeners = new ArrayMap<>();
+ @GuardedBy("mLock")
private final ArrayMap<IBinder, Identity>
mGnssNavigationMessageListeners = new ArrayMap<>();
@@ -246,22 +233,22 @@
private int mCurrentUserId = UserHandle.USER_SYSTEM;
private int[] mCurrentUserProfiles = new int[]{UserHandle.USER_SYSTEM};
- // Maximum age of last location returned to clients with foreground-only location permissions.
- private long mLastLocationMaxAgeMs;
-
private GnssLocationProvider.GnssSystemInfoProvider mGnssSystemInfoProvider;
private GnssLocationProvider.GnssMetricsProvider mGnssMetricsProvider;
private GnssBatchingProvider mGnssBatchingProvider;
+ @GuardedBy("mLock")
private IBatchedLocationCallback mGnssBatchingCallback;
+ @GuardedBy("mLock")
private LinkedCallback mGnssBatchingDeathCallback;
+ @GuardedBy("mLock")
private boolean mGnssBatchingInProgress = false;
public LocationManagerService(Context context) {
super();
mContext = context;
- mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+ mHandler = BackgroundThread.getHandler();
// Let the package manager query which are the default location
// providers as they get certain permissions granted by default.
@@ -271,134 +258,110 @@
userId -> mContext.getResources().getStringArray(
com.android.internal.R.array.config_locationProviderPackageNames));
- if (D) Log.d(TAG, "Constructed");
-
// most startup is deferred until systemRunning()
}
public void systemRunning() {
synchronized (mLock) {
- if (D) Log.d(TAG, "systemRunning()");
-
- // fetch package manager
- mPackageManager = mContext.getPackageManager();
-
- // fetch power manager
- mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-
- // fetch activity manager
- mActivityManager
- = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
-
- // prepare worker thread
- mLocationHandler = new LocationWorkerHandler(BackgroundThread.get().getLooper());
-
- // prepare mLocationHandler's dependents
- mLocationFudger = new LocationFudger(mContext, mLocationHandler);
- mBlacklist = new LocationBlacklist(mContext, mLocationHandler);
- mBlacklist.init();
- mGeofenceManager = new GeofenceManager(mContext, mBlacklist);
-
- // Monitor for app ops mode changes.
- AppOpsManager.OnOpChangedListener callback
- = new AppOpsManager.OnOpChangedInternalListener() {
- public void onOpChanged(int op, String packageName) {
- mLocationHandler.post(() -> {
- synchronized (mLock) {
- for (Receiver receiver : mReceivers.values()) {
- receiver.updateMonitoring(true);
- }
- applyAllProviderRequirementsLocked();
- }
- });
- }
- };
- mAppOps.startWatchingMode(AppOpsManager.OP_COARSE_LOCATION, null,
- AppOpsManager.WATCH_FOREGROUND_CHANGES, callback);
-
- PackageManager.OnPermissionsChangedListener permissionListener = uid -> {
- synchronized (mLock) {
- applyAllProviderRequirementsLocked();
- }
- };
- mPackageManager.addOnPermissionsChangeListener(permissionListener);
-
- // listen for background/foreground changes
- ActivityManager.OnUidImportanceListener uidImportanceListener =
- (uid, importance) -> mLocationHandler.post(
- () -> onUidImportanceChanged(uid, importance));
- mActivityManager.addOnUidImportanceListener(uidImportanceListener,
- FOREGROUND_IMPORTANCE_CUTOFF);
-
- mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- updateUserProfiles(mCurrentUserId);
-
- updateBackgroundThrottlingWhitelistLocked();
- updateLastLocationMaxAgeLocked();
-
- // prepare providers
- loadProvidersLocked();
- updateProvidersSettingsLocked();
- for (LocationProvider provider : mProviders) {
- applyRequirementsLocked(provider.getName());
- }
+ initializeLocked();
}
+ }
- // listen for settings changes
+ @GuardedBy("mLock")
+ private void initializeLocked() {
+ mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+ mPackageManager = mContext.getPackageManager();
+ mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+
+ mLocationFudger = new LocationFudger(mContext, mHandler);
+ mBlacklist = new LocationBlacklist(mContext, mHandler);
+ mBlacklist.init();
+ mGeofenceManager = new GeofenceManager(mContext, mBlacklist);
+
+ // prepare providers
+ initializeProvidersLocked();
+
+ // add listeners
+ mAppOps.startWatchingMode(
+ AppOpsManager.OP_COARSE_LOCATION,
+ null,
+ AppOpsManager.WATCH_FOREGROUND_CHANGES,
+ new AppOpsManager.OnOpChangedInternalListener() {
+ public void onOpChanged(int op, String packageName) {
+ synchronized (mLock) {
+ onAppOpChangedLocked();
+ }
+ }
+ });
+ mPackageManager.addOnPermissionsChangeListener(
+ uid -> {
+ synchronized (mLock) {
+ onPermissionsChangedLocked();
+ }
+ });
+
+ mActivityManager.addOnUidImportanceListener(
+ (uid, importance) -> {
+ synchronized (mLock) {
+ onUidImportanceChangedLocked(uid, importance);
+ }
+ },
+ FOREGROUND_IMPORTANCE_CUTOFF);
mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.LOCATION_PROVIDERS_ALLOWED), true,
- new ContentObserver(mLocationHandler) {
+ Settings.Secure.getUriFor(Settings.Secure.LOCATION_MODE), true,
+ new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
synchronized (mLock) {
- updateProvidersSettingsLocked();
+ onLocationModeChangedLocked(true);
+ }
+ }
+ }, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCATION_PROVIDERS_ALLOWED), true,
+ new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ synchronized (mLock) {
+ onProviderAllowedChangedLocked(true);
}
}
}, UserHandle.USER_ALL);
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS),
true,
- new ContentObserver(mLocationHandler) {
+ new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
synchronized (mLock) {
- for (LocationProvider provider : mProviders) {
- applyRequirementsLocked(provider.getName());
- }
+ onBackgroundThrottleIntervalChangedLocked();
}
}
}, UserHandle.USER_ALL);
mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS),
- true,
- new ContentObserver(mLocationHandler) {
- @Override
- public void onChange(boolean selfChange) {
- synchronized (mLock) {
- updateLastLocationMaxAgeLocked();
- }
- }
- }
- );
- mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(
Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST),
true,
- new ContentObserver(mLocationHandler) {
+ new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
synchronized (mLock) {
- updateBackgroundThrottlingWhitelistLocked();
- for (LocationProvider provider : mProviders) {
- applyRequirementsLocked(provider.getName());
- }
+ onBackgroundThrottleWhitelistChangedLocked();
}
}
}, UserHandle.USER_ALL);
- mPackageMonitor.register(mContext, mLocationHandler.getLooper(), true);
+ new PackageMonitor() {
+ @Override
+ public void onPackageDisappeared(String packageName, int reason) {
+ synchronized (mLock) {
+ LocationManagerService.this.onPackageDisappearedLocked(packageName);
+ }
+ }
+ }.register(mContext, mHandler.getLooper(), true);
- // listen for user change
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
@@ -407,81 +370,152 @@
mContext.registerReceiverAsUser(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (Intent.ACTION_USER_SWITCHED.equals(action)) {
- switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
- } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)
- || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
- updateUserProfiles(mCurrentUserId);
+ synchronized (mLock) {
+ String action = intent.getAction();
+ if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+ onUserChangedLocked(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
+ } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)
+ || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
+ onUserProfilesChangedLocked();
+ }
}
}
- }, UserHandle.ALL, intentFilter, null, mLocationHandler);
+ }, UserHandle.ALL, intentFilter, null, mHandler);
+
+ // switching the user from null to system here performs the bulk of the initialization work.
+ // the user being changed will cause a reload of all user specific settings, which causes
+ // provider initialization, and propagates changes until a steady state is reached
+ mCurrentUserId = UserHandle.USER_NULL;
+ onUserChangedLocked(UserHandle.USER_SYSTEM);
+
+ // initialize in-memory settings values
+ onBackgroundThrottleWhitelistChangedLocked();
}
- private void onUidImportanceChanged(int uid, int importance) {
+ @GuardedBy("mLock")
+ private void onAppOpChangedLocked() {
+ for (Receiver receiver : mReceivers.values()) {
+ receiver.updateMonitoring(true);
+ }
+ for (LocationProvider p : mProviders) {
+ applyRequirementsLocked(p);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void onPermissionsChangedLocked() {
+ for (LocationProvider p : mProviders) {
+ applyRequirementsLocked(p);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void onLocationModeChangedLocked(boolean broadcast) {
+ for (LocationProvider p : mProviders) {
+ p.onLocationModeChangedLocked();
+ }
+
+ if (broadcast) {
+ mContext.sendBroadcastAsUser(
+ new Intent(LocationManager.MODE_CHANGED_ACTION),
+ UserHandle.ALL);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void onProviderAllowedChangedLocked(boolean broadcast) {
+ for (LocationProvider p : mProviders) {
+ p.onAllowedChangedLocked();
+ }
+
+ if (broadcast) {
+ mContext.sendBroadcastAsUser(
+ new Intent(LocationManager.PROVIDERS_CHANGED_ACTION),
+ UserHandle.ALL);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void onPackageDisappearedLocked(String packageName) {
+ ArrayList<Receiver> deadReceivers = null;
+
+ for (Receiver receiver : mReceivers.values()) {
+ if (receiver.mIdentity.mPackageName.equals(packageName)) {
+ if (deadReceivers == null) {
+ deadReceivers = new ArrayList<>();
+ }
+ deadReceivers.add(receiver);
+ }
+ }
+
+ // perform removal outside of mReceivers loop
+ if (deadReceivers != null) {
+ for (Receiver receiver : deadReceivers) {
+ removeUpdatesLocked(receiver);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void onUidImportanceChangedLocked(int uid, int importance) {
boolean foreground = isImportanceForeground(importance);
HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size());
- synchronized (mLock) {
- for (Entry<String, ArrayList<UpdateRecord>> entry
- : mRecordsByProvider.entrySet()) {
- String provider = entry.getKey();
- for (UpdateRecord record : entry.getValue()) {
- if (record.mReceiver.mIdentity.mUid == uid
- && record.mIsForegroundUid != foreground) {
- if (D) {
- Log.d(TAG, "request from uid " + uid + " is now "
- + (foreground ? "foreground" : "background)"));
- }
- record.updateForeground(foreground);
-
- if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) {
- affectedProviders.add(provider);
- }
- }
- }
- }
- for (String provider : affectedProviders) {
- applyRequirementsLocked(provider);
- }
-
- for (Entry<IBinder, Identity> entry : mGnssMeasurementsListeners.entrySet()) {
- Identity callerIdentity = entry.getValue();
- if (callerIdentity.mUid == uid) {
+ for (Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) {
+ String provider = entry.getKey();
+ for (UpdateRecord record : entry.getValue()) {
+ if (record.mReceiver.mIdentity.mUid == uid
+ && record.mIsForegroundUid != foreground) {
if (D) {
- Log.d(TAG, "gnss measurements listener from uid " + uid
- + " is now " + (foreground ? "foreground" : "background)"));
- }
- if (foreground || isThrottlingExemptLocked(entry.getValue())) {
- mGnssMeasurementsProvider.addListener(
- IGnssMeasurementsListener.Stub.asInterface(entry.getKey()),
- callerIdentity.mUid, callerIdentity.mPackageName);
- } else {
- mGnssMeasurementsProvider.removeListener(
- IGnssMeasurementsListener.Stub.asInterface(entry.getKey()));
- }
- }
- }
-
- for (Entry<IBinder, Identity> entry : mGnssNavigationMessageListeners.entrySet()) {
- Identity callerIdentity = entry.getValue();
- if (callerIdentity.mUid == uid) {
- if (D) {
- Log.d(TAG, "gnss navigation message listener from uid "
- + uid + " is now "
+ Log.d(TAG, "request from uid " + uid + " is now "
+ (foreground ? "foreground" : "background)"));
}
- if (foreground || isThrottlingExemptLocked(entry.getValue())) {
- mGnssNavigationMessageProvider.addListener(
- IGnssNavigationMessageListener.Stub.asInterface(entry.getKey()),
- callerIdentity.mUid, callerIdentity.mPackageName);
- } else {
- mGnssNavigationMessageProvider.removeListener(
- IGnssNavigationMessageListener.Stub.asInterface(entry.getKey()));
+ record.updateForeground(foreground);
+
+ if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) {
+ affectedProviders.add(provider);
}
}
}
+ }
+ for (String provider : affectedProviders) {
+ applyRequirementsLocked(provider);
+ }
- // TODO(b/120449926): The GNSS status listeners should be handled similar to the above.
+ for (Entry<IBinder, Identity> entry : mGnssMeasurementsListeners.entrySet()) {
+ Identity callerIdentity = entry.getValue();
+ if (callerIdentity.mUid == uid) {
+ if (D) {
+ Log.d(TAG, "gnss measurements listener from uid " + uid
+ + " is now " + (foreground ? "foreground" : "background)"));
+ }
+ if (foreground || isThrottlingExemptLocked(entry.getValue())) {
+ mGnssMeasurementsProvider.addListener(
+ IGnssMeasurementsListener.Stub.asInterface(entry.getKey()),
+ callerIdentity.mUid, callerIdentity.mPackageName);
+ } else {
+ mGnssMeasurementsProvider.removeListener(
+ IGnssMeasurementsListener.Stub.asInterface(entry.getKey()));
+ }
+ }
+ }
+
+ for (Entry<IBinder, Identity> entry : mGnssNavigationMessageListeners.entrySet()) {
+ Identity callerIdentity = entry.getValue();
+ if (callerIdentity.mUid == uid) {
+ if (D) {
+ Log.d(TAG, "gnss navigation message listener from uid "
+ + uid + " is now "
+ + (foreground ? "foreground" : "background)"));
+ }
+ if (foreground || isThrottlingExemptLocked(entry.getValue())) {
+ mGnssNavigationMessageProvider.addListener(
+ IGnssNavigationMessageListener.Stub.asInterface(entry.getKey()),
+ callerIdentity.mUid, callerIdentity.mPackageName);
+ } else {
+ mGnssNavigationMessageProvider.removeListener(
+ IGnssNavigationMessageListener.Stub.asInterface(entry.getKey()));
+ }
+ }
}
}
@@ -489,30 +523,43 @@
return importance <= FOREGROUND_IMPORTANCE_CUTOFF;
}
- /**
- * Makes a list of userids that are related to the current user. This is
- * relevant when using managed profiles. Otherwise the list only contains
- * the current user.
- *
- * @param currentUserId the current user, who might have an alter-ego.
- */
- private void updateUserProfiles(int currentUserId) {
- int[] profileIds = mUserManager.getProfileIdsWithDisabled(currentUserId);
- synchronized (mLock) {
- mCurrentUserProfiles = profileIds;
+ @GuardedBy("mLock")
+ private void onBackgroundThrottleIntervalChangedLocked() {
+ for (LocationProvider provider : mProviders) {
+ applyRequirementsLocked(provider);
}
}
- /**
- * Checks if the specified userId matches any of the current foreground
- * users stored in mCurrentUserProfiles.
- */
- private boolean isCurrentProfile(int userId) {
- synchronized (mLock) {
- return ArrayUtils.contains(mCurrentUserProfiles, userId);
+ @GuardedBy("mLock")
+ private void onBackgroundThrottleWhitelistChangedLocked() {
+ String setting = Settings.Global.getString(
+ mContext.getContentResolver(),
+ Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST);
+ if (setting == null) {
+ setting = "";
+ }
+
+ mBackgroundThrottlePackageWhitelist.clear();
+ mBackgroundThrottlePackageWhitelist.addAll(
+ SystemConfig.getInstance().getAllowUnthrottledLocation());
+ mBackgroundThrottlePackageWhitelist.addAll(Arrays.asList(setting.split(",")));
+
+ for (LocationProvider p : mProviders) {
+ applyRequirementsLocked(p);
}
}
+ @GuardedBy("mLock")
+ private void onUserProfilesChangedLocked() {
+ mCurrentUserProfiles = mUserManager.getProfileIdsWithDisabled(mCurrentUserId);
+ }
+
+ @GuardedBy("mLock")
+ private boolean isCurrentProfileLocked(int userId) {
+ return ArrayUtils.contains(mCurrentUserProfiles, userId);
+ }
+
+ @GuardedBy("mLock")
private void ensureFallbackFusedProviderPresentLocked(String[] pkgs) {
PackageManager pm = mContext.getPackageManager();
String systemPackageName = mContext.getPackageName();
@@ -583,30 +630,30 @@
+ "partition. The fallback must also be marked coreApp=\"true\" in the manifest");
}
- private void loadProvidersLocked() {
+ @GuardedBy("mLock")
+ private void initializeProvidersLocked() {
// create a passive location provider, which is always enabled
- LocationProvider passiveProviderManager = new LocationProvider(
- LocationManager.PASSIVE_PROVIDER);
- PassiveProvider passiveProvider = new PassiveProvider(passiveProviderManager);
-
+ LocationProvider passiveProviderManager = new LocationProvider(PASSIVE_PROVIDER);
addProviderLocked(passiveProviderManager);
- mPassiveProvider = passiveProvider;
+ mPassiveProvider = new PassiveProvider(passiveProviderManager);
+ passiveProviderManager.attachLocked(mPassiveProvider);
if (GnssLocationProvider.isSupported()) {
// Create a gps location provider
- LocationProvider gnssProviderManager = new LocationProvider(
- LocationManager.GPS_PROVIDER);
+ LocationProvider gnssProviderManager = new LocationProvider(GPS_PROVIDER, true);
+ mRealProviders.add(gnssProviderManager);
+ addProviderLocked(gnssProviderManager);
+
GnssLocationProvider gnssProvider = new GnssLocationProvider(mContext,
gnssProviderManager,
- mLocationHandler.getLooper());
+ mHandler.getLooper());
+ gnssProviderManager.attachLocked(gnssProvider);
mGnssSystemInfoProvider = gnssProvider.getGnssSystemInfoProvider();
mGnssBatchingProvider = gnssProvider.getGnssBatchingProvider();
mGnssMetricsProvider = gnssProvider.getGnssMetricsProvider();
mGnssStatusProvider = gnssProvider.getGnssStatusProvider();
mNetInitiatedListener = gnssProvider.getNetInitiatedListener();
- addProviderLocked(gnssProviderManager);
- mRealProviders.put(LocationManager.GPS_PROVIDER, gnssProviderManager);
mGnssMeasurementsProvider = gnssProvider.getGnssMeasurementsProvider();
mGnssNavigationMessageProvider = gnssProvider.getGnssNavigationMessageProvider();
mGpsGeofenceProxy = gnssProvider.getGpsGeofenceProxy();
@@ -634,9 +681,7 @@
ensureFallbackFusedProviderPresentLocked(pkgs);
// bind to network provider
-
- LocationProvider networkProviderManager = new LocationProvider(
- LocationManager.NETWORK_PROVIDER);
+ LocationProvider networkProviderManager = new LocationProvider(NETWORK_PROVIDER, true);
LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind(
mContext,
networkProviderManager,
@@ -645,16 +690,15 @@
com.android.internal.R.string.config_networkLocationProviderPackageName,
com.android.internal.R.array.config_locationProviderPackageNames);
if (networkProvider != null) {
- mRealProviders.put(LocationManager.NETWORK_PROVIDER, networkProviderManager);
- mProxyProviders.add(networkProvider);
+ mRealProviders.add(networkProviderManager);
addProviderLocked(networkProviderManager);
+ networkProviderManager.attachLocked(networkProvider);
} else {
Slog.w(TAG, "no network location provider found");
}
// bind to fused provider
- LocationProvider fusedProviderManager = new LocationProvider(
- LocationManager.FUSED_PROVIDER);
+ LocationProvider fusedProviderManager = new LocationProvider(FUSED_PROVIDER);
LocationProviderProxy fusedProvider = LocationProviderProxy.createAndBind(
mContext,
fusedProviderManager,
@@ -663,9 +707,9 @@
com.android.internal.R.string.config_fusedLocationProviderPackageName,
com.android.internal.R.array.config_locationProviderPackageNames);
if (fusedProvider != null) {
+ mRealProviders.add(fusedProviderManager);
addProviderLocked(fusedProviderManager);
- mProxyProviders.add(fusedProvider);
- mRealProviders.put(LocationManager.FUSED_PROVIDER, fusedProviderManager);
+ fusedProviderManager.attachLocked(fusedProvider);
} else {
Slog.e(TAG, "no fused location provider found",
new IllegalStateException("Location service needs a fused location provider"));
@@ -715,9 +759,6 @@
for (String testProviderString : testProviderStrings) {
String fragments[] = testProviderString.split(",");
String name = fragments[0].trim();
- if (mProvidersByName.get(name) != null) {
- throw new IllegalArgumentException("Provider \"" + name + "\" already exists");
- }
ProviderProperties properties = new ProviderProperties(
Boolean.parseBoolean(fragments[1]) /* requiresNetwork */,
Boolean.parseBoolean(fragments[2]) /* requiresSatellite */,
@@ -728,28 +769,37 @@
Boolean.parseBoolean(fragments[7]) /* supportsBearing */,
Integer.parseInt(fragments[8]) /* powerRequirement */,
Integer.parseInt(fragments[9]) /* accuracy */);
- addTestProviderLocked(name, properties);
+ LocationProvider testProviderManager = new LocationProvider(name);
+ addProviderLocked(testProviderManager);
+ new MockProvider(testProviderManager, properties);
}
}
- /**
- * Called when the device's active user changes.
- *
- * @param userId the new active user's UserId
- */
- private void switchUser(int userId) {
+ @GuardedBy("mLock")
+ private void onUserChangedLocked(int userId) {
if (mCurrentUserId == userId) {
return;
}
- mBlacklist.switchUser(userId);
- mLocationHandler.removeMessages(MSG_LOCATION_CHANGED);
- synchronized (mLock) {
- mLastLocation.clear();
- mLastLocationCoarseInterval.clear();
- updateUserProfiles(userId);
- updateProvidersSettingsLocked();
- mCurrentUserId = userId;
+
+ // this call has the side effect of forcing a write to the LOCATION_MODE setting in an OS
+ // upgrade case, and ensures that if anyone checks the LOCATION_MODE setting directly, they
+ // will see it in an appropriate state (at least after that user becomes foreground for the
+ // first time...)
+ isLocationEnabledForUser(userId);
+
+ // let providers know the current user is on the way out before changing the user
+ for (LocationProvider p : mProviders) {
+ p.onUserChangingLocked();
}
+
+ mCurrentUserId = userId;
+ onUserProfilesChangedLocked();
+
+ mBlacklist.switchUser(userId);
+
+ // if the user changes, per-user settings may also have changed
+ onLocationModeChangedLocked(false);
+ onProviderAllowedChangedLocked(false);
}
private static final class Identity {
@@ -767,158 +817,380 @@
private class LocationProvider implements AbstractLocationProvider.LocationProviderManager {
private final String mName;
- private AbstractLocationProvider mProvider;
- // whether the provider is enabled in location settings
- private boolean mSettingsEnabled;
+ // whether this provider should respect LOCATION_PROVIDERS_ALLOWED (ie gps and network)
+ private final boolean mIsManagedBySettings;
- // whether the provider considers itself enabled
- private volatile boolean mEnabled;
+ // remember to clear binder identity before invoking any provider operation
+ @GuardedBy("mLock")
+ @Nullable protected AbstractLocationProvider mProvider;
- @Nullable
- private volatile ProviderProperties mProperties;
+ @GuardedBy("mLock")
+ private boolean mUseable; // combined state
+ @GuardedBy("mLock")
+ private boolean mAllowed; // state of LOCATION_PROVIDERS_ALLOWED
+ @GuardedBy("mLock")
+ private boolean mEnabled; // state of provider
+
+ @GuardedBy("mLock")
+ @Nullable private ProviderProperties mProperties;
private LocationProvider(String name) {
- mName = name;
- // TODO: initialize settings enabled?
+ this(name, false);
}
- @Override
- public void onAttachProvider(AbstractLocationProvider provider, boolean initiallyEnabled) {
- checkState(mProvider == null);
+ private LocationProvider(String name, boolean isManagedBySettings) {
+ mName = name;
+ mIsManagedBySettings = isManagedBySettings;
- // the provider is not yet fully constructed at this point, so we may not do anything
- // except save a reference for later use here. do not call any provider methods.
- mProvider = provider;
- mEnabled = initiallyEnabled;
+ mProvider = null;
+ mUseable = false;
+ mAllowed = !mIsManagedBySettings;
+ mEnabled = false;
mProperties = null;
}
+ @GuardedBy("mLock")
+ public void attachLocked(AbstractLocationProvider provider) {
+ checkNotNull(provider);
+ checkState(mProvider == null);
+ mProvider = provider;
+
+ onUseableChangedLocked();
+ }
+
public String getName() {
return mName;
}
- public boolean isEnabled() {
- return mSettingsEnabled && mEnabled;
+ @GuardedBy("mLock")
+ @Nullable
+ public String getPackageLocked() {
+ if (mProvider == null) {
+ return null;
+ } else if (mProvider instanceof LocationProviderProxy) {
+ // safe to not clear binder context since this doesn't call into the actual provider
+ return ((LocationProviderProxy) mProvider).getConnectedPackageName();
+ } else {
+ return mContext.getPackageName();
+ }
}
+ public boolean isMock() {
+ return false;
+ }
+
+ @GuardedBy("mLock")
+ public boolean isPassiveLocked() {
+ return mProvider == mPassiveProvider;
+ }
+
+ @GuardedBy("mLock")
@Nullable
- public ProviderProperties getProperties() {
+ public ProviderProperties getPropertiesLocked() {
return mProperties;
}
- public void setRequest(ProviderRequest request, WorkSource workSource) {
- mProvider.setRequest(request, workSource);
+ @GuardedBy("mLock")
+ public void setRequestLocked(ProviderRequest request, WorkSource workSource) {
+ if (mProvider != null) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mProvider.setRequest(request, workSource);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
}
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ @GuardedBy("mLock")
+ public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(mName + " provider:");
- pw.println(" setting=" + mSettingsEnabled);
+ if (isMock()) {
+ pw.println(" mock=true");
+ }
+ pw.println(" attached=" + (mProvider != null));
+ if (mIsManagedBySettings) {
+ pw.println(" allowed=" + mAllowed);
+ }
pw.println(" enabled=" + mEnabled);
+ pw.println(" useable=" + mUseable);
pw.println(" properties=" + mProperties);
- mProvider.dump(fd, pw, args);
+
+ if (mProvider != null) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mProvider.dump(fd, pw, args);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
}
- public long getStatusUpdateTime() {
- return mProvider.getStatusUpdateTime();
+ @GuardedBy("mLock")
+ public long getStatusUpdateTimeLocked() {
+ if (mProvider != null) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ return mProvider.getStatusUpdateTime();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ } else {
+ return 0;
+ }
}
- public int getStatus(Bundle extras) {
- return mProvider.getStatus(extras);
+ @GuardedBy("mLock")
+ public int getStatusLocked(Bundle extras) {
+ if (mProvider != null) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ return mProvider.getStatus(extras);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ } else {
+ return AVAILABLE;
+ }
}
- public void sendExtraCommand(String command, Bundle extras) {
- mProvider.sendExtraCommand(command, extras);
+ @GuardedBy("mLock")
+ public void sendExtraCommandLocked(String command, Bundle extras) {
+ if (mProvider != null) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mProvider.sendExtraCommand(command, extras);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
}
// called from any thread
@Override
public void onReportLocation(Location location) {
- runOnHandler(() -> LocationManagerService.this.reportLocation(location,
- mProvider == mPassiveProvider));
+ // no security check necessary because this is coming from an internal-only interface
+ // move calls coming from below LMS onto a different thread to avoid deadlock
+ runInternal(() -> {
+ synchronized (mLock) {
+ handleLocationChangedLocked(location, this);
+ }
+ });
}
// called from any thread
@Override
public void onReportLocation(List<Location> locations) {
- runOnHandler(() -> LocationManagerService.this.reportLocationBatch(locations));
+ // move calls coming from below LMS onto a different thread to avoid deadlock
+ runInternal(() -> {
+ synchronized (mLock) {
+ LocationProvider gpsProvider = getLocationProviderLocked(GPS_PROVIDER);
+ if (gpsProvider == null || !gpsProvider.isUseableLocked()) {
+ Slog.w(TAG, "reportLocationBatch() called without user permission");
+ return;
+ }
+
+ if (mGnssBatchingCallback == null) {
+ Slog.e(TAG, "reportLocationBatch() called without active Callback");
+ return;
+ }
+
+ try {
+ mGnssBatchingCallback.onLocationBatch(locations);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "mGnssBatchingCallback.onLocationBatch failed", e);
+ }
+ }
+ });
}
// called from any thread
@Override
public void onSetEnabled(boolean enabled) {
- runOnHandler(() -> {
- if (enabled == mEnabled) {
- return;
- }
-
- mEnabled = enabled;
-
- if (!mSettingsEnabled) {
- // this provider was disabled in settings anyways, so a change to it's own
- // enabled status won't have any affect.
- return;
- }
-
- // traditionally clients can listen for changes to the LOCATION_PROVIDERS_ALLOWED
- // setting to detect when providers are enabled or disabled (even though they aren't
- // supposed to). to continue to support this we must force a change to this setting.
- // we use the fused provider because this is forced to be always enabled in settings
- // anyways, and so won't have any visible effect beyond triggering content observers
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
- "+" + LocationManager.FUSED_PROVIDER, mCurrentUserId);
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
- "-" + LocationManager.FUSED_PROVIDER, mCurrentUserId);
-
+ // move calls coming from below LMS onto a different thread to avoid deadlock
+ runInternal(() -> {
synchronized (mLock) {
- if (!enabled) {
- // If any provider has been disabled, clear all last locations for all
- // providers. This is to be on the safe side in case a provider has location
- // derived from this disabled provider.
- mLastLocation.clear();
- mLastLocationCoarseInterval.clear();
+ if (enabled == mEnabled) {
+ return;
}
- updateProviderListenersLocked(mName);
- }
+ mEnabled = enabled;
- mContext.sendBroadcastAsUser(new Intent(LocationManager.PROVIDERS_CHANGED_ACTION),
- UserHandle.ALL);
+ // update provider allowed settings to reflect enabled status
+ if (mIsManagedBySettings) {
+ if (mEnabled && !mAllowed) {
+ Settings.Secure.putStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+ "+" + mName,
+ mCurrentUserId);
+ } else if (!mEnabled && mAllowed) {
+ Settings.Secure.putStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+ "-" + mName,
+ mCurrentUserId);
+ }
+ }
+
+ onUseableChangedLocked();
+ }
});
}
@Override
public void onSetProperties(ProviderProperties properties) {
- runOnHandler(() -> mProperties = properties);
+ // move calls coming from below LMS onto a different thread to avoid deadlock
+ runInternal(() -> {
+ synchronized (mLock) {
+ mProperties = properties;
+ }
+ });
}
- private void setSettingsEnabled(boolean enabled) {
- synchronized (mLock) {
- if (mSettingsEnabled == enabled) {
+ @GuardedBy("mLock")
+ public void onLocationModeChangedLocked() {
+ onUseableChangedLocked();
+ }
+
+ private boolean isAllowed() {
+ return isAllowedForUser(mCurrentUserId);
+ }
+
+ private boolean isAllowedForUser(int userId) {
+ String allowedProviders = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+ userId);
+ return TextUtils.delimitedStringContains(allowedProviders, ',', mName);
+ }
+
+ @GuardedBy("mLock")
+ public void onAllowedChangedLocked() {
+ if (mIsManagedBySettings) {
+ boolean allowed = isAllowed();
+ if (allowed == mAllowed) {
return;
}
+ mAllowed = allowed;
- mSettingsEnabled = enabled;
- if (!mSettingsEnabled) {
- // if any provider has been disabled, clear all last locations for all
- // providers. this is to be on the safe side in case a provider has location
- // derived from this disabled provider.
- mLastLocation.clear();
- mLastLocationCoarseInterval.clear();
- updateProviderListenersLocked(mName);
- } else if (mEnabled) {
- updateProviderListenersLocked(mName);
+ // make a best effort to keep the setting matching the real enabled state of the
+ // provider so that legacy applications aren't broken.
+ if (mAllowed && !mEnabled) {
+ Settings.Secure.putStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+ "-" + mName,
+ mCurrentUserId);
+ }
+
+ onUseableChangedLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ public boolean isUseableLocked() {
+ return isUseableForUserLocked(mCurrentUserId);
+ }
+
+ @GuardedBy("mLock")
+ public boolean isUseableForUserLocked(int userId) {
+ return userId == mCurrentUserId && mUseable;
+ }
+
+ @GuardedBy("mLock")
+ public void onUseableChangedLocked() {
+ // if any property that contributes to "useability" here changes state, it MUST result
+ // in a direct or indrect call to onUseableChangedLocked. this allows the provider to
+ // guarantee that it will always eventually reach the correct state.
+ boolean useable = mProvider != null
+ && mProviders.contains(this) && isLocationEnabled() && mAllowed && mEnabled;
+ if (useable == mUseable) {
+ return;
+ }
+ mUseable = useable;
+
+ if (!mUseable) {
+ // If any provider has been disabled, clear all last locations for all
+ // providers. This is to be on the safe side in case a provider has location
+ // derived from this disabled provider.
+ mLastLocation.clear();
+ mLastLocationCoarseInterval.clear();
+ }
+
+ updateProviderUseableLocked(this);
+ }
+
+ @GuardedBy("mLock")
+ public void onUserChangingLocked() {
+ // when the user is about to change, we set this provider to un-useable, and notify all
+ // of the current user clients. when the user is finished changing, useability will be
+ // updated back via onLocationModeChanged() and onAllowedChanged().
+ mUseable = false;
+ updateProviderUseableLocked(this);
+ }
+
+ // binder transactions coming from below LMS (ie location providers) need to be moved onto
+ // a different thread to avoid potential deadlock as code reenters the location providers
+ private void runInternal(Runnable runnable) {
+ if (Looper.myLooper() == mHandler.getLooper()) {
+ runnable.run();
+ } else {
+ mHandler.post(runnable);
+ }
+ }
+ }
+
+ private class MockLocationProvider extends LocationProvider {
+
+ private MockLocationProvider(String name) {
+ super(name);
+ }
+
+ @Override
+ public void attachLocked(AbstractLocationProvider provider) {
+ checkState(provider instanceof MockProvider);
+ super.attachLocked(provider);
+ }
+
+ public boolean isMock() {
+ return true;
+ }
+
+ @GuardedBy("mLock")
+ public void setEnabledLocked(boolean enabled) {
+ if (mProvider != null) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ ((MockProvider) mProvider).setEnabled(enabled);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
}
}
- private void runOnHandler(Runnable runnable) {
- if (Looper.myLooper() == mLocationHandler.getLooper()) {
- runnable.run();
- } else {
- mLocationHandler.post(runnable);
+ @GuardedBy("mLock")
+ public void setLocationLocked(Location location) {
+ if (mProvider != null) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ ((MockProvider) mProvider).setLocation(location);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ public void setStatusLocked(int status, Bundle extras, long updateTime) {
+ if (mProvider != null) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ ((MockProvider) mProvider).setStatus(status, extras, updateTime);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
}
@@ -1022,19 +1294,18 @@
// See if receiver has any enabled update records. Also note if any update records
// are high power (has a high power provider with an interval under a threshold).
for (UpdateRecord updateRecord : mUpdateRecords.values()) {
- if (isAllowedByUserSettingsLockedForUser(updateRecord.mProvider,
- mCurrentUserId)) {
- requestingLocation = true;
- LocationManagerService.LocationProvider locationProvider
- = mProvidersByName.get(updateRecord.mProvider);
- ProviderProperties properties = locationProvider != null
- ? locationProvider.getProperties() : null;
- if (properties != null
- && properties.mPowerRequirement == Criteria.POWER_HIGH
- && updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) {
- requestingHighPowerLocation = true;
- break;
- }
+ LocationProvider provider = getLocationProviderLocked(updateRecord.mProvider);
+ if (provider == null || !provider.isUseableLocked()) {
+ continue;
+ }
+
+ requestingLocation = true;
+ ProviderProperties properties = provider.getPropertiesLocked();
+ if (properties != null
+ && properties.mPowerRequirement == Criteria.POWER_HIGH
+ && updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) {
+ requestingHighPowerLocation = true;
+ break;
}
}
}
@@ -1122,7 +1393,7 @@
synchronized (this) {
// synchronize to ensure incrementPendingBroadcastsLocked()
// is called before decrementPendingBroadcasts()
- mPendingIntent.send(mContext, 0, statusChanged, this, mLocationHandler,
+ mPendingIntent.send(mContext, 0, statusChanged, this, mHandler,
getResolutionPermission(mAllowedResolutionLevel),
PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
// call this after broadcasting so we do not increment
@@ -1158,7 +1429,7 @@
synchronized (this) {
// synchronize to ensure incrementPendingBroadcastsLocked()
// is called before decrementPendingBroadcasts()
- mPendingIntent.send(mContext, 0, locationChanged, this, mLocationHandler,
+ mPendingIntent.send(mContext, 0, locationChanged, this, mHandler,
getResolutionPermission(mAllowedResolutionLevel),
PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
// call this after broadcasting so we do not increment
@@ -1201,7 +1472,7 @@
synchronized (this) {
// synchronize to ensure incrementPendingBroadcastsLocked()
// is called before decrementPendingBroadcasts()
- mPendingIntent.send(mContext, 0, providerIntent, this, mLocationHandler,
+ mPendingIntent.send(mContext, 0, providerIntent, this, mHandler,
getResolutionPermission(mAllowedResolutionLevel),
PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
// call this after broadcasting so we do not increment
@@ -1273,16 +1544,16 @@
synchronized (receiver) {
// so wakelock calls will succeed
long identity = Binder.clearCallingIdentity();
- receiver.decrementPendingBroadcastsLocked();
- Binder.restoreCallingIdentity(identity);
+ try {
+ receiver.decrementPendingBroadcastsLocked();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
}
}
- /**
- * Returns the year of the GNSS hardware.
- */
@Override
public int getGnssYearOfHardware() {
if (mGnssSystemInfoProvider != null) {
@@ -1292,10 +1563,6 @@
}
}
-
- /**
- * Returns the model name of the GNSS hardware.
- */
@Override
@Nullable
public String getGnssHardwareModelName() {
@@ -1306,32 +1573,24 @@
}
}
- /**
- * Runs some checks for GNSS (FINE) level permissions, used by several methods which directly
- * (try to) access GNSS information at this layer.
- */
private boolean hasGnssPermissions(String packageName) {
- int allowedResolutionLevel = getCallerAllowedResolutionLevel();
- checkResolutionLevelIsSufficientForProviderUse(
- allowedResolutionLevel,
- LocationManager.GPS_PROVIDER);
+ synchronized (mLock) {
+ int allowedResolutionLevel = getCallerAllowedResolutionLevel();
+ checkResolutionLevelIsSufficientForProviderUseLocked(
+ allowedResolutionLevel,
+ GPS_PROVIDER);
- int pid = Binder.getCallingPid();
- int uid = Binder.getCallingUid();
- long identity = Binder.clearCallingIdentity();
- boolean hasLocationAccess;
- try {
- hasLocationAccess = checkLocationAccess(pid, uid, packageName, allowedResolutionLevel);
- } finally {
- Binder.restoreCallingIdentity(identity);
+ int pid = Binder.getCallingPid();
+ int uid = Binder.getCallingUid();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ return checkLocationAccess(pid, uid, packageName, allowedResolutionLevel);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
-
- return hasLocationAccess;
}
- /**
- * Returns the GNSS batching size, if available.
- */
@Override
public int getGnssBatchSize(String packageName) {
mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
@@ -1344,10 +1603,6 @@
}
}
- /**
- * Adds a callback for GNSS Batching events, if permissions allow, which are transported
- * to potentially multiple listeners by the BatchedLocationCallbackTransport above this.
- */
@Override
public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName) {
mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
@@ -1357,18 +1612,20 @@
return false;
}
- mGnssBatchingCallback = callback;
- mGnssBatchingDeathCallback = new LinkedCallback(callback);
- try {
- callback.asBinder().linkToDeath(mGnssBatchingDeathCallback, 0 /* flags */);
- } catch (RemoteException e) {
- // if the remote process registering the listener is already dead, just swallow the
- // exception and return
- Log.e(TAG, "Remote listener already died.", e);
- return false;
- }
+ synchronized (mLock) {
+ mGnssBatchingCallback = callback;
+ mGnssBatchingDeathCallback = new LinkedCallback(callback);
+ try {
+ callback.asBinder().linkToDeath(mGnssBatchingDeathCallback, 0 /* flags */);
+ } catch (RemoteException e) {
+ // if the remote process registering the listener is already dead, just swallow the
+ // exception and return
+ Log.e(TAG, "Remote listener already died.", e);
+ return false;
+ }
- return true;
+ return true;
+ }
}
private class LinkedCallback implements IBinder.DeathRecipient {
@@ -1391,27 +1648,22 @@
}
}
- /**
- * Removes callback for GNSS batching
- */
@Override
public void removeGnssBatchingCallback() {
- try {
- mGnssBatchingCallback.asBinder().unlinkToDeath(mGnssBatchingDeathCallback,
- 0 /* flags */);
- } catch (NoSuchElementException e) {
- // if the death callback isn't connected (it should be...), log error, swallow the
- // exception and return
- Log.e(TAG, "Couldn't unlink death callback.", e);
+ synchronized (mLock) {
+ try {
+ mGnssBatchingCallback.asBinder().unlinkToDeath(mGnssBatchingDeathCallback,
+ 0 /* flags */);
+ } catch (NoSuchElementException e) {
+ // if the death callback isn't connected (it should be...), log error, swallow the
+ // exception and return
+ Log.e(TAG, "Couldn't unlink death callback.", e);
+ }
+ mGnssBatchingCallback = null;
+ mGnssBatchingDeathCallback = null;
}
- mGnssBatchingCallback = null;
- mGnssBatchingDeathCallback = null;
}
-
- /**
- * Starts GNSS batching, if available.
- */
@Override
public boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName) {
mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
@@ -1421,20 +1673,20 @@
return false;
}
- if (mGnssBatchingInProgress) {
- // Current design does not expect multiple starts to be called repeatedly
- Log.e(TAG, "startGnssBatch unexpectedly called w/o stopping prior batch");
- // Try to clean up anyway, and continue
- stopGnssBatch();
- }
+ synchronized (mLock) {
+ if (mGnssBatchingInProgress) {
+ // Current design does not expect multiple starts to be called repeatedly
+ Log.e(TAG, "startGnssBatch unexpectedly called w/o stopping prior batch");
+ // Try to clean up anyway, and continue
+ stopGnssBatch();
+ }
- mGnssBatchingInProgress = true;
- return mGnssBatchingProvider.start(periodNanos, wakeOnFifoFull);
+ mGnssBatchingInProgress = true;
+ return mGnssBatchingProvider.start(periodNanos, wakeOnFifoFull);
+ }
}
- /**
- * Flushes a GNSS batch in progress
- */
+
@Override
public void flushGnssBatch(String packageName) {
mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
@@ -1445,117 +1697,66 @@
return;
}
- if (!mGnssBatchingInProgress) {
- Log.w(TAG, "flushGnssBatch called with no batch in progress");
- }
+ synchronized (mLock) {
+ if (!mGnssBatchingInProgress) {
+ Log.w(TAG, "flushGnssBatch called with no batch in progress");
+ }
- if (mGnssBatchingProvider != null) {
- mGnssBatchingProvider.flush();
+ if (mGnssBatchingProvider != null) {
+ mGnssBatchingProvider.flush();
+ }
}
}
- /**
- * Stops GNSS batching
- */
@Override
public boolean stopGnssBatch() {
mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
"Location Hardware permission not granted to access hardware batching");
- if (mGnssBatchingProvider != null) {
- mGnssBatchingInProgress = false;
- return mGnssBatchingProvider.stop();
- } else {
- return false;
- }
- }
-
- @Override
- public void reportLocationBatch(List<Location> locations) {
- checkCallerIsProvider();
-
- // Currently used only for GNSS locations - update permissions check if changed
- if (isAllowedByUserSettingsLockedForUser(LocationManager.GPS_PROVIDER, mCurrentUserId)) {
- if (mGnssBatchingCallback == null) {
- Slog.e(TAG, "reportLocationBatch() called without active Callback");
- return;
- }
- try {
- mGnssBatchingCallback.onLocationBatch(locations);
- } catch (RemoteException e) {
- Slog.e(TAG, "mGnssBatchingCallback.onLocationBatch failed", e);
- }
- } else {
- Slog.w(TAG, "reportLocationBatch() called without user permission, locations blocked");
- }
- }
-
- private void addProviderLocked(LocationProvider provider) {
- mProviders.add(provider);
- mProvidersByName.put(provider.getName(), provider);
- }
-
- private void removeProviderLocked(LocationProvider provider) {
- mProviders.remove(provider);
- mProvidersByName.remove(provider.getName());
- }
-
- /**
- * Returns "true" if access to the specified location provider is allowed by the specified
- * user's settings. Access to all location providers is forbidden to non-location-provider
- * processes belonging to background users.
- *
- * @param provider the name of the location provider
- * @param userId the user id to query
- */
- private boolean isAllowedByUserSettingsLockedForUser(String provider, int userId) {
- if (LocationManager.PASSIVE_PROVIDER.equals(provider)) {
- return isLocationEnabledForUser(userId);
- }
- if (LocationManager.FUSED_PROVIDER.equals(provider)) {
- return isLocationEnabledForUser(userId);
- }
synchronized (mLock) {
- if (mMockProviders.containsKey(provider)) {
- return isLocationEnabledForUser(userId);
+ if (mGnssBatchingProvider != null) {
+ mGnssBatchingInProgress = false;
+ return mGnssBatchingProvider.stop();
+ } else {
+ return false;
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void addProviderLocked(LocationProvider provider) {
+ Preconditions.checkState(getLocationProviderLocked(provider.getName()) == null);
+
+ mProviders.add(provider);
+
+ provider.onAllowedChangedLocked(); // allowed state may change while provider was inactive
+ provider.onUseableChangedLocked();
+ }
+
+ @GuardedBy("mLock")
+ private void removeProviderLocked(LocationProvider provider) {
+ if (mProviders.remove(provider)) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ provider.onUseableChangedLocked();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private LocationProvider getLocationProviderLocked(String providerName) {
+ for (LocationProvider provider : mProviders) {
+ if (providerName.equals(provider.getName())) {
+ return provider;
}
}
- long identity = Binder.clearCallingIdentity();
- try {
- // Use system settings
- ContentResolver cr = mContext.getContentResolver();
- String allowedProviders = Settings.Secure.getStringForUser(
- cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, userId);
- return TextUtils.delimitedStringContains(allowedProviders, ',', provider);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
+ return null;
}
-
- /**
- * Returns "true" if access to the specified location provider is allowed by the specified
- * user's settings. Access to all location providers is forbidden to non-location-provider
- * processes belonging to background users.
- *
- * @param provider the name of the location provider
- * @param uid the requestor's UID
- * @param userId the user id to query
- */
- private boolean isAllowedByUserSettingsLocked(String provider, int uid, int userId) {
- if (!isCurrentProfile(UserHandle.getUserId(uid)) && !isUidALocationProvider(uid)) {
- return false;
- }
- return isAllowedByUserSettingsLockedForUser(provider, userId);
- }
-
- /**
- * Returns the permission string associated with the specified resolution level.
- *
- * @param resolutionLevel the resolution level
- * @return the permission string
- */
private String getResolutionPermission(int resolutionLevel) {
switch (resolutionLevel) {
case RESOLUTION_LEVEL_FINE:
@@ -1567,13 +1768,6 @@
}
}
- /**
- * Returns the resolution level allowed to the given PID/UID pair.
- *
- * @param pid the PID
- * @param uid the UID
- * @return resolution level allowed to the pid/uid pair
- */
private int getAllowedResolutionLevel(int pid, int uid) {
if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
pid, uid) == PERMISSION_GRANTED) {
@@ -1586,39 +1780,22 @@
}
}
- /**
- * Returns the resolution level allowed to the caller
- *
- * @return resolution level allowed to caller
- */
private int getCallerAllowedResolutionLevel() {
return getAllowedResolutionLevel(Binder.getCallingPid(), Binder.getCallingUid());
}
- /**
- * Throw SecurityException if specified resolution level is insufficient to use geofences.
- *
- * @param allowedResolutionLevel resolution level allowed to caller
- */
private void checkResolutionLevelIsSufficientForGeofenceUse(int allowedResolutionLevel) {
if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
throw new SecurityException("Geofence usage requires ACCESS_FINE_LOCATION permission");
}
}
- /**
- * Return the minimum resolution level required to use the specified location provider.
- *
- * @param provider the name of the location provider
- * @return minimum resolution level required for provider
- */
- private int getMinimumResolutionLevelForProviderUse(String provider) {
- if (LocationManager.GPS_PROVIDER.equals(provider) ||
- LocationManager.PASSIVE_PROVIDER.equals(provider)) {
+ @GuardedBy("mLock")
+ private int getMinimumResolutionLevelForProviderUseLocked(String provider) {
+ if (GPS_PROVIDER.equals(provider) || PASSIVE_PROVIDER.equals(provider)) {
// gps and passive providers require FINE permission
return RESOLUTION_LEVEL_FINE;
- } else if (LocationManager.NETWORK_PROVIDER.equals(provider) ||
- LocationManager.FUSED_PROVIDER.equals(provider)) {
+ } else if (NETWORK_PROVIDER.equals(provider) || FUSED_PROVIDER.equals(provider)) {
// network and fused providers are ok with COARSE or FINE
return RESOLUTION_LEVEL_COARSE;
} else {
@@ -1627,7 +1804,7 @@
continue;
}
- ProviderProperties properties = lp.getProperties();
+ ProviderProperties properties = lp.getPropertiesLocked();
if (properties != null) {
if (properties.mRequiresSatellite) {
// provider requiring satellites require FINE permission
@@ -1643,16 +1820,10 @@
return RESOLUTION_LEVEL_FINE; // if in doubt, require FINE
}
- /**
- * Throw SecurityException if specified resolution level is insufficient to use the named
- * location provider.
- *
- * @param allowedResolutionLevel resolution level allowed to caller
- * @param providerName the name of the location provider
- */
- private void checkResolutionLevelIsSufficientForProviderUse(int allowedResolutionLevel,
+ @GuardedBy("mLock")
+ private void checkResolutionLevelIsSufficientForProviderUseLocked(int allowedResolutionLevel,
String providerName) {
- int requiredResolutionLevel = getMinimumResolutionLevelForProviderUse(providerName);
+ int requiredResolutionLevel = getMinimumResolutionLevelForProviderUseLocked(providerName);
if (allowedResolutionLevel < requiredResolutionLevel) {
switch (requiredResolutionLevel) {
case RESOLUTION_LEVEL_FINE:
@@ -1668,20 +1839,6 @@
}
}
- /**
- * Throw SecurityException if WorkSource use is not allowed (i.e. can't blame other packages
- * for battery).
- */
- private void checkDeviceStatsAllowed() {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.UPDATE_DEVICE_STATS, null);
- }
-
- private void checkUpdateAppOpsAllowed() {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.UPDATE_APP_OPS_STATS, null);
- }
-
public static int resolutionLevelToOp(int allowedResolutionLevel) {
if (allowedResolutionLevel != RESOLUTION_LEVEL_NONE) {
if (allowedResolutionLevel == RESOLUTION_LEVEL_COARSE) {
@@ -1739,19 +1896,17 @@
*/
@Override
public List<String> getAllProviders() {
- ArrayList<String> out;
synchronized (mLock) {
- out = new ArrayList<>(mProviders.size());
+ ArrayList<String> providers = new ArrayList<>(mProviders.size());
for (LocationProvider provider : mProviders) {
String name = provider.getName();
- if (LocationManager.FUSED_PROVIDER.equals(name)) {
+ if (FUSED_PROVIDER.equals(name)) {
continue;
}
- out.add(name);
+ providers.add(name);
}
+ return providers;
}
- if (D) Log.d(TAG, "getAllProviders()=" + out);
- return out;
}
/**
@@ -1762,37 +1917,28 @@
@Override
public List<String> getProviders(Criteria criteria, boolean enabledOnly) {
int allowedResolutionLevel = getCallerAllowedResolutionLevel();
- ArrayList<String> out;
- int uid = Binder.getCallingUid();
- long identity = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
- out = new ArrayList<>(mProviders.size());
- for (LocationProvider provider : mProviders) {
- String name = provider.getName();
- if (LocationManager.FUSED_PROVIDER.equals(name)) {
- continue;
- }
- if (allowedResolutionLevel >= getMinimumResolutionLevelForProviderUse(name)) {
- if (enabledOnly
- && !isAllowedByUserSettingsLocked(name, uid, mCurrentUserId)) {
- continue;
- }
- if (criteria != null
- && !android.location.LocationProvider.propertiesMeetCriteria(
- name, provider.getProperties(), criteria)) {
- continue;
- }
- out.add(name);
- }
+ synchronized (mLock) {
+ ArrayList<String> providers = new ArrayList<>(mProviders.size());
+ for (LocationProvider provider : mProviders) {
+ String name = provider.getName();
+ if (FUSED_PROVIDER.equals(name)) {
+ continue;
}
+ if (allowedResolutionLevel < getMinimumResolutionLevelForProviderUseLocked(name)) {
+ continue;
+ }
+ if (enabledOnly && !provider.isUseableLocked()) {
+ continue;
+ }
+ if (criteria != null
+ && !android.location.LocationProvider.propertiesMeetCriteria(
+ name, provider.getPropertiesLocked(), criteria)) {
+ continue;
+ }
+ providers.add(name);
}
- } finally {
- Binder.restoreCallingIdentity(identity);
+ return providers;
}
-
- if (D) Log.d(TAG, "getProviders()=" + out);
- return out;
}
/**
@@ -1804,71 +1950,36 @@
*/
@Override
public String getBestProvider(Criteria criteria, boolean enabledOnly) {
- String result;
-
List<String> providers = getProviders(criteria, enabledOnly);
- if (!providers.isEmpty()) {
- result = pickBest(providers);
- if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + result);
- return result;
- }
- providers = getProviders(null, enabledOnly);
- if (!providers.isEmpty()) {
- result = pickBest(providers);
- if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + result);
- return result;
+ if (providers.isEmpty()) {
+ providers = getProviders(null, enabledOnly);
}
- if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + null);
+ if (!providers.isEmpty()) {
+ if (providers.contains(GPS_PROVIDER)) {
+ return GPS_PROVIDER;
+ } else if (providers.contains(NETWORK_PROVIDER)) {
+ return NETWORK_PROVIDER;
+ } else {
+ return providers.get(0);
+ }
+ }
+
return null;
}
- private String pickBest(List<String> providers) {
- if (providers.contains(LocationManager.GPS_PROVIDER)) {
- return LocationManager.GPS_PROVIDER;
- } else if (providers.contains(LocationManager.NETWORK_PROVIDER)) {
- return LocationManager.NETWORK_PROVIDER;
- } else {
- return providers.get(0);
- }
- }
-
- @Override
- public boolean providerMeetsCriteria(String provider, Criteria criteria) {
- LocationProvider p = mProvidersByName.get(provider);
- if (p == null) {
- throw new IllegalArgumentException("provider=" + provider);
- }
-
- boolean result = android.location.LocationProvider.propertiesMeetCriteria(
- p.getName(), p.getProperties(), criteria);
- if (D) Log.d(TAG, "providerMeetsCriteria(" + provider + ", " + criteria + ")=" + result);
- return result;
- }
-
- private void updateProvidersSettingsLocked() {
- for (LocationProvider p : mProviders) {
- p.setSettingsEnabled(isAllowedByUserSettingsLockedForUser(p.getName(), mCurrentUserId));
- }
-
- mContext.sendBroadcastAsUser(new Intent(LocationManager.MODE_CHANGED_ACTION),
- UserHandle.ALL);
- }
-
- private void updateProviderListenersLocked(String provider) {
- LocationProvider p = mProvidersByName.get(provider);
- if (p == null) return;
-
- boolean enabled = p.isEnabled();
+ @GuardedBy("mLock")
+ private void updateProviderUseableLocked(LocationProvider provider) {
+ boolean useable = provider.isUseableLocked();
ArrayList<Receiver> deadReceivers = null;
- ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
+ ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
if (records != null) {
for (UpdateRecord record : records) {
- if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
+ if (isCurrentProfileLocked(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
// Sends a notification message to the receiver
- if (!record.mReceiver.callProviderEnabledLocked(provider, enabled)) {
+ if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) {
if (deadReceivers == null) {
deadReceivers = new ArrayList<>();
}
@@ -1887,25 +1998,37 @@
applyRequirementsLocked(provider);
}
- private void applyRequirementsLocked(String provider) {
- LocationProvider p = mProvidersByName.get(provider);
- if (p == null) return;
+ @GuardedBy("mLock")
+ private void applyRequirementsLocked(String providerName) {
+ LocationProvider provider = getLocationProviderLocked(providerName);
+ if (provider != null) {
+ applyRequirementsLocked(provider);
+ }
+ }
- ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
+ @GuardedBy("mLock")
+ private void applyRequirementsLocked(LocationProvider provider) {
+ ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
WorkSource worksource = new WorkSource();
ProviderRequest providerRequest = new ProviderRequest();
- ContentResolver resolver = mContext.getContentResolver();
- long backgroundThrottleInterval = Settings.Global.getLong(
- resolver,
- Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
- DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS);
+ long backgroundThrottleInterval;
- if (p.isEnabled() && records != null && !records.isEmpty()) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ backgroundThrottleInterval = Settings.Global.getLong(
+ mContext.getContentResolver(),
+ Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
+ DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+
+ if (provider.isUseableLocked() && records != null && !records.isEmpty()) {
// initialize the low power mode to true and set to false if any of the records requires
providerRequest.lowPowerMode = true;
for (UpdateRecord record : records) {
- if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
+ if (isCurrentProfileLocked(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
if (checkLocationAccess(
record.mReceiver.mIdentity.mPid,
record.mReceiver.mIdentity.mUid,
@@ -1945,7 +2068,8 @@
// under that threshold.
long thresholdInterval = (providerRequest.interval + 1000) * 3 / 2;
for (UpdateRecord record : records) {
- if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
+ if (isCurrentProfileLocked(
+ UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
LocationRequest locationRequest = record.mRequest;
// Don't assign battery blame for update records whose
@@ -1972,7 +2096,7 @@
}
if (D) Log.d(TAG, "provider request: " + provider + " " + providerRequest);
- p.setRequest(providerRequest, worksource);
+ provider.setRequestLocked(providerRequest, worksource);
}
/**
@@ -1995,34 +2119,11 @@
@Override
public String[] getBackgroundThrottlingWhitelist() {
synchronized (mLock) {
- return mBackgroundThrottlePackageWhitelist.toArray(
- new String[0]);
+ return mBackgroundThrottlePackageWhitelist.toArray(new String[0]);
}
}
- private void updateBackgroundThrottlingWhitelistLocked() {
- String setting = Settings.Global.getString(
- mContext.getContentResolver(),
- Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST);
- if (setting == null) {
- setting = "";
- }
-
- mBackgroundThrottlePackageWhitelist.clear();
- mBackgroundThrottlePackageWhitelist.addAll(
- SystemConfig.getInstance().getAllowUnthrottledLocation());
- mBackgroundThrottlePackageWhitelist.addAll(
- Arrays.asList(setting.split(",")));
- }
-
- private void updateLastLocationMaxAgeLocked() {
- mLastLocationMaxAgeMs =
- Settings.Global.getLong(
- mContext.getContentResolver(),
- Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS,
- DEFAULT_LAST_LOCATION_MAX_AGE_MS);
- }
-
+ @GuardedBy("mLock")
private boolean isThrottlingExemptLocked(Identity identity) {
if (identity.mUid == Process.SYSTEM_UID) {
return true;
@@ -2032,8 +2133,8 @@
return true;
}
- for (LocationProviderProxy provider : mProxyProviders) {
- if (identity.mPackageName.equals(provider.getConnectedPackageName())) {
+ for (LocationProvider provider : mProviders) {
+ if (identity.mPackageName.equals(provider.getPackageLocked())) {
return true;
}
}
@@ -2119,6 +2220,7 @@
}
}
+ @GuardedBy("mLock")
private Receiver getReceiverLocked(ILocationListener listener, int pid, int uid,
String packageName, WorkSource workSource, boolean hideFromAppOps) {
IBinder binder = listener.asBinder();
@@ -2137,6 +2239,7 @@
return receiver;
}
+ @GuardedBy("mLock")
private Receiver getReceiverLocked(PendingIntent intent, int pid, int uid, String packageName,
WorkSource workSource, boolean hideFromAppOps) {
Receiver receiver = mReceivers.get(intent);
@@ -2202,67 +2305,65 @@
throw new SecurityException("invalid package name: " + packageName);
}
- private void checkPendingIntent(PendingIntent intent) {
- if (intent == null) {
- throw new IllegalArgumentException("invalid pending intent: " + null);
- }
- }
-
- private Receiver checkListenerOrIntentLocked(ILocationListener listener, PendingIntent intent,
- int pid, int uid, String packageName, WorkSource workSource, boolean hideFromAppOps) {
- if (intent == null && listener == null) {
- throw new IllegalArgumentException("need either listener or intent");
- } else if (intent != null && listener != null) {
- throw new IllegalArgumentException("cannot register both listener and intent");
- } else if (intent != null) {
- checkPendingIntent(intent);
- return getReceiverLocked(intent, pid, uid, packageName, workSource, hideFromAppOps);
- } else {
- return getReceiverLocked(listener, pid, uid, packageName, workSource, hideFromAppOps);
- }
- }
-
@Override
public void requestLocationUpdates(LocationRequest request, ILocationListener listener,
PendingIntent intent, String packageName) {
- if (request == null) request = DEFAULT_LOCATION_REQUEST;
- checkPackageName(packageName);
- int allowedResolutionLevel = getCallerAllowedResolutionLevel();
- checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,
- request.getProvider());
- WorkSource workSource = request.getWorkSource();
- if (workSource != null && !workSource.isEmpty()) {
- checkDeviceStatsAllowed();
- }
- boolean hideFromAppOps = request.getHideFromAppOps();
- if (hideFromAppOps) {
- checkUpdateAppOpsAllowed();
- }
- boolean callerHasLocationHardwarePermission =
- mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
- == PERMISSION_GRANTED;
- LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel,
- callerHasLocationHardwarePermission);
-
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- // providers may use public location API's, need to clear identity
- long identity = Binder.clearCallingIdentity();
- try {
- // We don't check for MODE_IGNORED here; we will do that when we go to deliver
- // a location.
- checkLocationAccess(pid, uid, packageName, allowedResolutionLevel);
-
- synchronized (mLock) {
- Receiver recevier = checkListenerOrIntentLocked(listener, intent, pid, uid,
- packageName, workSource, hideFromAppOps);
- requestLocationUpdatesLocked(sanitizedRequest, recevier, uid, packageName);
+ synchronized (mLock) {
+ if (request == null) request = DEFAULT_LOCATION_REQUEST;
+ checkPackageName(packageName);
+ int allowedResolutionLevel = getCallerAllowedResolutionLevel();
+ checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel,
+ request.getProvider());
+ WorkSource workSource = request.getWorkSource();
+ if (workSource != null && !workSource.isEmpty()) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.UPDATE_DEVICE_STATS, null);
}
- } finally {
- Binder.restoreCallingIdentity(identity);
+ boolean hideFromAppOps = request.getHideFromAppOps();
+ if (hideFromAppOps) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.UPDATE_APP_OPS_STATS, null);
+ }
+ boolean callerHasLocationHardwarePermission =
+ mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ == PERMISSION_GRANTED;
+ LocationRequest sanitizedRequest = createSanitizedRequest(request,
+ allowedResolutionLevel,
+ callerHasLocationHardwarePermission);
+
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+
+ long identity = Binder.clearCallingIdentity();
+ try {
+
+ // We don't check for MODE_IGNORED here; we will do that when we go to deliver
+ // a location.
+ checkLocationAccess(pid, uid, packageName, allowedResolutionLevel);
+
+ if (intent == null && listener == null) {
+ throw new IllegalArgumentException("need either listener or intent");
+ } else if (intent != null && listener != null) {
+ throw new IllegalArgumentException(
+ "cannot register both listener and intent");
+ }
+
+ Receiver receiver;
+ if (intent != null) {
+ receiver = getReceiverLocked(intent, pid, uid, packageName, workSource,
+ hideFromAppOps);
+ } else {
+ receiver = getReceiverLocked(listener, pid, uid, packageName, workSource,
+ hideFromAppOps);
+ }
+ requestLocationUpdatesLocked(sanitizedRequest, receiver, uid, packageName);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
+ @GuardedBy("mLock")
private void requestLocationUpdatesLocked(LocationRequest request, Receiver receiver,
int uid, String packageName) {
// Figure out the provider. Either its explicitly request (legacy use cases), or
@@ -2273,7 +2374,7 @@
throw new IllegalArgumentException("provider name must not be null");
}
- LocationProvider provider = mProvidersByName.get(name);
+ LocationProvider provider = getLocationProviderLocked(name);
if (provider == null) {
throw new IllegalArgumentException("provider doesn't exist: " + name);
}
@@ -2292,7 +2393,7 @@
oldRecord.disposeLocked(false);
}
- if (provider.isEnabled()) {
+ if (provider.isUseableLocked()) {
applyRequirementsLocked(name);
} else {
// Notify the listener that updates are currently disabled
@@ -2308,14 +2409,23 @@
String packageName) {
checkPackageName(packageName);
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
+ int pid = Binder.getCallingPid();
+ int uid = Binder.getCallingUid();
+
+ if (intent == null && listener == null) {
+ throw new IllegalArgumentException("need either listener or intent");
+ } else if (intent != null && listener != null) {
+ throw new IllegalArgumentException("cannot register both listener and intent");
+ }
synchronized (mLock) {
- Receiver receiver = checkListenerOrIntentLocked(listener, intent, pid, uid,
- packageName, null, false);
+ Receiver receiver;
+ if (intent != null) {
+ receiver = getReceiverLocked(intent, pid, uid, packageName, null, false);
+ } else {
+ receiver = getReceiverLocked(listener, pid, uid, packageName, null, false);
+ }
- // providers may use public location API's, need to clear identity
long identity = Binder.clearCallingIdentity();
try {
removeUpdatesLocked(receiver);
@@ -2325,6 +2435,7 @@
}
}
+ @GuardedBy("mLock")
private void removeUpdatesLocked(Receiver receiver) {
if (D) Log.i(TAG, "remove " + Integer.toHexString(System.identityHashCode(receiver)));
@@ -2356,51 +2467,53 @@
}
}
- private void applyAllProviderRequirementsLocked() {
- for (LocationProvider p : mProviders) {
- applyRequirementsLocked(p.getName());
- }
- }
-
@Override
- public Location getLastLocation(LocationRequest request, String packageName) {
- if (D) Log.d(TAG, "getLastLocation: " + request);
- if (request == null) request = DEFAULT_LOCATION_REQUEST;
- int allowedResolutionLevel = getCallerAllowedResolutionLevel();
- checkPackageName(packageName);
- checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,
- request.getProvider());
- // no need to sanitize this request, as only the provider name is used
+ public Location getLastLocation(LocationRequest r, String packageName) {
+ if (D) Log.d(TAG, "getLastLocation: " + r);
+ synchronized (mLock) {
+ LocationRequest request = r != null ? r : DEFAULT_LOCATION_REQUEST;
+ int allowedResolutionLevel = getCallerAllowedResolutionLevel();
+ checkPackageName(packageName);
+ checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel,
+ request.getProvider());
+ // no need to sanitize this request, as only the provider name is used
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final long identity = Binder.clearCallingIdentity();
- try {
- if (mBlacklist.isBlacklisted(packageName)) {
- if (D) {
- Log.d(TAG, "not returning last loc for blacklisted app: " +
- packageName);
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (mBlacklist.isBlacklisted(packageName)) {
+ if (D) {
+ Log.d(TAG, "not returning last loc for blacklisted app: "
+ + packageName);
+ }
+ return null;
}
- return null;
- }
- if (!reportLocationAccessNoThrow(pid, uid, packageName, allowedResolutionLevel)) {
- if (D) {
- Log.d(TAG, "not returning last loc for no op app: " +
- packageName);
+ if (!reportLocationAccessNoThrow(pid, uid, packageName, allowedResolutionLevel)) {
+ if (D) {
+ Log.d(TAG, "not returning last loc for no op app: "
+ + packageName);
+ }
+ return null;
}
- return null;
- }
- synchronized (mLock) {
// Figure out the provider. Either its explicitly request (deprecated API's),
// or use the fused provider
String name = request.getProvider();
if (name == null) name = LocationManager.FUSED_PROVIDER;
- LocationProvider provider = mProvidersByName.get(name);
+ LocationProvider provider = getLocationProviderLocked(name);
if (provider == null) return null;
- if (!isAllowedByUserSettingsLocked(name, uid, mCurrentUserId)) return null;
+ // only the current user or location providers may get location this way
+ if (!isCurrentProfileLocked(UserHandle.getUserId(uid)) && !isLocationProviderLocked(
+ uid)) {
+ return null;
+ }
+
+ if (!provider.isUseableLocked()) {
+ return null;
+ }
Location location;
if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
@@ -2416,9 +2529,12 @@
// Don't return stale location to apps with foreground-only location permission.
String op = resolutionLevelToOpStr(allowedResolutionLevel);
- long locationAgeMs = SystemClock.elapsedRealtime() -
- location.getElapsedRealtimeNanos() / NANOS_PER_MILLI;
- if ((locationAgeMs > mLastLocationMaxAgeMs)
+ long locationAgeMs = SystemClock.elapsedRealtime()
+ - location.getElapsedRealtimeNanos() / NANOS_PER_MILLI;
+ if ((locationAgeMs > Settings.Global.getLong(
+ mContext.getContentResolver(),
+ Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS,
+ DEFAULT_LAST_LOCATION_MAX_AGE_MS))
&& (mAppOps.unsafeCheckOp(op, uid, packageName)
== AppOpsManager.MODE_FOREGROUND)) {
return null;
@@ -2433,24 +2549,13 @@
} else {
return new Location(location);
}
+ return null;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
- return null;
- } finally {
- Binder.restoreCallingIdentity(identity);
}
}
- /**
- * Provides an interface to inject and set the last location if location is not available
- * currently.
- *
- * This helps in cases where the product (Cars for example) has saved the last known location
- * before powering off. This interface lets the client inject the saved location while the GPS
- * chipset is getting its first fix, there by improving user experience.
- *
- * @param location - Location object to inject
- * @return true if update was successful, false if not
- */
@Override
public boolean injectLocation(Location location) {
mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
@@ -2464,38 +2569,23 @@
}
return false;
}
- LocationProvider p = null;
- String provider = location.getProvider();
- if (provider != null) {
- p = mProvidersByName.get(provider);
- }
- if (p == null) {
- if (D) {
- Log.d(TAG, "injectLocation(): unknown provider");
- }
- return false;
- }
+
synchronized (mLock) {
- if (!isAllowedByUserSettingsLockedForUser(provider, mCurrentUserId)) {
- if (D) {
- Log.d(TAG, "Location disabled in Settings for current user:" + mCurrentUserId);
- }
+ LocationProvider provider = getLocationProviderLocked(location.getProvider());
+ if (provider == null || !provider.isUseableLocked()) {
return false;
- } else {
- // NOTE: If last location is already available, location is not injected. If
- // provider's normal source (like a GPS chipset) have already provided an output,
- // there is no need to inject this location.
- if (mLastLocation.get(provider) == null) {
- updateLastLocationLocked(location, provider);
- } else {
- if (D) {
- Log.d(TAG, "injectLocation(): Location exists. Not updating");
- }
- return false;
- }
}
+
+ // NOTE: If last location is already available, location is not injected. If
+ // provider's normal source (like a GPS chipset) have already provided an output
+ // there is no need to inject this location.
+ if (mLastLocation.get(provider.getName()) != null) {
+ return false;
+ }
+
+ updateLastLocationLocked(location, provider.getName());
+ return true;
}
- return true;
}
@Override
@@ -2504,39 +2594,49 @@
if (request == null) request = DEFAULT_LOCATION_REQUEST;
int allowedResolutionLevel = getCallerAllowedResolutionLevel();
checkResolutionLevelIsSufficientForGeofenceUse(allowedResolutionLevel);
- checkPendingIntent(intent);
- checkPackageName(packageName);
- checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,
- request.getProvider());
- // Require that caller can manage given document
- boolean callerHasLocationHardwarePermission =
- mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
- == PERMISSION_GRANTED;
- LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel,
- callerHasLocationHardwarePermission);
-
- if (D) Log.d(TAG, "requestGeofence: " + sanitizedRequest + " " + geofence + " " + intent);
-
- // geo-fence manager uses the public location API, need to clear identity
- int uid = Binder.getCallingUid();
- // TODO: http://b/23822629
- if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM) {
- // temporary measure until geofences work for secondary users
- Log.w(TAG, "proximity alerts are currently available only to the primary user");
- return;
+ if (intent == null) {
+ throw new IllegalArgumentException("invalid pending intent: " + null);
}
- long identity = Binder.clearCallingIdentity();
- try {
- mGeofenceManager.addFence(sanitizedRequest, geofence, intent, allowedResolutionLevel,
- uid, packageName);
- } finally {
- Binder.restoreCallingIdentity(identity);
+ checkPackageName(packageName);
+ synchronized (mLock) {
+ checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel,
+ request.getProvider());
+ // Require that caller can manage given document
+ boolean callerHasLocationHardwarePermission =
+ mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ == PERMISSION_GRANTED;
+ LocationRequest sanitizedRequest = createSanitizedRequest(request,
+ allowedResolutionLevel,
+ callerHasLocationHardwarePermission);
+
+ if (D) {
+ Log.d(TAG, "requestGeofence: " + sanitizedRequest + " " + geofence + " " + intent);
+ }
+
+ // geo-fence manager uses the public location API, need to clear identity
+ int uid = Binder.getCallingUid();
+ // TODO: http://b/23822629
+ if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM) {
+ // temporary measure until geofences work for secondary users
+ Log.w(TAG, "proximity alerts are currently available only to the primary user");
+ return;
+ }
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mGeofenceManager.addFence(sanitizedRequest, geofence, intent,
+ allowedResolutionLevel,
+ uid, packageName);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
@Override
public void removeGeofence(Geofence geofence, PendingIntent intent, String packageName) {
- checkPendingIntent(intent);
+ if (intent == null) {
+ throw new IllegalArgumentException("invalid pending intent: " + null);
+ }
checkPackageName(packageName);
if (D) Log.d(TAG, "removeGeofence: " + geofence + " " + intent);
@@ -2641,6 +2741,7 @@
synchronized (mLock) {
Identity callerIdentity
= new Identity(Binder.getCallingUid(), Binder.getCallingPid(), packageName);
+
// TODO(b/120481270): Register for client death notification and update map.
mGnssNavigationMessageListeners.put(listener.asBinder(), callerIdentity);
long identity = Binder.clearCallingIdentity();
@@ -2670,25 +2771,26 @@
}
@Override
- public boolean sendExtraCommand(String provider, String command, Bundle extras) {
- if (provider == null) {
+ public boolean sendExtraCommand(String providerName, String command, Bundle extras) {
+ if (providerName == null) {
// throw NullPointerException to remain compatible with previous implementation
throw new NullPointerException();
}
- checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(),
- provider);
-
- // and check for ACCESS_LOCATION_EXTRA_COMMANDS
- if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS)
- != PERMISSION_GRANTED)) {
- throw new SecurityException("Requires ACCESS_LOCATION_EXTRA_COMMANDS permission");
- }
-
synchronized (mLock) {
- LocationProvider p = mProvidersByName.get(provider);
- if (p == null) return false;
+ checkResolutionLevelIsSufficientForProviderUseLocked(getCallerAllowedResolutionLevel(),
+ providerName);
- p.sendExtraCommand(command, extras);
+ // and check for ACCESS_LOCATION_EXTRA_COMMANDS
+ if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS)
+ != PERMISSION_GRANTED)) {
+ throw new SecurityException("Requires ACCESS_LOCATION_EXTRA_COMMANDS permission");
+ }
+
+ LocationProvider provider = getLocationProviderLocked(providerName);
+ if (provider != null) {
+ provider.sendExtraCommandLocked(command, extras);
+ }
+
return true;
}
}
@@ -2707,44 +2809,29 @@
}
}
- /**
- * @return null if the provider does not exist
- * @throws SecurityException if the provider is not allowed to be
- * accessed by the caller
- */
@Override
- public ProviderProperties getProviderProperties(String provider) {
- checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(),
- provider);
-
- LocationProvider p;
+ public ProviderProperties getProviderProperties(String providerName) {
synchronized (mLock) {
- p = mProvidersByName.get(provider);
- }
+ checkResolutionLevelIsSufficientForProviderUseLocked(getCallerAllowedResolutionLevel(),
+ providerName);
- if (p == null) return null;
- return p.getProperties();
+ LocationProvider provider = getLocationProviderLocked(providerName);
+ if (provider == null) {
+ return null;
+ }
+ return provider.getPropertiesLocked();
+ }
}
- /**
- * @return null if the provider does not exist
- * @throws SecurityException if the provider is not allowed to be
- * accessed by the caller
- */
@Override
public String getNetworkProviderPackage() {
- LocationProvider p;
synchronized (mLock) {
- p = mProvidersByName.get(LocationManager.NETWORK_PROVIDER);
+ LocationProvider provider = getLocationProviderLocked(NETWORK_PROVIDER);
+ if (provider == null) {
+ return null;
+ }
+ return provider.getPackageLocked();
}
-
- if (p == null) {
- return null;
- }
- if (p.mProvider instanceof LocationProviderProxy) {
- return ((LocationProviderProxy) p.mProvider).getConnectedPackageName();
- }
- return null;
}
@Override
@@ -2780,241 +2867,96 @@
}
}
- /**
- * Returns the current location enabled/disabled status for a user
- *
- * @param userId the id of the user
- * @return true if location is enabled
- */
+ private boolean isLocationEnabled() {
+ return isLocationEnabledForUser(mCurrentUserId);
+ }
+
@Override
public boolean isLocationEnabledForUser(int userId) {
// Check INTERACT_ACROSS_USERS permission if userId is not current user id.
- checkInteractAcrossUsersPermission(userId);
+ if (UserHandle.getCallingUserId() != userId) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS,
+ "Requires INTERACT_ACROSS_USERS permission");
+ }
long identity = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- final String allowedProviders = Settings.Secure.getStringForUser(
+ boolean enabled;
+ try {
+ enabled = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCATION_MODE,
+ userId) != Settings.Secure.LOCATION_MODE_OFF;
+ } catch (Settings.SettingNotFoundException e) {
+ // OS upgrade case where mode isn't set yet
+ enabled = !TextUtils.isEmpty(Settings.Secure.getStringForUser(
mContext.getContentResolver(),
Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
- userId);
- if (allowedProviders == null) {
- return false;
+ userId));
+
+ try {
+ Settings.Secure.putIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCATION_MODE,
+ enabled
+ ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY
+ : Settings.Secure.LOCATION_MODE_OFF,
+ userId);
+ } catch (RuntimeException ex) {
+ // any problem with writing should not be propagated
+ Slog.e(TAG, "error updating location mode", ex);
}
- final List<String> providerList = Arrays.asList(allowedProviders.split(","));
- for (String provider : mRealProviders.keySet()) {
- if (provider.equals(LocationManager.PASSIVE_PROVIDER)
- || provider.equals(LocationManager.FUSED_PROVIDER)) {
- continue;
- }
- if (providerList.contains(provider)) {
- return true;
- }
- }
- return false;
}
+ return enabled;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
- /**
- * Enable or disable location for a user
- *
- * @param enabled true to enable location, false to disable location
- * @param userId the id of the user
- */
- @Override
- public void setLocationEnabledForUser(boolean enabled, int userId) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.WRITE_SECURE_SETTINGS,
- "Requires WRITE_SECURE_SETTINGS permission");
-
- // Check INTERACT_ACROSS_USERS permission if userId is not current user id.
- checkInteractAcrossUsersPermission(userId);
-
- long identity = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
- final Set<String> allRealProviders = mRealProviders.keySet();
- // Update all providers on device plus gps and network provider when disabling
- // location
- Set<String> allProvidersSet = new ArraySet<>(allRealProviders.size() + 2);
- allProvidersSet.addAll(allRealProviders);
- // When disabling location, disable gps and network provider that could have been
- // enabled by location mode api.
- if (!enabled) {
- allProvidersSet.add(LocationManager.GPS_PROVIDER);
- allProvidersSet.add(LocationManager.NETWORK_PROVIDER);
- }
- if (allProvidersSet.isEmpty()) {
- return;
- }
- // to ensure thread safety, we write the provider name with a '+' or '-'
- // and let the SettingsProvider handle it rather than reading and modifying
- // the list of enabled providers.
- final String prefix = enabled ? "+" : "-";
- StringBuilder locationProvidersAllowed = new StringBuilder();
- for (String provider : allProvidersSet) {
- if (provider.equals(LocationManager.PASSIVE_PROVIDER)
- || provider.equals(LocationManager.FUSED_PROVIDER)) {
- continue;
- }
- locationProvidersAllowed.append(prefix);
- locationProvidersAllowed.append(provider);
- locationProvidersAllowed.append(",");
- }
- // Remove the trailing comma
- locationProvidersAllowed.setLength(locationProvidersAllowed.length() - 1);
- Settings.Secure.putStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
- locationProvidersAllowed.toString(),
- userId);
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- /**
- * Returns the current enabled/disabled status of a location provider and user
- *
- * @param providerName name of the provider
- * @param userId the id of the user
- * @return true if the provider exists and is enabled
- */
@Override
public boolean isProviderEnabledForUser(String providerName, int userId) {
// Check INTERACT_ACROSS_USERS permission if userId is not current user id.
- checkInteractAcrossUsersPermission(userId);
-
- if (!isLocationEnabledForUser(userId)) {
- return false;
+ if (UserHandle.getCallingUserId() != userId) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS,
+ "Requires INTERACT_ACROSS_USERS permission");
}
// Fused provider is accessed indirectly via criteria rather than the provider-based APIs,
// so we discourage its use
- if (LocationManager.FUSED_PROVIDER.equals(providerName)) return false;
+ if (FUSED_PROVIDER.equals(providerName)) return false;
- long identity = Binder.clearCallingIdentity();
- try {
- LocationProvider provider;
- synchronized (mLock) {
- provider = mProvidersByName.get(providerName);
- }
- return provider != null && provider.isEnabled();
- } finally {
- Binder.restoreCallingIdentity(identity);
+ synchronized (mLock) {
+ LocationProvider provider = getLocationProviderLocked(providerName);
+ return provider != null && provider.isUseableForUserLocked(userId);
}
}
- /**
- * Enable or disable a single location provider.
- *
- * @param provider name of the provider
- * @param enabled true to enable the provider. False to disable the provider
- * @param userId the id of the user to set
- * @return true if the value was set, false on errors
- */
- @Override
- public boolean setProviderEnabledForUser(String provider, boolean enabled, int userId) {
- return false;
- }
-
- /**
- * Method for checking INTERACT_ACROSS_USERS permission if specified user id is not the same as
- * current user id
- *
- * @param userId the user id to get or set value
- */
- private void checkInteractAcrossUsersPermission(int userId) {
- int uid = Binder.getCallingUid();
- if (UserHandle.getUserId(uid) != userId) {
- if (ActivityManager.checkComponentPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS, uid, -1, true)
- != PERMISSION_GRANTED) {
- throw new SecurityException("Requires INTERACT_ACROSS_USERS permission");
- }
- }
- }
-
- /**
- * Returns "true" if the UID belongs to a bound location provider.
- *
- * @param uid the uid
- * @return true if uid belongs to a bound location provider
- */
- private boolean isUidALocationProvider(int uid) {
+ @GuardedBy("mLock")
+ private boolean isLocationProviderLocked(int uid) {
if (uid == Process.SYSTEM_UID) {
return true;
}
- if (mGeocodeProvider != null) {
- if (doesUidHavePackage(uid, mGeocodeProvider.getConnectedPackageName())) return true;
- }
- for (LocationProviderProxy proxy : mProxyProviders) {
- if (doesUidHavePackage(uid, proxy.getConnectedPackageName())) return true;
- }
- return false;
- }
- private void checkCallerIsProvider() {
- if (mContext.checkCallingOrSelfPermission(INSTALL_LOCATION_PROVIDER)
- == PERMISSION_GRANTED) {
- return;
- }
-
- // Previously we only used the INSTALL_LOCATION_PROVIDER
- // check. But that is system or signature
- // protection level which is not flexible enough for
- // providers installed oustide the system image. So
- // also allow providers with a UID matching the
- // currently bound package name
-
- if (isUidALocationProvider(Binder.getCallingUid())) {
- return;
- }
-
- throw new SecurityException("need INSTALL_LOCATION_PROVIDER permission, " +
- "or UID of a currently bound location provider");
- }
-
- /**
- * Returns true if the given package belongs to the given uid.
- */
- private boolean doesUidHavePackage(int uid, String packageName) {
- if (packageName == null) {
- return false;
- }
String[] packageNames = mPackageManager.getPackagesForUid(uid);
if (packageNames == null) {
return false;
}
- for (String name : packageNames) {
- if (packageName.equals(name)) {
+ for (LocationProvider provider : mProviders) {
+ String packageName = provider.getPackageLocked();
+ if (packageName == null) {
+ continue;
+ }
+ if (ArrayUtils.contains(packageNames, packageName)) {
return true;
}
}
return false;
}
- @Override
- public void reportLocation(Location location, boolean passive) {
- checkCallerIsProvider();
-
- if (!location.isComplete()) {
- Log.w(TAG, "Dropping incomplete location: " + location);
- return;
- }
-
- mLocationHandler.removeMessages(MSG_LOCATION_CHANGED, location);
- Message m = Message.obtain(mLocationHandler, MSG_LOCATION_CHANGED, location);
- m.arg1 = (passive ? 1 : 0);
- mLocationHandler.sendMessageAtFrontOfQueue(m);
- }
-
-
- private static boolean shouldBroadcastSafe(
+ @GuardedBy("mLock")
+ private static boolean shouldBroadcastSafeLocked(
Location loc, Location lastLoc, UpdateRecord record, long now) {
// Always broadcast the first update
if (lastLoc == null) {
@@ -3046,26 +2988,36 @@
return record.mRealRequest.getExpireAt() >= now;
}
- private void handleLocationChangedLocked(Location location, boolean passive) {
+ @GuardedBy("mLock")
+ private void handleLocationChangedLocked(Location location, LocationProvider provider) {
+ if (!mProviders.contains(provider)) {
+ return;
+ }
+ if (!location.isComplete()) {
+ Log.w(TAG, "Dropping incomplete location: " + location);
+ return;
+ }
+
+ if (!provider.isPassiveLocked()) {
+ // notify passive provider of the new location
+ mPassiveProvider.updateLocation(location);
+ }
+
if (D) Log.d(TAG, "incoming location: " + location);
long now = SystemClock.elapsedRealtime();
- String provider = (passive ? LocationManager.PASSIVE_PROVIDER : location.getProvider());
- // Skip if the provider is unknown.
- LocationProvider p = mProvidersByName.get(provider);
- if (p == null) return;
- updateLastLocationLocked(location, provider);
+ updateLastLocationLocked(location, provider.getName());
// mLastLocation should have been updated from the updateLastLocationLocked call above.
- Location lastLocation = mLastLocation.get(provider);
+ Location lastLocation = mLastLocation.get(provider.getName());
if (lastLocation == null) {
Log.e(TAG, "handleLocationChangedLocked() updateLastLocation failed");
return;
}
// Update last known coarse interval location if enough time has passed.
- Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(provider);
+ Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(provider.getName());
if (lastLocationCoarseInterval == null) {
lastLocationCoarseInterval = new Location(location);
- mLastLocationCoarseInterval.put(provider, lastLocationCoarseInterval);
+ mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval);
}
long timeDiffNanos = location.getElapsedRealtimeNanos()
- lastLocationCoarseInterval.getElapsedRealtimeNanos();
@@ -3079,7 +3031,7 @@
lastLocationCoarseInterval.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
// Skip if there are no UpdateRecords for this provider.
- ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
+ ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
if (records == null || records.size() == 0) return;
// Fetch coarse location
@@ -3088,13 +3040,6 @@
coarseLocation = mLocationFudger.getOrCreate(noGPSLocation);
}
- // Fetch latest status update time
- long newStatusUpdateTime = p.getStatusUpdateTime();
-
- // Get latest status
- Bundle extras = new Bundle();
- int status = p.getStatus(extras);
-
ArrayList<Receiver> deadReceivers = null;
ArrayList<UpdateRecord> deadUpdateRecords = null;
@@ -3104,8 +3049,8 @@
boolean receiverDead = false;
int receiverUserId = UserHandle.getUserId(receiver.mIdentity.mUid);
- if (!isCurrentProfile(receiverUserId)
- && !isUidALocationProvider(receiver.mIdentity.mUid)) {
+ if (!isCurrentProfileLocked(receiverUserId)
+ && !isLocationProviderLocked(receiver.mIdentity.mUid)) {
if (D) {
Log.d(TAG, "skipping loc update for background user " + receiverUserId +
" (current user: " + mCurrentUserId + ", app: " +
@@ -3142,7 +3087,8 @@
}
if (notifyLocation != null) {
Location lastLoc = r.mLastFixBroadcast;
- if ((lastLoc == null) || shouldBroadcastSafe(notifyLocation, lastLoc, r, now)) {
+ if ((lastLoc == null)
+ || shouldBroadcastSafeLocked(notifyLocation, lastLoc, r, now)) {
if (lastLoc == null) {
lastLoc = new Location(notifyLocation);
r.mLastFixBroadcast = lastLoc;
@@ -3150,7 +3096,8 @@
lastLoc.set(notifyLocation);
}
if (!receiver.callLocationChangedLocked(notifyLocation)) {
- Slog.w(TAG, "RemoteException calling onLocationChanged on " + receiver);
+ Slog.w(TAG, "RemoteException calling onLocationChanged on "
+ + receiver);
receiverDead = true;
}
r.mRealRequest.decrementNumUpdates();
@@ -3161,12 +3108,16 @@
// guarded behind this setting now. should be removed completely post-Q
if (Settings.Global.getInt(mContext.getContentResolver(),
LOCATION_DISABLE_STATUS_CALLBACKS, 1) == 0) {
+ long newStatusUpdateTime = provider.getStatusUpdateTimeLocked();
+ Bundle extras = new Bundle();
+ int status = provider.getStatusLocked(extras);
+
long prevStatusUpdateTime = r.mLastStatusBroadcast;
if ((newStatusUpdateTime > prevStatusUpdateTime)
&& (prevStatusUpdateTime != 0 || status != AVAILABLE)) {
r.mLastStatusBroadcast = newStatusUpdateTime;
- if (!receiver.callStatusChangedLocked(provider, status, extras)) {
+ if (!receiver.callStatusChangedLocked(provider.getName(), status, extras)) {
receiverDead = true;
Slog.w(TAG, "RemoteException calling onStatusChanged on " + receiver);
}
@@ -3205,12 +3156,7 @@
}
}
- /**
- * Updates last location with the given location
- *
- * @param location new location to update
- * @param provider Location provider to update for
- */
+ @GuardedBy("mLock")
private void updateLastLocationLocked(Location location, String provider) {
Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
Location lastNoGPSLocation;
@@ -3229,75 +3175,6 @@
lastLocation.set(location);
}
- private class LocationWorkerHandler extends Handler {
- public LocationWorkerHandler(Looper looper) {
- super(looper, null, true);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_LOCATION_CHANGED:
- handleLocationChanged((Location) msg.obj, msg.arg1 == 1);
- break;
- }
- }
- }
-
- private boolean isMockProvider(String provider) {
- synchronized (mLock) {
- return mMockProviders.containsKey(provider);
- }
- }
-
- private void handleLocationChanged(Location location, boolean passive) {
- // create a working copy of the incoming Location so that the service can modify it without
- // disturbing the caller's copy
- Location myLocation = new Location(location);
- String provider = myLocation.getProvider();
-
- // set "isFromMockProvider" bit if location came from a mock provider. we do not clear this
- // bit if location did not come from a mock provider because passive/fused providers can
- // forward locations from mock providers, and should not grant them legitimacy in doing so.
- if (!myLocation.isFromMockProvider() && isMockProvider(provider)) {
- myLocation.setIsFromMockProvider(true);
- }
-
- synchronized (mLock) {
- if (!passive) {
- // notify passive provider of the new location
- mPassiveProvider.updateLocation(myLocation);
- }
- handleLocationChangedLocked(myLocation, passive);
- }
- }
-
- private final PackageMonitor mPackageMonitor = new PackageMonitor() {
- @Override
- public void onPackageDisappeared(String packageName, int reason) {
- // remove all receivers associated with this package name
- synchronized (mLock) {
- ArrayList<Receiver> deadReceivers = null;
-
- for (Receiver receiver : mReceivers.values()) {
- if (receiver.mIdentity.mPackageName.equals(packageName)) {
- if (deadReceivers == null) {
- deadReceivers = new ArrayList<>();
- }
- deadReceivers.add(receiver);
- }
- }
-
- // perform removal outside of mReceivers loop
- if (deadReceivers != null) {
- for (Receiver receiver : deadReceivers) {
- removeUpdatesLocked(receiver);
- }
- }
- }
- }
- };
-
// Geocoder
@Override
@@ -3343,63 +3220,60 @@
return;
}
- if (LocationManager.PASSIVE_PROVIDER.equals(name)) {
+ if (PASSIVE_PROVIDER.equals(name)) {
throw new IllegalArgumentException("Cannot mock the passive location provider");
}
- long identity = Binder.clearCallingIdentity();
synchronized (mLock) {
- // remove the real provider if we are replacing GPS or network provider
- if (LocationManager.GPS_PROVIDER.equals(name)
- || LocationManager.NETWORK_PROVIDER.equals(name)
- || LocationManager.FUSED_PROVIDER.equals(name)) {
- LocationProvider p = mProvidersByName.get(name);
- if (p != null) {
- removeProviderLocked(p);
+ long identity = Binder.clearCallingIdentity();
+ try {
+ LocationProvider oldProvider = getLocationProviderLocked(name);
+ if (oldProvider != null) {
+ if (oldProvider.isMock()) {
+ throw new IllegalArgumentException(
+ "Provider \"" + name + "\" already exists");
+ }
+
+ removeProviderLocked(oldProvider);
}
+
+ MockLocationProvider mockProviderManager = new MockLocationProvider(name);
+ addProviderLocked(mockProviderManager);
+ mockProviderManager.attachLocked(new MockProvider(mockProviderManager, properties));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
- addTestProviderLocked(name, properties);
}
- Binder.restoreCallingIdentity(identity);
- }
-
- private void addTestProviderLocked(String name, ProviderProperties properties) {
- if (mProvidersByName.get(name) != null) {
- throw new IllegalArgumentException("Provider \"" + name + "\" already exists");
- }
-
- LocationProvider provider = new LocationProvider(name);
- MockProvider mockProvider = new MockProvider(provider, properties);
-
- addProviderLocked(provider);
- mMockProviders.put(name, mockProvider);
- mLastLocation.put(name, null);
- mLastLocationCoarseInterval.put(name, null);
}
@Override
- public void removeTestProvider(String provider, String opPackageName) {
+ public void removeTestProvider(String name, String opPackageName) {
if (!canCallerAccessMockLocation(opPackageName)) {
return;
}
synchronized (mLock) {
- MockProvider mockProvider = mMockProviders.remove(provider);
- if (mockProvider == null) {
- throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
- }
-
long identity = Binder.clearCallingIdentity();
try {
- removeProviderLocked(mProvidersByName.get(provider));
+ LocationProvider testProvider = getLocationProviderLocked(name);
+ if (testProvider == null || !testProvider.isMock()) {
+ throw new IllegalArgumentException("Provider \"" + name + "\" unknown");
+ }
+
+ removeProviderLocked(testProvider);
// reinstate real provider if available
- LocationProvider realProvider = mRealProviders.get(provider);
+ LocationProvider realProvider = null;
+ for (LocationProvider provider : mRealProviders) {
+ if (name.equals(provider.getName())) {
+ realProvider = provider;
+ break;
+ }
+ }
+
if (realProvider != null) {
addProviderLocked(realProvider);
}
- mLastLocation.put(provider, null);
- mLastLocationCoarseInterval.put(provider, null);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -3407,78 +3281,60 @@
}
@Override
- public void setTestProviderLocation(String provider, Location loc, String opPackageName) {
- if (!canCallerAccessMockLocation(opPackageName)) {
- return;
- }
-
- synchronized (mLock) {
- MockProvider mockProvider = mMockProviders.get(provider);
- if (mockProvider == null) {
- throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
- }
-
- // Ensure that the location is marked as being mock. There's some logic to do this in
- // handleLocationChanged(), but it fails if loc has the wrong provider (bug 33091107).
- Location mock = new Location(loc);
- mock.setIsFromMockProvider(true);
-
- if (!TextUtils.isEmpty(loc.getProvider()) && !provider.equals(loc.getProvider())) {
- // The location has an explicit provider that is different from the mock provider
- // name. The caller may be trying to fool us via bug 33091107.
- EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(),
- provider + "!=" + loc.getProvider());
- }
-
- // clear calling identity so INSTALL_LOCATION_PROVIDER permission is not required
- long identity = Binder.clearCallingIdentity();
- try {
- mockProvider.setLocation(mock);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- }
-
- @Override
- public void setTestProviderEnabled(String provider, boolean enabled, String opPackageName) {
- if (!canCallerAccessMockLocation(opPackageName)) {
- return;
- }
-
- synchronized (mLock) {
- MockProvider mockProvider = mMockProviders.get(provider);
- if (mockProvider == null) {
- throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
- }
- long identity = Binder.clearCallingIdentity();
- try {
- mockProvider.setEnabled(enabled);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- }
-
- @Override
- public void setTestProviderStatus(String provider, int status, Bundle extras, long updateTime,
+ public void setTestProviderLocation(String providerName, Location location,
String opPackageName) {
if (!canCallerAccessMockLocation(opPackageName)) {
return;
}
synchronized (mLock) {
- MockProvider mockProvider = mMockProviders.get(provider);
- if (mockProvider == null) {
- throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
+ LocationProvider testProvider = getLocationProviderLocked(providerName);
+ if (testProvider == null || !testProvider.isMock()) {
+ throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
}
- mockProvider.setStatus(status, extras, updateTime);
+
+ String locationProvider = location.getProvider();
+ if (!TextUtils.isEmpty(locationProvider) && !providerName.equals(locationProvider)) {
+ // The location has an explicit provider that is different from the mock
+ // provider name. The caller may be trying to fool us via b/33091107.
+ EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(),
+ providerName + "!=" + location.getProvider());
+ }
+
+ ((MockLocationProvider) testProvider).setLocationLocked(location);
}
}
- private void log(String log) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Slog.d(TAG, log);
+ @Override
+ public void setTestProviderEnabled(String providerName, boolean enabled, String opPackageName) {
+ if (!canCallerAccessMockLocation(opPackageName)) {
+ return;
+ }
+
+ synchronized (mLock) {
+ LocationProvider testProvider = getLocationProviderLocked(providerName);
+ if (testProvider == null || !testProvider.isMock()) {
+ throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
+ }
+
+ ((MockLocationProvider) testProvider).setEnabledLocked(enabled);
+ }
+ }
+
+ @Override
+ public void setTestProviderStatus(String providerName, int status, Bundle extras,
+ long updateTime, String opPackageName) {
+ if (!canCallerAccessMockLocation(opPackageName)) {
+ return;
+ }
+
+ synchronized (mLock) {
+ LocationProvider testProvider = getLocationProviderLocked(providerName);
+ if (testProvider == null || !testProvider.isMock()) {
+ throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
+ }
+
+ ((MockLocationProvider) testProvider).setStatusLocked(status, extras, updateTime);
}
}
@@ -3494,6 +3350,7 @@
return;
}
pw.println("Current Location Manager state:");
+ pw.println(" Location Mode: " + isLocationEnabled());
pw.println(" Location Listeners:");
for (Receiver receiver : mReceivers.values()) {
pw.println(" " + receiver);
@@ -3548,12 +3405,6 @@
pw.append(" ");
mBlacklist.dump(pw);
- if (mMockProviders.size() > 0) {
- pw.println(" Mock Providers:");
- for (Map.Entry<String, MockProvider> i : mMockProviders.entrySet()) {
- i.getValue().dump(fd, pw, args);
- }
- }
if (mLocationControllerExtraPackage != null) {
pw.println(" Location controller extra package: " + mLocationControllerExtraPackage
@@ -3574,7 +3425,7 @@
return;
}
for (LocationProvider provider : mProviders) {
- provider.dump(fd, pw, args);
+ provider.dumpLocked(fd, pw, args);
}
if (mGnssBatchingInProgress) {
pw.println(" GNSS batching in progress");
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 5d4c5c3..faca750 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -534,12 +534,6 @@
return;
}
verifyIncomingUid(uid);
- if (mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
- > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
- Slog.e(TAG, "Ignoring incoming vibration as process with uid = "
- + uid + " is background");
- return;
- }
if (!verifyVibrationEffect(effect)) {
return;
}
@@ -577,6 +571,13 @@
}
Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg, reason);
+ if (mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
+ > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
+ && vib.isHapticFeedback()) {
+ Slog.e(TAG, "Ignoring incoming vibration as process with uid = "
+ + uid + " is background");
+ return;
+ }
linkVibration(vib);
long ident = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4f21ee8..7f67230 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -50,6 +50,7 @@
import static android.os.IServiceManager.DUMP_FLAG_PROTO;
import static android.os.Process.BLUETOOTH_UID;
import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.os.Process.NETWORK_STACK_UID;
import static android.os.Process.NFC_UID;
import static android.os.Process.PHONE_UID;
import static android.os.Process.PROC_CHAR;
@@ -14343,6 +14344,7 @@
case BLUETOOTH_UID:
case NFC_UID:
case SE_UID:
+ case NETWORK_STACK_UID:
isCallerSystem = true;
break;
default:
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index 905f826..9d6628c 100644
--- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
@@ -206,7 +206,7 @@
switch (event) {
case AudioManager.RECORD_CONFIG_EVENT_STOP:
// return failure if an unknown recording session stopped
- configChanged = (mRecordConfigs.remove(new Integer(session)) != null);
+ configChanged = (mRecordConfigs.remove(new Integer(portId)) != null);
if (configChanged) {
sEventLogger.log(new RecordingEvent(event, uid, session, source, null));
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 36ca4dc..fc1e326 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -132,10 +132,13 @@
Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED);
private final Uri FACE_UNLOCK_APP_ENABLED =
Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_APP_ENABLED);
+ private final Uri FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION =
+ Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION);
private final ContentResolver mContentResolver;
private boolean mFaceEnabledOnKeyguard;
private boolean mFaceEnabledForApps;
+ private boolean mFaceAlwaysRequireConfirmation;
/**
* Creates a content observer.
@@ -158,10 +161,15 @@
false /* notifyForDescendents */,
this /* observer */,
UserHandle.USER_CURRENT);
+ mContentResolver.registerContentObserver(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
+ false /* notifyForDescendents */,
+ this /* observer */,
+ UserHandle.USER_CURRENT);
// Update the value immediately
onChange(true /* selfChange */, FACE_UNLOCK_KEYGUARD_ENABLED);
onChange(true /* selfChange */, FACE_UNLOCK_APP_ENABLED);
+ onChange(true /* selfChange */, FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION);
}
@Override
@@ -185,6 +193,13 @@
Settings.Secure.FACE_UNLOCK_APP_ENABLED,
1 /* default */,
UserHandle.USER_CURRENT) != 0;
+ } else if (FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION.equals(uri)) {
+ mFaceAlwaysRequireConfirmation =
+ Settings.Secure.getIntForUser(
+ mContentResolver,
+ Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
+ 0 /* default */,
+ UserHandle.USER_CURRENT) != 0;
}
}
@@ -195,6 +210,10 @@
boolean getFaceEnabledForApps() {
return mFaceEnabledForApps;
}
+
+ boolean getFaceAlwaysRequireConfirmation() {
+ return mFaceAlwaysRequireConfirmation;
+ }
}
private final class EnabledOnKeyguardCallback implements IBinder.DeathRecipient {
@@ -395,7 +414,7 @@
// Notify SysUI that the biometric has been authenticated. SysUI already knows
// the implicit/explicit state and will react accordingly.
- mStatusBarService.onBiometricAuthenticated();
+ mStatusBarService.onBiometricAuthenticated(true);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
@@ -412,17 +431,20 @@
return;
}
- mStatusBarService.onBiometricHelp(getContext().getResources().getString(
- com.android.internal.R.string.biometric_not_recognized));
- if (requireConfirmation) {
+ mStatusBarService.onBiometricAuthenticated(false);
+
+ // TODO: This logic will need to be updated if BP is multi-modal
+ if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) {
+ // Pause authentication. onBiometricAuthenticated(false) causes the
+ // dialog to show a "try again" button for passive modalities.
mCurrentAuthSession.mState = STATE_AUTH_PAUSED;
- mStatusBarService.showBiometricTryAgain();
// Cancel authentication. Skip the token/package check since we are
// cancelling from system server. The interface is permission protected so
// this is fine.
cancelInternal(null /* token */, null /* package */,
false /* fromClient */);
}
+
mCurrentAuthSession.mClientReceiver.onAuthenticationFailed();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
@@ -579,8 +601,10 @@
}
if (mPendingAuthSession.mModalitiesWaiting.isEmpty()) {
- final boolean mContinuing = mCurrentAuthSession != null
- && mCurrentAuthSession.mState == STATE_AUTH_PAUSED;
+ final boolean continuing = mCurrentAuthSession != null &&
+ (mCurrentAuthSession.mState == STATE_AUTH_PAUSED
+ || mCurrentAuthSession.mState == STATE_AUTH_PAUSED_CANCELED);
+
mCurrentAuthSession = mPendingAuthSession;
mPendingAuthSession = null;
@@ -602,7 +626,7 @@
modality |= pair.getKey();
}
- if (!mContinuing) {
+ if (!continuing) {
mStatusBarService.showBiometricDialog(mCurrentAuthSession.mBundle,
mInternalReceiver, modality, requireConfirmation, userId);
mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
@@ -706,7 +730,8 @@
mCurrentModality = modality;
- // Actually start authentication
+ // Start preparing for authentication. Authentication starts when
+ // all modalities requested have invoked onReadyForAuthentication.
authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle,
callingUid, callingPid, callingUserId, modality);
});
@@ -725,6 +750,9 @@
IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle,
int callingUid, int callingPid, int callingUserId, int modality) {
try {
+ boolean requireConfirmation = bundle.getBoolean(
+ BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */);
+
// Generate random cookies to pass to the services that should prepare to start
// authenticating. Store the cookie here and wait for all services to "ack"
// with the cookie. Once all cookies are received, we can show the prompt
@@ -748,7 +776,10 @@
Slog.w(TAG, "Iris unsupported");
}
if ((modality & TYPE_FACE) != 0) {
- mFaceService.prepareForAuthentication(true /* requireConfirmation */,
+ // Check if the user has forced confirmation to be required in Settings.
+ requireConfirmation = requireConfirmation
+ || mSettingObserver.getFaceAlwaysRequireConfirmation();
+ mFaceService.prepareForAuthentication(requireConfirmation,
token, sessionId, userId, mInternalReceiver, opPackageName,
cookie, callingUid, callingPid, callingUserId);
}
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index 72f73f6..f4d8d4b 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -156,7 +156,7 @@
mDaemonWrapper, mHalDeviceId, token,
new BiometricPromptServiceListenerImpl(wrapperReceiver),
mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, cookie,
- true /* requireConfirmation */);
+ requireConfirmation);
authenticateInternal(client, opId, opPackageName, callingUid, callingPid,
callingUserId);
}
diff --git a/services/core/java/com/android/server/connectivity/ConnectivityConstants.java b/services/core/java/com/android/server/connectivity/ConnectivityConstants.java
index 24865bc..6fa98b8 100644
--- a/services/core/java/com/android/server/connectivity/ConnectivityConstants.java
+++ b/services/core/java/com/android/server/connectivity/ConnectivityConstants.java
@@ -21,22 +21,6 @@
* @hide
*/
public class ConnectivityConstants {
- // IPC constants
- public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
- "android.net.conn.NETWORK_CONDITIONS_MEASURED";
- public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type";
- public static final String EXTRA_NETWORK_TYPE = "extra_network_type";
- public static final String EXTRA_RESPONSE_RECEIVED = "extra_response_received";
- public static final String EXTRA_IS_CAPTIVE_PORTAL = "extra_is_captive_portal";
- public static final String EXTRA_CELL_ID = "extra_cellid";
- public static final String EXTRA_SSID = "extra_ssid";
- public static final String EXTRA_BSSID = "extra_bssid";
- /** real time since boot */
- public static final String EXTRA_REQUEST_TIMESTAMP_MS = "extra_request_timestamp_ms";
- public static final String EXTRA_RESPONSE_TIMESTAMP_MS = "extra_response_timestamp_ms";
-
- public static final String PERMISSION_ACCESS_NETWORK_CONDITIONS =
- "android.permission.ACCESS_NETWORK_CONDITIONS";
// Penalty applied to scores of Networks that have not been validated.
public static final int UNVALIDATED_SCORE_PENALTY = 40;
diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java
index b8f057d..d8bb635 100644
--- a/services/core/java/com/android/server/connectivity/DnsManager.java
+++ b/services/core/java/com/android/server/connectivity/DnsManager.java
@@ -18,10 +18,9 @@
import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE_FALLBACK;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
-import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
-import static android.provider.Settings.Global.DNS_RESOLVER_MIN_SAMPLES;
import static android.provider.Settings.Global.DNS_RESOLVER_MAX_SAMPLES;
+import static android.provider.Settings.Global.DNS_RESOLVER_MIN_SAMPLES;
import static android.provider.Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS;
import static android.provider.Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT;
import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE;
@@ -35,6 +34,7 @@
import android.net.Network;
import android.net.NetworkUtils;
import android.net.Uri;
+import android.net.shared.PrivateDnsConfig;
import android.os.Binder;
import android.os.INetworkManagementService;
import android.os.UserHandle;
@@ -43,10 +43,7 @@
import android.util.Pair;
import android.util.Slog;
-import com.android.server.connectivity.MockableSystemProperties;
-
import java.net.InetAddress;
-import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -54,10 +51,8 @@
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
-import java.util.Objects;
-import java.util.stream.Collectors;
import java.util.Set;
-import java.util.StringJoiner;
+import java.util.stream.Collectors;
/**
@@ -123,43 +118,6 @@
private static final int DNS_RESOLVER_DEFAULT_MIN_SAMPLES = 8;
private static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64;
- public static class PrivateDnsConfig {
- public final boolean useTls;
- public final String hostname;
- public final InetAddress[] ips;
-
- public PrivateDnsConfig() {
- this(false);
- }
-
- public PrivateDnsConfig(boolean useTls) {
- this.useTls = useTls;
- this.hostname = "";
- this.ips = new InetAddress[0];
- }
-
- public PrivateDnsConfig(String hostname, InetAddress[] ips) {
- this.useTls = !TextUtils.isEmpty(hostname);
- this.hostname = useTls ? hostname : "";
- this.ips = (ips != null) ? ips : new InetAddress[0];
- }
-
- public PrivateDnsConfig(PrivateDnsConfig cfg) {
- useTls = cfg.useTls;
- hostname = cfg.hostname;
- ips = cfg.ips;
- }
-
- public boolean inStrictMode() {
- return useTls && !TextUtils.isEmpty(hostname);
- }
-
- public String toString() {
- return PrivateDnsConfig.class.getSimpleName() +
- "{" + useTls + ":" + hostname + "/" + Arrays.toString(ips) + "}";
- }
- }
-
public static PrivateDnsConfig getPrivateDnsConfig(ContentResolver cr) {
final String mode = getPrivateDnsMode(cr);
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 262184b..54c89aa 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -16,9 +16,8 @@
package com.android.server.connectivity;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
-
import android.content.Context;
+import android.net.INetworkMonitor;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
@@ -29,7 +28,6 @@
import android.os.Handler;
import android.os.INetworkManagementService;
import android.os.Messenger;
-import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;
@@ -37,11 +35,8 @@
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.WakeupMessage;
import com.android.server.ConnectivityService;
-import com.android.server.connectivity.NetworkMonitor;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Comparator;
import java.util.Objects;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -126,7 +121,6 @@
public LinkProperties linkProperties;
// This should only be modified via ConnectivityService.updateCapabilities().
public NetworkCapabilities networkCapabilities;
- public final NetworkMonitor networkMonitor;
public final NetworkMisc networkMisc;
// Indicates if netd has been told to create this Network. From this point on the appropriate
// routing rules are setup and routes are added so packets can begin flowing over the Network.
@@ -239,6 +233,9 @@
// Used by ConnectivityService to keep track of 464xlat.
public Nat464Xlat clatd;
+ // Set after asynchronous creation of the NetworkMonitor.
+ private volatile INetworkMonitor mNetworkMonitor;
+
private static final String TAG = ConnectivityService.class.getSimpleName();
private static final boolean VDBG = false;
private final ConnectivityService mConnService;
@@ -247,7 +244,7 @@
public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler,
- NetworkMisc misc, NetworkRequest defaultRequest, ConnectivityService connService) {
+ NetworkMisc misc, ConnectivityService connService) {
this.messenger = messenger;
asyncChannel = ac;
network = net;
@@ -258,10 +255,16 @@
mConnService = connService;
mContext = context;
mHandler = handler;
- networkMonitor = mConnService.createNetworkMonitor(context, handler, this, defaultRequest);
networkMisc = misc;
}
+ /**
+ * Inform NetworkAgentInfo that a new NetworkMonitor was created.
+ */
+ public void onNetworkMonitorCreated(INetworkMonitor networkMonitor) {
+ mNetworkMonitor = networkMonitor;
+ }
+
public ConnectivityService connService() {
return mConnService;
}
@@ -278,6 +281,15 @@
return network;
}
+ /**
+ * Get the INetworkMonitor in this NetworkAgentInfo.
+ *
+ * <p>This will be null before {@link #onNetworkMonitorCreated(INetworkMonitor)} is called.
+ */
+ public INetworkMonitor networkMonitor() {
+ return mNetworkMonitor;
+ }
+
// Functions for manipulating the requests satisfied by this network.
//
// These functions must only called on ConnectivityService's main thread.
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index ea01a0b..1b0eb19 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -279,10 +279,18 @@
* <p>True means enabling muting logic.
* <p>False means never mute device.
*/
- // TODO(OEM): set to true to disable muting.
static final String PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE =
"ro.hdmi.property_system_audio_mode_muting_enable";
+ /**
+ * When set to true the HdmiControlService will never request a Logical Address for the
+ * playback device type. Default is false.
+ *
+ * <p> This is useful when HDMI CEC multiple device types is not supported by the cec driver
+ */
+ static final String PROPERTY_HDMI_CEC_NEVER_CLAIM_PLAYBACK_LOGICAL_ADDRESS =
+ "ro.hdmi.property_hdmi_cec_never_claim_playback_logical_address";
+
// Set to false to allow playback device to go to suspend mode even
// when it's an active source. True by default.
static final String PROPERTY_KEEP_AWAKE = "persist.sys.hdmi.keep_awake";
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
index 11faa56..2da698b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
@@ -252,6 +252,10 @@
return (HdmiCecLocalDevicePlayback) mSource;
}
+ protected final HdmiCecLocalDeviceSource source() {
+ return (HdmiCecLocalDeviceSource) mSource;
+ }
+
protected final HdmiCecLocalDeviceTv tv() {
return (HdmiCecLocalDeviceTv) mSource;
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 7860122..c338e21 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -27,10 +27,12 @@
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@@ -51,6 +53,11 @@
// Within the timer, a received <User Control Pressed> will start "Press and Hold" behavior.
// When it expires, we can assume <User Control Release> is received.
private static final int FOLLOWER_SAFETY_TIMEOUT = 550;
+ /**
+ * Return value of {@link #getLocalPortFromPhysicalAddress(int)}
+ */
+ private static final int TARGET_NOT_UNDER_LOCAL_DEVICE = -1;
+ private static final int TARGET_SAME_PHYSICAL_ADDRESS = 0;
protected final HdmiControlService mService;
protected final int mDeviceType;
@@ -434,10 +441,14 @@
return true;
}
+ // Audio System device with no Playback device type
+ // needs to refactor this function if it's also a switch
protected boolean handleRoutingChange(HdmiCecMessage message) {
return false;
}
+ // Audio System device with no Playback device type
+ // needs to refactor this function if it's also a switch
protected boolean handleRoutingInformation(HdmiCecMessage message) {
return false;
}
@@ -1033,4 +1044,45 @@
pw.println("mActiveSource: " + mActiveSource);
pw.println(String.format("mActiveRoutingPath: 0x%04x", mActiveRoutingPath));
}
+
+ /**
+ * Method to parse target physical address to the port number on the current device.
+ *
+ * <p>This check assumes target address is valid.
+ * @param targetPhysicalAddress is the physical address of the target device
+ * @return
+ * <p>If the target device is under the current device, return the port number of current device
+ * that the target device is connected to.
+ *
+ * <p>If the target device has the same physical address as the current device, return
+ * {@link #TARGET_SAME_PHYSICAL_ADDRESS}.
+ *
+ * <p>If the target device is not under the current device, return
+ * {@link #TARGET_NOT_UNDER_LOCAL_DEVICE}.
+ */
+ protected int getLocalPortFromPhysicalAddress(int targetPhysicalAddress) {
+ int myPhysicalAddress = mService.getPhysicalAddress();
+ if (myPhysicalAddress == targetPhysicalAddress) {
+ return TARGET_SAME_PHYSICAL_ADDRESS;
+ }
+ int finalMask = 0xF000;
+ int mask;
+ int port = 0;
+ for (mask = 0x0F00; mask > 0x000F; mask >>= 4) {
+ if ((myPhysicalAddress & mask) == 0) {
+ port = mask & targetPhysicalAddress;
+ break;
+ } else {
+ finalMask |= mask;
+ }
+ }
+ if (finalMask != 0xFFFF && (finalMask & targetPhysicalAddress) == myPhysicalAddress) {
+ while (mask != 0x000F) {
+ mask >>= 4;
+ port >>= 4;
+ }
+ return port;
+ }
+ return TARGET_NOT_UNDER_LOCAL_DEVICE;
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 7358eaa..20908b6 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -36,7 +36,7 @@
* Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android
* system.
*/
-public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice {
+public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
private static final String TAG = "HdmiCecLocalDeviceAudioSystem";
@@ -77,8 +77,6 @@
// TODO(amyjojo) make System Audio Control controllable by users
/*mSystemAudioControlFeatureEnabled =
mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, true);*/
- // TODO(b/80297700): set read-only property in config instead of setting here
- SystemProperties.set(Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE, "false");
mAutoDeviceOff = mService.readBooleanSetting(
Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, true);
mAutoTvOff = mService.readBooleanSetting(
@@ -143,43 +141,17 @@
int systemAudioOnPowerOnProp, boolean lastSystemAudioControlStatus) {
if ((systemAudioOnPowerOnProp == ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
|| ((systemAudioOnPowerOnProp == USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
- && lastSystemAudioControlStatus)) {
+ && lastSystemAudioControlStatus)) {
addAndStartAction(new SystemAudioInitiationActionFromAvr(this));
}
}
- @ServiceThreadOnly
- protected boolean handleActiveSource(HdmiCecMessage message) {
- assertRunOnServiceThread();
- int logicalAddress = message.getSource();
- int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
- ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
- if (!mActiveSource.equals(activeSource)) {
- setActiveSource(activeSource);
- }
- return true;
- }
-
- @Override
- @ServiceThreadOnly
- protected boolean handleSetStreamPath(HdmiCecMessage message) {
- assertRunOnServiceThread();
- int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
- // If current device is the target path, playback device should handle it.
- // If the path is under the current device, should switch
- int port = getLocalPortFromPhysicalAddress(physicalAddress);
- if (port > 0) {
- routeToPort(port);
- }
- return true;
- }
-
@Override
@ServiceThreadOnly
protected int getPreferredAddress() {
assertRunOnServiceThread();
return SystemProperties.getInt(
- Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, Constants.ADDR_UNREGISTERED);
+ Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, Constants.ADDR_UNREGISTERED);
}
@Override
@@ -323,7 +295,7 @@
for (int i = 0; i < params.length; i++) {
byte val = params[i];
audioFormatCodes[i] =
- val >= 1 && val <= Constants.AUDIO_CODEC_MAX ? val : Constants.AUDIO_CODEC_NONE;
+ val >= 1 && val <= Constants.AUDIO_CODEC_MAX ? val : Constants.AUDIO_CODEC_NONE;
}
return audioFormatCodes;
}
@@ -333,7 +305,23 @@
protected boolean handleSystemAudioModeRequest(HdmiCecMessage message) {
assertRunOnServiceThread();
boolean systemAudioStatusOn = message.getParams().length != 0;
- if (!setSystemAudioMode(systemAudioStatusOn)) {
+ // Check if the request comes from a non-TV device.
+ // Need to check if TV supports System Audio Control
+ // if non-TV device tries to turn on the feature
+ if (message.getSource() != Constants.ADDR_TV) {
+ if (systemAudioStatusOn) {
+ handleSystemAudioModeOnFromNonTvDevice(message);
+ return true;
+ }
+ } else {
+ // If TV request the feature on
+ // cache TV supporting System Audio Control
+ // until Audio System loses its physical address.
+ setTvSystemAudioModeSupport(true);
+ }
+ // If TV or Audio System does not support the feature,
+ // will send abort command.
+ if (!checkSupportAndSetSystemAudioMode(systemAudioStatusOn)) {
mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
return true;
}
@@ -348,7 +336,8 @@
@ServiceThreadOnly
protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
assertRunOnServiceThread();
- if (!setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
+ if (!checkSupportAndSetSystemAudioMode(
+ HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
}
return true;
@@ -358,7 +347,8 @@
@ServiceThreadOnly
protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
assertRunOnServiceThread();
- if (!setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
+ if (!checkSupportAndSetSystemAudioMode(
+ HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
}
return true;
@@ -390,7 +380,7 @@
private void notifyArcStatusToAudioService(boolean enabled) {
// Note that we don't set any name to ARC.
mService.getAudioManager()
- .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI, enabled ? 1 : 0, "", "");
+ .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI, enabled ? 1 : 0, "", "");
}
private void reportAudioStatus(HdmiCecMessage message) {
@@ -406,7 +396,16 @@
mAddress, message.getSource(), scaledVolume, mute));
}
- protected boolean setSystemAudioMode(boolean newSystemAudioMode) {
+ /**
+ * Method to check if device support System Audio Control. If so, wake up device if necessary.
+ *
+ * <p> then call {@link #setSystemAudioMode(boolean)} to turn on or off System Audio Mode
+ * @param newSystemAudioMode turning feature on or off. True is on. False is off.
+ * @return true or false.
+ *
+ * <p>False when device does not support the feature. Otherwise returns true.
+ */
+ protected boolean checkSupportAndSetSystemAudioMode(boolean newSystemAudioMode) {
if (!isSystemAudioControlFeatureEnabled()) {
HdmiLogger.debug(
"Cannot turn "
@@ -422,6 +421,17 @@
if (newSystemAudioMode && mService.isPowerStandbyOrTransient()) {
mService.wakeUp();
}
+ setSystemAudioMode(newSystemAudioMode);
+ return true;
+ }
+
+ /**
+ * Real work to turn on or off System Audio Mode.
+ *
+ * Use {@link #checkSupportAndSetSystemAudioMode(boolean)}
+ * if trying to turn on or off the feature.
+ */
+ private void setSystemAudioMode(boolean newSystemAudioMode) {
int targetPhysicalAddress = getActiveSource().physicalAddress;
int port = getLocalPortFromPhysicalAddress(targetPhysicalAddress);
if (newSystemAudioMode && port >= 0) {
@@ -449,48 +459,13 @@
mService.announceSystemAudioModeChange(newSystemAudioMode);
}
}
- return true;
- }
-
- /**
- * Method to parse target physical address to the port number on the current device.
- *
- * <p>This check assumes target address is valid.
- * @param targetPhysicalAddress is the physical address of the target device
- * @return
- * <p>If the target device is under the current device, return the port number of current device
- * that the target device is connected to.
- *
- * <p>If the target device has the same physical address as the current device, return
- * {@link #TARGET_SAME_PHYSICAL_ADDRESS}.
- *
- * <p>If the target device is not under the current device, return
- * {@link #TARGET_NOT_UNDER_LOCAL_DEVICE}.
- */
- protected int getLocalPortFromPhysicalAddress(int targetPhysicalAddress) {
- int myPhysicalAddress = mService.getPhysicalAddress();
- if (myPhysicalAddress == targetPhysicalAddress) {
- return TARGET_SAME_PHYSICAL_ADDRESS;
+ // Init arc whenever System Audio Mode is on
+ // Since some TV like LG don't request ARC on with System Audio Mode on request
+ if (newSystemAudioMode
+ && SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)
+ && !isArcEnabled() && isDirectConnectToTv()) {
+ addAndStartAction(new ArcInitiationActionFromAvr(this));
}
- int finalMask = 0xF000;
- int mask;
- int port = 0;
- for (mask = 0x0F00; mask > 0x000F; mask >>= 4) {
- if ((myPhysicalAddress & mask) == 0) {
- port = mask & targetPhysicalAddress;
- break;
- } else {
- finalMask |= mask;
- }
- }
- if (finalMask != 0xFFFF && (finalMask & targetPhysicalAddress) == myPhysicalAddress) {
- while (mask != 0x000F) {
- mask >>= 4;
- port >>= 4;
- }
- return port;
- }
- return TARGET_NOT_UNDER_LOCAL_DEVICE;
}
protected void switchToAudioInput() {
@@ -534,7 +509,7 @@
return;
}
- if (setSystemAudioMode(false)) {
+ if (checkSupportAndSetSystemAudioMode(false)) {
// send <Set System Audio Mode> [“Off”]
mService.sendCecCommand(
HdmiCecMessageBuilder.buildSetSystemAudioMode(
@@ -557,6 +532,7 @@
* <p>The result of the query may be cached until Audio device type is put in standby or loses
* its physical address.
*/
+ // TODO(amyjojo): making mTvSystemAudioModeSupport null originally and fix the logic.
void queryTvSystemAudioModeSupport(TvSystemAudioModeSupportedCallback callback) {
if (!mTvSystemAudioModeSupport) {
addAndStartAction(new DetectTvSystemAudioModeSupportAction(this, callback));
@@ -565,6 +541,37 @@
}
}
+ /**
+ * Handler of System Audio Mode Request on from non TV device
+ */
+ void handleSystemAudioModeOnFromNonTvDevice(HdmiCecMessage message) {
+ if (!isSystemAudioControlFeatureEnabled()) {
+ HdmiLogger.debug(
+ "Cannot turn on" + "system audio mode "
+ + "because the System Audio Control feature is disabled.");
+ mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+ return;
+ }
+ // Wake up device if it is still on standby
+ if (mService.isPowerStandbyOrTransient()) {
+ mService.wakeUp();
+ }
+ // Check if TV supports System Audio Control.
+ // Handle broadcasting setSystemAudioMode on or aborting message on callback.
+ queryTvSystemAudioModeSupport(new TvSystemAudioModeSupportedCallback() {
+ public void onResult(boolean supported) {
+ if (supported) {
+ setSystemAudioMode(true);
+ mService.sendCecCommand(
+ HdmiCecMessageBuilder.buildSetSystemAudioMode(
+ mAddress, Constants.ADDR_BROADCAST, true));
+ } else {
+ mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+ }
+ }
+ });
+ }
+
void setTvSystemAudioModeSupport(boolean supported) {
mTvSystemAudioModeSupport = supported;
}
@@ -588,14 +595,4 @@
assertRunOnServiceThread();
mAutoDeviceOff = autoDeviceOff;
}
-
- private void routeToPort(int portId) {
- // TODO(AMYJOJO): route to specific input of the port
- mLocalActivePath = portId;
- }
-
- @VisibleForTesting
- protected int getLocalActivePath() {
- return mLocalActivePath;
- }
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index d45b00b..e9dd682 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -26,6 +26,7 @@
import android.provider.Settings.Global;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.LocalePicker;
import com.android.internal.app.LocalePicker.LocaleInfo;
import com.android.internal.util.IndentingPrintWriter;
@@ -35,12 +36,10 @@
import java.util.List;
import java.util.Locale;
-import java.util.List;
-
/**
* Represent a logical device of type Playback residing in Android system.
*/
-final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice {
+final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
private static final String TAG = "HdmiCecLocalDevicePlayback";
private static final boolean WAKE_ON_HOTPLUG =
@@ -62,6 +61,11 @@
// If true, turn off TV upon standby. False by default.
private boolean mAutoTvOff;
+ // Local active port number used for Routing Control.
+ // Default 0 means HOME is the current active path. Temp solution only.
+ // TODO(amyjojo): adding system constants for input ports to TIF mapping.
+ private int mLocalActivePath = 0;
+
HdmiCecLocalDevicePlayback(HdmiControlService service) {
super(service, HdmiDeviceInfo.DEVICE_PLAYBACK);
@@ -100,25 +104,6 @@
}
@ServiceThreadOnly
- void oneTouchPlay(IHdmiControlCallback callback) {
- assertRunOnServiceThread();
- List<OneTouchPlayAction> actions = getActions(OneTouchPlayAction.class);
- if (!actions.isEmpty()) {
- Slog.i(TAG, "oneTouchPlay already in progress");
- actions.get(0).addCallback(callback);
- return;
- }
- OneTouchPlayAction action = OneTouchPlayAction.create(this, Constants.ADDR_TV,
- callback);
- if (action == null) {
- Slog.w(TAG, "Cannot initiate oneTouchPlay");
- invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
- return;
- }
- addAndStartAction(action);
- }
-
- @ServiceThreadOnly
void queryDisplayStatus(IHdmiControlCallback callback) {
assertRunOnServiceThread();
List<DevicePowerStatusAction> actions = getActions(DevicePowerStatusAction.class);
@@ -189,7 +174,7 @@
}
@ServiceThreadOnly
- void setActiveSource(boolean on) {
+ void setIsActiveSource(boolean on) {
assertRunOnServiceThread();
mIsActiveSource = on;
if (on) {
@@ -227,21 +212,6 @@
return !getWakeLock().isHeld();
}
- @Override
- @ServiceThreadOnly
- protected boolean handleActiveSource(HdmiCecMessage message) {
- assertRunOnServiceThread();
- int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
- mayResetActiveSource(physicalAddress);
- return true; // Broadcast message.
- }
-
- private void mayResetActiveSource(int physicalAddress) {
- if (physicalAddress != mService.getPhysicalAddress()) {
- setActiveSource(false);
- }
- }
-
@ServiceThreadOnly
protected boolean handleUserControlPressed(HdmiCecMessage message) {
assertRunOnServiceThread();
@@ -254,10 +224,21 @@
protected boolean handleSetStreamPath(HdmiCecMessage message) {
assertRunOnServiceThread();
int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
- maySetActiveSource(physicalAddress);
- maySendActiveSource(message.getSource());
- wakeUpIfActiveSource();
- return true; // Broadcast message.
+ // If current device is the target path, set to Active Source.
+ // If the path is under the current device, should switch
+ int port = getLocalPortFromPhysicalAddress(physicalAddress);
+ if (port == 0) {
+ setIsActiveSource(true);
+ maySendActiveSource(message.getSource());
+ wakeUpIfActiveSource();
+ } else if (port > 0) {
+ // Wake up the device if the power is in standby mode for routing
+ if (mService.isPowerStandbyOrTransient()) {
+ mService.wakeUp();
+ }
+ routeToPort(port);
+ }
+ return true;
}
// Samsung model we tested sends <Routing Change> and <Request Active Source>
@@ -282,7 +263,7 @@
}
private void maySetActiveSource(int physicalAddress) {
- setActiveSource(physicalAddress == mService.getPhysicalAddress());
+ setIsActiveSource(physicalAddress == mService.getPhysicalAddress());
}
private void wakeUpIfActiveSource() {
@@ -306,14 +287,6 @@
}
}
- @Override
- @ServiceThreadOnly
- protected boolean handleRequestActiveSource(HdmiCecMessage message) {
- assertRunOnServiceThread();
- maySendActiveSource(message.getSource());
- return true; // Broadcast message.
- }
-
@ServiceThreadOnly
protected boolean handleSetMenuLanguage(HdmiCecMessage message) {
assertRunOnServiceThread();
@@ -361,16 +334,6 @@
@Override
@ServiceThreadOnly
- protected void sendStandby(int deviceId) {
- assertRunOnServiceThread();
-
- // Playback device can send <Standby> to TV only. Ignore the parameter.
- int targetAddress = Constants.ADDR_TV;
- mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
- }
-
- @Override
- @ServiceThreadOnly
protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
super.disableDevice(initiatedByCec, callback);
@@ -379,10 +342,20 @@
mService.sendCecCommand(HdmiCecMessageBuilder.buildInactiveSource(
mAddress, mService.getPhysicalAddress()));
}
- setActiveSource(false);
+ setIsActiveSource(false);
checkIfPendingActionsCleared();
}
+ private void routeToPort(int portId) {
+ // TODO(AMYJOJO): route to specific input of the port
+ mLocalActivePath = portId;
+ }
+
+ @VisibleForTesting
+ protected int getLocalActivePath() {
+ return mLocalActivePath;
+ }
+
@Override
protected void dump(final IndentingPrintWriter pw) {
super.dump(pw);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
new file mode 100644
index 0000000..fed6622
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
+
+import java.util.List;
+
+/**
+ * Represent a logical source device residing in Android system.
+ */
+abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice {
+
+ private static final String TAG = "HdmiCecLocalDeviceSource";
+
+ // Indicate if current device is Active Source or not
+ private boolean mIsActiveSource = false;
+
+ protected HdmiCecLocalDeviceSource(HdmiControlService service, int deviceType) {
+ super(service, deviceType);
+ }
+
+ @Override
+ @ServiceThreadOnly
+ void onHotplug(int portId, boolean connected) {
+ assertRunOnServiceThread();
+ mCecMessageCache.flushAll();
+ // We'll not clear mIsActiveSource on the hotplug event to pass CETC 11.2.2-2 ~ 3.
+ if (mService.isPowerStandbyOrTransient()) {
+ mService.wakeUp();
+ }
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected void sendStandby(int deviceId) {
+ assertRunOnServiceThread();
+
+ // Send standby to TV only for now
+ int targetAddress = Constants.ADDR_TV;
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
+ }
+
+ @ServiceThreadOnly
+ void oneTouchPlay(IHdmiControlCallback callback) {
+ assertRunOnServiceThread();
+ List<OneTouchPlayAction> actions = getActions(OneTouchPlayAction.class);
+ if (!actions.isEmpty()) {
+ Slog.i(TAG, "oneTouchPlay already in progress");
+ actions.get(0).addCallback(callback);
+ return;
+ }
+ OneTouchPlayAction action = OneTouchPlayAction.create(this, Constants.ADDR_TV,
+ callback);
+ if (action == null) {
+ Slog.w(TAG, "Cannot initiate oneTouchPlay");
+ invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
+ return;
+ }
+ addAndStartAction(action);
+ }
+
+ @ServiceThreadOnly
+ private void invokeCallback(IHdmiControlCallback callback, int result) {
+ assertRunOnServiceThread();
+ try {
+ callback.onComplete(result);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Invoking callback failed:" + e);
+ }
+ }
+
+ @ServiceThreadOnly
+ protected boolean handleActiveSource(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ int logicalAddress = message.getSource();
+ int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
+ ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
+ if (!mActiveSource.equals(activeSource)) {
+ setActiveSource(activeSource);
+ }
+ setIsActiveSource(physicalAddress == mService.getPhysicalAddress());
+ return true;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleRequestActiveSource(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ if (mIsActiveSource) {
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
+ mAddress, mService.getPhysicalAddress()));
+ }
+ return true;
+ }
+
+ @ServiceThreadOnly
+ void setIsActiveSource(boolean on) {
+ assertRunOnServiceThread();
+ mIsActiveSource = on;
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index 941c321..3f949ba 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -365,6 +365,20 @@
}
/**
+ * Build <Routing Information> command.
+ *
+ * <p>This is a broadcast message sent to all devices on the bus.
+ *
+ * @param src source address of command
+ * @param physicalAddress physical address of the new active routing path
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildRoutingInformation(int src, int physicalAddress) {
+ return buildCommand(src, Constants.ADDR_BROADCAST,
+ Constants.MESSAGE_ROUTING_INFORMATION, physicalAddressToParam(physicalAddress));
+ }
+
+ /**
* Build <Give Device Power Status> command.
*
* @param src source address of command
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index e3a4084..7e369598 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -140,6 +140,10 @@
static final int STANDBY_SCREEN_OFF = 0;
static final int STANDBY_SHUTDOWN = 1;
+ private static final boolean isHdmiCecNeverClaimPlaybackLogicAddr =
+ SystemProperties.getBoolean(
+ Constants.PROPERTY_HDMI_CEC_NEVER_CLAIM_PLAYBACK_LOGICAL_ADDRESS, false);
+
/**
* Interface to report send result.
*/
@@ -639,6 +643,10 @@
// A container for [Device type, Local device info].
ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
for (int type : mLocalDevices) {
+ if (type == HdmiDeviceInfo.DEVICE_PLAYBACK
+ && isHdmiCecNeverClaimPlaybackLogicAddr) {
+ continue;
+ }
HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
if (localDevice == null) {
localDevice = HdmiCecLocalDevice.create(this, type);
@@ -1003,6 +1011,10 @@
if (connected && !isTvDevice()) {
ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
for (int type : mLocalDevices) {
+ if (type == HdmiDeviceInfo.DEVICE_PLAYBACK
+ && isHdmiCecNeverClaimPlaybackLogicAddr) {
+ continue;
+ }
HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
if (localDevice == null) {
localDevice = HdmiCecLocalDevice.create(this, type);
@@ -1651,6 +1663,9 @@
}
HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
if (device == null) {
+ device = audioSystem();
+ }
+ if (device == null) {
Slog.w(TAG, "Local device not available");
return;
}
@@ -1830,9 +1845,13 @@
@ServiceThreadOnly
private void oneTouchPlay(final IHdmiControlCallback callback) {
assertRunOnServiceThread();
- HdmiCecLocalDevicePlayback source = playback();
+ HdmiCecLocalDeviceSource source = playback();
if (source == null) {
- Slog.w(TAG, "Local playback device not available");
+ source = audioSystem();
+ }
+
+ if (source == null) {
+ Slog.w(TAG, "Local source device not available");
invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
return;
}
diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
index 5c66316..48c71d8 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
@@ -16,8 +16,8 @@
package com.android.server.hdmi;
import android.hardware.hdmi.HdmiControlManager;
-import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
+import android.hardware.hdmi.IHdmiControlCallback;
import android.os.RemoteException;
import android.util.Slog;
@@ -55,7 +55,7 @@
private int mPowerStatusCounter = 0;
// Factory method. Ensures arguments are valid.
- static OneTouchPlayAction create(HdmiCecLocalDevicePlayback source,
+ static OneTouchPlayAction create(HdmiCecLocalDeviceSource source,
int targetAddress, IHdmiControlCallback callback) {
if (source == null || callback == null) {
Slog.e(TAG, "Wrong arguments");
@@ -84,8 +84,8 @@
private void broadcastActiveSource() {
sendCommand(HdmiCecMessageBuilder.buildActiveSource(getSourceAddress(), getSourcePath()));
- // Because only playback device can create this action, it's safe to cast.
- playback().setActiveSource(true);
+ // Because only source device can create this action, it's safe to cast.
+ source().setIsActiveSource(true);
}
private void queryDevicePowerStatus() {
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java
index 2fdcb51..a6e6965 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java
@@ -16,6 +16,7 @@
package com.android.server.hdmi;
import android.hardware.tv.cec.V1_0.SendMessageResult;
+
import com.android.internal.annotations.VisibleForTesting;
/**
@@ -91,7 +92,7 @@
mSendRequestActiveSourceRetryCount++;
sendRequestActiveSource();
} else {
- audioSystem().setSystemAudioMode(false);
+ audioSystem().checkSupportAndSetSystemAudioMode(false);
finish();
}
}
@@ -106,7 +107,7 @@
mSendSetSystemAudioModeRetryCount++;
sendSetSystemAudioMode(on, dest);
} else {
- audioSystem().setSystemAudioMode(false);
+ audioSystem().checkSupportAndSetSystemAudioMode(false);
finish();
}
}
@@ -115,7 +116,7 @@
private void handleActiveSourceTimeout() {
HdmiLogger.debug("Cannot get active source.");
- audioSystem().setSystemAudioMode(false);
+ audioSystem().checkSupportAndSetSystemAudioMode(false);
finish();
}
@@ -123,12 +124,12 @@
audioSystem().queryTvSystemAudioModeSupport(
supported -> {
if (supported) {
- if (audioSystem().setSystemAudioMode(true)) {
+ if (audioSystem().checkSupportAndSetSystemAudioMode(true)) {
sendSetSystemAudioMode(true, Constants.ADDR_BROADCAST);
}
finish();
} else {
- audioSystem().setSystemAudioMode(false);
+ audioSystem().checkSupportAndSetSystemAudioMode(false);
finish();
}
});
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 3264790..d4b8eb2 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -16,7 +16,15 @@
package com.android.server.inputmethod;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.content.ComponentName;
+import android.view.inputmethod.InputMethodInfo;
+
+import com.android.server.LocalServices;
+
+import java.util.Collections;
+import java.util.List;
/**
* Input method manager local system service interface.
@@ -39,9 +47,25 @@
public abstract void startVrInputMethodNoCheck(ComponentName componentName);
/**
+ * Returns the list of installed input methods for the specified user.
+ *
+ * @param userId The user ID to be queried.
+ * @return A list of {@link InputMethodInfo}. VR-only IMEs are already excluded.
+ */
+ public abstract List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId);
+
+ /**
+ * Returns the list of installed input methods that are enabled for the specified user.
+ *
+ * @param userId The user ID to be queried.
+ * @return A list of {@link InputMethodInfo} that are enabled for {@code userId}.
+ */
+ public abstract List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId);
+
+ /**
* Fake implementation of {@link InputMethodManagerInternal}. All the methods do nothing.
*/
- public static final InputMethodManagerInternal NOP =
+ private static final InputMethodManagerInternal NOP =
new InputMethodManagerInternal() {
@Override
public void setInteractive(boolean interactive) {
@@ -54,5 +78,25 @@
@Override
public void startVrInputMethodNoCheck(ComponentName componentName) {
}
+
+ @Override
+ public List<InputMethodInfo> getInputMethodListAsUser(int userId) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) {
+ return Collections.emptyList();
+ }
};
+
+ /**
+ * @return Global instance if exists. Otherwise, a dummy no-op instance.
+ */
+ @NonNull
+ public static InputMethodManagerInternal get() {
+ final InputMethodManagerInternal instance =
+ LocalServices.getService(InputMethodManagerInternal.class);
+ return instance != null ? instance : NOP;
+ }
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index d45869e..5fa3f52 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -20,6 +20,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.inputmethod.InputMethodSystemProperty.PER_PROFILE_IME_ENABLED;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -1603,7 +1604,7 @@
// 1) it comes from the system process
// 2) the calling process' user id is identical to the current user id IMMS thinks.
@GuardedBy("mMethodMap")
- private boolean calledFromValidUserLocked() {
+ private boolean calledFromValidUserLocked(boolean allowCrossProfileAccess) {
final int uid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(uid);
if (DEBUG) {
@@ -1613,7 +1614,13 @@
+ mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid()
+ InputMethodUtils.getApiCallStack());
}
- if (uid == Process.SYSTEM_UID || mSettings.isCurrentProfile(userId)) {
+ if (uid == Process.SYSTEM_UID) {
+ return true;
+ }
+ if (userId == mSettings.getCurrentUserId()) {
+ return true;
+ }
+ if (allowCrossProfileAccess && mSettings.isCurrentProfile(userId)) {
return true;
}
@@ -1674,40 +1681,93 @@
@Override
public List<InputMethodInfo> getInputMethodList() {
- return getInputMethodList(false /* isVrOnly */);
+ final int callingUserId = UserHandle.getCallingUserId();
+ synchronized (mMethodMap) {
+ final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId,
+ mSettings.getCurrentUserId(), null);
+ if (resolvedUserIds.length != 1) {
+ return Collections.emptyList();
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return getInputMethodListLocked(false /* isVrOnly */, resolvedUserIds[0]);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
}
@Override
public List<InputMethodInfo> getVrInputMethodList() {
- return getInputMethodList(true /* isVrOnly */);
- }
-
- private List<InputMethodInfo> getInputMethodList(final boolean isVrOnly) {
+ final int callingUserId = UserHandle.getCallingUserId();
synchronized (mMethodMap) {
- // TODO: Make this work even for non-current users?
- if (!calledFromValidUserLocked()) {
+ final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId,
+ mSettings.getCurrentUserId(), null);
+ if (resolvedUserIds.length != 1) {
return Collections.emptyList();
}
- ArrayList<InputMethodInfo> methodList = new ArrayList<>();
- for (InputMethodInfo info : mMethodList) {
-
- if (info.isVrOnly() == isVrOnly) {
- methodList.add(info);
- }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return getInputMethodListLocked(true /* isVrOnly */, resolvedUserIds[0]);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
- return methodList;
}
}
@Override
public List<InputMethodInfo> getEnabledInputMethodList() {
+ final int callingUserId = UserHandle.getCallingUserId();
synchronized (mMethodMap) {
- // TODO: Make this work even for non-current users?
- if (!calledFromValidUserLocked()) {
+ final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId,
+ mSettings.getCurrentUserId(), null);
+ if (resolvedUserIds.length != 1) {
return Collections.emptyList();
}
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return getEnabledInputMethodListLocked(resolvedUserIds[0]);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ @GuardedBy("mMethodMap")
+ private List<InputMethodInfo> getInputMethodListLocked(boolean isVrOnly,
+ @UserIdInt int userId) {
+ final ArrayList<InputMethodInfo> methodList;
+ if (userId == mSettings.getCurrentUserId()) {
+ // Create a copy.
+ methodList = new ArrayList<>(mMethodList);
+ } else {
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ methodList = new ArrayList<>();
+ final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
+ new ArrayMap<>();
+ AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+ queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
+ methodList);
+ }
+ methodList.removeIf(imi -> imi.isVrOnly() != isVrOnly);
+ return methodList;
+ }
+
+ @GuardedBy("mMethodMap")
+ private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId) {
+ if (userId == mSettings.getCurrentUserId()) {
return mSettings.getEnabledInputMethodListLocked();
}
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
+ final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
+ new ArrayMap<>();
+ AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+ queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
+ methodList);
+ final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(),
+ mContext.getContentResolver(), methodMap, methodList, userId, true);
+ return settings.getEnabledInputMethodListLocked();
}
/**
@@ -1717,11 +1777,27 @@
@Override
public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
boolean allowsImplicitlySelectedSubtypes) {
+ final int callingUserId = UserHandle.getCallingUserId();
synchronized (mMethodMap) {
- // TODO: Make this work even for non-current users?
- if (!calledFromValidUserLocked()) {
+ final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId,
+ mSettings.getCurrentUserId(), null);
+ if (resolvedUserIds.length != 1) {
return Collections.emptyList();
}
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return getEnabledInputMethodSubtypeListLocked(imiId,
+ allowsImplicitlySelectedSubtypes, resolvedUserIds[0]);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ @GuardedBy("mMethodMap")
+ private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId,
+ boolean allowsImplicitlySelectedSubtypes, @UserIdInt int userId) {
+ if (userId == mSettings.getCurrentUserId()) {
final InputMethodInfo imi;
if (imiId == null && mCurMethodId != null) {
imi = mMethodMap.get(mCurMethodId);
@@ -1734,6 +1810,21 @@
return mSettings.getEnabledInputMethodSubtypeListLocked(
mContext, imi, allowsImplicitlySelectedSubtypes);
}
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
+ final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
+ new ArrayMap<>();
+ AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+ queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
+ methodList);
+ final InputMethodInfo imi = methodMap.get(imiId);
+ if (imi == null) {
+ return Collections.emptyList();
+ }
+ final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(),
+ mContext.getContentResolver(), methodMap, methodList, userId, true);
+ return settings.getEnabledInputMethodSubtypeListLocked(
+ mContext, imi, allowsImplicitlySelectedSubtypes);
}
/**
@@ -2434,7 +2525,7 @@
@Override
public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked()) {
+ if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
return;
}
final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
@@ -2450,7 +2541,7 @@
@Override
public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked()) {
+ if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
return false;
}
final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span);
@@ -2617,7 +2708,7 @@
ResultReceiver resultReceiver) {
int uid = Binder.getCallingUid();
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked()) {
+ if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
return false;
}
final long ident = Binder.clearCallingIdentity();
@@ -2702,7 +2793,7 @@
ResultReceiver resultReceiver) {
int uid = Binder.getCallingUid();
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked()) {
+ if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
return false;
}
final long ident = Binder.clearCallingIdentity();
@@ -2809,10 +2900,12 @@
@SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo attribute,
IInputContext inputContext, @MissingMethodFlags int missingMethods,
int unverifiedTargetSdkVersion) {
+ final int userId = UserHandle.getUserId(Binder.getCallingUid());
InputBindResult res = null;
synchronized (mMethodMap) {
// Needs to check the validity before clearing calling identity
- final boolean calledFromValidUser = calledFromValidUserLocked();
+ // Note that cross-profile access is always allowed here to allow profile-switching.
+ final boolean calledFromValidUser = calledFromValidUserLocked(true);
final int windowDisplayId =
mWindowManagerInternal.getDisplayIdForWindow(windowToken);
final long ident = Binder.clearCallingIdentity();
@@ -2864,6 +2957,10 @@
return InputBindResult.INVALID_USER;
}
+ if (PER_PROFILE_IME_ENABLED && userId != mSettings.getCurrentUserId()) {
+ switchUserLocked(userId);
+ }
+
if (mCurFocusedWindow == windowToken) {
if (DEBUG) {
Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
@@ -3032,7 +3129,7 @@
public void showInputMethodPickerFromClient(
IInputMethodClient client, int auxiliarySubtypeMode) {
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked()) {
+ if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
return;
}
if(!canShowInputMethodPickerLocked(client)) {
@@ -3103,7 +3200,7 @@
IInputMethodClient client, String inputMethodId) {
synchronized (mMethodMap) {
// TODO(yukawa): Should we verify the display ID?
- if (!calledFromValidUserLocked()) {
+ if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
return;
}
executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
@@ -3218,7 +3315,7 @@
@Override
public InputMethodSubtype getLastInputMethodSubtype() {
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked()) {
+ if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
return null;
}
final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
@@ -3256,7 +3353,7 @@
}
}
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked()) {
+ if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
return;
}
if (!mSystemReady) {
@@ -4102,7 +4199,7 @@
public InputMethodSubtype getCurrentInputMethodSubtype() {
synchronized (mMethodMap) {
// TODO: Make this work even for non-current users?
- if (!calledFromValidUserLocked()) {
+ if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
return null;
}
return getCurrentInputMethodSubtypeLocked();
@@ -4152,7 +4249,7 @@
public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
synchronized (mMethodMap) {
// TODO: Make this work even for non-current users?
- if (!calledFromValidUserLocked()) {
+ if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
return false;
}
if (subtype != null && mCurMethodId != null) {
@@ -4167,6 +4264,18 @@
}
}
+ private List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
+ synchronized (mMethodMap) {
+ return getInputMethodListLocked(false, userId);
+ }
+ }
+
+ private List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) {
+ synchronized (mMethodMap) {
+ return getEnabledInputMethodListLocked(userId);
+ }
+ }
+
private static final class LocalServiceImpl extends InputMethodManagerInternal {
@NonNull
private final InputMethodManagerService mService;
@@ -4192,6 +4301,16 @@
public void startVrInputMethodNoCheck(@Nullable ComponentName componentName) {
mService.mHandler.obtainMessage(MSG_START_VR_INPUT, componentName).sendToTarget();
}
+
+ @Override
+ public List<InputMethodInfo> getInputMethodListAsUser(int userId) {
+ return mService.getInputMethodListAsUser(userId);
+ }
+
+ @Override
+ public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) {
+ return mService.getEnabledInputMethodListAsUser(userId);
+ }
}
@BinderThread
@@ -4545,6 +4664,7 @@
private int handleShellCommandListInputMethods(@NonNull ShellCommand shellCommand) {
boolean all = false;
boolean brief = false;
+ int userIdToBeResolved = UserHandle.USER_CURRENT;
while (true) {
final String nextOption = shellCommand.getNextOption();
if (nextOption == null) {
@@ -4557,19 +4677,34 @@
case "-s":
brief = true;
break;
+ case "-u":
+ case "--user":
+ userIdToBeResolved = UserHandle.parseUserArg(shellCommand.getNextArgRequired());
+ break;
}
}
- final List<InputMethodInfo> methods = all ?
- getInputMethodList() : getEnabledInputMethodList();
- final PrintWriter pr = shellCommand.getOutPrintWriter();
- final Printer printer = x -> pr.println(x);
- final int N = methods.size();
- for (int i = 0; i < N; ++i) {
- if (brief) {
- pr.println(methods.get(i).getId());
- } else {
- pr.print(methods.get(i).getId()); pr.println(":");
- methods.get(i).dump(printer, " ");
+ synchronized (mMethodMap) {
+ final PrintWriter pr = shellCommand.getOutPrintWriter();
+ final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
+ mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter());
+ for (int userId : userIds) {
+ final List<InputMethodInfo> methods = all
+ ? getInputMethodListLocked(false, userId)
+ : getEnabledInputMethodListLocked(userId);
+ if (userIds.length > 1) {
+ pr.print("User #");
+ pr.print(userId);
+ pr.println(":");
+ }
+ for (InputMethodInfo info : methods) {
+ if (brief) {
+ pr.println(info.getId());
+ } else {
+ pr.print(info.getId());
+ pr.println(":");
+ info.dump(pr::println, " ");
+ }
+ }
}
}
return ShellCommandResult.SUCCESS;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 1137bf9..88d1a9c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -29,21 +29,27 @@
import android.os.Build;
import android.os.LocaleList;
import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManagerInternal;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.IntArray;
import android.util.Pair;
import android.util.Printer;
import android.util.Slog;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
+import android.view.inputmethod.InputMethodSystemProperty;
import android.view.textservice.SpellCheckerInfo;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.StartInputFlags;
+import com.android.server.LocalServices;
import com.android.server.textservices.TextServicesManagerInternal;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
@@ -1286,4 +1292,62 @@
return true;
}
+ /**
+ * Converts a user ID, which can be a pseudo user ID such as {@link UserHandle#USER_ALL} to a
+ * list of real user IDs.
+ *
+ * <p>This method also converts profile user ID to profile parent user ID unless
+ * {@link InputMethodSystemProperty#PER_PROFILE_IME_ENABLED} is {@code true}.</p>
+ *
+ * @param userIdToBeResolved A user ID. Two pseudo user ID {@link UserHandle#USER_CURRENT} and
+ * {@link UserHandle#USER_ALL} are also supported
+ * @param currentUserId A real user ID, which will be used when {@link UserHandle#USER_CURRENT}
+ * is specified in {@code userIdToBeResolved}.
+ * @param warningWriter A {@link PrintWriter} to output some debug messages. {@code null} if
+ * no debug message is required.
+ * @return An integer array that contain user IDs.
+ */
+ static int[] resolveUserId(@UserIdInt int userIdToBeResolved,
+ @UserIdInt int currentUserId, @Nullable PrintWriter warningWriter) {
+ final UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+
+ if (userIdToBeResolved == UserHandle.USER_ALL) {
+ if (InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
+ return userManagerInternal.getUserIds();
+ }
+ final IntArray result = new IntArray();
+ for (int userId : userManagerInternal.getUserIds()) {
+ final int parentUserId = userManagerInternal.getProfileParentId(userId);
+ if (result.indexOf(parentUserId) < 0) {
+ result.add(parentUserId);
+ }
+ }
+ return result.toArray();
+ }
+
+ final int sourceUserId;
+ if (userIdToBeResolved == UserHandle.USER_CURRENT) {
+ sourceUserId = currentUserId;
+ } else if (userIdToBeResolved < 0) {
+ if (warningWriter != null) {
+ warningWriter.print("Pseudo user ID ");
+ warningWriter.print(userIdToBeResolved);
+ warningWriter.println(" is not supported.");
+ }
+ return new int[]{};
+ } else if (userManagerInternal.exists(userIdToBeResolved)) {
+ sourceUserId = userIdToBeResolved;
+ } else {
+ if (warningWriter != null) {
+ warningWriter.print("User #");
+ warningWriter.print(userIdToBeResolved);
+ warningWriter.println(" does not exit.");
+ }
+ return new int[]{};
+ }
+ final int resolvedUserId = InputMethodSystemProperty.PER_PROFILE_IME_ENABLED
+ ? sourceUserId : userManagerInternal.getProfileParentId(sourceUserId);
+ return new int[]{resolvedUserId};
+ }
}
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 98ed3ea..f304ceb 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -163,6 +163,18 @@
public void startVrInputMethodNoCheck(ComponentName componentName) {
reportNotSupported();
}
+
+ @Override
+ public List<InputMethodInfo> getInputMethodListAsUser(
+ @UserIdInt int userId) {
+ return userIdToInputMethodInfoMapper.getAsList(userId);
+ }
+
+ @Override
+ public List<InputMethodInfo> getEnabledInputMethodListAsUser(
+ @UserIdInt int userId) {
+ return userIdToInputMethodInfoMapper.getAsList(userId);
+ }
});
}
diff --git a/services/core/java/com/android/server/location/AbstractLocationProvider.java b/services/core/java/com/android/server/location/AbstractLocationProvider.java
index 4c7c420..b3f1018 100644
--- a/services/core/java/com/android/server/location/AbstractLocationProvider.java
+++ b/services/core/java/com/android/server/location/AbstractLocationProvider.java
@@ -29,7 +29,7 @@
import java.util.List;
/**
- * Location Manager's interface for location providers.
+ * Location Manager's interface for location providers. Always starts as disabled.
*
* @hide
*/
@@ -41,12 +41,6 @@
public interface LocationProviderManager {
/**
- * Called on location provider construction to make the location service aware of this
- * provider and what it's initial enabled/disabled state should be.
- */
- void onAttachProvider(AbstractLocationProvider locationProvider, boolean initiallyEnabled);
-
- /**
* May be called to inform the location service of a change in this location provider's
* enabled/disabled state.
*/
@@ -74,13 +68,7 @@
private final LocationProviderManager mLocationProviderManager;
protected AbstractLocationProvider(LocationProviderManager locationProviderManager) {
- this(locationProviderManager, true);
- }
-
- protected AbstractLocationProvider(LocationProviderManager locationProviderManager,
- boolean initiallyEnabled) {
mLocationProviderManager = locationProviderManager;
- mLocationProviderManager.onAttachProvider(this, initiallyEnabled);
}
/**
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 3c81a45..269767a 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -559,7 +559,7 @@
public GnssLocationProvider(Context context, LocationProviderManager locationProviderManager,
Looper looper) {
- super(locationProviderManager, true);
+ super(locationProviderManager);
mContext = context;
@@ -652,6 +652,7 @@
}, UserHandle.ALL, intentFilter, null, mHandler);
setProperties(PROPERTIES);
+ setEnabled(true);
}
/**
diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java
index dfcef70..a6da8c5 100644
--- a/services/core/java/com/android/server/location/LocationProviderProxy.java
+++ b/services/core/java/com/android/server/location/LocationProviderProxy.java
@@ -101,7 +101,7 @@
private LocationProviderProxy(Context context, LocationProviderManager locationProviderManager,
String action, int overlaySwitchResId, int defaultServicePackageNameResId,
int initialPackageNamesResId) {
- super(locationProviderManager, false);
+ super(locationProviderManager);
mServiceWatcher = new ServiceWatcher(context, TAG, action, overlaySwitchResId,
defaultServicePackageNameResId, initialPackageNamesResId,
diff --git a/services/core/java/com/android/server/location/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java
index bfbebf7..86bc9f3 100644
--- a/services/core/java/com/android/server/location/MockProvider.java
+++ b/services/core/java/com/android/server/location/MockProvider.java
@@ -43,7 +43,7 @@
public MockProvider(
LocationProviderManager locationProviderManager, ProviderProperties properties) {
- super(locationProviderManager, true);
+ super(locationProviderManager);
mEnabled = true;
mLocation = null;
@@ -52,6 +52,7 @@
mExtras = null;
setProperties(properties);
+ setEnabled(true);
}
/** Sets the enabled state of this mock provider. */
@@ -63,8 +64,11 @@
/** Sets the location to report for this mock provider. */
public void setLocation(Location l) {
mLocation = new Location(l);
+ if (!mLocation.isFromMockProvider()) {
+ mLocation.setIsFromMockProvider(true);
+ }
if (mEnabled) {
- reportLocation(l);
+ reportLocation(mLocation);
}
}
diff --git a/services/core/java/com/android/server/location/PassiveProvider.java b/services/core/java/com/android/server/location/PassiveProvider.java
index 70d64b0..30260b2 100644
--- a/services/core/java/com/android/server/location/PassiveProvider.java
+++ b/services/core/java/com/android/server/location/PassiveProvider.java
@@ -43,11 +43,12 @@
private boolean mReportLocation;
public PassiveProvider(LocationProviderManager locationProviderManager) {
- super(locationProviderManager, true);
+ super(locationProviderManager);
mReportLocation = false;
setProperties(PROPERTIES);
+ setEnabled(true);
}
@Override
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index b70c64e..d611a17 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -19,7 +19,6 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ParceledListSlice;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
@@ -27,14 +26,15 @@
import android.media.MediaMetadata;
import android.media.Rating;
import android.media.VolumeProvider;
+import android.media.session.ControllerCallbackLink;
import android.media.session.ISession;
-import android.media.session.ISessionCallback;
import android.media.session.ISessionController;
-import android.media.session.ISessionControllerCallback;
import android.media.session.MediaController;
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.MediaSession;
+import android.media.session.MediaSession.QueueItem;
import android.media.session.PlaybackState;
+import android.media.session.SessionCallbackLink;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -44,7 +44,6 @@
import android.os.Looper;
import android.os.Message;
import android.os.Process;
-import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemClock;
import android.util.Log;
@@ -55,6 +54,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.List;
/**
* This is the system implementation of a Session. Apps will interact with the
@@ -84,7 +84,7 @@
private final Context mContext;
private final Object mLock = new Object();
- private final ArrayList<ISessionControllerCallbackHolder> mControllerCallbackHolders =
+ private final ArrayList<ControllerCallbackLinkHolder> mControllerCallbackHolders =
new ArrayList<>();
private long mFlags;
@@ -97,7 +97,7 @@
// may result in throwing an exception.
private MediaMetadata mMetadata;
private PlaybackState mPlaybackState;
- private ParceledListSlice mQueue;
+ private List<QueueItem> mQueue;
private CharSequence mQueueTitle;
private int mRatingType;
// End TransportPerformer fields
@@ -116,11 +116,11 @@
private boolean mIsActive = false;
private boolean mDestroyed = false;
- private long mDuration;
+ private long mDuration = -1;
private String mMetadataDescription;
public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
- ISessionCallback cb, String tag, MediaSessionService service, Looper handlerLooper) {
+ SessionCallbackLink cb, String tag, MediaSessionService service, Looper handlerLooper) {
mOwnerPid = ownerPid;
mOwnerUid = ownerUid;
mUserId = userId;
@@ -249,7 +249,7 @@
* @param useSuggested True to use adjustSuggestedStreamVolume instead of
*/
public void adjustVolume(String packageName, String opPackageName, int pid, int uid,
- ISessionControllerCallback caller, boolean asSystemService, int direction, int flags,
+ ControllerCallbackLink caller, boolean asSystemService, int direction, int flags,
boolean useSuggested) {
int previousFlagPlaySound = flags & AudioManager.FLAG_PLAY_SOUND;
if (isPlaybackActive() || hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
@@ -291,7 +291,7 @@
}
private void setVolumeTo(String packageName, String opPackageName, int pid, int uid,
- ISessionControllerCallback caller, int value, int flags) {
+ ControllerCallbackLink caller, int value, int flags) {
if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs);
final int volumeValue = value;
@@ -440,7 +440,7 @@
}
}
- public ISessionCallback getCallback() {
+ public SessionCallbackLink getCallback() {
return mSessionCb.mCb;
}
@@ -468,7 +468,7 @@
+ ", max=" + mMaxVolume + ", current=" + mCurrentVolume);
pw.println(indent + "metadata: " + mMetadataDescription);
pw.println(indent + "queueTitle=" + mQueueTitle + ", size="
- + (mQueue == null ? 0 : mQueue.getList().size()));
+ + (mQueue == null ? 0 : mQueue.size()));
}
@Override
@@ -518,7 +518,7 @@
}
private void logCallbackException(
- String msg, ISessionControllerCallbackHolder holder, Exception e) {
+ String msg, ControllerCallbackLinkHolder holder, Exception e) {
Log.v(TAG, msg + ", this=" + this + ", callback package=" + holder.mPackageName
+ ", exception=" + e);
}
@@ -529,16 +529,18 @@
return;
}
for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+ ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
try {
- holder.mCallback.onPlaybackStateChanged(mPlaybackState);
- } catch (DeadObjectException e) {
- mControllerCallbackHolders.remove(i);
- logCallbackException("Removed dead callback in pushPlaybackStateUpdate",
- holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushPlaybackStateUpdate",
- holder, e);
+ holder.mCallback.notifyPlaybackStateChanged(mPlaybackState);
+ } catch (RuntimeException e) {
+ if (e.getCause() instanceof DeadObjectException) {
+ mControllerCallbackHolders.remove(i);
+ logCallbackException("Removing dead callback in pushPlaybackStateUpdate",
+ holder, e);
+ } else {
+ logCallbackException("unexpected exception in pushPlaybackStateUpdate",
+ holder, e);
+ }
}
}
}
@@ -550,14 +552,18 @@
return;
}
for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+ ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
try {
- holder.mCallback.onMetadataChanged(mMetadata);
- } catch (DeadObjectException e) {
- logCallbackException("Removing dead callback in pushMetadataUpdate", holder, e);
- mControllerCallbackHolders.remove(i);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushMetadataUpdate", holder, e);
+ holder.mCallback.notifyMetadataChanged(mMetadata);
+ } catch (RuntimeException e) {
+ if (e.getCause() instanceof DeadObjectException) {
+ mControllerCallbackHolders.remove(i);
+ logCallbackException("Removing dead callback in pushMetadataUpdate",
+ holder, e);
+ } else {
+ logCallbackException("unexpected exception in pushMetadataUpdate",
+ holder, e);
+ }
}
}
}
@@ -569,14 +575,17 @@
return;
}
for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+ ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
try {
- holder.mCallback.onQueueChanged(mQueue);
- } catch (DeadObjectException e) {
- mControllerCallbackHolders.remove(i);
- logCallbackException("Removed dead callback in pushQueueUpdate", holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushQueueUpdate", holder, e);
+ holder.mCallback.notifyQueueChanged(mQueue);
+ } catch (RuntimeException e) {
+ if (e.getCause() instanceof DeadObjectException) {
+ mControllerCallbackHolders.remove(i);
+ logCallbackException("Removing dead callback in pushQueueUpdate",
+ holder, e);
+ } else {
+ logCallbackException("unexpected exception in pushQueueUpdate", holder, e);
+ }
}
}
}
@@ -588,16 +597,18 @@
return;
}
for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+ ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
try {
- holder.mCallback.onQueueTitleChanged(mQueueTitle);
- } catch (DeadObjectException e) {
- mControllerCallbackHolders.remove(i);
- logCallbackException("Removed dead callback in pushQueueTitleUpdate",
- holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushQueueTitleUpdate",
- holder, e);
+ holder.mCallback.notifyQueueTitleChanged(mQueueTitle);
+ } catch (RuntimeException e) {
+ if (e.getCause() instanceof DeadObjectException) {
+ mControllerCallbackHolders.remove(i);
+ logCallbackException("Removing dead callback in pushQueueTitleUpdate",
+ holder, e);
+ } else {
+ logCallbackException("unexpected exception in pushQueueTitleUpdate",
+ holder, e);
+ }
}
}
}
@@ -609,14 +620,17 @@
return;
}
for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+ ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
try {
- holder.mCallback.onExtrasChanged(mExtras);
- } catch (DeadObjectException e) {
- mControllerCallbackHolders.remove(i);
- logCallbackException("Removed dead callback in pushExtrasUpdate", holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushExtrasUpdate", holder, e);
+ holder.mCallback.notifyExtrasChanged(mExtras);
+ } catch (RuntimeException e) {
+ if (e.getCause() instanceof DeadObjectException) {
+ mControllerCallbackHolders.remove(i);
+ logCallbackException("Removing dead callback in pushExtrasUpdate",
+ holder, e);
+ } else {
+ logCallbackException("unexpected exception in pushExtrasUpdate", holder, e);
+ }
}
}
}
@@ -629,14 +643,17 @@
}
PlaybackInfo info = mController.getVolumeAttributes();
for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+ ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
try {
- holder.mCallback.onVolumeInfoChanged(info);
- } catch (DeadObjectException e) {
- mControllerCallbackHolders.remove(i);
- logCallbackException("Removing dead callback in pushVolumeUpdate", holder, e);
- } catch (RemoteException e) {
- logCallbackException("Unexpected exception in pushVolumeUpdate", holder, e);
+ holder.mCallback.notifyVolumeInfoChanged(info);
+ } catch (RuntimeException e) {
+ if (e.getCause() instanceof DeadObjectException) {
+ mControllerCallbackHolders.remove(i);
+ logCallbackException("Removing dead callback in pushVolumeUpdate",
+ holder, e);
+ } else {
+ logCallbackException("unexpected exception in pushVolumeUpdate", holder, e);
+ }
}
}
}
@@ -648,14 +665,16 @@
return;
}
for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+ ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
try {
- holder.mCallback.onEvent(event, data);
- } catch (DeadObjectException e) {
- mControllerCallbackHolders.remove(i);
- logCallbackException("Removing dead callback in pushEvent", holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushEvent", holder, e);
+ holder.mCallback.notifyEvent(event, data);
+ } catch (RuntimeException e) {
+ if (e.getCause() instanceof DeadObjectException) {
+ mControllerCallbackHolders.remove(i);
+ logCallbackException("Removing dead callback in pushEvent", holder, e);
+ } else {
+ logCallbackException("unexpected exception in pushEvent", holder, e);
+ }
}
}
}
@@ -669,14 +688,18 @@
return;
}
for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+ ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
try {
- holder.mCallback.onSessionDestroyed();
- } catch (DeadObjectException e) {
- logCallbackException("Removing dead callback in pushEvent", holder, e);
- mControllerCallbackHolders.remove(i);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushEvent", holder, e);
+ holder.mCallback.notifySessionDestroyed();
+ } catch (RuntimeException e) {
+ if (e.getCause() instanceof DeadObjectException) {
+ mControllerCallbackHolders.remove(i);
+ logCallbackException("Removing dead callback in pushSessionDestroyed",
+ holder, e);
+ } else {
+ logCallbackException("unexpected exception in pushSessionDestroyed",
+ holder, e);
+ }
}
}
// After notifying clear all listeners
@@ -716,10 +739,10 @@
return result == null ? state : result;
}
- private int getControllerHolderIndexForCb(ISessionControllerCallback cb) {
- IBinder binder = cb.asBinder();
+ private int getControllerHolderIndexForCb(ControllerCallbackLink cb) {
+ IBinder binder = cb.getBinder();
for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- if (binder.equals(mControllerCallbackHolders.get(i).mCallback.asBinder())) {
+ if (binder.equals(mControllerCallbackHolders.get(i).mCallback.getBinder())) {
return i;
}
}
@@ -843,7 +866,7 @@
}
@Override
- public void setQueue(ParceledListSlice queue) {
+ public void setQueue(List<QueueItem> queue) {
synchronized (mLock) {
mQueue = queue;
}
@@ -920,9 +943,9 @@
}
class SessionCb {
- private final ISessionCallback mCb;
+ private final SessionCallbackLink mCb;
- public SessionCb(ISessionCallback cb) {
+ SessionCb(SessionCallbackLink cb) {
mCb = cb;
}
@@ -930,224 +953,224 @@
boolean asSystemService, KeyEvent keyEvent, int sequenceId, ResultReceiver cb) {
try {
if (asSystemService) {
- mCb.onMediaButton(mContext.getPackageName(), Process.myPid(),
+ mCb.notifyMediaButton(mContext.getPackageName(), Process.myPid(),
Process.SYSTEM_UID, createMediaButtonIntent(keyEvent), sequenceId, cb);
} else {
- mCb.onMediaButton(packageName, pid, uid,
+ mCb.notifyMediaButton(packageName, pid, uid,
createMediaButtonIntent(keyEvent), sequenceId, cb);
}
return true;
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in sendMediaRequest.", e);
}
return false;
}
public boolean sendMediaButton(String packageName, int pid, int uid,
- ISessionControllerCallback caller, boolean asSystemService,
+ ControllerCallbackLink caller, boolean asSystemService,
KeyEvent keyEvent) {
try {
if (asSystemService) {
- mCb.onMediaButton(mContext.getPackageName(), Process.myPid(),
+ mCb.notifyMediaButton(mContext.getPackageName(), Process.myPid(),
Process.SYSTEM_UID, createMediaButtonIntent(keyEvent), 0, null);
} else {
- mCb.onMediaButtonFromController(packageName, pid, uid, caller,
+ mCb.notifyMediaButtonFromController(packageName, pid, uid, caller,
createMediaButtonIntent(keyEvent));
}
return true;
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in sendMediaRequest.", e);
}
return false;
}
public void sendCommand(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String command, Bundle args, ResultReceiver cb) {
+ ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) {
try {
- mCb.onCommand(packageName, pid, uid, caller, command, args, cb);
- } catch (RemoteException e) {
+ mCb.notifyCommand(packageName, pid, uid, caller, command, args, cb);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in sendCommand.", e);
}
}
public void sendCustomAction(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String action,
+ ControllerCallbackLink caller, String action,
Bundle args) {
try {
- mCb.onCustomAction(packageName, pid, uid, caller, action, args);
- } catch (RemoteException e) {
+ mCb.notifyCustomAction(packageName, pid, uid, caller, action, args);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in sendCustomAction.", e);
}
}
public void prepare(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
try {
- mCb.onPrepare(packageName, pid, uid, caller);
- } catch (RemoteException e) {
+ mCb.notifyPrepare(packageName, pid, uid, caller);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in prepare.", e);
}
}
public void prepareFromMediaId(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String mediaId, Bundle extras) {
+ ControllerCallbackLink caller, String mediaId, Bundle extras) {
try {
- mCb.onPrepareFromMediaId(packageName, pid, uid, caller, mediaId, extras);
- } catch (RemoteException e) {
+ mCb.notifyPrepareFromMediaId(packageName, pid, uid, caller, mediaId, extras);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in prepareFromMediaId.", e);
}
}
public void prepareFromSearch(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String query, Bundle extras) {
+ ControllerCallbackLink caller, String query, Bundle extras) {
try {
- mCb.onPrepareFromSearch(packageName, pid, uid, caller, query, extras);
- } catch (RemoteException e) {
+ mCb.notifyPrepareFromSearch(packageName, pid, uid, caller, query, extras);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in prepareFromSearch.", e);
}
}
public void prepareFromUri(String packageName, int pid, int uid,
- ISessionControllerCallback caller, Uri uri, Bundle extras) {
+ ControllerCallbackLink caller, Uri uri, Bundle extras) {
try {
- mCb.onPrepareFromUri(packageName, pid, uid, caller, uri, extras);
- } catch (RemoteException e) {
+ mCb.notifyPrepareFromUri(packageName, pid, uid, caller, uri, extras);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in prepareFromUri.", e);
}
}
- public void play(String packageName, int pid, int uid, ISessionControllerCallback caller) {
+ public void play(String packageName, int pid, int uid, ControllerCallbackLink caller) {
try {
- mCb.onPlay(packageName, pid, uid, caller);
- } catch (RemoteException e) {
+ mCb.notifyPlay(packageName, pid, uid, caller);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in play.", e);
}
}
public void playFromMediaId(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String mediaId, Bundle extras) {
+ ControllerCallbackLink caller, String mediaId, Bundle extras) {
try {
- mCb.onPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras);
- } catch (RemoteException e) {
+ mCb.notifyPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in playFromMediaId.", e);
}
}
public void playFromSearch(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String query, Bundle extras) {
+ ControllerCallbackLink caller, String query, Bundle extras) {
try {
- mCb.onPlayFromSearch(packageName, pid, uid, caller, query, extras);
- } catch (RemoteException e) {
+ mCb.notifyPlayFromSearch(packageName, pid, uid, caller, query, extras);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in playFromSearch.", e);
}
}
public void playFromUri(String packageName, int pid, int uid,
- ISessionControllerCallback caller, Uri uri, Bundle extras) {
+ ControllerCallbackLink caller, Uri uri, Bundle extras) {
try {
- mCb.onPlayFromUri(packageName, pid, uid, caller, uri, extras);
- } catch (RemoteException e) {
+ mCb.notifyPlayFromUri(packageName, pid, uid, caller, uri, extras);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in playFromUri.", e);
}
}
public void skipToTrack(String packageName, int pid, int uid,
- ISessionControllerCallback caller, long id) {
+ ControllerCallbackLink caller, long id) {
try {
- mCb.onSkipToTrack(packageName, pid, uid, caller, id);
- } catch (RemoteException e) {
+ mCb.notifySkipToTrack(packageName, pid, uid, caller, id);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in skipToTrack", e);
}
}
- public void pause(String packageName, int pid, int uid, ISessionControllerCallback caller) {
+ public void pause(String packageName, int pid, int uid, ControllerCallbackLink caller) {
try {
- mCb.onPause(packageName, pid, uid, caller);
- } catch (RemoteException e) {
+ mCb.notifyPause(packageName, pid, uid, caller);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in pause.", e);
}
}
- public void stop(String packageName, int pid, int uid, ISessionControllerCallback caller) {
+ public void stop(String packageName, int pid, int uid, ControllerCallbackLink caller) {
try {
- mCb.onStop(packageName, pid, uid, caller);
- } catch (RemoteException e) {
+ mCb.notifyStop(packageName, pid, uid, caller);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in stop.", e);
}
}
- public void next(String packageName, int pid, int uid, ISessionControllerCallback caller) {
+ public void next(String packageName, int pid, int uid, ControllerCallbackLink caller) {
try {
- mCb.onNext(packageName, pid, uid, caller);
- } catch (RemoteException e) {
+ mCb.notifyNext(packageName, pid, uid, caller);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in next.", e);
}
}
public void previous(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
try {
- mCb.onPrevious(packageName, pid, uid, caller);
- } catch (RemoteException e) {
+ mCb.notifyPrevious(packageName, pid, uid, caller);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in previous.", e);
}
}
public void fastForward(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
try {
- mCb.onFastForward(packageName, pid, uid, caller);
- } catch (RemoteException e) {
+ mCb.notifyFastForward(packageName, pid, uid, caller);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in fastForward.", e);
}
}
public void rewind(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
try {
- mCb.onRewind(packageName, pid, uid, caller);
- } catch (RemoteException e) {
+ mCb.notifyRewind(packageName, pid, uid, caller);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in rewind.", e);
}
}
- public void seekTo(String packageName, int pid, int uid, ISessionControllerCallback caller,
+ public void seekTo(String packageName, int pid, int uid, ControllerCallbackLink caller,
long pos) {
try {
- mCb.onSeekTo(packageName, pid, uid, caller, pos);
- } catch (RemoteException e) {
+ mCb.notifySeekTo(packageName, pid, uid, caller, pos);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in seekTo.", e);
}
}
- public void rate(String packageName, int pid, int uid, ISessionControllerCallback caller,
+ public void rate(String packageName, int pid, int uid, ControllerCallbackLink caller,
Rating rating) {
try {
- mCb.onRate(packageName, pid, uid, caller, rating);
- } catch (RemoteException e) {
+ mCb.notifyRate(packageName, pid, uid, caller, rating);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in rate.", e);
}
}
public void adjustVolume(String packageName, int pid, int uid,
- ISessionControllerCallback caller, boolean asSystemService, int direction) {
+ ControllerCallbackLink caller, boolean asSystemService, int direction) {
try {
if (asSystemService) {
- mCb.onAdjustVolume(mContext.getPackageName(), Process.myPid(),
+ mCb.notifyAdjustVolume(mContext.getPackageName(), Process.myPid(),
Process.SYSTEM_UID, null, direction);
} else {
- mCb.onAdjustVolume(packageName, pid, uid, caller, direction);
+ mCb.notifyAdjustVolume(packageName, pid, uid, caller, direction);
}
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in adjustVolume.", e);
}
}
public void setVolumeTo(String packageName, int pid, int uid,
- ISessionControllerCallback caller, int value) {
+ ControllerCallbackLink caller, int value) {
try {
- mCb.onSetVolumeTo(packageName, pid, uid, caller, value);
- } catch (RemoteException e) {
+ mCb.notifySetVolumeTo(packageName, pid, uid, caller, value);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in setVolumeTo.", e);
}
}
@@ -1161,34 +1184,34 @@
class ControllerStub extends ISessionController.Stub {
@Override
- public void sendCommand(String packageName, ISessionControllerCallback caller,
+ public void sendCommand(String packageName, ControllerCallbackLink caller,
String command, Bundle args, ResultReceiver cb) {
mSessionCb.sendCommand(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
caller, command, args, cb);
}
@Override
- public boolean sendMediaButton(String packageName, ISessionControllerCallback cb,
+ public boolean sendMediaButton(String packageName, ControllerCallbackLink cb,
boolean asSystemService, KeyEvent keyEvent) {
return mSessionCb.sendMediaButton(packageName, Binder.getCallingPid(),
Binder.getCallingUid(), cb, asSystemService, keyEvent);
}
@Override
- public void registerCallbackListener(String packageName, ISessionControllerCallback cb) {
+ public void registerCallbackListener(String packageName, ControllerCallbackLink cb) {
synchronized (mLock) {
// If this session is already destroyed tell the caller and
// don't add them.
if (mDestroyed) {
try {
- cb.onSessionDestroyed();
+ cb.notifySessionDestroyed();
} catch (Exception e) {
// ignored
}
return;
}
if (getControllerHolderIndexForCb(cb) < 0) {
- mControllerCallbackHolders.add(new ISessionControllerCallbackHolder(cb,
+ mControllerCallbackHolders.add(new ControllerCallbackLinkHolder(cb,
packageName, Binder.getCallingUid()));
if (DEBUG) {
Log.d(TAG, "registering controller callback " + cb + " from controller"
@@ -1199,14 +1222,14 @@
}
@Override
- public void unregisterCallbackListener(ISessionControllerCallback cb) {
+ public void unregisterCallbackListener(ControllerCallbackLink cb) {
synchronized (mLock) {
int index = getControllerHolderIndexForCb(cb);
if (index != -1) {
mControllerCallbackHolders.remove(index);
}
if (DEBUG) {
- Log.d(TAG, "unregistering callback " + cb.asBinder());
+ Log.d(TAG, "unregistering callback " + cb.getBinder());
}
}
}
@@ -1253,7 +1276,7 @@
@Override
public void adjustVolume(String packageName, String opPackageName,
- ISessionControllerCallback caller, boolean asSystemService, int direction,
+ ControllerCallbackLink caller, boolean asSystemService, int direction,
int flags) {
int pid = Binder.getCallingPid();
int uid = Binder.getCallingUid();
@@ -1268,7 +1291,7 @@
@Override
public void setVolumeTo(String packageName, String opPackageName,
- ISessionControllerCallback caller, int value, int flags) {
+ ControllerCallbackLink caller, int value, int flags) {
int pid = Binder.getCallingPid();
int uid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
@@ -1281,110 +1304,110 @@
}
@Override
- public void prepare(String packageName, ISessionControllerCallback caller) {
+ public void prepare(String packageName, ControllerCallbackLink caller) {
mSessionCb.prepare(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
}
@Override
- public void prepareFromMediaId(String packageName, ISessionControllerCallback caller,
+ public void prepareFromMediaId(String packageName, ControllerCallbackLink caller,
String mediaId, Bundle extras) {
mSessionCb.prepareFromMediaId(packageName, Binder.getCallingPid(),
Binder.getCallingUid(), caller, mediaId, extras);
}
@Override
- public void prepareFromSearch(String packageName, ISessionControllerCallback caller,
+ public void prepareFromSearch(String packageName, ControllerCallbackLink caller,
String query, Bundle extras) {
mSessionCb.prepareFromSearch(packageName, Binder.getCallingPid(),
Binder.getCallingUid(), caller, query, extras);
}
@Override
- public void prepareFromUri(String packageName, ISessionControllerCallback caller,
+ public void prepareFromUri(String packageName, ControllerCallbackLink caller,
Uri uri, Bundle extras) {
mSessionCb.prepareFromUri(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
caller, uri, extras);
}
@Override
- public void play(String packageName, ISessionControllerCallback caller) {
+ public void play(String packageName, ControllerCallbackLink caller) {
mSessionCb.play(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
}
@Override
- public void playFromMediaId(String packageName, ISessionControllerCallback caller,
+ public void playFromMediaId(String packageName, ControllerCallbackLink caller,
String mediaId, Bundle extras) {
mSessionCb.playFromMediaId(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
caller, mediaId, extras);
}
@Override
- public void playFromSearch(String packageName, ISessionControllerCallback caller,
+ public void playFromSearch(String packageName, ControllerCallbackLink caller,
String query, Bundle extras) {
mSessionCb.playFromSearch(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
caller, query, extras);
}
@Override
- public void playFromUri(String packageName, ISessionControllerCallback caller,
+ public void playFromUri(String packageName, ControllerCallbackLink caller,
Uri uri, Bundle extras) {
mSessionCb.playFromUri(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
caller, uri, extras);
}
@Override
- public void skipToQueueItem(String packageName, ISessionControllerCallback caller,
+ public void skipToQueueItem(String packageName, ControllerCallbackLink caller,
long id) {
mSessionCb.skipToTrack(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
caller, id);
}
@Override
- public void pause(String packageName, ISessionControllerCallback caller) {
+ public void pause(String packageName, ControllerCallbackLink caller) {
mSessionCb.pause(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
}
@Override
- public void stop(String packageName, ISessionControllerCallback caller) {
+ public void stop(String packageName, ControllerCallbackLink caller) {
mSessionCb.stop(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
}
@Override
- public void next(String packageName, ISessionControllerCallback caller) {
+ public void next(String packageName, ControllerCallbackLink caller) {
mSessionCb.next(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
}
@Override
- public void previous(String packageName, ISessionControllerCallback caller) {
+ public void previous(String packageName, ControllerCallbackLink caller) {
mSessionCb.previous(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
caller);
}
@Override
- public void fastForward(String packageName, ISessionControllerCallback caller) {
+ public void fastForward(String packageName, ControllerCallbackLink caller) {
mSessionCb.fastForward(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
caller);
}
@Override
- public void rewind(String packageName, ISessionControllerCallback caller) {
+ public void rewind(String packageName, ControllerCallbackLink caller) {
mSessionCb.rewind(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
}
@Override
- public void seekTo(String packageName, ISessionControllerCallback caller, long pos) {
+ public void seekTo(String packageName, ControllerCallbackLink caller, long pos) {
mSessionCb.seekTo(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller,
pos);
}
@Override
- public void rate(String packageName, ISessionControllerCallback caller, Rating rating) {
+ public void rate(String packageName, ControllerCallbackLink caller, Rating rating) {
mSessionCb.rate(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller,
rating);
}
@Override
- public void sendCustomAction(String packageName, ISessionControllerCallback caller,
+ public void sendCustomAction(String packageName, ControllerCallbackLink caller,
String action, Bundle args) {
mSessionCb.sendCustomAction(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
caller, action, args);
@@ -1403,7 +1426,7 @@
}
@Override
- public ParceledListSlice getQueue() {
+ public List<QueueItem> getQueue() {
synchronized (mLock) {
return mQueue;
}
@@ -1432,12 +1455,12 @@
}
}
- private class ISessionControllerCallbackHolder {
- private final ISessionControllerCallback mCallback;
+ private class ControllerCallbackLinkHolder {
+ private final ControllerCallbackLink mCallback;
private final String mPackageName;
private final int mUid;
- ISessionControllerCallbackHolder(ISessionControllerCallback callback, String packageName,
+ ControllerCallbackLinkHolder(ControllerCallbackLink callback, String packageName,
int uid) {
mCallback = callback;
mPackageName = packageName;
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 7f2e047..ce0e72b 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -48,10 +48,10 @@
import android.media.session.IOnMediaKeyListener;
import android.media.session.IOnVolumeKeyLongPressListener;
import android.media.session.ISession;
-import android.media.session.ISessionCallback;
import android.media.session.ISessionManager;
import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
+import android.media.session.SessionCallbackLink;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -114,10 +114,11 @@
@GuardedBy("mLock")
private final ArrayList<SessionsListenerRecord> mSessionsListeners
= new ArrayList<SessionsListenerRecord>();
+ // Map user id as index to list of Session2Tokens
// TODO: Keep session2 info in MediaSessionStack for prioritizing both session1 and session2 in
// one place.
@GuardedBy("mLock")
- private final List<Session2Token> mSession2Tokens = new ArrayList<>();
+ private final SparseArray<List<Session2Token>> mSession2TokensPerUser = new SparseArray<>();
private KeyguardManager mKeyguardManager;
private IAudioService mAudioService;
@@ -305,10 +306,13 @@
updateUser();
}
+ // Called when the user with the userId is removed.
@Override
public void onStopUser(int userId) {
if (DEBUG) Log.d(TAG, "onStopUser: " + userId);
synchronized (mLock) {
+ // TODO: Also handle removing user in updateUser() because adding/switching user is
+ // handled in updateUser().
FullUserRecord user = getFullUserRecordLocked(userId);
if (user != null) {
if (user.mFullUserId == userId) {
@@ -318,6 +322,7 @@
user.destroySessionsForUserLocked(userId);
}
}
+ mSession2TokensPerUser.remove(userId);
updateUser();
}
}
@@ -363,6 +368,9 @@
mUserRecords.put(userInfo.id, new FullUserRecord(userInfo.id));
}
}
+ if (mSession2TokensPerUser.get(userInfo.id) == null) {
+ mSession2TokensPerUser.put(userInfo.id, new ArrayList<>());
+ }
}
}
// Ensure that the current full user exists.
@@ -372,6 +380,9 @@
Log.w(TAG, "Cannot find FullUserInfo for the current user " + currentFullUserId);
mCurrentFullUserRecord = new FullUserRecord(currentFullUserId);
mUserRecords.put(currentFullUserId, mCurrentFullUserRecord);
+ if (mSession2TokensPerUser.get(currentFullUserId) == null) {
+ mSession2TokensPerUser.put(currentFullUserId, new ArrayList<>());
+ }
}
mFullUserIds.put(currentFullUserId, currentFullUserId);
}
@@ -425,7 +436,7 @@
}
try {
- session.getCallback().asBinder().unlinkToDeath(session, 0);
+ session.getCallback().getBinder().unlinkToDeath(session, 0);
} catch (Exception e) {
// ignore exceptions while destroying a session.
}
@@ -511,7 +522,7 @@
}
private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
- String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
+ String callerPackageName, SessionCallbackLink cb, String tag) throws RemoteException {
synchronized (mLock) {
return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
}
@@ -525,7 +536,7 @@
* 4. It needs to be added to the relevant user record.
*/
private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
- String callerPackageName, ISessionCallback cb, String tag) {
+ String callerPackageName, SessionCallbackLink cb, String tag) {
FullUserRecord user = getFullUserRecordLocked(userId);
if (user == null) {
Log.wtf(TAG, "Request from invalid user: " + userId);
@@ -535,7 +546,7 @@
final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
callerPackageName, cb, tag, this, mHandler.getLooper());
try {
- cb.asBinder().linkToDeath(session, 0);
+ cb.getBinder().linkToDeath(session, 0);
} catch (RemoteException e) {
throw new RuntimeException("Media Session owner died prematurely.", e);
}
@@ -732,9 +743,15 @@
pw.println(indent + "Restored MediaButtonReceiverComponentType: "
+ mRestoredMediaButtonReceiverComponentType);
mPriorityStack.dump(pw, indent);
- pw.println(indent + "Session2Tokens - " + mSession2Tokens.size());
- for (Session2Token session2Token : mSession2Tokens) {
- pw.println(indent + " " + session2Token);
+ pw.println(indent + "Session2Tokens:");
+ for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
+ List<Session2Token> list = mSession2TokensPerUser.valueAt(i);
+ if (list == null || list.size() == 0) {
+ continue;
+ }
+ for (Session2Token token : list) {
+ pw.println(indent + " " + token);
+ }
}
}
@@ -890,7 +907,7 @@
private boolean mVoiceButtonHandled = false;
@Override
- public ISession createSession(String packageName, ISessionCallback cb, String tag,
+ public ISession createSession(String packageName, SessionCallbackLink cb, String tag,
int userId) throws RemoteException {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -956,6 +973,34 @@
}
@Override
+ public List<Session2Token> getSession2Tokens(int userId) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+
+ try {
+ // Check that they can make calls on behalf of the user and
+ // get the final user id
+ int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
+ true /* allowAll */, true /* requireFull */, "getSession2Tokens",
+ null /* optional packageName */);
+ List<Session2Token> result = new ArrayList<>();
+ synchronized (mLock) {
+ if (resolvedUserId == UserHandle.USER_ALL) {
+ for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
+ result.addAll(mSession2TokensPerUser.valueAt(i));
+ }
+ } else {
+ result.addAll(mSession2TokensPerUser.get(userId));
+ }
+ }
+ return result;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
public void addSessionsListener(IActiveSessionsListener listener,
ComponentName componentName, int userId) throws RemoteException {
final int pid = Binder.getCallingPid();
@@ -1965,14 +2010,16 @@
@Override
public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) {
synchronized (mLock) {
- mSession2Tokens.add(mToken);
+ int userId = UserHandle.getUserId(mToken.getUid());
+ mSession2TokensPerUser.get(userId).add(mToken);
}
}
@Override
public void onDisconnected(MediaController2 controller) {
synchronized (mLock) {
- mSession2Tokens.remove(mToken);
+ int userId = UserHandle.getUserId(mToken.getUid());
+ mSession2TokensPerUser.get(userId).remove(mToken);
}
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 7ca39df..d0b20e8 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -329,6 +329,9 @@
if (valid) {
mSessions.put(session.sessionId, session);
+ if (session.isStaged()) {
+ mStagingManager.restoreSession(session);
+ }
} else {
// Since this is early during boot we don't send
// any observer events about the session, but we
@@ -533,7 +536,7 @@
session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
mInstallThread.getLooper(), mStagingManager, sessionId, userId,
installerPackageName, callingUid, params, createdMillis, stageDir, stageCid, false,
- false, null, SessionInfo.INVALID_ID);
+ false, null, SessionInfo.INVALID_ID, false, false, false, SessionInfo.NO_ERROR);
synchronized (mSessions) {
mSessions.put(sessionId, session);
@@ -1131,7 +1134,9 @@
}
}
synchronized (mSessions) {
- mSessions.remove(session.sessionId);
+ if (!session.isStaged() || !success) {
+ mSessions.remove(session.sessionId);
+ }
addHistoricalSessionLocked(session);
final File appIconFile = buildAppIconFile(session.sessionId);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 5cb6c34..516927f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -151,6 +151,10 @@
private static final String ATTR_MULTI_PACKAGE = "multiPackage";
private static final String ATTR_PARENT_SESSION_ID = "parentSessionId";
private static final String ATTR_STAGED_SESSION = "stagedSession";
+ private static final String ATTR_IS_READY = "isReady";
+ private static final String ATTR_IS_FAILED = "isFailed";
+ private static final String ATTR_IS_APPLIED = "isApplied";
+ private static final String ATTR_STAGED_SESSION_ERROR_CODE = "errorCode";
private static final String ATTR_MODE = "mode";
private static final String ATTR_INSTALL_FLAGS = "installFlags";
private static final String ATTR_INSTALL_LOCATION = "installLocation";
@@ -408,7 +412,8 @@
int sessionId, int userId,
String installerPackageName, int installerUid, SessionParams params, long createdMillis,
File stageDir, String stageCid, boolean prepared, boolean sealed,
- @Nullable int[] childSessionIds, int parentSessionId) {
+ @Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
+ boolean isFailed, boolean isApplied, int stagedSessionErrorCode) {
mCallback = callback;
mContext = context;
mPm = pm;
@@ -438,7 +443,10 @@
}
mPrepared = prepared;
-
+ mStagedSessionReady = isReady;
+ mStagedSessionFailed = isFailed;
+ mStagedSessionApplied = isApplied;
+ mStagedSessionErrorCode = stagedSessionErrorCode;
if (sealed) {
synchronized (mLock) {
try {
@@ -2023,8 +2031,9 @@
private void destroyInternal() {
synchronized (mLock) {
mSealed = true;
- mDestroyed = true;
-
+ if (!params.isStaged) {
+ mDestroyed = true;
+ }
// Force shut down all bridges
for (RevocableFileDescriptor fd : mFds) {
fd.revoke();
@@ -2131,6 +2140,10 @@
writeBooleanAttribute(out, ATTR_MULTI_PACKAGE, params.isMultiPackage);
writeBooleanAttribute(out, ATTR_STAGED_SESSION, params.isStaged);
+ writeBooleanAttribute(out, ATTR_IS_READY, mStagedSessionReady);
+ writeBooleanAttribute(out, ATTR_IS_FAILED, mStagedSessionFailed);
+ writeBooleanAttribute(out, ATTR_IS_APPLIED, mStagedSessionApplied);
+ writeIntAttribute(out, ATTR_STAGED_SESSION_ERROR_CODE, mStagedSessionErrorCode);
// TODO(patb,109941548): avoid writing to xml and instead infer / validate this after
// we've read all sessions.
writeIntAttribute(out, ATTR_PARENT_SESSION_ID, mParentSessionId);
@@ -2269,10 +2282,16 @@
params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath());
params.appIconLastModified = appIconFile.lastModified();
}
+ final boolean isReady = readBooleanAttribute(in, ATTR_IS_READY);
+ final boolean isFailed = readBooleanAttribute(in, ATTR_IS_FAILED);
+ final boolean isApplied = readBooleanAttribute(in, ATTR_IS_APPLIED);
+ final int stagedSessionErrorCode = readIntAttribute(in, ATTR_STAGED_SESSION_ERROR_CODE);
+
return new PackageInstallerSession(callback, context, pm, sessionProvider,
installerThread, stagingManager, sessionId, userId, installerPackageName,
installerUid, params, createdMillis, stageDir, stageCid, prepared, sealed,
- EMPTY_CHILD_SESSION_ARRAY, parentSessionId);
+ EMPTY_CHILD_SESSION_ARRAY, parentSessionId, isReady, isFailed, isApplied,
+ stagedSessionErrorCode);
}
/**
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 2329356..c297c62 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.apex.ApexInfo;
+import android.apex.ApexInfoList;
import android.apex.IApexService;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
@@ -52,9 +53,6 @@
private final PackageManagerService mPm;
private final Handler mBgHandler;
- // STOPSHIP: This is a temporary mock implementation of staged sessions. This variable
- // shouldn't be needed at all.
- // TODO(b/118865310): Implement staged sessions logic.
@GuardedBy("mStagedSessions")
private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
@@ -127,28 +125,55 @@
return false;
}
- void commitSession(@NonNull PackageInstallerSession sessionInfo) {
- updateStoredSession(sessionInfo);
+ private static boolean submitSessionToApexService(int sessionId, ApexInfoList apexInfoList) {
+ final IApexService apex = IApexService.Stub.asInterface(
+ ServiceManager.getService("apexservice"));
+ boolean success;
+ try {
+ success = apex.submitStagedSession(sessionId, apexInfoList);
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Unable to contact apexservice", re);
+ return false;
+ }
+ return success;
+ }
- mBgHandler.post(() -> {
- sessionInfo.setStagedSessionReady();
+ void preRebootVerification(@NonNull PackageInstallerSession session) {
+ boolean success = true;
+ if ((session.params.installFlags & PackageManager.INSTALL_APEX) != 0) {
- SessionInfo session = sessionInfo.generateInfo(false);
- // For APEXes, we validate the signature here before we write the package to the
- // staging directory. For APKs, the signature verification will be done by the package
- // manager at the point at which it applies the staged install.
- //
- // TODO: Decide whether we want to fail fast by detecting signature mismatches right
- // away.
- if ((sessionInfo.params.installFlags & PackageManager.INSTALL_APEX) != 0) {
- if (!validateApexSignatureLocked(session.resolvedBaseCodePath,
- session.appPackageName)) {
- sessionInfo.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED);
+ final ApexInfoList apexInfoList = new ApexInfoList();
+
+ if (!submitSessionToApexService(session.sessionId, apexInfoList)) {
+ success = false;
+ } else {
+ // For APEXes, we validate the signature here before we mark the session as ready,
+ // so we fail the session early if there is a signature mismatch. For APKs, the
+ // signature verification will be done by the package manager at the point at which
+ // it applies the staged install.
+ //
+ // TODO: Decide whether we want to fail fast by detecting signature mismatches right
+ // away.
+ for (ApexInfo apexPackage : apexInfoList.apexInfos) {
+ if (!validateApexSignatureLocked(apexPackage.packagePath,
+ apexPackage.packageName)) {
+ success = false;
+ break;
+ }
}
}
+ }
+ if (success) {
+ session.setStagedSessionReady();
+ } else {
+ session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED);
+ }
+ mPm.sendSessionUpdatedBroadcast(session.generateInfo(false), session.userId);
+ }
- mPm.sendSessionUpdatedBroadcast(sessionInfo.generateInfo(false), sessionInfo.userId);
- });
+ void commitSession(@NonNull PackageInstallerSession session) {
+ updateStoredSession(session);
+ mBgHandler.post(() -> preRebootVerification(session));
}
void createSession(@NonNull PackageInstallerSession sessionInfo) {
@@ -163,4 +188,11 @@
mStagedSessions.remove(sessionInfo.sessionId);
}
}
+
+ void restoreSession(@NonNull PackageInstallerSession session) {
+ updateStoredSession(session);
+ // TODO(b/118865310): This method is called when PackageInstaller is re-instantiated, e.g.
+ // at reboot. Staging manager should at this point recover state from apexd and decide what
+ // to do with the session.
+ }
}
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index dd04652..aaa1874 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -620,9 +620,6 @@
&& callingUid != Process.SYSTEM_UID) {
return true;
} else if (String.valueOf(Settings.Secure.LOCATION_MODE_OFF).equals(value)) {
- // Note LOCATION_MODE will be converted into LOCATION_PROVIDERS_ALLOWED
- // in android.provider.Settings.Secure.putStringForUser(), so we shouldn't come
- // here normally, but we still protect it here from a direct provider write.
return false;
}
restriction = UserManager.DISALLOW_SHARE_LOCATION;
diff --git a/services/core/java/com/android/server/policy/TEST_MAPPING b/services/core/java/com/android/server/policy/TEST_MAPPING
index e212b04..437ef73 100644
--- a/services/core/java/com/android/server/policy/TEST_MAPPING
+++ b/services/core/java/com/android/server/policy/TEST_MAPPING
@@ -10,7 +10,7 @@
"include-annotation": "android.platform.test.annotations.Presubmit"
},
{
- "exclude-annotation": "android.support.test.filters.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
},
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 02689a9..7d03d82 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -134,10 +134,14 @@
if (!halConnected) {
mHalWrapper = new ThermalHal20Wrapper();
halConnected = mHalWrapper.connectToHal();
- if (!halConnected) {
- mHalWrapper = new ThermalHal11Wrapper();
- halConnected = mHalWrapper.connectToHal();
- }
+ }
+ if (!halConnected) {
+ mHalWrapper = new ThermalHal11Wrapper();
+ halConnected = mHalWrapper.connectToHal();
+ }
+ if (!halConnected) {
+ mHalWrapper = new ThermalHal10Wrapper();
+ halConnected = mHalWrapper.connectToHal();
}
mHalWrapper.setCallback(this::onTemperatureChangedCallback);
if (!halConnected) {
@@ -616,6 +620,81 @@
}
}
+
+ static class ThermalHal10Wrapper extends ThermalHalWrapper {
+ /** Proxy object for the Thermal HAL 1.0 service. */
+ @GuardedBy("mHalLock")
+ private android.hardware.thermal.V1_0.IThermal mThermalHal10 = null;
+
+ @Override
+ protected List<Temperature> getCurrentTemperatures(boolean shouldFilter,
+ int type) {
+ synchronized (mHalLock) {
+ List<Temperature> ret = new ArrayList<>();
+ if (mThermalHal10 == null) {
+ return ret;
+ }
+ try {
+ mThermalHal10.getTemperatures(
+ (ThermalStatus status,
+ ArrayList<android.hardware.thermal.V1_0.Temperature>
+ temperatures) -> {
+ if (ThermalStatusCode.SUCCESS == status.code) {
+ for (android.hardware.thermal.V1_0.Temperature
+ temperature : temperatures) {
+ if (shouldFilter && type != temperature.type) {
+ continue;
+ }
+ // Thermal HAL 1.0 doesn't report current throttling status
+ ret.add(new Temperature(
+ temperature.currentValue, temperature.type,
+ temperature.name,
+ Temperature.THROTTLING_NONE));
+ }
+ } else {
+ Slog.e(TAG,
+ "Couldn't get temperatures because of HAL error: "
+ + status.debugMessage);
+ }
+
+ });
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Couldn't getCurrentTemperatures, reconnecting...", e);
+ connectToHal();
+ }
+ return ret;
+ }
+ }
+
+ @Override
+ protected boolean connectToHal() {
+ synchronized (mHalLock) {
+ try {
+ mThermalHal10 = android.hardware.thermal.V1_0.IThermal.getService();
+ mThermalHal10.linkToDeath(new DeathRecipient(),
+ THERMAL_HAL_DEATH_COOKIE);
+ Slog.i(TAG,
+ "Thermal HAL 1.0 service connected, no thermal call back will be "
+ + "called due to legacy API.");
+ } catch (NoSuchElementException | RemoteException e) {
+ Slog.e(TAG,
+ "Thermal HAL 1.0 service not connected.");
+ mThermalHal10 = null;
+ }
+ return (mThermalHal10 != null);
+ }
+ }
+
+ @Override
+ protected void dump(PrintWriter pw, String prefix) {
+ synchronized (mHalLock) {
+ pw.print(prefix);
+ pw.println("ThermalHAL 1.0 connected: " + (mThermalHal10 != null ? "yes"
+ : "no"));
+ }
+ }
+ }
+
static class ThermalHal11Wrapper extends ThermalHalWrapper {
/** Proxy object for the Thermal HAL 1.1 service. */
@GuardedBy("mHalLock")
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index fc21adb..7c1e619 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -598,11 +598,11 @@
}
@Override
- public void onBiometricAuthenticated() {
+ public void onBiometricAuthenticated(boolean authenticated) {
enforceBiometricDialog();
if (mBar != null) {
try {
- mBar.onBiometricAuthenticated();
+ mBar.onBiometricAuthenticated(authenticated);
} catch (RemoteException ex) {
}
}
@@ -641,17 +641,6 @@
}
}
- @Override
- public void showBiometricTryAgain() {
- enforceBiometricDialog();
- if (mBar != null) {
- try {
- mBar.showBiometricTryAgain();
- } catch (RemoteException ex) {
- }
- }
- }
-
// TODO(b/117478341): make it aware of multi-display if needed.
@Override
public void disable(int what, IBinder token, String pkg) {
diff --git a/services/core/java/com/android/server/textservices/TextServicesManagerService.java b/services/core/java/com/android/server/textservices/TextServicesManagerService.java
index 65d5b10..7236d79 100644
--- a/services/core/java/com/android/server/textservices/TextServicesManagerService.java
+++ b/services/core/java/com/android/server/textservices/TextServicesManagerService.java
@@ -16,8 +16,6 @@
package com.android.server.textservices;
-import static android.view.textservice.TextServicesManager.DISABLE_PER_PROFILE_SPELL_CHECKER;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -43,6 +41,7 @@
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
+import android.view.inputmethod.InputMethodSystemProperty;
import android.view.textservice.SpellCheckerInfo;
import android.view.textservice.SpellCheckerSubtype;
@@ -334,7 +333,7 @@
mContext = context;
mUserManager = mContext.getSystemService(UserManager.class);
mSpellCheckerOwnerUserIdMap = new LazyIntToIntMap(callingUserId -> {
- if (DISABLE_PER_PROFILE_SPELL_CHECKER) {
+ if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
final long token = Binder.clearCallingIdentity();
try {
final UserInfo parent = mUserManager.getProfileParent(callingUserId);
@@ -355,7 +354,7 @@
private void initializeInternalStateLocked(@UserIdInt int userId) {
// When DISABLE_PER_PROFILE_SPELL_CHECKER is true, we make sure here that work profile users
// will never have non-null TextServicesData for their user ID.
- if (DISABLE_PER_PROFILE_SPELL_CHECKER
+ if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED
&& userId != mSpellCheckerOwnerUserIdMap.get(userId)) {
return;
}
@@ -780,7 +779,7 @@
private TextServicesData getDataFromCallingUserIdLocked(@UserIdInt int callingUserId) {
final int spellCheckerOwnerUserId = mSpellCheckerOwnerUserIdMap.get(callingUserId);
final TextServicesData data = mUserData.get(spellCheckerOwnerUserId);
- if (DISABLE_PER_PROFILE_SPELL_CHECKER) {
+ if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
if (spellCheckerOwnerUserId != callingUserId) {
// Calling process is running under child profile.
if (data == null) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 410f864..fcc8284 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -84,6 +84,7 @@
import android.system.ErrnoException;
import android.system.Os;
import android.util.EventLog;
+import android.util.FeatureFlagUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -2219,8 +2220,12 @@
synchronized (mLock) {
mInAmbientMode = inAmbientMode;
final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
- if (data != null && data.connection != null && data.connection.mInfo != null
- && data.connection.mInfo.supportsAmbientMode()) {
+ final boolean hasConnection = data != null && data.connection != null;
+ final WallpaperInfo info = hasConnection ? data.connection.mInfo : null;
+
+ // The wallpaper info is null for image wallpaper, also use the engine in this case.
+ if (hasConnection && (info == null && isAodImageWallpaperEnabled()
+ || info != null && info.supportsAmbientMode())) {
// TODO(multi-display) Extends this method with specific display.
engine = data.connection.getDisplayConnectorOrCreate(DEFAULT_DISPLAY).mEngine;
} else {
@@ -2237,6 +2242,10 @@
}
}
+ private boolean isAodImageWallpaperEnabled() {
+ return FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED);
+ }
+
@Override
public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) {
checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW);
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 9ecafb3..c8c5e8f 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -107,6 +107,7 @@
"android.hardware.gnss@1.0",
"android.hardware.gnss@1.1",
"android.hardware.gnss@2.0",
+ "android.hardware.input.classifier@1.0",
"android.hardware.ir@1.0",
"android.hardware.light@2.0",
"android.hardware.power@1.0",
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 7f6895a..f78f6ed 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -221,7 +221,7 @@
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSystemProperty;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -245,6 +245,7 @@
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo;
+import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.pm.UserRestrictionsUtils;
import com.android.server.storage.DeviceStorageMonitorInternal;
@@ -9188,21 +9189,15 @@
}
Preconditions.checkNotNull(who, "ComponentName is null");
- // TODO When InputMethodManager supports per user calls remove
- // this restriction.
- if (!checkCallerIsCurrentUserOrProfile()) {
+ // TODO When InputMethodManager supports per user calls remove this restriction.
+ if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED
+ && !checkCallerIsCurrentUserOrProfile()) {
return false;
}
-
final int callingUserId = mInjector.userHandleGetCallingUserId();
if (packageList != null) {
- // InputMethodManager fetches input methods for current user.
- // So this can only be set when calling user is the current user
- // or parent is current user in case of managed profiles.
- InputMethodManager inputMethodManager =
- mContext.getSystemService(InputMethodManager.class);
- List<InputMethodInfo> enabledImes = inputMethodManager.getEnabledInputMethodList();
-
+ List<InputMethodInfo> enabledImes = InputMethodManagerInternal.get()
+ .getEnabledInputMethodListAsUser(callingUserId);
if (enabledImes != null) {
List<String> enabledPackages = new ArrayList<String>();
for (InputMethodInfo ime : enabledImes) {
@@ -9250,22 +9245,16 @@
@Override
public List getPermittedInputMethodsForCurrentUser() {
enforceManageUsers();
- UserInfo currentUser;
- try {
- currentUser = mInjector.getIActivityManager().getCurrentUser();
- } catch (RemoteException e) {
- Slog.e(LOG_TAG, "Failed to make remote calls to get current user", e);
- // Activity managed is dead, just allow all IMEs
- return null;
- }
- int userId = currentUser.id;
+ final int callingUserId = mInjector.userHandleGetCallingUserId();
synchronized (getLockObject()) {
List<String> result = null;
// If we have multiple profiles we return the intersection of the
// permitted lists. This can happen in cases where we have a device
// and profile owner.
- int[] profileIds = mUserManager.getProfileIdsWithDisabled(userId);
+ int[] profileIds = InputMethodSystemProperty.PER_PROFILE_IME_ENABLED
+ ? new int[]{callingUserId}
+ : mUserManager.getProfileIdsWithDisabled(callingUserId);
for (int profileId : profileIds) {
// Just loop though all admins, only device or profiles
// owners can have permitted lists set.
@@ -9286,9 +9275,8 @@
// If we have a permitted list add all system input methods.
if (result != null) {
- InputMethodManager inputMethodManager =
- mContext.getSystemService(InputMethodManager.class);
- List<InputMethodInfo> imes = inputMethodManager.getInputMethodList();
+ List<InputMethodInfo> imes =
+ InputMethodManagerInternal.get().getInputMethodListAsUser(callingUserId);
if (imes != null) {
for (InputMethodInfo ime : imes) {
ServiceInfo serviceInfo = ime.getServiceInfo();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
index d0ec0ee..699bec2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
@@ -23,6 +23,7 @@
import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.app.admin.DeviceAdminReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -30,15 +31,13 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.util.ArraySet;
import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSystemProperty;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.view.IInputMethodManager;
+import com.android.server.inputmethod.InputMethodManagerInternal;
import java.util.Arrays;
import java.util.List;
@@ -53,18 +52,38 @@
protected static final String TAG = "OverlayPackagesProvider";
private final PackageManager mPm;
- private final IInputMethodManager mIInputMethodManager;
private final Context mContext;
+ private final Injector mInjector;
public OverlayPackagesProvider(Context context) {
- this(context, getIInputMethodManager());
+ this(context, new DefaultInjector());
}
@VisibleForTesting
- OverlayPackagesProvider(Context context, IInputMethodManager iInputMethodManager) {
+ interface Injector {
+ boolean isPerProfileImeEnabled();
+ @NonNull
+ List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId);
+ }
+
+ private static final class DefaultInjector implements Injector {
+ @Override
+ public boolean isPerProfileImeEnabled() {
+ return InputMethodSystemProperty.PER_PROFILE_IME_ENABLED;
+ }
+
+ @NonNull
+ @Override
+ public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
+ return InputMethodManagerInternal.get().getInputMethodListAsUser(userId);
+ }
+ }
+
+ @VisibleForTesting
+ OverlayPackagesProvider(Context context, Injector injector) {
mContext = context;
mPm = checkNotNull(context.getPackageManager());
- mIInputMethodManager = checkNotNull(iInputMethodManager);
+ mInjector = checkNotNull(injector);
}
/**
@@ -89,10 +108,12 @@
// Newly installed system apps are uninstalled when they are not required and are either
// disallowed or have a launcher icon.
nonRequiredApps.removeAll(getRequiredApps(provisioningAction, admin.getPackageName()));
- // Don't delete the system input method packages in case of Device owner provisioning.
- if (ACTION_PROVISION_MANAGED_DEVICE.equals(provisioningAction)
+ if (mInjector.isPerProfileImeEnabled()) {
+ nonRequiredApps.removeAll(getSystemInputMethods(userId));
+ } else if (ACTION_PROVISION_MANAGED_DEVICE.equals(provisioningAction)
|| ACTION_PROVISION_MANAGED_USER.equals(provisioningAction)) {
- nonRequiredApps.removeAll(getSystemInputMethods());
+ // Don't delete the system input method packages in case of Device owner provisioning.
+ nonRequiredApps.removeAll(getSystemInputMethods(userId));
}
nonRequiredApps.addAll(getDisallowedApps(provisioningAction));
return nonRequiredApps;
@@ -114,16 +135,8 @@
return apps;
}
- private Set<String> getSystemInputMethods() {
- // InputMethodManager is final so it cannot be mocked.
- // So, we're using IInputMethodManager directly because it can be mocked.
- final List<InputMethodInfo> inputMethods;
- try {
- inputMethods = mIInputMethodManager.getInputMethodList();
- } catch (RemoteException e) {
- // Should not happen
- return null;
- }
+ private Set<String> getSystemInputMethods(int userId) {
+ final List<InputMethodInfo> inputMethods = mInjector.getInputMethodListAsUser(userId);
final Set<String> systemInputMethods = new ArraySet<>();
for (InputMethodInfo inputMethodInfo : inputMethods) {
ApplicationInfo applicationInfo = inputMethodInfo.getServiceInfo().applicationInfo;
@@ -149,11 +162,6 @@
return disallowedApps;
}
- private static IInputMethodManager getIInputMethodManager() {
- final IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
- return IInputMethodManager.Stub.asInterface(b);
- }
-
private Set<String> getRequiredAppsSet(String provisioningAction) {
final int resId;
switch (provisioningAction) {
diff --git a/services/ipmemorystore/Android.bp b/services/ipmemorystore/Android.bp
new file mode 100644
index 0000000..013cf56
--- /dev/null
+++ b/services/ipmemorystore/Android.bp
@@ -0,0 +1,4 @@
+java_library_static {
+ name: "services.ipmemorystore",
+ srcs: ["java/**/*.java"],
+}
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
new file mode 100644
index 0000000..c9759bf
--- /dev/null
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.ipmemorystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.IIpMemoryStore;
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.IOnBlobRetrievedListener;
+import android.net.ipmemorystore.IOnL2KeyResponseListener;
+import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
+import android.net.ipmemorystore.IOnSameNetworkResponseListener;
+import android.net.ipmemorystore.IOnStatusListener;
+import android.net.ipmemorystore.NetworkAttributesParcelable;
+
+/**
+ * Implementation for the IP memory store.
+ * This component offers specialized services for network components to store and retrieve
+ * knowledge about networks, and provides intelligence that groups level 2 networks together
+ * into level 3 networks.
+ *
+ * @hide
+ */
+public class IpMemoryStoreService extends IIpMemoryStore.Stub {
+ final Context mContext;
+
+ public IpMemoryStoreService(@NonNull final Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Store network attributes for a given L2 key.
+ *
+ * @param l2Key The L2 key for the L2 network. Clients that don't know or care about the L2
+ * key and only care about grouping can pass a unique ID here like the ones
+ * generated by {@code java.util.UUID.randomUUID()}, but keep in mind the low
+ * relevance of such a network will lead to it being evicted soon if it's not
+ * refreshed. Use findL2Key to try and find a similar L2Key to these attributes.
+ * @param attributes The attributes for this network.
+ * @param listener A listener to inform of the completion of this call, or null if the client
+ * is not interested in learning about success/failure.
+ * Through the listener, returns the L2 key. This is useful if the L2 key was not specified.
+ * If the call failed, the L2 key will be null.
+ */
+ @Override
+ public void storeNetworkAttributes(@NonNull final String l2Key,
+ @NonNull final NetworkAttributesParcelable attributes,
+ @Nullable final IOnStatusListener listener) {
+ // TODO : implement this
+ }
+
+ /**
+ * Store a binary blob associated with an L2 key and a name.
+ *
+ * @param l2Key The L2 key for this network.
+ * @param clientId The ID of the client.
+ * @param name The name of this data.
+ * @param data The data to store.
+ * @param listener The listener that will be invoked to return the answer, or null if the
+ * is not interested in learning about success/failure.
+ * Through the listener, returns a status to indicate success or failure.
+ */
+ @Override
+ public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId,
+ @NonNull final String name, @NonNull final Blob data,
+ @Nullable final IOnStatusListener listener) {
+ // TODO : implement this
+ }
+
+ /**
+ * Returns the best L2 key associated with the attributes.
+ *
+ * This will find a record that would be in the same group as the passed attributes. This is
+ * useful to choose the key for storing a sample or private data when the L2 key is not known.
+ * If multiple records are group-close to these attributes, the closest match is returned.
+ * If multiple records have the same closeness, the one with the smaller (unicode codepoint
+ * order) L2 key is returned.
+ * If no record matches these attributes, null is returned.
+ *
+ * @param attributes The attributes of the network to find.
+ * @param listener The listener that will be invoked to return the answer.
+ * Through the listener, returns the L2 key if one matched, or null.
+ */
+ @Override
+ public void findL2Key(@NonNull final NetworkAttributesParcelable attributes,
+ @NonNull final IOnL2KeyResponseListener listener) {
+ // TODO : implement this
+ }
+
+ /**
+ * Returns whether, to the best of the store's ability to tell, the two specified L2 keys point
+ * to the same L3 network. Group-closeness is used to determine this.
+ *
+ * @param l2Key1 The key for the first network.
+ * @param l2Key2 The key for the second network.
+ * @param listener The listener that will be invoked to return the answer.
+ * Through the listener, a SameL3NetworkResponse containing the answer and confidence.
+ */
+ @Override
+ public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2,
+ @NonNull final IOnSameNetworkResponseListener listener) {
+ // TODO : implement this
+ }
+
+ /**
+ * Retrieve the network attributes for a key.
+ * If no record is present for this key, this will return null attributes.
+ *
+ * @param l2Key The key of the network to query.
+ * @param listener The listener that will be invoked to return the answer.
+ * Through the listener, returns the network attributes and the L2 key associated with
+ * the query.
+ */
+ @Override
+ public void retrieveNetworkAttributes(@NonNull final String l2Key,
+ @NonNull final IOnNetworkAttributesRetrieved listener) {
+ // TODO : implement this.
+ }
+
+ /**
+ * Retrieve previously stored private data.
+ * If no data was stored for this L2 key and name this will return null.
+ *
+ * @param l2Key The L2 key.
+ * @param clientId The id of the client that stored this data.
+ * @param name The name of the data.
+ * @param listener The listener that will be invoked to return the answer.
+ * Through the listener, returns the private data if any or null if none, with the L2 key
+ * and the name of the data associated with the query.
+ */
+ @Override
+ public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId,
+ @NonNull final String name, @NonNull final IOnBlobRetrievedListener listener) {
+ // TODO : implement this.
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index a73ff9d..8235ca3 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -102,6 +102,7 @@
import com.android.server.media.projection.MediaProjectionManagerService;
import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.net.NetworkStatsService;
+import com.android.server.net.ipmemorystore.IpMemoryStoreService;
import com.android.server.net.watchlist.NetworkWatchlistService;
import com.android.server.notification.NotificationManagerService;
import com.android.server.oemlock.OemLockService;
@@ -1026,6 +1027,18 @@
Slog.e("System", "************ Failure starting core service", e);
}
+ // Before things start rolling, be sure we have decided whether
+ // we are in safe mode.
+ final boolean safeMode = wm.detectSafeMode();
+ if (safeMode) {
+ // If yes, immediately turn on the global setting for airplane mode.
+ // Note that this does not send broadcasts at this stage because
+ // subsystems are not yet up. We will send broadcasts later to ensure
+ // all listeners have the chance to react with special handling.
+ Settings.Global.putInt(context.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, 1);
+ }
+
StatusBarManagerService statusBar = null;
INotificationManager notification = null;
LocationManagerService location = null;
@@ -1173,6 +1186,15 @@
}
traceEnd();
+ traceBeginAndSlog("StartIpMemoryStoreService");
+ try {
+ ServiceManager.addService(Context.IP_MEMORY_STORE_SERVICE,
+ new IpMemoryStoreService(context));
+ } catch (Throwable e) {
+ reportWtf("starting IP Memory Store Service", e);
+ }
+ traceEnd();
+
traceBeginAndSlog("StartIpSecService");
try {
ipSecService = IpSecService.create(context);
@@ -1792,9 +1814,6 @@
mSystemServiceManager.startService(StatsCompanionService.Lifecycle.class);
traceEnd();
- // Before things start rolling, be sure we have decided whether
- // we are in safe mode.
- final boolean safeMode = wm.detectSafeMode();
if (safeMode) {
traceBeginAndSlog("EnterSafeModeAndDisableJitCompilation");
mActivityManagerService.enterSafeMode();
@@ -1991,6 +2010,20 @@
reportWtf("starting System UI", e);
}
traceEnd();
+ // Enable airplane mode in safe mode. setAirplaneMode() cannot be called
+ // earlier as it sends broadcasts to other services.
+ // TODO: This may actually be too late if radio firmware already started leaking
+ // RF before the respective services start. However, fixing this requires changes
+ // to radio firmware and interfaces.
+ if (safeMode) {
+ traceBeginAndSlog("EnableAirplaneModeInSafeMode");
+ try {
+ connectivityF.setAirplaneMode(true);
+ } catch (Throwable e) {
+ reportWtf("enabling Airplane Mode during Safe Mode bootup", e);
+ }
+ traceEnd();
+ }
traceBeginAndSlog("MakeNetworkManagementServiceReady");
try {
if (networkManagementF != null) {
@@ -2185,7 +2218,7 @@
windowManager.onSystemUiStarted();
}
- private static void traceBeginAndSlog(String name) {
+ private static void traceBeginAndSlog(@NonNull String name) {
Slog.i(TAG, name);
BOOT_TIMINGS_TRACE_LOG.traceBegin(name);
}
diff --git a/services/net/Android.bp b/services/net/Android.bp
index ae697b7..3b4d6a7 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -11,10 +11,10 @@
]
}
-// TODO: move to networking module with IpNeighborMonitor/ConnectivityPacketTracker and remove lib
-java_library {
- name: "frameworks-net-shared-utils",
+filegroup {
+ name: "services-networkstack-shared-srcs",
srcs: [
- "java/android/net/util/FdEventsReader.java",
+ "java/android/net/util/FdEventsReader.java", // TODO: move to NetworkStack with IpClient
+ "java/android/net/shared/*.java",
]
-}
\ No newline at end of file
+}
diff --git a/services/net/java/android/net/shared/NetworkMonitorUtils.java b/services/net/java/android/net/shared/NetworkMonitorUtils.java
new file mode 100644
index 0000000..463cf2a
--- /dev/null
+++ b/services/net/java/android/net/shared/NetworkMonitorUtils.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.shared;
+
+import android.content.Context;
+import android.net.NetworkCapabilities;
+import android.provider.Settings;
+
+/** @hide */
+public class NetworkMonitorUtils {
+
+ // Network conditions broadcast constants
+ public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
+ "android.net.conn.NETWORK_CONDITIONS_MEASURED";
+ public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type";
+ public static final String EXTRA_NETWORK_TYPE = "extra_network_type";
+ public static final String EXTRA_RESPONSE_RECEIVED = "extra_response_received";
+ public static final String EXTRA_IS_CAPTIVE_PORTAL = "extra_is_captive_portal";
+ public static final String EXTRA_CELL_ID = "extra_cellid";
+ public static final String EXTRA_SSID = "extra_ssid";
+ public static final String EXTRA_BSSID = "extra_bssid";
+ /** real time since boot */
+ public static final String EXTRA_REQUEST_TIMESTAMP_MS = "extra_request_timestamp_ms";
+ public static final String EXTRA_RESPONSE_TIMESTAMP_MS = "extra_response_timestamp_ms";
+ public static final String PERMISSION_ACCESS_NETWORK_CONDITIONS =
+ "android.permission.ACCESS_NETWORK_CONDITIONS";
+
+ // TODO: once the URL is a resource overlay, remove and have the resource define the default
+ private static final String DEFAULT_HTTP_URL =
+ "http://connectivitycheck.gstatic.com/generate_204";
+
+ /**
+ * Get the captive portal server HTTP URL that is configured on the device.
+ */
+ public static String getCaptivePortalServerHttpUrl(Context context) {
+ final String settingUrl = Settings.Global.getString(
+ context.getContentResolver(),
+ Settings.Global.CAPTIVE_PORTAL_HTTP_URL);
+ return settingUrl != null ? settingUrl : DEFAULT_HTTP_URL;
+ }
+
+ /**
+ * Return whether validation is required for a network.
+ * @param dfltNetCap Default requested network capabilities.
+ * @param nc Network capabilities of the network to test.
+ */
+ public static boolean isValidationRequired(
+ NetworkCapabilities dfltNetCap, NetworkCapabilities nc) {
+ // TODO: Consider requiring validation for DUN networks.
+ return dfltNetCap.satisfiedByNetworkCapabilities(nc);
+ }
+}
diff --git a/services/net/java/android/net/shared/PrivateDnsConfig.java b/services/net/java/android/net/shared/PrivateDnsConfig.java
new file mode 100644
index 0000000..41e0bad
--- /dev/null
+++ b/services/net/java/android/net/shared/PrivateDnsConfig.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.shared;
+
+import android.net.InetAddresses;
+import android.net.PrivateDnsConfigParcel;
+import android.text.TextUtils;
+
+import java.net.InetAddress;
+import java.util.Arrays;
+
+/** @hide */
+public class PrivateDnsConfig {
+ public final boolean useTls;
+ public final String hostname;
+ public final InetAddress[] ips;
+
+ public PrivateDnsConfig() {
+ this(false);
+ }
+
+ public PrivateDnsConfig(boolean useTls) {
+ this.useTls = useTls;
+ this.hostname = "";
+ this.ips = new InetAddress[0];
+ }
+
+ public PrivateDnsConfig(String hostname, InetAddress[] ips) {
+ this.useTls = !TextUtils.isEmpty(hostname);
+ this.hostname = useTls ? hostname : "";
+ this.ips = (ips != null) ? ips : new InetAddress[0];
+ }
+
+ public PrivateDnsConfig(PrivateDnsConfig cfg) {
+ useTls = cfg.useTls;
+ hostname = cfg.hostname;
+ ips = cfg.ips;
+ }
+
+ /**
+ * Indicates whether this is a strict mode private DNS configuration.
+ */
+ public boolean inStrictMode() {
+ return useTls && !TextUtils.isEmpty(hostname);
+ }
+
+ @Override
+ public String toString() {
+ return PrivateDnsConfig.class.getSimpleName()
+ + "{" + useTls + ":" + hostname + "/" + Arrays.toString(ips) + "}";
+ }
+
+ /**
+ * Create a stable AIDL-compatible parcel from the current instance.
+ */
+ public PrivateDnsConfigParcel toParcel() {
+ final PrivateDnsConfigParcel parcel = new PrivateDnsConfigParcel();
+ parcel.hostname = hostname;
+
+ final String[] parceledIps = new String[ips.length];
+ for (int i = 0; i < ips.length; i++) {
+ parceledIps[i] = ips[i].getHostAddress();
+ }
+ parcel.ips = parceledIps;
+
+ return parcel;
+ }
+
+ /**
+ * Build a configuration from a stable AIDL-compatible parcel.
+ */
+ public static PrivateDnsConfig fromParcel(PrivateDnsConfigParcel parcel) {
+ final InetAddress[] ips = new InetAddress[parcel.ips.length];
+ for (int i = 0; i < ips.length; i++) {
+ ips[i] = InetAddresses.parseNumericAddress(parcel.ips[i]);
+ }
+
+ return new PrivateDnsConfig(parcel.hostname, ips);
+ }
+}
diff --git a/services/net/java/android/net/util/SharedLog.java b/services/net/java/android/net/util/SharedLog.java
index 8b7b59d..2cdb2b0 100644
--- a/services/net/java/android/net/util/SharedLog.java
+++ b/services/net/java/android/net/util/SharedLog.java
@@ -70,6 +70,10 @@
mComponent = component;
}
+ public String getTag() {
+ return mTag;
+ }
+
/**
* Create a SharedLog based on this log with an additional component prefix on each logged line.
*/
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index 5e84ab0..7dac795 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -71,6 +71,7 @@
import android.annotation.Nullable;
import android.app.Application;
+import android.app.ApplicationPackageManager;
import android.app.IBackupAgent;
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
@@ -134,6 +135,8 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
import org.robolectric.shadows.ShadowLooper;
import org.robolectric.shadows.ShadowPackageManager;
import org.robolectric.shadows.ShadowQueuedWork;
@@ -157,6 +160,7 @@
@Config(
shadows = {
FrameworkShadowLooper.class,
+ KeyValueBackupTaskTest.ShadowApplicationPackageManager.class,
ShadowBackupDataInput.class,
ShadowBackupDataOutput.class,
ShadowEventLog.class,
@@ -244,6 +248,7 @@
@After
public void tearDown() throws Exception {
ShadowBackupDataInput.reset();
+ ShadowApplicationPackageManager.reset();
}
@Test
@@ -2435,7 +2440,8 @@
mPackageManager.setApplicationEnabledSetting(
packageData.packageName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
PackageInfo packageInfo = getPackageInfo(packageData);
- mShadowPackageManager.addPackage(packageInfo);
+ mShadowPackageManager.installPackage(packageInfo);
+ ShadowApplicationPackageManager.setPackageInfo(packageInfo);
mContext.sendBroadcast(getPackageAddedIntent(packageData));
// Run the backup looper because on the receiver we post MSG_SCHEDULE_BACKUP_PACKAGE
mShadowBackupLooper.runToEndOfTasks();
@@ -2848,4 +2854,29 @@
throw mException;
}
}
+
+ /**
+ * Extends {@link org.robolectric.shadows.ShadowApplicationPackageManager} to return the correct
+ * package in user-specific invocations.
+ */
+ @Implements(value = ApplicationPackageManager.class)
+ public static class ShadowApplicationPackageManager
+ extends org.robolectric.shadows.ShadowApplicationPackageManager {
+ private static PackageInfo sPackageInfo;
+
+ static void setPackageInfo(PackageInfo packageInfo) {
+ sPackageInfo = packageInfo;
+ }
+
+ @Override
+ protected PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId) {
+ return sPackageInfo;
+ }
+
+ /** Clear {@link #sPackageInfo}. */
+ @Resetter
+ public static void reset() {
+ sPackageInfo = null;
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
index 34edd9f..757a046 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
@@ -32,7 +32,6 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
-import android.os.RemoteException;
import android.test.AndroidTestCase;
import android.test.mock.MockPackageManager;
import android.view.inputmethod.InputMethodInfo;
@@ -40,7 +39,6 @@
import androidx.test.InstrumentationRegistry;
import com.android.internal.R;
-import com.android.internal.view.IInputMethodManager;
import org.junit.Before;
import org.junit.Test;
@@ -61,8 +59,8 @@
private @Mock
Resources mResources;
- private @Mock
- IInputMethodManager mIInputMethodManager;
+ @Mock
+ private OverlayPackagesProvider.Injector mInjector;
private @Mock
Context mTestContext;
private Resources mRealResources;
@@ -81,6 +79,7 @@
InstrumentationRegistry.getTargetContext().getCacheDir());
setSystemInputMethods();
+ setIsPerProfileModeEnabled(false);
setRequiredAppsManagedDevice();
setVendorRequiredAppsManagedDevice();
setDisallowedAppsManagedDevice();
@@ -95,7 +94,7 @@
setVendorDisallowedAppsManagedUser();
mRealResources = InstrumentationRegistry.getTargetContext().getResources();
- mHelper = new OverlayPackagesProvider(mTestContext, mIInputMethodManager);
+ mHelper = new OverlayPackagesProvider(mTestContext, mInjector);
}
@Test
@@ -165,6 +164,15 @@
}
@Test
+ public void testProfileOwnerImesAreRequiredForPerProfileImeMode() {
+ setSystemAppsWithLauncher("app.a", "app.b");
+ setSystemInputMethods("app.a");
+ setIsPerProfileModeEnabled(true);
+
+ verifyAppsAreNonRequired(ACTION_PROVISION_MANAGED_PROFILE, "app.b");
+ }
+
+ @Test
public void testManagedUserImesAreRequired() {
setSystemAppsWithLauncher("app.a", "app.b");
setSystemInputMethods("app.a");
@@ -333,11 +341,11 @@
InputMethodInfo inputMethodInfo = new InputMethodInfo(ri, false, null, null, 0, false);
inputMethods.add(inputMethodInfo);
}
- try {
- when(mIInputMethodManager.getInputMethodList()).thenReturn(inputMethods);
- } catch (RemoteException e) {
- fail(e.toString());
- }
+ when(mInjector.getInputMethodListAsUser(eq(TEST_USER_ID))).thenReturn(inputMethods);
+ }
+
+ private void setIsPerProfileModeEnabled(boolean enabled) {
+ when(mInjector.isPerProfileImeEnabled()).thenReturn(enabled);
}
private void setSystemAppsWithLauncher(String... apps) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index bdede33..7049b21 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -17,6 +17,7 @@
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
+import static com.android.server.hdmi.Constants.ADDR_TUNER_1;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF;
@@ -64,70 +65,71 @@
@Before
public void setUp() {
mHdmiControlService =
- new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
- @Override
- AudioManager getAudioManager() {
- return new AudioManager() {
- @Override
- public int getStreamVolume(int streamType) {
- switch (streamType) {
- case STREAM_MUSIC:
- return mMusicVolume;
- default:
- return 0;
- }
+ new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
+ @Override
+ AudioManager getAudioManager() {
+ return new AudioManager() {
+ @Override
+ public int getStreamVolume(int streamType) {
+ switch (streamType) {
+ case STREAM_MUSIC:
+ return mMusicVolume;
+ default:
+ return 0;
}
+ }
- @Override
- public boolean isStreamMute(int streamType) {
- switch (streamType) {
- case STREAM_MUSIC:
- return mMusicMute;
- default:
- return false;
- }
+ @Override
+ public boolean isStreamMute(int streamType) {
+ switch (streamType) {
+ case STREAM_MUSIC:
+ return mMusicMute;
+ default:
+ return false;
}
+ }
- @Override
- public int getStreamMaxVolume(int streamType) {
- switch (streamType) {
- case STREAM_MUSIC:
- return mMusicMaxVolume;
- default:
- return 100;
- }
+ @Override
+ public int getStreamMaxVolume(int streamType) {
+ switch (streamType) {
+ case STREAM_MUSIC:
+ return mMusicMaxVolume;
+ default:
+ return 100;
}
+ }
- @Override
- public void adjustStreamVolume(
- int streamType, int direction, int flags) {
- switch (streamType) {
- case STREAM_MUSIC:
- if (direction == AudioManager.ADJUST_UNMUTE) {
- mMusicMute = false;
- } else if (direction == AudioManager.ADJUST_MUTE) {
- mMusicMute = true;
- }
- default:
- }
+ @Override
+ public void adjustStreamVolume(
+ int streamType, int direction, int flags) {
+ switch (streamType) {
+ case STREAM_MUSIC:
+ if (direction == AudioManager.ADJUST_UNMUTE) {
+ mMusicMute = false;
+ } else if (direction == AudioManager.ADJUST_MUTE) {
+ mMusicMute = true;
+ }
+ break;
+ default:
}
+ }
- @Override
- public void setWiredDeviceConnectionState(
- int type, int state, String address, String name) {
- // Do nothing.
- }
- };
- }
+ @Override
+ public void setWiredDeviceConnectionState(
+ int type, int state, String address, String name) {
+ // Do nothing.
+ }
+ };
+ }
- @Override
- void wakeUp() {}
+ @Override
+ void wakeUp() {}
- @Override
- boolean isControlEnabled() {
- return true;
- }
- };
+ @Override
+ boolean isControlEnabled() {
+ return true;
+ }
+ };
mMyLooper = mTestLooper.getLooper();
mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService);
@@ -135,7 +137,7 @@
mHdmiControlService.setIoLooper(mMyLooper);
mNativeWrapper = new FakeNativeWrapper();
mHdmiCecController =
- HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
+ HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
@@ -170,11 +172,12 @@
@Test
public void handleGiveSystemAudioModeStatus_originalOff() throws Exception {
HdmiCecMessage expectedMessage =
- HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false);
+ HdmiCecMessageBuilder.buildReportSystemAudioMode(
+ ADDR_AUDIO_SYSTEM, ADDR_TV, false);
HdmiCecMessage messageGive =
HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(ADDR_TV, ADDR_AUDIO_SYSTEM);
assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
- .isTrue();
+ .isTrue();
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
}
@@ -190,9 +193,9 @@
mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(false);
assertThat(
- mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
- MESSAGE_REQUEST_SAD_LCPM))
- .isTrue();
+ mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
+ MESSAGE_REQUEST_SAD_LCPM))
+ .isTrue();
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
}
@@ -206,11 +209,11 @@
Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR,
Constants.ABORT_NOT_IN_CORRECT_MODE);
- mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(false);
+ mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(false);
assertThat(
- mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
- MESSAGE_REQUEST_SAD_LCPM))
- .isEqualTo(true);
+ mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
+ MESSAGE_REQUEST_SAD_LCPM))
+ .isEqualTo(true);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
}
@@ -224,11 +227,11 @@
Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR,
Constants.ABORT_UNABLE_TO_DETERMINE);
- mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(true);
+ mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true);
assertThat(
- mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
- MESSAGE_REQUEST_SAD_LCPM))
- .isEqualTo(true);
+ mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
+ MESSAGE_REQUEST_SAD_LCPM))
+ .isEqualTo(true);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
}
@@ -244,17 +247,17 @@
HdmiCecMessage expectedMessage =
HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false);
assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
- .isTrue();
+ .isTrue();
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
// Check if correctly turned on
mNativeWrapper.clearResultMessages();
expectedMessage =
- HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, true);
+ HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, true);
assertThat(mHdmiCecLocalDeviceAudioSystem.handleSetSystemAudioMode(messageSet)).isTrue();
mTestLooper.dispatchAll();
assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
- .isTrue();
+ .isTrue();
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
assertThat(mMusicMute).isFalse();
@@ -273,15 +276,15 @@
HdmiCecMessageBuilder.buildSetSystemAudioMode(
ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false);
assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(messageRequestOff))
- .isTrue();
+ .isTrue();
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
mNativeWrapper.clearResultMessages();
expectedMessage =
- HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false);
+ HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false);
assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
- .isTrue();
+ .isTrue();
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
assertThat(mMusicMute).isTrue();
@@ -292,7 +295,7 @@
mHdmiCecLocalDeviceAudioSystem.setAutoDeviceOff(false);
mHdmiCecLocalDeviceAudioSystem.setAutoTvOff(false);
// Set system audio control on first
- mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(true);
+ mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true);
// Check if standby correctly turns off the feature
mHdmiCecLocalDeviceAudioSystem.onStandby(false, STANDBY_SCREEN_OFF);
mTestLooper.dispatchAll();
@@ -309,9 +312,9 @@
mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, true);
assertThat(
- mHdmiCecLocalDeviceAudioSystem.getActions(
- SystemAudioInitiationActionFromAvr.class))
- .isNotEmpty();
+ mHdmiCecLocalDeviceAudioSystem.getActions(
+ SystemAudioInitiationActionFromAvr.class))
+ .isNotEmpty();
}
@Test
@@ -320,9 +323,9 @@
mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
Constants.NEVER_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, false);
assertThat(
- mHdmiCecLocalDeviceAudioSystem.getActions(
- SystemAudioInitiationActionFromAvr.class))
- .isEmpty();
+ mHdmiCecLocalDeviceAudioSystem.getActions(
+ SystemAudioInitiationActionFromAvr.class))
+ .isEmpty();
}
@Test
@@ -331,9 +334,9 @@
mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, false);
assertThat(
- mHdmiCecLocalDeviceAudioSystem.getActions(
- SystemAudioInitiationActionFromAvr.class))
- .isEmpty();
+ mHdmiCecLocalDeviceAudioSystem.getActions(
+ SystemAudioInitiationActionFromAvr.class))
+ .isEmpty();
}
@Test
@@ -342,9 +345,9 @@
mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, true);
assertThat(
- mHdmiCecLocalDeviceAudioSystem.getActions(
- SystemAudioInitiationActionFromAvr.class))
- .isNotEmpty();
+ mHdmiCecLocalDeviceAudioSystem.getActions(
+ SystemAudioInitiationActionFromAvr.class))
+ .isNotEmpty();
}
@Test
@@ -354,12 +357,12 @@
assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)).isTrue();
mTestLooper.dispatchAll();
assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource().equals(expectedActiveSource))
- .isTrue();
+ .isTrue();
}
@Test
public void terminateSystemAudioMode_systemAudioModeOff() throws Exception {
- mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(false);
+ mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(false);
assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isFalse();
mMusicMute = false;
HdmiCecMessage message =
@@ -373,7 +376,7 @@
@Test
public void terminateSystemAudioMode_systemAudioModeOn() throws Exception {
- mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(true);
+ mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true);
assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isTrue();
mMusicMute = false;
HdmiCecMessage expectedMessage =
@@ -458,7 +461,7 @@
assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)).isTrue();
mTestLooper.dispatchAll();
assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcInitiationActionFromAvr.class))
- .isNotEmpty();
+ .isNotEmpty();
}
@Test
@@ -473,7 +476,7 @@
assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message)).isTrue();
mTestLooper.dispatchAll();
assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcTerminationActionFromAvr.class))
- .isNotEmpty();
+ .isNotEmpty();
}
@Test
@@ -521,12 +524,37 @@
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
}
- @Test
- public void handleSetStreamPath_underCurrentDevice() {
- assertThat(mHdmiCecLocalDeviceAudioSystem.getLocalActivePath()).isEqualTo(0);
+ public void handleSystemAudioModeRequest_fromNonTV_tVNotSupport() {
HdmiCecMessage message =
- HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2100);
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleSetStreamPath(message)).isTrue();
- assertThat(mHdmiCecLocalDeviceAudioSystem.getLocalActivePath()).isEqualTo(1);
+ HdmiCecMessageBuilder.buildSystemAudioModeRequest(
+ ADDR_TUNER_1, ADDR_AUDIO_SYSTEM,
+ mAvrPhysicalAddress, true);
+ HdmiCecMessage expectedMessage =
+ HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TUNER_1,
+ Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST,
+ Constants.ABORT_REFUSED);
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)).isTrue();
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+ }
+
+ @Test
+ public void handleSystemAudioModeRequest_fromNonTV_tVSupport() {
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildSystemAudioModeRequest(
+ ADDR_TUNER_1, ADDR_AUDIO_SYSTEM,
+ mAvrPhysicalAddress, true);
+ HdmiCecMessage expectedMessage =
+ HdmiCecMessageBuilder.buildSetSystemAudioMode(
+ ADDR_AUDIO_SYSTEM, Constants.ADDR_BROADCAST, true);
+ mHdmiCecLocalDeviceAudioSystem.setTvSystemAudioModeSupport(true);
+
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)).isTrue();
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
new file mode 100644
index 0000000..76f638c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.hdmi;
+
+import static com.android.server.hdmi.Constants.ADDR_TV;
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Looper;
+import android.os.test.TestLooper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+
+@SmallTest
+@RunWith(JUnit4.class)
+/** Tests for {@link HdmiCecLocalDevicePlayback} class. */
+public class HdmiCecLocalDevicePlaybackTest {
+
+ private HdmiControlService mHdmiControlService;
+ private HdmiCecController mHdmiCecController;
+ private HdmiCecLocalDevicePlayback mHdmiCecLocalDevicePlayback;
+ private FakeNativeWrapper mNativeWrapper;
+ private Looper mMyLooper;
+ private TestLooper mTestLooper = new TestLooper();
+ private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+ private int mPlaybackPhysicalAddress;
+
+ @Before
+ public void setUp() {
+ mHdmiControlService =
+ new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
+ @Override
+ void wakeUp() {
+ }
+
+ @Override
+ boolean isControlEnabled() {
+ return true;
+ }
+ };
+
+ mMyLooper = mTestLooper.getLooper();
+ mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService);
+ mHdmiCecLocalDevicePlayback.init();
+ mHdmiControlService.setIoLooper(mMyLooper);
+ mNativeWrapper = new FakeNativeWrapper();
+ mHdmiCecController =
+ HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
+ mHdmiControlService.setCecController(mHdmiCecController);
+ mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
+ mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
+ mLocalDevices.add(mHdmiCecLocalDevicePlayback);
+ mHdmiControlService.initPortInfo();
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mTestLooper.dispatchAll();
+ mNativeWrapper.clearResultMessages();
+ mPlaybackPhysicalAddress = 0x2000;
+ mNativeWrapper.setPhysicalAddress(mPlaybackPhysicalAddress);
+ }
+
+ @Test
+ public void handleSetStreamPath_underCurrentDevice() {
+ assertThat(mHdmiCecLocalDevicePlayback.getLocalActivePath()).isEqualTo(0);
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2100);
+ assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.getLocalActivePath()).isEqualTo(1);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
index c7809d3..ef974f2 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
@@ -18,6 +18,7 @@
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
import static com.android.server.hdmi.Constants.ADDR_TV;
+
import static com.google.common.truth.Truth.assertThat;
import android.hardware.hdmi.HdmiDeviceInfo;
@@ -51,6 +52,14 @@
assertThat(message).isEqualTo(buildMessage("05:A4:06:01"));
}
+ @Test
+ public void buildRoutingInformation() {
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildRoutingInformation(
+ ADDR_AUDIO_SYSTEM, 0x2100);
+ assertThat(message).isEqualTo(buildMessage("5F:81:21:00"));
+ }
+
/**
* Build a CEC message from a hex byte string with bytes separated by {@code :}.
*
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 2820836..dcaa499 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -239,6 +239,30 @@
"android.telecom.event.HANDOVER_FAILED";
public static class Details {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = { "DIRECTION_" },
+ value = {DIRECTION_UNKNOWN, DIRECTION_INCOMING, DIRECTION_OUTGOING})
+ public @interface CallDirection {}
+
+ /**
+ * Indicates that the call is neither and incoming nor an outgoing call. This can be the
+ * case for calls reported directly by a {@link ConnectionService} in special cases such as
+ * call handovers.
+ */
+ public static final int DIRECTION_UNKNOWN = -1;
+
+ /**
+ * Indicates that the call is an incoming call.
+ */
+ public static final int DIRECTION_INCOMING = 0;
+
+ /**
+ * Indicates that the call is an outgoing call.
+ */
+ public static final int DIRECTION_OUTGOING = 1;
+
/** Call can currently be put on hold or unheld. */
public static final int CAPABILITY_HOLD = 0x00000001;
@@ -519,6 +543,7 @@
private final Bundle mIntentExtras;
private final long mCreationTimeMillis;
private final CallIdentification mCallIdentification;
+ private final @CallDirection int mCallDirection;
/**
* Whether the supplied capabilities supports the specified capability.
@@ -838,6 +863,14 @@
return mCallIdentification;
}
+ /**
+ * Indicates whether the call is an incoming or outgoing call.
+ * @return The call's direction.
+ */
+ public @CallDirection int getCallDirection() {
+ return mCallDirection;
+ }
+
@Override
public boolean equals(Object o) {
if (o instanceof Details) {
@@ -859,7 +892,8 @@
areBundlesEqual(mExtras, d.mExtras) &&
areBundlesEqual(mIntentExtras, d.mIntentExtras) &&
Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis) &&
- Objects.equals(mCallIdentification, d.mCallIdentification);
+ Objects.equals(mCallIdentification, d.mCallIdentification) &&
+ Objects.equals(mCallDirection, d.mCallDirection);
}
return false;
}
@@ -881,7 +915,8 @@
mExtras,
mIntentExtras,
mCreationTimeMillis,
- mCallIdentification);
+ mCallIdentification,
+ mCallDirection);
}
/** {@hide} */
@@ -902,7 +937,8 @@
Bundle extras,
Bundle intentExtras,
long creationTimeMillis,
- CallIdentification callIdentification) {
+ CallIdentification callIdentification,
+ int callDirection) {
mTelecomCallId = telecomCallId;
mHandle = handle;
mHandlePresentation = handlePresentation;
@@ -920,6 +956,7 @@
mIntentExtras = intentExtras;
mCreationTimeMillis = creationTimeMillis;
mCallIdentification = callIdentification;
+ mCallDirection = callDirection;
}
/** {@hide} */
@@ -941,7 +978,8 @@
parcelableCall.getExtras(),
parcelableCall.getIntentExtras(),
parcelableCall.getCreationTimeMillis(),
- parcelableCall.getCallIdentification());
+ parcelableCall.getCallIdentification(),
+ parcelableCall.getCallDirection());
}
@Override
diff --git a/telecomm/java/android/telecom/CallIdentification.java b/telecomm/java/android/telecom/CallIdentification.java
index 97af06c..87834fd 100644
--- a/telecomm/java/android/telecom/CallIdentification.java
+++ b/telecomm/java/android/telecom/CallIdentification.java
@@ -250,8 +250,8 @@
mDetails = details;
mPhoto = photo;
mNuisanceConfidence = nuisanceConfidence;
- mCallScreeningAppName = callScreeningPackageName;
- mCallScreeningPackageName = callScreeningAppName;
+ mCallScreeningAppName = callScreeningAppName;
+ mCallScreeningPackageName = callScreeningPackageName;
}
private String mName;
@@ -430,4 +430,22 @@
return Objects.hash(mName, mDescription, mDetails, mPhoto, mNuisanceConfidence,
mCallScreeningAppName, mCallScreeningPackageName);
}
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[CallId mName=");
+ sb.append(Log.pii(mName));
+ sb.append(", mDesc=");
+ sb.append(mDescription);
+ sb.append(", mDet=");
+ sb.append(mDetails);
+ sb.append(", conf=");
+ sb.append(mNuisanceConfidence);
+ sb.append(", appName=");
+ sb.append(mCallScreeningAppName);
+ sb.append(", pkgName=");
+ sb.append(mCallScreeningPackageName);
+ return sb.toString();
+ }
}
diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index be96b3c..826ad82 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -21,6 +21,7 @@
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
+import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -33,8 +34,9 @@
/**
* This service can be implemented by the default dialer (see
- * {@link TelecomManager#getDefaultDialerPackage()}) to allow or disallow incoming calls before
- * they are shown to a user.
+ * {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow
+ * incoming calls before they are shown to a user. This service can also provide
+ * {@link CallIdentification} information for calls.
* <p>
* Below is an example manifest registration for a {@code CallScreeningService}.
* <pre>
@@ -56,6 +58,34 @@
* information about a {@link Call.Details call} which will be shown to the user in the
* Dialer app.</li>
* </ol>
+ * <p>
+ * <h2>Becoming the {@link CallScreeningService}</h2>
+ * Telecom will bind to a single app chosen by the user which implements the
+ * {@link CallScreeningService} API when there are new incoming and outgoing calls.
+ * <p>
+ * The code snippet below illustrates how your app can request that it fills the call screening
+ * role.
+ * <pre>
+ * {@code
+ * private static final int REQUEST_ID = 1;
+ *
+ * public void requestRole() {
+ * RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE);
+ * Intent intent = roleManager.createRequestRoleIntent("android.app.role.CALL_SCREENING_APP");
+ * startActivityForResult(intent, REQUEST_ID);
+ * }
+ *
+ * @Override
+ * public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ * if (requestCode == REQUEST_ID) {
+ * if (resultCode == android.app.Activity.RESULT_OK) {
+ * // Your app is now the call screening app
+ * } else {
+ * // Your app is not the call screening app
+ * }
+ * }
+ * }
+ * </pre>
*/
public abstract class CallScreeningService extends Service {
/**
@@ -222,30 +252,46 @@
}
/**
- * Called when a new incoming call is added.
- * {@link CallScreeningService#respondToCall(Call.Details, CallScreeningService.CallResponse)}
- * should be called to allow or disallow the call.
+ * Called when a new incoming or outgoing call is added which is not in the user's contact list.
+ * <p>
+ * A {@link CallScreeningService} must indicate whether an incoming call is allowed or not by
+ * calling
+ * {@link CallScreeningService#respondToCall(Call.Details, CallScreeningService.CallResponse)}.
+ * Your app can tell if a call is an incoming call by checking to see if
+ * {@link Call.Details#getCallDirection()} is {@link Call.Details#DIRECTION_INCOMING}.
+ * <p>
+ * For incoming or outgoing calls, the {@link CallScreeningService} can call
+ * {@link #provideCallIdentification(Call.Details, CallIdentification)} in order to provide
+ * {@link CallIdentification} for the call.
* <p>
* Note: The {@link Call.Details} instance provided to a call screening service will only have
* the following properties set. The rest of the {@link Call.Details} properties will be set to
* their default value or {@code null}.
* <ul>
- * <li>{@link Call.Details#getState()}</li>
+ * <li>{@link Call.Details#getCallDirection()}</li>
* <li>{@link Call.Details#getConnectTimeMillis()}</li>
* <li>{@link Call.Details#getCreationTimeMillis()}</li>
* <li>{@link Call.Details#getHandle()}</li>
* <li>{@link Call.Details#getHandlePresentation()}</li>
* </ul>
+ * <p>
+ * Only calls where the {@link Call.Details#getHandle() handle} {@link Uri#getScheme() scheme}
+ * is {@link PhoneAccount#SCHEME_TEL} are passed for call
+ * screening. Further, only calls which are not in the user's contacts are passed for
+ * screening. For outgoing calls, no post-dial digits are passed.
*
- * @param callDetails Information about a new incoming call, see {@link Call.Details}.
+ * @param callDetails Information about a new call, see {@link Call.Details}.
*/
public abstract void onScreenCall(@NonNull Call.Details callDetails);
/**
- * Responds to the given call, either allowing it or disallowing it.
+ * Responds to the given incoming call, either allowing it or disallowing it.
* <p>
* The {@link CallScreeningService} calls this method to inform the system whether the call
* should be silently blocked or not.
+ * <p>
+ * Calls to this method are ignored unless the {@link Call.Details#getCallDirection()} is
+ * {@link Call.Details#DIRECTION_INCOMING}.
*
* @param callDetails The call to allow.
* <p>
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 1aeeca7..f5f0af7 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -40,11 +40,30 @@
import java.util.List;
/**
- * This service is implemented by any app that wishes to provide the user-interface for managing
- * phone calls. Telecom binds to this service while there exists a live (active or incoming) call,
- * and uses it to notify the in-call app of any live and recently disconnected calls. An app must
- * first be set as the default phone app (See {@link TelecomManager#getDefaultDialerPackage()})
- * before the telecom service will bind to its {@code InCallService} implementation.
+ * This service is implemented by an app that wishes to provide functionality for managing
+ * phone calls.
+ * <p>
+ * There are three types of apps which Telecom can bind to when there exists a live (active or
+ * incoming) call:
+ * <ol>
+ * <li>Default Dialer/Phone app - the default dialer/phone app is one which provides the
+ * in-call user interface while the device is in a call. A device is bundled with a system
+ * provided default dialer/phone app. The user may choose a single app to take over this role
+ * from the system app.</li>
+ * <li>Default Car-mode Dialer/Phone app - the default car-mode dialer/phone app is one which
+ * provides the in-call user interface while the device is in a call and the device is in car
+ * mode. The user may choose a single app to fill this role.</li>
+ * <li>Call Companion app - a call companion app is one which provides no user interface itself,
+ * but exposes call information to another display surface, such as a wearable device. The
+ * user may choose multiple apps to fill this role.</li>
+ * </ol>
+ * <p>
+ * Apps which wish to fulfill one of the above roles use the {@link android.app.role.RoleManager}
+ * to request that they fill the desired role.
+ *
+ * <h2>Becoming the Default Phone App</h2>
+ * An app filling the role of the default phone app provides a user interface while the device is in
+ * a call, and the device is not in car mode.
* <p>
* Below is an example manifest registration for an {@code InCallService}. The meta-data
* {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} indicates that this particular
@@ -82,12 +101,34 @@
* }
* </pre>
* <p>
- * When a user installs your application and runs it for the first time, you should prompt the user
- * to see if they would like your application to be the new default phone app. See the
- * {@link TelecomManager#ACTION_CHANGE_DEFAULT_DIALER} intent documentation for more information on
- * how to do this.
+ * When a user installs your application and runs it for the first time, you should use the
+ * {@link android.app.role.RoleManager} to prompt the user to see if they would like your app to
+ * be the new default phone app.
+ * <p id="requestRole">
+ * The code below shows how your app can request to become the default phone/dialer app:
+ * <pre>
+ * {@code
+ * private static final int REQUEST_ID = 1;
+ *
+ * public void requestRole() {
+ * RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE);
+ * Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER);
+ * startActivityForResult(intent, REQUEST_ID);
+ * }
+ *
+ * @Override
+ * public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ * if (requestCode == REQUEST_ID) {
+ * if (resultCode == android.app.Activity.RESULT_OK) {
+ * // Your app is now the default dialer app
+ * } else {
+ * // Your app is not the default dialer app
+ * }
+ * }
+ * }
+ * </pre>
* <p id="incomingCallNotification">
- * <h2>Showing the Incoming Call Notification</h2>
+ * <h3>Showing the Incoming Call Notification</h3>
* When your app receives a new incoming call via {@link InCallService#onCallAdded(Call)}, it is
* responsible for displaying an incoming call UI for the incoming call. It should do this using
* {@link android.app.NotificationManager} APIs to post a new incoming call notification.
@@ -121,7 +162,7 @@
* heads-up notification if the user is actively using the phone. When the user is not using the
* phone, your full-screen incoming call UI is used instead.
* For example:
- * <pre><code>
+ * <pre><code>{@code
* // Create an intent which triggers your fullscreen incoming call user interface.
* Intent intent = new Intent(Intent.ACTION_MAIN, null);
* intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -151,7 +192,49 @@
* NotificationManager notificationManager = mContext.getSystemService(
* NotificationManager.class);
* notificationManager.notify(YOUR_CHANNEL_ID, YOUR_TAG, YOUR_ID, builder.build());
- * </code></pre>
+ * }</pre>
+ * <p>
+ * <h2>Becoming the Default Car-mode Phone App</h2>
+ * An app filling the role of the default car-mode dialer/phone app provides a user interface while
+ * the device is in a call, and in car mode. See
+ * {@link android.app.UiModeManager#ACTION_ENTER_CAR_MODE} for more information about car mode.
+ * When the device is in car mode, Telecom binds to the default car-mode dialer/phone app instead
+ * of the usual dialer/phone app.
+ * <p>
+ * Similar to the requirements for becoming the default dialer/phone app, your app must declare a
+ * manifest entry for its {@link InCallService} implementation. Your manifest entry should ensure
+ * the following conditions are met:
+ * <ul>
+ * <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} metadata.</li>
+ * <li>Set the {@link TelecomManager#METADATA_IN_CALL_SERVICE_CAR_MODE_UI} metadata to
+ * {@code true}<li>
+ * <li>Your app must request the permission
+ * {@link android.Manifest.permission.CALL_COMPANION_APP}.</li>
+ * </ul>
+ * <p>
+ * Your app should request to fill the role {@code android.app.role.CAR_MODE_DIALER_APP} in order to
+ * become the default (see <a href="#requestRole">above</a> for how to request your app fills this
+ * role).
+ *
+ * <h2>Becoming a Call Companion App</h2>
+ * An app which fills the companion app role does not directly provide a user interface while the
+ * device is in a call. Instead, it is typically used to relay information about calls to another
+ * display surface, such as a wearable device.
+ * <p>
+ * Similar to the requirements for becoming the default dialer/phone app, your app must declare a
+ * manifest entry for its {@link InCallService} implementation. Your manifest entry should
+ * ensure the following conditions are met:
+ * <ul>
+ * <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} metadata.</li>
+ * <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_CAR_MODE_UI}
+ * metadata.</li>
+ * <li>Your app must request the permission
+ * {@link android.Manifest.permission.CALL_COMPANION_APP}.</li>
+ * </ul>
+ * <p>
+ * Your app should request to fill the role {@code android.app.role.CALL_COMPANION_APP} in order to
+ * become a call companion app (see <a href="#requestRole">above</a> for how to request your app
+ * fills this role).
*/
public abstract class InCallService extends Service {
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index 911786e..f7dec83 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -24,6 +24,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.telecom.Call.Details.CallDirection;
import java.util.ArrayList;
import java.util.Collections;
@@ -64,6 +65,7 @@
private final Bundle mExtras;
private final long mCreationTimeMillis;
private final CallIdentification mCallIdentification;
+ private final int mCallDirection;
public ParcelableCall(
String id,
@@ -92,7 +94,8 @@
Bundle intentExtras,
Bundle extras,
long creationTimeMillis,
- CallIdentification callIdentification) {
+ CallIdentification callIdentification,
+ int callDirection) {
mId = id;
mState = state;
mDisconnectCause = disconnectCause;
@@ -120,6 +123,7 @@
mExtras = extras;
mCreationTimeMillis = creationTimeMillis;
mCallIdentification = callIdentification;
+ mCallDirection = callDirection;
}
/** The unique ID of the call. */
@@ -318,6 +322,13 @@
return mCallIdentification;
}
+ /**
+ * Indicates whether the call is an incoming or outgoing call.
+ */
+ public @CallDirection int getCallDirection() {
+ return mCallDirection;
+ }
+
/** Responsible for creating ParcelableCall objects for deserialized Parcels. */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public static final Parcelable.Creator<ParcelableCall> CREATOR =
@@ -356,6 +367,7 @@
ParcelableRttCall rttCall = source.readParcelable(classLoader);
long creationTimeMillis = source.readLong();
CallIdentification callIdentification = source.readParcelable(classLoader);
+ int callDirection = source.readInt();
return new ParcelableCall(
id,
state,
@@ -383,7 +395,8 @@
intentExtras,
extras,
creationTimeMillis,
- callIdentification);
+ callIdentification,
+ callDirection);
}
@Override
@@ -429,6 +442,7 @@
destination.writeParcelable(mRttCall, 0);
destination.writeLong(mCreationTimeMillis);
destination.writeParcelable(mCallIdentification, 0);
+ destination.writeInt(mCallDirection);
}
@Override
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index a1e8b19..51d5ab1 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -174,6 +174,16 @@
private boolean mIsGroupDisabled = false;
/**
+ * Profile class, PROFILE_CLASS_TESTING, PROFILE_CLASS_OPERATIONAL
+ * PROFILE_CLASS_PROVISIONING, or PROFILE_CLASS_UNSET.
+ * A profile on the eUICC can be defined as test, operational, provisioning, or unset.
+ * The profile class will be populated from the profile metadata if present. Otherwise,
+ * the profile class defaults to unset if there is no profile metadata or the subscription
+ * is not on an eUICC ({@link #isEmbedded} returns false).
+ */
+ private int mProfileClass;
+
+ /**
* @hide
*/
public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
@@ -182,7 +192,8 @@
@Nullable UiccAccessRule[] accessRules, String cardString) {
this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString,
- false, null, true, TelephonyManager.UNKNOWN_CARRIER_ID);
+ false, null, true, TelephonyManager.UNKNOWN_CARRIER_ID,
+ SubscriptionManager.PROFILE_CLASS_DEFAULT);
}
/**
@@ -192,10 +203,10 @@
CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@Nullable UiccAccessRule[] accessRules, String cardString, boolean isOpportunistic,
- @Nullable String groupUUID, boolean isMetered, int carrierId) {
+ @Nullable String groupUUID, boolean isMetered, int carrierId, int profileClass) {
this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, -1,
- isOpportunistic, groupUUID, isMetered, false, carrierId);
+ isOpportunistic, groupUUID, isMetered, false, carrierId, profileClass);
}
/**
@@ -206,7 +217,7 @@
Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@Nullable UiccAccessRule[] accessRules, String cardString, int cardId,
boolean isOpportunistic, @Nullable String groupUUID, boolean isMetered,
- boolean isGroupDisabled, int carrierid) {
+ boolean isGroupDisabled, int carrierid, int profileClass) {
this.mId = id;
this.mIccId = iccId;
this.mSimSlotIndex = simSlotIndex;
@@ -229,6 +240,7 @@
this.mIsMetered = isMetered;
this.mIsGroupDisabled = isGroupDisabled;
this.mCarrierId = carrierid;
+ this.mProfileClass = profileClass;
}
@@ -466,6 +478,15 @@
}
/**
+ * @return the profile class of this subscription.
+ * @hide
+ */
+ @SystemApi
+ public @SubscriptionManager.ProfileClass int getProfileClass() {
+ return this.mProfileClass;
+ }
+
+ /**
* Checks whether the app with the given context is authorized to manage this subscription
* according to its metadata. Only supported for embedded subscriptions (if {@link #isEmbedded}
* returns true).
@@ -590,11 +611,12 @@
boolean isMetered = source.readBoolean();
boolean isGroupDisabled = source.readBoolean();
int carrierid = source.readInt();
+ int profileClass = source.readInt();
return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso,
isEmbedded, accessRules, cardString, cardId, isOpportunistic, groupUUID,
- isMetered, isGroupDisabled, carrierid);
+ isMetered, isGroupDisabled, carrierid, profileClass);
}
@Override
@@ -627,6 +649,7 @@
dest.writeBoolean(mIsMetered);
dest.writeBoolean(mIsGroupDisabled);
dest.writeInt(mCarrierId);
+ dest.writeInt(mProfileClass);
}
@Override
@@ -662,7 +685,8 @@
+ " accessRules " + Arrays.toString(mAccessRules)
+ " cardString=" + cardStringToPrint + " cardId=" + mCardId
+ " isOpportunistic " + mIsOpportunistic + " mGroupUUID=" + mGroupUUID
- + " isMetered=" + mIsMetered + " mIsGroupDisabled=" + mIsGroupDisabled + "}";
+ + " isMetered=" + mIsMetered + " mIsGroupDisabled=" + mIsGroupDisabled
+ + " profileClass=" + mProfileClass + "}";
}
@Override
@@ -670,7 +694,7 @@
return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded,
mIsOpportunistic, mGroupUUID, mIsMetered, mIccId, mNumber, mMcc, mMnc,
mCountryIso, mCardString, mCardId, mDisplayName, mCarrierName, mAccessRules,
- mIsGroupDisabled, mCarrierId);
+ mIsGroupDisabled, mCarrierId, mProfileClass);
}
@Override
@@ -705,6 +729,7 @@
&& Objects.equals(mCardId, toCompare.mCardId)
&& TextUtils.equals(mDisplayName, toCompare.mDisplayName)
&& TextUtils.equals(mCarrierName, toCompare.mCarrierName)
- && Arrays.equals(mAccessRules, toCompare.mAccessRules);
+ && Arrays.equals(mAccessRules, toCompare.mAccessRules)
+ && mProfileClass == toCompare.mProfileClass;
}
}
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 2c712a1..34f7abd 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -22,6 +22,7 @@
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.DurationMillisLong;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -62,6 +63,8 @@
import com.android.internal.telephony.ITelephonyRegistry;
import com.android.internal.telephony.PhoneConstants;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -599,6 +602,73 @@
* @hide
*/
public static final String IS_METERED = "is_metered";
+
+ /**
+ * TelephonyProvider column name for the profile class of a subscription
+ * Only present if {@link #IS_EMBEDDED} is 1.
+ * <P>Type: INTEGER (int)</P>
+ * @hide
+ */
+ public static final String PROFILE_CLASS = "profile_class";
+
+ /**
+ * Profile class of the subscription
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "PROFILE_CLASS_" }, value = {
+ PROFILE_CLASS_TESTING,
+ PROFILE_CLASS_PROVISIONING,
+ PROFILE_CLASS_OPERATIONAL,
+ PROFILE_CLASS_UNSET,
+ PROFILE_CLASS_DEFAULT
+ })
+ public @interface ProfileClass {}
+
+ /**
+ * A testing profile can be pre-loaded or downloaded onto
+ * the eUICC and provides connectivity to test equipment
+ * for the purpose of testing the device and the eUICC. It
+ * is not intended to store any operator credentials.
+ * @hide
+ */
+ @SystemApi
+ public static final int PROFILE_CLASS_TESTING = 0;
+
+ /**
+ * A provisioning profile is pre-loaded onto the eUICC and
+ * provides connectivity to a mobile network solely for the
+ * purpose of provisioning profiles.
+ * @hide
+ */
+ @SystemApi
+ public static final int PROFILE_CLASS_PROVISIONING = 1;
+
+ /**
+ * An operational profile can be pre-loaded or downloaded
+ * onto the eUICC and provides services provided by the
+ * operator.
+ * @hide
+ */
+ @SystemApi
+ public static final int PROFILE_CLASS_OPERATIONAL = 2;
+
+ /**
+ * The profile class is unset. This occurs when profile class
+ * info is not available. The subscription either has no profile
+ * metadata or the profile metadata did not encode profile class.
+ * @hide
+ */
+ @SystemApi
+ public static final int PROFILE_CLASS_UNSET = -1;
+
+ /**
+ * Default profile class
+ * @hide
+ */
+ @SystemApi
+ public static final int PROFILE_CLASS_DEFAULT = PROFILE_CLASS_UNSET;
+
/**
* Broadcast Action: The user has changed one of the default subs related to
* data, phone calls, or sms</p>
@@ -1102,17 +1172,33 @@
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
- List<SubscriptionInfo> result = null;
+ return getActiveSubscriptionInfoList(false);
+ }
+
+ /**
+ * This is similar to {@link #getActiveSubscriptionInfoList()}, but if userVisibleOnly
+ * is true, it will filter out the hidden subscriptions.
+ *
+ * @hide
+ */
+ public List<SubscriptionInfo> getActiveSubscriptionInfoList(boolean userVisibleOnly) {
+ List<SubscriptionInfo> activeList = null;
try {
ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
if (iSub != null) {
- result = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName());
+ activeList = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName());
}
} catch (RemoteException ex) {
// ignore it
}
- return result;
+
+ if (!userVisibleOnly || activeList == null) {
+ return activeList;
+ } else {
+ return activeList.stream().filter(subInfo -> !shouldHideSubscription(subInfo))
+ .collect(Collectors.toList());
+ }
}
/**
diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java
index a94b163..a5f56bb 100644
--- a/telephony/java/android/telephony/emergency/EmergencyNumber.java
+++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java
@@ -232,18 +232,21 @@
private final String mCountryIso;
private final String mMnc;
private final int mEmergencyServiceCategoryBitmask;
+ private final List<String> mEmergencyUrns;
private final int mEmergencyNumberSourceBitmask;
private final int mEmergencyCallRouting;
/** @hide */
public EmergencyNumber(@NonNull String number, @NonNull String countryIso, @NonNull String mnc,
@EmergencyServiceCategories int emergencyServiceCategories,
+ @NonNull List<String> emergencyUrns,
@EmergencyNumberSources int emergencyNumberSources,
@EmergencyCallRouting int emergencyCallRouting) {
this.mNumber = number;
this.mCountryIso = countryIso;
this.mMnc = mnc;
this.mEmergencyServiceCategoryBitmask = emergencyServiceCategories;
+ this.mEmergencyUrns = emergencyUrns;
this.mEmergencyNumberSourceBitmask = emergencyNumberSources;
this.mEmergencyCallRouting = emergencyCallRouting;
}
@@ -254,6 +257,7 @@
mCountryIso = source.readString();
mMnc = source.readString();
mEmergencyServiceCategoryBitmask = source.readInt();
+ mEmergencyUrns = source.createStringArrayList();
mEmergencyNumberSourceBitmask = source.readInt();
mEmergencyCallRouting = source.readInt();
}
@@ -265,6 +269,7 @@
dest.writeString(mCountryIso);
dest.writeString(mMnc);
dest.writeInt(mEmergencyServiceCategoryBitmask);
+ dest.writeStringList(mEmergencyUrns);
dest.writeInt(mEmergencyNumberSourceBitmask);
dest.writeInt(mEmergencyCallRouting);
}
@@ -345,6 +350,22 @@
}
/**
+ * Returns the list of emergency Uniform Resources Names (URN) of the emergency number.
+ *
+ * For example, {@code urn:service:sos} is the generic URN for contacting emergency services
+ * of all type.
+ *
+ * Reference: 3gpp 24.503, Section 5.1.6.8.1 - General;
+ * RFC 5031
+ *
+ * @return list of emergency Uniform Resources Names (URN) or an empty list if the emergency
+ * number does not have a specified emergency Uniform Resource Name.
+ */
+ public @NonNull List<String> getEmergencyUrns() {
+ return mEmergencyUrns;
+ }
+
+ /**
* Checks if the emergency service category is unspecified for the emergency number
* {@link #EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED}.
*
@@ -434,6 +455,7 @@
return "EmergencyNumber:" + "Number-" + mNumber + "|CountryIso-" + mCountryIso
+ "|Mnc-" + mMnc
+ "|ServiceCategories-" + Integer.toBinaryString(mEmergencyServiceCategoryBitmask)
+ + "|Urns-" + mEmergencyUrns
+ "|Sources-" + Integer.toBinaryString(mEmergencyNumberSourceBitmask)
+ "|Routing-" + Integer.toBinaryString(mEmergencyCallRouting);
}
@@ -448,6 +470,7 @@
&& mCountryIso.equals(other.mCountryIso)
&& mMnc.equals(other.mMnc)
&& mEmergencyServiceCategoryBitmask == other.mEmergencyServiceCategoryBitmask
+ && mEmergencyUrns.equals(other.mEmergencyUrns)
&& mEmergencyNumberSourceBitmask == other.mEmergencyNumberSourceBitmask
&& mEmergencyCallRouting == other.mEmergencyCallRouting;
}
@@ -455,7 +478,7 @@
@Override
public int hashCode() {
return Objects.hash(mNumber, mCountryIso, mMnc, mEmergencyServiceCategoryBitmask,
- mEmergencyNumberSourceBitmask, mEmergencyCallRouting);
+ mEmergencyUrns, mEmergencyNumberSourceBitmask, mEmergencyCallRouting);
}
/**
@@ -584,6 +607,9 @@
!= second.getEmergencyServiceCategoryBitmask()) {
return false;
}
+ if (first.getEmergencyUrns().equals(second.getEmergencyUrns())) {
+ return false;
+ }
if (first.getEmergencyCallRouting() != second.getEmergencyCallRouting()) {
return false;
}
@@ -605,6 +631,7 @@
if (areSameEmergencyNumbers(first, second)) {
return new EmergencyNumber(first.getNumber(), first.getCountryIso(), first.getMnc(),
first.getEmergencyServiceCategoryBitmask(),
+ first.getEmergencyUrns(),
first.getEmergencyNumberSourceBitmask()
| second.getEmergencyNumberSourceBitmask(),
first.getEmergencyCallRouting());
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 6326cc6..cc9befe 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -616,14 +616,13 @@
/**
* Update the nickname for the given subscription.
*
- * <p>Requires that the calling app has the
- * {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission. This is for
- * internal system use only.
+ * <p>Requires that the calling app has carrier privileges according to the metadata of the
+ * profile to be updated, or the
+ * {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
*
* @param subscriptionId the ID of the subscription to update.
* @param nickname the new nickname to apply.
* @param callbackIntent a PendingIntent to launch when the operation completes.
- * @hide
*/
@RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
public void updateSubscriptionNickname(
@@ -634,7 +633,7 @@
}
try {
getIEuiccController().updateSubscriptionNickname(
- subscriptionId, nickname, callbackIntent);
+ subscriptionId, nickname, mContext.getOpPackageName(), callbackIntent);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index 9c8d078..525a96a 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -33,6 +33,8 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
/**
* Parcelable object to handle IMS call profile.
@@ -323,6 +325,15 @@
EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED;
/**
+ * The emergency Uniform Resource Names (URN), only valid if {@link #getServiceType} returns
+ * {@link #SERVICE_TYPE_EMERGENCY}.
+ *
+ * Reference: 3gpp 24.503, Section 5.1.6.8.1 - General;
+ * 3gpp 22.101, Section 10 - Emergency Calls.
+ */
+ private List<String> mEmergencyUrns = new ArrayList<>();
+
+ /**
* The emergency call routing, only valid if {@link #getServiceType} returns
* {@link #SERVICE_TYPE_EMERGENCY}
*
@@ -524,6 +535,7 @@
+ ", restrictCause=" + mRestrictCause
+ ", mediaProfile=" + mMediaProfile.toString()
+ ", emergencyServiceCategories=" + mEmergencyCallRouting
+ + ", emergencyUrns=" + mEmergencyUrns
+ ", emergencyCallRouting=" + mEmergencyCallRouting + " }";
}
@@ -540,6 +552,7 @@
out.writeBundle(filteredExtras);
out.writeParcelable(mMediaProfile, 0);
out.writeInt(mEmergencyServiceCategories);
+ out.writeStringList(mEmergencyUrns);
out.writeInt(mEmergencyCallRouting);
}
@@ -549,6 +562,7 @@
mCallExtras = in.readBundle();
mMediaProfile = in.readParcelable(ImsStreamMediaProfile.class.getClassLoader());
mEmergencyServiceCategories = in.readInt();
+ mEmergencyUrns = in.createStringArrayList();
mEmergencyCallRouting = in.readInt();
}
@@ -760,20 +774,21 @@
}
/**
- * Set the emergency service categories and emergency call routing. The set value is valid
+ * Set the emergency number information. The set value is valid
* only if {@link #getServiceType} returns {@link #SERVICE_TYPE_EMERGENCY}
*
* Reference: 3gpp 23.167, Section 6 - Functional description;
+ * 3gpp 24.503, Section 5.1.6.8.1 - General;
* 3gpp 22.101, Section 10 - Emergency Calls.
*
* @hide
*/
public void setEmergencyCallInfo(EmergencyNumber num) {
setEmergencyServiceCategories(num.getEmergencyServiceCategoryBitmask());
+ setEmergencyUrns(num.getEmergencyUrns());
setEmergencyCallRouting(num.getEmergencyCallRouting());
}
-
/**
* Set the emergency service categories. The set value is valid only if
* {@link #getServiceType} returns {@link #SERVICE_TYPE_EMERGENCY}
@@ -800,6 +815,18 @@
}
/**
+ * Set the emergency Uniform Resource Names (URN), only valid if {@link #getServiceType}
+ * returns {@link #SERVICE_TYPE_EMERGENCY}.
+ *
+ * Reference: 3gpp 24.503, Section 5.1.6.8.1 - General;
+ * 3gpp 22.101, Section 10 - Emergency Calls.
+ */
+ @VisibleForTesting
+ public void setEmergencyUrns(List<String> emergencyUrns) {
+ mEmergencyUrns = emergencyUrns;
+ }
+
+ /**
* Set the emergency call routing, only valid if {@link #getServiceType} returns
* {@link #SERVICE_TYPE_EMERGENCY}
*
@@ -841,6 +868,17 @@
}
/**
+ * Get the emergency Uniform Resource Names (URN), only valid if {@link #getServiceType}
+ * returns {@link #SERVICE_TYPE_EMERGENCY}.
+ *
+ * Reference: 3gpp 24.503, Section 5.1.6.8.1 - General;
+ * 3gpp 22.101, Section 10 - Emergency Calls.
+ */
+ public List<String> getEmergencyUrns() {
+ return mEmergencyUrns;
+ }
+
+ /**
* Get the emergency call routing, only valid if {@link #getServiceType} returns
* {@link #SERVICE_TYPE_EMERGENCY}
*
diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
index 870a689..dd40d56 100644
--- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
@@ -39,7 +39,7 @@
oneway void switchToSubscription(int subscriptionId, String callingPackage,
in PendingIntent callbackIntent);
oneway void updateSubscriptionNickname(int subscriptionId, String nickname,
- in PendingIntent callbackIntent);
+ String callingPackage, in PendingIntent callbackIntent);
oneway void eraseSubscriptions(in PendingIntent callbackIntent);
oneway void retainSubscriptionsForFactoryReset(in PendingIntent callbackIntent);
}
\ No newline at end of file
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 37158e5..e1d6e01 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -25,7 +25,8 @@
"android.test.mock",
],
+ srcs_lib: "framework",
+ srcs_lib_whitelist_dirs: ["core/java"],
srcs_lib_whitelist_pkgs: ["android"],
- metalava_enabled: false,
compile_dex: true,
}
diff --git a/tests/net/Android.mk b/tests/net/Android.mk
index 9d1edbf..f6f35fd 100644
--- a/tests/net/Android.mk
+++ b/tests/net/Android.mk
@@ -18,6 +18,7 @@
mockito-target-minus-junit4 \
platform-test-annotations \
services.core \
+ services.ipmemorystore \
services.net
LOCAL_JAVA_LIBRARIES := \
diff --git a/tests/net/java/android/net/IpMemoryStoreTest.java b/tests/net/java/android/net/IpMemoryStoreTest.java
new file mode 100644
index 0000000..eae9710
--- /dev/null
+++ b/tests/net/java/android/net/IpMemoryStoreTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.content.Context;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpMemoryStoreTest {
+ @Mock
+ Context mMockContext;
+ @Mock
+ IIpMemoryStore mMockService;
+ IpMemoryStore mStore;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mStore = new IpMemoryStore(mMockContext, mMockService);
+ }
+
+ @Test
+ public void testNetworkAttributes() {
+ // TODO : implement this
+ }
+
+ @Test
+ public void testPrivateData() {
+ // TODO : implement this
+ }
+
+ @Test
+ public void testFindL2Key() {
+ // TODO : implement this
+ }
+
+ @Test
+ public void testIsSameNetwork() {
+ // TODO : implement this
+ }
+
+}
diff --git a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
new file mode 100644
index 0000000..a9f9758
--- /dev/null
+++ b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.util.Arrays;
+import java.util.Collections;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ParcelableTests {
+ @Test
+ public void testNetworkAttributesParceling() throws Exception {
+ final NetworkAttributes.Builder builder = new NetworkAttributes.Builder();
+ NetworkAttributes in = builder.build();
+ assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+
+ builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+ // groupHint stays null this time around
+ builder.setDnsAddresses(Collections.emptyList());
+ builder.setMtu(18);
+ in = builder.build();
+ assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+
+ builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("6.7.8.9"));
+ builder.setGroupHint("groupHint");
+ builder.setDnsAddresses(Arrays.asList(
+ InetAddress.getByName("ACA1:652B:0911:DE8F:1200:115E:913B:AA2A"),
+ InetAddress.getByName("6.7.8.9")));
+ builder.setMtu(1_000_000);
+ in = builder.build();
+ assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+
+ builder.setMtu(null);
+ in = builder.build();
+ assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+ }
+
+ @Test
+ public void testPrivateDataParceling() throws Exception {
+ final Blob in = new Blob();
+ in.data = new byte[] {89, 111, 108, 111};
+ final Blob out = parcelingRoundTrip(in);
+ // Object.equals on byte[] tests the references
+ assertEquals(in.data.length, out.data.length);
+ assertTrue(Arrays.equals(in.data, out.data));
+ }
+
+ @Test
+ public void testSameL3NetworkResponseParceling() throws Exception {
+ final SameL3NetworkResponseParcelable parcelable = new SameL3NetworkResponseParcelable();
+ parcelable.l2Key1 = "key 1";
+ parcelable.l2Key2 = "key 2";
+ parcelable.confidence = 0.43f;
+
+ final SameL3NetworkResponse in = new SameL3NetworkResponse(parcelable);
+ assertEquals("key 1", in.l2Key1);
+ assertEquals("key 2", in.l2Key2);
+ assertEquals(0.43f, in.confidence, 0.01f /* delta */);
+
+ final SameL3NetworkResponse out =
+ new SameL3NetworkResponse(parcelingRoundTrip(in.toParcelable()));
+
+ assertEquals(in, out);
+ assertEquals(in.l2Key1, out.l2Key1);
+ assertEquals(in.l2Key2, out.l2Key2);
+ assertEquals(in.confidence, out.confidence, 0.01f /* delta */);
+ }
+
+ private <T extends Parcelable> T parcelingRoundTrip(final T in) throws Exception {
+ final Parcel p = Parcel.obtain();
+ in.writeToParcel(p, /* flags */ 0);
+ p.setDataPosition(0);
+ final byte[] marshalledData = p.marshall();
+ p.recycle();
+
+ final Parcel q = Parcel.obtain();
+ q.unmarshall(marshalledData, 0, marshalledData.length);
+ q.setDataPosition(0);
+
+ final Parcelable.Creator<T> creator = (Parcelable.Creator<T>)
+ in.getClass().getField("CREATOR").get(null); // static object, so null receiver
+ final T unmarshalled = (T) creator.createFromParcel(q);
+ q.recycle();
+ return unmarshalled;
+ }
+}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 71529fd..bf39644 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -26,6 +26,8 @@
import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
import static android.net.ConnectivityManager.TYPE_NONE;
import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
@@ -69,17 +71,19 @@
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
-
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -89,7 +93,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
-import android.net.CaptivePortal;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.ConnectivityManager.PacketKeepalive;
@@ -97,6 +100,8 @@
import android.net.ConnectivityManager.TooManyRequestsException;
import android.net.ConnectivityThread;
import android.net.INetd;
+import android.net.INetworkMonitor;
+import android.net.INetworkMonitorCallbacks;
import android.net.INetworkPolicyListener;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
@@ -114,12 +119,14 @@
import android.net.NetworkMisc;
import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
+import android.net.NetworkStack;
import android.net.NetworkUtils;
import android.net.RouteInfo;
import android.net.StringNetworkSpecifier;
import android.net.UidRange;
-import android.net.captiveportal.CaptivePortalProbeResult;
import android.net.metrics.IpConnectivityLog;
+import android.net.shared.NetworkMonitorUtils;
+import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
import android.os.ConditionVariable;
import android.os.Handler;
@@ -148,12 +155,9 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.connectivity.ConnectivityConstants;
import com.android.server.connectivity.DefaultNetworkMetrics;
-import com.android.server.connectivity.DnsManager;
import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.Nat464Xlat;
-import com.android.server.connectivity.NetworkAgentInfo;
-import com.android.server.connectivity.NetworkMonitor;
import com.android.server.connectivity.Tethering;
import com.android.server.connectivity.Vpn;
import com.android.server.net.NetworkPinner;
@@ -168,6 +172,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
+import org.mockito.stubbing.Answer;
import java.net.Inet4Address;
import java.net.InetAddress;
@@ -230,6 +235,7 @@
@Mock INetworkStatsService mStatsService;
@Mock INetworkPolicyManager mNpm;
@Mock INetd mMockNetd;
+ @Mock NetworkStack mNetworkStack;
private ArgumentCaptor<String[]> mStringArrayCaptor = ArgumentCaptor.forClass(String[].class);
@@ -299,6 +305,7 @@
public Object getSystemService(String name) {
if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
if (Context.NOTIFICATION_SERVICE.equals(name)) return mock(NotificationManager.class);
+ if (Context.NETWORK_STACK_SERVICE.equals(name)) return mNetworkStack;
return super.getSystemService(name);
}
@@ -386,7 +393,7 @@
}
private class MockNetworkAgent {
- private final WrappedNetworkMonitor mWrappedNetworkMonitor;
+ private final INetworkMonitor mNetworkMonitor;
private final NetworkInfo mNetworkInfo;
private final NetworkCapabilities mNetworkCapabilities;
private final HandlerThread mHandlerThread;
@@ -402,6 +409,26 @@
// mNetworkStatusReceived.
private String mRedirectUrl;
+ private INetworkMonitorCallbacks mNmCallbacks;
+ private int mNmValidationResult = NETWORK_TEST_RESULT_INVALID;
+ private String mNmValidationRedirectUrl = null;
+ private boolean mNmProvNotificationRequested = false;
+
+ void setNetworkValid() {
+ mNmValidationResult = NETWORK_TEST_RESULT_VALID;
+ mNmValidationRedirectUrl = null;
+ }
+
+ void setNetworkInvalid() {
+ mNmValidationResult = NETWORK_TEST_RESULT_INVALID;
+ mNmValidationRedirectUrl = null;
+ }
+
+ void setNetworkPortal(String redirectUrl) {
+ setNetworkInvalid();
+ mNmValidationRedirectUrl = redirectUrl;
+ }
+
MockNetworkAgent(int transport) {
this(transport, new LinkProperties());
}
@@ -434,6 +461,29 @@
}
mHandlerThread = new HandlerThread("Mock-" + typeName);
mHandlerThread.start();
+
+ mNetworkMonitor = mock(INetworkMonitor.class);
+ final Answer validateAnswer = inv -> {
+ new Thread(this::onValidationRequested).start();
+ return null;
+ };
+
+ try {
+ doAnswer(validateAnswer).when(mNetworkMonitor).notifyNetworkConnected();
+ doAnswer(validateAnswer).when(mNetworkMonitor).forceReevaluation(anyInt());
+ } catch (RemoteException e) {
+ fail(e.getMessage());
+ }
+
+ final ArgumentCaptor<Network> nmNetworkCaptor =
+ ArgumentCaptor.forClass(Network.class);
+ final ArgumentCaptor<INetworkMonitorCallbacks> nmCbCaptor =
+ ArgumentCaptor.forClass(INetworkMonitorCallbacks.class);
+ doNothing().when(mNetworkStack).makeNetworkMonitor(
+ nmNetworkCaptor.capture(),
+ any() /* name */,
+ nmCbCaptor.capture());
+
mNetworkAgent = new NetworkAgent(mHandlerThread.getLooper(), mServiceContext,
"Mock-" + typeName, mNetworkInfo, mNetworkCapabilities,
linkProperties, mScore, new NetworkMisc()) {
@@ -465,10 +515,40 @@
mPreventReconnectReceived.open();
}
};
+
+ assertEquals(mNetworkAgent.netId, nmNetworkCaptor.getValue().netId);
+ mNmCallbacks = nmCbCaptor.getValue();
+
+ try {
+ mNmCallbacks.onNetworkMonitorCreated(mNetworkMonitor);
+ } catch (RemoteException e) {
+ fail(e.getMessage());
+ }
+
// Waits for the NetworkAgent to be registered, which includes the creation of the
// NetworkMonitor.
waitForIdle();
- mWrappedNetworkMonitor = mService.getLastCreatedWrappedNetworkMonitor();
+ }
+
+ private void onValidationRequested() {
+ try {
+ if (mNmProvNotificationRequested
+ && mNmValidationResult == NETWORK_TEST_RESULT_VALID) {
+ mNmCallbacks.hideProvisioningNotification();
+ mNmProvNotificationRequested = false;
+ }
+
+ mNmCallbacks.notifyNetworkTested(
+ mNmValidationResult, mNmValidationRedirectUrl);
+
+ if (mNmValidationRedirectUrl != null) {
+ mNmCallbacks.showProvisioningNotification(
+ "test_provisioning_notif_action");
+ mNmProvNotificationRequested = true;
+ }
+ } catch (RemoteException e) {
+ fail(e.getMessage());
+ }
}
public void adjustScore(int change) {
@@ -539,7 +619,7 @@
NetworkCallback callback = null;
final ConditionVariable validatedCv = new ConditionVariable();
if (validated) {
- mWrappedNetworkMonitor.gen204ProbeResult = 204;
+ setNetworkValid();
NetworkRequest request = new NetworkRequest.Builder()
.addTransportType(mNetworkCapabilities.getTransportTypes()[0])
.clearCapabilities()
@@ -564,15 +644,14 @@
if (validated) {
// Wait for network to validate.
waitFor(validatedCv);
- mWrappedNetworkMonitor.gen204ProbeResult = 500;
+ setNetworkInvalid();
}
if (callback != null) mCm.unregisterNetworkCallback(callback);
}
public void connectWithCaptivePortal(String redirectUrl) {
- mWrappedNetworkMonitor.gen204ProbeResult = 200;
- mWrappedNetworkMonitor.gen204ProbeRedirectUrl = redirectUrl;
+ setNetworkPortal(redirectUrl);
connect(false);
}
@@ -603,10 +682,6 @@
return mDisconnected;
}
- public WrappedNetworkMonitor getWrappedNetworkMonitor() {
- return mWrappedNetworkMonitor;
- }
-
public void sendLinkProperties(LinkProperties lp) {
mNetworkAgent.sendLinkProperties(lp);
}
@@ -880,28 +955,6 @@
}
}
- // NetworkMonitor implementation allowing overriding of Internet connectivity probe result.
- private class WrappedNetworkMonitor extends NetworkMonitor {
- public final Handler connectivityHandler;
- // HTTP response code fed back to NetworkMonitor for Internet connectivity probe.
- public int gen204ProbeResult = 500;
- public String gen204ProbeRedirectUrl = null;
-
- public WrappedNetworkMonitor(Context context, Handler handler,
- NetworkAgentInfo networkAgentInfo, NetworkRequest defaultRequest,
- IpConnectivityLog log) {
- super(context, handler, networkAgentInfo, defaultRequest, log,
- NetworkMonitor.Dependencies.DEFAULT);
- connectivityHandler = handler;
- }
-
- @Override
- protected CaptivePortalProbeResult isCaptivePortal() {
- if (!mIsCaptivePortalCheckEnabled) { return new CaptivePortalProbeResult(204); }
- return new CaptivePortalProbeResult(gen204ProbeResult, gen204ProbeRedirectUrl, null);
- }
- }
-
private class WrappedMultinetworkPolicyTracker extends MultinetworkPolicyTracker {
public volatile boolean configRestrictsAvoidBadWifi;
public volatile int configMeteredMultipathPreference;
@@ -923,7 +976,6 @@
private class WrappedConnectivityService extends ConnectivityService {
public WrappedMultinetworkPolicyTracker wrappedMultinetworkPolicyTracker;
- private WrappedNetworkMonitor mLastCreatedNetworkMonitor;
private MockableSystemProperties mSystemProperties;
public WrappedConnectivityService(Context context, INetworkManagementService netManager,
@@ -971,15 +1023,6 @@
}
}
- @Override
- public NetworkMonitor createNetworkMonitor(Context context, Handler handler,
- NetworkAgentInfo nai, NetworkRequest defaultRequest) {
- final WrappedNetworkMonitor monitor = new WrappedNetworkMonitor(
- context, handler, nai, defaultRequest, mock(IpConnectivityLog.class));
- mLastCreatedNetworkMonitor = monitor;
- return monitor;
- }
-
public Nat464Xlat getNat464Xlat(MockNetworkAgent mna) {
return getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd;
}
@@ -1017,10 +1060,6 @@
protected void registerNetdEventCallback() {
}
- public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() {
- return mLastCreatedNetworkMonitor;
- }
-
public void mockVpn(int uid) {
synchronized (mVpns) {
int userId = UserHandle.getUserId(uid);
@@ -2439,7 +2478,7 @@
// Make captive portal disappear then revalidate.
// Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL.
- mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204;
+ mWiFiNetworkAgent.setNetworkValid();
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
@@ -2448,13 +2487,13 @@
// Break network connectivity.
// Expect NET_CAPABILITY_VALIDATED onLost callback.
- mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 500;
+ mWiFiNetworkAgent.setNetworkInvalid();
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
}
@Test
- public void testCaptivePortalApp() {
+ public void testCaptivePortalApp() throws RemoteException {
final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
@@ -2477,21 +2516,19 @@
mServiceContext.expectNoStartActivityIntent(fastTimeoutMs);
// Turn into a captive portal.
- mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 302;
+ mWiFiNetworkAgent.setNetworkPortal("http://example.com");
mCm.reportNetworkConnectivity(wifiNetwork, false);
captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- // Check that startCaptivePortalApp sends the expected intent.
+ // Check that startCaptivePortalApp sends the expected command to NetworkMonitor.
mCm.startCaptivePortalApp(wifiNetwork);
- Intent intent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS);
- assertEquals(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN, intent.getAction());
- assertEquals(wifiNetwork, intent.getExtra(ConnectivityManager.EXTRA_NETWORK));
+ verify(mWiFiNetworkAgent.mNetworkMonitor, timeout(TIMEOUT_MS).times(1))
+ .launchCaptivePortalApp();
- // Have the app report that the captive portal is dismissed, and check that we revalidate.
- mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204;
- CaptivePortal c = (CaptivePortal) intent.getExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL);
- c.reportCaptivePortalDismissed();
+ // Report that the captive portal is dismissed, and check that callbacks are fired
+ mWiFiNetworkAgent.setNetworkValid();
+ mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
@@ -2524,20 +2561,6 @@
waitFor(avoidCv);
assertNoCallbacks(captivePortalCallback, validatedCallback);
-
- // Now test ignore mode.
- setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE);
-
- // Bring up a network with a captive portal.
- // Since we're ignoring captive portals, the network will validate.
- mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
- String secondRedirectUrl = "http://example.com/secondPath";
- mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
-
- // Expect NET_CAPABILITY_VALIDATED onAvailable callback.
- validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
- // But there should be no CaptivePortal callback.
- captivePortalCallback.assertNoCallback();
}
private NetworkRequest.Builder newWifiRequestBuilder() {
@@ -3169,7 +3192,7 @@
Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
// Fail validation on wifi.
- mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 599;
+ mWiFiNetworkAgent.setNetworkInvalid();
mCm.reportNetworkConnectivity(wifiNetwork, false);
defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
@@ -3213,7 +3236,7 @@
wifiNetwork = mWiFiNetworkAgent.getNetwork();
// Fail validation on wifi and expect the dialog to appear.
- mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 599;
+ mWiFiNetworkAgent.setNetworkInvalid();
mCm.reportNetworkConnectivity(wifiNetwork, false);
defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
@@ -4002,11 +4025,9 @@
final String TLS_SERVER6 = "2001:db8:53::53";
final InetAddress[] TLS_IPS = new InetAddress[]{ InetAddress.getByName(TLS_SERVER6) };
final String[] TLS_SERVERS = new String[]{ TLS_SERVER6 };
- final Handler h = mCellNetworkAgent.getWrappedNetworkMonitor().connectivityHandler;
- h.sendMessage(h.obtainMessage(
- NetworkMonitor.EVENT_PRIVATE_DNS_CONFIG_RESOLVED, 0,
- mCellNetworkAgent.getNetwork().netId,
- new DnsManager.PrivateDnsConfig(TLS_SPECIFIER, TLS_IPS)));
+ mCellNetworkAgent.mNmCallbacks.notifyPrivateDnsConfigResolved(
+ new PrivateDnsConfig(TLS_SPECIFIER, TLS_IPS).toParcel());
+
waitForIdle();
verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork(
anyInt(), mStringArrayCaptor.capture(), any(), any(),
@@ -4294,6 +4315,12 @@
ranges.add(new UidRange(uid, uid));
mMockVpn.setNetworkAgent(vpnNetworkAgent);
mMockVpn.setUids(ranges);
+ // VPN networks do not satisfy the default request and are automatically validated
+ // by NetworkMonitor
+ assertFalse(NetworkMonitorUtils.isValidationRequired(
+ mCm.getDefaultRequest().networkCapabilities, vpnNetworkAgent.mNetworkCapabilities));
+ vpnNetworkAgent.setNetworkValid();
+
vpnNetworkAgent.connect(false);
mMockVpn.connect();
diff --git a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
index 01b468a..38322e9 100644
--- a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
@@ -17,7 +17,6 @@
package com.android.server.connectivity;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
-import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE;
import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
@@ -29,13 +28,13 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
-import android.content.ContentResolver;
import android.content.Context;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.RouteInfo;
+import android.net.shared.PrivateDnsConfig;
import android.os.INetworkManagementService;
import android.provider.Settings;
import android.support.test.filters.SmallTest;
@@ -43,18 +42,16 @@
import android.test.mock.MockContentResolver;
import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.connectivity.DnsManager.PrivateDnsConfig;
-import com.android.server.connectivity.MockableSystemProperties;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.net.InetAddress;
import java.util.Arrays;
-import org.junit.runner.RunWith;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
/**
* Tests for {@link DnsManager}.
*
@@ -133,7 +130,7 @@
PRIVATE_DNS_MODE, PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
Settings.Global.putString(mContentResolver, PRIVATE_DNS_SPECIFIER, "strictmode.com");
mDnsManager.updatePrivateDns(new Network(TEST_NETID),
- new DnsManager.PrivateDnsConfig("strictmode.com", new InetAddress[] {
+ new PrivateDnsConfig("strictmode.com", new InetAddress[] {
InetAddress.parseNumericAddress("6.6.6.6"),
InetAddress.parseNumericAddress("2001:db8:66:66::1")
}));
diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
index 354cf2f..4c52d81 100644
--- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -23,10 +23,10 @@
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.reset;
import android.app.PendingIntent;
import android.content.Context;
@@ -36,18 +36,18 @@
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkMisc;
-import android.support.test.runner.AndroidJUnit4;
+import android.net.NetworkStack;
import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
import android.text.format.DateUtils;
import com.android.internal.R;
import com.android.server.ConnectivityService;
-import com.android.server.connectivity.NetworkNotificationManager;
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
-import org.junit.runner.RunWith;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -70,13 +70,16 @@
@Mock NetworkMisc mMisc;
@Mock NetworkNotificationManager mNotifier;
@Mock Resources mResources;
+ @Mock NetworkStack mNetworkStack;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mCtx.getResources()).thenReturn(mResources);
when(mCtx.getPackageName()).thenReturn("com.android.server.connectivity");
- when(mConnService.createNetworkMonitor(any(), any(), any(), any())).thenReturn(null);
+ when(mCtx.getSystemServiceName(NetworkStack.class))
+ .thenReturn(Context.NETWORK_STACK_SERVICE);
+ when(mCtx.getSystemService(Context.NETWORK_STACK_SERVICE)).thenReturn(mNetworkStack);
mMonitor = new TestableLingerMonitor(mCtx, mNotifier, HIGH_DAILY_LIMIT, HIGH_RATE_LIMIT);
}
@@ -349,7 +352,7 @@
caps.addCapability(0);
caps.addTransportType(transport);
NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null,
- caps, 50, mCtx, null, mMisc, null, mConnService);
+ caps, 50, mCtx, null, mMisc, mConnService);
nai.everValidated = true;
return nai;
}
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
new file mode 100644
index 0000000..859a54d
--- /dev/null
+++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.ipmemorystore;
+
+import android.content.Context;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link IpMemoryStoreServiceTest}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class IpMemoryStoreServiceTest {
+ @Mock
+ Context mMockContext;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testNetworkAttributes() {
+ final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
+ // TODO : implement this
+ }
+
+ @Test
+ public void testPrivateData() {
+ final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
+ // TODO : implement this
+ }
+
+ @Test
+ public void testFindL2Key() {
+ final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
+ // TODO : implement this
+ }
+
+ @Test
+ public void testIsSameNetwork() {
+ final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
+ // TODO : implement this
+ }
+}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index b265c40..7f56a01 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -234,6 +234,12 @@
@SystemApi
public static final int WIFI_CREDENTIAL_FORGOT = 1;
+ /** @hide */
+ public static final int PASSPOINT_HOME_NETWORK = 0;
+
+ /** @hide */
+ public static final int PASSPOINT_ROAMING_NETWORK = 1;
+
/**
* Broadcast intent action indicating that a Passpoint provider icon has been received.
*
@@ -1207,26 +1213,30 @@
* match the ScanResult.
*
* @param scanResults a list of scanResult that represents the BSSID
- * @return List that consists of {@link WifiConfiguration} and corresponding scanResults.
+ * @return List that consists of {@link WifiConfiguration} and corresponding scanResults per
+ * network type({@link #PASSPOINT_HOME_NETWORK} and {@link #PASSPOINT_ROAMING_NETWORK}).
* @throws UnsupportedOperationException if Passpoint is not enabled on the device.
* @hide
*/
@RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
- public List<Pair<WifiConfiguration, List<ScanResult>>> getAllMatchingWifiConfigs(
+ public List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> getAllMatchingWifiConfigs(
@NonNull List<ScanResult> scanResults) {
- List<Pair<WifiConfiguration, List<ScanResult>>> configs = new ArrayList<>();
+ List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> configs = new ArrayList<>();
try {
- Map<String, List<ScanResult>> results = mService.getAllMatchingFqdnsForScanResults(
- scanResults);
+ Map<String, Map<Integer, List<ScanResult>>> results =
+ mService.getAllMatchingFqdnsForScanResults(
+ scanResults);
if (results.isEmpty()) {
return configs;
}
List<WifiConfiguration> wifiConfigurations =
- mService.getWifiConfigsForPasspointProfiles(new ArrayList<>(results.keySet()));
+ mService.getWifiConfigsForPasspointProfiles(
+ new ArrayList<>(results.keySet()));
for (WifiConfiguration configuration : wifiConfigurations) {
- List<ScanResult> scanResultList = results.get(configuration.FQDN);
- if (scanResultList != null) {
- configs.add(Pair.create(configuration, scanResultList));
+ Map<Integer, List<ScanResult>> scanResultsPerNetworkType = results.get(
+ configuration.FQDN);
+ if (scanResultsPerNetworkType != null) {
+ configs.add(Pair.create(configuration, scanResultsPerNetworkType));
}
}
} catch (RemoteException e) {
diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java
index 4e29dd1..2c96c05 100644
--- a/wifi/java/com/android/server/wifi/BaseWifiService.java
+++ b/wifi/java/com/android/server/wifi/BaseWifiService.java
@@ -89,7 +89,7 @@
}
@Override
- public Map<String, List<ScanResult>> getAllMatchingFqdnsForScanResults(
+ public Map<String, Map<Integer, List<ScanResult>>> getAllMatchingFqdnsForScanResults(
List<ScanResult> scanResults) {
throw new UnsupportedOperationException();
}