Merge "Fix ConnectivityServiceTest testRequestBenchmark"
diff --git a/api/current.txt b/api/current.txt
index 8a00273..3914021 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1357,6 +1357,7 @@
field public static final int toYDelta = 16843209; // 0x10101c9
field public static final int toYScale = 16843205; // 0x10101c5
field public static final int toolbarStyle = 16843946; // 0x10104aa
+ field public static final int tooltip = 16844084; // 0x1010534
field public static final int top = 16843182; // 0x10101ae
field public static final int topBright = 16842955; // 0x10100cb
field public static final int topDark = 16842951; // 0x10100c7
@@ -19914,6 +19915,7 @@
field public static final int SCO_AUDIO_STATE_CONNECTING = 2; // 0x2
field public static final int SCO_AUDIO_STATE_DISCONNECTED = 0; // 0x0
field public static final int SCO_AUDIO_STATE_ERROR = -1; // 0xffffffff
+ field public static final int STREAM_ACCESSIBILITY = 10; // 0xa
field public static final int STREAM_ALARM = 4; // 0x4
field public static final int STREAM_DTMF = 8; // 0x8
field public static final int STREAM_MUSIC = 3; // 0x3
@@ -37629,6 +37631,7 @@
method public int getCallState();
method public android.telephony.CellLocation getCellLocation();
method public int getDataActivity();
+ method public boolean getDataEnabled();
method public int getDataNetworkType();
method public int getDataState();
method public java.lang.String getDeviceId();
@@ -37675,6 +37678,7 @@
method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
method public void sendUssdRequest(java.lang.String, int, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
+ method public void setDataEnabled(boolean);
method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
method public boolean setOperatorBrandOverride(java.lang.String);
method public boolean setPreferredNetworkTypeToGlobal();
@@ -37682,6 +37686,7 @@
field public static final java.lang.String ACTION_CONFIGURE_VOICEMAIL = "android.telephony.action.CONFIGURE_VOICEMAIL";
field public static final java.lang.String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE";
field public static final java.lang.String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE";
+ field public static final java.lang.String ACTION_SHOW_VOICEMAIL_NOTIFICATION = "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION";
field public static final int APPTYPE_CSIM = 4; // 0x4
field public static final int APPTYPE_ISIM = 5; // 0x5
field public static final int APPTYPE_RUIM = 3; // 0x3
@@ -37701,11 +37706,15 @@
field public static final int DATA_CONNECTING = 1; // 0x1
field public static final int DATA_DISCONNECTED = 0; // 0x0
field public static final int DATA_SUSPENDED = 3; // 0x3
+ field public static final java.lang.String EXTRA_CALL_VOICEMAIL_INTENT = "android.telephony.extra.CALL_VOICEMAIL_INTENT";
field public static final java.lang.String EXTRA_INCOMING_NUMBER = "incoming_number";
+ field public static final java.lang.String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT";
+ field public static final java.lang.String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT";
field public static final java.lang.String EXTRA_STATE = "state";
field public static final java.lang.String EXTRA_STATE_IDLE;
field public static final java.lang.String EXTRA_STATE_OFFHOOK;
field public static final java.lang.String EXTRA_STATE_RINGING;
+ field public static final java.lang.String EXTRA_VOICEMAIL_NUMBER = "android.telephony.extra.VOICEMAIL_NUMBER";
field public static final int NETWORK_TYPE_1xRTT = 7; // 0x7
field public static final int NETWORK_TYPE_CDMA = 4; // 0x4
field public static final int NETWORK_TYPE_EDGE = 2; // 0x2
@@ -42827,6 +42836,7 @@
method public java.lang.Object getTag(int);
method public int getTextAlignment();
method public int getTextDirection();
+ method public final java.lang.CharSequence getTooltip();
method public final int getTop();
method protected float getTopFadingEdgeStrength();
method protected int getTopPaddingOffset();
@@ -43115,6 +43125,7 @@
method public void setTag(int, java.lang.Object);
method public void setTextAlignment(int);
method public void setTextDirection(int);
+ method public final void setTooltip(java.lang.CharSequence);
method public final void setTop(int);
method public void setTouchDelegate(android.view.TouchDelegate);
method public final void setTransitionName(java.lang.String);
diff --git a/api/system-current.txt b/api/system-current.txt
index 3ecc8aa..bcf4897 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1468,6 +1468,7 @@
field public static final int toYDelta = 16843209; // 0x10101c9
field public static final int toYScale = 16843205; // 0x10101c5
field public static final int toolbarStyle = 16843946; // 0x10104aa
+ field public static final int tooltip = 16844084; // 0x1010534
field public static final int top = 16843182; // 0x10101ae
field public static final int topBright = 16842955; // 0x10100cb
field public static final int topDark = 16842951; // 0x10100c7
@@ -21476,6 +21477,7 @@
field public static final int SCO_AUDIO_STATE_CONNECTING = 2; // 0x2
field public static final int SCO_AUDIO_STATE_DISCONNECTED = 0; // 0x0
field public static final int SCO_AUDIO_STATE_ERROR = -1; // 0xffffffff
+ field public static final int STREAM_ACCESSIBILITY = 10; // 0xa
field public static final int STREAM_ALARM = 4; // 0x4
field public static final int STREAM_DTMF = 8; // 0x8
field public static final int STREAM_MUSIC = 3; // 0x3
@@ -35380,6 +35382,7 @@
field public static final java.lang.String ACTION_VR_LISTENER_SETTINGS = "android.settings.VR_LISTENER_SETTINGS";
field public static final java.lang.String ACTION_WEBVIEW_SETTINGS = "android.settings.WEBVIEW_SETTINGS";
field public static final java.lang.String ACTION_WIFI_IP_SETTINGS = "android.settings.WIFI_IP_SETTINGS";
+ field public static final java.lang.String ACTION_WIFI_SAVED_NETWORK_SETTINGS = "android.settings.WIFI_SAVED_NETWORK_SETTINGS";
field public static final java.lang.String ACTION_WIFI_SETTINGS = "android.settings.WIFI_SETTINGS";
field public static final java.lang.String ACTION_WIRELESS_SETTINGS = "android.settings.WIRELESS_SETTINGS";
field public static final java.lang.String AUTHORITY = "settings";
@@ -45995,6 +45998,7 @@
method public java.lang.Object getTag(int);
method public int getTextAlignment();
method public int getTextDirection();
+ method public final java.lang.CharSequence getTooltip();
method public final int getTop();
method protected float getTopFadingEdgeStrength();
method protected int getTopPaddingOffset();
@@ -46283,6 +46287,7 @@
method public void setTag(int, java.lang.Object);
method public void setTextAlignment(int);
method public void setTextDirection(int);
+ method public final void setTooltip(java.lang.CharSequence);
method public final void setTop(int);
method public void setTouchDelegate(android.view.TouchDelegate);
method public final void setTransitionName(java.lang.String);
diff --git a/api/test-current.txt b/api/test-current.txt
index 13b0c94..2dcb743 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1357,6 +1357,7 @@
field public static final int toYDelta = 16843209; // 0x10101c9
field public static final int toYScale = 16843205; // 0x10101c5
field public static final int toolbarStyle = 16843946; // 0x10104aa
+ field public static final int tooltip = 16844084; // 0x1010534
field public static final int top = 16843182; // 0x10101ae
field public static final int topBright = 16842955; // 0x10100cb
field public static final int topDark = 16842951; // 0x10100c7
@@ -19995,6 +19996,7 @@
field public static final int SCO_AUDIO_STATE_CONNECTING = 2; // 0x2
field public static final int SCO_AUDIO_STATE_DISCONNECTED = 0; // 0x0
field public static final int SCO_AUDIO_STATE_ERROR = -1; // 0xffffffff
+ field public static final int STREAM_ACCESSIBILITY = 10; // 0xa
field public static final int STREAM_ALARM = 4; // 0x4
field public static final int STREAM_DTMF = 8; // 0x8
field public static final int STREAM_MUSIC = 3; // 0x3
@@ -37719,6 +37721,7 @@
method public int getCallState();
method public android.telephony.CellLocation getCellLocation();
method public int getDataActivity();
+ method public boolean getDataEnabled();
method public int getDataNetworkType();
method public int getDataState();
method public java.lang.String getDeviceId();
@@ -37765,6 +37768,7 @@
method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
method public void sendUssdRequest(java.lang.String, int, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
+ method public void setDataEnabled(boolean);
method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
method public boolean setOperatorBrandOverride(java.lang.String);
method public boolean setPreferredNetworkTypeToGlobal();
@@ -37772,6 +37776,7 @@
field public static final java.lang.String ACTION_CONFIGURE_VOICEMAIL = "android.telephony.action.CONFIGURE_VOICEMAIL";
field public static final java.lang.String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE";
field public static final java.lang.String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE";
+ field public static final java.lang.String ACTION_SHOW_VOICEMAIL_NOTIFICATION = "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION";
field public static final int APPTYPE_CSIM = 4; // 0x4
field public static final int APPTYPE_ISIM = 5; // 0x5
field public static final int APPTYPE_RUIM = 3; // 0x3
@@ -37791,11 +37796,15 @@
field public static final int DATA_CONNECTING = 1; // 0x1
field public static final int DATA_DISCONNECTED = 0; // 0x0
field public static final int DATA_SUSPENDED = 3; // 0x3
+ field public static final java.lang.String EXTRA_CALL_VOICEMAIL_INTENT = "android.telephony.extra.CALL_VOICEMAIL_INTENT";
field public static final java.lang.String EXTRA_INCOMING_NUMBER = "incoming_number";
+ field public static final java.lang.String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT";
+ field public static final java.lang.String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT";
field public static final java.lang.String EXTRA_STATE = "state";
field public static final java.lang.String EXTRA_STATE_IDLE;
field public static final java.lang.String EXTRA_STATE_OFFHOOK;
field public static final java.lang.String EXTRA_STATE_RINGING;
+ field public static final java.lang.String EXTRA_VOICEMAIL_NUMBER = "android.telephony.extra.VOICEMAIL_NUMBER";
field public static final int NETWORK_TYPE_1xRTT = 7; // 0x7
field public static final int NETWORK_TYPE_CDMA = 4; // 0x4
field public static final int NETWORK_TYPE_EDGE = 2; // 0x2
@@ -43072,6 +43081,8 @@
method public java.lang.Object getTag(int);
method public int getTextAlignment();
method public int getTextDirection();
+ method public final java.lang.CharSequence getTooltip();
+ method public android.view.View getTooltipView();
method public final int getTop();
method protected float getTopFadingEdgeStrength();
method protected int getTopPaddingOffset();
@@ -43360,6 +43371,7 @@
method public void setTag(int, java.lang.Object);
method public void setTextAlignment(int);
method public void setTextDirection(int);
+ method public final void setTooltip(java.lang.CharSequence);
method public final void setTop(int);
method public void setTouchDelegate(android.view.TouchDelegate);
method public final void setTransitionName(java.lang.String);
@@ -43642,10 +43654,14 @@
method public static deprecated int getEdgeSlop();
method public static deprecated int getFadingEdgeLength();
method public static deprecated long getGlobalActionKeyTimeout();
+ method public static int getHoverTooltipHideShortTimeout();
+ method public static int getHoverTooltipHideTimeout();
+ method public static int getHoverTooltipShowTimeout();
method public static int getJumpTapTimeout();
method public static int getKeyRepeatDelay();
method public static int getKeyRepeatTimeout();
method public static int getLongPressTimeout();
+ method public static int getLongPressTooltipHideTimeout();
method public static deprecated int getMaximumDrawingCacheSize();
method public static deprecated int getMaximumFlingVelocity();
method public static deprecated int getMinimumFlingVelocity();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 1aa13a9..f052bf7 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1309,8 +1309,19 @@
}
@Override
- public final void updateTimePrefs(boolean is24Hour) {
- DateFormat.set24HourTimePref(is24Hour);
+ public final void updateTimePrefs(int timeFormatPreference) {
+ final Boolean timeFormatPreferenceBool;
+ // For convenience we are using the Intent extra values.
+ if (timeFormatPreference == Intent.EXTRA_TIME_PREF_VALUE_USE_12_HOUR) {
+ timeFormatPreferenceBool = Boolean.FALSE;
+ } else if (timeFormatPreference == Intent.EXTRA_TIME_PREF_VALUE_USE_24_HOUR) {
+ timeFormatPreferenceBool = Boolean.TRUE;
+ } else {
+ // timeFormatPreference == Intent.EXTRA_TIME_PREF_VALUE_USE_LOCALE_DEFAULT
+ // (or unknown).
+ timeFormatPreferenceBool = null;
+ }
+ DateFormat.set24HourTimePref(timeFormatPreferenceBool);
}
@Override
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 191cc49..ba6bc15 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -240,7 +240,9 @@
/** @hide Control whether an application is allowed to run in the background. */
public static final int OP_RUN_IN_BACKGROUND = 63;
/** @hide */
- public static final int _NUM_OP = 64;
+ public static final int OP_AUDIO_ACCESSIBILITY_VOLUME = 64;
+ /** @hide */
+ public static final int _NUM_OP = 65;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -452,6 +454,7 @@
OP_TURN_SCREEN_ON,
OP_GET_ACCOUNTS,
OP_RUN_IN_BACKGROUND,
+ OP_AUDIO_ACCESSIBILITY_VOLUME,
};
/**
@@ -523,6 +526,7 @@
null,
OPSTR_GET_ACCOUNTS,
null,
+ null, // OP_AUDIO_ACCESSIBILITY_VOLUME
};
/**
@@ -594,6 +598,7 @@
"TURN_ON_SCREEN",
"GET_ACCOUNTS",
"RUN_IN_BACKGROUND",
+ "AUDIO_ACCESSIBILITY_VOLUME",
};
/**
@@ -665,6 +670,7 @@
null, // no permission for turning the screen on
Manifest.permission.GET_ACCOUNTS,
null, // no permission for running in background
+ null, // no permission for changing accessibility volume
};
/**
@@ -737,6 +743,7 @@
null, // TURN_ON_SCREEN
null, // GET_ACCOUNTS
null, // RUN_IN_BACKGROUND
+ UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_ACCESSIBILITY_VOLUME
};
/**
@@ -808,6 +815,7 @@
false, // TURN_ON_SCREEN
false, // GET_ACCOUNTS
false, // RUN_IN_BACKGROUND
+ false, // AUDIO_ACCESSIBILITY_VOLUME
};
/**
@@ -878,6 +886,7 @@
AppOpsManager.MODE_ALLOWED, // OP_TURN_ON_SCREEN
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED, // OP_RUN_IN_BACKGROUND
+ AppOpsManager.MODE_ALLOWED, // OP_AUDIO_ACCESSIBILITY_VOLUME
};
/**
@@ -952,6 +961,7 @@
false,
false,
false,
+ false, // OP_AUDIO_ACCESSIBILITY_VOLUME
};
/**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 496c05f..827e026 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -33,6 +33,7 @@
import android.content.ReceiverCallNotAllowedException;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
@@ -159,7 +160,7 @@
private final String mOpPackageName;
private final @NonNull ResourcesManager mResourcesManager;
- private @NonNull Resources mResources;
+ private final @NonNull Resources mResources;
private @Nullable Display mDisplay; // may be null if default display
private final int mFlags;
@@ -1834,19 +1835,6 @@
}
}
- private static Resources createResources(IBinder activityToken, LoadedApk pi, int displayId,
- Configuration overrideConfig, CompatibilityInfo compatInfo) {
- return ResourcesManager.getInstance().getResources(activityToken,
- pi.getResDir(),
- pi.getSplitResDirs(),
- pi.getOverlayDirs(),
- pi.getApplicationInfo().sharedLibraryFiles,
- displayId,
- overrideConfig,
- compatInfo,
- pi.getClassLoader());
- }
-
@Override
public Context createApplicationContext(ApplicationInfo application, int flags)
throws NameNotFoundException {
@@ -1854,13 +1842,8 @@
flags | CONTEXT_REGISTER_PACKAGE);
if (pi != null) {
ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
- new UserHandle(UserHandle.getUserId(application.uid)), flags);
-
- final int displayId = mDisplay != null
- ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
-
- c.mResources = createResources(mActivityToken, pi, displayId, null,
- getDisplayAdjustments(displayId).getCompatibilityInfo());
+ new UserHandle(UserHandle.getUserId(application.uid)), flags,
+ mDisplay, null, Display.INVALID_DISPLAY);
if (c.mResources != null) {
return c;
}
@@ -1881,21 +1864,15 @@
public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
throws NameNotFoundException {
if (packageName.equals("system") || packageName.equals("android")) {
- // The system resources are loaded in every application, so we can safely copy
- // the context without reloading Resources.
- return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, user, flags);
+ return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
+ user, flags, mDisplay, null, Display.INVALID_DISPLAY);
}
LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(),
flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier());
if (pi != null) {
- ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken, user, flags);
-
- final int displayId = mDisplay != null
- ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
-
- c.mResources = createResources(mActivityToken, pi, displayId, null,
- getDisplayAdjustments(displayId).getCompatibilityInfo());
+ ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
+ user, flags, mDisplay, null, Display.INVALID_DISPLAY);
if (c.mResources != null) {
return c;
}
@@ -1912,14 +1889,8 @@
throw new IllegalArgumentException("overrideConfiguration must not be null");
}
- ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
- mUser, mFlags);
-
- final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
- context.mResources = createResources(mActivityToken, mPackageInfo, displayId,
- overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo());
- context.mDisplay = mDisplay;
- return context;
+ return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
+ mUser, mFlags, mDisplay, overrideConfiguration, Display.INVALID_DISPLAY);
}
@Override
@@ -1928,28 +1899,24 @@
throw new IllegalArgumentException("display must not be null");
}
- ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
- mUser, mFlags);
-
- final int displayId = display.getDisplayId();
- context.mResources = createResources(mActivityToken, mPackageInfo, displayId, null,
- getDisplayAdjustments(displayId).getCompatibilityInfo());
- context.mDisplay = display;
- return context;
+ return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
+ mUser, mFlags, display, null, Display.INVALID_DISPLAY);
}
@Override
public Context createDeviceProtectedStorageContext() {
final int flags = (mFlags & ~Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE)
| Context.CONTEXT_DEVICE_PROTECTED_STORAGE;
- return new ContextImplFlagContextWrapper(this, flags);
+ return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
+ mUser, flags, mDisplay, null, Display.INVALID_DISPLAY);
}
@Override
public Context createCredentialProtectedStorageContext() {
final int flags = (mFlags & ~Context.CONTEXT_DEVICE_PROTECTED_STORAGE)
| Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE;
- return new ContextImplFlagContextWrapper(this, flags);
+ return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
+ mUser, flags, mDisplay, null, Display.INVALID_DISPLAY);
}
@Override
@@ -2036,8 +2003,8 @@
static ContextImpl createSystemContext(ActivityThread mainThread) {
LoadedApk packageInfo = new LoadedApk(mainThread);
- ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, 0);
- context.mResources = packageInfo.getResources(mainThread);
+ ContextImpl context = new ContextImpl(null, mainThread,
+ packageInfo, null, null, 0, null, null, Display.INVALID_DISPLAY);
context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
context.mResourcesManager.getDisplayMetrics());
return context;
@@ -2045,35 +2012,21 @@
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
- ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, 0);
- context.mResources = packageInfo.getResources(mainThread);
- return context;
+ return new ContextImpl(null, mainThread,
+ packageInfo, null, null, 0, null, null, Display.INVALID_DISPLAY);
}
static ContextImpl createActivityContext(ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken, int displayId,
Configuration overrideConfiguration) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
-
- ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityToken, null,
- 0);
-
- // Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY.
- displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY;
-
- final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY)
- ? packageInfo.getCompatibilityInfo()
- : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
-
- context.mResources = createResources(activityToken, packageInfo, displayId,
- overrideConfiguration, compatInfo);
- context.mDisplay = ResourcesManager.getInstance().getAdjustedDisplay(displayId,
- context.mResources.getDisplayAdjustments());
- return context;
+ return new ContextImpl(null, mainThread, packageInfo, activityToken, null, 0,
+ null, overrideConfiguration, displayId);
}
private ContextImpl(ContextImpl container, ActivityThread mainThread,
- LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags) {
+ LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
+ Display display, Configuration overrideConfiguration, int createDisplayWithId) {
mOuterContext = this;
// If creator didn't specify which storage to use, use the default
@@ -2100,11 +2053,64 @@
mPackageInfo = packageInfo;
mResourcesManager = ResourcesManager.getInstance();
+ final int displayId = (createDisplayWithId != Display.INVALID_DISPLAY)
+ ? createDisplayWithId
+ : (display != null) ? display.getDisplayId() : Display.DEFAULT_DISPLAY;
+
+ CompatibilityInfo compatInfo = null;
+ if (container != null) {
+ compatInfo = container.getDisplayAdjustments(displayId).getCompatibilityInfo();
+ }
+ if (compatInfo == null) {
+ compatInfo = (displayId == Display.DEFAULT_DISPLAY)
+ ? packageInfo.getCompatibilityInfo()
+ : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+ }
+
+ Resources resources = packageInfo.getResources(mainThread);
+ if (resources != null) {
+ if (displayId != Display.DEFAULT_DISPLAY
+ || overrideConfiguration != null
+ || (compatInfo != null && compatInfo.applicationScale
+ != resources.getCompatibilityInfo().applicationScale)) {
+
+ if (container != null) {
+ // This is a nested Context, so it can't be a base Activity context.
+ // Just create a regular Resources object associated with the Activity.
+ resources = mResourcesManager.getResources(
+ activityToken,
+ packageInfo.getResDir(),
+ packageInfo.getSplitResDirs(),
+ packageInfo.getOverlayDirs(),
+ packageInfo.getApplicationInfo().sharedLibraryFiles,
+ displayId,
+ overrideConfiguration,
+ compatInfo,
+ packageInfo.getClassLoader());
+ } else {
+ // This is not a nested Context, so it must be the root Activity context.
+ // All other nested Contexts will inherit the configuration set here.
+ resources = mResourcesManager.createBaseActivityResources(
+ activityToken,
+ packageInfo.getResDir(),
+ packageInfo.getSplitResDirs(),
+ packageInfo.getOverlayDirs(),
+ packageInfo.getApplicationInfo().sharedLibraryFiles,
+ displayId,
+ overrideConfiguration,
+ compatInfo,
+ packageInfo.getClassLoader());
+ }
+ }
+ }
+ mResources = resources;
+
+ mDisplay = (createDisplayWithId == Display.INVALID_DISPLAY) ? display
+ : mResourcesManager.getAdjustedDisplay(displayId, mResources.getDisplayAdjustments());
+
if (container != null) {
mBasePackageName = container.mBasePackageName;
mOpPackageName = container.mOpPackageName;
- mResources = container.getResources();
- mDisplay = container.getDisplay();
} else {
mBasePackageName = packageInfo.mPackageName;
ApplicationInfo ainfo = packageInfo.getApplicationInfo();
diff --git a/core/java/android/app/ContextImplFlagContextWrapper.java b/core/java/android/app/ContextImplFlagContextWrapper.java
deleted file mode 100644
index a83f130..0000000
--- a/core/java/android/app/ContextImplFlagContextWrapper.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.app;
-
-import android.content.Context;
-import android.content.ContextWrapper;
-
-class ContextImplFlagContextWrapper extends ContextWrapper {
- private final int mFlags;
-
- public ContextImplFlagContextWrapper(Context base, int flags) {
- super(base);
- mFlags = flags;
- }
-
- @Override
- public boolean isRestricted() {
- return (mFlags & Context.CONTEXT_RESTRICTED) != 0;
- }
-
- @Override
- public boolean isDeviceProtectedStorage() {
- return (mFlags & Context.CONTEXT_DEVICE_PROTECTED_STORAGE) != 0;
- }
-
- @Override
- public boolean isCredentialProtectedStorage() {
- return (mFlags & Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE) != 0;
- }
-}
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 8c9837b..6b962b9 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -138,7 +138,7 @@
void scheduleTranslucentConversionComplete(IBinder token, boolean timeout);
void setProcessState(int state);
void scheduleInstallProvider(in ProviderInfo provider);
- void updateTimePrefs(boolean is24Hour);
+ void updateTimePrefs(int timeFormatPreference);
void scheduleCancelVisibleBehind(IBinder token);
void scheduleBackgroundVisibleBehindChanged(IBinder token, boolean enabled);
void scheduleEnterAnimationComplete(IBinder token);
@@ -152,4 +152,4 @@
IVoiceInteractor voiceInteractor);
void handleTrustStorageUpdate();
void attachAgent(String path);
-}
\ No newline at end of file
+}
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index 5d27662..94d03e5 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -235,6 +235,7 @@
// >=0: registered and advertising started
private int mAdvertiserId;
private boolean mIsAdvertising = false;
+ private int registrationError = AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR;
public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback,
AdvertiseData advertiseData, AdvertiseData scanResponse,
@@ -262,12 +263,11 @@
mLeAdvertisers.put(mAdvertiseCallback, this);
} else if (mAdvertiserId < 0) {
- // Registration timeout, reset mClientIf to -1 so no subsequent operations can
+ // Registration timeout, reset mClientIf to -2 so no subsequent operations can
// proceed.
- if (mAdvertiserId == 0) mAdvertiserId = -2;
+ if (mAdvertiserId == -1) mAdvertiserId = -2;
// Post internal error if registration failed.
- postStartFailure(mAdvertiseCallback,
- AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
+ postStartFailure(mAdvertiseCallback, registrationError);
} else {
// Unregister application if it's already registered but advertise failed.
try {
@@ -318,6 +318,8 @@
} catch (RemoteException e) {
Log.e(TAG, "failed to start advertising", e);
}
+ } else if (status == AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS) {
+ registrationError = status;
}
// Registration failed.
mAdvertiserId = -2;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index c87de9a..50589fe 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4186,13 +4186,21 @@
= "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
/**
- * Optional boolean extra for {@link #ACTION_TIME_CHANGED} that indicates the
- * user has set their time format preferences to the 24 hour format.
+ * Optional int extra for {@link #ACTION_TIME_CHANGED} that indicates the
+ * user has set their time format preference. See {@link #EXTRA_TIME_PREF_VALUE_USE_12_HOUR},
+ * {@link #EXTRA_TIME_PREF_VALUE_USE_24_HOUR} and
+ * {@link #EXTRA_TIME_PREF_VALUE_USE_LOCALE_DEFAULT}. The value must not be negative.
*
* @hide for internal use only.
*/
public static final String EXTRA_TIME_PREF_24_HOUR_FORMAT =
"android.intent.extra.TIME_PREF_24_HOUR_FORMAT";
+ /** @hide */
+ public static final int EXTRA_TIME_PREF_VALUE_USE_12_HOUR = 0;
+ /** @hide */
+ public static final int EXTRA_TIME_PREF_VALUE_USE_24_HOUR = 1;
+ /** @hide */
+ public static final int EXTRA_TIME_PREF_VALUE_USE_LOCALE_DEFAULT = 2;
/** {@hide} */
public static final String EXTRA_REASON = "android.intent.extra.REASON";
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 44ae6ee..760df45 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -316,6 +316,16 @@
stringToFile(file.getAbsolutePath(), string);
}
+ /*
+ * Writes the bytes given in {@code content} to the file whose absolute path
+ * is {@code filename}.
+ */
+ public static void bytesToFile(String filename, byte[] content) throws IOException {
+ try (FileOutputStream fos = new FileOutputStream(filename)) {
+ fos.write(content);
+ }
+ }
+
/**
* Writes string to file. Basically same as "echo -n $string > $filename"
*
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index d31036c..6a751e8 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -77,6 +77,7 @@
private static native boolean native_get_boolean(String key, boolean def);
private static native void native_set(String key, String def);
private static native void native_add_change_callback();
+ private static native void native_report_sysprop_change();
/**
* Get the value for the given key.
@@ -195,4 +196,11 @@
}
}
}
+
+ /*
+ * Notifies listeners that a system property has changed
+ */
+ public static void reportSyspropChanged() {
+ native_report_sysprop_change();
+ }
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index afbc09b..0946906 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -369,6 +369,22 @@
"android.settings.WIFI_IP_SETTINGS";
/**
+ * Activity Action: Show settings to allow configuration of Wi-Fi saved networks.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_WIFI_SAVED_NETWORK_SETTINGS =
+ "android.settings.WIFI_SAVED_NETWORK_SETTINGS";
+
+ /**
* Activity Action: Show settings to allow configuration of Bluetooth.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
diff --git a/core/java/android/util/BootTimingsTraceLog.java b/core/java/android/util/BootTimingsTraceLog.java
new file mode 100644
index 0000000..2e4319c
--- /dev/null
+++ b/core/java/android/util/BootTimingsTraceLog.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.util;
+
+import android.os.Build;
+import android.os.SystemClock;
+import android.os.Trace;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+/**
+ * Helper class for reporting boot timing metrics.
+ * @hide
+ */
+public class BootTimingsTraceLog {
+ // Debug boot time for every step if it's non-user build.
+ private static final boolean DEBUG_BOOT_TIME = !"user".equals(Build.TYPE);
+ private final Deque<Pair<String, Long>> mStartTimes
+ = DEBUG_BOOT_TIME ? new ArrayDeque<>() : null;
+ private final String mTag;
+ private long mTraceTag;
+
+ public BootTimingsTraceLog(String tag, long traceTag) {
+ mTag = tag;
+ mTraceTag = traceTag;
+ }
+
+ public void traceBegin(String name) {
+ Trace.traceBegin(mTraceTag, name);
+ if (DEBUG_BOOT_TIME) {
+ mStartTimes.push(Pair.create(name, SystemClock.elapsedRealtime()));
+ }
+ }
+
+ public void traceEnd() {
+ Trace.traceEnd(mTraceTag);
+ if (!DEBUG_BOOT_TIME) {
+ return;
+ }
+ if (mStartTimes.peek() == null) {
+ Slog.w(mTag, "traceEnd called more times than traceBegin");
+ return;
+ }
+ Pair<String, Long> event = mStartTimes.pop();
+ // Log the duration so it can be parsed by external tools for performance reporting
+ Slog.d(mTag, event.first + " took to complete: "
+ + (SystemClock.elapsedRealtime() - event.second) + "ms");
+ }
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 02a8521..84d7548 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -37,6 +37,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
+import android.annotation.TestApi;
import android.annotation.UiThread;
import android.content.ClipData;
import android.content.Context;
@@ -111,6 +112,7 @@
import com.android.internal.R;
import com.android.internal.util.Predicate;
+import com.android.internal.view.TooltipPopup;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.widget.ScrollBarUtils;
@@ -1196,6 +1198,12 @@
private static Paint sDebugPaint;
+ /**
+ * <p>Indicates this view can display a tooltip on hover or long press.</p>
+ * {@hide}
+ */
+ static final int TOOLTIP = 0x40000000;
+
/** @hide */
@IntDef(flag = true,
value = {
@@ -3619,6 +3627,39 @@
ListenerInfo mListenerInfo;
+ private static class TooltipInfo {
+ /**
+ * Text to be displayed in a tooltip popup.
+ */
+ @Nullable
+ CharSequence mTooltip;
+
+ /**
+ * View-relative position of the tooltip anchor point.
+ */
+ int mAnchorX;
+ int mAnchorY;
+
+ /**
+ * The tooltip popup.
+ */
+ @Nullable
+ TooltipPopup mTooltipPopup;
+
+ /**
+ * Set to true if the tooltip was shown as a result of a long click.
+ */
+ boolean mTooltipFromLongClick;
+
+ /**
+ * Keep these Runnables so that they can be used to reschedule.
+ */
+ Runnable mShowTooltipRunnable;
+ Runnable mHideTooltipRunnable;
+ }
+
+ TooltipInfo mTooltipInfo;
+
// Temporary values used to hold (x,y) coordinates when delegating from the
// two-arg performLongClick() method to the legacy no-arg version.
private float mLongClickX = Float.NaN;
@@ -4576,6 +4617,9 @@
}
break;
+ case R.styleable.View_tooltip:
+ setTooltip(a.getText(attr));
+ break;
}
}
@@ -5712,6 +5756,11 @@
final boolean isAnchored = !Float.isNaN(x) && !Float.isNaN(y);
handled = isAnchored ? showContextMenu(x, y) : showContextMenu();
}
+ if ((mViewFlags & TOOLTIP) == TOOLTIP) {
+ if (!handled) {
+ handled = showLongClickTooltip((int) x, (int) y);
+ }
+ }
if (handled) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
@@ -10603,17 +10652,21 @@
return true;
}
- // Long clickable items don't necessarily have to be clickable.
- if (((mViewFlags & CLICKABLE) == CLICKABLE
- || (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
- && (event.getRepeatCount() == 0)) {
- // For the purposes of menu anchoring and drawable hotspots,
- // key events are considered to be at the center of the view.
- final float x = getWidth() / 2f;
- final float y = getHeight() / 2f;
- setPressed(true, x, y);
- checkForLongClick(0, x, y);
- return true;
+ if (event.getRepeatCount() == 0) {
+ // Long clickable items don't necessarily have to be clickable.
+ final boolean clickable = (mViewFlags & CLICKABLE) == CLICKABLE
+ || (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE;
+ if (clickable || (mViewFlags & TOOLTIP) == TOOLTIP) {
+ // For the purposes of menu anchoring and drawable hotspots,
+ // key events are considered to be at the center of the view.
+ final float x = getWidth() / 2f;
+ final float y = getHeight() / 2f;
+ if (clickable) {
+ setPressed(true, x, y);
+ }
+ checkForLongClick(0, x, y);
+ return true;
+ }
}
}
@@ -11160,15 +11213,17 @@
final int viewFlags = mViewFlags;
final int action = event.getAction();
+ final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
+ || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
+ || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
+
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
- return (((viewFlags & CLICKABLE) == CLICKABLE
- || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
- || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
+ return clickable;
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
@@ -11176,11 +11231,20 @@
}
}
- if (((viewFlags & CLICKABLE) == CLICKABLE ||
- (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
- (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
+ if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
+ if ((viewFlags & TOOLTIP) == TOOLTIP) {
+ handleTooltipUp();
+ }
+ if (!clickable) {
+ removeTapCallback();
+ removeLongPressCallback();
+ mInContextButtonPress = false;
+ mHasPerformedLongPress = false;
+ mIgnoreNextUpEvent = false;
+ break;
+ }
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
@@ -11196,7 +11260,7 @@
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
- }
+ }
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
@@ -11236,6 +11300,11 @@
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
+ if (!clickable) {
+ checkForLongClick(0, x, y);
+ break;
+ }
+
if (performButtonActionOnTouchDown(event)) {
break;
}
@@ -11261,7 +11330,9 @@
break;
case MotionEvent.ACTION_CANCEL:
- setPressed(false);
+ if (clickable) {
+ setPressed(false);
+ }
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
@@ -11270,16 +11341,17 @@
break;
case MotionEvent.ACTION_MOVE:
- drawableHotspotChanged(x, y);
+ if (clickable) {
+ drawableHotspotChanged(x, y);
+ }
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
+ // Remove any future long press/tap checks
removeTapCallback();
+ removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
- // Remove any future long press/tap checks
- removeLongPressCallback();
-
setPressed(false);
}
}
@@ -11311,7 +11383,7 @@
*/
private void removeLongPressCallback() {
if (mPendingCheckForLongPress != null) {
- removeCallbacks(mPendingCheckForLongPress);
+ removeCallbacks(mPendingCheckForLongPress);
}
}
@@ -15379,6 +15451,10 @@
cleanupDraw();
mCurrentAnimation = null;
+
+ if ((mViewFlags & TOOLTIP) == TOOLTIP) {
+ hideTooltip();
+ }
}
private void cleanupDraw() {
@@ -21031,7 +21107,7 @@
}
private void checkForLongClick(int delayOffset, float x, float y) {
- if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
+ if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
@@ -21039,6 +21115,7 @@
}
mPendingCheckForLongPress.setAnchor(x, y);
mPendingCheckForLongPress.rememberWindowAttachCount();
+ mPendingCheckForLongPress.rememberPressedState();
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
@@ -22439,10 +22516,11 @@
private int mOriginalWindowAttachCount;
private float mX;
private float mY;
+ private boolean mOriginalPressedState;
@Override
public void run() {
- if (isPressed() && (mParent != null)
+ if ((mOriginalPressedState == isPressed()) && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
if (performLongClick(mX, mY)) {
mHasPerformedLongPress = true;
@@ -22458,6 +22536,10 @@
public void rememberWindowAttachCount() {
mOriginalWindowAttachCount = mWindowAttachCount;
}
+
+ public void rememberPressedState() {
+ mOriginalPressedState = isPressed();
+ }
}
private final class CheckForTap implements Runnable {
@@ -23246,6 +23328,12 @@
*/
public Surface mDragSurface;
+
+ /**
+ * The view that currently has a tooltip displayed.
+ */
+ View mTooltipHost;
+
/**
* Creates a new set of attachment information with the specified
* events handler and thread.
@@ -23982,4 +24070,167 @@
return mAttachInfo.mTmpLocation[0] == insets.getStableInsetLeft()
&& mAttachInfo.mTmpLocation[1] == insets.getStableInsetTop();
}
+
+ /**
+ * Sets the tooltip text which will be displayed in a small popup next to the view.
+ * <p>
+ * The tooltip will be displayed:
+ * <li>On long click, unless is not handled otherwise (by OnLongClickListener or a context
+ * menu). </li>
+ * <li>On hover, after a brief delay since the pointer has stopped moving </li>
+ *
+ * @param tooltip the tooltip text, or null if no tooltip is required
+ */
+ public final void setTooltip(@Nullable CharSequence tooltip) {
+ if (TextUtils.isEmpty(tooltip)) {
+ setFlags(0, TOOLTIP);
+ hideTooltip();
+ mTooltipInfo = null;
+ } else {
+ setFlags(TOOLTIP, TOOLTIP);
+ if (mTooltipInfo == null) {
+ mTooltipInfo = new TooltipInfo();
+ mTooltipInfo.mShowTooltipRunnable = this::showHoverTooltip;
+ mTooltipInfo.mHideTooltipRunnable = this::hideTooltip;
+ }
+ mTooltipInfo.mTooltip = tooltip;
+ if (mTooltipInfo.mTooltipPopup != null && mTooltipInfo.mTooltipPopup.isShowing()) {
+ mTooltipInfo.mTooltipPopup.updateContent(mTooltipInfo.mTooltip);
+ }
+ }
+ }
+
+ /**
+ * Returns the view's tooltip text.
+ *
+ * @return the tooltip text
+ */
+ @Nullable
+ public final CharSequence getTooltip() {
+ return mTooltipInfo != null ? mTooltipInfo.mTooltip : null;
+ }
+
+ private boolean showTooltip(int x, int y, boolean fromLongClick) {
+ if (mAttachInfo == null) {
+ return false;
+ }
+ if ((mViewFlags & ENABLED_MASK) != ENABLED) {
+ return false;
+ }
+ final CharSequence tooltipText = getTooltip();
+ if (TextUtils.isEmpty(tooltipText)) {
+ return false;
+ }
+ hideTooltip();
+ mTooltipInfo.mTooltipFromLongClick = fromLongClick;
+ mTooltipInfo.mTooltipPopup = new TooltipPopup(getContext());
+ mTooltipInfo.mTooltipPopup.show(this, x, y, tooltipText);
+ mAttachInfo.mTooltipHost = this;
+ return true;
+ }
+
+ void hideTooltip() {
+ if (mTooltipInfo == null) {
+ return;
+ }
+ removeCallbacks(mTooltipInfo.mShowTooltipRunnable);
+ if (mTooltipInfo.mTooltipPopup == null) {
+ return;
+ }
+ mTooltipInfo.mTooltipPopup.hide();
+ mTooltipInfo.mTooltipPopup = null;
+ mTooltipInfo.mTooltipFromLongClick = false;
+ if (mAttachInfo != null) {
+ mAttachInfo.mTooltipHost = null;
+ }
+ }
+
+ private boolean showLongClickTooltip(int x, int y) {
+ removeCallbacks(mTooltipInfo.mShowTooltipRunnable);
+ removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
+ return showTooltip(x, y, true);
+ }
+
+ private void showHoverTooltip() {
+ showTooltip(mTooltipInfo.mAnchorX, mTooltipInfo.mAnchorY, false);
+ }
+
+ boolean dispatchTooltipHoverEvent(MotionEvent event) {
+ if (mTooltipInfo == null) {
+ return false;
+ }
+ switch(event.getAction()) {
+ case MotionEvent.ACTION_HOVER_MOVE:
+ if ((mViewFlags & TOOLTIP) != TOOLTIP || (mViewFlags & ENABLED_MASK) != ENABLED) {
+ break;
+ }
+ if (!mTooltipInfo.mTooltipFromLongClick) {
+ if (mTooltipInfo.mTooltipPopup == null) {
+ // Schedule showing the tooltip after a timeout.
+ mTooltipInfo.mAnchorX = (int) event.getX();
+ mTooltipInfo.mAnchorY = (int) event.getY();
+ removeCallbacks(mTooltipInfo.mShowTooltipRunnable);
+ postDelayed(mTooltipInfo.mShowTooltipRunnable,
+ ViewConfiguration.getHoverTooltipShowTimeout());
+ }
+
+ // Hide hover-triggered tooltip after a period of inactivity.
+ // Match the timeout used by NativeInputManager to hide the mouse pointer
+ // (depends on SYSTEM_UI_FLAG_LOW_PROFILE being set).
+ final int timeout;
+ if ((getWindowSystemUiVisibility() & SYSTEM_UI_FLAG_LOW_PROFILE)
+ == SYSTEM_UI_FLAG_LOW_PROFILE) {
+ timeout = ViewConfiguration.getHoverTooltipHideShortTimeout();
+ } else {
+ timeout = ViewConfiguration.getHoverTooltipHideTimeout();
+ }
+ removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
+ postDelayed(mTooltipInfo.mHideTooltipRunnable, timeout);
+ }
+ return true;
+
+ case MotionEvent.ACTION_HOVER_EXIT:
+ if (!mTooltipInfo.mTooltipFromLongClick) {
+ hideTooltip();
+ }
+ break;
+ }
+ return false;
+ }
+
+ void handleTooltipKey(KeyEvent event) {
+ switch (event.getAction()) {
+ case KeyEvent.ACTION_DOWN:
+ if (event.getRepeatCount() == 0) {
+ hideTooltip();
+ }
+ break;
+
+ case KeyEvent.ACTION_UP:
+ handleTooltipUp();
+ break;
+ }
+ }
+
+ private void handleTooltipUp() {
+ if (mTooltipInfo == null || mTooltipInfo.mTooltipPopup == null) {
+ return;
+ }
+ removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
+ postDelayed(mTooltipInfo.mHideTooltipRunnable,
+ ViewConfiguration.getLongPressTooltipHideTimeout());
+ }
+
+ /**
+ * @return The content view of the tooltip popup currently being shown, or null if the tooltip
+ * is not showing.
+ * @hide
+ */
+ @TestApi
+ public View getTooltipView() {
+ if (mTooltipInfo == null || mTooltipInfo.mTooltipPopup == null) {
+ return null;
+ }
+ return mTooltipInfo.mTooltipPopup.getContentView();
+ }
}
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 33b488f..6d2f850 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -16,6 +16,7 @@
package android.view;
+import android.annotation.TestApi;
import android.app.AppGlobals;
import android.content.Context;
import android.content.res.Configuration;
@@ -230,6 +231,29 @@
private static final long ACTION_MODE_HIDE_DURATION_DEFAULT = 2000;
/**
+ * Defines the duration in milliseconds before an end of a long press causes a tooltip to be
+ * hidden.
+ */
+ private static final int LONG_PRESS_TOOLTIP_HIDE_TIMEOUT = 1500;
+
+ /**
+ * Defines the duration in milliseconds before a hover event causes a tooltip to be shown.
+ */
+ private static final int HOVER_TOOLTIP_SHOW_TIMEOUT = 500;
+
+ /**
+ * Defines the duration in milliseconds before mouse inactivity causes a tooltip to be hidden.
+ * (default variant to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is not set).
+ */
+ private static final int HOVER_TOOLTIP_HIDE_TIMEOUT = 15000;
+
+ /**
+ * Defines the duration in milliseconds before mouse inactivity causes a tooltip to be hidden
+ * (short version to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is set).
+ */
+ private static final int HOVER_TOOLTIP_HIDE_SHORT_TIMEOUT = 3000;
+
+ /**
* Configuration values for overriding {@link #hasPermanentMenuKey()} behavior.
* These constants must match the definition in res/values/config.xml.
*/
@@ -800,4 +824,43 @@
public boolean isFadingMarqueeEnabled() {
return mFadingMarqueeEnabled;
}
+
+ /**
+ * @return the duration in milliseconds before an end of a long press causes a tooltip to be
+ * hidden
+ * @hide
+ */
+ @TestApi
+ public static int getLongPressTooltipHideTimeout() {
+ return LONG_PRESS_TOOLTIP_HIDE_TIMEOUT;
+ }
+
+ /**
+ * @return the duration in milliseconds before a hover event causes a tooltip to be shown
+ * @hide
+ */
+ @TestApi
+ public static int getHoverTooltipShowTimeout() {
+ return HOVER_TOOLTIP_SHOW_TIMEOUT;
+ }
+
+ /**
+ * @return the duration in milliseconds before mouse inactivity causes a tooltip to be hidden
+ * (default variant to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is not set).
+ * @hide
+ */
+ @TestApi
+ public static int getHoverTooltipHideTimeout() {
+ return HOVER_TOOLTIP_HIDE_TIMEOUT;
+ }
+
+ /**
+ * @return the duration in milliseconds before mouse inactivity causes a tooltip to be hidden
+ * (shorter variant to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is set).
+ * @hide
+ */
+ @TestApi
+ public static int getHoverTooltipHideShortTimeout() {
+ return HOVER_TOOLTIP_HIDE_SHORT_TIMEOUT;
+ }
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index e39cb96..c0191ce 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -199,6 +199,13 @@
// It might not have actually handled the hover event.
private boolean mHoveredSelf;
+ // The child capable of showing a tooltip and currently under the pointer.
+ private View mTooltipHoverTarget;
+
+ // True if the view group is capable of showing a tooltip and the pointer is directly
+ // over the view group but not one of its child views.
+ private boolean mTooltipHoveredSelf;
+
/**
* Internal flags.
*
@@ -1970,6 +1977,104 @@
}
}
+ @Override
+ boolean dispatchTooltipHoverEvent(MotionEvent event) {
+ final int action = event.getAction();
+ switch (action) {
+ case MotionEvent.ACTION_HOVER_ENTER:
+ break;
+
+ case MotionEvent.ACTION_HOVER_MOVE:
+ View newTarget = null;
+
+ // Check what the child under the pointer says about the tooltip.
+ final int childrenCount = mChildrenCount;
+ if (childrenCount != 0) {
+ final float x = event.getX();
+ final float y = event.getY();
+
+ final ArrayList<View> preorderedList = buildOrderedChildList();
+ final boolean customOrder = preorderedList == null
+ && isChildrenDrawingOrderEnabled();
+ final View[] children = mChildren;
+ for (int i = childrenCount - 1; i >= 0; i--) {
+ final int childIndex =
+ getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+ final View child =
+ getAndVerifyPreorderedView(preorderedList, children, childIndex);
+ final PointF point = getLocalPoint();
+ if (isTransformedTouchPointInView(x, y, child, point)) {
+ if (dispatchTooltipHoverEvent(event, child)) {
+ newTarget = child;
+ }
+ break;
+ }
+ }
+ if (preorderedList != null) preorderedList.clear();
+ }
+
+ if (mTooltipHoverTarget != newTarget) {
+ if (mTooltipHoverTarget != null) {
+ event.setAction(MotionEvent.ACTION_HOVER_EXIT);
+ mTooltipHoverTarget.dispatchTooltipHoverEvent(event);
+ event.setAction(action);
+ }
+ mTooltipHoverTarget = newTarget;
+ }
+
+ if (mTooltipHoverTarget != null) {
+ if (mTooltipHoveredSelf) {
+ mTooltipHoveredSelf = false;
+ event.setAction(MotionEvent.ACTION_HOVER_EXIT);
+ super.dispatchTooltipHoverEvent(event);
+ event.setAction(action);
+ }
+ return true;
+ }
+
+ mTooltipHoveredSelf = super.dispatchTooltipHoverEvent(event);
+ return mTooltipHoveredSelf;
+
+ case MotionEvent.ACTION_HOVER_EXIT:
+ if (mTooltipHoverTarget != null) {
+ mTooltipHoverTarget.dispatchTooltipHoverEvent(event);
+ mTooltipHoverTarget = null;
+ } else if (mTooltipHoveredSelf) {
+ super.dispatchTooltipHoverEvent(event);
+ mTooltipHoveredSelf = false;
+ }
+ break;
+ }
+ return false;
+ }
+
+ private boolean dispatchTooltipHoverEvent(MotionEvent event, View child) {
+ final boolean result;
+ if (!child.hasIdentityMatrix()) {
+ MotionEvent transformedEvent = getTransformedMotionEvent(event, child);
+ result = child.dispatchTooltipHoverEvent(transformedEvent);
+ transformedEvent.recycle();
+ } else {
+ final float offsetX = mScrollX - child.mLeft;
+ final float offsetY = mScrollY - child.mTop;
+ event.offsetLocation(offsetX, offsetY);
+ result = child.dispatchTooltipHoverEvent(event);
+ event.offsetLocation(-offsetX, -offsetY);
+ }
+ return result;
+ }
+
+ private void exitTooltipHoverTargets() {
+ if (mTooltipHoveredSelf || mTooltipHoverTarget != null) {
+ final long now = SystemClock.uptimeMillis();
+ MotionEvent event = MotionEvent.obtain(now, now,
+ MotionEvent.ACTION_HOVER_EXIT, 0.0f, 0.0f, 0);
+ event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+ dispatchTooltipHoverEvent(event);
+ event.recycle();
+ }
+ }
+
/** @hide */
@Override
protected boolean hasHoveredChild() {
@@ -3186,6 +3291,7 @@
// Similarly, set ACTION_EXIT to all hover targets and clear them.
exitHoverTargets();
+ exitTooltipHoverTargets();
// In case view is detached while transition is running
mLayoutCalledWhileSuppressed = false;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1ff8fb0..e030e76 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3589,6 +3589,10 @@
mAttachInfo.mKeyDispatchState.reset();
mView.dispatchWindowFocusChanged(hasWindowFocus);
mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
+
+ if (mAttachInfo.mTooltipHost != null) {
+ mAttachInfo.mTooltipHost.hideTooltip();
+ }
}
// Note: must be done after the focus change callbacks,
@@ -4206,6 +4210,10 @@
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
+ if (mAttachInfo.mTooltipHost != null) {
+ mAttachInfo.mTooltipHost.handleTooltipKey(event);
+ }
+
// If the key's purpose is to exit touch mode then we consume it
// and consider it handled.
if (checkForLeavingTouchModeAndConsume(event)) {
@@ -4232,6 +4240,10 @@
ensureTouchMode(true);
}
+ if (action == MotionEvent.ACTION_DOWN && mAttachInfo.mTooltipHost != null) {
+ mAttachInfo.mTooltipHost.hideTooltip();
+ }
+
// Offset the scroll position.
if (mCurScrollY != 0) {
event.offsetLocation(0, mCurScrollY);
@@ -4425,6 +4437,7 @@
mAttachInfo.mHandlingPointerEvent = true;
boolean handled = eventTarget.dispatchPointerEvent(event);
maybeUpdatePointerIcon(event);
+ maybeUpdateTooltip(event);
mAttachInfo.mHandlingPointerEvent = false;
if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
mUnbufferedInputDispatch = true;
@@ -4512,6 +4525,27 @@
return true;
}
+ private void maybeUpdateTooltip(MotionEvent event) {
+ if (event.getPointerCount() != 1) {
+ return;
+ }
+ final int action = event.getActionMasked();
+ if (action != MotionEvent.ACTION_HOVER_ENTER
+ && action != MotionEvent.ACTION_HOVER_MOVE
+ && action != MotionEvent.ACTION_HOVER_EXIT) {
+ return;
+ }
+ AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
+ if (manager.isEnabled() && manager.isTouchExplorationEnabled()) {
+ return;
+ }
+ if (mView == null) {
+ Slog.d(mTag, "maybeUpdateTooltip called after view was removed");
+ return;
+ }
+ mView.dispatchTooltipHoverEvent(event);
+ }
+
/**
* Performs synthesis of new input events from unhandled input events.
*/
diff --git a/core/java/com/android/internal/os/WrapperInit.java b/core/java/com/android/internal/os/WrapperInit.java
index 594b6ab..c03bcdf 100644
--- a/core/java/com/android/internal/os/WrapperInit.java
+++ b/core/java/com/android/internal/os/WrapperInit.java
@@ -17,6 +17,8 @@
package com.android.internal.os;
import android.os.Process;
+import android.os.Trace;
+import android.util.BootTimingsTraceLog;
import android.util.Slog;
import dalvik.system.VMRuntime;
@@ -75,7 +77,8 @@
}
// Mimic system Zygote preloading.
- ZygoteInit.preload();
+ ZygoteInit.preload(new BootTimingsTraceLog("WrapperInitTiming",
+ Trace.TRACE_TAG_DALVIK));
// Launch the application.
String[] runtimeArgs = new String[args.length - 2];
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index e1118d8..cdd267e 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -36,11 +36,13 @@
import android.system.Os;
import android.system.OsConstants;
import android.text.Hyphenator;
+import android.util.BootTimingsTraceLog;
import android.util.EventLog;
import android.util.Log;
import android.webkit.WebViewFactory;
import android.widget.TextView;
+import com.android.internal.logging.MetricsLogger;
import com.android.internal.os.InstallerConnection.InstallerException;
import dalvik.system.DexFile;
@@ -106,20 +108,20 @@
private static final int ROOT_UID = 0;
private static final int ROOT_GID = 0;
- static void preload() {
+ static void preload(BootTimingsTraceLog bootTimingsTraceLog) {
Log.d(TAG, "begin preload");
- Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "BeginIcuCachePinning");
+ bootTimingsTraceLog.traceBegin("BeginIcuCachePinning");
beginIcuCachePinning();
- Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
- Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadClasses");
+ bootTimingsTraceLog.traceEnd(); // BeginIcuCachePinning
+ bootTimingsTraceLog.traceBegin("PreloadClasses");
preloadClasses();
- Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
- Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadResources");
+ bootTimingsTraceLog.traceEnd(); // PreloadClasses
+ bootTimingsTraceLog.traceBegin("PreloadResources");
preloadResources();
- Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
- Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadOpenGL");
+ bootTimingsTraceLog.traceEnd(); // PreloadResources
+ bootTimingsTraceLog.traceBegin("PreloadOpenGL");
preloadOpenGL();
- Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
+ bootTimingsTraceLog.traceEnd(); // PreloadOpenGL
preloadSharedLibraries();
preloadTextResources();
// Ask the WebViewFactory to do any initialization that must run in the zygote process,
@@ -639,7 +641,13 @@
}
try {
- Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "ZygoteInit");
+ // Report Zygote start time to tron
+ MetricsLogger.histogram(null, "boot_zygote_init", (int) SystemClock.uptimeMillis());
+
+ String bootTimeTag = Process.is64Bit() ? "Zygote64Timing" : "Zygote32Timing";
+ BootTimingsTraceLog bootTimingsTraceLog = new BootTimingsTraceLog(bootTimeTag,
+ Trace.TRACE_TAG_DALVIK);
+ bootTimingsTraceLog.traceBegin("ZygoteInit");
RuntimeInit.enableDdms();
// Start profiling the zygote initialization.
SamplingProfilerIntegration.start();
@@ -664,22 +672,23 @@
}
zygoteServer.registerServerSocket(socketName);
- Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "ZygotePreload");
+ bootTimingsTraceLog.traceBegin("ZygotePreload");
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
SystemClock.uptimeMillis());
- preload();
+ preload(bootTimingsTraceLog);
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
SystemClock.uptimeMillis());
- Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
+ bootTimingsTraceLog.traceEnd(); // ZygotePreload
// Finish profiling the zygote initialization.
SamplingProfilerIntegration.writeZygoteSnapshot();
// Do an initial gc to clean up after startup
- Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PostZygoteInitGC");
+ bootTimingsTraceLog.traceBegin("PostZygoteInitGC");
gcAndFinalize();
- Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
+ bootTimingsTraceLog.traceEnd(); // PostZygoteInitGC
+ bootTimingsTraceLog.traceEnd(); // ZygoteInit
// Disable tracing so that forked processes do not inherit stale tracing tags from
// Zygote.
Trace.setTracingEnabled(false);
diff --git a/core/java/com/android/internal/view/TooltipPopup.java b/core/java/com/android/internal/view/TooltipPopup.java
new file mode 100644
index 0000000..4f48b96
--- /dev/null
+++ b/core/java/com/android/internal/view/TooltipPopup.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+public class TooltipPopup {
+ private final Context mContext;
+
+ private final View mContentView;
+ private final TextView mMessageView;
+
+ private final WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams();
+ private final Rect mTmpDisplayFrame = new Rect();
+ private final int[] mTmpAnchorPos = new int[2];
+
+ public TooltipPopup(Context context) {
+ mContext = context;
+
+ mContentView = LayoutInflater.from(mContext).inflate(
+ com.android.internal.R.layout.tooltip, null);
+ mMessageView = (TextView) mContentView.findViewById(
+ com.android.internal.R.id.message);
+
+ mLayoutParams.setTitle(
+ mContext.getString(com.android.internal.R.string.tooltip_popup_title));
+ mLayoutParams.packageName = mContext.getOpPackageName();
+ mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL;
+ mLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
+ mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ mLayoutParams.format = PixelFormat.TRANSLUCENT;
+ mLayoutParams.windowAnimations = com.android.internal.R.style.Animation_Tooltip;
+ mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+ }
+
+ public void show(View anchorView, int anchorX, int anchorY, CharSequence tooltipText) {
+ if (isShowing()) {
+ hide();
+ }
+
+ mMessageView.setText(tooltipText);
+
+ computePosition(anchorView, anchorX, anchorY, mLayoutParams);
+
+ WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+ wm.addView(mContentView, mLayoutParams);
+ }
+
+ public void hide() {
+ if (!isShowing()) {
+ return;
+ }
+
+ WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+ wm.removeView(mContentView);
+ }
+
+ public View getContentView() {
+ return mContentView;
+ }
+
+ public boolean isShowing() {
+ return mContentView.getParent() != null;
+ }
+
+ public void updateContent(CharSequence tooltipText) {
+ mMessageView.setText(tooltipText);
+ }
+
+ private void computePosition(View anchorView, int anchorX, int anchorY,
+ WindowManager.LayoutParams outParams) {
+ final int tooltipPreciseAnchorThreshold = mContext.getResources().getDimensionPixelOffset(
+ com.android.internal.R.dimen.tooltip_precise_anchor_threshold);
+
+ final int offsetX;
+ if (anchorView.getWidth() >= tooltipPreciseAnchorThreshold) {
+ // Wide view. Align the tooltip horizontally to the precise X position.
+ offsetX = anchorX;
+ } else {
+ // Otherwise anchor the tooltip to the view center.
+ offsetX = anchorView.getWidth() / 2; // Center on the view horizontally.
+ }
+
+ final int offsetBelow;
+ final int offsetAbove;
+ if (anchorView.getHeight() >= tooltipPreciseAnchorThreshold) {
+ // Tall view. Align the tooltip vertically to the precise Y position.
+ offsetBelow = anchorY;
+ offsetAbove = anchorY;
+ } else {
+ // Otherwise anchor the tooltip to the view center.
+ offsetBelow = anchorView.getHeight(); // Place below the view in most cases.
+ offsetAbove = 0; // Place above the view if the tooltip does not fit below.
+ }
+
+ outParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
+
+ final int tooltipOffset = mContext.getResources().getDimensionPixelOffset(
+ com.android.internal.R.dimen.tooltip_y_offset);
+
+ anchorView.getWindowVisibleDisplayFrame(mTmpDisplayFrame);
+ anchorView.getLocationInWindow(mTmpAnchorPos);
+ outParams.x = mTmpAnchorPos[0] + offsetX - mTmpDisplayFrame.width() / 2;
+ outParams.y = mTmpAnchorPos[1] + offsetBelow + tooltipOffset;
+
+ final int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ mContentView.measure(spec, spec);
+ final int tooltipHeight = mContentView.getMeasuredHeight();
+
+ if (outParams.y + tooltipHeight > mTmpDisplayFrame.height()) {
+ // The tooltip does not fit below the anchor point, show above instead.
+ outParams.y = mTmpAnchorPos[1] + offsetAbove - (tooltipOffset + tooltipHeight);
+ }
+ }
+}
diff --git a/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
index 4b279f63..b23757e 100644
--- a/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
+++ b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
@@ -22,256 +22,89 @@
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/Log.h>
-#include "activity_recognition.h"
-
-
-// keep base connection data from the HAL
-static activity_recognition_module_t* sModule = NULL;
-static activity_recognition_device_t* sDevice = NULL;
-
-static jobject sCallbacksObject = NULL;
-static jmethodID sOnActivityChanged = NULL;
-
-
-static void check_and_clear_exceptions(JNIEnv* env, const char* method_name) {
- if (!env->ExceptionCheck()) {
- return;
- }
-
- ALOGE("An exception was thrown by '%s'.", method_name);
- LOGE_EX(env);
- env->ExceptionClear();
-}
-
-static jint attach_thread(JNIEnv** env) {
- JavaVM* java_vm = android::AndroidRuntime::getJavaVM();
- assert(java_vm != NULL);
-
- JavaVMAttachArgs args = {
- JNI_VERSION_1_6,
- "ActivityRecognition HAL callback.",
- NULL /* group */
- };
-
- jint result = java_vm->AttachCurrentThread(env, &args);
- if (result != JNI_OK) {
- ALOGE("Attach to callback thread failed: %d", result);
- }
-
- return result;
-}
-
-static jint detach_thread() {
- JavaVM* java_vm = android::AndroidRuntime::getJavaVM();
- assert(java_vm != NULL);
-
- jint result = java_vm->DetachCurrentThread();
- if (result != JNI_OK) {
- ALOGE("Detach of callback thread failed: %d", result);
- }
-
- return result;
-}
-
-
-/**
- * Handle activity recognition events from HAL.
- */
-static void activity_callback(
- const activity_recognition_callback_procs_t* procs,
- const activity_event_t* events,
- int count) {
- if (sOnActivityChanged == NULL) {
- ALOGE("Dropping activity_callback because onActivityChanged handler is null.");
- return;
- }
-
- if (events == NULL || count <= 0) {
- ALOGE("Invalid activity_callback. Count: %d, Events: %p", count, events);
- return;
- }
-
- JNIEnv* env = NULL;
- int result = attach_thread(&env);
- if (result != JNI_OK) {
- ALOGE("Unable to attach thread with JNI.");
- return;
- }
-
- jclass event_class =
- env->FindClass("android/hardware/location/ActivityRecognitionHardware$Event");
- jmethodID event_ctor = env->GetMethodID(event_class, "<init>", "()V");
- jfieldID activity_field = env->GetFieldID(event_class, "activity", "I");
- jfieldID type_field = env->GetFieldID(event_class, "type", "I");
- jfieldID timestamp_field = env->GetFieldID(event_class, "timestamp", "J");
-
- jobjectArray events_array = env->NewObjectArray(count, event_class, NULL);
- for (int i = 0; i < count; ++i) {
- const activity_event_t* event = &events[i];
- jobject event_object = env->NewObject(event_class, event_ctor);
- env->SetIntField(event_object, activity_field, event->activity);
- env->SetIntField(event_object, type_field, event->event_type);
- env->SetLongField(event_object, timestamp_field, event->timestamp);
- env->SetObjectArrayElement(events_array, i, event_object);
- env->DeleteLocalRef(event_object);
- }
-
- env->CallVoidMethod(sCallbacksObject, sOnActivityChanged, events_array);
- check_and_clear_exceptions(env, __FUNCTION__);
-
- // TODO: ideally we'd let the HAL register the callback thread only once
- detach_thread();
-}
-
-activity_recognition_callback_procs_t sCallbacks = {
- activity_callback,
-};
+// #include "activity_recognition.h"
+// The activity recognition HAL is being deprecated. This means -
+// i) Android framework code shall not depend on activity recognition
+// being provided through the activity_recognition.h interface.
+// ii) activity recognition HAL will not be binderized as the other HALs.
+//
/**
* Initializes the ActivityRecognitionHardware class from the native side.
*/
-static void class_init(JNIEnv* env, jclass clazz) {
- // open the hardware module
- int error = hw_get_module(
- ACTIVITY_RECOGNITION_HARDWARE_MODULE_ID,
- (const hw_module_t**) &sModule);
- if (error != 0) {
- ALOGE("Error hw_get_module: %d", error);
- return;
- }
-
- error = activity_recognition_open(&sModule->common, &sDevice);
- if (error != 0) {
- ALOGE("Error opening device: %d", error);
- return;
- }
-
- // get references to the Java provided methods
- sOnActivityChanged = env->GetMethodID(
- clazz,
- "onActivityChanged",
- "([Landroid/hardware/location/ActivityRecognitionHardware$Event;)V");
- if (sOnActivityChanged == NULL) {
- ALOGE("Error obtaining ActivityChanged callback.");
- return;
- }
-
- // register callbacks
- sDevice->register_activity_callback(sDevice, &sCallbacks);
+static void class_init(JNIEnv* /*env*/, jclass /*clazz*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
}
/**
* Initializes and connect the callbacks handlers in the HAL.
*/
-static void initialize(JNIEnv* env, jobject obj) {
- if (sCallbacksObject == NULL) {
- sCallbacksObject = env->NewGlobalRef(obj);
- } else {
- ALOGD("Callbacks Object was already initialized.");
- }
-
- if (sDevice != NULL) {
- sDevice->register_activity_callback(sDevice, &sCallbacks);
- } else {
- ALOGD("ActivityRecognition device not found during initialization.");
- }
+static void initialize(JNIEnv* /*env*/, jobject /*obj*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
}
/**
* De-initializes the ActivityRecognitionHardware from the native side.
*/
-static void release(JNIEnv* env, jobject obj) {
- if (sDevice == NULL) {
- return;
- }
-
- int error = activity_recognition_close(sDevice);
- if (error != 0) {
- ALOGE("Error closing device: %d", error);
- return;
- }
+static void release(JNIEnv* /*env*/, jobject /*obj*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
}
/**
* Returns true if ActivityRecognition HAL is supported, false otherwise.
*/
-static jboolean is_supported(JNIEnv* env, jclass clazz) {
- if (sModule != NULL && sDevice != NULL ) {
- return JNI_TRUE;
- }
+static jboolean is_supported(JNIEnv* /*env*/, jclass /*clazz*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
return JNI_FALSE;
}
/**
* Gets an array representing the supported activities.
*/
-static jobjectArray get_supported_activities(JNIEnv* env, jobject obj) {
- if (sModule == NULL) {
- return NULL;
- }
-
- char const* const* list = NULL;
- int list_size = sModule->get_supported_activities_list(sModule, &list);
- if (list_size <= 0 || list == NULL) {
- return NULL;
- }
-
- jclass string_class = env->FindClass("java/lang/String");
- if (string_class == NULL) {
- ALOGE("Unable to find String class for supported activities.");
- return NULL;
- }
-
- jobjectArray string_array = env->NewObjectArray(list_size, string_class, NULL);
- if (string_array == NULL) {
- ALOGE("Unable to create string array for supported activities.");
- return NULL;
- }
-
- for (int i = 0; i < list_size; ++i) {
- const char* string_ptr = const_cast<const char*>(list[i]);
- jstring string = env->NewStringUTF(string_ptr);
- env->SetObjectArrayElement(string_array, i, string);
- }
-
- return string_array;
+static jobjectArray get_supported_activities(JNIEnv* /*env*/, jobject /*obj*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+ return NULL;
}
/**
* Enables a given activity event to be actively monitored.
*/
static int enable_activity_event(
- JNIEnv* env,
- jobject obj,
- jint activity_handle,
- jint event_type,
- jlong report_latency_ns) {
- return sDevice->enable_activity_event(
- sDevice,
- (uint32_t) activity_handle,
- (uint32_t) event_type,
- report_latency_ns);
+ JNIEnv* /*env*/,
+ jobject /*obj*/,
+ jint /*activity_handle*/,
+ jint /*event_type*/,
+ jlong /*report_latency_ns*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+ return android::NO_INIT;
}
/**
* Disables a given activity event from being actively monitored.
*/
static int disable_activity_event(
- JNIEnv* env,
- jobject obj,
- jint activity_handle,
- jint event_type) {
- return sDevice->disable_activity_event(
- sDevice,
- (uint32_t) activity_handle,
- (uint32_t) event_type);
+ JNIEnv* /*env*/,
+ jobject /*obj*/,
+ jint /*activity_handle*/,
+ jint /*event_type*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+ return android::NO_INIT;
}
/**
* Request flush for al batch buffers.
*/
-static int flush(JNIEnv* env, jobject obj) {
- return sDevice->flush(sDevice);
+static int flush(JNIEnv* /*env*/, jobject /*obj*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+ return android::NO_INIT;
}
diff --git a/core/jni/android_os_SystemProperties.cpp b/core/jni/android_os_SystemProperties.cpp
index 5dace6b..8844fb0 100644
--- a/core/jni/android_os_SystemProperties.cpp
+++ b/core/jni/android_os_SystemProperties.cpp
@@ -220,6 +220,11 @@
}
}
+static void SystemProperties_report_sysprop_change(JNIEnv /**env*/, jobject /*clazz*/)
+{
+ report_sysprop_change();
+}
+
static const JNINativeMethod method_table[] = {
{ "native_get", "(Ljava/lang/String;)Ljava/lang/String;",
(void*) SystemProperties_getS },
@@ -235,6 +240,8 @@
(void*) SystemProperties_set },
{ "native_add_change_callback", "()V",
(void*) SystemProperties_add_change_callback },
+ { "native_report_sysprop_change", "()V",
+ (void*) SystemProperties_report_sysprop_change },
};
int register_android_os_SystemProperties(JNIEnv *env)
diff --git a/core/res/res/anim/tooltip_enter.xml b/core/res/res/anim/tooltip_enter.xml
new file mode 100644
index 0000000..7eceb4c
--- /dev/null
+++ b/core/res/res/anim/tooltip_enter.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@interpolator/decelerate_quad"
+ android:fromAlpha="0.0" android:toAlpha="1.0"
+ android:duration="@android:integer/config_tooltipAnimTime" />
diff --git a/core/res/res/anim/tooltip_exit.xml b/core/res/res/anim/tooltip_exit.xml
new file mode 100644
index 0000000..e346ca9
--- /dev/null
+++ b/core/res/res/anim/tooltip_exit.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@interpolator/accelerate_quad"
+ android:fromAlpha="1.0" android:toAlpha="0.0"
+ android:duration="@android:integer/config_tooltipAnimTime" />
diff --git a/core/res/res/drawable/tooltip_frame.xml b/core/res/res/drawable/tooltip_frame.xml
new file mode 100644
index 0000000..14130c8
--- /dev/null
+++ b/core/res/res/drawable/tooltip_frame.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="?attr/tooltipBackgroundColor" />
+ <corners android:radius="@dimen/tooltip_corner_radius" />
+</shape>
\ No newline at end of file
diff --git a/core/res/res/layout/tooltip.xml b/core/res/res/layout/tooltip.xml
new file mode 100644
index 0000000..0aa6a87
--- /dev/null
+++ b/core/res/res/layout/tooltip.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@android:id/message"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/tooltip_height"
+ android:layout_margin="@dimen/tooltip_margin"
+ android:paddingStart="@dimen/tooltip_horizontal_padding"
+ android:paddingEnd="@dimen/tooltip_horizontal_padding"
+ android:gravity="center"
+ android:background="?android:attr/tooltipFrameBackground"
+ android:textAppearance="@style/TextAppearance.Tooltip"
+ android:textColor="?android:attr/tooltipForegroundColor"
+ android:singleLine="true"
+ android:ellipsize="end"
+ />
+
+</LinearLayout>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 2d61e6e..acafbcf 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -582,7 +582,7 @@
<!-- Image elements -->
<!-- ============== -->
<eat-comment />
-i
+
<!-- Background that can be used behind parts of a UI that provide
details on data the user is selecting. For example, this is
the background element of PreferenceActivity's embedded
@@ -1008,6 +1008,15 @@
<!-- Background to use for toasts -->
<attr name="toastFrameBackground" format="reference" />
+ <!-- Background to use for tooltip popups -->
+ <attr name="tooltipFrameBackground" format="reference" />
+
+ <!-- Foreground color to use for tooltip popups -->
+ <attr name="tooltipForegroundColor" format="reference|color" />
+
+ <!-- Background color to use for tooltip popups -->
+ <attr name="tooltipBackgroundColor" format="reference|color" />
+
<!-- Theme to use for Search Dialogs -->
<attr name="searchDialogTheme" format="reference" />
@@ -2865,6 +2874,9 @@
{@link android.view.View#forceHasOverlappingRendering(boolean)}. -->
<attr name="forceHasOverlappingRendering" format="boolean" />
+ <!-- Defines text displayed in a small popup window on hover or long press. -->
+ <attr name="tooltip" format="string" localization="suggested" />
+
</declare-styleable>
<!-- Attributes that can be assigned to a tag for a particular View. -->
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index de86cef..0995bc3 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -186,4 +186,7 @@
<color name="resize_shadow_start_color">#2a000000</color>
<color name="resize_shadow_end_color">#00000000</color>
+
+ <color name="tooltip_background_dark">#e6616161</color>
+ <color name="tooltip_background_light">#e6FFFFFF</color>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d4119d0..a7c5b2a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -128,6 +128,9 @@
<integer name="config_activityShortDur">150</integer>
<integer name="config_activityDefaultDur">220</integer>
+ <!-- The duration (in milliseconds) of the tooltip show/hide animations. -->
+ <integer name="config_tooltipAnimTime">150</integer>
+
<!-- Duration for the dim animation behind a dialog. This may be either
a percentage, which is relative to the duration of the enter/open
animation of the window being shown that is dimming behind, or it may
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 91d7227..5efa55c 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -487,4 +487,19 @@
<!-- Minimum "smallest width" of the display for cascading menus to be enabled. -->
<dimen name="cascading_menus_min_smallest_width">720dp</dimen>
+
+ <!-- Tooltip dimensions. -->
+ <!-- Vertical offset from the edge of the anchor view. -->
+ <dimen name="tooltip_y_offset">20dp</dimen>
+ <!-- Height of the tooltip. -->
+ <dimen name="tooltip_height">32dp</dimen>
+ <!-- The tooltip does not get closer than this to the window edge -->
+ <dimen name="tooltip_margin">8dp</dimen>
+ <!-- Left/right padding of the tooltip text. -->
+ <dimen name="tooltip_horizontal_padding">16dp</dimen>
+ <!-- Border corner radius of the tooltip window. -->
+ <dimen name="tooltip_corner_radius">2dp</dimen>
+ <!-- View with the height equal or above this threshold will have a tooltip anchored
+ to the mouse/touch position -->
+ <dimen name="tooltip_precise_anchor_threshold">96dp</dimen>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index ed68582..ae82128 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2759,6 +2759,7 @@
<public name="fontStyle" />
<public name="font" />
<public name="fontWeight" />
+ <public name="tooltip" />
</public-group>
<public-group type="style" first-id="0x010302e0">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d42ec90..baf3cd8 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4450,4 +4450,7 @@
<!-- Label used by Telephony code, assigned as the display name for conference calls [CHAR LIMIT=60] -->
<string name="conference_call">Conference Call</string>
+
+ <!-- Title for a tooltip popup window [CHAR LIMIT=NONE] -->
+ <string name="tooltip_popup_title">Tooltip Popup</string>
</resources>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 937428b..0f756b9 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -159,6 +159,11 @@
<item name="windowExitAnimation">@anim/toast_exit</item>
</style>
+ <style name="Animation.Tooltip">
+ <item name="windowEnterAnimation">@anim/tooltip_enter</item>
+ <item name="windowExitAnimation">@anim/tooltip_exit</item>
+ </style>
+
<style name="Animation.DropDownDown">
<item name="windowEnterAnimation">@anim/grow_fade_in</item>
<item name="windowExitAnimation">@anim/shrink_fade_out</item>
@@ -950,6 +955,11 @@
<item name="fontFamily">sans-serif-condensed</item>
</style>
+ <style name="TextAppearance.Tooltip">
+ <item name="fontFamily">sans-serif</item>
+ <item name="textSize">14sp</item>
+ </style>
+
<style name="Widget.ActivityChooserView">
<item name="gravity">center</item>
<item name="background">@drawable/ab_share_pack_holo_dark</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c719664..5b608b8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -435,6 +435,8 @@
<java-symbol type="dimen" name="search_view_preferred_height" />
<java-symbol type="dimen" name="textview_error_popup_default_width" />
<java-symbol type="dimen" name="toast_y_offset" />
+ <java-symbol type="dimen" name="tooltip_precise_anchor_threshold" />
+ <java-symbol type="dimen" name="tooltip_y_offset" />
<java-symbol type="dimen" name="action_bar_stacked_max_height" />
<java-symbol type="dimen" name="action_bar_stacked_tab_max_width" />
<java-symbol type="dimen" name="notification_text_size" />
@@ -1123,6 +1125,7 @@
<java-symbol type="string" name="demo_starting_message" />
<java-symbol type="string" name="demo_restarting_message" />
<java-symbol type="string" name="conference_call" />
+ <java-symbol type="string" name="tooltip_popup_title" />
<java-symbol type="plurals" name="bugreport_countdown" />
@@ -1373,6 +1376,7 @@
<java-symbol type="layout" name="textview_hint" />
<java-symbol type="layout" name="time_picker_legacy" />
<java-symbol type="layout" name="time_picker_dialog" />
+ <java-symbol type="layout" name="tooltip" />
<java-symbol type="layout" name="transient_notification" />
<java-symbol type="layout" name="voice_interaction_session" />
<java-symbol type="layout" name="web_text_view_dropdown" />
@@ -1423,6 +1427,7 @@
<java-symbol type="style" name="Animation.DropDownUp" />
<java-symbol type="style" name="Animation.DropDownDown" />
<java-symbol type="style" name="Animation.PopupWindow" />
+ <java-symbol type="style" name="Animation.Tooltip" />
<java-symbol type="style" name="Animation.TypingFilter" />
<java-symbol type="style" name="Animation.TypingFilterRestore" />
<java-symbol type="style" name="Animation.Dream" />
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 5b2522f..357eb4b 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -439,6 +439,11 @@
<item name="lightRadius">@dimen/light_radius</item>
<item name="ambientShadowAlpha">@dimen/ambient_shadow_alpha</item>
<item name="spotShadowAlpha">@dimen/spot_shadow_alpha</item>
+
+ <!-- Tooltip popup properties -->
+ <item name="tooltipFrameBackground">@drawable/tooltip_frame</item>
+ <item name="tooltipForegroundColor">@color/bright_foreground_light</item>
+ <item name="tooltipBackgroundColor">@color/tooltip_background_light</item>
</style>
<!-- Variant of {@link #Theme} with no title bar -->
@@ -553,6 +558,10 @@
<item name="floatingToolbarItemBackgroundDrawable">@drawable/item_background_material_light</item>
<item name="floatingToolbarOpenDrawable">@drawable/ic_menu_moreoverflow_material_light</item>
<item name="floatingToolbarPopupBackgroundDrawable">@drawable/floating_popup_background_light</item>
+
+ <!-- Tooltip popup colors -->
+ <item name="tooltipForegroundColor">@color/bright_foreground_dark</item>
+ <item name="tooltipBackgroundColor">@color/tooltip_background_dark</item>
</style>
<!-- Variant of {@link #Theme_Light} with no title bar -->
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index 0de773b..92bb3ea 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -398,6 +398,10 @@
<item name="colorControlHighlight">@color/ripple_material_dark</item>
<item name="colorButtonNormal">@color/btn_default_material_dark</item>
<item name="colorSwitchThumbNormal">@color/switch_thumb_material_dark</item>
+
+ <!-- Tooltip popup properties -->
+ <item name="tooltipForegroundColor">@color/foreground_material_light</item>
+ <item name="tooltipBackgroundColor">@color/tooltip_background_light</item>
</style>
<!-- Material theme (light version). -->
@@ -762,6 +766,10 @@
<item name="colorControlHighlight">@color/ripple_material_light</item>
<item name="colorButtonNormal">@color/btn_default_material_light</item>
<item name="colorSwitchThumbNormal">@color/switch_thumb_material_light</item>
+
+ <!-- Tooltip popup properties -->
+ <item name="tooltipForegroundColor">@color/foreground_material_dark</item>
+ <item name="tooltipBackgroundColor">@color/tooltip_background_dark</item>
</style>
<!-- Variant of the material (light) theme that has a solid (opaque) action bar
diff --git a/core/tests/coretests/src/android/provider/SettingsProviderTest.java b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
index e6d3158..0a32e43 100644
--- a/core/tests/coretests/src/android/provider/SettingsProviderTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
@@ -349,6 +349,7 @@
assertCanBeHandled(new Intent(Settings.ACTION_USER_DICTIONARY_SETTINGS));
assertCanBeHandled(new Intent(Settings.ACTION_WIFI_IP_SETTINGS));
assertCanBeHandled(new Intent(Settings.ACTION_WIFI_SETTINGS));
+ assertCanBeHandled(new Intent(Settings.ACTION_WIFI_SAVED_NETWORK_SETTINGS));
assertCanBeHandled(new Intent(Settings.ACTION_WIRELESS_SETTINGS));
}
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index fdf4d52..eff2499 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -181,6 +181,7 @@
external/skia/include/private \
external/skia/src/core \
external/skia/src/effects \
+ external/skia/src/image \
external/harfbuzz_ng/src \
external/freetype/include
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index 2087fca..749efdd 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -116,6 +116,9 @@
bool canDrawThisFrame = true;
} out;
+ // This flag helps to disable projection for receiver nodes that do not have any backward
+ // projected children.
+ bool hasBackwardProjectedNodes = false;
// TODO: Damage calculations
};
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 7dcbbd0..da9002d 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -24,8 +24,41 @@
namespace uirenderer {
namespace skiapipeline {
+void RenderNodeDrawable::drawBackwardsProjectedNodes(SkCanvas* canvas, const SkiaDisplayList& displayList,
+ int nestLevel) {
+ LOG_ALWAYS_FATAL_IF(0 == nestLevel && !displayList.mProjectionReceiver);
+ for (auto& child : displayList.mChildNodes) {
+ const RenderProperties& childProperties = child.getNodeProperties();
+
+ //immediate children cannot be projected on their parent
+ if (childProperties.getProjectBackwards() && nestLevel > 0) {
+ SkAutoCanvasRestore acr2(canvas, true);
+ //Apply recorded matrix, which is a total matrix saved at recording time to avoid
+ //replaying all DL commands.
+ canvas->concat(child.getRecordedMatrix());
+ child.drawContent(canvas);
+ }
+
+ //skip walking sub-nodes if current display list contains a receiver with exception of
+ //level 0, which is a known receiver
+ if (0 == nestLevel || !displayList.containsProjectionReceiver()) {
+ SkAutoCanvasRestore acr(canvas, true);
+ SkMatrix nodeMatrix;
+ mat4 hwuiMatrix(child.getRecordedMatrix());
+ auto childNode = child.getRenderNode();
+ childNode->applyViewPropertyTransforms(hwuiMatrix);
+ hwuiMatrix.copyTo(nodeMatrix);
+ canvas->concat(nodeMatrix);
+ SkiaDisplayList* childDisplayList = static_cast<SkiaDisplayList*>(
+ (const_cast<DisplayList*>(childNode->getDisplayList())));
+ if (childDisplayList) {
+ drawBackwardsProjectedNodes(canvas, *childDisplayList, nestLevel+1);
+ }
+ }
+ }
+}
+
static void clipOutline(const Outline& outline, SkCanvas* canvas, const SkRect* pendingClip) {
- SkASSERT(outline.willClip());
Rect possibleRect;
float radius;
LOG_ALWAYS_FATAL_IF(!outline.getAsRoundRect(&possibleRect, &radius),
@@ -74,53 +107,25 @@
SkiaDisplayList* displayList = (SkiaDisplayList*)renderNode->getDisplayList();
SkAutoCanvasRestore acr(canvas, true);
-
const RenderProperties& properties = this->getNodeProperties();
- if (displayList->mIsProjectionReceiver) {
- // this node is a projection receiver. We will gather the projected nodes as we draw our
- // children, and then draw them on top of this node's content.
- std::vector<ProjectedChild> newList;
- for (auto& child : displayList->mChildNodes) {
- // our direct children are not supposed to project into us (nodes project to, at the
- // nearest, their grandparents). So we "delay" the list's activation one level by
- // passing it into mNextProjectedChildrenTarget rather than mProjectedChildrenTarget.
- child.mProjectedChildrenTarget = mNextProjectedChildrenTarget;
- child.mNextProjectedChildrenTarget = &newList;
+ //pass this outline to the children that may clip backward projected nodes
+ displayList->mProjectedOutline = displayList->containsProjectionReceiver()
+ ? &properties.getOutline() : nullptr;
+ if (!properties.getProjectBackwards()) {
+ drawContent(canvas);
+ if (mProjectedDisplayList) {
+ acr.restore(); //draw projected children using parent matrix
+ LOG_ALWAYS_FATAL_IF(!mProjectedDisplayList->mProjectedOutline);
+ const bool shouldClip = mProjectedDisplayList->mProjectedOutline->getPath();
+ SkAutoCanvasRestore acr2(canvas, shouldClip);
+ canvas->setMatrix(mProjectedDisplayList->mProjectedReceiverParentMatrix);
+ if (shouldClip) {
+ clipOutline(*mProjectedDisplayList->mProjectedOutline, canvas, nullptr);
+ }
+ drawBackwardsProjectedNodes(canvas, *mProjectedDisplayList);
}
- // draw ourselves and our children. As a side effect, this will add projected nodes to
- // newList.
- this->drawContent(canvas);
- bool willClip = properties.getOutline().willClip();
- if (willClip) {
- canvas->save();
- clipOutline(properties.getOutline(), canvas, nullptr);
- }
- // draw the collected projected nodes
- for (auto& projectedChild : newList) {
- canvas->setMatrix(projectedChild.matrix);
- projectedChild.node->drawContent(canvas);
- }
- if (willClip) {
- canvas->restore();
- }
- } else {
- if (properties.getProjectBackwards() && mProjectedChildrenTarget) {
- // We are supposed to project this node, so add it to the list and do not actually draw
- // yet. It will be drawn by its projection receiver.
- mProjectedChildrenTarget->push_back({ this, canvas->getTotalMatrix() });
- return;
- }
- for (auto& child : displayList->mChildNodes) {
- // storing these values in the nodes themselves is a bit ugly; they should "really" be
- // function parameters, but we have to go through the preexisting draw() method and
- // therefore cannot add additional parameters to it
- child.mProjectedChildrenTarget = mNextProjectedChildrenTarget;
- child.mNextProjectedChildrenTarget = mNextProjectedChildrenTarget;
- }
- this->drawContent(canvas);
}
- mProjectedChildrenTarget = nullptr;
- mNextProjectedChildrenTarget = nullptr;
+ displayList->mProjectedOutline = nullptr;
}
static bool layerNeedsPaint(const LayerProperties& properties,
@@ -148,6 +153,10 @@
if (mComposeLayer) {
setViewProperties(properties, canvas, &alphaMultiplier);
}
+ SkiaDisplayList* displayList = (SkiaDisplayList*)mRenderNode->getDisplayList();
+ if (displayList->containsProjectionReceiver()) {
+ displayList->mProjectedReceiverParentMatrix = canvas->getTotalMatrix();
+ }
//TODO should we let the bound of the drawable do this for us?
const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.h b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
index a2ffc6c..3eed647 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.h
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
@@ -29,6 +29,8 @@
namespace skiapipeline {
+class SkiaDisplayList;
+
/**
* This drawable wraps a RenderNode and enables it to be recorded into a list
* of Skia drawing commands.
@@ -36,18 +38,6 @@
class RenderNodeDrawable : public SkDrawable {
public:
/**
- * This struct contains a pointer to a node that is to be
- * projected into the drawing order of its closest ancestor
- * (excluding its parent) that is marked as a projection
- * receiver. The matrix is used to ensure that the node is
- * drawn with same matrix as it would have prior to projection.
- */
- struct ProjectedChild {
- const RenderNodeDrawable* node;
- const SkMatrix matrix;
- };
-
- /**
* Creates a new RenderNodeDrawable backed by a render node.
*
* @param node that has to be drawn
@@ -86,6 +76,14 @@
*/
const SkMatrix& getRecordedMatrix() const { return mRecordedTransform; }
+ /**
+ * Sets a pointer to a display list of the parent render node. The display list is used when
+ * drawing backward projected nodes, when this node is a projection receiver.
+ */
+ void setProjectedDisplayList(SkiaDisplayList* projectedDisplayList) {
+ mProjectedDisplayList = projectedDisplayList;
+ }
+
protected:
/*
* Return the (conservative) bounds of what the drawable will draw.
@@ -108,6 +106,16 @@
sp<RenderNode> mRenderNode;
/**
+ * Walks recursively the display list and draws the content of backward projected nodes.
+ *
+ * @param canvas used to draw the backward projected nodes
+ * @param displayList is a display list that contains a projection receiver
+ * @param nestLevel should be always 0. Used to track how far we are from the receiver.
+ */
+ void drawBackwardsProjectedNodes(SkCanvas* canvas, const SkiaDisplayList& displayList,
+ int nestLevel = 0);
+
+ /**
* Applies the rendering properties of a view onto a SkCanvas.
*/
static void setViewProperties(const RenderProperties& properties, SkCanvas* canvas,
@@ -126,19 +134,6 @@
*/
const bool mComposeLayer;
- /**
- * List to which we will add any projected children we encounter while walking our descendents.
- * This pointer is valid only while the node (including its children) is actively being drawn.
- */
- std::vector<ProjectedChild>* mProjectedChildrenTarget = nullptr;
-
- /**
- * The value to which we should set our children's mProjectedChildrenTarget. We use two pointers
- * (mProjectedChildrenTarget and mNextProjectedChildrenTarget) because we need to skip over our
- * parent when looking for a projection receiver.
- */
- std::vector<ProjectedChild>* mNextProjectedChildrenTarget = nullptr;
-
/*
* True if the render node is in a reordering section
*/
@@ -148,6 +143,11 @@
* Draw the content into a canvas, depending on the render node layer type and mComposeLayer.
*/
void drawContent(SkCanvas* canvas) const;
+
+ /*
+ * display list that is searched for any render nodes with getProjectBackwards==true
+ */
+ SkiaDisplayList* mProjectedDisplayList = nullptr;
};
}; // namespace skiapipeline
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index 2ad7f74..9db8cd3 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -64,16 +64,31 @@
info.canvasContext.unpinImages();
}
+ bool hasBackwardProjectedNodesHere = false;
+ bool hasBackwardProjectedNodesSubtree= false;
+
for (auto& child : mChildNodes) {
+ hasBackwardProjectedNodesHere |= child.getNodeProperties().getProjectBackwards();
RenderNode* childNode = child.getRenderNode();
Matrix4 mat4(child.getRecordedMatrix());
info.damageAccumulator->pushTransform(&mat4);
// TODO: a layer is needed if the canvas is rotated or has a non-rect clip
- bool childFunctorsNeedLayer = functorsNeedLayer;
- childFn(childNode, info, childFunctorsNeedLayer);
+ info.hasBackwardProjectedNodes = false;
+ childFn(childNode, info, functorsNeedLayer);
+ hasBackwardProjectedNodesSubtree |= info.hasBackwardProjectedNodes;
info.damageAccumulator->popTransform();
}
+ //The purpose of next block of code is to reset projected display list if there are no
+ //backward projected nodes. This speeds up drawing, by avoiding an extra walk of the tree
+ if (mProjectionReceiver) {
+ mProjectionReceiver->setProjectedDisplayList(hasBackwardProjectedNodesSubtree ? this : nullptr);
+ info.hasBackwardProjectedNodes = hasBackwardProjectedNodesHere;
+ } else {
+ info.hasBackwardProjectedNodes = hasBackwardProjectedNodesSubtree
+ || hasBackwardProjectedNodesHere;
+ }
+
bool isDirty = false;
for (auto& vectorDrawable : mVectorDrawables) {
// If any vector drawable in the display list needs update, damage the node.
@@ -86,7 +101,7 @@
}
void SkiaDisplayList::reset(SkRect bounds) {
- mIsProjectionReceiver = false;
+ mProjectionReceiver = nullptr;
mDrawable->reset(bounds);
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index f34b485..ff86fd1 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -26,6 +26,9 @@
namespace android {
namespace uirenderer {
+
+class Outline;
+
namespace skiapipeline {
/**
@@ -119,6 +122,11 @@
void updateChildren(std::function<void(RenderNode*)> updateFn) override;
/**
+ * Returns true if there is a child render node that is a projection receiver.
+ */
+ inline bool containsProjectionReceiver() const { return mProjectionReceiver; }
+
+ /**
* We use std::deque here because (1) we need to iterate through these
* elements and (2) mDrawable holds pointers to the elements, so they cannot
* relocate.
@@ -129,7 +137,22 @@
std::vector<VectorDrawableRoot*> mVectorDrawables;
sk_sp<SkLiteDL> mDrawable;
- bool mIsProjectionReceiver = false;
+ //mProjectionReceiver points to a child node (stored in mChildNodes) that is as a projection
+ //receiver. It is set at record time and used at both prepare and draw tree traversals to
+ //make sure backward projected nodes are found and drawn immediately after mProjectionReceiver.
+ RenderNodeDrawable* mProjectionReceiver = nullptr;
+
+ //mProjectedOutline is valid only when render node tree is traversed during the draw pass.
+ //Render nodes that have a child receiver node, will store a pointer to their outline in
+ //mProjectedOutline. Child receiver node will apply the clip before any backward projected
+ //node is drawn.
+ const Outline* mProjectedOutline = nullptr;
+
+ //mProjectedReceiverParentMatrix is valid when render node tree is traversed during the draw
+ //pass. Render nodes that have a child receiver node, will store their matrix in
+ //mProjectedReceiverParentMatrix. Child receiver node will set the matrix and then clip with the
+ //outline of their parent.
+ SkMatrix mProjectedReceiverParentMatrix;
};
}; // namespace skiapipeline
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 621816a..6df544f 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -108,11 +108,12 @@
// record the child node
mDisplayList->mChildNodes.emplace_back(renderNode, asSkCanvas(), true, mCurrentBarrier);
- drawDrawable(&mDisplayList->mChildNodes.back());
+ auto& renderNodeDrawable = mDisplayList->mChildNodes.back();
+ drawDrawable(&renderNodeDrawable);
// use staging property, since recording on UI thread
if (renderNode->stagingProperties().isProjectionReceiver()) {
- mDisplayList->mIsProjectionReceiver = true;
+ mDisplayList->mProjectionReceiver = &renderNodeDrawable;
// set projectionReceiveIndex so that RenderNode.hasProjectionReceiver returns true
mDisplayList->projectionReceiveIndex = mDisplayList->mChildNodes.size() - 1;
}
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index 01046e1..8c956e5 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -1487,6 +1487,8 @@
*layerHandle = nullptr;
}
+namespace {
+
static void drawOrderedRect(Canvas* canvas, uint8_t expectedDrawOrder) {
SkPaint paint;
// order put in blue channel, transparent so overlapped content doesn't get rejected
@@ -1502,15 +1504,30 @@
node->setPropertyFieldsDirty(RenderNode::TRANSLATION_Z);
canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
}
-RENDERTHREAD_TEST(FrameBuilder, zReorder) {
- class ZReorderTestRenderer : public TestRendererBase {
- public:
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- int expectedOrder = SkColorGetB(op.paint->getColor()); // extract order from blue channel
- EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
- }
- };
+static void drawOrderedNode(Canvas* canvas, uint8_t expectedDrawOrder,
+ std::function<void(RenderProperties& props, RecordingCanvas& canvas)> setup) {
+ auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+ [expectedDrawOrder, setup](RenderProperties& props, RecordingCanvas& canvas) {
+ drawOrderedRect(&canvas, expectedDrawOrder);
+ if (setup) {
+ setup(props, canvas);
+ }
+ });
+ canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
+}
+
+class ZReorderTestRenderer : public TestRendererBase {
+public:
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ int expectedOrder = SkColorGetB(op.paint->getColor()); // extract order from blue channel
+ EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
+ }
+};
+
+} // end anonymous namespace
+
+RENDERTHREAD_TEST(FrameBuilder, zReorder) {
auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
[](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, 10.0f); // in reorder=false at this point, so played inorder
@@ -2238,5 +2255,349 @@
EXPECT_EQ(1, renderer.getIndex());
}
+TEST(FrameBuilder, projectionReorderProjectedInMiddle) {
+ /* R is backward projected on B
+ A
+ / \
+ B C
+ |
+ R
+ */
+ auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) {
+ props.setProjectionReceiver(true);
+ } ); //nodeB
+ drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
+ props.setProjectBackwards(true);
+ props.setClipToBounds(false);
+ } ); //nodeR
+ } ); //nodeC
+ }); //nodeA
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+ ZReorderTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(3, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderProjectLast) {
+ /* R is backward projected on E
+ A
+ / | \
+ / | \
+ B C E
+ |
+ R
+ */
+ auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, nullptr); //nodeB
+ drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 3, [](RenderProperties& props, RecordingCanvas& canvas) { //drawn as 2
+ props.setProjectBackwards(true);
+ props.setClipToBounds(false);
+ } ); //nodeR
+ } ); //nodeC
+ drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) { //drawn as 3
+ props.setProjectionReceiver(true);
+ } ); //nodeE
+ }); //nodeA
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+ ZReorderTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(4, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderNoReceivable) {
+ /* R is backward projected without receiver
+ A
+ / \
+ B C
+ |
+ R
+ */
+ auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, nullptr); //nodeB
+ drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 255, [](RenderProperties& props, RecordingCanvas& canvas) {
+ //not having a projection receiver is an undefined behavior
+ props.setProjectBackwards(true);
+ props.setClipToBounds(false);
+ } ); //nodeR
+ } ); //nodeC
+ }); //nodeA
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+ ZReorderTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(2, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderParentReceivable) {
+ /* R is backward projected on C
+ A
+ / \
+ B C
+ |
+ R
+ */
+ auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, nullptr); //nodeB
+ drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
+ props.setProjectionReceiver(true);
+ drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) {
+ props.setProjectBackwards(true);
+ props.setClipToBounds(false);
+ } ); //nodeR
+ } ); //nodeC
+ }); //nodeA
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+ ZReorderTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(3, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderSameNodeReceivable) {
+ auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, nullptr); //nodeB
+ drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 255, [](RenderProperties& props, RecordingCanvas& canvas) {
+ //having a node that is projected on itself is an undefined/unexpected behavior
+ props.setProjectionReceiver(true);
+ props.setProjectBackwards(true);
+ props.setClipToBounds(false);
+ } ); //nodeR
+ } ); //nodeC
+ }); //nodeA
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+ ZReorderTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(2, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderProjectedSibling) {
+ //TODO: this test together with the next "projectionReorderProjectedSibling2" likely expose a
+ //bug in HWUI. First test draws R, while the second test does not draw R for a nearly identical
+ //tree setup. The correct behaviour is to not draw R, because the receiver cannot be a sibling
+ /* R is backward projected on B. R is not expected to be drawn (see Sibling2 outcome below),
+ but for some reason it is drawn.
+ A
+ /|\
+ / | \
+ B C R
+ */
+ auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) {
+ props.setProjectionReceiver(true);
+ } ); //nodeB
+ drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) {
+ } ); //nodeC
+ drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
+ props.setProjectBackwards(true);
+ props.setClipToBounds(false);
+ } ); //nodeR
+ }); //nodeA
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+ ZReorderTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(3, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderProjectedSibling2) {
+ /* R is set to project on B, but R is not drawn because projecting on a sibling is not allowed.
+ A
+ |
+ G
+ /|\
+ / | \
+ B C R
+ */
+ auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) { //G
+ drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) { //B
+ props.setProjectionReceiver(true);
+ } ); //nodeB
+ drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) { //C
+ } ); //nodeC
+ drawOrderedNode(&canvas, 255, [](RenderProperties& props, RecordingCanvas& canvas) { //R
+ props.setProjectBackwards(true);
+ props.setClipToBounds(false);
+ } ); //nodeR
+ } ); //nodeG
+ }); //nodeA
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+ ZReorderTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(3, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderGrandparentReceivable) {
+ /* R is backward projected on B
+ A
+ |
+ B
+ |
+ C
+ |
+ R
+ */
+ auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) {
+ props.setProjectionReceiver(true);
+ drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) {
+ props.setProjectBackwards(true);
+ props.setClipToBounds(false);
+ } ); //nodeR
+ } ); //nodeC
+ } ); //nodeB
+ }); //nodeA
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+ ZReorderTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(3, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderTwoReceivables) {
+ /* B and G are receivables, R is backward projected
+ A
+ / \
+ B C
+ / \
+ G R
+ */
+ auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) { //B
+ props.setProjectionReceiver(true);
+ } ); //nodeB
+ drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) { //C
+ drawOrderedNode(&canvas, 3, [](RenderProperties& props, RecordingCanvas& canvas) { //G
+ props.setProjectionReceiver(true);
+ } ); //nodeG
+ drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) { //R
+ props.setProjectBackwards(true);
+ props.setClipToBounds(false);
+ } ); //nodeR
+ } ); //nodeC
+ }); //nodeA
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+ ZReorderTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(4, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderTwoReceivablesLikelyScenario) {
+ /* B and G are receivables, G is backward projected
+ A
+ / \
+ B C
+ / \
+ G R
+ */
+ auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) { //B
+ props.setProjectionReceiver(true);
+ } ); //nodeB
+ drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) { //C
+ drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) { //G
+ props.setProjectionReceiver(true);
+ props.setProjectBackwards(true);
+ props.setClipToBounds(false);
+ } ); //nodeG
+ drawOrderedNode(&canvas, 3, [](RenderProperties& props, RecordingCanvas& canvas) { //R
+ } ); //nodeR
+ } ); //nodeC
+ }); //nodeA
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+ ZReorderTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(4, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderTwoReceivablesDeeper) {
+ /* B and G are receivables, R is backward projected
+ A
+ / \
+ B C
+ / \
+ G D
+ |
+ R
+ */
+ auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) { //B
+ props.setProjectionReceiver(true);
+ } ); //nodeB
+ drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) { //C
+ drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) { //G
+ props.setProjectionReceiver(true);
+ } ); //nodeG
+ drawOrderedNode(&canvas, 4, [](RenderProperties& props, RecordingCanvas& canvas) { //D
+ drawOrderedNode(&canvas, 3, [](RenderProperties& props, RecordingCanvas& canvas) { //R
+ props.setProjectBackwards(true);
+ props.setClipToBounds(false);
+ } ); //nodeR
+ } ); //nodeD
+ } ); //nodeC
+ }); //nodeA
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+ ZReorderTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(5, renderer.getIndex());
+}
+
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index 623d971..ae4f0f4 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -21,11 +21,14 @@
#include "DamageAccumulator.h"
#include "IContextFactory.h"
#include "pipeline/skia/SkiaDisplayList.h"
+#include "pipeline/skia/SkiaPipeline.h"
#include "pipeline/skia/SkiaRecordingCanvas.h"
#include "renderthread/CanvasContext.h"
#include "tests/common/TestUtils.h"
#include "SkiaCanvas.h"
+#include <SkSurface_Base.h>
#include <SkLiteRecorder.h>
+#include <SkClipStack.h>
#include <string.h>
@@ -51,6 +54,8 @@
ASSERT_EQ(drawable.getRecordedMatrix(), canvas.getTotalMatrix());
}
+namespace {
+
static void drawOrderedRect(Canvas* canvas, uint8_t expectedDrawOrder) {
SkPaint paint;
// order put in blue channel, transparent so overlapped content doesn't get rejected
@@ -67,18 +72,33 @@
canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
}
-TEST(RenderNodeDrawable, zReorder) {
- class ZReorderCanvas : public SkCanvas {
- public:
- ZReorderCanvas(int width, int height) : SkCanvas(width, height) {}
- void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
- int expectedOrder = SkColorGetB(paint.getColor()); // extract order from blue channel
- EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
+static void drawOrderedNode(Canvas* canvas, uint8_t expectedDrawOrder,
+ std::function<void(RenderProperties& props, SkiaRecordingCanvas& canvas)> setup) {
+ auto node = TestUtils::createSkiaNode(0, 0, 100, 100,
+ [expectedDrawOrder, setup](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ drawOrderedRect(&canvas, expectedDrawOrder);
+ if (setup) {
+ setup(props, canvas);
}
- int getIndex() { return mIndex; }
- protected:
- int mIndex = 0;
- };
+ });
+ canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
+}
+
+class ZReorderCanvas : public SkCanvas {
+public:
+ ZReorderCanvas(int width, int height) : SkCanvas(width, height) {}
+ void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
+ int expectedOrder = SkColorGetB(paint.getColor()); // extract order from blue channel
+ EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
+ }
+ int getIndex() { return mIndex; }
+protected:
+ int mIndex = 0;
+};
+
+} // end anonymous namespace
+
+TEST(RenderNodeDrawable, zReorder) {
auto parent = TestUtils::createSkiaNode(0, 0, 100, 100,
[](RenderProperties& props, SkiaRecordingCanvas& canvas) {
@@ -136,42 +156,622 @@
rootNode->setLayerSurface(sk_sp<SkSurface>());
}
-//TODO: refactor to cover test cases from FrameBuilderTests_projectionReorder
-//validate with bounds and projection path mask.
-//TODO: research if we could hook in and mock/validate different aspects of the drawing,
-//instead of validating pixels
-TEST(RenderNodeDrawable, projectDraw) {
- auto surface = SkSurface::MakeRasterN32Premul(1, 1);
- SkCanvas& canvas = *surface->getCanvas();
- canvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
- ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
+namespace {
+class ContextFactory : public IContextFactory {
+public:
+ virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
+ return new AnimationContext(clock);
+ }
+};
- auto redNode = TestUtils::createSkiaNode(0, 0, 1, 1,
- [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
- redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
- }, "redNode");
+inline SkRect getBounds(const SkCanvas* canvas) {
+ SkClipStack::BoundsType boundType;
+ SkRect clipBounds;
+ canvas->getClipStack()->getBounds(&clipBounds, &boundType);
+ return clipBounds;
+}
+inline SkRect getLocalBounds(const SkCanvas* canvas) {
+ SkMatrix invertedTotalMatrix;
+ EXPECT_TRUE(canvas->getTotalMatrix().invert(&invertedTotalMatrix));
+ SkRect outlineInDeviceCoord = getBounds(canvas);
+ SkRect outlineInLocalCoord;
+ invertedTotalMatrix.mapRect(&outlineInLocalCoord, outlineInDeviceCoord);
+ return outlineInLocalCoord;
+}
+} // end anonymous namespace
- auto greenNodeWithRedChild = TestUtils::createSkiaNode(0, 0, 1, 1,
- [&](RenderProperties& props, SkiaRecordingCanvas& greenCanvasWithRedChild) {
- greenCanvasWithRedChild.drawRenderNode(redNode.get());
- greenCanvasWithRedChild.drawColor(SK_ColorGREEN, SkBlendMode::kSrcOver);
- }, "greenNodeWithRedChild");
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorder) {
+ static const int SCROLL_X = 5;
+ static const int SCROLL_Y = 10;
+ class ProjectionTestCanvas : public SkCanvas {
+ public:
+ ProjectionTestCanvas(int width, int height) : SkCanvas(width, height) {}
+ void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
+ const int index = mIndex++;
+ SkMatrix expectedMatrix;;
+ switch (index) {
+ case 0: //this is node "B"
+ EXPECT_EQ(SkRect::MakeWH(100, 100), rect);
+ EXPECT_EQ(SK_ColorWHITE, paint.getColor());
+ expectedMatrix.reset();
+ EXPECT_EQ(SkRect::MakeLTRB(0, 0, 100, 100), getBounds(this));
+ break;
+ case 1: //this is node "P"
+ EXPECT_EQ(SkRect::MakeLTRB(-10, -10, 60, 60), rect);
+ EXPECT_EQ(SK_ColorDKGRAY, paint.getColor());
+ expectedMatrix.setTranslate(50 - SCROLL_X, 50 - SCROLL_Y);
+ EXPECT_EQ(SkRect::MakeLTRB(-35, -30, 45, 50), getLocalBounds(this));
+ break;
+ case 2: //this is node "C"
+ EXPECT_EQ(SkRect::MakeWH(100, 50), rect);
+ EXPECT_EQ(SK_ColorBLUE, paint.getColor());
+ expectedMatrix.setTranslate(-SCROLL_X, 50 - SCROLL_Y);
+ EXPECT_EQ(SkRect::MakeLTRB(0, 40, 95, 90), getBounds(this));
+ break;
+ default:
+ ADD_FAILURE();
+ }
+ EXPECT_EQ(expectedMatrix, getTotalMatrix());
+ }
- auto rootNode = TestUtils::createSkiaNode(0, 0, 1, 1,
- [&](RenderProperties& props, SkiaRecordingCanvas& rootCanvas) {
- rootCanvas.drawRenderNode(greenNodeWithRedChild.get());
- }, "rootNode");
- SkiaDisplayList* rootDisplayList = static_cast<SkiaDisplayList*>(
- (const_cast<DisplayList*>(rootNode->getDisplayList())));
+ int getIndex() { return mIndex; }
+ protected:
+ int mIndex = 0;
+ };
- RenderNodeDrawable rootDrawable(rootNode.get(), &canvas, false);
- canvas.drawDrawable(&rootDrawable);
- ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorGREEN);
+ /**
+ * Construct a tree of nodes, where the root (A) has a receiver background (B), and a child (C)
+ * with a projecting child (P) of its own. P would normally draw between B and C's "background"
+ * draw, but because it is projected backwards, it's drawn in between B and C.
+ *
+ * The parent is scrolled by SCROLL_X/SCROLL_Y, but this does not affect the background
+ * (which isn't affected by scroll).
+ */
+ auto receiverBackground = TestUtils::createSkiaNode(0, 0, 100, 100,
+ [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+ properties.setProjectionReceiver(true);
+ // scroll doesn't apply to background, so undone via translationX/Y
+ // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
+ properties.setTranslationX(SCROLL_X);
+ properties.setTranslationY(SCROLL_Y);
- //project redNode on rootNode, which will change the test outcome,
- //because redNode will draw after greenNodeWithRedChild
- rootDisplayList->mIsProjectionReceiver = true;
- redNode->animatorProperties().setProjectBackwards(true);
- canvas.drawDrawable(&rootDrawable);
- ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
+ SkPaint paint;
+ paint.setColor(SK_ColorWHITE);
+ canvas.drawRect(0, 0, 100, 100, paint);
+ }, "B");
+
+ auto projectingRipple = TestUtils::createSkiaNode(50, 0, 100, 50,
+ [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+ properties.setProjectBackwards(true);
+ properties.setClipToBounds(false);
+ SkPaint paint;
+ paint.setColor(SK_ColorDKGRAY);
+ canvas.drawRect(-10, -10, 60, 60, paint);
+ }, "P");
+ auto child = TestUtils::createSkiaNode(0, 50, 100, 100,
+ [&projectingRipple](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setColor(SK_ColorBLUE);
+ canvas.drawRect(0, 0, 100, 50, paint);
+ canvas.drawRenderNode(projectingRipple.get());
+ }, "C");
+ auto parent = TestUtils::createSkiaNode(0, 0, 100, 100,
+ [&receiverBackground, &child](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+ // Set a rect outline for the projecting ripple to be masked against.
+ properties.mutableOutline().setRoundRect(10, 10, 90, 90, 5, 1.0f);
+
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.translate(-SCROLL_X, -SCROLL_Y); // Apply scroll (note: bg undoes this internally)
+ canvas.drawRenderNode(receiverBackground.get());
+ canvas.drawRenderNode(child.get());
+ canvas.restore();
+ }, "A");
+ ContextFactory contextFactory;
+ std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
+ renderThread, false, parent.get(), &contextFactory));
+ TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
+ DamageAccumulator damageAccumulator;
+ info.damageAccumulator = &damageAccumulator;
+ info.observer = nullptr;
+ parent->prepareTree(info);
+
+ //parent(A) -> (receiverBackground, child)
+ //child(C) -> (rect[0, 0, 100, 50], projectingRipple)
+ //projectingRipple(P) -> (rect[-10, -10, 60, 60]) -> projects backwards
+ //receiverBackground(B) -> (rect[0, 0, 100, 100]) -> projection receiver
+
+ //create a canvas not backed by any device/pixels, but with dimensions to avoid quick rejection
+ ProjectionTestCanvas canvas(100, 100);
+ RenderNodeDrawable drawable(parent.get(), &canvas, true);
+ canvas.drawDrawable(&drawable);
+ EXPECT_EQ(3, canvas.getIndex());
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) {
+ /* R is backward projected on B and C is a layer.
+ A
+ / \
+ B C
+ |
+ R
+ */
+ static const int SCROLL_X = 5;
+ static const int SCROLL_Y = 10;
+ static const int CANVAS_WIDTH = 400;
+ static const int CANVAS_HEIGHT = 400;
+ static const int LAYER_WIDTH = 200;
+ static const int LAYER_HEIGHT = 200;
+ class ProjectionTestCanvas : public SkCanvas {
+ public:
+ ProjectionTestCanvas() : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) {}
+ void onDrawArc(const SkRect&, SkScalar startAngle, SkScalar sweepAngle, bool useCenter,
+ const SkPaint&) override {
+ EXPECT_EQ(0, mIndex++); //part of painting the layer
+ EXPECT_EQ(SkRect::MakeLTRB(0, 0, LAYER_WIDTH, LAYER_HEIGHT), getBounds(this));
+ }
+ void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
+ EXPECT_EQ(1, mIndex++);
+ EXPECT_EQ(SkRect::MakeLTRB(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), getBounds(this));
+ }
+ void onDrawOval(const SkRect&, const SkPaint&) override {
+ EXPECT_EQ(2, mIndex++);
+ SkMatrix expectedMatrix;
+ expectedMatrix.setTranslate(100 - SCROLL_X, 100 - SCROLL_Y);
+ EXPECT_EQ(expectedMatrix, getTotalMatrix());
+ EXPECT_EQ(SkRect::MakeLTRB(-85, -80, 295, 300), getLocalBounds(this));
+ }
+ int mIndex = 0;
+ };
+
+ class ProjectionLayer : public SkSurface_Base {
+ public:
+ ProjectionLayer(ProjectionTestCanvas *canvas)
+ : SkSurface_Base(SkImageInfo::MakeN32Premul(LAYER_WIDTH, LAYER_HEIGHT), nullptr)
+ , mCanvas(canvas) {
+ }
+ void onDraw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*) override {
+ EXPECT_EQ(3, mCanvas->mIndex++);
+ EXPECT_EQ(SkRect::MakeLTRB(100 - SCROLL_X, 100 - SCROLL_Y, 300 - SCROLL_X,
+ 300 - SCROLL_Y), getBounds(mCanvas));
+ }
+ SkCanvas* onNewCanvas() override {
+ mCanvas->ref();
+ return mCanvas;
+ }
+ sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override {
+ return sk_sp<SkSurface>();
+ }
+ sk_sp<SkImage> onNewImageSnapshot(SkBudgeted, SkCopyPixelsMode) override {
+ return sk_sp<SkImage>();
+ }
+ void onCopyOnWrite(ContentChangeMode) override {}
+ ProjectionTestCanvas* mCanvas;
+ };
+
+ auto receiverBackground = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
+ [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+ properties.setProjectionReceiver(true);
+ // scroll doesn't apply to background, so undone via translationX/Y
+ // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
+ properties.setTranslationX(SCROLL_X);
+ properties.setTranslationY(SCROLL_Y);
+
+ canvas.drawRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, SkPaint());
+ }, "B"); //B
+ auto projectingRipple = TestUtils::createSkiaNode(0, 0, LAYER_WIDTH, LAYER_HEIGHT,
+ [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+ properties.setProjectBackwards(true);
+ properties.setClipToBounds(false);
+ canvas.drawOval(100, 100, 300, 300, SkPaint()); // drawn mostly out of layer bounds
+ }, "R"); //R
+ auto child = TestUtils::createSkiaNode(100, 100, 300, 300,
+ [&projectingRipple](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+ canvas.drawRenderNode(projectingRipple.get());
+ canvas.drawArc(0, 0, LAYER_WIDTH, LAYER_HEIGHT, 0.0f, 280.0f, true, SkPaint());
+ }, "C"); //C
+ auto parent = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
+ [&receiverBackground, &child](RenderProperties& properties,
+ SkiaRecordingCanvas& canvas) {
+ // Set a rect outline for the projecting ripple to be masked against.
+ properties.mutableOutline().setRoundRect(10, 10, 390, 390, 0, 1.0f);
+ canvas.translate(-SCROLL_X, -SCROLL_Y); // Apply scroll (note: bg undoes this internally)
+ canvas.drawRenderNode(receiverBackground.get());
+ canvas.drawRenderNode(child.get());
+ }, "A"); //A
+
+ //prepareTree is required to find, which receivers have backward projected nodes
+ ContextFactory contextFactory;
+ std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
+ renderThread, false, parent.get(), &contextFactory));
+ TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
+ DamageAccumulator damageAccumulator;
+ info.damageAccumulator = &damageAccumulator;
+ info.observer = nullptr;
+ parent->prepareTree(info);
+
+ sk_sp<ProjectionTestCanvas> canvas(new ProjectionTestCanvas());
+ //set a layer after prepareTree to avoid layer logic there
+ child->animatorProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
+ sk_sp<SkSurface> surfaceLayer1(new ProjectionLayer(canvas.get()));
+ child->setLayerSurface(surfaceLayer1);
+ Matrix4 windowTransform;
+ windowTransform.loadTranslate(100, 100, 0);
+ child->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform);
+
+ LayerUpdateQueue layerUpdateQueue;
+ layerUpdateQueue.enqueueLayerWithDamage(child.get(),
+ android::uirenderer::Rect(LAYER_WIDTH, LAYER_HEIGHT));
+ SkiaPipeline::renderLayersImpl(layerUpdateQueue, true);
+ EXPECT_EQ(1, canvas->mIndex); //assert index 0 is drawn on the layer
+
+ RenderNodeDrawable drawable(parent.get(), canvas.get(), true);
+ canvas->drawDrawable(&drawable);
+ EXPECT_EQ(4, canvas->mIndex);
+
+ // clean up layer pointer, so we can safely destruct RenderNode
+ child->setLayerSurface(nullptr);
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionChildScroll) {
+ /* R is backward projected on B.
+ A
+ / \
+ B C
+ |
+ R
+ */
+ static const int SCROLL_X = 500000;
+ static const int SCROLL_Y = 0;
+ static const int CANVAS_WIDTH = 400;
+ static const int CANVAS_HEIGHT = 400;
+ class ProjectionChildScrollTestCanvas : public SkCanvas {
+ public:
+ ProjectionChildScrollTestCanvas() : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) {}
+ void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_TRUE(getTotalMatrix().isIdentity());
+ }
+ void onDrawOval(const SkRect&, const SkPaint&) override {
+ EXPECT_EQ(1, mIndex++);
+ EXPECT_EQ(SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), getBounds(this));
+ EXPECT_TRUE(getTotalMatrix().isIdentity());
+ }
+ int mIndex = 0;
+ };
+
+ auto receiverBackground = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
+ [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+ properties.setProjectionReceiver(true);
+ canvas.drawRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, SkPaint());
+ }, "B"); //B
+ auto projectingRipple = TestUtils::createSkiaNode(0, 0, 200, 200,
+ [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+ // scroll doesn't apply to background, so undone via translationX/Y
+ // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
+ properties.setTranslationX(SCROLL_X);
+ properties.setTranslationY(SCROLL_Y);
+ properties.setProjectBackwards(true);
+ properties.setClipToBounds(false);
+ canvas.drawOval(0, 0, 200, 200, SkPaint());
+ }, "R"); //R
+ auto child = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
+ [&projectingRipple](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+ // Record time clip will be ignored by projectee
+ canvas.clipRect(100, 100, 300, 300, SkRegion::kIntersect_Op);
+
+ canvas.translate(-SCROLL_X, -SCROLL_Y); // Apply scroll (note: bg undoes this internally)
+ canvas.drawRenderNode(projectingRipple.get());
+ }, "C"); //C
+ auto parent = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
+ [&receiverBackground, &child](RenderProperties& properties,
+ SkiaRecordingCanvas& canvas) {
+ canvas.drawRenderNode(receiverBackground.get());
+ canvas.drawRenderNode(child.get());
+ }, "A"); //A
+
+ //prepareTree is required to find, which receivers have backward projected nodes
+ ContextFactory contextFactory;
+ std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
+ renderThread, false, parent.get(), &contextFactory));
+ TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
+ DamageAccumulator damageAccumulator;
+ info.damageAccumulator = &damageAccumulator;
+ info.observer = nullptr;
+ parent->prepareTree(info);
+
+ sk_sp<ProjectionChildScrollTestCanvas> canvas(new ProjectionChildScrollTestCanvas());
+ RenderNodeDrawable drawable(parent.get(), canvas.get(), true);
+ canvas->drawDrawable(&drawable);
+ EXPECT_EQ(2, canvas->mIndex);
+}
+
+namespace {
+static int drawNode(RenderThread& renderThread, const sp<RenderNode>& renderNode)
+{
+ ContextFactory contextFactory;
+ std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
+ renderThread, false, renderNode.get(), &contextFactory));
+ TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
+ DamageAccumulator damageAccumulator;
+ info.damageAccumulator = &damageAccumulator;
+ info.observer = nullptr;
+ renderNode->prepareTree(info);
+
+ //create a canvas not backed by any device/pixels, but with dimensions to avoid quick rejection
+ ZReorderCanvas canvas(100, 100);
+ RenderNodeDrawable drawable(renderNode.get(), &canvas, false);
+ canvas.drawDrawable(&drawable);
+ return canvas.getIndex();
+}
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderProjectedInMiddle) {
+ /* R is backward projected on B
+ A
+ / \
+ B C
+ |
+ R
+ */
+ auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+ [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ props.setProjectionReceiver(true);
+ } ); //nodeB
+ drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ props.setProjectBackwards(true);
+ props.setClipToBounds(false);
+ } ); //nodeR
+ } ); //nodeC
+ }); //nodeA
+ EXPECT_EQ(3, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderProjectLast) {
+ /* R is backward projected on E
+ A
+ / | \
+ / | \
+ B C E
+ |
+ R
+ */
+ auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+ [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, nullptr); //nodeB
+ drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 3, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //drawn as 2
+ props.setProjectBackwards(true);
+ props.setClipToBounds(false);
+ } ); //nodeR
+ } ); //nodeC
+ drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //drawn as 3
+ props.setProjectionReceiver(true);
+ } ); //nodeE
+ }); //nodeA
+ EXPECT_EQ(4, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderNoReceivable) {
+ /* R is backward projected without receiver
+ A
+ / \
+ B C
+ |
+ R
+ */
+ auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+ [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, nullptr); //nodeB
+ drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 255, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ //not having a projection receiver is an undefined behavior
+ props.setProjectBackwards(true);
+ props.setClipToBounds(false);
+ } ); //nodeR
+ } ); //nodeC
+ }); //nodeA
+ EXPECT_EQ(2, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderParentReceivable) {
+ /* R is backward projected on C
+ A
+ / \
+ B C
+ |
+ R
+ */
+ auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+ [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, nullptr); //nodeB
+ drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ props.setProjectionReceiver(true);
+ drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ props.setProjectBackwards(true);
+ props.setClipToBounds(false);
+ } ); //nodeR
+ } ); //nodeC
+ }); //nodeA
+ EXPECT_EQ(3, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderSameNodeReceivable) {
+ /* R is backward projected on R
+ A
+ / \
+ B C
+ |
+ R
+ */
+ auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+ [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, nullptr); //nodeB
+ drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 255, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ //having a node that is projected on itself is an undefined/unexpected behavior
+ props.setProjectionReceiver(true);
+ props.setProjectBackwards(true);
+ props.setClipToBounds(false);
+ } ); //nodeR
+ } ); //nodeC
+ }); //nodeA
+ EXPECT_EQ(2, drawNode(renderThread, nodeA));
+}
+
+//Note: the outcome for this test is different in HWUI
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderProjectedSibling) {
+ /* R is set to project on B, but R is not drawn because projecting on a sibling is not allowed.
+ A
+ /|\
+ / | \
+ B C R
+ */
+ auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+ [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ props.setProjectionReceiver(true);
+ } ); //nodeB
+ drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ } ); //nodeC
+ drawOrderedNode(&canvas, 255, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ props.setProjectBackwards(true);
+ props.setClipToBounds(false);
+ } ); //nodeR
+ }); //nodeA
+ EXPECT_EQ(2, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderProjectedSibling2) {
+ /* R is set to project on B, but R is not drawn because projecting on a sibling is not allowed.
+ A
+ |
+ G
+ /|\
+ / | \
+ B C R
+ */
+ auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+ [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ props.setProjectionReceiver(true);
+ } ); //nodeB
+ drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ } ); //nodeC
+ drawOrderedNode(&canvas, 255, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ props.setProjectBackwards(true);
+ props.setClipToBounds(false);
+ } ); //nodeR
+ } ); //nodeG
+ }); //nodeA
+ EXPECT_EQ(3, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderGrandparentReceivable) {
+ /* R is backward projected on B
+ A
+ |
+ B
+ |
+ C
+ |
+ R
+ */
+ auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+ [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ props.setProjectionReceiver(true);
+ drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ props.setProjectBackwards(true);
+ props.setClipToBounds(false);
+ } ); //nodeR
+ } ); //nodeC
+ } ); //nodeB
+ }); //nodeA
+ EXPECT_EQ(3, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderTwoReceivables) {
+ /* B and G are receivables, R is backward projected
+ A
+ / \
+ B C
+ / \
+ G R
+ */
+ auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+ [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //B
+ props.setProjectionReceiver(true);
+ } ); //nodeB
+ drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //C
+ drawOrderedNode(&canvas, 3, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //G
+ props.setProjectionReceiver(true);
+ } ); //nodeG
+ drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //R
+ props.setProjectBackwards(true);
+ props.setClipToBounds(false);
+ } ); //nodeR
+ } ); //nodeC
+ }); //nodeA
+ EXPECT_EQ(4, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderTwoReceivablesLikelyScenario) {
+ /* B and G are receivables, G is backward projected
+ A
+ / \
+ B C
+ / \
+ G R
+ */
+ auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+ [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //B
+ props.setProjectionReceiver(true);
+ } ); //nodeB
+ drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //C
+ drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //G
+ props.setProjectionReceiver(true);
+ props.setProjectBackwards(true);
+ props.setClipToBounds(false);
+ } ); //nodeG
+ drawOrderedNode(&canvas, 3, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //R
+ } ); //nodeR
+ } ); //nodeC
+ }); //nodeA
+ EXPECT_EQ(4, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderTwoReceivablesDeeper) {
+ /* B and G are receivables, R is backward projected
+ A
+ / \
+ B C
+ / \
+ G D
+ |
+ R
+ */
+ auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+ [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //B
+ props.setProjectionReceiver(true);
+ } ); //nodeB
+ drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //C
+ drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //G
+ props.setProjectionReceiver(true);
+ } ); //nodeG
+ drawOrderedNode(&canvas, 4, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //D
+ drawOrderedNode(&canvas, 3, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //R
+ props.setProjectBackwards(true);
+ props.setClipToBounds(false);
+ } ); //nodeR
+ } ); //nodeD
+ } ); //nodeC
+ }); //nodeA
+ EXPECT_EQ(5, drawNode(renderThread, nodeA));
}
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index 67fb78a..899758a 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -33,7 +33,7 @@
SkRect bounds = SkRect::MakeWH(200, 200);
SkiaDisplayList skiaDL(bounds);
ASSERT_TRUE(skiaDL.isEmpty());
- ASSERT_FALSE(skiaDL.mIsProjectionReceiver);
+ ASSERT_FALSE(skiaDL.mProjectionReceiver);
ASSERT_EQ(skiaDL.mDrawable->getBounds(), bounds);
}
@@ -42,12 +42,13 @@
SkiaDisplayList skiaDL(bounds);
SkCanvas dummyCanvas;
+ RenderNodeDrawable drawable(nullptr, &dummyCanvas);
skiaDL.mChildNodes.emplace_back(nullptr, &dummyCanvas);
skiaDL.mChildFunctors.emplace_back(nullptr, nullptr, &dummyCanvas);
skiaDL.mMutableImages.push_back(nullptr);
skiaDL.mVectorDrawables.push_back(nullptr);
skiaDL.mDrawable->drawAnnotation(bounds, "testAnnotation", nullptr);
- skiaDL.mIsProjectionReceiver = true;
+ skiaDL.mProjectionReceiver = &drawable;
ASSERT_EQ(skiaDL.mDrawable->getBounds(), bounds);
ASSERT_FALSE(skiaDL.mChildNodes.empty());
@@ -55,7 +56,7 @@
ASSERT_FALSE(skiaDL.mMutableImages.empty());
ASSERT_FALSE(skiaDL.mVectorDrawables.empty());
ASSERT_FALSE(skiaDL.isEmpty());
- ASSERT_TRUE(skiaDL.mIsProjectionReceiver);
+ ASSERT_TRUE(skiaDL.mProjectionReceiver);
bounds = SkRect::MakeWH(100, 100);
skiaDL.reset(bounds);
@@ -66,7 +67,7 @@
ASSERT_TRUE(skiaDL.mMutableImages.empty());
ASSERT_TRUE(skiaDL.mVectorDrawables.empty());
ASSERT_TRUE(skiaDL.isEmpty());
- ASSERT_FALSE(skiaDL.mIsProjectionReceiver);
+ ASSERT_FALSE(skiaDL.mProjectionReceiver);
}
TEST(SkiaDisplayList, reuseDisplayList) {
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 89709ee..5440f0f 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -584,6 +584,10 @@
* @return the same Builder instance.
*/
public Builder setLegacyStreamType(int streamType) {
+ if (streamType == AudioManager.STREAM_ACCESSIBILITY) {
+ throw new IllegalArgumentException("STREAM_ACCESSIBILITY is not a legacy stream "
+ + "type that was used for audio playback");
+ }
return setInternalLegacyStreamType(streamType);
}
@@ -624,12 +628,15 @@
mContentType = CONTENT_TYPE_SONIFICATION;
break;
case AudioSystem.STREAM_TTS:
+ mContentType = CONTENT_TYPE_SONIFICATION;
+ break;
+ case AudioSystem.STREAM_ACCESSIBILITY:
mContentType = CONTENT_TYPE_SPEECH;
break;
default:
Log.e(TAG, "Invalid stream type " + streamType + " for AudioAttributes");
}
- mUsage = usageForLegacyStreamType(streamType);
+ mUsage = usageForStreamType(streamType);
return this;
}
@@ -842,8 +849,7 @@
}
}
- /** @hide */
- public static int usageForLegacyStreamType(int streamType) {
+ private static int usageForStreamType(int streamType) {
switch(streamType) {
case AudioSystem.STREAM_VOICE_CALL:
return USAGE_VOICE_COMMUNICATION;
@@ -862,8 +868,9 @@
return USAGE_VOICE_COMMUNICATION;
case AudioSystem.STREAM_DTMF:
return USAGE_VOICE_COMMUNICATION_SIGNALLING;
- case AudioSystem.STREAM_TTS:
+ case AudioSystem.STREAM_ACCESSIBILITY:
return USAGE_ASSISTANCE_ACCESSIBILITY;
+ case AudioSystem.STREAM_TTS:
default:
return USAGE_UNKNOWN;
}
@@ -915,7 +922,6 @@
switch (aa.getUsage()) {
case USAGE_MEDIA:
case USAGE_GAME:
- case USAGE_ASSISTANCE_ACCESSIBILITY:
case USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
return AudioSystem.STREAM_MUSIC;
case USAGE_ASSISTANCE_SONIFICATION:
@@ -935,6 +941,8 @@
case USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
case USAGE_NOTIFICATION_EVENT:
return AudioSystem.STREAM_NOTIFICATION;
+ case USAGE_ASSISTANCE_ACCESSIBILITY:
+ return AudioSystem.STREAM_ACCESSIBILITY;
case USAGE_UNKNOWN:
return fromGetVolumeControlStream ?
AudioManager.USE_DEFAULT_STREAM_TYPE : AudioSystem.STREAM_MUSIC;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index f24bf09..435e6ba 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -335,6 +335,9 @@
/** @hide Used to identify the volume of audio streams exclusively transmitted through the
* speaker (TTS) of the device */
public static final int STREAM_TTS = AudioSystem.STREAM_TTS;
+ /** Used to identify the volume of audio streams for accessibility prompts */
+ public static final int STREAM_ACCESSIBILITY = AudioSystem.STREAM_ACCESSIBILITY;
+
/** Number of audio streams */
/**
* @deprecated Do not iterate on volume stream type values.
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 384be5c..28c7253 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -63,13 +63,15 @@
/** Used to identify the volume of audio streams exclusively transmitted through the
* speaker (TTS) of the device */
public static final int STREAM_TTS = 9;
+ /** Used to identify the volume of audio streams for accessibility prompts */
+ public static final int STREAM_ACCESSIBILITY = 10;
/**
* @deprecated Use {@link #numStreamTypes() instead}
*/
public static final int NUM_STREAMS = 5;
// Expose only the getter method publicly so we can change it in the future
- private static final int NUM_STREAM_TYPES = 10;
+ private static final int NUM_STREAM_TYPES = 11;
public static final int getNumStreamTypes() { return NUM_STREAM_TYPES; }
public static final String[] STREAM_NAMES = new String[] {
@@ -82,7 +84,8 @@
"STREAM_BLUETOOTH_SCO",
"STREAM_SYSTEM_ENFORCED",
"STREAM_DTMF",
- "STREAM_TTS"
+ "STREAM_TTS",
+ "STREAM_ACCESSIBILITY"
};
/*
@@ -773,7 +776,8 @@
7, // STREAM_BLUETOOTH_SCO
7, // STREAM_SYSTEM_ENFORCED
11, // STREAM_DTMF
- 11 // STREAM_TTS
+ 11, // STREAM_TTS
+ 11, // STREAM_ACCESSIBILITY
};
public static String streamToString(int stream) {
diff --git a/media/mca/filterfw/jni/jni_gl_environment.cpp b/media/mca/filterfw/jni/jni_gl_environment.cpp
index 096120e..823e88a 100644
--- a/media/mca/filterfw/jni/jni_gl_environment.cpp
+++ b/media/mca/filterfw/jni/jni_gl_environment.cpp
@@ -63,7 +63,8 @@
};
jboolean Java_android_filterfw_core_GLEnvironment_nativeAllocate(JNIEnv* env, jobject thiz) {
- return ToJBool(WrapObjectInJava(new GLEnv(), env, thiz, true));
+ std::unique_ptr<GLEnv> glEnv(new GLEnv());
+ return ToJBool(WrapOwnedObjectInJava(std::move(glEnv), env, thiz, true));
}
jboolean Java_android_filterfw_core_GLEnvironment_nativeDeallocate(JNIEnv* env, jobject thiz) {
diff --git a/media/mca/filterfw/jni/jni_gl_frame.cpp b/media/mca/filterfw/jni/jni_gl_frame.cpp
index b55bc5d..27b4cd2 100644
--- a/media/mca/filterfw/jni/jni_gl_frame.cpp
+++ b/media/mca/filterfw/jni/jni_gl_frame.cpp
@@ -48,13 +48,11 @@
jint height) {
GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env);
if (!gl_env_ptr) return JNI_FALSE;
- GLFrame* frame = new GLFrame(gl_env_ptr);
+ std::unique_ptr<GLFrame> frame(new GLFrame(gl_env_ptr));
if (frame->Init(width, height)) {
- return ToJBool(WrapObjectInJava(frame, env, thiz, true));
- } else {
- delete frame;
- return JNI_FALSE;
+ return ToJBool(WrapOwnedObjectInJava(std::move(frame), env, thiz, true));
}
+ return JNI_FALSE;
}
jboolean Java_android_filterfw_core_GLFrame_nativeAllocateWithTexture(JNIEnv* env,
@@ -65,13 +63,11 @@
jint height) {
GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env);
if (!gl_env_ptr) return JNI_FALSE;
- GLFrame* frame = new GLFrame(gl_env_ptr);
+ std::unique_ptr<GLFrame> frame(new GLFrame(gl_env_ptr));
if (frame->InitWithTexture(tex_id, width, height)) {
- return ToJBool(WrapObjectInJava(frame, env, thiz, true));
- } else {
- delete frame;
- return JNI_FALSE;
+ return ToJBool(WrapOwnedObjectInJava(std::move(frame), env, thiz, true));
}
+ return JNI_FALSE;
}
jboolean Java_android_filterfw_core_GLFrame_nativeAllocateWithFbo(JNIEnv* env,
@@ -82,13 +78,11 @@
jint height) {
GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env);
if (!gl_env_ptr) return JNI_FALSE;
- GLFrame* frame = new GLFrame(gl_env_ptr);
+ std::unique_ptr<GLFrame> frame(new GLFrame(gl_env_ptr));
if (frame->InitWithFbo(fbo_id, width, height)) {
- return ToJBool(WrapObjectInJava(frame, env, thiz, true));
- } else {
- delete frame;
- return JNI_FALSE;
+ return ToJBool(WrapOwnedObjectInJava(std::move(frame), env, thiz, true));
}
+ return JNI_FALSE;
}
jboolean Java_android_filterfw_core_GLFrame_nativeAllocateExternal(JNIEnv* env,
@@ -96,13 +90,11 @@
jobject gl_env) {
GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env);
if (!gl_env_ptr) return JNI_FALSE;
- GLFrame* frame = new GLFrame(gl_env_ptr);
+ std::unique_ptr<GLFrame> frame(new GLFrame(gl_env_ptr));
if (frame->InitWithExternalTexture()) {
- return ToJBool(WrapObjectInJava(frame, env, thiz, true));
- } else {
- delete frame;
- return JNI_FALSE;
+ return ToJBool(WrapOwnedObjectInJava(std::move(frame), env, thiz, true));
}
+ return JNI_FALSE;
}
jboolean Java_android_filterfw_core_GLFrame_nativeDeallocate(JNIEnv* env, jobject thiz) {
diff --git a/media/mca/filterfw/jni/jni_native_frame.cpp b/media/mca/filterfw/jni/jni_native_frame.cpp
index c8f2352..1a11a19 100644
--- a/media/mca/filterfw/jni/jni_native_frame.cpp
+++ b/media/mca/filterfw/jni/jni_native_frame.cpp
@@ -35,7 +35,8 @@
jboolean Java_android_filterfw_core_NativeFrame_nativeAllocate(JNIEnv* env,
jobject thiz,
jint size) {
- return ToJBool(WrapObjectInJava(new NativeFrame(size), env, thiz, true));
+ std::unique_ptr<NativeFrame> frame(new NativeFrame(size));
+ return ToJBool(WrapOwnedObjectInJava(std::move(frame), env, thiz, true));
}
jboolean Java_android_filterfw_core_NativeFrame_nativeDeallocate(JNIEnv* env, jobject thiz) {
diff --git a/media/mca/filterfw/jni/jni_native_program.cpp b/media/mca/filterfw/jni/jni_native_program.cpp
index b30b769..1424607 100644
--- a/media/mca/filterfw/jni/jni_native_program.cpp
+++ b/media/mca/filterfw/jni/jni_native_program.cpp
@@ -28,7 +28,8 @@
using android::filterfw::NativeProgram;
jboolean Java_android_filterfw_core_NativeProgram_allocate(JNIEnv* env, jobject thiz) {
- return ToJBool(WrapObjectInJava(new NativeProgram(), env, thiz, true));
+ std::unique_ptr<NativeProgram> program(new NativeProgram());
+ return ToJBool(WrapOwnedObjectInJava(std::move(program), env, thiz, true));
}
jboolean Java_android_filterfw_core_NativeProgram_deallocate(JNIEnv* env, jobject thiz) {
diff --git a/media/mca/filterfw/jni/jni_shader_program.cpp b/media/mca/filterfw/jni/jni_shader_program.cpp
index 19f43cd..98be04c 100644
--- a/media/mca/filterfw/jni/jni_shader_program.cpp
+++ b/media/mca/filterfw/jni/jni_shader_program.cpp
@@ -46,21 +46,14 @@
// Create the shader
if (!fragment_shader || !gl_env_ptr)
return false;
- else if (!vertex_shader)
- return ToJBool(WrapObjectInJava(new ShaderProgram(
- gl_env_ptr,
- ToCppString(env, fragment_shader)),
- env,
- thiz,
- true));
+
+ std::unique_ptr<ShaderProgram> shader;
+ if (!vertex_shader)
+ shader.reset(new ShaderProgram(gl_env_ptr, ToCppString(env, fragment_shader)));
else
- return ToJBool(WrapObjectInJava(new ShaderProgram(
- gl_env_ptr,
- ToCppString(env, vertex_shader),
- ToCppString(env, fragment_shader)),
- env,
- thiz,
- true));
+ shader.reset(new ShaderProgram(gl_env_ptr, ToCppString(env, vertex_shader),
+ ToCppString(env, fragment_shader)));
+ return ToJBool(WrapOwnedObjectInJava(std::move(shader), env, thiz, true));
}
jboolean Java_android_filterfw_core_ShaderProgram_deallocate(JNIEnv* env, jobject thiz) {
diff --git a/media/mca/filterfw/jni/jni_util.h b/media/mca/filterfw/jni/jni_util.h
index 11c0871..803ed29 100644
--- a/media/mca/filterfw/jni/jni_util.h
+++ b/media/mca/filterfw/jni/jni_util.h
@@ -16,6 +16,7 @@
#include <jni.h>
+#include <memory>
#include <unordered_map>
#include <string>
@@ -214,6 +215,17 @@
return pool ? pool->WrapObject(c_object, env, j_object, owns) : false;
}
+// Calls WrapObjectInJava, safely freeing c_object if object creation fails.
+template<typename T>
+bool WrapOwnedObjectInJava(std::unique_ptr<T> c_object, JNIEnv* env,
+ jobject j_object, bool owns) {
+ if (!WrapObjectInJava<T>(c_object.get(), env, j_object, owns))
+ return false;
+ // If we succeeded, a Java object now owns our c object; don't free it.
+ c_object.release();
+ return true;
+}
+
// Creates a new Java instance, which wraps the passed C++ instance. Returns
// the wrapped object or JNI_NULL if there was an error. Pass true to owns, if
// the Java layer should own the object.
diff --git a/media/mca/filterfw/jni/jni_vertex_frame.cpp b/media/mca/filterfw/jni/jni_vertex_frame.cpp
index caae938..d0439fe 100644
--- a/media/mca/filterfw/jni/jni_vertex_frame.cpp
+++ b/media/mca/filterfw/jni/jni_vertex_frame.cpp
@@ -24,7 +24,8 @@
jboolean Java_android_filterfw_core_VertexFrame_nativeAllocate(JNIEnv* env,
jobject thiz,
jint size) {
- return ToJBool(WrapObjectInJava(new VertexFrame(size), env, thiz, true));
+ std::unique_ptr<VertexFrame> frame(new VertexFrame(size));
+ return ToJBool(WrapOwnedObjectInJava(std::move(frame), env, thiz, true));
}
jboolean Java_android_filterfw_core_VertexFrame_nativeDeallocate(JNIEnv* env, jobject thiz) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
new file mode 100644
index 0000000..651a7cb
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.drawer;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Pair;
+
+import com.android.settingslib.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.when;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class TileUtilsTest {
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private PackageManager mPackageManager;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ }
+
+ @Test
+ public void getTilesForIntent_shouldParseCategory() {
+ final String testCategory = "category1";
+ Intent intent = new Intent();
+ Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+ List<Tile> outTiles = new ArrayList<>();
+ List<ResolveInfo> info = new ArrayList<>();
+ info.add(newInfo(true, testCategory));
+
+ when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
+ .thenReturn(info);
+
+ TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
+ null /* defaultCategory */, outTiles, false /* usePriority */,
+ false /* checkCategory */);
+
+ assertThat(outTiles.size()).isEqualTo(1);
+ assertThat(outTiles.get(0).category).isEqualTo(testCategory);
+ }
+
+ @Test
+ public void getTilesForIntent_shouldSkipNonSystemApp() {
+ final String testCategory = "category1";
+ Intent intent = new Intent();
+ Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+ List<Tile> outTiles = new ArrayList<>();
+ List<ResolveInfo> info = new ArrayList<>();
+ info.add(newInfo(false, testCategory));
+
+ when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
+ .thenReturn(info);
+
+ TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
+ null /* defaultCategory */, outTiles, false /* usePriority */,
+ false /* checkCategory */);
+
+ assertThat(outTiles.isEmpty()).isTrue();
+ }
+
+ private ResolveInfo newInfo(boolean systemApp, String category) {
+ ResolveInfo info = new ResolveInfo();
+ info.system = systemApp;
+ info.activityInfo = new ActivityInfo();
+ info.activityInfo.packageName = "abc";
+ info.activityInfo.name = "123";
+ info.activityInfo.metaData = new Bundle();
+ info.activityInfo.metaData.putString("com.android.settings.category", category);
+ info.activityInfo.applicationInfo = new ApplicationInfo();
+ return info;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index 995f4dd..29c0705 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -137,7 +137,8 @@
private void applyFocusableFlag(State state) {
boolean panelFocusable = state.statusBarFocusable && state.panelExpanded;
- if (state.bouncerShowing || BaseStatusBar.ENABLE_REMOTE_INPUT && state.remoteInputActive) {
+ if (state.bouncerShowing && (state.keyguardOccluded || state.keyguardNeedsInput)
+ || BaseStatusBar.ENABLE_REMOTE_INPUT && state.remoteInputActive) {
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
} else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index de70026..5eceb9f 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -5342,6 +5342,7 @@
// notify only this one new request of the current state
protected void notifyNetworkCallback(NetworkAgentInfo nai, NetworkRequestInfo nri) {
int notifyType = ConnectivityManager.CALLBACK_AVAILABLE;
+ mHandler.removeMessages(EVENT_TIMEOUT_NETWORK_REQUEST, nri);
if (nri.mPendingIntent == null) {
callCallbackForRequest(nri, nai, notifyType, 0);
} else {
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index f4ddc06..6701431 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -249,16 +249,13 @@
try {
randomLockSeed = SecureRandom.getInstance("SHA1PRNG").generateSeed(40);
String newPassword = String.valueOf(HexEncoding.encode(randomLockSeed));
- tieProfileLockToParent(managedUserId, newPassword);
setLockPasswordInternal(newPassword, managedUserPassword, managedUserId);
// We store a private credential for the managed user that's unlocked by the primary
// account holder's credential. As such, the user will never be prompted to enter this
// password directly, so we always store a password.
setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, managedUserId);
- } catch (KeyStoreException e) {
- // Bug: 32490092
- Slog.e(TAG, "Not able to set keys to keystore", e);
+ tieProfileLockToParent(managedUserId, newPassword);
} catch (NoSuchAlgorithmException | RemoteException e) {
Slog.e(TAG, "Fail to tie managed profile", e);
// Nothing client can do to fix this issue, so we do not throw exception out
@@ -779,7 +776,6 @@
}
private void unlockChildProfile(int profileHandle) throws RemoteException {
- if (DEBUG) Slog.v(TAG, "Unlock child profile");
try {
doVerifyPassword(getDecryptedPasswordForTiedProfile(profileHandle), false,
0 /* no challenge */, profileHandle, null /* progressCallback */);
@@ -1039,7 +1035,7 @@
}
}
- private void tieProfileLockToParent(int userId, String password) throws KeyStoreException {
+ private void tieProfileLockToParent(int userId, String password) {
if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
byte[] randomLockSeed = password.getBytes(StandardCharsets.UTF_8);
byte[] encryptionResult;
@@ -1081,7 +1077,7 @@
keyStore.deleteEntry(LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userId);
}
} catch (CertificateException | UnrecoverableKeyException
- | IOException | BadPaddingException | IllegalBlockSizeException
+ | IOException | BadPaddingException | IllegalBlockSizeException | KeyStoreException
| NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException("Failed to encrypt key", e);
}
@@ -1223,11 +1219,7 @@
} finally {
if (managedUserId != -1 && managedUserDecryptedPassword != null) {
if (DEBUG) Slog.v(TAG, "Restore tied profile lock");
- try {
- tieProfileLockToParent(managedUserId, managedUserDecryptedPassword);
- } catch (KeyStoreException e) {
- throw new RuntimeException("Failed to tie profile lock", e);
- }
+ tieProfileLockToParent(managedUserId, managedUserDecryptedPassword);
}
}
}
diff --git a/services/core/java/com/android/server/accounts/AccountsDb.java b/services/core/java/com/android/server/accounts/AccountsDb.java
index cb594f6..a160b3a 100644
--- a/services/core/java/com/android/server/accounts/AccountsDb.java
+++ b/services/core/java/com/android/server/accounts/AccountsDb.java
@@ -54,7 +54,7 @@
private static final String DATABASE_NAME = "accounts.db";
private static final int PRE_N_DATABASE_VERSION = 9;
private static final int CE_DATABASE_VERSION = 10;
- private static final int DE_DATABASE_VERSION = 1;
+ private static final int DE_DATABASE_VERSION = 2; // Added visibility support in O.
static final String TABLE_ACCOUNTS = "accounts";
@@ -73,6 +73,11 @@
private static final String AUTHTOKENS_TYPE = "type";
private static final String AUTHTOKENS_AUTHTOKEN = "authtoken";
+ private static final String TABLE_VISIBILITY = "visibility";
+ private static final String VISIBILITY_ACCOUNTS_ID = "accounts_id";
+ private static final String VISIBILITY_UID = "_uid";
+ private static final String VISIBILITY_VALUE = "value";
+
private static final String TABLE_GRANTS = "grants";
private static final String GRANTS_ACCOUNTS_ID = "accounts_id";
private static final String GRANTS_AUTH_TOKEN_TYPE = "auth_token_type";
@@ -153,14 +158,12 @@
+ " AND " + ACCOUNTS_NAME + "=?"
+ " AND " + ACCOUNTS_TYPE + "=?";
- private static final String SELECTION_AUTHTOKENS_BY_ACCOUNT =
- AUTHTOKENS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)";
+ private static final String SELECTION_ACCOUNTS_ID_BY_ACCOUNT =
+ "accounts_id=(select _id FROM accounts WHERE name=? AND type=?)";
- private static final String[] COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN = {AUTHTOKENS_TYPE,
- AUTHTOKENS_AUTHTOKEN};
+ private static final String[] COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN =
+ {AUTHTOKENS_TYPE, AUTHTOKENS_AUTHTOKEN};
- private static final String SELECTION_USERDATA_BY_ACCOUNT =
- EXTRAS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)";
private static final String[] COLUMNS_EXTRAS_KEY_AND_VALUE = {EXTRAS_KEY, EXTRAS_VALUE};
private static final String ACCOUNT_ACCESS_GRANTS = ""
@@ -334,7 +337,7 @@
HashMap<String, String> authTokensForAccount = new HashMap<>();
Cursor cursor = db.query(CE_TABLE_AUTHTOKENS,
COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN,
- SELECTION_AUTHTOKENS_BY_ACCOUNT,
+ SELECTION_ACCOUNTS_ID_BY_ACCOUNT,
new String[] {account.name, account.type},
null, null, null);
try {
@@ -438,7 +441,7 @@
String[] selectionArgs = {account.name, account.type};
try (Cursor cursor = db.query(CE_TABLE_EXTRAS,
COLUMNS_EXTRAS_KEY_AND_VALUE,
- SELECTION_USERDATA_BY_ACCOUNT,
+ SELECTION_ACCOUNTS_ID_BY_ACCOUNT,
selectionArgs,
null, null, null)) {
while (cursor.moveToNext()) {
@@ -523,6 +526,8 @@
createSharedAccountsTable(db);
createAccountsDeletionTrigger(db);
createDebugTable(db);
+ createAccountsVisibilityTable(db);
+ createAccountsDeletionVisibilityCleanupTrigger(db);
}
private void createSharedAccountsTable(SQLiteDatabase db) {
@@ -551,6 +556,14 @@
+ "," + GRANTS_GRANTEE_UID + "))");
}
+ private void createAccountsVisibilityTable(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + TABLE_VISIBILITY + " ( "
+ + VISIBILITY_ACCOUNTS_ID + " INTEGER NOT NULL, "
+ + VISIBILITY_UID + " TEXT NOT NULL, "
+ + VISIBILITY_VALUE + " INTEGER, "
+ + "PRIMARY KEY(" + VISIBILITY_ACCOUNTS_ID + "," + VISIBILITY_UID + "))");
+ }
+
static void createDebugTable(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE_DEBUG + " ( "
+ ACCOUNTS_ID + " INTEGER,"
@@ -563,10 +576,26 @@
+ DEBUG_TABLE_TIMESTAMP + ")");
}
+ private void createAccountsDeletionVisibilityCleanupTrigger(SQLiteDatabase db) {
+ db.execSQL(""
+ + " CREATE TRIGGER "
+ + TABLE_ACCOUNTS + "DeleteVisibility DELETE ON " + TABLE_ACCOUNTS
+ + " BEGIN"
+ + " DELETE FROM " + TABLE_VISIBILITY
+ + " WHERE " + VISIBILITY_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
+ + " END");
+ }
+
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.i(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
+ if (oldVersion == 1) {
+ createAccountsVisibilityTable(db);
+ createAccountsDeletionVisibilityCleanupTrigger(db);
+ ++oldVersion;
+ }
+
if (oldVersion != newVersion) {
Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
}
@@ -861,6 +890,84 @@
new String[] {Integer.toString(uid)}) > 0;
}
+ boolean setAccountVisibility(long accountId, int uid, int visibility) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(VISIBILITY_ACCOUNTS_ID, String.valueOf(accountId));
+ values.put(VISIBILITY_UID, String.valueOf(uid));
+ values.put(VISIBILITY_VALUE, String.valueOf(visibility));
+ return (db.replace(TABLE_VISIBILITY, VISIBILITY_VALUE, values) != -1);
+ }
+
+ Integer findAccountVisibility(Account account, int uid) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+ final Cursor cursor = db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_VALUE},
+ SELECTION_ACCOUNTS_ID_BY_ACCOUNT + " AND " + VISIBILITY_UID + "=? ",
+ new String[] {account.name, account.type, String.valueOf(uid)}, null, null, null);
+ try {
+ while (cursor.moveToNext()) {
+ return cursor.getInt(0);
+ }
+ } finally {
+ cursor.close();
+ }
+ return null;
+ }
+
+ Integer findAccountVisibility(long accountId, int uid) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+ final Cursor cursor = db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_VALUE},
+ VISIBILITY_ACCOUNTS_ID + "=? AND " + VISIBILITY_UID + "=? ",
+ new String[] {String.valueOf(accountId), String.valueOf(uid)}, null, null, null);
+ try {
+ while (cursor.moveToNext()) {
+ return cursor.getInt(0);
+ }
+ } finally {
+ cursor.close();
+ }
+ return null;
+ }
+
+ Account findDeAccountByAccountId(long accountId) {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabase();
+ final Cursor cursor = db.query(TABLE_ACCOUNTS, new String[] {ACCOUNTS_NAME, ACCOUNTS_TYPE},
+ ACCOUNTS_ID + "=? ", new String[] {String.valueOf(accountId)}, null, null, null);
+ try {
+ while (cursor.moveToNext()) {
+ return new Account(cursor.getString(0), cursor.getString(1));
+ }
+ } finally {
+ cursor.close();
+ }
+ return null;
+ }
+
+ /**
+ * Returns a map from uid to visibility value.
+ */
+ Map<Integer, Integer> findAccountVisibilityForAccountId(long accountId) {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabase();
+ Map<Integer, Integer> result = new HashMap<>();
+ final Cursor cursor = db.query(TABLE_VISIBILITY,
+ new String[] {VISIBILITY_UID, VISIBILITY_VALUE}, VISIBILITY_ACCOUNTS_ID + "=? ",
+ new String[] {String.valueOf(accountId)}, null, null, null);
+ try {
+ while (cursor.moveToNext()) {
+ result.put(cursor.getInt(0), cursor.getInt(1));
+ }
+ } finally {
+ cursor.close();
+ }
+ return result;
+ }
+
+ boolean deleteAccountVisibilityForUid(int uid) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+ return db.delete(TABLE_VISIBILITY, VISIBILITY_UID + "=? ",
+ new String[] {Integer.toString(uid)}) > 0;
+ }
+
long insertOrReplaceMetaAuthTypeAndUid(String authenticatorType, int uid) {
SQLiteDatabase db = mDeDatabase.getWritableDatabase();
ContentValues values = new ContentValues();
@@ -1199,4 +1306,4 @@
return new AccountsDb(deDatabaseHelper, context, preNDatabaseFile);
}
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8bd7506..c8ed872 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1520,7 +1520,7 @@
static final int PERSIST_URI_GRANTS_MSG = 38;
static final int REQUEST_ALL_PSS_MSG = 39;
static final int START_PROFILES_MSG = 40;
- static final int UPDATE_TIME = 41;
+ static final int UPDATE_TIME_PREFERENCE_MSG = 41;
static final int SYSTEM_USER_START_MSG = 42;
static final int SYSTEM_USER_CURRENT_MSG = 43;
static final int ENTER_ANIMATION_COMPLETE_MSG = 44;
@@ -2026,15 +2026,18 @@
}
break;
}
- case UPDATE_TIME: {
+ case UPDATE_TIME_PREFERENCE_MSG: {
+ // The user's time format preference might have changed.
+ // For convenience we re-use the Intent extra values.
synchronized (ActivityManagerService.this) {
- for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
+ for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord r = mLruProcesses.get(i);
if (r.thread != null) {
try {
- r.thread.updateTimePrefs(msg.arg1 == 0 ? false : true);
+ r.thread.updateTimePrefs(msg.arg1);
} catch (RemoteException ex) {
- Slog.w(TAG, "Failed to update preferences for: " + r.info.processName);
+ Slog.w(TAG, "Failed to update preferences for: "
+ + r.info.processName);
}
}
}
@@ -18169,11 +18172,20 @@
mHandler.sendEmptyMessage(UPDATE_TIME_ZONE);
break;
case Intent.ACTION_TIME_CHANGED:
- // If the user set the time, let all running processes know.
- final int is24Hour =
- intent.getBooleanExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, false) ? 1
- : 0;
- mHandler.sendMessage(mHandler.obtainMessage(UPDATE_TIME, is24Hour, 0));
+ // EXTRA_TIME_PREF_24_HOUR_FORMAT is optional so we must distinguish between
+ // the tri-state value it may contain and "unknown".
+ // For convenience we re-use the Intent extra values.
+ final int NO_EXTRA_VALUE_FOUND = -1;
+ final int timeFormatPreferenceMsgValue = intent.getIntExtra(
+ Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT,
+ NO_EXTRA_VALUE_FOUND /* defaultValue */);
+ // Only send a message if the time preference is available.
+ if (timeFormatPreferenceMsgValue != NO_EXTRA_VALUE_FOUND) {
+ Message updateTimePreferenceMsg =
+ mHandler.obtainMessage(UPDATE_TIME_PREFERENCE_MSG,
+ timeFormatPreferenceMsgValue, 0);
+ mHandler.sendMessage(updateTimePreferenceMsg);
+ }
BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
synchronized (stats) {
stats.noteCurrentTimeChangedLocked();
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index da016da..ac9545c 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -272,7 +272,8 @@
15, // STREAM_BLUETOOTH_SCO
7, // STREAM_SYSTEM_ENFORCED
15, // STREAM_DTMF
- 15 // STREAM_TTS
+ 15, // STREAM_TTS
+ 15 // STREAM_ACCESSIBILITY
};
/** Minimum volume index values for audio streams */
@@ -286,7 +287,8 @@
0, // STREAM_BLUETOOTH_SCO
0, // STREAM_SYSTEM_ENFORCED
0, // STREAM_DTMF
- 0 // STREAM_TTS
+ 0, // STREAM_TTS
+ 0 // STREAM_ACCESSIBILITY
};
/* mStreamVolumeAlias[] indicates for each stream if it uses the volume settings
@@ -308,7 +310,8 @@
AudioSystem.STREAM_BLUETOOTH_SCO, // STREAM_BLUETOOTH_SCO
AudioSystem.STREAM_RING, // STREAM_SYSTEM_ENFORCED
AudioSystem.STREAM_RING, // STREAM_DTMF
- AudioSystem.STREAM_MUSIC // STREAM_TTS
+ AudioSystem.STREAM_MUSIC, // STREAM_TTS
+ AudioSystem.STREAM_MUSIC // STREAM_ACCESSIBILITY
};
private final int[] STREAM_VOLUME_ALIAS_TELEVISION = new int[] {
AudioSystem.STREAM_MUSIC, // STREAM_VOICE_CALL
@@ -320,7 +323,8 @@
AudioSystem.STREAM_MUSIC, // STREAM_BLUETOOTH_SCO
AudioSystem.STREAM_MUSIC, // STREAM_SYSTEM_ENFORCED
AudioSystem.STREAM_MUSIC, // STREAM_DTMF
- AudioSystem.STREAM_MUSIC // STREAM_TTS
+ AudioSystem.STREAM_MUSIC, // STREAM_TTS
+ AudioSystem.STREAM_MUSIC // STREAM_ACCESSIBILITY
};
private final int[] STREAM_VOLUME_ALIAS_DEFAULT = new int[] {
AudioSystem.STREAM_VOICE_CALL, // STREAM_VOICE_CALL
@@ -332,7 +336,8 @@
AudioSystem.STREAM_BLUETOOTH_SCO, // STREAM_BLUETOOTH_SCO
AudioSystem.STREAM_RING, // STREAM_SYSTEM_ENFORCED
AudioSystem.STREAM_RING, // STREAM_DTMF
- AudioSystem.STREAM_MUSIC // STREAM_TTS
+ AudioSystem.STREAM_MUSIC, // STREAM_TTS
+ AudioSystem.STREAM_MUSIC // STREAM_ACCESSIBILITY
};
private int[] mStreamVolumeAlias;
@@ -351,6 +356,7 @@
AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_SYSTEM_ENFORCED
AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_DTMF
AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_TTS
+ AppOpsManager.OP_AUDIO_ACCESSIBILITY_VOLUME, // STREAM_ACCESSIBILITY
};
private final boolean mUseFixedVolume;
diff --git a/services/core/java/com/android/server/pm/EphemeralResolver.java b/services/core/java/com/android/server/pm/EphemeralResolver.java
index 7ddd058..3ce5007 100644
--- a/services/core/java/com/android/server/pm/EphemeralResolver.java
+++ b/services/core/java/com/android/server/pm/EphemeralResolver.java
@@ -46,10 +46,6 @@
/** @hide */
public abstract class EphemeralResolver {
-
- /** TODO b/30204367 remove when the platform fully supports ephemeral applications */
- public static final boolean USE_DEFAULT_EPHEMERAL_LAUNCHER = false;
-
public static EphemeralResponse doEphemeralResolutionPhaseOne(Context context,
EphemeralResolverConnection connection, EphemeralRequest requestObj) {
final Intent intent = requestObj.origIntent;
@@ -162,16 +158,10 @@
new IntentSender(failureIntentTarget));
} catch (RemoteException ignore) { /* ignore; same process */ }
- final Intent ephemeralIntent;
- if (EphemeralResolver.USE_DEFAULT_EPHEMERAL_LAUNCHER) {
- // Force the intent to be directed to the ephemeral package
- ephemeralIntent = new Intent(origIntent);
- ephemeralIntent.setPackage(ephemeralPackageName);
- } else {
- // Success intent goes back to the installer
- ephemeralIntent = new Intent(launchIntent);
- }
-
+ // Success intent goes back to the installer
+ final Intent ephemeralIntent = new Intent(launchIntent)
+ .setComponent(null)
+ .setPackage(ephemeralPackageName);
// Intent that is eventually launched if the ephemeral package was
// installed successfully. This will actually be launched by a platform
// broadcast receiver.
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ee3f42b..a8db535 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -391,19 +391,17 @@
static final int SCAN_FORCE_DEX = 1<<2;
static final int SCAN_UPDATE_SIGNATURE = 1<<3;
static final int SCAN_NEW_INSTALL = 1<<4;
- static final int SCAN_NO_PATHS = 1<<5;
- static final int SCAN_UPDATE_TIME = 1<<6;
- static final int SCAN_DEFER_DEX = 1<<7;
- static final int SCAN_BOOTING = 1<<8;
- static final int SCAN_TRUSTED_OVERLAY = 1<<9;
- static final int SCAN_DELETE_DATA_ON_FAILURES = 1<<10;
- static final int SCAN_REPLACING = 1<<11;
- static final int SCAN_REQUIRE_KNOWN = 1<<12;
- static final int SCAN_MOVE = 1<<13;
- static final int SCAN_INITIAL = 1<<14;
- static final int SCAN_CHECK_ONLY = 1<<15;
- static final int SCAN_DONT_KILL_APP = 1<<17;
- static final int SCAN_IGNORE_FROZEN = 1<<18;
+ static final int SCAN_UPDATE_TIME = 1<<5;
+ static final int SCAN_BOOTING = 1<<6;
+ static final int SCAN_TRUSTED_OVERLAY = 1<<7;
+ static final int SCAN_DELETE_DATA_ON_FAILURES = 1<<8;
+ static final int SCAN_REPLACING = 1<<9;
+ static final int SCAN_REQUIRE_KNOWN = 1<<10;
+ static final int SCAN_MOVE = 1<<11;
+ static final int SCAN_INITIAL = 1<<12;
+ static final int SCAN_CHECK_ONLY = 1<<13;
+ static final int SCAN_DONT_KILL_APP = 1<<14;
+ static final int SCAN_IGNORE_FROZEN = 1<<15;
static final int REMOVE_CHATTY = 1<<16;
@@ -2219,7 +2217,7 @@
// Set flag to monitor and not change apk file paths when
// scanning install directories.
- final int scanFlags = SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING | SCAN_INITIAL;
+ final int scanFlags = SCAN_BOOTING | SCAN_INITIAL;
final String bootClassPath = System.getenv("BOOTCLASSPATH");
final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH");
@@ -16223,7 +16221,8 @@
final PackageParser.Package newPkg;
try {
- newPkg = scanPackageTracedLI(disabledPs.codePath, parseFlags, SCAN_NO_PATHS, 0, null);
+ newPkg = scanPackageTracedLI(disabledPs.codePath, parseFlags, 0 /* scanFlags */,
+ 0 /* currentTime */, null);
} catch (PackageManagerException e) {
Slog.w(TAG, "Failed to restore system package:" + deletedPkg.packageName + ": "
+ e.getMessage());
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index eef8ce2..e330585 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2470,7 +2470,10 @@
final File file = new File(dir, "appid");
try {
- FileUtils.stringToFile(file, Integer.toString(ps.appId));
+ // Note that the use of US_ASCII here is safe, we're only writing a decimal
+ // number to the file.
+ FileUtils.bytesToFile(file.getAbsolutePath(),
+ Integer.toString(ps.appId).getBytes(StandardCharsets.US_ASCII));
mKernelMapping.put(ps.name, ps.appId);
} catch (IOException ignored) {
}
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index e1b598a..c42647e 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -411,7 +411,7 @@
}
if (mService.mInputMethodTarget != null
&& mService.mInputMethodTarget.mAppToken == mAppToken) {
- mAppToken.getDisplayContent().moveInputMethodWindowsIfNeeded(true);
+ mAppToken.getDisplayContent().computeImeTarget(true /* updateImeTarget */);
}
if (DEBUG_ANIM) Slog.v(TAG, "Animation done in " + mAppToken
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 5d739d1..4569596 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -1006,10 +1006,7 @@
tStartingWindow.mToken = this;
tStartingWindow.mAppToken = this;
- if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) Slog.v(TAG_WM,
- "Removing starting window: " + tStartingWindow);
- getDisplayContent().removeFromWindowList(tStartingWindow);
- if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+ if (DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) Slog.v(TAG_WM,
"Removing starting " + tStartingWindow + " from " + fromToken);
fromToken.removeChild(tStartingWindow);
fromToken.postWindowRemoveStartingWindowCleanup(tStartingWindow);
@@ -1259,18 +1256,6 @@
}
}
- int rebuildWindowListUnchecked(int addIndex) {
- return super.rebuildWindowList(addIndex);
- }
-
- @Override
- int rebuildWindowList(int addIndex) {
- if (mIsExiting && !waitingForReplacement()) {
- return addIndex;
- }
- return rebuildWindowListUnchecked(addIndex);
- }
-
@Override
boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
// For legacy reasons we process the TaskStack.mExitingAppTokens first in DisplayContent
@@ -1332,6 +1317,32 @@
mLastContainsShowWhenLockedWindow = containsShowWhenLocked;
}
+ WindowState getImeTargetBelowWindow(WindowState w) {
+ final int index = mChildren.indexOf(w);
+ if (index > 0) {
+ final WindowState target = mChildren.get(index - 1);
+ if (target.canBeImeTarget()) {
+ return target;
+ }
+ }
+ return null;
+ }
+
+ WindowState getHighestAnimLayerWindow(WindowState currentTarget) {
+ WindowState candidate = null;
+ for (int i = mChildren.indexOf(currentTarget); i >= 0; i--) {
+ final WindowState w = mChildren.get(i);
+ if (w.mRemoved) {
+ continue;
+ }
+ if (candidate == null || w.mWinAnimator.mAnimLayer >
+ candidate.mWinAnimator.mAnimLayer) {
+ candidate = w;
+ }
+ }
+ return candidate;
+ }
+
@Override
void dump(PrintWriter pw, String prefix) {
super.dump(pw, prefix);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e73acde..24b9d69 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -33,10 +33,6 @@
import static android.view.WindowManager.DOCKED_BOTTOM;
import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.DOCKED_TOP;
-import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION;
-import static android.view.WindowManager.INPUT_CONSUMER_PIP;
-import static android.view.WindowManager.INPUT_CONSUMER_WALLPAPER;
-import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
@@ -44,13 +40,10 @@
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.NEEDS_MENU_SET_TRUE;
import static android.view.WindowManager.LayoutParams.NEEDS_MENU_UNSET;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
@@ -64,7 +57,6 @@
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
@@ -77,10 +69,8 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREEN_ON;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -94,7 +84,6 @@
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
import static com.android.server.wm.WindowManagerService.dipToPixel;
-import static com.android.server.wm.WindowManagerService.localLOGV;
import static com.android.server.wm.WindowManagerService.logSurface;
import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;
import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING;
@@ -120,29 +109,21 @@
import android.util.Slog;
import android.view.Display;
import android.view.DisplayInfo;
-import android.view.IWindow;
-import android.view.InputChannel;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManagerPolicy;
-import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.ToBooleanFunction;
import com.android.internal.view.IInputMethodClient;
-import com.android.server.input.InputWindowHandle;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.io.StringWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
-import java.util.function.Consumer;
-import java.util.function.Function;
/**
* Utility class for keeping track of the WindowStates and other pertinent contents of a
@@ -174,8 +155,7 @@
private final NonAppWindowContainers mImeWindowsContainers =
new NonAppWindowContainers("mImeWindowsContainers");
- // Z-ordered (bottom-most first) list of all Window objects.
- private final WindowList mWindows = new WindowList();
+ private WindowState mTmpWindow;
// Mapping from a token IBinder to a WindowToken object on this display.
private final HashMap<IBinder, WindowToken> mTokenMap = new HashMap();
@@ -232,21 +212,19 @@
final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>();
- /** Used when rebuilding window list to keep track of windows that have been removed. */
- private WindowState[] mRebuildTmp = new WindowState[20];
-
- /**
- * Temporary list for comparison. Always clear this after use so we don't end up with
- * orphaned windows references
- */
- private final ArrayList<WindowState> mTmpWindows = new ArrayList<>();
+ private boolean mHaveBootMsg = false;
+ private boolean mHaveApp = false;
+ private boolean mHaveWallpaper = false;
+ private boolean mHaveKeyguard = true;
private final LinkedList<AppWindowToken> mTmpUpdateAllDrawn = new LinkedList();
private final TaskForResizePointSearchResult mTmpTaskForResizePointSearchResult =
new TaskForResizePointSearchResult();
- private final GetWindowOnDisplaySearchResult mTmpGetWindowOnDisplaySearchResult =
- new GetWindowOnDisplaySearchResult();
+ private final ApplySurfaceChangesTransactionState mTmpApplySurfaceChangesTransactionState =
+ new ApplySurfaceChangesTransactionState();
+ private final ScreenshotApplicationState mScreenshotApplicationState =
+ new ScreenshotApplicationState();
// True if this display is in the process of being removed. Used to determine if the removal of
// the display's direct children should be allowed.
@@ -455,17 +433,13 @@
@Override
void onAppTransitionDone() {
super.onAppTransitionDone();
- rebuildAppWindowList();
+ mService.mWindowsChanged = true;
}
@Override
int getOrientation() {
final WindowManagerPolicy policy = mService.mPolicy;
- // TODO: All the logic before the last return statement in this method should really go in
- // #NonAppWindowContainer.getOrientation() since it is trying to decide orientation based
- // on non-app windows. But, we can not do that until the window list is always correct in
- // terms of z-ordering based on layers.
if (mService.mDisplayFrozen) {
if (mService.mLastWindowForcedOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
@@ -486,31 +460,9 @@
return mService.mLastOrientation;
}
} else {
- for (int pos = mWindows.size() - 1; pos >= 0; --pos) {
- final WindowState win = mWindows.get(pos);
- if (win.mAppToken != null) {
- // We hit an application window. so the orientation will be determined by the
- // app window. No point in continuing further.
- break;
- }
- if (!win.isVisibleLw() || !win.mPolicyVisibilityAfterAnim) {
- continue;
- }
- int req = win.mAttrs.screenOrientation;
- if(req == SCREEN_ORIENTATION_UNSPECIFIED || req == SCREEN_ORIENTATION_BEHIND) {
- continue;
- }
-
- if (DEBUG_ORIENTATION) Slog.v(TAG_WM, win + " forcing orientation to " + req);
- if (policy.isKeyguardHostWindow(win.mAttrs)) {
- mService.mLastKeyguardForcedOrientation = req;
- }
- return (mService.mLastWindowForcedOrientation = req);
- }
- mService.mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
-
- if (policy.isKeyguardShowingAndNotOccluded()) {
- return mService.mLastKeyguardForcedOrientation;
+ final int orientation = mAboveAppWindowsContainers.getOrientation();
+ if (orientation != SCREEN_ORIENTATION_UNSET) {
+ return orientation;
}
}
@@ -738,22 +690,10 @@
}
}
+ @Override
void switchUser() {
- final int count = mWindows.size();
- for (int i = 0; i < count; i++) {
- final WindowState win = mWindows.get(i);
- if (win.isHiddenFromUserLocked()) {
- if (DEBUG_VISIBILITY) Slog.w(TAG_WM, "user changing, hiding " + win
- + ", attrs=" + win.mAttrs.type + ", belonging to " + win.mOwnerUid);
- win.hideLw(false);
- }
- }
-
- for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
- mTaskStackContainers.get(stackNdx).switchUser();
- }
-
- rebuildAppWindowList();
+ super.switchUser();
+ mService.mWindowsChanged = true;
}
private void resetAnimationBackgroundAnimator() {
@@ -915,19 +855,9 @@
void setInputMethodAnimLayerAdjustment(int adj) {
if (DEBUG_LAYERS) Slog.v(TAG_WM, "Setting im layer adj to " + adj);
mInputMethodAnimLayerAdjustment = adj;
- final WindowState imw = mService.mInputMethodWindow;
- if (imw != null) {
- imw.adjustAnimLayer(adj);
- }
- for (int i = mService.mInputMethodDialogs.size() - 1; i >= 0; i--) {
- final WindowState dialog = mService.mInputMethodDialogs.get(i);
- // TODO: This and other places setting mAnimLayer can probably use WS.adjustAnimLayer,
- // but need to make sure we are not setting things twice for child windows that are
- // already in the list.
- dialog.mWinAnimator.mAnimLayer = dialog.mLayer + adj;
- if (DEBUG_LAYERS) Slog.v(TAG_WM, "IM win " + imw
- + " anim layer: " + dialog.mWinAnimator.mAnimLayer);
- }
+ mImeWindowsContainers.forAllWindows(w -> {
+ w.adjustAnimLayer(adj);
+ }, true /* traverseTopToBottom */);
}
/**
@@ -936,11 +866,11 @@
* suddenly disappear.
*/
int getLayerForAnimationBackground(WindowStateAnimator winAnimator) {
- for (int i = mWindows.size() - 1; i >= 0; --i) {
- final WindowState win = mWindows.get(i);
- if (win.mIsWallpaper && win.isVisibleNow()) {
- return win.mWinAnimator.mAnimLayer;
- }
+ final WindowState visibleWallpaper = mBelowAppWindowsContainers.getWindow(
+ w -> w.mIsWallpaper && w.isVisibleNow());
+
+ if (visibleWallpaper != null) {
+ return visibleWallpaper.mWinAnimator.mAnimLayer;
}
return winAnimator.mAnimLayer;
}
@@ -1088,48 +1018,37 @@
/** Find the visible, touch-deliverable window under the given point */
WindowState getTouchableWinAtPointLocked(float xf, float yf) {
- WindowState touchedWin = null;
final int x = (int) xf;
final int y = (int) yf;
-
- for (int i = mWindows.size() - 1; i >= 0; i--) {
- WindowState window = mWindows.get(i);
- final int flags = window.mAttrs.flags;
- if (!window.isVisibleLw()) {
- continue;
+ final WindowState touchedWin = getWindow(w -> {
+ final int flags = w.mAttrs.flags;
+ if (!w.isVisibleLw()) {
+ return false;
}
if ((flags & FLAG_NOT_TOUCHABLE) != 0) {
- continue;
+ return false;
}
- window.getVisibleBounds(mTmpRect);
+ w.getVisibleBounds(mTmpRect);
if (!mTmpRect.contains(x, y)) {
- continue;
+ return false;
}
- window.getTouchableRegion(mTmpRegion);
+ w.getTouchableRegion(mTmpRegion);
final int touchFlags = flags & (FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL);
- if (mTmpRegion.contains(x, y) || touchFlags == 0) {
- touchedWin = window;
- break;
- }
- }
+ return mTmpRegion.contains(x, y) || touchFlags == 0;
+ });
return touchedWin;
}
boolean canAddToastWindowForUid(int uid) {
// We allow one toast window per UID being shown at a time.
- final int windowCount = mWindows.size();
- for (int i = 0; i < windowCount; i++) {
- final WindowState window = mWindows.get(i);
- if (window.mAttrs.type == TYPE_TOAST && window.mOwnerUid == uid
- && !window.mPermanentlyHidden && !window.mWindowRemovalAllowed) {
- return false;
- }
- }
- return true;
+ final WindowState win = getWindow(w ->
+ w.mAttrs.type == TYPE_TOAST && w.mOwnerUid == uid && !w.mPermanentlyHidden
+ && !w.mWindowRemovalAllowed);
+ return win == null;
}
void scheduleToastWindowsTimeoutIfNeededLocked(WindowState oldFocus, WindowState newFocus) {
@@ -1137,268 +1056,76 @@
return;
}
final int lostFocusUid = oldFocus.mOwnerUid;
- final int windowCount = mWindows.size();
final Handler handler = mService.mH;
- for (int i = 0; i < windowCount; i++) {
- final WindowState window = mWindows.get(i);
- if (window.mAttrs.type == TYPE_TOAST && window.mOwnerUid == lostFocusUid) {
- if (!handler.hasMessages(WINDOW_HIDE_TIMEOUT, window)) {
- handler.sendMessageDelayed(handler.obtainMessage(WINDOW_HIDE_TIMEOUT, window),
- window.mAttrs.hideTimeoutMilliseconds);
+
+ forAllWindows(w -> {
+ if (w.mAttrs.type == TYPE_TOAST && w.mOwnerUid == lostFocusUid) {
+ if (!handler.hasMessages(WINDOW_HIDE_TIMEOUT, w)) {
+ handler.sendMessageDelayed(handler.obtainMessage(WINDOW_HIDE_TIMEOUT, w),
+ w.mAttrs.hideTimeoutMilliseconds);
}
}
- }
+ }, false /* traverseTopToBottom */);
}
WindowState findFocusedWindow() {
final AppWindowToken focusedApp = mService.mFocusedApp;
+ mTmpWindow = null;
- for (int i = mWindows.size() - 1; i >= 0; i--) {
- final WindowState win = mWindows.get(i);
+ forAllWindows(w -> {
+ if (DEBUG_FOCUS) Slog.v(TAG_WM, "Looking for focus: " + w
+ + ", flags=" + w.mAttrs.flags + ", canReceive=" + w.canReceiveKeys());
- if (DEBUG_FOCUS) Slog.v(TAG_WM, "Looking for focus: " + i + " = " + win
- + ", flags=" + win.mAttrs.flags + ", canReceive=" + win.canReceiveKeys());
-
- if (!win.canReceiveKeys()) {
- continue;
+ if (!w.canReceiveKeys()) {
+ return false;
}
- final AppWindowToken wtoken = win.mAppToken;
+ final AppWindowToken wtoken = w.mAppToken;
// If this window's application has been removed, just skip it.
if (wtoken != null && (wtoken.removed || wtoken.sendingToBottom)) {
if (DEBUG_FOCUS) Slog.v(TAG_WM, "Skipping " + wtoken + " because "
+ (wtoken.removed ? "removed" : "sendingToBottom"));
- continue;
+ return false;
}
if (focusedApp == null) {
if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: focusedApp=null"
- + " using new focus @ " + i + " = " + win);
- return win;
+ + " using new focus @ " + w);
+ mTmpWindow = w;
+ return true;
}
if (!focusedApp.windowsAreFocusable()) {
// Current focused app windows aren't focusable...
if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: focusedApp windows not"
- + " focusable using new focus @ " + i + " = " + win);
- return win;
+ + " focusable using new focus @ " + w);
+ mTmpWindow = w;
+ return true;
}
// Descend through all of the app tokens and find the first that either matches
// win.mAppToken (return win) or mFocusedApp (return null).
- if (wtoken != null && win.mAttrs.type != TYPE_APPLICATION_STARTING) {
+ if (wtoken != null && w.mAttrs.type != TYPE_APPLICATION_STARTING) {
if (focusedApp.compareTo(wtoken) > 0) {
// App stack below focused app stack. No focus for you!!!
if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM,
"findFocusedWindow: Reached focused app=" + focusedApp);
- return null;
+ mTmpWindow = null;
+ return true;
}
}
- if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: Found new focus @ "
- + i + " = " + win);
- return win;
+ if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: Found new focus @ " + w);
+ mTmpWindow = w;
+ return true;
+ }, true /* traverseTopToBottom */);
+
+ if (mTmpWindow == null) {
+ if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: No focusable windows.");
+ return null;
}
-
- if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: No focusable windows.");
- return null;
- }
-
- void addAppWindowToWindowList(final WindowState win) {
- final IWindow client = win.mClient;
-
- WindowList tokenWindowList = getTokenWindowsOnDisplay(win.mToken);
- if (!tokenWindowList.isEmpty()) {
- addAppWindowExisting(win, tokenWindowList);
- return;
- }
-
- // No windows from this token on this display
- if (localLOGV) Slog.v(TAG_WM, "Figuring out where to add app window "
- + client.asBinder() + " (token=" + this + ")");
-
- final WindowToken wToken = win.mToken;
-
- // Figure out where the window should go, based on the order of applications.
- mTmpGetWindowOnDisplaySearchResult.reset();
- for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
- final TaskStack stack = mTaskStackContainers.get(i);
- stack.getWindowOnDisplayBeforeToken(this, wToken, mTmpGetWindowOnDisplaySearchResult);
- if (mTmpGetWindowOnDisplaySearchResult.reachedToken) {
- // We have reach the token we are interested in. End search.
- break;
- }
- }
-
- WindowState pos = mTmpGetWindowOnDisplaySearchResult.foundWindow;
-
- // We now know the index into the apps. If we found an app window above, that gives us the
- // position; else we need to look some more.
- if (pos != null) {
- // Move behind any windows attached to this one.
- final WindowToken atoken = getWindowToken(pos.mClient.asBinder());
- if (atoken != null) {
- tokenWindowList = getTokenWindowsOnDisplay(atoken);
- final int NC = tokenWindowList.size();
- if (NC > 0) {
- WindowState bottom = tokenWindowList.get(0);
- if (bottom.mSubLayer < 0) {
- pos = bottom;
- }
- }
- }
- addWindowToListBefore(win, pos);
- return;
- }
-
- // Continue looking down until we find the first token that has windows on this display.
- mTmpGetWindowOnDisplaySearchResult.reset();
- for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
- final TaskStack stack = mTaskStackContainers.get(i);
- stack.getWindowOnDisplayAfterToken(this, wToken, mTmpGetWindowOnDisplaySearchResult);
- if (mTmpGetWindowOnDisplaySearchResult.foundWindow != null) {
- // We have found a window after the token. End search.
- break;
- }
- }
-
- pos = mTmpGetWindowOnDisplaySearchResult.foundWindow;
-
- if (pos != null) {
- // Move in front of any windows attached to this one.
- final WindowToken atoken = getWindowToken(pos.mClient.asBinder());
- if (atoken != null) {
- final WindowState top = atoken.getTopWindow();
- if (top != null && top.mSubLayer >= 0) {
- pos = top;
- }
- }
- addWindowToListAfter(win, pos);
- return;
- }
-
- // Just search for the start of this layer.
- final int myLayer = win.mBaseLayer;
- int i;
- for (i = mWindows.size() - 1; i >= 0; --i) {
- final WindowState w = mWindows.get(i);
- // Dock divider shares the base layer with application windows, but we want to always
- // keep it above the application windows. The sharing of the base layer is intended
- // for window animations, which need to be above the dock divider for the duration
- // of the animation.
- if (w.mBaseLayer <= myLayer && w.mAttrs.type != TYPE_DOCK_DIVIDER) {
- break;
- }
- }
- if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
- "Based on layer: Adding window " + win + " at " + (i + 1) + " of "
- + mWindows.size());
- mWindows.add(i + 1, win);
- mService.mWindowsChanged = true;
- }
-
- /** Adds this non-app window to the window list. */
- void addNonAppWindowToWindowList(WindowState win) {
- // Figure out where window should go, based on layer.
- int i;
- for (i = mWindows.size() - 1; i >= 0; i--) {
- final WindowState otherWin = mWindows.get(i);
- if (otherWin.getBaseType() != TYPE_WALLPAPER && otherWin.mBaseLayer <= win.mBaseLayer) {
- // Wallpaper wanders through the window list, for example to position itself
- // directly behind keyguard. Because of this it will break the ordering based on
- // WindowState.mBaseLayer. There might windows with higher mBaseLayer behind it and
- // we don't want the new window to appear above them. An example of this is adding
- // of the docked stack divider. Consider a scenario with the following ordering (top
- // to bottom): keyguard, wallpaper, assist preview, apps. We want the dock divider
- // to land below the assist preview, so the dock divider must ignore the wallpaper,
- // with which it shares the base layer.
- break;
- }
- }
-
- i++;
- if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
- "Free window: Adding window " + this + " at " + i + " of " + mWindows.size());
- mWindows.add(i, win);
- mService.mWindowsChanged = true;
- }
-
- void addToWindowList(WindowState win, int index) {
- mService.mWindowsChanged = true;
- mWindows.add(index, win);
- }
-
- boolean removeFromWindowList(WindowState win) {
- mService.mWindowsChanged = true;
- return mWindows.remove(win);
- }
-
- private int removeWindowAndChildrenFromWindowList(WindowState win, int interestingPos) {
- int wpos = mWindows.indexOf(win);
- if (wpos < 0) {
- return interestingPos;
- }
-
- if (wpos < interestingPos) interestingPos--;
- if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Temp removing at " + wpos + ": " + this);
- mWindows.remove(wpos);
- mService.mWindowsChanged = true;
- int childWinCount = win.mChildren.size();
- while (childWinCount > 0) {
- childWinCount--;
- final WindowState cw = win.mChildren.get(childWinCount);
- int cpos = mWindows.indexOf(cw);
- if (cpos >= 0) {
- if (cpos < interestingPos) interestingPos--;
- if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM,
- "Temp removing child at " + cpos + ": " + cw);
- mWindows.remove(cpos);
- }
- }
- return interestingPos;
- }
-
- void addChildWindowToWindowList(WindowState win) {
- final WindowState parentWindow = win.getParentWindow();
-
- WindowList windowsOnSameDisplay = getTokenWindowsOnDisplay(win.mToken);
-
- // Figure out this window's ordering relative to the parent window.
- final int wCount = windowsOnSameDisplay.size();
- final int sublayer = win.mSubLayer;
- int largestSublayer = Integer.MIN_VALUE;
- WindowState windowWithLargestSublayer = null;
- int i;
- for (i = 0; i < wCount; i++) {
- WindowState w = windowsOnSameDisplay.get(i);
- final int wSublayer = w.mSubLayer;
- if (wSublayer >= largestSublayer) {
- largestSublayer = wSublayer;
- windowWithLargestSublayer = w;
- }
- if (sublayer < 0) {
- // For negative sublayers, we go below all windows in the same sublayer.
- if (wSublayer >= sublayer) {
- addWindowToListBefore(win, wSublayer >= 0 ? parentWindow : w);
- break;
- }
- } else {
- // For positive sublayers, we go above all windows in the same sublayer.
- if (wSublayer > sublayer) {
- addWindowToListBefore(win, w);
- break;
- }
- }
- }
- if (i >= wCount) {
- if (sublayer < 0) {
- addWindowToListBefore(win, parentWindow);
- } else {
- addWindowToListAfter(win,
- largestSublayer >= 0 ? windowWithLargestSublayer : parentWindow);
- }
- }
+ return mTmpWindow;
}
/** Updates the layer assignment of windows on this display. */
@@ -1409,136 +1136,9 @@
}
}
- void adjustWallpaperWindows() {
- mWallpaperController.adjustWallpaperWindows(this);
- }
-
- /**
- * Z-orders the display window list so that:
- * <ul>
- * <li>Any windows that are currently below the wallpaper window stay below the wallpaper
- * window.
- * <li>Exiting application windows are at the bottom, but above the wallpaper window.
- * <li>All other application windows are above the exiting application windows and ordered based
- * on the ordering of their stacks and tasks on the display.
- * <li>Non-application windows are at the very top.
- * </ul>
- * <p>
- * NOTE: This isn't a complete picture of what the user see. Further manipulation of the window
- * surface layering is done in {@link WindowLayersController}.
- */
- void rebuildAppWindowList() {
- int count = mWindows.size();
- int i;
- int lastBelow = -1;
- int numRemoved = 0;
-
- if (mRebuildTmp.length < count) {
- mRebuildTmp = new WindowState[count + 10];
- }
-
- // First remove all existing app windows.
- i = 0;
- while (i < count) {
- final WindowState w = mWindows.get(i);
- if (w.mAppToken != null) {
- final WindowState win = mWindows.remove(i);
- win.mRebuilding = true;
- mRebuildTmp[numRemoved] = win;
- mService.mWindowsChanged = true;
- if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Rebuild removing window: " + win);
- count--;
- numRemoved++;
- continue;
- } else if (lastBelow == i-1) {
- if (w.mAttrs.type == TYPE_WALLPAPER) {
- lastBelow = i;
- }
- }
- i++;
- }
-
- // Keep whatever windows were below the app windows still below, by skipping them.
- lastBelow++;
- i = lastBelow;
-
- // First add all of the exiting app tokens... these are no longer in the main app list,
- // but still have windows shown. We put them in the back because now that the animation is
- // over we no longer will care about them.
- final int numStacks = mTaskStackContainers.size();
- for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
- AppTokenList exitingAppTokens = mTaskStackContainers.get(stackNdx).mExitingAppTokens;
- int NT = exitingAppTokens.size();
- for (int j = 0; j < NT; j++) {
- i = exitingAppTokens.get(j).rebuildWindowListUnchecked(i);
- }
- }
-
- // And add in the still active app tokens in Z order.
- for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
- i = mTaskStackContainers.get(stackNdx).rebuildWindowList(i);
- }
-
- i -= lastBelow;
- if (i != numRemoved) {
- setLayoutNeeded();
- Slog.w(TAG_WM, "On display=" + mDisplayId + " Rebuild removed " + numRemoved
- + " windows but added " + i + " rebuildAppWindowListLocked() "
- + " callers=" + Debug.getCallers(10));
- for (i = 0; i < numRemoved; i++) {
- WindowState ws = mRebuildTmp[i];
- if (ws.mRebuilding) {
- StringWriter sw = new StringWriter();
- PrintWriter pw = new FastPrintWriter(sw, false, 1024);
- ws.dump(pw, "", true);
- pw.flush();
- Slog.w(TAG_WM, "This window was lost: " + ws);
- Slog.w(TAG_WM, sw.toString());
- ws.mWinAnimator.destroySurfaceLocked();
- }
- }
- Slog.w(TAG_WM, "Current window hierarchy:");
- dumpChildrenNames();
- Slog.w(TAG_WM, "Final window list:");
- dumpWindows();
- }
- Arrays.fill(mRebuildTmp, null);
- }
-
- /** Rebuilds the display's window list and does a relayout if something changed. */
- void rebuildAppWindowsAndLayoutIfNeeded() {
- mTmpWindows.clear();
- mTmpWindows.addAll(mWindows);
-
- rebuildAppWindowList();
-
- // Set displayContent.mLayoutNeeded if window order changed.
- final int tmpSize = mTmpWindows.size();
- final int winSize = mWindows.size();
- int tmpNdx = 0, winNdx = 0;
- while (tmpNdx < tmpSize && winNdx < winSize) {
- // Skip over all exiting windows, they've been moved out of order.
- WindowState tmp;
- do {
- tmp = mTmpWindows.get(tmpNdx++);
- } while (tmpNdx < tmpSize && tmp.mAppToken != null && tmp.mAppToken.mIsExiting);
-
- WindowState win;
- do {
- win = mWindows.get(winNdx++);
- } while (winNdx < winSize && win.mAppToken != null && win.mAppToken.mIsExiting);
-
- if (tmp != win) {
- // Window order changed.
- setLayoutNeeded();
- break;
- }
- }
- if (tmpNdx != winNdx) {
- // One list was different from the other.
- setLayoutNeeded();
- }
- mTmpWindows.clear();
+ void layoutAndAssignWindowLayersIfNeeded() {
+ mService.mWindowsChanged = true;
+ setLayoutNeeded();
if (!mService.updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
false /*updateInputWindows*/)) {
@@ -1550,321 +1150,69 @@
mService.mInputMonitor.updateInputWindowsLw(false /*force*/);
}
- void updateInputWindows(InputMonitor inputMonitor, WindowState inputFocus, boolean inDrag) {
- final InputConsumerImpl navInputConsumer =
- mService.mInputMonitor.getInputConsumer(INPUT_CONSUMER_NAVIGATION, mDisplayId);
- final InputConsumerImpl pipInputConsumer =
- mService.mInputMonitor.getInputConsumer(INPUT_CONSUMER_PIP, mDisplayId);
- final InputConsumerImpl wallpaperInputConsumer =
- mService.mInputMonitor.getInputConsumer(INPUT_CONSUMER_WALLPAPER, mDisplayId);
- boolean addInputConsumerHandle = navInputConsumer != null;
- boolean addPipInputConsumerHandle = pipInputConsumer != null;
- boolean addWallpaperInputConsumerHandle = wallpaperInputConsumer != null;
- final Rect pipTouchableBounds = addPipInputConsumerHandle ? new Rect() : null;
- boolean disableWallpaperTouchEvents = false;
-
- for (int winNdx = mWindows.size() - 1; winNdx >= 0; --winNdx) {
- final WindowState child = mWindows.get(winNdx);
- final InputChannel inputChannel = child.mInputChannel;
- final InputWindowHandle inputWindowHandle = child.mInputWindowHandle;
- if (inputChannel == null || inputWindowHandle == null || child.mRemoved
- || child.isAdjustedForMinimizedDock()) {
- // Skip this window because it cannot possibly receive input.
- continue;
- }
-
- if (addPipInputConsumerHandle
- && child.getStackId() == PINNED_STACK_ID
- && inputWindowHandle.layer <= pipInputConsumer.mWindowHandle.layer) {
- // Update the bounds of the Pip input consumer to match the Pinned stack
- child.getStack().getBounds(pipTouchableBounds);
- pipInputConsumer.mWindowHandle.touchableRegion.set(pipTouchableBounds);
- inputMonitor.addInputWindowHandle(pipInputConsumer.mWindowHandle);
- addPipInputConsumerHandle = false;
- }
-
- if (addInputConsumerHandle
- && inputWindowHandle.layer <= navInputConsumer.mWindowHandle.layer) {
- inputMonitor.addInputWindowHandle(navInputConsumer.mWindowHandle);
- addInputConsumerHandle = false;
- }
-
- if (addWallpaperInputConsumerHandle) {
- if (child.mAttrs.type == TYPE_WALLPAPER && child.isVisibleLw()) {
- // Add the wallpaper input consumer above the first visible wallpaper.
- inputMonitor.addInputWindowHandle(wallpaperInputConsumer.mWindowHandle);
- addWallpaperInputConsumerHandle = false;
- }
- }
-
- final int flags = child.mAttrs.flags;
- final int privateFlags = child.mAttrs.privateFlags;
- final int type = child.mAttrs.type;
-
- final boolean hasFocus = child == inputFocus;
- final boolean isVisible = child.isVisibleLw();
- if ((privateFlags & PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS) != 0) {
- disableWallpaperTouchEvents = true;
- }
- final boolean hasWallpaper = mWallpaperController.isWallpaperTarget(child)
- && (privateFlags & PRIVATE_FLAG_KEYGUARD) == 0
- && !disableWallpaperTouchEvents;
-
- // If there's a drag in progress and 'child' is a potential drop target,
- // make sure it's been told about the drag
- if (inDrag && isVisible && isDefaultDisplay) {
- mService.mDragState.sendDragStartedIfNeededLw(child);
- }
-
- inputMonitor.addInputWindowHandle(
- inputWindowHandle, child, flags, type, isVisible, hasFocus, hasWallpaper);
- }
-
- if (addWallpaperInputConsumerHandle) {
- // No visible wallpaper found, add the wallpaper input consumer at the end.
- inputMonitor.addInputWindowHandle(wallpaperInputConsumer.mWindowHandle);
- }
- }
-
/** Returns true if a leaked surface was destroyed */
boolean destroyLeakedSurfaces() {
- boolean leakedSurface = false;
- final int numWindows = mWindows.size();
- for (int winNdx = 0; winNdx < numWindows; ++winNdx) {
- final WindowState ws = mWindows.get(winNdx);
- final WindowStateAnimator wsa = ws.mWinAnimator;
+ // Used to indicate that a surface was leaked.
+ mTmpWindow = null;
+ forAllWindows(w -> {
+ final WindowStateAnimator wsa = w.mWinAnimator;
if (wsa.mSurfaceController == null) {
- continue;
+ return;
}
if (!mService.mSessions.contains(wsa.mSession)) {
Slog.w(TAG_WM, "LEAKED SURFACE (session doesn't exist): "
- + ws + " surface=" + wsa.mSurfaceController
- + " token=" + ws.mToken
- + " pid=" + ws.mSession.mPid
- + " uid=" + ws.mSession.mUid);
+ + w + " surface=" + wsa.mSurfaceController
+ + " token=" + w.mToken
+ + " pid=" + w.mSession.mPid
+ + " uid=" + w.mSession.mUid);
wsa.destroySurface();
- mService.mForceRemoves.add(ws);
- leakedSurface = true;
- } else if (ws.mAppToken != null && ws.mAppToken.clientHidden) {
+ mService.mForceRemoves.add(w);
+ mTmpWindow = w;
+ } else if (w.mAppToken != null && w.mAppToken.clientHidden) {
Slog.w(TAG_WM, "LEAKED SURFACE (app token hidden): "
- + ws + " surface=" + wsa.mSurfaceController
- + " token=" + ws.mAppToken
- + " saved=" + ws.hasSavedSurface());
- if (SHOW_TRANSACTIONS) logSurface(ws, "LEAK DESTROY", false);
+ + w + " surface=" + wsa.mSurfaceController
+ + " token=" + w.mAppToken
+ + " saved=" + w.hasSavedSurface());
+ if (SHOW_TRANSACTIONS) logSurface(w, "LEAK DESTROY", false);
wsa.destroySurface();
- leakedSurface = true;
+ mTmpWindow = w;
}
- }
+ }, false /* traverseTopToBottom */);
- return leakedSurface;
- }
-
- /** Return the list of Windows on this display associated with the input token. */
- WindowList getTokenWindowsOnDisplay(WindowToken token) {
- final WindowList windowList = new WindowList();
- final int count = mWindows.size();
- for (int i = 0; i < count; i++) {
- final WindowState win = mWindows.get(i);
- if (win.mToken == token) {
- windowList.add(win);
- }
- }
- return windowList;
- }
-
- private void reAddToWindowList(WindowState win) {
- win.mToken.addWindow(win);
- // This is a hack to get all of the child windows added as well at the right position. Child
- // windows should be rare and this case should be rare, so it shouldn't be that big a deal.
- int wpos = mWindows.indexOf(win);
- if (wpos >= 0) {
- if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "ReAdd removing from " + wpos + ": " + win);
- mWindows.remove(wpos);
- mService.mWindowsChanged = true;
- win.reAddWindow(wpos);
- }
- }
-
- void moveInputMethodDialogs(int pos) {
- ArrayList<WindowState> dialogs = mService.mInputMethodDialogs;
-
- final int N = dialogs.size();
- if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Removing " + N + " dialogs w/pos=" + pos);
- for (int i = 0; i < N; i++) {
- pos = removeWindowAndChildrenFromWindowList(dialogs.get(i), pos);
- }
- if (DEBUG_INPUT_METHOD) {
- Slog.v(TAG_WM, "Window list w/pos=" + pos);
- logWindowList(mWindows, " ");
- }
-
- WindowState ime = mService.mInputMethodWindow;
- if (pos >= 0) {
- // Skip windows owned by the input method.
- if (ime != null) {
- while (pos < mWindows.size()) {
- WindowState wp = mWindows.get(pos);
- if (wp == ime || wp.getParentWindow() == ime) {
- pos++;
- continue;
- }
- break;
- }
- }
- if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Adding " + N + " dialogs at pos=" + pos);
- for (int i=0; i<N; i++) {
- WindowState win = dialogs.get(i);
- pos = win.reAddWindow(pos);
- }
- if (DEBUG_INPUT_METHOD) {
- Slog.v(TAG_WM, "Final window list:");
- logWindowList(mWindows, " ");
- }
- return;
- }
- for (int i=0; i<N; i++) {
- WindowState win = dialogs.get(i);
- reAddToWindowList(win);
- if (DEBUG_INPUT_METHOD) {
- Slog.v(TAG_WM, "No IM target, final list:");
- logWindowList(mWindows, " ");
- }
- }
- }
-
- boolean moveInputMethodWindowsIfNeeded(boolean needAssignLayers) {
- final WindowState imWin = mService.mInputMethodWindow;
- final int DN = mService.mInputMethodDialogs.size();
- if (imWin == null && DN == 0) {
- return false;
- }
-
- // TODO(multidisplay): IMEs are only supported on the default display.
- int imPos = findDesiredInputMethodWindowIndex(true);
- if (imPos >= 0) {
- // In this case, the input method windows are to be placed
- // immediately above the window they are targeting.
-
- // First check to see if the input method windows are already
- // located here, and contiguous.
- final int N = mWindows.size();
- final WindowState firstImWin = imPos < N ? mWindows.get(imPos) : null;
-
- // Figure out the actual input method window that should be
- // at the bottom of their stack.
- WindowState baseImWin = imWin != null ? imWin : mService.mInputMethodDialogs.get(0);
- final WindowState cw = baseImWin.getBottomChild();
- if (cw != null && cw.mSubLayer < 0) {
- baseImWin = cw;
- }
-
- if (firstImWin == baseImWin) {
- // The windows haven't moved... but are they still contiguous?
- // First find the top IM window.
- int pos = imPos+1;
- while (pos < N) {
- if (!(mWindows.get(pos)).mIsImWindow) {
- break;
- }
- pos++;
- }
- pos++;
- // Now there should be no more input method windows above.
- while (pos < N) {
- if ((mWindows.get(pos)).mIsImWindow) {
- break;
- }
- pos++;
- }
- if (pos >= N) {
- return false;
- }
- }
-
- if (imWin != null) {
- if (DEBUG_INPUT_METHOD) {
- Slog.v(TAG_WM, "Moving IM from " + imPos);
- logWindowList(mWindows, " ");
- }
- imPos = removeWindowAndChildrenFromWindowList(imWin, imPos);
- if (DEBUG_INPUT_METHOD) {
- Slog.v(TAG_WM, "List after removing with new pos " + imPos + ":");
- logWindowList(mWindows, " ");
- }
- imWin.reAddWindow(imPos);
- if (DEBUG_INPUT_METHOD) {
- Slog.v(TAG_WM, "List after moving IM to " + imPos + ":");
- logWindowList(mWindows, " ");
- }
- if (DN > 0) moveInputMethodDialogs(imPos+1);
- } else {
- moveInputMethodDialogs(imPos);
- }
-
- } else {
- // In this case, the input method windows go in a fixed layer,
- // because they aren't currently associated with a focus window.
-
- if (imWin != null) {
- if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Moving IM from " + imPos);
- removeWindowAndChildrenFromWindowList(imWin, 0);
- reAddToWindowList(imWin);
- if (DEBUG_INPUT_METHOD) {
- Slog.v(TAG_WM, "List with no IM target:");
- logWindowList(mWindows, " ");
- }
- if (DN > 0) moveInputMethodDialogs(-1);
- } else {
- moveInputMethodDialogs(-1);
- }
-
- }
-
- if (needAssignLayers) {
- assignWindowLayers(false /* setLayoutNeeded */);
- }
-
- return true;
+ return mTmpWindow != null;
}
/**
- * Dig through the WindowStates and find the one that the Input Method will target.
- * @param willMove
- * @return The index+1 in mWindows of the discovered target.
+ * Determine and return the window that should be the IME target.
+ * @param updateImeTarget If true the system IME target will be updated to match what we found.
+ * @return The window that should be used as the IME target or null if there isn't any.
*/
- int findDesiredInputMethodWindowIndex(boolean willMove) {
+ WindowState computeImeTarget(boolean updateImeTarget) {
// TODO(multidisplay): Needs some serious rethought when the target and IME are not on the
// same display. Or even when the current IME/target are not on the same screen as the next
// IME/target. For now only look for input windows on the main screen.
- WindowState w = null;
- int i;
- for (i = mWindows.size() - 1; i >= 0; --i) {
- final WindowState win = mWindows.get(i);
+ WindowState target = getWindow(w -> {
+ if (DEBUG_INPUT_METHOD && updateImeTarget) Slog.i(TAG_WM, "Checking window @"
+ + w + " fl=0x" + Integer.toHexString(w.mAttrs.flags));
+ return w.canBeImeTarget();
+ });
- if (DEBUG_INPUT_METHOD && willMove) Slog.i(TAG_WM, "Checking window @" + i
- + " " + win + " fl=0x" + Integer.toHexString(win.mAttrs.flags));
- if (canBeImeTarget(win)) {
- w = win;
- //Slog.i(TAG_WM, "Putting input method here!");
- // Yet more tricksyness! If this window is a "starting" window, we do actually want
- // to be on top of it, but it is not -really- where input will go. So if the caller
- // is not actually looking to move the IME, look down below for a real window to
- // target...
- if (!willMove && w.mAttrs.type == TYPE_APPLICATION_STARTING && i > 0) {
- final WindowState wb = mWindows.get(i-1);
- if (wb.mAppToken == w.mAppToken && canBeImeTarget(wb)) {
- i--;
- w = wb;
- }
+ // Yet more tricksyness! If this window is a "starting" window, we do actually want
+ // to be on top of it, but it is not -really- where input will go. So look down below
+ // for a real window to target...
+ if (target != null && target.mAttrs.type == TYPE_APPLICATION_STARTING) {
+ final AppWindowToken token = target.mAppToken;
+ if (token != null) {
+ final WindowState betterTarget = token.getImeTargetBelowWindow(target);
+ if (betterTarget != null) {
+ target = betterTarget;
}
- break;
}
}
- // Now w is either mWindows[0] or an IME (or null if mWindows is empty).
-
- if (DEBUG_INPUT_METHOD && willMove) Slog.v(TAG_WM, "Proposed new IME target: " + w);
+ if (DEBUG_INPUT_METHOD && updateImeTarget) Slog.v(TAG_WM,
+ "Proposed new IME target: " + target);
// Now, a special case -- if the last target's window is in the process of exiting, and is
// above the new target, keep on the last target to avoid flicker. Consider for example a
@@ -1872,18 +1220,28 @@
// until it is completely gone so it doesn't drop behind the dialog or its full-screen
// scrim.
final WindowState curTarget = mService.mInputMethodTarget;
- if (curTarget != null
- && curTarget.isDisplayedLw()
- && curTarget.isClosing()
- && (w == null || curTarget.mWinAnimator.mAnimLayer > w.mWinAnimator.mAnimLayer)) {
+ if (curTarget != null && curTarget.isDisplayedLw() && curTarget.isClosing()
+ && (target == null
+ || curTarget.mWinAnimator.mAnimLayer > target.mWinAnimator.mAnimLayer)) {
if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Current target higher, not changing");
- return mWindows.indexOf(curTarget) + 1;
+ return curTarget;
}
- if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Desired input method target="
- + w + " willMove=" + willMove);
+ if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Desired input method target=" + target
+ + " updateImeTarget=" + updateImeTarget);
- if (willMove && w != null) {
+ if (target == null) {
+ if (updateImeTarget) {
+ if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget
+ + " to null." + (SHOW_STACK_CRAWLS ? " Callers="
+ + Debug.getCallers(4) : ""));
+ setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim, 0);
+ }
+
+ return null;
+ }
+
+ if (updateImeTarget) {
AppWindowToken token = curTarget == null ? null : curTarget.mAppToken;
if (token != null) {
@@ -1891,24 +1249,8 @@
// to look at all windows below the current target that are in this app, finding the
// highest visible one in layering.
WindowState highestTarget = null;
- int highestPos = 0;
if (token.mAppAnimator.animating || token.mAppAnimator.animation != null) {
- WindowList curWindows = token.getDisplayContent().mWindows;
- int pos = curWindows.indexOf(curTarget);
- while (pos >= 0) {
- WindowState win = curWindows.get(pos);
- if (win.mAppToken != token) {
- break;
- }
- if (!win.mRemoved) {
- if (highestTarget == null || win.mWinAnimator.mAnimLayer >
- highestTarget.mWinAnimator.mAnimLayer) {
- highestTarget = win;
- highestPos = pos;
- }
- }
- pos--;
- }
+ highestTarget = token.getHighestAnimLayerWindow(curTarget);
}
if (highestTarget != null) {
@@ -1916,121 +1258,76 @@
if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, appTransition + " " + highestTarget
+ " animating=" + highestTarget.mWinAnimator.isAnimationSet()
+ " layer=" + highestTarget.mWinAnimator.mAnimLayer
- + " new layer=" + w.mWinAnimator.mAnimLayer);
+ + " new layer=" + target.mWinAnimator.mAnimLayer);
if (appTransition.isTransitionSet()) {
// If we are currently setting up for an animation, hold everything until we
// can find out what will happen.
- mService.mInputMethodTargetWaitingAnim = true;
- mService.mInputMethodTarget = highestTarget;
- return highestPos + 1;
+ setInputMethodTarget(highestTarget, true, mInputMethodAnimLayerAdjustment);
+ return highestTarget;
} else if (highestTarget.mWinAnimator.isAnimationSet() &&
- highestTarget.mWinAnimator.mAnimLayer > w.mWinAnimator.mAnimLayer) {
+ highestTarget.mWinAnimator.mAnimLayer > target.mWinAnimator.mAnimLayer) {
// If the window we are currently targeting is involved with an animation,
// and it is on top of the next target we will be over, then hold off on
// moving until that is done.
- mService.mInputMethodTargetWaitingAnim = true;
- mService.mInputMethodTarget = highestTarget;
- return highestPos + 1;
+ setInputMethodTarget(highestTarget, true, mInputMethodAnimLayerAdjustment);
+ return highestTarget;
}
}
}
+
+ if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget + " to "
+ + target + (SHOW_STACK_CRAWLS ? " Callers=" + Debug.getCallers(4) : ""));
+ setInputMethodTarget(target, false, target.mAppToken != null
+ ? target.mAppToken.mAppAnimator.animLayerAdjustment : 0);
}
- //Slog.i(TAG_WM, "Placing input method @" + (i+1));
- if (w != null) {
- if (willMove) {
- if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget + " to "
- + w + (SHOW_STACK_CRAWLS ? " Callers=" + Debug.getCallers(4) : ""));
- mService.mInputMethodTarget = w;
- mService.mInputMethodTargetWaitingAnim = false;
- if (w.mAppToken != null) {
- setInputMethodAnimLayerAdjustment(
- w.mAppToken.mAppAnimator.animLayerAdjustment);
- } else {
- setInputMethodAnimLayerAdjustment(0);
- }
- }
-
- // If the docked divider is visible, we still need to go through this whole excercise to
- // find the appropriate input method target (used for animations and dialog
- // adjustments), but for purposes of Z ordering we simply wish to place it above the
- // docked divider. Unless it is already above the divider.
- final WindowState dockedDivider = mDividerControllerLocked.getWindow();
- if (dockedDivider != null && dockedDivider.isVisibleLw()) {
- int dividerIndex = mWindows.indexOf(dockedDivider);
- if (dividerIndex > 0 && dividerIndex > i) {
- return dividerIndex + 1;
- }
- }
- return i+1;
- }
- if (willMove) {
- if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget
- + " to null." + (SHOW_STACK_CRAWLS ? " Callers=" + Debug.getCallers(4) : ""));
- mService.mInputMethodTarget = null;
- setInputMethodAnimLayerAdjustment(0);
- }
- return -1;
+ return target;
}
- private static boolean canBeImeTarget(WindowState w) {
- final int fl = w.mAttrs.flags & (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM);
- final int type = w.mAttrs.type;
-
- if (fl != 0 && fl != (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM)
- && type != TYPE_APPLICATION_STARTING) {
- return false;
+ private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim, int layerAdj) {
+ if (target == mService.mInputMethodTarget
+ && mService.mInputMethodTargetWaitingAnim == targetWaitingAnim
+ && mInputMethodAnimLayerAdjustment == layerAdj) {
+ return;
}
- if (DEBUG_INPUT_METHOD) {
- Slog.i(TAG_WM, "isVisibleOrAdding " + w + ": " + w.isVisibleOrAdding());
- if (!w.isVisibleOrAdding()) {
- Slog.i(TAG_WM, " mSurfaceController=" + w.mWinAnimator.mSurfaceController
- + " relayoutCalled=" + w.mRelayoutCalled
- + " viewVis=" + w.mViewVisibility
- + " policyVis=" + w.mPolicyVisibility
- + " policyVisAfterAnim=" + w.mPolicyVisibilityAfterAnim
- + " parentHidden=" + w.isParentWindowHidden()
- + " exiting=" + w.mAnimatingExit + " destroying=" + w.mDestroying);
- if (w.mAppToken != null) {
- Slog.i(TAG_WM, " mAppToken.hiddenRequested=" + w.mAppToken.hiddenRequested);
- }
+ mService.mInputMethodTarget = target;
+ mService.mInputMethodTargetWaitingAnim = targetWaitingAnim;
+ setInputMethodAnimLayerAdjustment(layerAdj);
+ assignWindowLayers(false /* setLayoutNeeded */);
+ }
+
+ boolean getNeedsMenu(WindowState top, WindowManagerPolicy.WindowState bottom) {
+ if (top.mAttrs.needsMenuKey != NEEDS_MENU_UNSET) {
+ return top.mAttrs.needsMenuKey == NEEDS_MENU_SET_TRUE;
+ }
+
+ // Used to indicate we have reached the first window in the range we are interested in.
+ mTmpWindow = null;
+
+ // TODO: Figure-out a more efficient way to do this.
+ final WindowState candidate = getWindow(w -> {
+ if (w == top) {
+ // Reached the first window in the range we are interested in.
+ mTmpWindow = w;
}
- }
- return w.isVisibleOrAdding();
- }
+ if (mTmpWindow == null) {
+ return false;
+ }
- private void logWindowList(final WindowList windows, String prefix) {
- int N = windows.size();
- while (N > 0) {
- N--;
- Slog.v(TAG_WM, prefix + "#" + N + ": " + windows.get(N));
- }
- }
-
- boolean getNeedsMenu(WindowState win, WindowManagerPolicy.WindowState bottom) {
- int index = -1;
- while (true) {
- if (win.mAttrs.needsMenuKey != NEEDS_MENU_UNSET) {
- return win.mAttrs.needsMenuKey == NEEDS_MENU_SET_TRUE;
+ if (w.mAttrs.needsMenuKey != NEEDS_MENU_UNSET) {
+ return true;
}
// If we reached the bottom of the range of windows we are considering,
// assume no menu is needed.
- if (win == bottom) {
- return false;
+ if (w == bottom) {
+ return true;
}
- // The current window hasn't specified whether menu key is needed; look behind it.
- // First, we may need to determine the starting position.
- if (index < 0) {
- index = mWindows.indexOf(win);
- }
- index--;
- if (index < 0) {
- return false;
- }
- win = mWindows.get(index);
- }
+ return false;
+ });
+
+ return candidate != null && candidate.mAttrs.needsMenuKey == NEEDS_MENU_SET_TRUE;
}
void setLayoutNeeded() {
@@ -2047,85 +1344,6 @@
return mLayoutNeeded;
}
- private void addAppWindowExisting(WindowState win, WindowList tokenWindowList) {
-
- // If this application has existing windows, we simply place the new window on top of
- // them... but keep the starting window on top.
- if (win.mAttrs.type == TYPE_BASE_APPLICATION) {
- // Base windows go behind everything else.
- final WindowState lowestWindow = tokenWindowList.get(0);
- addWindowToListBefore(win, lowestWindow);
- } else {
- final AppWindowToken atoken = win.mAppToken;
- final int windowListPos = tokenWindowList.size();
- final WindowState lastWindow = tokenWindowList.get(windowListPos - 1);
- if (atoken != null && lastWindow == atoken.startingWindow) {
- addWindowToListBefore(win, lastWindow);
- } else {
- int newIdx = findIdxBasedOnAppTokens(win);
- // There is a window above this one associated with the same apptoken note that the
- // window could be a floating window that was created later or a window at the top
- // of the list of windows associated with this token.
- if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
- "not Base app: Adding window " + win + " at " + (newIdx + 1) + " of "
- + mWindows.size());
- mWindows.add(newIdx + 1, win);
- mService.mWindowsChanged = true;
- }
- }
- }
-
- /** Places the first input window after the second input window in the window list. */
- private void addWindowToListAfter(WindowState first, WindowState second) {
- final int i = mWindows.indexOf(second);
- if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
- "Adding window " + this + " at " + (i + 1) + " of " + mWindows.size()
- + " (after " + second + ")");
- mWindows.add(i + 1, first);
- mService.mWindowsChanged = true;
- }
-
- /** Places the first input window before the second input window in the window list. */
- private void addWindowToListBefore(WindowState first, WindowState second) {
- int i = mWindows.indexOf(second);
- if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
- "Adding window " + this + " at " + i + " of " + mWindows.size()
- + " (before " + second + ")");
- if (i < 0) {
- Slog.w(TAG_WM, "addWindowToListBefore: Unable to find " + second + " in " + mWindows);
- i = 0;
- }
- mWindows.add(i, first);
- mService.mWindowsChanged = true;
- }
-
- /**
- * This method finds out the index of a window that has the same app token as win. used for z
- * ordering the windows in mWindows
- */
- private int findIdxBasedOnAppTokens(WindowState win) {
- for(int j = mWindows.size() - 1; j >= 0; j--) {
- final WindowState wentry = mWindows.get(j);
- if(wentry.mAppToken == win.mAppToken) {
- return j;
- }
- }
- return -1;
- }
-
- private void dumpChildrenNames() {
- StringBuilder output = new StringBuilder();
- dumpChildrenNames(output, " ");
- Slog.v(TAG_WM, output.toString());
- }
-
- private void dumpWindows() {
- Slog.v(TAG_WM, " Display #" + mDisplayId);
- for (int winNdx = mWindows.size() - 1; winNdx >= 0; --winNdx) {
- Slog.v(TAG_WM, " #" + winNdx + ": " + mWindows.get(winNdx));
- }
- }
-
void dumpTokens(PrintWriter pw, boolean dumpAll) {
if (mTokenMap.isEmpty()) {
return;
@@ -2146,25 +1364,24 @@
}
void dumpWindowAnimators(PrintWriter pw, String subPrefix) {
- final int count = mWindows.size();
- for (int j = 0; j < count; j++) {
- final WindowStateAnimator wAnim = mWindows.get(j).mWinAnimator;
- pw.println(subPrefix + "Window #" + j + ": " + wAnim);
- }
+ final int[] index = new int[1];
+ forAllWindows(w -> {
+ final WindowStateAnimator wAnim = w.mWinAnimator;
+ pw.println(subPrefix + "Window #" + index[0] + ": " + wAnim);
+ index[0] = index[0] + 1;
+ }, false /* traverseTopToBottom */);
}
void enableSurfaceTrace(FileDescriptor fd) {
- for (int i = mWindows.size() - 1; i >= 0; i--) {
- final WindowState win = mWindows.get(i);
- win.mWinAnimator.enableSurfaceTrace(fd);
- }
+ forAllWindows(w -> {
+ w.mWinAnimator.enableSurfaceTrace(fd);
+ }, true /* traverseTopToBottom */);
}
void disableSurfaceTrace() {
- for (int i = mWindows.size() - 1; i >= 0; i--) {
- final WindowState win = mWindows.get(i);
- win.mWinAnimator.disableSurfaceTrace();
- }
+ forAllWindows(w -> {
+ w.mWinAnimator.disableSurfaceTrace();
+ }, true /* traverseTopToBottom */);
}
/**
@@ -2172,63 +1389,68 @@
*/
void startKeyguardExitOnNonAppWindows(boolean onWallpaper, boolean goingToShade) {
final WindowManagerPolicy policy = mService.mPolicy;
- for (int i = mWindows.size() - 1; i >= 0; i--) {
- final WindowState window = mWindows.get(i);
- if (window.mAppToken == null && policy.canBeHiddenByKeyguardLw(window)) {
- window.mWinAnimator.setAnimation(
+ forAllWindows(w -> {
+ if (w.mAppToken == null && policy.canBeHiddenByKeyguardLw(w)) {
+ w.mWinAnimator.setAnimation(
policy.createHiddenByKeyguardExit(onWallpaper, goingToShade));
}
- }
+ }, true /* traverseTopToBottom */);
}
boolean checkWaitingForWindows() {
- boolean haveBootMsg = false;
- boolean haveApp = false;
- // if the wallpaper service is disabled on the device, we're never going to have
- // wallpaper, don't bother waiting for it
- boolean haveWallpaper = false;
- boolean wallpaperEnabled = mService.mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_enableWallpaperService)
- && !mService.mOnlyCore;
- boolean haveKeyguard = true;
- final int count = mWindows.size();
- for (int i = 0; i < count; i++) {
- final WindowState w = mWindows.get(i);
+ mHaveBootMsg = false;
+ mHaveApp = false;
+ mHaveWallpaper = false;
+ mHaveKeyguard = true;
+
+ final WindowState visibleWindow = getWindow(w -> {
if (w.isVisibleLw() && !w.mObscured && !w.isDrawnLw()) {
return true;
}
if (w.isDrawnLw()) {
if (w.mAttrs.type == TYPE_BOOT_PROGRESS) {
- haveBootMsg = true;
+ mHaveBootMsg = true;
} else if (w.mAttrs.type == TYPE_APPLICATION
|| w.mAttrs.type == TYPE_DRAWN_APPLICATION) {
- haveApp = true;
+ mHaveApp = true;
} else if (w.mAttrs.type == TYPE_WALLPAPER) {
- haveWallpaper = true;
+ mHaveWallpaper = true;
} else if (w.mAttrs.type == TYPE_STATUS_BAR) {
- haveKeyguard = mService.mPolicy.isKeyguardDrawnLw();
+ mHaveKeyguard = mService.mPolicy.isKeyguardDrawnLw();
}
}
+ return false;
+ });
+
+ if (visibleWindow != null) {
+ // We have a visible window.
+ return true;
}
+ // if the wallpaper service is disabled on the device, we're never going to have
+ // wallpaper, don't bother waiting for it
+ boolean wallpaperEnabled = mService.mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableWallpaperService)
+ && !mService.mOnlyCore;
+
if (DEBUG_SCREEN_ON || DEBUG_BOOT) Slog.i(TAG_WM,
"******** booted=" + mService.mSystemBooted
+ " msg=" + mService.mShowingBootMessages
- + " haveBoot=" + haveBootMsg + " haveApp=" + haveApp
- + " haveWall=" + haveWallpaper + " wallEnabled=" + wallpaperEnabled
- + " haveKeyguard=" + haveKeyguard);
+ + " haveBoot=" + mHaveBootMsg + " haveApp=" + mHaveApp
+ + " haveWall=" + mHaveWallpaper + " wallEnabled=" + wallpaperEnabled
+ + " haveKeyguard=" + mHaveKeyguard);
// If we are turning on the screen to show the boot message, don't do it until the boot
// message is actually displayed.
- if (!mService.mSystemBooted && !haveBootMsg) {
+ if (!mService.mSystemBooted && !mHaveBootMsg) {
return true;
}
// If we are turning on the screen after the boot is completed normally, don't do so until
// we have the application and wallpaper.
- if (mService.mSystemBooted && ((!haveApp && !haveKeyguard) ||
- (wallpaperEnabled && !haveWallpaper))) {
+ if (mService.mSystemBooted
+ && ((!mHaveApp && !mHaveKeyguard) || (wallpaperEnabled && !mHaveWallpaper))) {
return true;
}
@@ -2236,10 +1458,8 @@
}
void updateWindowsForAnimator(WindowAnimator animator) {
- final WallpaperController wallpaperController = mWallpaperController;
- for (int i = mWindows.size() - 1; i >= 0; i--) {
- WindowState win = mWindows.get(i);
- WindowStateAnimator winAnimator = win.mWinAnimator;
+ forAllWindows(w -> {
+ WindowStateAnimator winAnimator = w.mWinAnimator;
if (winAnimator.hasSurface()) {
final boolean wasAnimating = winAnimator.mWasAnimating;
final boolean nowAnimating = winAnimator.stepAnimationLocked(animator.mCurrentTime);
@@ -2247,10 +1467,10 @@
animator.orAnimating(nowAnimating);
if (DEBUG_WALLPAPER) Slog.v(TAG,
- win + ": wasAnimating=" + wasAnimating + ", nowAnimating=" + nowAnimating);
+ w + ": wasAnimating=" + wasAnimating + ", nowAnimating=" + nowAnimating);
if (wasAnimating && !winAnimator.mAnimating
- && wallpaperController.isWallpaperTarget(win)) {
+ && mWallpaperController.isWallpaperTarget(w)) {
animator.mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE;
pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
if (DEBUG_LAYOUT_REPEATS) {
@@ -2260,10 +1480,10 @@
}
}
- final AppWindowToken atoken = win.mAppToken;
+ final AppWindowToken atoken = w.mAppToken;
if (winAnimator.mDrawState == READY_TO_SHOW) {
if (atoken == null || atoken.allDrawn) {
- if (win.performShowLocked()) {
+ if (w.performShowLocked()) {
pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM;
if (DEBUG_LAYOUT_REPEATS) {
mService.mWindowPlacerLocked.debugLayoutRepeats(
@@ -2282,23 +1502,22 @@
appAnimator.thumbnailLayer = winAnimator.mAnimLayer;
}
}
- } // end forall windows
+ }, true /* traverseTopToBottom */);
}
void updateWallpaperForAnimator(WindowAnimator animator) {
resetAnimationBackgroundAnimator();
- final WindowList windows = mWindows;
- WindowState detachedWallpaper = null;
+ // Used to indicate a detached wallpaper.
+ mTmpWindow = null;
- for (int i = windows.size() - 1; i >= 0; i--) {
- final WindowState win = windows.get(i);
- final WindowStateAnimator winAnimator = win.mWinAnimator;
+ forAllWindows(w -> {
+ final WindowStateAnimator winAnimator = w.mWinAnimator;
if (winAnimator.mSurfaceController == null || !winAnimator.hasSurface()) {
- continue;
+ return;
}
- final int flags = win.mAttrs.flags;
+ final int flags = w.mAttrs.flags;
// If this window is animating, make a note that we have an animating window and take
// care of a request to run a detached wallpaper animation.
@@ -2306,11 +1525,11 @@
if (winAnimator.mAnimation != null) {
if ((flags & FLAG_SHOW_WALLPAPER) != 0
&& winAnimator.mAnimation.getDetachWallpaper()) {
- detachedWallpaper = win;
+ mTmpWindow = w;
}
final int color = winAnimator.mAnimation.getBackgroundColor();
if (color != 0) {
- final TaskStack stack = win.getStack();
+ final TaskStack stack = w.getStack();
if (stack != null) {
stack.setAnimationBackground(winAnimator, color);
}
@@ -2326,62 +1545,43 @@
&& appAnimator.animating) {
if ((flags & FLAG_SHOW_WALLPAPER) != 0
&& appAnimator.animation.getDetachWallpaper()) {
- detachedWallpaper = win;
+ mTmpWindow = w;
}
final int color = appAnimator.animation.getBackgroundColor();
if (color != 0) {
- final TaskStack stack = win.getStack();
+ final TaskStack stack = w.getStack();
if (stack != null) {
stack.setAnimationBackground(winAnimator, color);
}
}
}
- } // end forall windows
+ }, true /* traverseTopToBottom */);
- if (animator.mWindowDetachedWallpaper != detachedWallpaper) {
+ if (animator.mWindowDetachedWallpaper != mTmpWindow) {
if (DEBUG_WALLPAPER) Slog.v(TAG, "Detached wallpaper changed from "
- + animator.mWindowDetachedWallpaper + " to " + detachedWallpaper);
- animator.mWindowDetachedWallpaper = detachedWallpaper;
+ + animator.mWindowDetachedWallpaper + " to " + mTmpWindow);
+ animator.mWindowDetachedWallpaper = mTmpWindow;
animator.mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE;
}
}
void prepareWindowSurfaces() {
- final int count = mWindows.size();
- for (int j = 0; j < count; j++) {
- mWindows.get(j).mWinAnimator.prepareSurfaceLocked(true);
- }
+ forAllWindows(w -> {
+ w.mWinAnimator.prepareSurfaceLocked(true);
+ }, false /* traverseTopToBottom */);
}
boolean inputMethodClientHasFocus(IInputMethodClient client) {
- // The focus for the client is the window immediately below where we would place the input
- // method window.
- int idx = findDesiredInputMethodWindowIndex(false);
- if (idx <= 0) {
- return false;
- }
-
- WindowState imFocus = mWindows.get(idx - 1);
- if (DEBUG_INPUT_METHOD) {
- Slog.i(TAG_WM, "Desired input method target: " + imFocus);
- Slog.i(TAG_WM, "Current focus: " + mService.mCurrentFocus);
- Slog.i(TAG_WM, "Last focus: " + mService.mLastFocus);
- }
-
+ final WindowState imFocus = computeImeTarget(false /* updateImeTarget */);
if (imFocus == null) {
return false;
}
- // This may be a starting window, in which case we still want to count it as okay.
- if (imFocus.mAttrs.type == TYPE_APPLICATION_STARTING && imFocus.mAppToken != null) {
- // The client has definitely started, so it really should have a window in this app
- // token. Let's look for it.
- final WindowState w = imFocus.mAppToken.getFirstNonStartingWindow();
- if (w != null) {
- if (DEBUG_INPUT_METHOD) Slog.i(TAG_WM, "Switching to real app window: " + w);
- imFocus = w;
- }
+ if (DEBUG_INPUT_METHOD) {
+ Slog.i(TAG_WM, "Desired input method target: " + imFocus);
+ Slog.i(TAG_WM, "Current focus: " + mService.mCurrentFocus);
+ Slog.i(TAG_WM, "Last focus: " + mService.mLastFocus);
}
final IInputMethodClient imeClient = imFocus.mSession.mClient;
@@ -2398,75 +1598,63 @@
}
boolean hasSecureWindowOnScreen() {
- for (int i = mWindows.size() - 1; i >= 0; --i) {
- final WindowState ws = mWindows.get(i);
- if (ws.isOnScreen() && (ws.mAttrs.flags & FLAG_SECURE) != 0) {
- return true;
- }
- }
- return false;
+ final WindowState win = getWindow(
+ w -> w.isOnScreen() && (w.mAttrs.flags & FLAG_SECURE) != 0);
+ return win != null;
}
void updateSystemUiVisibility(int visibility, int globalDiff) {
- for (int i = mWindows.size() - 1; i >= 0; --i) {
- final WindowState ws = mWindows.get(i);
+ forAllWindows(w -> {
try {
- int curValue = ws.mSystemUiVisibility;
- int diff = (curValue ^ visibility) & globalDiff;
- int newValue = (curValue & ~diff) | (visibility & diff);
+ final int curValue = w.mSystemUiVisibility;
+ final int diff = (curValue ^ visibility) & globalDiff;
+ final int newValue = (curValue & ~diff) | (visibility & diff);
if (newValue != curValue) {
- ws.mSeq++;
- ws.mSystemUiVisibility = newValue;
+ w.mSeq++;
+ w.mSystemUiVisibility = newValue;
}
- if (newValue != curValue || ws.mAttrs.hasSystemUiListeners) {
- ws.mClient.dispatchSystemUiVisibilityChanged(ws.mSeq,
+ if (newValue != curValue || w.mAttrs.hasSystemUiListeners) {
+ w.mClient.dispatchSystemUiVisibilityChanged(w.mSeq,
visibility, newValue, diff);
}
} catch (RemoteException e) {
// so sorry
}
- }
+ }, true /* traverseTopToBottom */);
}
void onWindowFreezeTimeout() {
Slog.w(TAG_WM, "Window freeze timeout expired.");
mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
- for (int i = mWindows.size() - 1; i >= 0; --i) {
- final WindowState w = mWindows.get(i);
+
+ forAllWindows(w -> {
if (!w.mOrientationChanging) {
- continue;
+ return;
}
w.mOrientationChanging = false;
w.mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
- mService.mDisplayFreezeTime);
Slog.w(TAG_WM, "Force clearing orientation change: " + w);
- }
+ }, true /* traverseTopToBottom */);
mService.mWindowPlacerLocked.performSurfacePlacement();
}
void waitForAllWindowsDrawn() {
final WindowManagerPolicy policy = mService.mPolicy;
- for (int winNdx = mWindows.size() - 1; winNdx >= 0; --winNdx) {
- final WindowState win = mWindows.get(winNdx);
- final boolean keyguard = policy.isKeyguardHostWindow(win.mAttrs);
- if (win.isVisibleLw() && (win.mAppToken != null || keyguard)) {
- win.mWinAnimator.mDrawState = DRAW_PENDING;
+ forAllWindows(w -> {
+ final boolean keyguard = policy.isKeyguardHostWindow(w.mAttrs);
+ if (w.isVisibleLw() && (w.mAppToken != null || keyguard)) {
+ w.mWinAnimator.mDrawState = DRAW_PENDING;
// Force add to mResizingWindows.
- win.mLastContentInsets.set(-1, -1, -1, -1);
- mService.mWaitingForDrawn.add(win);
+ w.mLastContentInsets.set(-1, -1, -1, -1);
+ mService.mWaitingForDrawn.add(w);
}
- }
+ }, true /* traverseTopToBottom */);
}
// TODO: Super crazy long method that should be broken down...
boolean applySurfaceChangesTransaction(boolean recoveringMemory) {
- boolean focusDisplayed = false;
- boolean displayHasContent = false;
- float preferredRefreshRate = 0;
- int preferredModeId = 0;
-
-
final int dw = mDisplayInfo.logicalWidth;
final int dh = mDisplayInfo.logicalHeight;
final WindowSurfacePlacer surfacePlacer = mService.mWindowPlacerLocked;
@@ -2490,7 +1678,7 @@
// Remove check for default display when there will be support for multiple wallpaper
// targets (on different displays).
if (isDefaultDisplay && (pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
- adjustWallpaperWindows();
+ mWallpaperController.adjustWallpaperWindows(this);
}
if (isDefaultDisplay && (pendingLayoutChanges & FINISH_LAYOUT_REDO_CONFIG) != 0) {
@@ -2517,55 +1705,59 @@
if (isDefaultDisplay) {
mService.mPolicy.beginPostLayoutPolicyLw(dw, dh);
- for (int i = mWindows.size() - 1; i >= 0; i--) {
- final WindowState w = mWindows.get(i);
+ forAllWindows(w -> {
mService.mPolicy.applyPostLayoutPolicyLw(w, w.mAttrs, w.getParentWindow(),
mService.mInputMethodTarget);
- }
+ }, true /* traverseTopToBottom */);
pendingLayoutChanges |= mService.mPolicy.finishPostLayoutPolicyLw();
if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats(
"after finishPostLayoutPolicyLw", pendingLayoutChanges);
}
} while (pendingLayoutChanges != 0);
- RootWindowContainer root = mService.mRoot;
- boolean obscured = false;
- boolean syswin = false;
+ final RootWindowContainer root = mService.mRoot;
+ mTmpApplySurfaceChangesTransactionState.reset();
resetDimming();
// Only used if default window
final boolean someoneLosingFocus = !mService.mLosingFocus.isEmpty();
- for (int i = mWindows.size() - 1; i >= 0; i--) {
- final WindowState w = mWindows.get(i);
- final Task task = w.getTask();
- final boolean obscuredChanged = w.mObscured != obscured;
+ forAllWindows(w -> {
+ final boolean obscuredChanged = w.mObscured !=
+ mTmpApplySurfaceChangesTransactionState.obscured;
// Update effect.
- w.mObscured = obscured;
- if (!obscured) {
+ w.mObscured = mTmpApplySurfaceChangesTransactionState.obscured;
+ if (!mTmpApplySurfaceChangesTransactionState.obscured) {
final boolean isDisplayed = w.isDisplayedLw();
if (isDisplayed && w.isObscuringFullscreen(mDisplayInfo)) {
// This window completely covers everything behind it, so we want to leave all
// of them as undimmed (for performance reasons).
root.mObscuringWindow = w;
- obscured = true;
+ mTmpApplySurfaceChangesTransactionState.obscured = true;
}
- displayHasContent |= root.handleNotObscuredLocked(w, obscured, syswin);
+ mTmpApplySurfaceChangesTransactionState.displayHasContent |=
+ root.handleNotObscuredLocked(w,
+ mTmpApplySurfaceChangesTransactionState.obscured,
+ mTmpApplySurfaceChangesTransactionState.syswin);
if (w.mHasSurface && isDisplayed) {
final int type = w.mAttrs.type;
if (type == TYPE_SYSTEM_DIALOG || type == TYPE_SYSTEM_ERROR
|| (w.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
- syswin = true;
+ mTmpApplySurfaceChangesTransactionState.syswin = true;
}
- if (preferredRefreshRate == 0 && w.mAttrs.preferredRefreshRate != 0) {
- preferredRefreshRate = w.mAttrs.preferredRefreshRate;
+ if (mTmpApplySurfaceChangesTransactionState.preferredRefreshRate == 0
+ && w.mAttrs.preferredRefreshRate != 0) {
+ mTmpApplySurfaceChangesTransactionState.preferredRefreshRate
+ = w.mAttrs.preferredRefreshRate;
}
- if (preferredModeId == 0 && w.mAttrs.preferredDisplayModeId != 0) {
- preferredModeId = w.mAttrs.preferredDisplayModeId;
+ if (mTmpApplySurfaceChangesTransactionState.preferredModeId == 0
+ && w.mAttrs.preferredDisplayModeId != 0) {
+ mTmpApplySurfaceChangesTransactionState.preferredModeId
+ = w.mAttrs.preferredDisplayModeId;
}
}
}
@@ -2638,16 +1830,16 @@
if (isDefaultDisplay && someoneLosingFocus && w == mService.mCurrentFocus
&& w.isDisplayedLw()) {
- focusDisplayed = true;
+ mTmpApplySurfaceChangesTransactionState.focusDisplayed = true;
}
w.updateResizingWindowIfNeeded();
- }
+ }, true /* traverseTopToBottom */);
mService.mDisplayManagerInternal.setDisplayProperties(mDisplayId,
- displayHasContent,
- preferredRefreshRate,
- preferredModeId,
+ mTmpApplySurfaceChangesTransactionState.displayHasContent,
+ mTmpApplySurfaceChangesTransactionState.preferredRefreshRate,
+ mTmpApplySurfaceChangesTransactionState.preferredModeId,
true /* inTraversal, must call performTraversalInTrans... below */);
stopDimmingIfNeeded();
@@ -2659,7 +1851,7 @@
atoken.updateAllDrawn(this);
}
- return focusDisplayed;
+ return mTmpApplySurfaceChangesTransactionState.focusDisplayed;
}
void performLayout(boolean initial, boolean updateInputWindows) {
@@ -2692,110 +1884,110 @@
if (seq < 0) seq = 0;
mService.mLayoutSeq = seq;
- boolean behindDream = false;
+ // Used to indicate that we have processed the dream window and all additional windows are
+ // behind it.
+ mTmpWindow = null;
// First perform layout of any root windows (not attached to another window).
- int topAttached = -1;
- for (i = mWindows.size() - 1; i >= 0; i--) {
- final WindowState win = mWindows.get(i);
-
+ forAllWindows(w -> {
// Don't do layout of a window if it is not visible, or soon won't be visible, to avoid
// wasting time and funky changes while a window is animating away.
- final boolean gone = (behindDream && mService.mPolicy.canBeHiddenByKeyguardLw(win))
- || win.isGoneForLayoutLw();
+ final boolean gone = (mTmpWindow != null && mService.mPolicy.canBeHiddenByKeyguardLw(w))
+ || w.isGoneForLayoutLw();
- if (DEBUG_LAYOUT && !win.mLayoutAttached) {
- Slog.v(TAG, "1ST PASS " + win + ": gone=" + gone + " mHaveFrame=" + win.mHaveFrame
- + " mLayoutAttached=" + win.mLayoutAttached
- + " screen changed=" + win.isConfigChanged());
- final AppWindowToken atoken = win.mAppToken;
- if (gone) Slog.v(TAG, " GONE: mViewVisibility=" + win.mViewVisibility
- + " mRelayoutCalled=" + win.mRelayoutCalled + " hidden=" + win.mToken.hidden
+ if (DEBUG_LAYOUT && !w.mLayoutAttached) {
+ Slog.v(TAG, "1ST PASS " + w + ": gone=" + gone + " mHaveFrame=" + w.mHaveFrame
+ + " mLayoutAttached=" + w.mLayoutAttached
+ + " screen changed=" + w.isConfigChanged());
+ final AppWindowToken atoken = w.mAppToken;
+ if (gone) Slog.v(TAG, " GONE: mViewVisibility=" + w.mViewVisibility
+ + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.hidden
+ " hiddenRequested=" + (atoken != null && atoken.hiddenRequested)
- + " parentHidden=" + win.isParentWindowHidden());
- else Slog.v(TAG, " VIS: mViewVisibility=" + win.mViewVisibility
- + " mRelayoutCalled=" + win.mRelayoutCalled + " hidden=" + win.mToken.hidden
+ + " parentHidden=" + w.isParentWindowHidden());
+ else Slog.v(TAG, " VIS: mViewVisibility=" + w.mViewVisibility
+ + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.hidden
+ " hiddenRequested=" + (atoken != null && atoken.hiddenRequested)
- + " parentHidden=" + win.isParentWindowHidden());
+ + " parentHidden=" + w.isParentWindowHidden());
}
// If this view is GONE, then skip it -- keep the current frame, and let the caller know
// so they can ignore it if they want. (We do the normal layout for INVISIBLE windows,
// since that means "perform layout as normal, just don't display").
- if (!gone || !win.mHaveFrame || win.mLayoutNeeded
- || ((win.isConfigChanged() || win.setReportResizeHints())
- && !win.isGoneForLayoutLw() &&
- ((win.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 ||
- (win.mHasSurface && win.mAppToken != null &&
- win.mAppToken.layoutConfigChanges)))) {
- if (!win.mLayoutAttached) {
+ if (!gone || !w.mHaveFrame || w.mLayoutNeeded
+ || ((w.isConfigChanged() || w.setReportResizeHints())
+ && !w.isGoneForLayoutLw() &&
+ ((w.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 ||
+ (w.mHasSurface && w.mAppToken != null &&
+ w.mAppToken.layoutConfigChanges)))) {
+ if (!w.mLayoutAttached) {
if (initial) {
//Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
- win.mContentChanged = false;
+ w.mContentChanged = false;
}
- if (win.mAttrs.type == TYPE_DREAM) {
+ if (w.mAttrs.type == TYPE_DREAM) {
// Don't layout windows behind a dream, so that if it does stuff like hide
// the status bar we won't get a bad transition when it goes away.
- behindDream = true;
+ mTmpWindow = w;
}
- win.mLayoutNeeded = false;
- win.prelayout();
- mService.mPolicy.layoutWindowLw(win, null);
- win.mLayoutSeq = seq;
+ w.mLayoutNeeded = false;
+ w.prelayout();
+ mService.mPolicy.layoutWindowLw(w, null);
+ w.mLayoutSeq = mService.mLayoutSeq;
// Window frames may have changed. Update dim layer with the new bounds.
- final Task task = win.getTask();
+ final Task task = w.getTask();
if (task != null) {
mDimLayerController.updateDimLayer(task);
}
- if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + win.mFrame
- + " mContainingFrame=" + win.mContainingFrame
- + " mDisplayFrame=" + win.mDisplayFrame);
- } else {
- if (topAttached < 0) topAttached = i;
+ if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.mFrame
+ + " mContainingFrame=" + w.mContainingFrame
+ + " mDisplayFrame=" + w.mDisplayFrame);
}
}
- }
+ }, true /* traverseTopToBottom */);
- boolean attachedBehindDream = false;
+ // Used to indicate that we have processed the dream window and all additional attached
+ // windows are behind it.
+ final WindowState dreamWin = mTmpWindow;
+ mTmpWindow = null;
// Now perform layout of attached windows, which usually depend on the position of the
// window they are attached to. XXX does not deal with windows that are attached to windows
// that are themselves attached.
- for (i = topAttached; i >= 0; i--) {
- final WindowState win = mWindows.get(i);
-
- if (win.mLayoutAttached) {
- if (DEBUG_LAYOUT) Slog.v(TAG, "2ND PASS " + win + " mHaveFrame=" + win.mHaveFrame
- + " mViewVisibility=" + win.mViewVisibility
- + " mRelayoutCalled=" + win.mRelayoutCalled);
- // If this view is GONE, then skip it -- keep the current frame, and let the caller
- // know so they can ignore it if they want. (We do the normal layout for INVISIBLE
- // windows, since that means "perform layout as normal, just don't display").
- if (attachedBehindDream && mService.mPolicy.canBeHiddenByKeyguardLw(win)) {
- continue;
+ forAllWindows(w -> {
+ if (w.mLayoutAttached) {
+ if (DEBUG_LAYOUT) Slog.v(TAG, "2ND PASS " + w + " mHaveFrame=" + w.mHaveFrame
+ + " mViewVisibility=" + w.mViewVisibility
+ + " mRelayoutCalled=" + w.mRelayoutCalled);
+ // If this view is GONE, then skip it -- keep the current frame, and let the
+ // caller know so they can ignore it if they want. (We do the normal layout for
+ // INVISIBLE windows, since that means "perform layout as normal, just don't
+ // display").
+ if (mTmpWindow != null && mService.mPolicy.canBeHiddenByKeyguardLw(w)) {
+ return;
}
- if ((win.mViewVisibility != GONE && win.mRelayoutCalled) || !win.mHaveFrame
- || win.mLayoutNeeded) {
+ if ((w.mViewVisibility != GONE && w.mRelayoutCalled) || !w.mHaveFrame
+ || w.mLayoutNeeded) {
if (initial) {
//Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
- win.mContentChanged = false;
+ w.mContentChanged = false;
}
- win.mLayoutNeeded = false;
- win.prelayout();
- mService.mPolicy.layoutWindowLw(win, win.getParentWindow());
- win.mLayoutSeq = seq;
- if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + win.mFrame
- + " mContainingFrame=" + win.mContainingFrame
- + " mDisplayFrame=" + win.mDisplayFrame);
+ w.mLayoutNeeded = false;
+ w.prelayout();
+ mService.mPolicy.layoutWindowLw(w, w.getParentWindow());
+ w.mLayoutSeq = mService.mLayoutSeq;
+ if (DEBUG_LAYOUT)
+ Slog.v(TAG, " LAYOUT: mFrame=" + w.mFrame
+ + " mContainingFrame=" + w.mContainingFrame
+ + " mDisplayFrame=" + w.mDisplayFrame);
}
- } else if (win.mAttrs.type == TYPE_DREAM) {
+ } else if (w.mAttrs.type == TYPE_DREAM) {
// Don't layout windows behind a dream, so that if it does stuff like hide the
// status bar we won't get a bad transition when it goes away.
- attachedBehindDream = behindDream;
+ mTmpWindow = dreamWin;
}
- }
+ }, true /* traverseTopToBottom */);
// Window frames may have changed. Tell the input dispatcher about it.
mService.mInputMonitor.layoutInputConsumers(dw, dh);
@@ -2819,7 +2011,7 @@
* @param config of the output bitmap
* @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot
*/
- Bitmap screenshotApplications(IBinder appToken, int displayId, int width, int height,
+ Bitmap screenshotApplications(IBinder appToken, int width, int height,
boolean includeFullDisplay, float frameScale, Bitmap.Config config,
boolean wallpaperOnly) {
int dw = mDisplayInfo.logicalWidth;
@@ -2832,22 +2024,10 @@
Bitmap bm = null;
- int maxLayer = 0;
+ mScreenshotApplicationState.reset(appToken == null && !wallpaperOnly);
final Rect frame = new Rect();
final Rect stackBounds = new Rect();
- boolean screenshotReady;
- int minLayer;
- if (appToken == null && !wallpaperOnly) {
- screenshotReady = true;
- minLayer = 0;
- } else {
- screenshotReady = false;
- minLayer = Integer.MAX_VALUE;
- }
-
- WindowState appWin = null;
-
boolean includeImeInScreenshot;
synchronized(mService.mWindowMap) {
final AppWindowToken imeTargetAppToken = mService.mInputMethodTarget != null
@@ -2868,70 +2048,69 @@
synchronized(mService.mWindowMap) {
// Figure out the part of the screen that is actually the app.
- appWin = null;
- for (int i = mWindows.size() - 1; i >= 0; i--) {
- final WindowState ws = mWindows.get(i);
- if (!ws.mHasSurface) {
- continue;
+ mScreenshotApplicationState.appWin = null;
+ forAllWindows(w -> {
+ if (!w.mHasSurface) {
+ return false;
}
- if (ws.mLayer >= aboveAppLayer) {
- continue;
+ if (w.mLayer >= aboveAppLayer) {
+ return false;
}
- if (wallpaperOnly && !ws.mIsWallpaper) {
- continue;
+ if (wallpaperOnly && !w.mIsWallpaper) {
+ return false;
}
- if (ws.mIsImWindow) {
+ if (w.mIsImWindow) {
if (!includeImeInScreenshot) {
- continue;
+ return false;
}
- } else if (ws.mIsWallpaper) {
+ } else if (w.mIsWallpaper) {
// If this is the wallpaper layer and we're only looking for the wallpaper layer
// then the target window state is this one.
if (wallpaperOnly) {
- appWin = ws;
+ mScreenshotApplicationState.appWin = w;
}
- if (appWin == null) {
+ if (mScreenshotApplicationState.appWin == null) {
// We have not ran across the target window yet, so it is probably behind
// the wallpaper. This can happen when the keyguard is up and all windows
// are moved behind the wallpaper. We don't want to include the wallpaper
// layer in the screenshot as it will cover-up the layer of the target
// window.
- continue;
+ return false;
}
// Fall through. The target window is in front of the wallpaper. For this
// case we want to include the wallpaper layer in the screenshot because
// the target window might have some transparent areas.
} else if (appToken != null) {
- if (ws.mAppToken == null || ws.mAppToken.token != appToken) {
+ if (w.mAppToken == null || w.mAppToken.token != appToken) {
// This app window is of no interest if it is not associated with the
// screenshot app.
- continue;
+ return false;
}
- appWin = ws;
+ mScreenshotApplicationState.appWin = w;
}
// Include this window.
- final WindowStateAnimator winAnim = ws.mWinAnimator;
+ final WindowStateAnimator winAnim = w.mWinAnimator;
int layer = winAnim.mSurfaceController.getLayer();
- if (maxLayer < layer) {
- maxLayer = layer;
+ if (mScreenshotApplicationState.maxLayer < layer) {
+ mScreenshotApplicationState.maxLayer = layer;
}
- if (minLayer > layer) {
- minLayer = layer;
+ if (mScreenshotApplicationState.minLayer > layer) {
+ mScreenshotApplicationState.minLayer = layer;
}
// Don't include wallpaper in bounds calculation
- if (!includeFullDisplay && !ws.mIsWallpaper) {
- final Rect wf = ws.mFrame;
- final Rect cr = ws.mContentInsets;
+ if (!includeFullDisplay && !w.mIsWallpaper) {
+ final Rect wf = w.mFrame;
+ final Rect cr = w.mContentInsets;
int left = wf.left + cr.left;
int top = wf.top + cr.top;
int right = wf.right - cr.right;
int bottom = wf.bottom - cr.bottom;
frame.union(left, top, right, bottom);
- ws.getVisibleBounds(stackBounds);
+ w.getVisibleBounds(stackBounds);
if (!Rect.intersects(frame, stackBounds)) {
// Set frame empty if there's no intersection.
frame.setEmpty();
@@ -2939,16 +2118,22 @@
}
final boolean foundTargetWs =
- (ws.mAppToken != null && ws.mAppToken.token == appToken)
- || (appWin != null && wallpaperOnly);
- if (foundTargetWs && ws.isDisplayedLw() && winAnim.getShown()) {
- screenshotReady = true;
+ (w.mAppToken != null && w.mAppToken.token == appToken)
+ || (mScreenshotApplicationState.appWin != null && wallpaperOnly);
+ if (foundTargetWs && w.isDisplayedLw() && winAnim.getShown()) {
+ mScreenshotApplicationState.screenshotReady = true;
}
- if (ws.isObscuringFullscreen(mDisplayInfo)){
- break;
+ if (w.isObscuringFullscreen(mDisplayInfo)){
+ return true;
}
- }
+ return false;
+ }, true /* traverseTopToBottom */);
+
+ final WindowState appWin = mScreenshotApplicationState.appWin;
+ final boolean screenshotReady = mScreenshotApplicationState.screenshotReady;
+ final int maxLayer = mScreenshotApplicationState.maxLayer;
+ final int minLayer = mScreenshotApplicationState.minLayer;
if (appToken != null && appWin == null) {
// Can't find a window to snapshot.
@@ -3020,14 +2205,13 @@
if (DEBUG_SCREENSHOT) {
Slog.i(TAG_WM, "Screenshot: " + dw + "x" + dh + " from " + minLayer + " to "
+ maxLayer + " appToken=" + appToken);
- for (int i = 0; i < mWindows.size(); i++) {
- final WindowState win = mWindows.get(i);
- final WindowSurfaceController controller = win.mWinAnimator.mSurfaceController;
- Slog.i(TAG_WM, win + ": " + win.mLayer
- + " animLayer=" + win.mWinAnimator.mAnimLayer
+ forAllWindows(w -> {
+ final WindowSurfaceController controller = w.mWinAnimator.mSurfaceController;
+ Slog.i(TAG_WM, w + ": " + w.mLayer
+ + " animLayer=" + w.mWinAnimator.mAnimLayer
+ " surfaceLayer=" + ((controller == null)
? "null" : controller.getLayer()));
- }
+ }, false /* traverseTopToBottom */);
}
final ScreenRotationAnimation screenRotationAnimation =
@@ -3064,6 +2248,9 @@
}
}
if (allBlack) {
+ final WindowState appWin = mScreenshotApplicationState.appWin;
+ final int maxLayer = mScreenshotApplicationState.maxLayer;
+ final int minLayer = mScreenshotApplicationState.minLayer;
Slog.i(TAG_WM, "Screenshot " + appWin + " was monochrome(" +
Integer.toHexString(firstColor) + ")! mSurfaceLayer=" +
(appWin != null ?
@@ -3104,32 +2291,23 @@
}
void onSeamlessRotationTimeout() {
- boolean layoutNeeded = false;
- for (int i = mWindows.size() - 1; i >= 0; i--) {
- final WindowState w = mWindows.get(i);
+ // Used to indicate the layout is needed.
+ mTmpWindow = null;
+
+ forAllWindows(w -> {
if (!w.mSeamlesslyRotated) {
- continue;
+ return;
}
- layoutNeeded = true;
+ mTmpWindow = w;
w.setDisplayLayoutNeeded();
mService.markForSeamlessRotation(w, false);
- }
+ }, true /* traverseTopToBottom */);
- if (layoutNeeded) {
+ if (mTmpWindow != null) {
mService.mWindowPlacerLocked.performSurfacePlacement();
}
}
- static final class GetWindowOnDisplaySearchResult {
- boolean reachedToken;
- WindowState foundWindow;
-
- void reset() {
- reachedToken = false;
- foundWindow = null;
- }
- }
-
static final class TaskForResizePointSearchResult {
boolean searchDone;
Task taskForResize;
@@ -3140,6 +2318,39 @@
}
}
+ private static final class ApplySurfaceChangesTransactionState {
+ boolean displayHasContent;
+ boolean obscured;
+ boolean syswin;
+ boolean focusDisplayed;
+ float preferredRefreshRate;
+ int preferredModeId;
+
+ void reset() {
+ displayHasContent = false;
+ obscured = false;
+ syswin = false;
+ focusDisplayed = false;
+ preferredRefreshRate = 0;
+ preferredModeId = 0;
+ }
+ }
+
+ private static final class ScreenshotApplicationState {
+ WindowState appWin;
+ int maxLayer;
+ int minLayer;
+ boolean screenshotReady;
+
+ void reset(boolean screenshotReady) {
+ appWin = null;
+ maxLayer = 0;
+ minLayer = 0;
+ this.screenshotReady = screenshotReady;
+ minLayer = (screenshotReady) ? 0 : Integer.MAX_VALUE;
+ }
+ }
+
/**
* Base class for any direct child window container of {@link #DisplayContent} need to inherit
* from. This is mainly a pass through class that allows {@link #DisplayContent} to have
@@ -3192,15 +2403,6 @@
void removeStackFromDisplay(TaskStack stack) {
removeChild(stack);
stack.onRemovedFromDisplay();
- // TODO: remove when window list will be gone.
- // Manually remove records from window list and tap excluded windows list.
- for (int i = mWindows.size() - 1; i >= 0; --i) {
- final WindowState windowState = mWindows.get(i);
- if (stack == windowState.getStack()) {
- mWindows.remove(i);
- mTapExcludedWindows.remove(windowState);
- }
- }
}
void moveStack(TaskStack stack, boolean toTop) {
@@ -3345,6 +2547,40 @@
}
@Override
+ int getOrientation() {
+ final WindowManagerPolicy policy = mService.mPolicy;
+ // Find a window requesting orientation.
+ final WindowState win = getWindow((w) -> {
+ if (!w.isVisibleLw() || !w.mPolicyVisibilityAfterAnim) {
+ return false;
+ }
+ final int req = w.mAttrs.screenOrientation;
+ if(req == SCREEN_ORIENTATION_UNSPECIFIED || req == SCREEN_ORIENTATION_BEHIND
+ || req == SCREEN_ORIENTATION_UNSET) {
+ return false;
+ }
+ return true;
+ });
+
+ if (win != null) {
+ final int req = win.mAttrs.screenOrientation;
+ if (DEBUG_ORIENTATION) Slog.v(TAG_WM, win + " forcing orientation to " + req);
+ if (policy.isKeyguardHostWindow(win.mAttrs)) {
+ mService.mLastKeyguardForcedOrientation = req;
+ }
+ return (mService.mLastWindowForcedOrientation = req);
+ }
+
+ mService.mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
+ if (policy.isKeyguardShowingAndNotOccluded()) {
+ return mService.mLastKeyguardForcedOrientation;
+ }
+
+ return SCREEN_ORIENTATION_UNSET;
+ }
+
+ @Override
String getName() {
return mName;
}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 12c72e9..01d1c30 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -16,10 +16,15 @@
package com.android.server.wm;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION;
import static android.view.WindowManager.INPUT_CONSUMER_PIP;
import static android.view.WindowManager.INPUT_CONSUMER_WALLPAPER;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
@@ -70,6 +75,10 @@
// Array of window handles to provide to the input dispatcher.
private InputWindowHandle[] mInputWindowHandles;
private int mInputWindowHandleCount;
+ private boolean mAddInputConsumerHandle;
+ private boolean mAddPipInputConsumerHandle;
+ private boolean mAddWallpaperInputConsumerHandle;
+ private boolean mDisableWallpaperTouchEvents;
// Set to true when the first input device configuration change notification
// is received to indicate that the input devices are ready.
@@ -323,12 +332,12 @@
}
}
- public void setUpdateInputWindowsNeededLw() {
+ void setUpdateInputWindowsNeededLw() {
mUpdateInputWindowsNeeded = true;
}
/* Updates the cached window information provided to the input dispatcher. */
- public void updateInputWindowsLw(boolean force) {
+ void updateInputWindowsLw(boolean force) {
if (!force && !mUpdateInputWindowsNeeded) {
return;
}
@@ -372,15 +381,92 @@
}
// Add all windows on the default display.
- mService.mRoot.updateInputWindows(this, mInputFocus, inDrag);
+ updateInputWindows(inDrag);
+
+ if (false) Slog.d(TAG_WM, "<<<<<<< EXITED updateInputWindowsLw");
+ }
+
+ private void updateInputWindows(boolean inDrag) {
+
+ clearInputWindowHandlesLw();
+
+ // TODO: multi-display
+ final InputConsumerImpl navInputConsumer =
+ getInputConsumer(INPUT_CONSUMER_NAVIGATION, DEFAULT_DISPLAY);
+ final InputConsumerImpl pipInputConsumer =
+ getInputConsumer(INPUT_CONSUMER_PIP, DEFAULT_DISPLAY);
+ final InputConsumerImpl wallpaperInputConsumer =
+ getInputConsumer(INPUT_CONSUMER_WALLPAPER, DEFAULT_DISPLAY);
+ mAddInputConsumerHandle = navInputConsumer != null;
+ mAddPipInputConsumerHandle = pipInputConsumer != null;
+ mAddWallpaperInputConsumerHandle = wallpaperInputConsumer != null;
+ final Rect pipTouchableBounds = mAddPipInputConsumerHandle ? new Rect() : null;
+ mDisableWallpaperTouchEvents = false;
+
+ final WallpaperController wallpaperController = mService.mRoot.mWallpaperController;
+ mService.mRoot.forAllWindows(w -> {
+ final InputChannel inputChannel = w.mInputChannel;
+ final InputWindowHandle inputWindowHandle = w.mInputWindowHandle;
+ if (inputChannel == null || inputWindowHandle == null || w.mRemoved
+ || w.isAdjustedForMinimizedDock()) {
+ // Skip this window because it cannot possibly receive input.
+ return;
+ }
+
+ if (mAddPipInputConsumerHandle
+ && w.getStackId() == PINNED_STACK_ID
+ && inputWindowHandle.layer <= pipInputConsumer.mWindowHandle.layer) {
+ // Update the bounds of the Pip input consumer to match the Pinned stack
+ w.getStack().getBounds(pipTouchableBounds);
+ pipInputConsumer.mWindowHandle.touchableRegion.set(pipTouchableBounds);
+ addInputWindowHandle(pipInputConsumer.mWindowHandle);
+ mAddPipInputConsumerHandle = false;
+ }
+
+ if (mAddInputConsumerHandle
+ && inputWindowHandle.layer <= navInputConsumer.mWindowHandle.layer) {
+ addInputWindowHandle(navInputConsumer.mWindowHandle);
+ mAddInputConsumerHandle = false;
+ }
+
+ if (mAddWallpaperInputConsumerHandle) {
+ if (w.mAttrs.type == TYPE_WALLPAPER && w.isVisibleLw()) {
+ // Add the wallpaper input consumer above the first visible wallpaper.
+ addInputWindowHandle(wallpaperInputConsumer.mWindowHandle);
+ mAddWallpaperInputConsumerHandle = false;
+ }
+ }
+
+ final int flags = w.mAttrs.flags;
+ final int privateFlags = w.mAttrs.privateFlags;
+ final int type = w.mAttrs.type;
+
+ final boolean hasFocus = w == mInputFocus;
+ final boolean isVisible = w.isVisibleLw();
+ if ((privateFlags & PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS) != 0) {
+ mDisableWallpaperTouchEvents = true;
+ }
+ final boolean hasWallpaper = wallpaperController.isWallpaperTarget(w)
+ && (privateFlags & PRIVATE_FLAG_KEYGUARD) == 0
+ && !mDisableWallpaperTouchEvents;
+
+ // If there's a drag in progress and 'child' is a potential drop target,
+ // make sure it's been told about the drag
+ if (inDrag && isVisible && w.getDisplayContent().isDefaultDisplay) {
+ mService.mDragState.sendDragStartedIfNeededLw(w);
+ }
+
+ addInputWindowHandle(
+ inputWindowHandle, w, flags, type, isVisible, hasFocus, hasWallpaper);
+ }, true /* traverseTopToBottom */);
+
+ if (mAddWallpaperInputConsumerHandle) {
+ // No visible wallpaper found, add the wallpaper input consumer at the end.
+ addInputWindowHandle(wallpaperInputConsumer.mWindowHandle);
+ }
// Send windows to native code.
mService.mInputManager.setInputWindows(mInputWindowHandles);
-
- // Clear the list in preparation for the next round.
- clearInputWindowHandlesLw();
-
- if (false) Slog.d(TAG_WM, "<<<<<<< EXITED updateInputWindowsLw");
}
/* Notifies that the input device configuration has changed. */
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 88986e3..1962ca7 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -219,7 +219,7 @@
return false;
}
- void getWindowsByName(WindowList output, String name) {
+ void getWindowsByName(ArrayList<WindowState> output, String name) {
int objectId = 0;
// See if this is an object ID.
try {
@@ -231,7 +231,7 @@
getWindowsByName(output, name, objectId);
}
- private void getWindowsByName(WindowList output, String name, int objectId) {
+ private void getWindowsByName(ArrayList<WindowState> output, String name, int objectId) {
forAllWindows((w) -> {
if (name != null) {
if (w.mAttrs.getTitle().toString().contains(name)) {
@@ -276,15 +276,6 @@
return null;
}
- // TODO: Users would have their own window containers under the display container?
- void switchUser() {
- final int count = mChildren.size();
- for (int i = 0; i < count; ++i) {
- final DisplayContent dc = mChildren.get(i);
- dc.switchUser();
- }
- }
-
/**
* Set new display override config and return array of ids of stacks that were changed during
* update. If called for the default display, global configuration will also be updated.
@@ -429,14 +420,6 @@
return hasChanges;
}
- void updateInputWindows(InputMonitor inputMonitor, WindowState inputFocus, boolean inDrag) {
- final int count = mChildren.size();
- for (int i = 0; i < count; ++i) {
- final DisplayContent dc = mChildren.get(i);
- dc.updateInputWindows(inputMonitor, inputFocus, inDrag);
- }
- }
-
boolean reclaimSomeSurfaceMemory(WindowStateAnimator winAnimator, String operation,
boolean secure) {
final WindowSurfaceController surfaceController = winAnimator.mSurfaceController;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b889db2..772833c8 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -589,46 +589,6 @@
}
}
- void getWindowOnDisplayBeforeToken(DisplayContent dc, WindowToken token,
- DisplayContent.GetWindowOnDisplaySearchResult result) {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final AppWindowToken current = mChildren.get(i);
- if (current == token) {
- // We have reach the token we are interested in. End search.
- result.reachedToken = true;
- return;
- }
-
- // We haven't reached the token yet; if this token is not going to the bottom and
- // has windows on this display, then it is a candidate for what we are looking for.
- final WindowList tokenWindowList = dc.getTokenWindowsOnDisplay(current);
- if (!current.sendingToBottom && tokenWindowList.size() > 0) {
- result.foundWindow = tokenWindowList.get(0);
- }
- }
- }
-
- void getWindowOnDisplayAfterToken(DisplayContent dc, WindowToken token,
- DisplayContent.GetWindowOnDisplaySearchResult result) {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final AppWindowToken current = mChildren.get(i);
- if (!result.reachedToken) {
- if (current == token) {
- // We have reached the token we are interested in. Get whichever window occurs
- // after it that is on the same display.
- result.reachedToken = true;
- }
- continue;
- }
-
- final WindowList tokenWindowList = dc.getTokenWindowsOnDisplay(current);
- if (tokenWindowList.size() > 0) {
- result.foundWindow = tokenWindowList.get(tokenWindowList.size() - 1);
- return;
- }
- }
- }
-
@Override
boolean fillsParent() {
return mFillsParent || !StackId.isTaskResizeAllowed(mStack.mStackId);
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 203ba72..f2c74df 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -859,7 +859,9 @@
}
// TODO: Should each user have there own stacks?
+ @Override
void switchUser() {
+ super.switchUser();
int top = mChildren.size();
for (int taskNdx = 0; taskNdx < top; ++taskNdx) {
Task task = mChildren.get(taskNdx);
@@ -1488,30 +1490,6 @@
}
}
- void getWindowOnDisplayBeforeToken(DisplayContent dc, WindowToken token,
- DisplayContent.GetWindowOnDisplaySearchResult result) {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final Task task = mChildren.get(i);
- task.getWindowOnDisplayBeforeToken(dc, token, result);
- if (result.reachedToken) {
- // We have reach the token we are interested in. End search.
- return;
- }
- }
- }
-
- void getWindowOnDisplayAfterToken(DisplayContent dc, WindowToken token,
- DisplayContent.GetWindowOnDisplaySearchResult result) {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final Task task = mChildren.get(i);
- task.getWindowOnDisplayAfterToken(dc, token, result);
- if (result.foundWindow != null) {
- // We have found a window after the token. End search.
- return;
- }
- }
- }
-
@Override
int getOrientation() {
return (StackId.canSpecifyOrientation(mStackId))
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 8dbf2b3..7fd8028 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -683,24 +683,6 @@
}
}
- void dumpTokens(PrintWriter pw, String prefix, boolean dumpAll) {
- if (!mWallpaperTokens.isEmpty()) {
- pw.println();
- pw.print(prefix); pw.println("Wallpaper tokens:");
- for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
- WindowToken token = mWallpaperTokens.get(i);
- pw.print(prefix); pw.print("Wallpaper #"); pw.print(i);
- pw.print(' '); pw.print(token);
- if (dumpAll) {
- pw.println(':');
- token.dump(pw, " ");
- } else {
- pw.println();
- }
- }
- }
- }
-
/** Helper class for storing the results of a wallpaper target find operation. */
final private static class FindWallpaperTargetResult {
WindowState topWallpaper = null;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index a6a907c..f5db0b6 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -18,7 +18,6 @@
import android.annotation.CallSuper;
import android.content.res.Configuration;
-import android.view.animation.Animation;
import com.android.internal.util.ToBooleanFunction;
import java.util.Comparator;
@@ -483,19 +482,11 @@
return false;
}
- /**
- * Rebuilds the WindowList for the input display content.
- * @param addIndex The index in the window list to add the next entry to.
- * @return The next index in the window list to.
- */
- // TODO: Hoping we can get rid of WindowList so this method wouldn't be needed.
- int rebuildWindowList(int addIndex) {
- final int count = mChildren.size();
- for (int i = 0; i < count; i++) {
- final WindowContainer wc = mChildren.get(i);
- addIndex = wc.rebuildWindowList(addIndex);
+ // TODO: Users would have their own window containers under the display container?
+ void switchUser() {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ mChildren.get(i).switchUser();
}
- return addIndex;
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 00d8fba..507679b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -606,17 +606,6 @@
boolean mInputMethodTargetWaitingAnim;
WindowState mInputMethodWindow = null;
- // TODO: Remove with extreme prejudice! This list is maintained so that we can keep track of
- // dialogs that should go on top of the IME so they can be re-arranged in the window list any
- // time the IME changes position in the window list. Normally you would have a dialog be a child
- // window of the IME, but they don't share the same token since they are added by different
- // clients. This doesn't really affect what the user sees on screen since this dialogs have an
- // higher base layer than the IME window, but it will affect users of the window list that
- // expect the list to represent the order of things on-screen (e.g input service). This makes
- // the code for managing the window list hard to follow (see all the places it is used).
- // We can remove the use of this field when we automatically assign layers and z-order the
- // window list before it is used whenever window container order changes.
- final ArrayList<WindowState> mInputMethodDialogs = new ArrayList<>();
boolean mHardKeyboardAvailable;
WindowManagerInternal.OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener;
@@ -1377,19 +1366,16 @@
boolean imMayMove = true;
+ win.mToken.addWindow(win);
if (type == TYPE_INPUT_METHOD) {
win.mGivenInsetsPending = true;
mInputMethodWindow = win;
- win.mToken.addImeWindow(win);
+ displayContent.computeImeTarget(true /* updateImeTarget */);
imMayMove = false;
} else if (type == TYPE_INPUT_METHOD_DIALOG) {
- mInputMethodDialogs.add(win);
- win.mToken.addWindow(win);
- displayContent.moveInputMethodDialogs(
- displayContent.findDesiredInputMethodWindowIndex(true));
+ displayContent.computeImeTarget(true /* updateImeTarget */);
imMayMove = false;
} else {
- win.mToken.addWindow(win);
if (type == TYPE_WALLPAPER) {
displayContent.mWallpaperController.clearLastWallpaperTimeoutTime();
displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
@@ -1462,7 +1448,7 @@
}
if (imMayMove) {
- displayContent.moveInputMethodWindowsIfNeeded(false);
+ displayContent.computeImeTarget(true /* updateImeTarget */);
}
// Don't do layout here, the window must call
@@ -1646,8 +1632,6 @@
if (mInputMethodWindow == win) {
mInputMethodWindow = null;
- } else if (win.mAttrs.type == TYPE_INPUT_METHOD_DIALOG) {
- mInputMethodDialogs.remove(win);
}
final WindowToken token = win.mToken;
@@ -1677,13 +1661,11 @@
dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
}
- if (dc != null && dc.removeFromWindowList(win)) {
- if (!mWindowPlacerLocked.isInLayout()) {
- dc.assignWindowLayers(true /* setLayoutNeeded */);
- mWindowPlacerLocked.performSurfacePlacement();
- if (win.mAppToken != null) {
- win.mAppToken.updateReportedVisibilityLocked();
- }
+ if (dc != null && !mWindowPlacerLocked.isInLayout()) {
+ dc.assignWindowLayers(true /* setLayoutNeeded */);
+ mWindowPlacerLocked.performSurfacePlacement();
+ if (win.mAppToken != null) {
+ win.mAppToken.updateReportedVisibilityLocked();
}
}
@@ -2064,14 +2046,15 @@
// reassign them at this point if the IM window state gets shuffled
boolean toBeDisplayed = (result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0;
final DisplayContent dc = win.getDisplayContent();
- if (imMayMove && (dc.moveInputMethodWindowsIfNeeded(false) || toBeDisplayed)) {
- // Little hack here -- we -should- be able to rely on the function to return true if
- // the IME has moved and needs its layer recomputed. However, if the IME was hidden
- // and isn't actually moved in the list, its layer may be out of data so we make
- // sure to recompute it.
- // TODO: Probably not needed once the window list always has the right z-ordering
- // when the window hierarchy is updated.
- dc.assignWindowLayers(false /* setLayoutNeeded */);
+ if (imMayMove) {
+ dc.computeImeTarget(true /* updateImeTarget */);
+ if (toBeDisplayed) {
+ // Little hack here -- we -should- be able to rely on the function to return
+ // true if the IME has moved and needs its layer recomputed. However, if the IME
+ // was hidden and isn't actually moved in the list, its layer may be out of data
+ // so we make sure to recompute it.
+ dc.assignWindowLayers(false /* setLayoutNeeded */);
+ }
}
if (wallpaperMayMove) {
@@ -3351,7 +3334,7 @@
if (mAppTransition.isTransitionSet()) {
task.setSendingToBottom(false);
}
- displayContent.rebuildAppWindowsAndLayoutIfNeeded();
+ displayContent.layoutAndAssignWindowLayersIfNeeded();
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -3373,7 +3356,7 @@
if (mAppTransition.isTransitionSet()) {
task.setSendingToBottom(true);
}
- stack.getDisplayContent().rebuildAppWindowsAndLayoutIfNeeded();
+ stack.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -4619,7 +4602,7 @@
return null;
}
}
- return displayContent.screenshotApplications(appToken, displayId, width, height,
+ return displayContent.screenshotApplications(appToken, width, height,
includeFullDisplay, frameScale, config, wallpaperOnly);
}
@@ -5181,7 +5164,7 @@
boolean result = true;
- final WindowList windows = new WindowList();
+ final ArrayList<WindowState> windows = new ArrayList();
synchronized (mWindowMap) {
mRoot.forAllWindows(w -> {
windows.add(w);
@@ -7312,7 +7295,7 @@
changes |= FINISH_LAYOUT_REDO_LAYOUT;
if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG_WM,
"Wallpaper layer changed: assigning layers + relayout");
- dc.moveInputMethodWindowsIfNeeded(true);
+ dc.computeImeTarget(true /* updateImeTarget */);
mRoot.mWallpaperMayChange = true;
// Since the window list has been rebuilt, focus might have to be recomputed since the
// actual order of windows might have changed again.
@@ -7405,10 +7388,25 @@
mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE);
// TODO(multidisplay): Focused windows on default display only.
final DisplayContent displayContent = getDefaultDisplayContentLocked();
- final boolean imWindowChanged = displayContent.moveInputMethodWindowsIfNeeded(
- mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS
- && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES);
+ boolean imWindowChanged = false;
+ if (mInputMethodWindow != null) {
+ final WindowState prevTarget = mInputMethodTarget;
+ final WindowState newTarget =
+ displayContent.computeImeTarget(true /* updateImeTarget*/);
+
+ imWindowChanged = prevTarget != newTarget;
+
+ if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS
+ && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) {
+ final int prevImeAnimLayer = mInputMethodWindow.mWinAnimator.mAnimLayer;
+ displayContent.assignWindowLayers(false /* setLayoutNeeded */);
+ imWindowChanged |=
+ prevImeAnimLayer != mInputMethodWindow.mWinAnimator.mAnimLayer;
+ }
+ }
+
if (imWindowChanged) {
+ mWindowsChanged = true;
displayContent.setLayoutNeeded();
newFocus = mRoot.computeFocusedWindow();
}
@@ -7883,7 +7881,6 @@
private void dumpTokensLocked(PrintWriter pw, boolean dumpAll) {
pw.println("WINDOW MANAGER TOKENS (dumpsys window tokens)");
mRoot.dumpTokens(pw, dumpAll);
- mRoot.mWallpaperController.dumpTokens(pw, " ", dumpAll);
if (!mFinishedStarting.isEmpty()) {
pw.println();
pw.println(" Finishing start of application tokens:");
@@ -7929,16 +7926,6 @@
ArrayList<WindowState> windows) {
mRoot.dumpWindowsNoHeader(pw, dumpAll, windows);
- if (mInputMethodDialogs.size() > 0) {
- pw.println();
- pw.println(" Input method dialogs:");
- for (int i=mInputMethodDialogs.size()-1; i>=0; i--) {
- WindowState w = mInputMethodDialogs.get(i);
- if (windows == null || windows.contains(w)) {
- pw.print(" IM Dialog #"); pw.print(i); pw.print(": "); pw.println(w);
- }
- }
- }
if (mPendingRemove.size() > 0) {
pw.println();
pw.println(" Remove pending for:");
@@ -8097,7 +8084,7 @@
private boolean dumpWindows(PrintWriter pw, String name, String[] args, int opti,
boolean dumpAll) {
- final WindowList windows = new WindowList();
+ final ArrayList<WindowState> windows = new ArrayList();
if ("apps".equals(name) || "visible".equals(name) || "visible-apps".equals(name)) {
final boolean appsOnly = name.contains("apps");
final boolean visibleOnly = name.contains("visible");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 661df9cd..d959d8c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -76,6 +76,7 @@
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
@@ -115,6 +116,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
@@ -140,9 +142,6 @@
import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
import static com.android.server.wm.WindowStateAnimator.READY_TO_SHOW;
-class WindowList extends ArrayList<WindowState> {
-}
-
/** A window in the window manager. */
class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState {
static final String TAG = TAG_WITH_CLASS_NAME ? "WindowState" : TAG_WM;
@@ -448,12 +447,6 @@
*/
boolean mWindowRemovalAllowed;
- /**
- * Temp for keeping track of windows that have been removed when
- * rebuilding window list.
- */
- boolean mRebuilding;
-
// Input channel and input window handle used by the input dispatcher.
final InputWindowHandle mInputWindowHandle;
InputChannel mInputChannel;
@@ -1721,7 +1714,7 @@
final DisplayContent dc = getDisplayContent();
if (mService.mInputMethodTarget == this) {
- dc.moveInputMethodWindowsIfNeeded(false);
+ dc.computeImeTarget(true /* updateImeTarget */);
}
final int type = mAttrs.type;
@@ -1931,6 +1924,33 @@
return mLayer + specialAdjustment;
}
+ boolean canBeImeTarget() {
+ final int fl = mAttrs.flags & (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM);
+ final int type = mAttrs.type;
+
+ if (fl != 0 && fl != (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM)
+ && type != TYPE_APPLICATION_STARTING) {
+ return false;
+ }
+
+ if (DEBUG_INPUT_METHOD) {
+ Slog.i(TAG_WM, "isVisibleOrAdding " + this + ": " + isVisibleOrAdding());
+ if (!isVisibleOrAdding()) {
+ Slog.i(TAG_WM, " mSurfaceController=" + mWinAnimator.mSurfaceController
+ + " relayoutCalled=" + mRelayoutCalled
+ + " viewVis=" + mViewVisibility
+ + " policyVis=" + mPolicyVisibility
+ + " policyVisAfterAnim=" + mPolicyVisibilityAfterAnim
+ + " parentHidden=" + isParentWindowHidden()
+ + " exiting=" + mAnimatingExit + " destroying=" + mDestroying);
+ if (mAppToken != null) {
+ Slog.i(TAG_WM, " mAppToken.hiddenRequested=" + mAppToken.hiddenRequested);
+ }
+ }
+ }
+ return isVisibleOrAdding();
+ }
+
void scheduleAnimationIfDimming() {
final DisplayContent dc = getDisplayContent();
if (dc == null) {
@@ -2085,7 +2105,7 @@
return false;
}
- void removeReplacedWindow() {
+ private void removeReplacedWindow() {
if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replaced window: " + this);
if (isDimming()) {
transferDimToReplacement();
@@ -2138,6 +2158,16 @@
}
}
+ @Override
+ void switchUser() {
+ super.switchUser();
+ if (isHiddenFromUserLocked()) {
+ if (DEBUG_VISIBILITY) Slog.w(TAG_WM, "user changing, hiding " + this
+ + ", attrs=" + mAttrs.type + ", belonging to " + mOwnerUid);
+ hideLw(false);
+ }
+ }
+
int getTouchableRegion(Region region, int flags) {
final boolean modal = (flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0;
if (modal && mAppToken != null) {
@@ -3499,16 +3529,6 @@
return mIsChildWindow;
}
- /**
- * Returns the bottom child window in regards to z-order of this window or null if no children.
- */
- WindowState getBottomChild() {
- // Child windows are z-ordered based on sub-layer using {@link #sWindowSubLayerComparator}
- // and the child with the lowest z-order will be at the head of the list.
- WindowState c = mChildren.peekFirst();
- return c == null ? null : c;
- }
-
boolean layoutInParentFrame() {
return mIsChildWindow
&& (mAttrs.privateFlags & PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME) != 0;
@@ -3795,44 +3815,6 @@
}
@Override
- int rebuildWindowList(int addIndex) {
- return reAddWindow(addIndex);
- }
-
- // TODO: come-up with a better name for this method that represents what it does.
- // Or, it is probably not going to matter anyways if we are successful in getting rid of
- // the WindowList concept.
- int reAddWindow(int index) {
- final DisplayContent dc = getDisplayContent();
- // Adding child windows relies on child windows being ordered by mSubLayer using
- // {@link #sWindowSubLayerComparator}.
- final int childCount = mChildren.size();
- boolean winAdded = false;
- for (int j = 0; j < childCount; j++) {
- final WindowState child = mChildren.get(j);
- if (!winAdded && child.mSubLayer >= 0) {
- if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM,
- "Re-adding child window at " + index + ": " + child);
- mRebuilding = false;
- dc.addToWindowList(this, index);
- index++;
- winAdded = true;
- }
- if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Re-adding window at " + index + ": " + child);
- child.mRebuilding = false;
- dc.addToWindowList(child, index);
- index++;
- }
- if (!winAdded) {
- if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Re-adding window at " + index + ": " + this);
- mRebuilding = false;
- dc.addToWindowList(this, index);
- index++;
- }
- return index;
- }
-
- @Override
boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
if (mChildren.isEmpty()) {
// The window has no children so we just return it.
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 7e1880f..de80837 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -239,7 +239,8 @@
mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
final DisplayContent displayContent = mService.getDefaultDisplayContentLocked();
- displayContent.rebuildAppWindowList();
+ // TODO: Don't believe this is really needed...
+ //mService.mWindowsChanged = true;
mService.mRoot.mWallpaperMayChange = false;
@@ -356,12 +357,8 @@
displayContent.setLayoutNeeded();
// TODO(multidisplay): IMEs are only supported on the default display.
- // TODO: Probably not needed once the window list always has the right z-ordering
- // when the window hierarchy is updated.
final DisplayContent dc = mService.getDefaultDisplayContentLocked();
- if (!dc.moveInputMethodWindowsIfNeeded(true)) {
- dc.assignWindowLayers(false /*setLayoutNeeded*/);
- }
+ dc.computeImeTarget(true /* updateImeTarget */);
mService.updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES,
true /*updateInputWindows*/);
mService.mFocusMayChange = false;
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index a2eebc3..40bd3fb 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -168,13 +168,6 @@
return highestAnimLayer;
}
- WindowState getTopWindow() {
- if (mChildren.isEmpty()) {
- return null;
- }
- return (WindowState) mChildren.get(mChildren.size() - 1).getTop();
- }
-
/**
* Returns true if the new window is considered greater than the existing window in terms of
* z-order.
@@ -189,51 +182,16 @@
if (DEBUG_FOCUS) Slog.d(TAG_WM,
"addWindow: win=" + win + " Callers=" + Debug.getCallers(5));
- if (!win.isChildWindow()) {
- if (asAppWindowToken() != null) {
- mDisplayContent.addAppWindowToWindowList(win);
- } else {
- mDisplayContent.addNonAppWindowToWindowList(win);
- }
-
- if (!mChildren.contains(win)) {
- if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Adding " + win + " to " + this);
- addChild(win, mWindowComparator);
- }
- } else {
- mDisplayContent.addChildWindowToWindowList(win);
- }
- }
-
- void addImeWindow(WindowState win) {
- int pos = mDisplayContent.findDesiredInputMethodWindowIndex(true);
-
- if (pos < 0) {
- addWindow(win);
- mDisplayContent.moveInputMethodDialogs(pos);
+ if (win.isChildWindow()) {
+ // Child windows are added to their parent windows.
return;
}
-
- if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
- "Adding input method window " + win + " at " + pos);
- mDisplayContent.addToWindowList(win, pos);
if (!mChildren.contains(win)) {
- addChild(win, null);
+ if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Adding " + win + " to " + this);
+ addChild(win, mWindowComparator);
+ mService.mWindowsChanged = true;
+ // TODO: Should we also be setting layout needed here and other places?
}
- mDisplayContent.moveInputMethodDialogs(pos + 1);
- }
-
- /** Return the first window in the token window list that isn't a starting window or null. */
- WindowState getFirstNonStartingWindow() {
- final int count = mChildren.size();
- // We only care about parent windows so no need to loop through child windows.
- for (int i = 0; i < count; i++) {
- final WindowState w = mChildren.get(i);
- if (w.mAttrs.type != TYPE_APPLICATION_STARTING) {
- return w;
- }
- }
- return null;
}
/** Returns true if the token windows list is empty. */
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index ba23f21..1fc4378 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -41,12 +41,10 @@
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
-import android.os.UserManager;
import android.os.storage.IStorageManager;
-import android.provider.Settings;
+import android.util.BootTimingsTraceLog;
import android.util.DisplayMetrics;
import android.util.EventLog;
-import android.util.Pair;
import android.util.Slog;
import android.view.WindowManager;
@@ -55,7 +53,6 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.SamplingProfilerIntegration;
-import com.android.internal.os.ZygoteInit;
import com.android.internal.policy.EmergencyAffordanceManager;
import com.android.internal.widget.ILockSettings;
import com.android.server.accessibility.AccessibilityManagerService;
@@ -102,8 +99,8 @@
import com.android.server.storage.DeviceStorageMonitorService;
import com.android.server.telecom.TelecomLoaderService;
import com.android.server.trust.TrustManagerService;
-import com.android.server.tv.TvRemoteService;
import com.android.server.tv.TvInputManagerService;
+import com.android.server.tv.TvRemoteService;
import com.android.server.twilight.TwilightService;
import com.android.server.usage.UsageStatsService;
import com.android.server.vr.VrManagerService;
@@ -112,8 +109,6 @@
import dalvik.system.VMRuntime;
-import java.util.ArrayDeque;
-import java.util.Deque;
import java.io.File;
import java.io.IOException;
import java.util.Locale;
@@ -125,11 +120,8 @@
public final class SystemServer {
private static final String TAG = "SystemServer";
- private static final boolean LOG_BOOT_TIME = true;
- // Debug boot time for every step if it's non-user build.
- private static final boolean DEBUG_BOOT_TIME = LOG_BOOT_TIME && !"user".equals(Build.TYPE);
- private static final String TAG_BOOT_TIME = "SystemServerTiming";
- private static final Deque<Pair<String, Long>> START_TIMES = new ArrayDeque<>();
+ private static final BootTimingsTraceLog BOOT_TIMINGS_TRACE_LOG
+ = new BootTimingsTraceLog("SystemServerTiming", Trace.TRACE_TAG_SYSTEM_SERVER);
private static final String ENCRYPTING_STATE = "trigger_restart_min_framework";
private static final String ENCRYPTED_STATE = "1";
@@ -279,6 +271,11 @@
int uptimeMillis = (int) SystemClock.uptimeMillis();
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN, uptimeMillis);
MetricsLogger.histogram(null, "boot_system_server_init", uptimeMillis);
+ // Also report when first stage of init has started
+ long initStartNs = SystemProperties.getLong("init.start", -1);
+ if (initStartNs >= 0) {
+ MetricsLogger.histogram(null, "boot_android_init", (int)(initStartNs / 1000000));
+ }
// In case the runtime switched since last boot (such as when
// the old runtime was removed in an OTA), set the system
@@ -1643,24 +1640,11 @@
}
private static void traceBeginAndSlog(String name) {
- Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, name);
Slog.i(TAG, name);
- if (DEBUG_BOOT_TIME) {
- START_TIMES.push(Pair.create(name, Long.valueOf(SystemClock.elapsedRealtime())));
- }
+ BOOT_TIMINGS_TRACE_LOG.traceBegin(name);
}
private static void traceEnd() {
- Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
- if (!DEBUG_BOOT_TIME) {
- return;
- }
- Pair<String, Long> event = START_TIMES.pollFirst();
- if (event == null) {
- Slog.w(TAG, "traceEnd called more times than traceBeginAndSlog");
- return;
- }
- Slog.d(TAG_BOOT_TIME, event.first + " took to complete: "
- + (SystemClock.elapsedRealtime() - event.second) + "ms");
+ BOOT_TIMINGS_TRACE_LOG.traceEnd();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
index 5b565a7..8591dae 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
@@ -37,6 +37,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
@@ -338,4 +339,65 @@
actualId = mAccountsDb.findDeAccountId(account);
assertEquals(-1, actualId);
}
+
+ @Test
+ public void testFindDeAccountByAccountId() {
+ long accId = 10;
+ Account account = new Account("name", "example.com");
+ assertNull(mAccountsDb.findDeAccountByAccountId(accId));
+
+ mAccountsDb.insertDeAccount(account, accId);
+
+ Account foundAccount = mAccountsDb.findDeAccountByAccountId(accId);
+ assertEquals(account, foundAccount);
+ }
+
+ @Test
+ public void testVisibilityFindSetDelete() {
+ long accId = 10;
+ int uid1 = 100500;
+ int uid2 = 100501;
+ Account account = new Account("name", "example.com");
+ assertNull(mAccountsDb.findAccountVisibility(account, uid1));
+
+ mAccountsDb.insertDeAccount(account, accId);
+ assertNull(mAccountsDb.findAccountVisibility(account, uid1));
+ assertNull(mAccountsDb.findAccountVisibility(accId, uid1));
+
+ mAccountsDb.setAccountVisibility(accId, uid1, 1);
+ assertEquals(mAccountsDb.findAccountVisibility(account, uid1), Integer.valueOf(1));
+ assertEquals(mAccountsDb.findAccountVisibility(accId, uid1), Integer.valueOf(1));
+
+ mAccountsDb.setAccountVisibility(accId, uid2, 2);
+ assertEquals(mAccountsDb.findAccountVisibility(accId, uid2), Integer.valueOf(2));
+
+ mAccountsDb.setAccountVisibility(accId, uid2, 3);
+ assertEquals(mAccountsDb.findAccountVisibility(accId, uid2), Integer.valueOf(3));
+
+ Map<Integer, Integer> vis = mAccountsDb.findAccountVisibilityForAccountId(accId);
+ assertEquals(vis.size(), 2);
+ assertEquals(vis.get(uid1), Integer.valueOf(1));
+ assertEquals(vis.get(uid2), Integer.valueOf(3));
+
+ assertTrue(mAccountsDb.deleteAccountVisibilityForUid(uid1));
+ assertNull(mAccountsDb.findAccountVisibility(accId, uid1));
+ assertFalse(mAccountsDb.deleteAccountVisibilityForUid(uid1)); // Already deleted.
+ }
+
+ @Test
+ public void testVisibilityCleanupTrigger() {
+ long accId = 10;
+ int uid1 = 100500;
+ Account account = new Account("name", "example.com");
+
+ assertNull(mAccountsDb.findAccountVisibility(account, uid1));
+ mAccountsDb.insertDeAccount(account, accId);
+ assertNull(mAccountsDb.findAccountVisibility(account, uid1));
+
+ mAccountsDb.setAccountVisibility(accId, uid1, 1);
+ assertEquals(mAccountsDb.findAccountVisibility(accId, uid1), Integer.valueOf(1));
+
+ assertTrue(mAccountsDb.deleteDeAccount(accId)); // Trigger should remove visibility.
+ assertNull(mAccountsDb.findAccountVisibility(account, uid1));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
index 50e5a22..3f47d5c 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
@@ -117,35 +117,6 @@
}
@Test
- public void testGetBottomChild() throws Exception {
- final WindowState parentWindow =
- createWindow(null, TYPE_APPLICATION, mWindowToken, "parentWindow");
- assertNull(parentWindow.getBottomChild());
-
- final WindowState child1 =
- createWindow(parentWindow, TYPE_APPLICATION_PANEL, mWindowToken, "child1");
- assertEquals(child1, parentWindow.getBottomChild());
-
- final WindowState child2 =
- createWindow(parentWindow, TYPE_APPLICATION_PANEL, mWindowToken, "child2");
- // Since child1 and child2 are at the same layer, then child2 is expect to be added on top
- // on child1
- assertEquals(child1, parentWindow.getBottomChild());
-
- final WindowState child3 =
- createWindow(parentWindow, TYPE_APPLICATION_MEDIA_OVERLAY, mWindowToken, "child3");
- // Since child3 is a negative layer, we would expect it to be added below current children
- // with positive layers.
- assertEquals(child3, parentWindow.getBottomChild());
-
- final WindowState child4 =
- createWindow(parentWindow, TYPE_APPLICATION_MEDIA_OVERLAY, mWindowToken, "child4");
- // We would also expect additional negative layers to be added below existing negative
- // layers.
- assertEquals(child4, parentWindow.getBottomChild());
- }
-
- @Test
public void testGetParentWindow() throws Exception {
final WindowState parentWindow =
createWindow(null, TYPE_APPLICATION, mWindowToken, "parentWindow");
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 20dd012..457fd88 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -686,42 +686,49 @@
public static final String EXTRA_DATA_FAILURE_CAUSE = PhoneConstants.DATA_FAILURE_CAUSE_KEY;
/**
- * Broadcast intent action for letting custom component know to show voicemail notification.
- * @hide
+ * Broadcast intent action for letting the default dialer to know to show voicemail
+ * notification.
+ *
+ * <p>
+ * The {@link #EXTRA_NOTIFICATION_COUNT} extra indicates the total numbers of unheard
+ * voicemails.
+ * The {@link #EXTRA_VOICEMAIL_NUMBER} extra indicates the voicemail number if available.
+ * The {@link #EXTRA_CALL_VOICEMAIL_INTENT} extra is a {@link android.app.PendingIntent} that
+ * will call the voicemail number when sent. This extra will be empty if the voicemail number
+ * is not set, and {@link #EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT} will be set instead.
+ * The {@link #EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT} extra is a
+ * {@link android.app.PendingIntent} that will launch the voicemail settings. This extra is only
+ * available when the voicemail number is not set.
+ *
+ * @see #EXTRA_NOTIFICATION_COUNT
+ * @see #EXTRA_VOICEMAIL_NUMBER
+ * @see #EXTRA_CALL_VOICEMAIL_INTENT
+ * @see #EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT
*/
- @SystemApi
public static final String ACTION_SHOW_VOICEMAIL_NOTIFICATION =
"android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION";
/**
* The number of voice messages associated with the notification.
- * @hide
*/
- @SystemApi
public static final String EXTRA_NOTIFICATION_COUNT =
"android.telephony.extra.NOTIFICATION_COUNT";
/**
* The voicemail number.
- * @hide
*/
- @SystemApi
public static final String EXTRA_VOICEMAIL_NUMBER =
"android.telephony.extra.VOICEMAIL_NUMBER";
/**
* The intent to call voicemail.
- * @hide
*/
- @SystemApi
public static final String EXTRA_CALL_VOICEMAIL_INTENT =
"android.telephony.extra.CALL_VOICEMAIL_INTENT";
/**
* The intent to launch voicemail settings.
- * @hide
*/
- @SystemApi
public static final String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT =
"android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT";
@@ -4850,10 +4857,19 @@
return false;
}
- /** @hide */
- @SystemApi
+ /**
+ * Turns mobile data on or off.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the
+ * calling app has carrier privileges.
+ *
+ * @param enable Whether to enable mobile data.
+ *
+ * @see #hasCarrierPrivileges
+ */
public void setDataEnabled(boolean enable) {
- setDataEnabled(SubscriptionManager.getDefaultDataSubscriptionId(), enable);
+ setDataEnabled(getSubId(), enable);
}
/** @hide */
@@ -4869,10 +4885,20 @@
}
}
- /** @hide */
- @SystemApi
+ /**
+ * Returns whether mobile data is enabled or not.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE ACCESS_NETWORK_STATE},
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}, or that the
+ * calling app has carrier privileges.
+ *
+ * @return true if mobile data is enabled.
+ *
+ * @see #hasCarrierPrivileges
+ */
public boolean getDataEnabled() {
- return getDataEnabled(SubscriptionManager.getDefaultDataSubscriptionId());
+ return getDataEnabled(getSubId());
}
/** @hide */
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 4d0e6f0..306dec5 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -728,10 +728,7 @@
static private void waitFor(Criteria criteria) {
int delays = 0;
while (!criteria.get()) {
- try {
- Thread.sleep(50);
- } catch (InterruptedException e) {
- }
+ sleepFor(50);
if (++delays == 10) fail();
}
}
@@ -2333,10 +2330,30 @@
networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
// pass timeout and validate that UNAVAILABLE is not called
- try {
- Thread.sleep(15);
- } catch (InterruptedException e) {
- }
+ sleepFor(15);
+ networkCallback.assertNoCallback();
+ }
+
+ /**
+ * Validate that a satisfied network request followed by a disconnected (lost) network does
+ * not trigger onUnavailable() once the time-out period expires.
+ */
+ @SmallTest
+ public void testSatisfiedThenLostNetworkRequestDoesNotTriggerOnUnavailable() {
+ NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
+ NetworkCapabilities.TRANSPORT_WIFI).build();
+ final TestNetworkCallback networkCallback = new TestNetworkCallback();
+ mCm.requestNetwork(nr, networkCallback, 500);
+
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(false);
+ networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ sleepFor(20);
+ mWiFiNetworkAgent.disconnect();
+ networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+
+ // pass timeout and validate that UNAVAILABLE is not called
+ sleepFor(600);
networkCallback.assertNoCallback();
}
@@ -2378,10 +2395,7 @@
// pass timeout and validate that no callbacks
// Note: doesn't validate that nothing called from CS since even if called the CM already
// unregisters the callback and won't pass it through!
- try {
- Thread.sleep(15);
- } catch (InterruptedException e) {
- }
+ sleepFor(15);
networkCallback.assertNoCallback();
// create a network satisfying request - validate that request not triggered
@@ -2795,4 +2809,13 @@
mCm.unregisterNetworkCallback(pendingIntent);
}
}
+
+ /* test utilities */
+ static private void sleepFor(int ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ }
+
+ }
}
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index a62a0fb..643753a 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -36,6 +36,27 @@
public HomeSP homeSp = null;
public Credential credential = null;
+ /**
+ * Constructor for creating PasspointConfiguration with default values.
+ */
+ public PasspointConfiguration() {}
+
+ /**
+ * Copy constructor.
+ *
+ * @param source The source to copy from
+ */
+ public PasspointConfiguration(PasspointConfiguration source) {
+ if (source != null) {
+ if (source.homeSp != null) {
+ homeSp = new HomeSP(source.homeSp);
+ }
+ if (source.credential != null) {
+ credential = new Credential(source.credential);
+ }
+ }
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
index 57e65eb..790dfaf 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
@@ -109,6 +109,25 @@
*/
public String nonEapInnerMethod = null;
+ /**
+ * Constructor for creating UserCredential with default values.
+ */
+ public UserCredential() {}
+
+ /**
+ * Copy constructor.
+ *
+ * @param source The source to copy from
+ */
+ public UserCredential(UserCredential source) {
+ if (source != null) {
+ username = source.username;
+ password = source.password;
+ eapType = source.eapType;
+ nonEapInnerMethod = source.nonEapInnerMethod;
+ }
+ }
+
@Override
public int describeContents() {
return 0;
@@ -221,6 +240,26 @@
*/
public byte[] certSha256FingerPrint = null;
+ /**
+ * Constructor for creating CertificateCredential with default values.
+ */
+ public CertificateCredential() {}
+
+ /**
+ * Copy constructor.
+ *
+ * @param source The source to copy from
+ */
+ public CertificateCredential(CertificateCredential source) {
+ if (source != null) {
+ certType = source.certType;
+ if (source.certSha256FingerPrint != null) {
+ certSha256FingerPrint = Arrays.copyOf(source.certSha256FingerPrint,
+ source.certSha256FingerPrint.length);
+ }
+ }
+ }
+
@Override
public int describeContents() {
return 0;
@@ -307,6 +346,23 @@
*/
public int eapType = Integer.MIN_VALUE;
+ /**
+ * Constructor for creating SimCredential with default values.
+ */
+ public SimCredential() {}
+
+ /**
+ * Copy constructor
+ *
+ * @param source The source to copy from
+ */
+ public SimCredential(SimCredential source) {
+ if (source != null) {
+ imsi = source.imsi;
+ eapType = source.eapType;
+ }
+ }
+
@Override
public int describeContents() {
return 0;
@@ -422,6 +478,37 @@
*/
public PrivateKey clientPrivateKey = null;
+ /**
+ * Constructor for creating Credential with default values.
+ */
+ public Credential() {}
+
+ /**
+ * Copy constructor.
+ *
+ * @param source The source to copy from
+ */
+ public Credential(Credential source) {
+ if (source != null) {
+ realm = source.realm;
+ if (source.userCredential != null) {
+ userCredential = new UserCredential(source.userCredential);
+ }
+ if (source.certCredential != null) {
+ certCredential = new CertificateCredential(source.certCredential);
+ }
+ if (source.simCredential != null) {
+ simCredential = new SimCredential(source.simCredential);
+ }
+ if (source.clientCertificateChain != null) {
+ clientCertificateChain = Arrays.copyOf(source.clientCertificateChain,
+ source.clientCertificateChain.length);
+ }
+ caCertificate = source.caCertificate;
+ clientPrivateKey = source.clientPrivateKey;
+ }
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java b/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
index 5837c06..d4a5792 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
@@ -53,6 +53,27 @@
*/
public long[] roamingConsortiumOIs = null;
+ /**
+ * Constructor for creating HomeSP with default values.
+ */
+ public HomeSP() {}
+
+ /**
+ * Copy constructor.
+ *
+ * @param source The source to copy from
+ */
+ public HomeSP(HomeSP source) {
+ if (source != null) {
+ fqdn = source.fqdn;
+ friendlyName = source.friendlyName;
+ if (source.roamingConsortiumOIs != null) {
+ roamingConsortiumOIs = Arrays.copyOf(source.roamingConsortiumOIs,
+ source.roamingConsortiumOIs.length);
+ }
+ }
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index b4a3acf..2350d32 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -33,6 +33,11 @@
@SmallTest
public class PasspointConfigurationTest {
+ /**
+ * Utility function for creating a {@link android.net.wifi.hotspot2.pps.HomeSP}.
+ *
+ * @return {@link android.net.wifi.hotspot2.pps.HomeSP}
+ */
private static HomeSP createHomeSp() {
HomeSP homeSp = new HomeSP();
homeSp.fqdn = "fqdn";
@@ -41,6 +46,11 @@
return homeSp;
}
+ /**
+ * Utility function for creating a {@link android.net.wifi.hotspot2.pps.Credential}.
+ *
+ * @return {@link android.net.wifi.hotspot2.pps.Credential}
+ */
private static Credential createCredential() {
Credential cred = new Credential();
cred.realm = "realm";
@@ -55,6 +65,12 @@
return cred;
}
+ /**
+ * Verify parcel write and read consistency for the given configuration.
+ *
+ * @param writeConfig The configuration to verify
+ * @throws Exception
+ */
private static void verifyParcel(PasspointConfiguration writeConfig) throws Exception {
Parcel parcel = Parcel.obtain();
writeConfig.writeToParcel(parcel, 0);
@@ -77,6 +93,7 @@
/**
* Verify parcel read/write for a configuration that contained both HomeSP and Credential.
+ *
* @throws Exception
*/
@Test
@@ -158,4 +175,30 @@
config.credential = createCredential();
assertTrue(config.validate());
}
-}
\ No newline at end of file
+
+ /**
+ * Verify that copy constructor works when pass in a null source.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateCopyConstructorWithNullSource() throws Exception {
+ PasspointConfiguration copyConfig = new PasspointConfiguration(null);
+ PasspointConfiguration defaultConfig = new PasspointConfiguration();
+ assertTrue(copyConfig.equals(defaultConfig));
+ }
+
+ /**
+ * Verify that copy constructor works when pass in a valid source.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateCopyConstructorWithValidSource() throws Exception {
+ PasspointConfiguration sourceConfig = new PasspointConfiguration();
+ sourceConfig.homeSp = createHomeSp();
+ sourceConfig.credential = createCredential();
+ PasspointConfiguration copyConfig = new PasspointConfiguration(sourceConfig);
+ assertTrue(copyConfig.equals(sourceConfig));
+ }
+}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
index 223aa52..9c8b749 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
@@ -470,4 +470,52 @@
cred.simCredential.eapType = EAPConstants.EAP_SIM;
assertFalse(cred.validate());
}
+
+ /**
+ * Verify that copy constructor works when pass in a null source.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateCopyConstructorWithNullSource() throws Exception {
+ Credential copyCred = new Credential(null);
+ Credential defaultCred = new Credential();
+ assertTrue(copyCred.equals(defaultCred));
+ }
+
+ /**
+ * Verify that copy constructor works when pass in a source with user credential.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateCopyConstructorWithSourceWithUserCred() throws Exception {
+ Credential sourceCred = createCredentialWithUserCredential();
+ Credential copyCred = new Credential(sourceCred);
+ assertTrue(copyCred.equals(sourceCred));
+ }
+
+ /**
+ * Verify that copy constructor works when pass in a source with certificate credential.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateCopyConstructorWithSourceWithCertCred() throws Exception {
+ Credential sourceCred = createCredentialWithCertificateCredential();
+ Credential copyCred = new Credential(sourceCred);
+ assertTrue(copyCred.equals(sourceCred));
+ }
+
+ /**
+ * Verify that copy constructor works when pass in a source with SIM credential.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateCopyConstructorWithSourceWithSimCred() throws Exception {
+ Credential sourceCred = createCredentialWithSimCredential();
+ Credential copyCred = new Credential(sourceCred);
+ assertTrue(copyCred.equals(sourceCred));
+ }
}
\ No newline at end of file
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java
index fff1477..c707993 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java
@@ -118,4 +118,31 @@
homeSp.roamingConsortiumOIs = new long[] {0x55, 0x66};
assertTrue(homeSp.validate());
}
+
+ /**
+ * Verify that copy constructor works when pass in a null source.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateCopyConstructorFromNullSource() throws Exception {
+ HomeSP copySp = new HomeSP(null);
+ HomeSP defaultSp = new HomeSP();
+ assertTrue(copySp.equals(defaultSp));
+ }
+
+ /**
+ * Verify that copy constructor works when pass in a valid source.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateCopyConstructorFromValidSource() throws Exception {
+ HomeSP sourceSp = new HomeSP();
+ sourceSp.fqdn = "fqdn";
+ sourceSp.friendlyName = "friendlyName";
+ sourceSp.roamingConsortiumOIs = new long[] {0x55, 0x66};
+ HomeSP copySp = new HomeSP(sourceSp);
+ assertTrue(copySp.equals(sourceSp));
+ }
}