Merge "Ignore missing font in fontchain_linter.py"
diff --git a/Android.bp b/Android.bp
index 6517a0c..19c0580 100644
--- a/Android.bp
+++ b/Android.bp
@@ -242,6 +242,7 @@
"core/java/android/security/IKeystoreService.aidl",
"core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl",
"core/java/android/service/autofill/IAutoFillService.aidl",
+ "core/java/android/service/autofill/IAutofillFieldClassificationService.aidl",
"core/java/android/service/autofill/IFillCallback.aidl",
"core/java/android/service/autofill/ISaveCallback.aidl",
"core/java/android/service/carrier/ICarrierService.aidl",
@@ -500,6 +501,7 @@
"telephony/java/com/android/ims/internal/IImsService.aidl",
"telephony/java/com/android/ims/internal/IImsServiceController.aidl",
"telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl",
+ "telephony/java/com/android/ims/internal/IImsSmsListener.aidl",
"telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl",
"telephony/java/com/android/ims/internal/IImsUt.aidl",
"telephony/java/com/android/ims/internal/IImsUtListener.aidl",
@@ -547,6 +549,7 @@
"telephony/java/com/android/internal/telephony/euicc/ISetDefaultSmdpAddressCallback.aidl",
"telephony/java/com/android/internal/telephony/euicc/ISetNicknameCallback.aidl",
"telephony/java/com/android/internal/telephony/euicc/ISwitchToProfileCallback.aidl",
+ "wifi/java/android/net/wifi/ISoftApCallback.aidl",
"wifi/java/android/net/wifi/IWifiManager.aidl",
"wifi/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl",
"wifi/java/android/net/wifi/aware/IWifiAwareEventCallback.aidl",
diff --git a/api/current.txt b/api/current.txt
index 06f3b2a..b90d80e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -211,6 +211,7 @@
field public static final int accessibilityEventTypes = 16843648; // 0x1010380
field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
field public static final int accessibilityFlags = 16843652; // 0x1010384
+ field public static final int accessibilityHeading = 16844160; // 0x1010580
field public static final int accessibilityLiveRegion = 16843758; // 0x10103ee
field public static final int accessibilityPaneTitle = 16844156; // 0x101057c
field public static final int accessibilityTraversalAfter = 16843986; // 0x10104d2
@@ -48201,6 +48202,7 @@
method public boolean isEnabled();
method public boolean isFocusable();
method public boolean isFocused();
+ method public boolean isHeading();
method public boolean isImportantForAccessibility();
method public boolean isLongClickable();
method public boolean isMultiLine();
@@ -48244,6 +48246,7 @@
method public void setError(java.lang.CharSequence);
method public void setFocusable(boolean);
method public void setFocused(boolean);
+ method public void setHeading(boolean);
method public void setHintText(java.lang.CharSequence);
method public void setImportantForAccessibility(boolean);
method public void setInputType(int);
@@ -48377,7 +48380,7 @@
method public int getColumnSpan();
method public int getRowIndex();
method public int getRowSpan();
- method public boolean isHeading();
+ method public deprecated boolean isHeading();
method public boolean isSelected();
method public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean);
method public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean, boolean);
@@ -52602,6 +52605,7 @@
method public android.graphics.Typeface getTypeface();
method public android.text.style.URLSpan[] getUrls();
method public boolean hasSelection();
+ method public boolean isAccessibilityHeading();
method public boolean isAllCaps();
method public boolean isCursorVisible();
method public boolean isElegantTextHeight();
@@ -52624,6 +52628,7 @@
method protected void onTextChanged(java.lang.CharSequence, int, int, int);
method public boolean onTextContextMenuItem(int);
method public void removeTextChangedListener(android.text.TextWatcher);
+ method public void setAccessibilityHeading(boolean);
method public void setAllCaps(boolean);
method public final void setAutoLinkMask(int);
method public void setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int);
diff --git a/api/system-current.txt b/api/system-current.txt
index a29d3a0..87cc6b5 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -790,7 +790,9 @@
field public static final java.lang.String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART";
field public static final java.lang.String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE";
field public static final java.lang.String ACTION_REVIEW_PERMISSIONS = "android.intent.action.REVIEW_PERMISSIONS";
- field public static final java.lang.String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
+ field public static final java.lang.String ACTION_SIM_APPLICATION_STATE_CHANGED = "android.intent.action.SIM_APPLICATION_STATE_CHANGED";
+ field public static final java.lang.String ACTION_SIM_CARD_STATE_CHANGED = "android.intent.action.SIM_CARD_STATE_CHANGED";
+ field public static final deprecated java.lang.String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
field public static final java.lang.String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
field public static final java.lang.String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED";
field public static final java.lang.String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
@@ -3856,6 +3858,28 @@
}
+package android.service.autofill {
+
+ public abstract class AutofillFieldClassificationService extends android.app.Service {
+ method public android.os.IBinder onBind(android.content.Intent);
+ method public java.util.List<java.lang.String> onGetAvailableAlgorithms();
+ method public java.lang.String onGetDefaultAlgorithm();
+ method public android.service.autofill.AutofillFieldClassificationService.Scores onGetScores(java.lang.String, android.os.Bundle, java.util.List<android.view.autofill.AutofillValue>, java.util.List<java.lang.String>);
+ field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutofillFieldClassificationService";
+ }
+
+ public static final class AutofillFieldClassificationService.Scores implements android.os.Parcelable {
+ ctor public AutofillFieldClassificationService.Scores(java.lang.String, int, int);
+ ctor public AutofillFieldClassificationService.Scores(android.os.Parcel);
+ method public int describeContents();
+ method public java.lang.String getAlgorithm();
+ method public float[][] getScores();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.AutofillFieldClassificationService.Scores> CREATOR;
+ }
+
+}
+
package android.service.notification {
public final class Adjustment implements android.os.Parcelable {
@@ -4438,6 +4462,8 @@
method public deprecated boolean getDataEnabled();
method public deprecated boolean getDataEnabled(int);
method public boolean getEmergencyCallbackMode();
+ method public int getSimApplicationState();
+ method public int getSimCardState();
method public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
method public android.os.Bundle getVisualVoicemailSettings();
method public int getVoiceActivationState();
@@ -4471,6 +4497,7 @@
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
+ field public static final java.lang.String EXTRA_SIM_STATE = "android.telephony.extra.SIM_STATE";
field public static final java.lang.String EXTRA_VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL = "android.telephony.extra.VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL";
field public static final java.lang.String EXTRA_VOICEMAIL_SCRAMBLED_PIN_STRING = "android.telephony.extra.VOICEMAIL_SCRAMBLED_PIN_STRING";
field public static final int SIM_ACTIVATION_STATE_ACTIVATED = 2; // 0x2
@@ -4478,6 +4505,8 @@
field public static final int SIM_ACTIVATION_STATE_DEACTIVATED = 3; // 0x3
field public static final int SIM_ACTIVATION_STATE_RESTRICTED = 4; // 0x4
field public static final int SIM_ACTIVATION_STATE_UNKNOWN = 0; // 0x0
+ field public static final int SIM_STATE_LOADED = 10; // 0xa
+ field public static final int SIM_STATE_PRESENT = 11; // 0xb
}
public abstract class VisualVoicemailService extends android.app.Service {
diff --git a/api/test-current.txt b/api/test-current.txt
index 6369bb4..acc819e 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -563,11 +563,6 @@
method public void apply(android.service.autofill.ValueFinder, android.widget.RemoteViews, int) throws java.lang.Exception;
}
- public final class EditDistanceScorer {
- method public static android.service.autofill.EditDistanceScorer getInstance();
- method public float getScore(android.view.autofill.AutofillValue, java.lang.String);
- }
-
public final class FillResponse implements android.os.Parcelable {
method public int getFlags();
}
@@ -966,6 +961,9 @@
public final class Choreographer {
method public static long getFrameDelay();
+ method public void postCallback(int, java.lang.Runnable, java.lang.Object);
+ method public void postCallbackDelayed(int, java.lang.Runnable, java.lang.Object, long);
+ method public void removeCallbacks(int, java.lang.Runnable, java.lang.Object);
method public static void setFrameDelay(long);
field public static final int CALLBACK_ANIMATION = 1; // 0x1
}
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index 9bddbcb..5eff548 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -134,7 +134,7 @@
LOCAL_MODULE_CLASS := EXECUTABLES
-LOCAL_INIT_RC := statsd.rc
+#LOCAL_INIT_RC := statsd.rc
include $(BUILD_EXECUTABLE)
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index 2525721..7a1bb0c 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -19,6 +19,7 @@
#include "SimpleConditionTracker.h"
#include "guardrail/StatsdStats.h"
+#include "dimension.h"
#include <log/logprint.h>
@@ -171,12 +172,12 @@
// We get a new output key.
newCondition = matchStart ? ConditionState::kTrue : ConditionState::kFalse;
if (matchStart && mInitialValue != ConditionState::kTrue) {
- mSlicedConditionState[outputKey] = 1;
+ mSlicedConditionState.insert(std::make_pair(outputKey, 1));
changed = true;
} else if (mInitialValue != ConditionState::kFalse) {
// it's a stop and we don't have history about it.
// If the default condition is not false, it means this stop is valuable to us.
- mSlicedConditionState[outputKey] = 0;
+ mSlicedConditionState.insert(std::make_pair(outputKey, 0));
changed = true;
}
} else {
@@ -341,7 +342,23 @@
conditionState = conditionState |
(startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse);
} else {
- conditionState = conditionState | mInitialValue;
+ // For unseen key, check whether the require dimensions are subset of sliced condition
+ // output.
+ bool seenDimension = false;
+ for (const auto& slice : mSlicedConditionState) {
+ if (IsSubDimension(slice.first.getDimensionsValue(),
+ key.getDimensionsValue())) {
+ seenDimension = true;
+ conditionState = conditionState |
+ (slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse);
+ }
+ if (conditionState == ConditionState::kTrue) {
+ break;
+ }
+ }
+ if (!seenDimension) {
+ conditionState = conditionState | mInitialValue;
+ }
}
}
conditionCache[mIndex] = conditionState;
diff --git a/cmds/statsd/src/dimension.cpp b/cmds/statsd/src/dimension.cpp
index 09499b6..bb7a044 100644
--- a/cmds/statsd/src/dimension.cpp
+++ b/cmds/statsd/src/dimension.cpp
@@ -331,13 +331,15 @@
case DimensionsValue::ValueCase::kValueFloat:
return dimension.value_float() == sub.value_float();
case DimensionsValue::ValueCase::kValueTuple: {
- if (dimension.value_tuple().dimensions_value_size() < sub.value_tuple().dimensions_value_size()) {
+ if (dimension.value_tuple().dimensions_value_size() <
+ sub.value_tuple().dimensions_value_size()) {
return false;
}
bool allSub = true;
- for (int i = 0; i < sub.value_tuple().dimensions_value_size(); ++i) {
+ for (int i = 0; allSub && i < sub.value_tuple().dimensions_value_size(); ++i) {
bool isSub = false;
- for (int j = 0; !isSub && j < dimension.value_tuple().dimensions_value_size(); ++j) {
+ for (int j = 0; !isSub &&
+ j < dimension.value_tuple().dimensions_value_size(); ++j) {
isSub |= IsSubDimension(dimension.value_tuple().dimensions_value(j),
sub.value_tuple().dimensions_value(i));
}
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index dcb8eed..eefb7dc 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -106,9 +106,9 @@
t->set_uid(uid[j]);
}
mBytesUsed += snapshot->ByteSize();
+ ensureBytesUsedBelowLimit();
StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
StatsdStats::getInstance().setUidMapSnapshots(mOutput.snapshots_size());
- ensureBytesUsedBelowLimit();
getListenerListCopyLocked(&broadcastList);
}
// To avoid invoking callback while holding the internal lock. we get a copy of the listener
@@ -142,9 +142,9 @@
log->set_uid(uid);
log->set_version(versionCode);
mBytesUsed += log->ByteSize();
+ ensureBytesUsedBelowLimit();
StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
StatsdStats::getInstance().setUidMapChanges(mOutput.changes_size());
- ensureBytesUsedBelowLimit();
auto range = mMap.equal_range(int(uid));
bool found = false;
@@ -222,9 +222,9 @@
log->set_app(app);
log->set_uid(uid);
mBytesUsed += log->ByteSize();
+ ensureBytesUsedBelowLimit();
StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
StatsdStats::getInstance().setUidMapChanges(mOutput.changes_size());
- ensureBytesUsedBelowLimit();
auto range = mMap.equal_range(int(uid));
for (auto it = range.first; it != range.second; ++it) {
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index 4e37977..3304f6c 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -152,9 +152,9 @@
// until the memory consumed by mOutput is below the specified limit.
void ensureBytesUsedBelowLimit();
- // Override used for testing the max memory allowed by uid map. -1 means we use the value
+ // Override used for testing the max memory allowed by uid map. 0 means we use the value
// specified in StatsdStats.h with the rest of the guardrails.
- size_t maxBytesOverride = -1;
+ size_t maxBytesOverride = 0;
// Cache the size of mOutput;
size_t mBytesUsed;
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index 393f795..bb2193f 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -58,9 +58,9 @@
message CountMetricData {
optional DimensionsValue dimensions_in_what = 1;
- optional DimensionsValue dimensions_in_condition = 3;
+ optional DimensionsValue dimensions_in_condition = 2;
- repeated CountBucketInfo bucket_info = 2;
+ repeated CountBucketInfo bucket_info = 3;
}
message DurationBucketInfo {
@@ -74,9 +74,9 @@
message DurationMetricData {
optional DimensionsValue dimensions_in_what = 1;
- optional DimensionsValue dimensions_in_condition = 3;
+ optional DimensionsValue dimensions_in_condition = 2;
- repeated DurationBucketInfo bucket_info = 2;
+ repeated DurationBucketInfo bucket_info = 3;
}
message ValueBucketInfo {
@@ -90,9 +90,9 @@
message ValueMetricData {
optional DimensionsValue dimensions_in_what = 1;
- optional DimensionsValue dimensions_in_condition = 3;
+ optional DimensionsValue dimensions_in_condition = 2;
- repeated ValueBucketInfo bucket_info = 2;
+ repeated ValueBucketInfo bucket_info = 3;
}
message GaugeBucketInfo {
@@ -106,9 +106,9 @@
message GaugeMetricData {
optional DimensionsValue dimensions_in_what = 1;
- optional DimensionsValue dimensions_in_condition = 3;
+ optional DimensionsValue dimensions_in_condition = 2;
- repeated GaugeBucketInfo bucket_info = 2;
+ repeated GaugeBucketInfo bucket_info = 3;
}
message UidMapping {
diff --git a/cmds/statsd/statsd.rc b/cmds/statsd/statsd.rc
index c260ae1..920273b 100644
--- a/cmds/statsd/statsd.rc
+++ b/cmds/statsd/statsd.rc
@@ -16,6 +16,7 @@
class main
user statsd
group statsd log
+ writepid /dev/cpuset/system-background/tasks
on post-fs-data
# Create directory for statsd
diff --git a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
index 178dd71..7512abc 100644
--- a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
@@ -46,7 +46,7 @@
auto isSyncingPredicate = CreateIsSyncingPredicate();
*isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions() =
CreateDimensions(
- android::util::SYNC_STATE_CHANGED, {1 /* uid field */});
+ android::util::SYNC_STATE_CHANGED, {1 /* uid field */, 2 /* name field*/});
auto isInBackgroundPredicate = CreateIsInBackgroundPredicate();
*isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() =
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index 9f1e983..ac6cba1 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -34,6 +34,7 @@
import android.view.SurfaceView;
import android.view.ViewGroup;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import dalvik.system.CloseGuard;
@@ -58,6 +59,8 @@
private StateCallback mActivityViewCallback;
private IInputForwarder mInputForwarder;
+ // Temp container to store view coordinates on screen.
+ private final int[] mLocationOnScreen = new int[2];
private final CloseGuard mGuard = CloseGuard.get();
private boolean mOpened; // Protected by mGuard.
@@ -198,11 +201,30 @@
performRelease();
}
+ /**
+ * Triggers an update of {@link ActivityView}'s location on screen to properly set touch exclude
+ * regions and avoid focus switches by touches on this view.
+ */
+ public void onLocationChanged() {
+ updateLocation();
+ }
+
@Override
public void onLayout(boolean changed, int l, int t, int r, int b) {
mSurfaceView.layout(0 /* left */, 0 /* top */, r - l /* right */, b - t /* bottom */);
}
+ /** Send current location and size to the WM to set tap exclude region for this view. */
+ private void updateLocation() {
+ try {
+ getLocationOnScreen(mLocationOnScreen);
+ WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
+ mLocationOnScreen[0], mLocationOnScreen[1], getWidth(), getHeight());
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
@Override
public boolean onTouchEvent(MotionEvent event) {
return injectInputEvent(event) || super.onTouchEvent(event);
@@ -241,6 +263,7 @@
} else {
mVirtualDisplay.setSurface(surfaceHolder.getSurface());
}
+ updateLocation();
}
@Override
@@ -248,6 +271,7 @@
if (mVirtualDisplay != null) {
mVirtualDisplay.resize(width, height, getBaseDisplayDensity());
}
+ updateLocation();
}
@Override
@@ -257,6 +281,7 @@
if (mVirtualDisplay != null) {
mVirtualDisplay.setSurface(null);
}
+ cleanTapExcludeRegion();
}
}
@@ -290,6 +315,7 @@
if (mInputForwarder != null) {
mInputForwarder = null;
}
+ cleanTapExcludeRegion();
final boolean displayReleased;
if (mVirtualDisplay != null) {
@@ -313,6 +339,17 @@
mOpened = false;
}
+ /** Report to server that tap exclude region on hosting display should be cleared. */
+ private void cleanTapExcludeRegion() {
+ // Update tap exclude region with an empty rect to clean the state on server.
+ try {
+ WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
+ 0 /* left */, 0 /* top */, 0 /* width */, 0 /* height */);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
/** Get density of the hosting display. */
private int getBaseDisplayDensity() {
final WindowManager wm = mContext.getSystemService(WindowManager.class);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 6e99709..f05a4d1 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3516,12 +3516,83 @@
* For more details see TelephonyIntents.ACTION_SIM_STATE_CHANGED. This is here
* because TelephonyIntents is an internal class.
* @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED} or
+ * {@link #ACTION_SIM_APPLICATION_STATE_CHANGED}
*/
+ @Deprecated
@SystemApi
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
/**
+ * Broadcast Action: The sim card state has changed.
+ * The intent will have the following extra values:</p>
+ * <dl>
+ * <dt>{@link android.telephony.TelephonyManager.EXTRA_SIM_STATE}</dt>
+ * <dd>The sim card state. One of:
+ * <dl>
+ * <dt>{@link android.telephony.TelephonyManager.SIM_STATE_ABSENT}</dt>
+ * <dd>SIM card not found</dd>
+ * <dt>{@link android.telephony.TelephonyManager.SIM_STATE_CARD_IO_ERROR}</dt>
+ * <dd>SIM card IO error</dd>
+ * <dt>{@link android.telephony.TelephonyManager.SIM_STATE_CARD_RESTRICTED}</dt>
+ * <dd>SIM card is restricted</dd>
+ * <dt>{@link android.telephony.TelephonyManager.SIM_STATE_PRESENT}</dt>
+ * <dd>SIM card is present</dd>
+ * </dl>
+ * </dd>
+ * </dl>
+ *
+ * <p class="note">Requires the READ_PRIVILEGED_PHONE_STATE permission.
+ *
+ * <p class="note">The current state can also be queried using
+ * {@link android.telephony.TelephonyManager.getSimCardState()}
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SIM_CARD_STATE_CHANGED =
+ "android.intent.action.SIM_CARD_STATE_CHANGED";
+
+ /**
+ * Broadcast Action: The sim application state has changed.
+ * The intent will have the following extra values:</p>
+ * <dl>
+ * <dt>{@link android.telephony.TelephonyManager.EXTRA_SIM_STATE}</dt>
+ * <dd>The sim application state. One of:
+ * <dl>
+ * <dt>{@link android.telephony.TelephonyManager.SIM_STATE_NOT_READY}</dt>
+ * <dd>SIM card applications not ready</dd>
+ * <dt>{@link android.telephony.TelephonyManager.SIM_STATE_PIN_REQUIRED}</dt>
+ * <dd>SIM card PIN locked</dd>
+ * <dt>{@link android.telephony.TelephonyManager.SIM_STATE_PUK_REQUIRED}</dt>
+ * <dd>SIM card PUK locked</dd>
+ * <dt>{@link android.telephony.TelephonyManager.SIM_STATE_NETWORK_LOCKED}</dt>
+ * <dd>SIM card network locked</dd>
+ * <dt>{@link android.telephony.TelephonyManager.SIM_STATE_PERM_DISABLED}</dt>
+ * <dd>SIM card permanently disabled due to PUK failures</dd>
+ * <dt>{@link android.telephony.TelephonyManager.SIM_STATE_LOADED}</dt>
+ * <dd>SIM card data loaded</dd>
+ * </dl>
+ * </dd>
+ * </dl>
+ *
+ * <p class="note">Requires the READ_PRIVILEGED_PHONE_STATE permission.
+ *
+ * <p class="note">The current state can also be queried using
+ * {@link android.telephony.TelephonyManager.getSimApplicationState()}
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SIM_APPLICATION_STATE_CHANGED =
+ "android.intent.action.SIM_APPLICATION_STATE_CHANGED";
+
+ /**
* Broadcast Action: indicate that the phone service state has changed.
* The intent will have the following extra values:</p>
* <p>
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 396c897..956e8f6 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9861,15 +9861,6 @@
public static final String FORCED_APP_STANDBY_ENABLED = "forced_app_standby_enabled";
/**
- * Whether or not to enable Forced App Standby on small battery devices.
- * Type: int (0 for false, 1 for true)
- * Default: 0
- * @hide
- */
- public static final String FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED
- = "forced_app_standby_for_small_battery_enabled";
-
- /**
* Whether or not Network Watchlist feature is enabled.
* Type: int (0 for false, 1 for true)
* Default: 0
diff --git a/core/java/android/service/autofill/AutofillFieldClassificationService.java b/core/java/android/service/autofill/AutofillFieldClassificationService.java
new file mode 100644
index 0000000..18f6dab
--- /dev/null
+++ b/core/java/android/service/autofill/AutofillFieldClassificationService.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.autofill;
+
+import static android.view.autofill.AutofillManager.EXTRA_AVAILABLE_ALGORITHMS;
+import static android.view.autofill.AutofillManager.EXTRA_DEFAULT_ALGORITHM;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.autofill.AutofillValue;
+
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A service that calculates field classification scores.
+ *
+ * <p>A field classification score is a {@code float} representing how well an
+ * {@link AutofillValue} filled matches a expected value predicted by an autofill service
+ * —a full-match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}.
+ *
+ * <p>The exact score depends on the algorithm used to calculate it— the service must provide
+ * at least one default algorithm (which is used when the algorithm is not specified or is invalid),
+ * but it could provide more (in which case the algorithm name should be specifiied by the caller
+ * when calculating the scores).
+ *
+ * {@hide}
+ */
+@SystemApi
+public abstract class AutofillFieldClassificationService extends Service {
+
+ private static final String TAG = "AutofillFieldClassificationService";
+
+ private static final int MSG_GET_AVAILABLE_ALGORITHMS = 1;
+ private static final int MSG_GET_DEFAULT_ALGORITHM = 2;
+ private static final int MSG_GET_SCORES = 3;
+
+ /**
+ * The {@link Intent} action that must be declared as handled by a service
+ * in its manifest for the system to recognize it as a quota providing service.
+ */
+ public static final String SERVICE_INTERFACE =
+ "android.service.autofill.AutofillFieldClassificationService";
+
+ /** {@hide} **/
+ public static final String EXTRA_SCORES = "scores";
+
+ private AutofillFieldClassificationServiceWrapper mWrapper;
+
+ private final HandlerCaller.Callback mHandlerCallback = (msg) -> {
+ final int action = msg.what;
+ final Bundle data = new Bundle();
+ final RemoteCallback callback;
+ switch (action) {
+ case MSG_GET_AVAILABLE_ALGORITHMS:
+ callback = (RemoteCallback) msg.obj;
+ final List<String> availableAlgorithms = onGetAvailableAlgorithms();
+ String[] asArray = null;
+ if (availableAlgorithms != null) {
+ asArray = new String[availableAlgorithms.size()];
+ availableAlgorithms.toArray(asArray);
+ }
+ data.putStringArray(EXTRA_AVAILABLE_ALGORITHMS, asArray);
+ break;
+ case MSG_GET_DEFAULT_ALGORITHM:
+ callback = (RemoteCallback) msg.obj;
+ final String defaultAlgorithm = onGetDefaultAlgorithm();
+ data.putString(EXTRA_DEFAULT_ALGORITHM, defaultAlgorithm);
+ break;
+ case MSG_GET_SCORES:
+ final SomeArgs args = (SomeArgs) msg.obj;
+ callback = (RemoteCallback) args.arg1;
+ final String algorithmName = (String) args.arg2;
+ final Bundle algorithmArgs = (Bundle) args.arg3;
+ @SuppressWarnings("unchecked")
+ final List<AutofillValue> actualValues = ((List<AutofillValue>) args.arg4);
+ @SuppressWarnings("unchecked")
+ final String[] userDataValues = (String[]) args.arg5;
+ final Scores scores = onGetScores(algorithmName, algorithmArgs, actualValues,
+ Arrays.asList(userDataValues));
+ data.putParcelable(EXTRA_SCORES, scores);
+ break;
+ default:
+ Log.w(TAG, "Handling unknown message: " + action);
+ return;
+ }
+ callback.sendResult(data);
+ };
+
+ private final HandlerCaller mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(),
+ mHandlerCallback, true);
+
+ /** @hide */
+ public AutofillFieldClassificationService() {
+
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mWrapper = new AutofillFieldClassificationServiceWrapper();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mWrapper;
+ }
+
+ /**
+ * Gets the name of all available algorithms.
+ *
+ * @throws UnsupportedOperationException if not implemented by service.
+ */
+ // TODO(b/70939974): rename to onGetAvailableAlgorithms if not removed
+ @NonNull
+ public List<String> onGetAvailableAlgorithms() {
+ throw new UnsupportedOperationException("Must be implemented by external service");
+ }
+
+ /**
+ * Gets the default algorithm that's used when an algorithm is not specified or is invalid.
+ *
+ * @throws UnsupportedOperationException if not implemented by service.
+ */
+ @NonNull
+ public String onGetDefaultAlgorithm() {
+ throw new UnsupportedOperationException("Must be implemented by external service");
+ }
+
+ /**
+ * Calculates field classification scores in a batch.
+ *
+ * <p>See {@link AutofillFieldClassificationService} for more info about field classification
+ * scores.
+ *
+ * @param algorithm name of the algorithm to be used to calculate the scores. If invalid, the
+ * default algorithm will be used instead.
+ * @param args optional arguments to be passed to the algorithm.
+ * @param actualValues values entered by the user.
+ * @param userDataValues values predicted from the user data.
+ * @return the calculated scores and the algorithm used.
+ *
+ * {@hide}
+ */
+ @Nullable
+ @SystemApi
+ public Scores onGetScores(@Nullable String algorithm,
+ @Nullable Bundle args, @NonNull List<AutofillValue> actualValues,
+ @NonNull List<String> userDataValues) {
+ throw new UnsupportedOperationException("Must be implemented by external service");
+ }
+
+ private final class AutofillFieldClassificationServiceWrapper
+ extends IAutofillFieldClassificationService.Stub {
+
+ @Override
+ public void getAvailableAlgorithms(RemoteCallback callback) throws RemoteException {
+ mHandlerCaller.obtainMessageO(MSG_GET_AVAILABLE_ALGORITHMS, callback).sendToTarget();
+ }
+
+ @Override
+ public void getDefaultAlgorithm(RemoteCallback callback) throws RemoteException {
+ mHandlerCaller.obtainMessageO(MSG_GET_DEFAULT_ALGORITHM, callback).sendToTarget();
+ }
+
+ @Override
+ public void getScores(RemoteCallback callback, String algorithmName, Bundle algorithmArgs,
+ List<AutofillValue> actualValues, String[] userDataValues)
+ throws RemoteException {
+ // TODO(b/70939974): refactor to use PooledLambda
+ mHandlerCaller.obtainMessageOOOOO(MSG_GET_SCORES, callback, algorithmName,
+ algorithmArgs, actualValues, userDataValues).sendToTarget();
+ }
+ }
+
+
+ // TODO(b/70939974): it might be simpler to remove this class and return the float[][] directly,
+ // ignoring the request if the algorithm name is invalid.
+ /**
+ * Represents field classification scores used in a batch calculation.
+ *
+ * {@hide}
+ */
+ @SystemApi
+ public static final class Scores implements Parcelable {
+ private final String mAlgorithmName;
+ private final float[][] mScores;
+
+ /* @hide */
+ public Scores(String algorithmName, int size1, int size2) {
+ mAlgorithmName = algorithmName;
+ mScores = new float[size1][size2];
+ }
+
+ public Scores(Parcel parcel) {
+ mAlgorithmName = parcel.readString();
+ final int size1 = parcel.readInt();
+ final int size2 = parcel.readInt();
+ mScores = new float[size1][size2];
+ for (int i = 0; i < size1; i++) {
+ for (int j = 0; j < size2; j++) {
+ mScores[i][j] = parcel.readFloat();
+ }
+ }
+ }
+
+ /**
+ * Gets the name of algorithm used to calculate the score.
+ */
+ @NonNull
+ public String getAlgorithm() {
+ return mAlgorithmName;
+ }
+
+ /**
+ * Gets the resulting scores, with the 1st dimension representing actual values and the 2nd
+ * dimension values from {@link UserData}.
+ */
+ @NonNull
+ public float[][] getScores() {
+ return mScores;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(mAlgorithmName);
+ int size1 = mScores.length;
+ int size2 = mScores[0].length;
+ parcel.writeInt(size1);
+ parcel.writeInt(size2);
+ for (int i = 0; i < size1; i++) {
+ for (int j = 0; j < size2; j++) {
+ parcel.writeFloat(mScores[i][j]);
+ }
+ }
+ }
+
+ public static final Creator<Scores> CREATOR = new Creator<Scores>() {
+
+ @Override
+ public Scores createFromParcel(Parcel parcel) {
+ return new Scores(parcel);
+ }
+
+ @Override
+ public Scores[] newArray(int size) {
+ return new Scores[size];
+ }
+
+ };
+ }
+}
diff --git a/core/java/android/service/autofill/CharSequenceTransformation.java b/core/java/android/service/autofill/CharSequenceTransformation.java
index 2413e97..f52ac85 100644
--- a/core/java/android/service/autofill/CharSequenceTransformation.java
+++ b/core/java/android/service/autofill/CharSequenceTransformation.java
@@ -22,7 +22,6 @@
import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import android.view.autofill.AutofillId;
@@ -31,6 +30,8 @@
import com.android.internal.util.Preconditions;
+import java.util.LinkedHashMap;
+import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -62,7 +63,9 @@
public final class CharSequenceTransformation extends InternalTransformation implements
Transformation, Parcelable {
private static final String TAG = "CharSequenceTransformation";
- @NonNull private final ArrayMap<AutofillId, Pair<Pattern, String>> mFields;
+
+ // Must use LinkedHashMap to preserve insertion order.
+ @NonNull private final LinkedHashMap<AutofillId, Pair<Pattern, String>> mFields;
private CharSequenceTransformation(Builder builder) {
mFields = builder.mFields;
@@ -76,9 +79,9 @@
final StringBuilder converted = new StringBuilder();
final int size = mFields.size();
if (sDebug) Log.d(TAG, size + " multiple fields on id " + childViewId);
- for (int i = 0; i < size; i++) {
- final AutofillId id = mFields.keyAt(i);
- final Pair<Pattern, String> field = mFields.valueAt(i);
+ for (Entry<AutofillId, Pair<Pattern, String>> entry : mFields.entrySet()) {
+ final AutofillId id = entry.getKey();
+ final Pair<Pattern, String> field = entry.getValue();
final String value = finder.findByAutofillId(id);
if (value == null) {
Log.w(TAG, "No value for id " + id);
@@ -107,8 +110,10 @@
* Builder for {@link CharSequenceTransformation} objects.
*/
public static class Builder {
- @NonNull private final ArrayMap<AutofillId, Pair<Pattern, String>> mFields =
- new ArrayMap<>();
+
+ // Must use LinkedHashMap to preserve insertion order.
+ @NonNull private final LinkedHashMap<AutofillId, Pair<Pattern, String>> mFields =
+ new LinkedHashMap<>();
private boolean mDestroyed;
/**
@@ -186,12 +191,15 @@
final Pattern[] regexs = new Pattern[size];
final String[] substs = new String[size];
Pair<Pattern, String> pair;
- for (int i = 0; i < size; i++) {
- ids[i] = mFields.keyAt(i);
- pair = mFields.valueAt(i);
+ int i = 0;
+ for (Entry<AutofillId, Pair<Pattern, String>> entry : mFields.entrySet()) {
+ ids[i] = entry.getKey();
+ pair = entry.getValue();
regexs[i] = pair.first;
substs[i] = pair.second;
+ i++;
}
+
parcel.writeParcelableArray(ids, flags);
parcel.writeSerializable(regexs);
parcel.writeStringArray(substs);
diff --git a/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl b/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl
new file mode 100644
index 0000000..d8e829d
--- /dev/null
+++ b/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import android.os.Bundle;
+import android.os.RemoteCallback;
+import android.view.autofill.AutofillValue;
+import java.util.List;
+
+/**
+ * Service used to calculate match scores for Autofill Field Classification.
+ *
+ * @hide
+ */
+oneway interface IAutofillFieldClassificationService {
+ void getAvailableAlgorithms(in RemoteCallback callback);
+ void getDefaultAlgorithm(in RemoteCallback callback);
+ void getScores(in RemoteCallback callback, String algorithmName, in Bundle algorithmArgs,
+ in List<AutofillValue> actualValues, in String[] userDataValues);
+}
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 33fbf73..1caea57 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -373,6 +373,7 @@
* @see #removeCallbacks
* @hide
*/
+ @TestApi
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
@@ -391,6 +392,7 @@
* @see #removeCallback
* @hide
*/
+ @TestApi
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
if (action == null) {
@@ -440,6 +442,7 @@
* @see #postCallbackDelayed
* @hide
*/
+ @TestApi
public void removeCallbacks(int callbackType, Runnable action, Object token) {
if (callbackType < 0 || callbackType > CALLBACK_LAST) {
throw new IllegalArgumentException("callbackType is invalid");
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index ed167c8..17b6ddc 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -29,6 +29,7 @@
import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.Surface;
+import android.view.SurfaceControl;
/**
* System private per-application interface to the window manager.
@@ -150,25 +151,32 @@
boolean performHapticFeedback(IWindow window, int effectId, boolean always);
/**
- * Allocate the drag's thumbnail surface. Also assigns a token that identifies
- * the drag to the OS and passes that as the return value. A return value of
- * null indicates failure.
- */
- IBinder prepareDrag(IWindow window, int flags,
- int thumbnailWidth, int thumbnailHeight, out Surface outSurface);
-
- /**
* Initiate the drag operation itself
+ *
+ * @param window Window which initiates drag operation.
+ * @param flags See {@code View#startDragAndDrop}
+ * @param surface Surface containing drag shadow image
+ * @param touchSource See {@code InputDevice#getSource()}
+ * @param touchX TODO (b/72072998): Fix the issue that the system server misuse the arguments as
+ * initial touch point while the framework passes drag shadow size.
+ * @param touchY TODO (b/72072998): Fix the issue that the system server misuse the arguments as
+ * initial touch point while the framework passes drag shadow size.
+ * @param thumbCenterX X coordinate for the position within the shadow image that should be
+ * underneath the touch point during the drag and drop operation.
+ * @param thumbCenterY Y coordinate for the position within the shadow image that should be
+ * underneath the touch point during the drag and drop operation.
+ * @param data Data transferred by drag and drop
+ * @return Token of drag operation which will be passed to cancelDragAndDrop.
*/
- boolean performDrag(IWindow window, IBinder dragToken, int touchSource,
+ IBinder performDrag(IWindow window, int flags, in SurfaceControl surface, int touchSource,
float touchX, float touchY, float thumbCenterX, float thumbCenterY, in ClipData data);
- /**
+ /**
* Report the result of a drop action targeted to the given window.
* consumed is 'true' when the drop was accepted by a valid recipient,
* 'false' otherwise.
*/
- void reportDropResult(IWindow window, boolean consumed);
+ void reportDropResult(IWindow window, boolean consumed);
/**
* Cancel the current drag operation.
@@ -236,4 +244,12 @@
boolean startMovingTask(IWindow window, float startX, float startY);
void updatePointerIcon(IWindow window);
+
+ /**
+ * Update a tap exclude region with a rectangular area identified by provided id in the window.
+ * Touches on this region will not switch focus to this window. Passing an empty rect will
+ * remove the area from the exclude region of this window.
+ */
+ void updateTapExcludeRegion(IWindow window, int regionId, int left, int top, int width,
+ int height);
}
diff --git a/core/java/android/view/SurfaceControl.aidl b/core/java/android/view/SurfaceControl.aidl
new file mode 100644
index 0000000..744ead2
--- /dev/null
+++ b/core/java/android/view/SurfaceControl.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+parcelable SurfaceControl;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f461e45..05770c3 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -17886,6 +17886,15 @@
}
/**
+ * Return the window this view is currently attached to. Used in
+ * {@link android.app.ActivityView} to communicate with WM.
+ * @hide
+ */
+ protected IWindow getWindow() {
+ return mAttachInfo != null ? mAttachInfo.mWindow : null;
+ }
+
+ /**
* Return the visibility value of the least visible component passed.
*/
int combineVisibility(int vis1, int vis2) {
@@ -23553,15 +23562,13 @@
data.prepareToLeaveProcess((flags & View.DRAG_FLAG_GLOBAL) != 0);
}
- boolean okay = false;
-
Point shadowSize = new Point();
Point shadowTouchPoint = new Point();
shadowBuilder.onProvideShadowMetrics(shadowSize, shadowTouchPoint);
- if ((shadowSize.x < 0) || (shadowSize.y < 0) ||
- (shadowTouchPoint.x < 0) || (shadowTouchPoint.y < 0)) {
- throw new IllegalStateException("Drag shadow dimensions must not be negative");
+ if ((shadowSize.x <= 0) || (shadowSize.y <= 0)
+ || (shadowTouchPoint.x < 0) || (shadowTouchPoint.y < 0)) {
+ throw new IllegalStateException("Drag shadow dimensions must be positive");
}
if (ViewDebug.DEBUG_DRAG) {
@@ -23572,40 +23579,50 @@
mAttachInfo.mDragSurface.release();
}
mAttachInfo.mDragSurface = new Surface();
+ mAttachInfo.mDragToken = null;
+
+ final ViewRootImpl root = mAttachInfo.mViewRootImpl;
+ final SurfaceSession session = new SurfaceSession(root.mSurface);
+ final SurfaceControl surface = new SurfaceControl.Builder(session)
+ .setName("drag surface")
+ .setSize(shadowSize.x, shadowSize.y)
+ .setFormat(PixelFormat.TRANSLUCENT)
+ .build();
try {
- mAttachInfo.mDragToken = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
- flags, shadowSize.x, shadowSize.y, mAttachInfo.mDragSurface);
- if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token="
- + mAttachInfo.mDragToken + " surface=" + mAttachInfo.mDragSurface);
- if (mAttachInfo.mDragToken != null) {
- Canvas canvas = mAttachInfo.mDragSurface.lockCanvas(null);
- try {
- canvas.drawColor(0, PorterDuff.Mode.CLEAR);
- shadowBuilder.onDrawShadow(canvas);
- } finally {
- mAttachInfo.mDragSurface.unlockCanvasAndPost(canvas);
- }
-
- final ViewRootImpl root = getViewRootImpl();
-
- // Cache the local state object for delivery with DragEvents
- root.setLocalDragState(myLocalState);
-
- // repurpose 'shadowSize' for the last touch point
- root.getLastTouchPoint(shadowSize);
-
- okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, mAttachInfo.mDragToken,
- root.getLastTouchSource(), shadowSize.x, shadowSize.y,
- shadowTouchPoint.x, shadowTouchPoint.y, data);
- if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "performDrag returned " + okay);
+ mAttachInfo.mDragSurface.copyFrom(surface);
+ final Canvas canvas = mAttachInfo.mDragSurface.lockCanvas(null);
+ try {
+ canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+ shadowBuilder.onDrawShadow(canvas);
+ } finally {
+ mAttachInfo.mDragSurface.unlockCanvasAndPost(canvas);
}
+
+ // Cache the local state object for delivery with DragEvents
+ root.setLocalDragState(myLocalState);
+
+ // repurpose 'shadowSize' for the last touch point
+ root.getLastTouchPoint(shadowSize);
+
+ mAttachInfo.mDragToken = mAttachInfo.mSession.performDrag(
+ mAttachInfo.mWindow, flags, surface, root.getLastTouchSource(),
+ shadowSize.x, shadowSize.y, shadowTouchPoint.x, shadowTouchPoint.y, data);
+ if (ViewDebug.DEBUG_DRAG) {
+ Log.d(VIEW_LOG_TAG, "performDrag returned " + mAttachInfo.mDragToken);
+ }
+
+ return mAttachInfo.mDragToken != null;
} catch (Exception e) {
Log.e(VIEW_LOG_TAG, "Unable to initiate drag", e);
- mAttachInfo.mDragSurface.destroy();
- mAttachInfo.mDragSurface = null;
+ return false;
+ } finally {
+ if (mAttachInfo.mDragToken == null) {
+ mAttachInfo.mDragSurface.destroy();
+ mAttachInfo.mDragSurface = null;
+ root.setLocalDragState(null);
+ }
+ session.kill();
}
-
- return okay;
}
/**
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 311dd4b..3ee282e 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -639,6 +639,8 @@
private static final int BOOLEAN_PROPERTY_IS_SHOWING_HINT = 0x0100000;
+ private static final int BOOLEAN_PROPERTY_IS_HEADING = 0x0200000;
+
/**
* Bits that provide the id of a virtual descendant of a view.
*/
@@ -2409,6 +2411,30 @@
}
/**
+ * Returns whether node represents a heading.
+ *
+ * @return {@code true} if the node is a heading, {@code false} otherwise.
+ */
+ public boolean isHeading() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_IS_HEADING);
+ }
+
+ /**
+ * Sets whether the node represents a heading.
+ *
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param isHeading {@code true} if the node is a heading, {@code false} otherwise.
+ */
+ public void setHeading(boolean isHeading) {
+ setBooleanProperty(BOOLEAN_PROPERTY_IS_HEADING, isHeading);
+ }
+
+ /**
* Gets the package this node comes from.
*
* @return The package name.
@@ -4597,7 +4623,8 @@
* @param rowSpan The number of rows the item spans.
* @param columnIndex The column index at which the item is located.
* @param columnSpan The number of columns the item spans.
- * @param heading Whether the item is a heading.
+ * @param heading Whether the item is a heading. (Prefer
+ * {@link AccessibilityNodeInfo#setHeading(boolean)}).
*/
public static CollectionItemInfo obtain(int rowIndex, int rowSpan,
int columnIndex, int columnSpan, boolean heading) {
@@ -4611,7 +4638,8 @@
* @param rowSpan The number of rows the item spans.
* @param columnIndex The column index at which the item is located.
* @param columnSpan The number of columns the item spans.
- * @param heading Whether the item is a heading.
+ * @param heading Whether the item is a heading. (Prefer
+ * {@link AccessibilityNodeInfo#setHeading(boolean)})
* @param selected Whether the item is selected.
*/
public static CollectionItemInfo obtain(int rowIndex, int rowSpan,
@@ -4698,6 +4726,7 @@
* heading, table header, etc.
*
* @return If the item is a heading.
+ * @deprecated Use {@link AccessibilityNodeInfo#isHeading()}
*/
public boolean isHeading() {
return mHeading;
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index de9b0d7..f54561b 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -33,6 +33,7 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcelable;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.service.autofill.AutofillService;
import android.service.autofill.FillEventHistory;
@@ -53,9 +54,12 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
// TODO: use java.lang.ref.Cleaner once Android supports Java 9
import sun.misc.Cleaner;
@@ -169,11 +173,15 @@
public static final String EXTRA_CLIENT_STATE =
"android.view.autofill.extra.CLIENT_STATE";
-
/** @hide */
public static final String EXTRA_RESTORE_SESSION_TOKEN =
"android.view.autofill.extra.RESTORE_SESSION_TOKEN";
+ /** @hide */
+ public static final String EXTRA_AVAILABLE_ALGORITHMS = "available_algorithms";
+ /** @hide */
+ public static final String EXTRA_DEFAULT_ALGORITHM = "default_algorithm";
+
private static final String SESSION_ID_TAG = "android:sessionId";
private static final String STATE_TAG = "android:state";
private static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData";
@@ -259,6 +267,12 @@
public static final int STATE_DISABLED_BY_SERVICE = 4;
/**
+ * Timeout in ms for calls to the field classification service.
+ * @hide
+ */
+ public static final int FC_SERVICE_TIMEOUT = 5000;
+
+ /**
* Makes an authentication id from a request id and a dataset id.
*
* @param requestId The request id.
@@ -1160,10 +1174,22 @@
* and it's ignored if the caller currently doesn't have an enabled autofill service for
* the user.
*/
+ // TODO(b/70939974): refactor this method to be "purely" sync by getting the info from the
+ // the ExtService manifest (instead of calling the service)
@Nullable
public String getDefaultFieldClassificationAlgorithm() {
+ final SyncRemoteCallbackListener<String> listener =
+ new SyncRemoteCallbackListener<String>() {
+
+ @Override
+ String getResult(Bundle result) {
+ return result == null ? null : result.getString(EXTRA_DEFAULT_ALGORITHM);
+ }
+ };
+
try {
- return mService.getDefaultFieldClassificationAlgorithm();
+ mService.getDefaultFieldClassificationAlgorithm(new RemoteCallback(listener));
+ return listener.getResult(FC_SERVICE_TIMEOUT);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return null;
@@ -1175,17 +1201,32 @@
* <a href="AutofillService.html#FieldClassification">field classification</a>.
*
* <p><b>Note:</b> This method should only be called by an app providing an autofill service,
- * and it's ignored if the caller currently doesn't have an enabled autofill service for
- * the user.
- *
- * @return list of all algorithms currently available, or an empty list if the caller currently
- * does not have an enabled autofill service for the user.
+ * and it returns an empty list if the caller currently doesn't have an enabled autofill service
+ * for the user.
*/
+ // TODO(b/70939974): refactor this method to be "purely" sync by getting the info from the
+ // the ExtService manifest (instead of calling the service)
@NonNull
public List<String> getAvailableFieldClassificationAlgorithms() {
+ final SyncRemoteCallbackListener<List<String>> listener =
+ new SyncRemoteCallbackListener<List<String>>() {
+
+ @Override
+ List<String> getResult(Bundle result) {
+ List<String> algorithms = null;
+ if (result != null) {
+ final String[] asArray = result.getStringArray(EXTRA_AVAILABLE_ALGORITHMS);
+ if (asArray != null) {
+ algorithms = Arrays.asList(asArray);
+ }
+ }
+ return algorithms != null ? algorithms : Collections.emptyList();
+ }
+ };
+
try {
- final List<String> names = mService.getAvailableFieldClassificationAlgorithms();
- return names != null ? names : Collections.emptyList();
+ mService.getAvailableFieldClassificationAlgorithms(new RemoteCallback(listener));
+ return listener.getResult(FC_SERVICE_TIMEOUT);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return null;
@@ -2281,4 +2322,36 @@
}
}
}
+
+ private abstract static class SyncRemoteCallbackListener<T>
+ implements RemoteCallback.OnResultListener {
+
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private T mResult;
+
+ @Override
+ public void onResult(Bundle result) {
+ if (sVerbose) Log.w(TAG, "SyncRemoteCallbackListener.onResult(): " + result);
+ mResult = getResult(result);
+ mLatch.countDown();
+ }
+
+ T getResult(int timeoutMs) {
+ T result = null;
+ try {
+ if (mLatch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
+ result = mResult;
+ } else {
+ Log.w(TAG, "SyncRemoteCallbackListener not called in " + timeoutMs + "ms");
+ }
+ } catch (InterruptedException e) {
+ Log.w(TAG, "SyncRemoteCallbackListener interrupted: " + e);
+ Thread.currentThread().interrupt();
+ }
+ if (sVerbose) Log.w(TAG, "SyncRemoteCallbackListener: returning " + result);
+ return result;
+ }
+
+ abstract T getResult(Bundle result);
+ }
}
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index 1afa35e..41672e7 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -20,6 +20,7 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.RemoteCallback;
import android.service.autofill.FillEventHistory;
import android.service.autofill.UserData;
import android.view.autofill.AutofillId;
@@ -58,6 +59,6 @@
void setUserData(in UserData userData);
boolean isFieldClassificationEnabled();
ComponentName getAutofillServiceComponentName();
- List<String> getAvailableFieldClassificationAlgorithms();
- String getDefaultFieldClassificationAlgorithm();
+ void getAvailableFieldClassificationAlgorithms(in RemoteCallback callback);
+ void getDefaultFieldClassificationAlgorithm(in RemoteCallback callback);
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index dac6c02..7d3fcf4 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -309,6 +309,7 @@
* @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
* @attr ref android.R.styleable#TextView_autoSizeStepGranularity
* @attr ref android.R.styleable#TextView_autoSizePresetSizes
+ * @attr ref android.R.styleable#TextView_accessibilityHeading
*/
@RemoteView
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
@@ -403,6 +404,7 @@
private int mCurTextColor;
private int mCurHintTextColor;
private boolean mFreezesText;
+ private boolean mIsAccessibilityHeading;
private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
@@ -1267,6 +1269,8 @@
case com.android.internal.R.styleable.TextView_lineHeight:
lineHeight = a.getDimensionPixelSize(attr, -1);
break;
+ case com.android.internal.R.styleable.TextView_accessibilityHeading:
+ mIsAccessibilityHeading = a.getBoolean(attr, false);
}
}
@@ -5128,6 +5132,31 @@
}
/**
+ * Gets whether this view is a heading for accessibility purposes.
+ *
+ * @return {@code true} if the view is a heading, {@code false} otherwise.
+ *
+ * @attr ref android.R.styleable#TextView_accessibilityHeading
+ */
+ public boolean isAccessibilityHeading() {
+ return mIsAccessibilityHeading;
+ }
+
+ /**
+ * Set if view is a heading for a section of content for accessibility purposes.
+ *
+ * @param isHeading {@code true} if the view is a heading, {@code false} otherwise.
+ *
+ * @attr ref android.R.styleable#TextView_accessibilityHeading
+ */
+ public void setAccessibilityHeading(boolean isHeading) {
+ if (isHeading != mIsAccessibilityHeading) {
+ mIsAccessibilityHeading = isHeading;
+ notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ }
+ }
+
+ /**
* Convenience method to append the specified text to the TextView's
* display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
* if it was not already editable.
@@ -10677,6 +10706,7 @@
info.setText(getTextForAccessibility());
info.setHintText(mHint);
info.setShowingHintText(isShowingHint());
+ info.setHeading(mIsAccessibilityHeading);
if (mBufferType == BufferType.EDITABLE) {
info.setEditable(true);
diff --git a/core/proto/android/server/forceappstandbytracker.proto b/core/proto/android/server/forceappstandbytracker.proto
index c9f7d52..8753bf7 100644
--- a/core/proto/android/server/forceappstandbytracker.proto
+++ b/core/proto/android/server/forceappstandbytracker.proto
@@ -41,13 +41,4 @@
// Packages that are disallowed OP_RUN_ANY_IN_BACKGROUND.
repeated RunAnyInBackgroundRestrictedPackages run_any_in_background_restricted_packages = 5;
-
- // Whether device is a small battery device
- optional bool is_small_battery_device = 6;
-
- // Whether force app standby for small battery device setting is enabled
- optional bool force_all_apps_standby_for_small_battery = 7;
-
- // Whether device is charging
- optional bool is_charging = 8;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d2a22d0..547e83c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -554,8 +554,6 @@
<protected-broadcast android:name="android.intent.action.DEVICE_LOCKED_CHANGED" />
<!-- Added in O -->
- <!-- TODO: temporary broadcast used by AutoFillManagerServiceImpl; will be removed -->
- <protected-broadcast android:name="com.android.internal.autofill.action.REQUEST_AUTOFILL" />
<protected-broadcast android:name="android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED" />
<protected-broadcast android:name="com.android.server.wm.ACTION_REVOKE_SYSTEM_ALERT_WINDOW_PERMISSION" />
<protected-broadcast android:name="android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED" />
@@ -2684,6 +2682,13 @@
<permission android:name="android.permission.BIND_AUTOFILL_SERVICE"
android:protectionLevel="signature" />
+ <!-- Must be required by an {@link android.service.autofill.AutofillFieldClassificationService}
+ to ensure that only the system can bind to it.
+ @hide This is not a third-party API (intended for OEMs and system apps).
+ -->
+ <permission android:name="android.permission.BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- Must be required by hotword enrollment application,
to ensure that only the system can interact with it.
@hide <p>Not for use by third-party applications.</p> -->
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index fd33dc9..4eaf93d 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4876,6 +4876,8 @@
<!-- Justification by stretching word spacing. -->
<enum name="inter_word" value = "1" />
</attr>
+ <!-- Whether or not this view is a heading for accessibility purposes. -->
+ <attr name="accessibilityHeading" format="boolean"/>
</declare-styleable>
<declare-styleable name="TextViewAppearance">
<!-- Base text color, typeface, size, and style. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 656add6..9cdf553 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2865,6 +2865,7 @@
<public name="firstBaselineToTopHeight" />
<public name="lastBaselineToBottomHeight" />
<public name="lineHeight" />
+ <public name="accessibilityHeading" />
</public-group>
<public-group type="style" first-id="0x010302e0">
diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
index 417faf2..eaabdc8 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
@@ -505,4 +505,84 @@
assertEquals(GLYPH_3EM_WIDTH, paint.measureText("b"), 0.0f);
assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
}
+
+ @Test
+ public void testBuildSystemFallback_ElegantFallback_customFallback_missingFile() {
+ final String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'>"
+ + " <font weight='400' style='normal'>no_coverage.ttf</font>"
+ + " </family>"
+ + " <family name='serif'>"
+ + " <font weight='400' style='normal'>no_coverage.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font weight='400' style='normal'>a3em.ttf</font>"
+ + " <font weight='400' style='normal' fallbackFor='serif'>NoSuchFont.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font weight='400' style='normal'>b3em.ttf</font>"
+ + " </family>"
+ + "</familyset>";
+ final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
+ final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
+
+ buildSystemFallback(xml, fontMap, fallbackMap);
+
+ final Paint paint = new Paint();
+
+ Typeface testTypeface = fontMap.get("serif");
+ assertNotNull(testTypeface);
+ paint.setTypeface(testTypeface);
+ assertEquals(GLYPH_3EM_WIDTH, paint.measureText("a"), 0.0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0.0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
+
+ testTypeface = fontMap.get("sans-serif");
+ assertNotNull(testTypeface);
+ paint.setTypeface(testTypeface);
+ assertEquals(GLYPH_3EM_WIDTH, paint.measureText("a"), 0.0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0.0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
+ }
+
+ @Test
+ public void testBuildSystemFallback_ElegantFallback_customFallback_missingFile2() {
+ final String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'>"
+ + " <font weight='400' style='normal'>no_coverage.ttf</font>"
+ + " </family>"
+ + " <family name='serif'>"
+ + " <font weight='400' style='normal'>no_coverage.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font weight='400' style='normal' fallbackFor='serif'>NoSuchFont.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font weight='400' style='normal'>a3em.ttf</font>"
+ + " </family>"
+ + "</familyset>";
+ final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
+ final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
+
+ buildSystemFallback(xml, fontMap, fallbackMap);
+
+ final Paint paint = new Paint();
+
+ Typeface testTypeface = fontMap.get("serif");
+ assertNotNull(testTypeface);
+ paint.setTypeface(testTypeface);
+ assertEquals(GLYPH_3EM_WIDTH, paint.measureText("a"), 0.0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0.0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
+
+ testTypeface = fontMap.get("sans-serif");
+ assertNotNull(testTypeface);
+ paint.setTypeface(testTypeface);
+ assertEquals(GLYPH_3EM_WIDTH, paint.measureText("a"), 0.0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0.0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
+ }
+
}
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index c2ae776..8da7ced 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -213,7 +213,6 @@
Settings.Global.FANCY_IME_ANIMATIONS,
Settings.Global.FORCE_ALLOW_ON_EXTERNAL,
Settings.Global.FORCED_APP_STANDBY_ENABLED,
- Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED,
Settings.Global.FSTRIM_MANDATORY_INTERVAL,
Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
Settings.Global.GLOBAL_HTTP_PROXY_HOST,
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 68b7ac2..ef41507 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1025,6 +1025,10 @@
xmlFamily.getName(), fallback, languageTags, variant, cache, fontDir);
if (family != null) {
fallbackMap.valueAt(i).add(family);
+ } else if (defaultFamily != null) {
+ fallbackMap.valueAt(i).add(defaultFamily);
+ } else {
+ // There is no valid for for default fallback. Ignore.
}
}
}
diff --git a/packages/ExtServices/AndroidManifest.xml b/packages/ExtServices/AndroidManifest.xml
index 291009e..63d3623 100644
--- a/packages/ExtServices/AndroidManifest.xml
+++ b/packages/ExtServices/AndroidManifest.xml
@@ -51,6 +51,13 @@
</intent-filter>
</service>
+ <service android:name=".autofill.AutofillFieldClassificationServiceImpl"
+ android:permission="android.permission.BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.autofill.AutofillFieldClassificationService" />
+ </intent-filter>
+ </service>
+
<library android:name="android.ext.services"/>
</application>
diff --git a/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java b/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java
new file mode 100644
index 0000000..ea516a1
--- /dev/null
+++ b/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.ext.services.autofill;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.service.autofill.AutofillFieldClassificationService;
+import android.util.Log;
+import android.view.autofill.AutofillValue;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class AutofillFieldClassificationServiceImpl extends AutofillFieldClassificationService {
+
+ private static final String TAG = "AutofillFieldClassificationServiceImpl";
+ private static final boolean DEBUG = false;
+ private static final List<String> sAvailableAlgorithms = Arrays.asList(EditDistanceScorer.NAME);
+
+ @Override
+ public List<String> onGetAvailableAlgorithms() {
+ return sAvailableAlgorithms;
+ }
+
+ @Override
+ public String onGetDefaultAlgorithm() {
+ return EditDistanceScorer.NAME;
+ }
+
+ @Nullable
+ @Override
+ public Scores onGetScores(@Nullable String algorithmName,
+ @Nullable Bundle algorithmArgs, @NonNull List<AutofillValue> actualValues,
+ @NonNull List<String> userDataValues) {
+ if (ArrayUtils.isEmpty(actualValues) || ArrayUtils.isEmpty(userDataValues)) {
+ Log.w(TAG, "getScores(): empty currentvalues (" + actualValues + ") or userValues ("
+ + userDataValues + ")");
+ // TODO(b/70939974): add unit test
+ return null;
+ }
+ if (algorithmName != null && !algorithmName.equals(EditDistanceScorer.NAME)) {
+ Log.w(TAG, "Ignoring invalid algorithm (" + algorithmName + ") and using "
+ + EditDistanceScorer.NAME + " instead");
+ }
+
+ final String actualAlgorithmName = EditDistanceScorer.NAME;
+ final int actualValuesSize = actualValues.size();
+ final int userDataValuesSize = userDataValues.size();
+ if (DEBUG) {
+ Log.d(TAG, "getScores() will return a " + actualValuesSize + "x"
+ + userDataValuesSize + " matrix for " + actualAlgorithmName);
+ }
+ final Scores scores = new Scores(actualAlgorithmName, actualValuesSize, userDataValuesSize);
+ final float[][] scoresMatrix = scores.getScores();
+
+ final EditDistanceScorer algorithm = EditDistanceScorer.getInstance();
+ for (int i = 0; i < actualValuesSize; i++) {
+ for (int j = 0; j < userDataValuesSize; j++) {
+ final float score = algorithm.getScore(actualValues.get(i), userDataValues.get(j));
+ scoresMatrix[i][j] = score;
+ }
+ }
+ return scores;
+ }
+}
diff --git a/core/java/android/service/autofill/EditDistanceScorer.java b/packages/ExtServices/src/android/ext/services/autofill/EditDistanceScorer.java
similarity index 91%
rename from core/java/android/service/autofill/EditDistanceScorer.java
rename to packages/ExtServices/src/android/ext/services/autofill/EditDistanceScorer.java
index 97a3868..d2e804a 100644
--- a/core/java/android/service/autofill/EditDistanceScorer.java
+++ b/packages/ExtServices/src/android/ext/services/autofill/EditDistanceScorer.java
@@ -13,10 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.service.autofill;
+package android.ext.services.autofill;
import android.annotation.NonNull;
-import android.annotation.TestApi;
import android.view.autofill.AutofillValue;
/**
@@ -24,20 +23,15 @@
* by the user and the expected value predicted by an autofill service.
*/
// TODO(b/70291841): explain algorithm once it's fully implemented
-/** @hide */
-@TestApi
-public final class EditDistanceScorer {
+final class EditDistanceScorer {
private static final EditDistanceScorer sInstance = new EditDistanceScorer();
- /** @hide */
public static final String NAME = "EDIT_DISTANCE";
/**
* Gets the singleton instance.
*/
- @TestApi
- /** @hide */
public static EditDistanceScorer getInstance() {
return sInstance;
}
@@ -52,9 +46,7 @@
* <p>A full-match is {@code 1.0} (representing 100%), a full mismatch is {@code 0.0} and
* partial mathces are something in between, typically using edit-distance algorithms.
*
- * @hide
*/
- @TestApi
public float getScore(@NonNull AutofillValue actualValue, @NonNull String userDataValue) {
if (actualValue == null || !actualValue.isText() || userDataValue == null) return 0;
// TODO(b/70291841): implement edit distance - currently it's returning either 0, 100%, or
diff --git a/packages/ExtServices/tests/src/android/ext/services/autofill/EditDistanceScorerTest.java b/packages/ExtServices/tests/src/android/ext/services/autofill/EditDistanceScorerTest.java
new file mode 100644
index 0000000..cc15719
--- /dev/null
+++ b/packages/ExtServices/tests/src/android/ext/services/autofill/EditDistanceScorerTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.ext.services.autofill;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.view.autofill.AutofillValue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class EditDistanceScorerTest {
+
+ private final EditDistanceScorer mScorer = EditDistanceScorer.getInstance();
+
+ @Test
+ public void testGetScore_nullValue() {
+ assertFloat(mScorer.getScore(null, "D'OH!"), 0);
+ }
+
+ @Test
+ public void testGetScore_nonTextValue() {
+ assertFloat(mScorer.getScore(AutofillValue.forToggle(true), "D'OH!"), 0);
+ }
+
+ @Test
+ public void testGetScore_nullUserData() {
+ assertFloat(mScorer.getScore(AutofillValue.forText("D'OH!"), null), 0);
+ }
+
+ @Test
+ public void testGetScore_fullMatch() {
+ assertFloat(mScorer.getScore(AutofillValue.forText("D'OH!"), "D'OH!"), 1);
+ }
+
+ @Test
+ public void testGetScore_fullMatchMixedCase() {
+ assertFloat(mScorer.getScore(AutofillValue.forText("D'OH!"), "D'oH!"), 1);
+ }
+
+ // TODO(b/70291841): might need to change it once it supports different sizes
+ @Test
+ public void testGetScore_mismatchDifferentSizes() {
+ assertFloat(mScorer.getScore(AutofillValue.forText("One"), "MoreThanOne"), 0);
+ assertFloat(mScorer.getScore(AutofillValue.forText("MoreThanOne"), "One"), 0);
+ }
+
+ @Test
+ public void testGetScore_partialMatch() {
+ assertFloat(mScorer.getScore(AutofillValue.forText("Dude"), "Dxxx"), 0.25F);
+ assertFloat(mScorer.getScore(AutofillValue.forText("Dude"), "DUxx"), 0.50F);
+ assertFloat(mScorer.getScore(AutofillValue.forText("Dude"), "DUDx"), 0.75F);
+ assertFloat(mScorer.getScore(AutofillValue.forText("Dxxx"), "Dude"), 0.25F);
+ assertFloat(mScorer.getScore(AutofillValue.forText("DUxx"), "Dude"), 0.50F);
+ assertFloat(mScorer.getScore(AutofillValue.forText("DUDx"), "Dude"), 0.75F);
+ }
+
+ public static void assertFloat(float actualValue, float expectedValue) {
+ assertThat(actualValue).isWithin(1.0e-10f).of(expectedValue);
+ }
+}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index 631cc0d..41dd0b3 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -67,7 +67,7 @@
android:paddingTop="8dip"
android:paddingBottom="8dip"
android:paddingRight="0dp"
- android:paddingLeft="24dp"
+ android:paddingLeft="0dp"
android:background="@drawable/ripple_drawable"
android:contentDescription="@string/keyboardview_keycode_delete"
android:layout_alignEnd="@+id/pinEntry"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
index 947f27d..33f7e75 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
@@ -81,7 +81,7 @@
android:paddingTop="8dip"
android:paddingBottom="8dip"
android:paddingRight="0dp"
- android:paddingLeft="24dp"
+ android:paddingLeft="0dp"
android:background="@drawable/ripple_drawable"
android:contentDescription="@string/keyboardview_keycode_delete"
android:layout_alignEnd="@+id/pinEntry"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
index 6f270b4..4b385fc 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
@@ -82,7 +82,7 @@
android:paddingTop="8dip"
android:paddingBottom="8dip"
android:paddingRight="0dp"
- android:paddingLeft="24dp"
+ android:paddingLeft="0dp"
android:background="@drawable/ripple_drawable"
android:contentDescription="@string/keyboardview_keycode_delete"
android:layout_alignEnd="@+id/pinEntry"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 653e500..8501519 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -707,6 +707,10 @@
&& !mLockPatternUtils.isLockScreenDisabled(
KeyguardUpdateMonitor.getCurrentUser()),
mSecondaryDisplayShowing, true /* forceCallbacks */);
+ } else {
+ // The system's keyguard is disabled or missing.
+ setShowingLocked(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser()),
+ mSecondaryDisplayShowing, true);
}
mStatusBarKeyguardViewManager =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 4d8da44..ebf4cda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.stack;
+import android.annotation.Nullable;
import android.content.Context;
import android.view.View;
@@ -236,6 +237,7 @@
mShelf = shelf;
}
+ @Nullable
public NotificationShelf getShelf() {
return mShelf;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 7374f11..2ce6df2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -122,7 +122,9 @@
}
private void updateShelfState(StackScrollState resultState, AmbientState ambientState) {
NotificationShelf shelf = ambientState.getShelf();
- shelf.updateState(resultState, ambientState);
+ if (shelf != null) {
+ shelf.updateState(resultState, ambientState);
+ }
}
private void updateClipping(StackScrollState resultState,
@@ -495,6 +497,10 @@
*/
private void clampPositionToShelf(ExpandableViewState childViewState,
AmbientState ambientState) {
+ if (ambientState.getShelf() == null) {
+ return;
+ }
+
int shelfStart = ambientState.getInnerHeight()
- ambientState.getShelf().getIntrinsicHeight();
childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart);
@@ -556,7 +562,8 @@
} else if (i == 0 && ambientState.isAboveShelf(child)) {
// In case this is a new view that has never been measured before, we don't want to
// elevate if we are currently expanded more then the notification
- int shelfHeight = ambientState.getShelf().getIntrinsicHeight();
+ int shelfHeight = ambientState.getShelf() == null ? 0 :
+ ambientState.getShelf().getIntrinsicHeight();
float shelfStart = ambientState.getInnerHeight()
- shelfHeight + ambientState.getTopPadding()
+ ambientState.getStackTranslation();
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index cac7fed..6d845f9 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -44,6 +44,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
@@ -51,6 +52,7 @@
import android.os.UserManager;
import android.os.UserManagerInternal;
import android.provider.Settings;
+import android.service.autofill.AutofillFieldClassificationService.Scores;
import android.service.autofill.FillEventHistory;
import android.service.autofill.UserData;
import android.util.LocalLog;
@@ -78,6 +80,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@@ -443,6 +446,18 @@
}
}
+ // Called by Shell command.
+ public void getScore(@Nullable String algorithmName, @NonNull String value1,
+ @NonNull String value2, @NonNull RemoteCallback callback) {
+ mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
+
+ final FieldClassificationStrategy strategy =
+ new FieldClassificationStrategy(mContext, UserHandle.USER_CURRENT);
+
+ strategy.getScores(callback, algorithmName, null,
+ Arrays.asList(AutofillValue.forText(value1)), new String[] { value2 });
+ }
+
private void setDebugLocked(boolean debug) {
com.android.server.autofill.Helper.sDebug = debug;
android.view.autofill.Helper.sDebug = debug;
@@ -518,6 +533,8 @@
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
service.removeClientLocked(client);
+ } else if (sVerbose) {
+ Slog.v(TAG, "removeClient(): no service for " + userId);
}
}
}
@@ -574,6 +591,8 @@
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
return service.getFillEventHistory(getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "getFillEventHistory(): no service for " + userId);
}
}
@@ -588,6 +607,8 @@
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
return service.getUserData(getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "getUserData(): no service for " + userId);
}
}
@@ -602,6 +623,8 @@
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
service.setUserData(getCallingUid(), userData);
+ } else if (sVerbose) {
+ Slog.v(TAG, "setUserData(): no service for " + userId);
}
}
}
@@ -614,6 +637,8 @@
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
return service.isFieldClassificationEnabled(getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "isFieldClassificationEnabled(): no service for " + userId);
}
}
@@ -621,31 +646,39 @@
}
@Override
- public String getDefaultFieldClassificationAlgorithm() throws RemoteException {
+ public void getDefaultFieldClassificationAlgorithm(RemoteCallback callback)
+ throws RemoteException {
final int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getDefaultFieldClassificationAlgorithm(getCallingUid());
+ service.getDefaultFieldClassificationAlgorithm(getCallingUid(), callback);
+ } else {
+ if (sVerbose) {
+ Slog.v(TAG, "getDefaultFcAlgorithm(): no service for " + userId);
+ }
+ callback.sendResult(null);
}
}
-
- return null;
}
@Override
- public List<String> getAvailableFieldClassificationAlgorithms() throws RemoteException {
+ public void getAvailableFieldClassificationAlgorithms(RemoteCallback callback)
+ throws RemoteException {
final int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getAvailableFieldClassificationAlgorithms(getCallingUid());
+ service.getAvailableFieldClassificationAlgorithms(getCallingUid(), callback);
+ } else {
+ if (sVerbose) {
+ Slog.v(TAG, "getAvailableFcAlgorithms(): no service for " + userId);
+ }
+ callback.sendResult(null);
}
}
-
- return null;
}
@Override
@@ -656,6 +689,8 @@
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
return service.getServiceComponentName();
+ } else if (sVerbose) {
+ Slog.v(TAG, "getAutofillServiceComponentName(): no service for " + userId);
}
}
@@ -665,15 +700,17 @@
@Override
public boolean restoreSession(int sessionId, IBinder activityToken, IBinder appCallback)
throws RemoteException {
+ final int userId = UserHandle.getCallingUserId();
activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
appCallback = Preconditions.checkNotNull(appCallback, "appCallback");
synchronized (mLock) {
- final AutofillManagerServiceImpl service = mServicesCache.get(
- UserHandle.getCallingUserId());
+ final AutofillManagerServiceImpl service = mServicesCache.get(userId);
if (service != null) {
return service.restoreSession(sessionId, getCallingUid(), activityToken,
appCallback);
+ } else if (sVerbose) {
+ Slog.v(TAG, "restoreSession(): no service for " + userId);
}
}
@@ -688,6 +725,8 @@
if (service != null) {
service.updateSessionLocked(sessionId, getCallingUid(), autoFillId, bounds,
value, action, flags);
+ } else if (sVerbose) {
+ Slog.v(TAG, "updateSession(): no service for " + userId);
}
}
}
@@ -703,6 +742,8 @@
if (service != null) {
restart = service.updateSessionLocked(sessionId, getCallingUid(), autoFillId,
bounds, value, action, flags);
+ } else if (sVerbose) {
+ Slog.v(TAG, "updateOrRestartSession(): no service for " + userId);
}
}
if (restart) {
@@ -720,6 +761,8 @@
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
service.finishSessionLocked(sessionId, getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "finishSession(): no service for " + userId);
}
}
}
@@ -730,6 +773,8 @@
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
service.cancelSessionLocked(sessionId, getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "cancelSession(): no service for " + userId);
}
}
}
@@ -740,6 +785,8 @@
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
service.disableOwnedAutofillServicesLocked(Binder.getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "cancelSession(): no service for " + userId);
}
}
}
@@ -755,8 +802,12 @@
public boolean isServiceEnabled(int userId, String packageName) {
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
- if (service == null) return false;
- return Objects.equals(packageName, service.getServicePackageName());
+ if (service != null) {
+ return Objects.equals(packageName, service.getServicePackageName());
+ } else if (sVerbose) {
+ Slog.v(TAG, "isServiceEnabled(): no service for " + userId);
+ }
+ return false;
}
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index da74dba..a5bd59a9 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -43,20 +43,15 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Parcel;
-import android.os.Parcelable;
+import android.os.RemoteCallback;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
-import android.os.Parcelable.Creator;
-import android.os.RemoteCallback;
import android.provider.Settings;
import android.service.autofill.AutofillService;
import android.service.autofill.AutofillServiceInfo;
-import android.service.autofill.Dataset;
-import android.service.autofill.EditDistanceScorer;
import android.service.autofill.FieldClassification;
import android.service.autofill.FieldClassification.Match;
import android.service.autofill.FillEventHistory;
@@ -69,8 +64,6 @@
import android.util.ArraySet;
import android.util.DebugUtils;
import android.util.LocalLog;
-import android.util.Log;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
@@ -89,7 +82,6 @@
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.Random;
@@ -124,137 +116,7 @@
private final LocalLog mRequestsHistory;
private final LocalLog mUiLatencyHistory;
-
- // TODO(b/70939974): temporary, will be moved to ExtServices
- static final class FieldClassificationAlgorithmService {
-
- static final String EXTRA_SCORES = "scores";
-
- /**
- * Gets the name of all available algorithms.
- */
- @NonNull
- public List<String> getAvailableAlgorithms() {
- return Arrays.asList(EditDistanceScorer.NAME);
- }
-
- /**
- * Gets the default algorithm that's used when an algorithm is not specified or is invalid.
- */
- @NonNull
- public String getDefaultAlgorithm() {
- return EditDistanceScorer.NAME;
- }
-
- /**
- * Gets the field classification scores.
- *
- * @param algorithmName algorithm to be used. If invalid, the default algorithm will be used
- * instead.
- * @param algorithmArgs optional arguments to be passed to the algorithm.
- * @param currentValues values entered by the user.
- * @param userValues values from the user data.
- * @param callback returns a nullable bundle with the parcelable results on
- * {@link #EXTRA_SCORES}.
- */
- @Nullable
- void getScores(@NonNull String algorithmName, @Nullable Bundle algorithmArgs,
- List<AutofillValue> currentValues, @NonNull String[] userValues,
- @NonNull RemoteCallback callback) {
- if (currentValues == null || userValues == null) {
- // TODO(b/70939974): use preconditions / add unit test
- throw new IllegalArgumentException("values cannot be null");
- }
- if (currentValues.isEmpty() || userValues.length == 0) {
- Slog.w(TAG, "getScores(): empty currentvalues (" + currentValues
- + ") or userValues (" + Arrays.toString(userValues) + ")");
- // TODO(b/70939974): add unit test
- callback.sendResult(null);
- }
- String actualAlgorithName = algorithmName;
- if (!EditDistanceScorer.NAME.equals(algorithmName)) {
- Slog.w(TAG, "Ignoring invalid algorithm (" + algorithmName + ") and using "
- + EditDistanceScorer.NAME + " instead");
- actualAlgorithName = EditDistanceScorer.NAME;
- }
- final int currentValuesSize = currentValues.size();
- if (sDebug) {
- Log.d(TAG, "getScores() will return a " + currentValuesSize + "x"
- + userValues.length + " matrix for " + actualAlgorithName);
- }
- final FieldClassificationScores scores = new FieldClassificationScores(
- actualAlgorithName, currentValuesSize, userValues.length);
- final EditDistanceScorer algorithm = EditDistanceScorer.getInstance();
- for (int i = 0; i < currentValuesSize; i++) {
- for (int j = 0; j < userValues.length; j++) {
- final float score = algorithm.getScore(currentValues.get(i), userValues[j]);
- scores.scores[i][j] = score;
- }
- }
- final Bundle result = new Bundle();
- result.putParcelable(EXTRA_SCORES, scores);
- callback.sendResult(result);
- }
- }
-
- // TODO(b/70939974): temporary, will be moved to ExtServices
- public static final class FieldClassificationScores implements Parcelable {
- public final String algorithmName;
- public final float[][] scores;
-
- public FieldClassificationScores(String algorithmName, int size1, int size2) {
- this.algorithmName = algorithmName;
- scores = new float[size1][size2];
- }
-
- public FieldClassificationScores(Parcel parcel) {
- algorithmName = parcel.readString();
- final int size1 = parcel.readInt();
- final int size2 = parcel.readInt();
- scores = new float[size1][size2];
- for (int i = 0; i < size1; i++) {
- for (int j = 0; j < size2; j++) {
- scores[i][j] = parcel.readFloat();
- }
- }
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeString(algorithmName);
- int size1 = scores.length;
- int size2 = scores[0].length;
- parcel.writeInt(size1);
- parcel.writeInt(size2);
- for (int i = 0; i < size1; i++) {
- for (int j = 0; j < size2; j++) {
- parcel.writeFloat(scores[i][j]);
- }
- }
- }
-
- public static final Creator<FieldClassificationScores> CREATOR = new Creator<FieldClassificationScores>() {
-
- @Override
- public FieldClassificationScores createFromParcel(Parcel parcel) {
- return new FieldClassificationScores(parcel);
- }
-
- @Override
- public FieldClassificationScores[] newArray(int size) {
- return new FieldClassificationScores[size];
- }
-
- };
- }
-
- private final FieldClassificationAlgorithmService mFcService =
- new FieldClassificationAlgorithmService();
+ private final FieldClassificationStrategy mFieldClassificationStrategy;
/**
* Apps disabled by the service; key is package name, value is when they will be enabled again.
@@ -324,6 +186,7 @@
mUiLatencyHistory = uiLatencyHistory;
mUserId = userId;
mUi = ui;
+ mFieldClassificationStrategy = new FieldClassificationStrategy(context, userId);
updateLocked(disabled);
}
@@ -1089,10 +952,8 @@
mUserData.dump(prefix2, pw);
}
- pw.print(prefix); pw.print("Available Field Classification algorithms: ");
- pw.println(mFcService.getAvailableAlgorithms());
- pw.print(prefix); pw.print("Default Field Classification algorithm: ");
- pw.println(mFcService.getDefaultAlgorithm());
+ pw.print(prefix); pw.println("Field Classification strategy: ");
+ mFieldClassificationStrategy.dump(prefix2, pw);
}
void destroySessionsLocked() {
@@ -1288,26 +1149,26 @@
mUserId) == 1;
}
- FieldClassificationAlgorithmService getFieldClassificationService() {
- return mFcService;
+ FieldClassificationStrategy getFieldClassificationStrategy() {
+ return mFieldClassificationStrategy;
}
- List<String> getAvailableFieldClassificationAlgorithms(int callingUid) {
+ void getAvailableFieldClassificationAlgorithms(int callingUid, RemoteCallback callback) {
synchronized (mLock) {
if (!isCalledByServiceLocked("getFCAlgorithms()", callingUid)) {
- return null;
+ return;
}
}
- return mFcService.getAvailableAlgorithms();
+ mFieldClassificationStrategy.getAvailableAlgorithms(callback);
}
- String getDefaultFieldClassificationAlgorithm(int callingUid) {
+ void getDefaultFieldClassificationAlgorithm(int callingUid, RemoteCallback callback) {
synchronized (mLock) {
if (!isCalledByServiceLocked("getDefaultFCAlgorithm()", callingUid)) {
- return null;
+ return;
}
}
- return mFcService.getDefaultAlgorithm();
+ mFieldClassificationStrategy.getDefaultAlgorithm(callback);
}
@Override
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
index f3de557..4456087 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
@@ -16,11 +16,15 @@
package com.android.server.autofill;
+import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
+
import static com.android.server.autofill.AutofillManagerService.RECEIVER_BUNDLE_EXTRA_SESSIONS;
import android.os.Bundle;
+import android.os.RemoteCallback;
import android.os.ShellCommand;
import android.os.UserHandle;
+import android.service.autofill.AutofillFieldClassificationService.Scores;
import android.view.autofill.AutofillManager;
import com.android.internal.os.IResultReceiver;
@@ -80,13 +84,16 @@
pw.println(" Sets the maximum number of partitions per session.");
pw.println("");
pw.println(" list sessions [--user USER_ID]");
- pw.println(" List all pending sessions.");
+ pw.println(" Lists all pending sessions.");
pw.println("");
pw.println(" destroy sessions [--user USER_ID]");
- pw.println(" Destroy all pending sessions.");
+ pw.println(" Destroys all pending sessions.");
pw.println("");
pw.println(" reset");
- pw.println(" Reset all pending sessions and cached service connections.");
+ pw.println(" Resets all pending sessions and cached service connections.");
+ pw.println("");
+ pw.println(" get fc_score [--algorithm ALGORITHM] value1 value2");
+ pw.println(" Gets the field classification score for 2 fields.");
pw.println("");
}
}
@@ -98,6 +105,8 @@
return getLogLevel(pw);
case "max_partitions":
return getMaxPartitions(pw);
+ case "fc_score":
+ return getFieldClassificationScore(pw);
default:
pw.println("Invalid set: " + what);
return -1;
@@ -164,6 +173,35 @@
return 0;
}
+ private int getFieldClassificationScore(PrintWriter pw) {
+ final String nextArg = getNextArgRequired();
+ final String algorithm, value1;
+ if ("--algorithm".equals(nextArg)) {
+ algorithm = getNextArgRequired();
+ value1 = getNextArgRequired();
+ } else {
+ algorithm = null;
+ value1 = nextArg;
+ }
+ final String value2 = getNextArgRequired();
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ mService.getScore(algorithm, value1, value2, new RemoteCallback((result) -> {
+ final Scores scores = result.getParcelable(EXTRA_SCORES);
+ if (scores == null) {
+ pw.println("no score");
+ } else {
+ pw.print("algorithm: ");
+ pw.print(scores.getAlgorithm());
+ pw.print(" score: ");
+ pw.println(scores.getScores()[0][0]);
+ }
+ latch.countDown();
+ }));
+
+ return waitForLatch(pw, latch);
+ }
+
private int requestDestroy(PrintWriter pw) {
if (!isNextArgSessions(pw)) {
return -1;
@@ -210,19 +248,13 @@
return true;
}
- private boolean isNextArgLogLevel(PrintWriter pw, String cmd) {
- final String type = getNextArgRequired();
- if (!type.equals("log_level")) {
- pw.println("Error: invalid " + cmd + " type: " + type);
- return false;
- }
- return true;
- }
-
private int requestSessionCommon(PrintWriter pw, CountDownLatch latch,
Runnable command) {
command.run();
+ return waitForLatch(pw, latch);
+ }
+ private int waitForLatch(PrintWriter pw, CountDownLatch latch) {
try {
final boolean received = latch.await(5, TimeUnit.SECONDS);
if (!received) {
diff --git a/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java b/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java
new file mode 100644
index 0000000..7228f1d
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.autofill;
+
+import static android.view.autofill.AutofillManager.EXTRA_AVAILABLE_ALGORITHMS;
+import static android.view.autofill.AutofillManager.EXTRA_DEFAULT_ALGORITHM;
+import static android.view.autofill.AutofillManager.FC_SERVICE_TIMEOUT;
+
+import static com.android.server.autofill.Helper.sDebug;
+import static com.android.server.autofill.Helper.sVerbose;
+
+import android.Manifest;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.autofill.AutofillFieldClassificationService;
+import android.service.autofill.IAutofillFieldClassificationService;
+import android.util.Log;
+import android.util.Slog;
+import android.view.autofill.AutofillValue;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Strategy used to bridge the field classification algorithms provided by a service in an external
+ * package.
+ */
+//TODO(b/70291841): add unit tests ?
+final class FieldClassificationStrategy {
+
+ private static final String TAG = "FieldClassificationStrategy";
+
+ private final Context mContext;
+ private final Object mLock = new Object();
+ private final int mUserId;
+
+ @GuardedBy("mLock")
+ private ServiceConnection mServiceConnection;
+
+ @GuardedBy("mLock")
+ private IAutofillFieldClassificationService mRemoteService;
+
+ @GuardedBy("mLock")
+ private ArrayList<Command> mQueuedCommands;
+
+ public FieldClassificationStrategy(Context context, int userId) {
+ mContext = context;
+ mUserId = userId;
+ }
+
+ private ComponentName getServiceComponentName() {
+ final String packageName =
+ mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
+ if (packageName == null) {
+ Slog.w(TAG, "no external services package!");
+ return null;
+ }
+
+ final Intent intent = new Intent(AutofillFieldClassificationService.SERVICE_INTERFACE);
+ intent.setPackage(packageName);
+ final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ Slog.w(TAG, "No valid components found.");
+ return null;
+ }
+ final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+ final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
+
+ if (!Manifest.permission.BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE
+ .equals(serviceInfo.permission)) {
+ Slog.w(TAG, name.flattenToShortString() + " does not require permission "
+ + Manifest.permission.BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE);
+ return null;
+ }
+
+ if (sVerbose) Slog.v(TAG, "getServiceComponentName(): " + name);
+ return name;
+ }
+
+ /**
+ * Run a command, starting the service connection if necessary.
+ */
+ private void connectAndRun(@NonNull Command command) {
+ synchronized (mLock) {
+ if (mRemoteService != null) {
+ try {
+ if (sVerbose) Slog.v(TAG, "running command right away");
+ command.run(mRemoteService);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "exception calling service: " + e);
+ }
+ return;
+ } else {
+ if (sDebug) Slog.d(TAG, "service is null; queuing command");
+ if (mQueuedCommands == null) {
+ mQueuedCommands = new ArrayList<>(1);
+ }
+ mQueuedCommands.add(command);
+ // If we're already connected, don't create a new connection, just leave - the
+ // command will be run when the service connects
+ if (mServiceConnection != null) return;
+ }
+
+ if (sVerbose) Slog.v(TAG, "creating connection");
+
+ // Create the connection
+ mServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (sVerbose) Slog.v(TAG, "onServiceConnected(): " + name);
+ synchronized (mLock) {
+ mRemoteService = IAutofillFieldClassificationService.Stub
+ .asInterface(service);
+ if (mQueuedCommands != null) {
+ final int size = mQueuedCommands.size();
+ if (sDebug) Slog.d(TAG, "running " + size + " queued commands");
+ for (int i = 0; i < size; i++) {
+ final Command queuedCommand = mQueuedCommands.get(i);
+ try {
+ if (sVerbose) Slog.v(TAG, "running queued command #" + i);
+ queuedCommand.run(mRemoteService);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "exception calling " + name + ": " + e);
+ }
+ }
+ mQueuedCommands = null;
+ } else if (sDebug) Slog.d(TAG, "no queued commands");
+ }
+ }
+
+ @Override
+ @MainThread
+ public void onServiceDisconnected(ComponentName name) {
+ if (sVerbose) Slog.v(TAG, "onServiceDisconnected(): " + name);
+ synchronized (mLock) {
+ mRemoteService = null;
+ }
+ }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ if (sVerbose) Slog.v(TAG, "onBindingDied(): " + name);
+ synchronized (mLock) {
+ mRemoteService = null;
+ }
+ }
+
+ @Override
+ public void onNullBinding(ComponentName name) {
+ if (sVerbose) Slog.v(TAG, "onNullBinding(): " + name);
+ synchronized (mLock) {
+ mRemoteService = null;
+ }
+ }
+ };
+
+ final ComponentName component = getServiceComponentName();
+ if (sVerbose) Slog.v(TAG, "binding to: " + component);
+ if (component != null) {
+ final Intent intent = new Intent();
+ intent.setComponent(component);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mContext.bindServiceAsUser(intent, mServiceConnection, Context.BIND_AUTO_CREATE,
+ UserHandle.of(mUserId));
+ if (sVerbose) Slog.v(TAG, "bound");
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+ }
+
+ void getAvailableAlgorithms(RemoteCallback callback) {
+ connectAndRun((service) -> service.getAvailableAlgorithms(callback));
+ }
+
+ void getDefaultAlgorithm(RemoteCallback callback) {
+ connectAndRun((service) -> service.getDefaultAlgorithm(callback));
+ }
+
+ //TODO(b/70291841): rename this method (and all others in the chain) to something like
+ // calculateScores() ?
+ void getScores(RemoteCallback callback, @Nullable String algorithmName,
+ @Nullable Bundle algorithmArgs, @NonNull List<AutofillValue> actualValues,
+ @NonNull String[] userDataValues) {
+ connectAndRun((service) -> service.getScores(callback, algorithmName,
+ algorithmArgs, actualValues, userDataValues));
+ }
+
+ void dump(String prefix, PrintWriter pw) {
+ final ComponentName impl = getServiceComponentName();
+ pw.print(prefix); pw.print("User ID: "); pw.println(mUserId);
+ pw.print(prefix); pw.print("Queued commands: ");
+ if (mQueuedCommands == null) {
+ pw.println("N/A");
+ } else {
+ pw.println(mQueuedCommands.size());
+ }
+ pw.print(prefix); pw.print("Implementation: ");
+ if (impl == null) {
+ pw.println("N/A");
+ return;
+ }
+ pw.println(impl.flattenToShortString());
+
+ final CountDownLatch latch = new CountDownLatch(2);
+
+ // Lock used to make sure lines don't overlap
+ final Object lock = latch;
+
+ connectAndRun((service) -> service.getAvailableAlgorithms(new RemoteCallback((bundle) -> {
+ synchronized (lock) {
+ pw.print(prefix); pw.print("Available algorithms: ");
+ pw.println(bundle.getStringArrayList(EXTRA_AVAILABLE_ALGORITHMS));
+ }
+ latch.countDown();
+ })));
+
+ connectAndRun((service) -> service.getDefaultAlgorithm(new RemoteCallback((bundle) -> {
+ synchronized (lock) {
+ pw.print(prefix); pw.print("Default algorithm: ");
+ pw.println(bundle.getString(EXTRA_DEFAULT_ALGORITHM));
+ }
+ latch.countDown();
+ })));
+
+ try {
+ if (!latch.await(FC_SERVICE_TIMEOUT, TimeUnit.MILLISECONDS)) {
+ synchronized (lock) {
+ pw.print(prefix); pw.print("timeout ("); pw.print(FC_SERVICE_TIMEOUT);
+ pw.println("ms) waiting for service");
+ }
+ }
+ } catch (InterruptedException e) {
+ synchronized (lock) {
+ pw.print(prefix); pw.println("interrupted while waiting for service");
+ }
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ private interface Command {
+ void run(IAutofillFieldClassificationService service) throws RemoteException;
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index f5d1336..a0e23a1 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -18,6 +18,7 @@
import static android.app.ActivityManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
import static android.app.ActivityManagerInternal.ASSIST_KEY_STRUCTURE;
+import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
@@ -25,7 +26,6 @@
import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
-import static com.android.server.autofill.AutofillManagerServiceImpl.FieldClassificationAlgorithmService.EXTRA_SCORES;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sPartitionMaxCount;
import static com.android.server.autofill.Helper.sVerbose;
@@ -54,11 +54,11 @@
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.service.autofill.AutofillFieldClassificationService.Scores;
import android.service.autofill.AutofillService;
import android.service.autofill.Dataset;
import android.service.autofill.FieldClassification;
import android.service.autofill.FieldClassification.Match;
-import android.service.carrier.CarrierMessagingService.ResultCallback;
import android.service.autofill.FillContext;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
@@ -86,8 +86,6 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.os.HandlerCaller;
import com.android.internal.util.ArrayUtils;
-import com.android.server.autofill.AutofillManagerServiceImpl.FieldClassificationAlgorithmService;
-import com.android.server.autofill.AutofillManagerServiceImpl.FieldClassificationScores;
import com.android.server.autofill.ui.AutoFillUI;
import com.android.server.autofill.ui.PendingUi;
@@ -99,7 +97,6 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
/**
* A session for a given activity.
@@ -1101,10 +1098,9 @@
}
// Sets field classification scores
- final FieldClassificationAlgorithmService fcService =
- mService.getFieldClassificationService();
- if (userData != null && fcService != null) {
- logFieldClassificationScoreLocked(fcService, ignoredDatasets, changedFieldIds,
+ final FieldClassificationStrategy fcStrategy = mService.getFieldClassificationStrategy();
+ if (userData != null && fcStrategy != null) {
+ logFieldClassificationScoreLocked(fcStrategy, ignoredDatasets, changedFieldIds,
changedDatasetIds, manuallyFilledFieldIds, manuallyFilledDatasetIds,
manuallyFilledIds, userData,
mViewStates.values());
@@ -1121,7 +1117,7 @@
* {@code fieldId} based on its {@code currentValue} and {@code userData}.
*/
private void logFieldClassificationScoreLocked(
- @NonNull AutofillManagerServiceImpl.FieldClassificationAlgorithmService fcService,
+ @NonNull FieldClassificationStrategy fcStrategy,
@NonNull ArraySet<String> ignoredDatasets,
@NonNull ArrayList<AutofillId> changedFieldIds,
@NonNull ArrayList<String> changedDatasetIds,
@@ -1161,6 +1157,7 @@
fieldIds[k++] = viewState.id;
}
+ // Then use the results, asynchronously
final RemoteCallback callback = new RemoteCallback((result) -> {
if (result == null) {
if (sDebug) Slog.d(TAG, "setFieldClassificationScore(): no results");
@@ -1170,35 +1167,46 @@
mComponentName.getPackageName());
return;
}
- final FieldClassificationScores matrix = result.getParcelable(EXTRA_SCORES);
+ final Scores scores = result.getParcelable(EXTRA_SCORES);
+ if (scores == null) {
+ Slog.w(TAG, "No field classification score on " + result);
+ return;
+ }
+ final float[][] scoresMatrix = scores.getScores();
- // Then use the results.
- for (int i = 0; i < viewsSize; i++) {
- final AutofillId fieldId = fieldIds[i];
+ int i = 0, j = 0;
+ try {
+ for (i = 0; i < viewsSize; i++) {
+ final AutofillId fieldId = fieldIds[i];
- ArrayList<Match> matches = null;
- for (int j = 0; j < userValues.length; j++) {
- String remoteId = remoteIds[j];
- final String actualAlgorithm = matrix.algorithmName;
- final float score = matrix.scores[i][j];
- if (score > 0) {
- if (sVerbose) {
- Slog.v(TAG, "adding score " + score + " at index " + j + " and id "
- + fieldId);
+ ArrayList<Match> matches = null;
+ for (j = 0; j < userValues.length; j++) {
+ String remoteId = remoteIds[j];
+ final String actualAlgorithm = scores.getAlgorithm();
+ final float score = scoresMatrix[i][j];
+ if (score > 0) {
+ if (sVerbose) {
+ Slog.v(TAG, "adding score " + score + " at index " + j + " and id "
+ + fieldId);
+ }
+ if (matches == null) {
+ matches = new ArrayList<>(userValues.length);
+ }
+ matches.add(new Match(remoteId, score, actualAlgorithm));
}
- if (matches == null) {
- matches = new ArrayList<>(userValues.length);
+ else if (sVerbose) {
+ Slog.v(TAG, "skipping score 0 at index " + j + " and id " + fieldId);
}
- matches.add(new Match(remoteId, score, actualAlgorithm));
}
- else if (sVerbose) {
- Slog.v(TAG, "skipping score 0 at index " + j + " and id " + fieldId);
+ if (matches != null) {
+ detectedFieldIds.add(fieldId);
+ detectedFieldClassifications.add(new FieldClassification(matches));
}
}
- if (matches != null) {
- detectedFieldIds.add(fieldId);
- detectedFieldClassifications.add(new FieldClassification(matches));
- }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ Slog.wtf(TAG, "Error accessing FC score at " + i + " x " + j + ": "
+ + Arrays.toString(scoresMatrix), e);
+ return;
}
mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds,
@@ -1207,7 +1215,7 @@
mComponentName.getPackageName());
});
- fcService.getScores(algorithm, algorithmArgs, currentValues, userValues, callback);
+ fcStrategy.getScores(callback, algorithm, algorithmArgs, currentValues, userValues);
}
/**
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index 30fd25a..5b901ee 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -62,9 +62,17 @@
private final PackageManager mPackageManager;
private final Set<ComponentName> mTransportWhitelist;
private final TransportClientManager mTransportClientManager;
- private final Object mTransportLock = new Object();
private OnTransportRegisteredListener mOnTransportRegisteredListener = (c, n) -> {};
+ /**
+ * Lock for registered transports and currently selected transport.
+ *
+ * <p><b>Warning:</b> No calls to {@link IBackupTransport} or calls that result in transport
+ * code being executed such as {@link TransportClient#connect(String)}} and its variants should
+ * be made with this lock held, risk of deadlock.
+ */
+ private final Object mTransportLock = new Object();
+
/** @see #getRegisteredTransportNames() */
@GuardedBy("mTransportLock")
private final Map<ComponentName, TransportDescription> mRegisteredTransportsDescriptionMap =
@@ -109,15 +117,16 @@
@WorkerThread
void onPackageChanged(String packageName, String... components) {
+ // Unfortunately this can't be atomic because we risk a deadlock if
+ // registerTransportsFromPackage() is put inside the synchronized block
+ Set<ComponentName> transportComponents =
+ Stream.of(components)
+ .map(component -> new ComponentName(packageName, component))
+ .collect(Collectors.toSet());
synchronized (mTransportLock) {
- Set<ComponentName> transportComponents =
- Stream.of(components)
- .map(component -> new ComponentName(packageName, component))
- .collect(Collectors.toSet());
-
mRegisteredTransportsDescriptionMap.keySet().removeIf(transportComponents::contains);
- registerTransportsFromPackage(packageName, transportComponents::contains);
}
+ registerTransportsFromPackage(packageName, transportComponents::contains);
}
/**
@@ -263,6 +272,9 @@
* This is called with an internal lock held, ensuring that the transport will remain registered
* while {@code transportConsumer} is being executed. Don't do heavy operations in {@code
* transportConsumer}.
+ *
+ * <p><b>Warning:</b> Do NOT make any calls to {@link IBackupTransport} or call any variants of
+ * {@link TransportClient#connect(String)} here, otherwise you risk deadlock.
*/
public void forEachRegisteredTransport(Consumer<String> transportConsumer) {
synchronized (mTransportLock) {
@@ -465,20 +477,27 @@
*/
@WorkerThread
public int registerAndSelectTransport(ComponentName transportComponent) {
+ // If it's already registered we select and return
synchronized (mTransportLock) {
- if (!mRegisteredTransportsDescriptionMap.containsKey(transportComponent)) {
- int result = registerTransport(transportComponent);
- if (result != BackupManager.SUCCESS) {
- return result;
- }
- }
-
try {
selectTransport(getTransportName(transportComponent));
return BackupManager.SUCCESS;
} catch (TransportNotRegisteredException e) {
- // Shouldn't happen because we are holding the lock
- Slog.wtf(TAG, "Transport unexpectedly not registered");
+ // Fall through and release lock
+ }
+ }
+
+ // We can't call registerTransport() with the transport lock held
+ int result = registerTransport(transportComponent);
+ if (result != BackupManager.SUCCESS) {
+ return result;
+ }
+ synchronized (mTransportLock) {
+ try {
+ selectTransport(getTransportName(transportComponent));
+ return BackupManager.SUCCESS;
+ } catch (TransportNotRegisteredException e) {
+ Slog.wtf(TAG, "Transport got unregistered");
return BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
}
}
@@ -512,13 +531,11 @@
if (hosts == null) {
return;
}
- synchronized (mTransportLock) {
- for (ResolveInfo host : hosts) {
- ComponentName transportComponent = host.serviceInfo.getComponentName();
- if (transportComponentFilter.test(transportComponent)
- && isTransportTrusted(transportComponent)) {
- registerTransport(transportComponent);
- }
+ for (ResolveInfo host : hosts) {
+ ComponentName transportComponent = host.serviceInfo.getComponentName();
+ if (transportComponentFilter.test(transportComponent)
+ && isTransportTrusted(transportComponent)) {
+ registerTransport(transportComponent);
}
}
}
@@ -547,22 +564,24 @@
/**
* Tries to register transport represented by {@code transportComponent}.
*
+ * <p><b>Warning:</b> Don't call this with the transport lock held.
+ *
* @param transportComponent Host of the transport that we want to register.
* @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID}
* or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}.
*/
@WorkerThread
private int registerTransport(ComponentName transportComponent) {
+ checkCanUseTransport();
+
if (!isTransportTrusted(transportComponent)) {
return BackupManager.ERROR_TRANSPORT_INVALID;
}
String transportString = transportComponent.flattenToShortString();
-
String callerLogString = "TransportManager.registerTransport()";
TransportClient transportClient =
mTransportClientManager.getTransportClient(transportComponent, callerLogString);
-
final IBackupTransport transport;
try {
transport = transportClient.connectOrThrow(callerLogString);
@@ -593,20 +612,26 @@
/** If {@link RemoteException} is thrown the transport is guaranteed to not be registered. */
private void registerTransport(ComponentName transportComponent, IBackupTransport transport)
throws RemoteException {
+ checkCanUseTransport();
+
+ TransportDescription description =
+ new TransportDescription(
+ transport.name(),
+ transport.transportDirName(),
+ transport.configurationIntent(),
+ transport.currentDestinationString(),
+ transport.dataManagementIntent(),
+ transport.dataManagementLabel());
synchronized (mTransportLock) {
- String name = transport.name();
- TransportDescription description =
- new TransportDescription(
- name,
- transport.transportDirName(),
- transport.configurationIntent(),
- transport.currentDestinationString(),
- transport.dataManagementIntent(),
- transport.dataManagementLabel());
mRegisteredTransportsDescriptionMap.put(transportComponent, description);
}
}
+ private void checkCanUseTransport() {
+ Preconditions.checkState(
+ !Thread.holdsLock(mTransportLock), "Can't call transport with transport lock held");
+ }
+
private static Predicate<ComponentName> fromPackageFilter(String packageName) {
return transportComponent -> packageName.equals(transportComponent.getPackageName());
}
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 04d292f..dc5f5a2 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -383,16 +383,16 @@
}
}
- private void update(HealthInfo info) {
+ private void update(android.hardware.health.V2_0.HealthInfo info) {
traceBegin("HealthInfoUpdate");
synchronized (mLock) {
if (!mUpdatesStopped) {
- mHealthInfo = info;
+ mHealthInfo = info.legacy;
// Process the new values.
processValuesLocked(false);
mLock.notifyAll(); // for any waiters on new info
} else {
- copy(mLastHealthInfo, info);
+ copy(mLastHealthInfo, info.legacy);
}
}
traceEnd();
@@ -1010,7 +1010,7 @@
private final class HealthHalCallback extends IHealthInfoCallback.Stub
implements HealthServiceWrapper.Callback {
- @Override public void healthInfoChanged(HealthInfo props) {
+ @Override public void healthInfoChanged(android.hardware.health.V2_0.HealthInfo props) {
BatteryService.this.update(props);
}
// on new service registered
diff --git a/services/core/java/com/android/server/ForceAppStandbyTracker.java b/services/core/java/com/android/server/ForceAppStandbyTracker.java
index 4551611..a75a367 100644
--- a/services/core/java/com/android/server/ForceAppStandbyTracker.java
+++ b/services/core/java/com/android/server/ForceAppStandbyTracker.java
@@ -26,8 +26,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.BatteryManager;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -91,9 +89,6 @@
private final MyHandler mHandler;
- @VisibleForTesting
- FeatureFlagsObserver mFlagsObserver;
-
/**
* Pair of (uid (not user-id), packageName) with OP_RUN_ANY_IN_BACKGROUND *not* allowed.
*/
@@ -119,32 +114,13 @@
boolean mStarted;
@GuardedBy("mLock")
- boolean mIsCharging;
+ boolean mForceAllAppsStandby; // True if device is in extreme battery saver mode
@GuardedBy("mLock")
- boolean mBatterySaverEnabled;
+ boolean mForcedAppStandbyEnabled; // True if the forced app standby feature is enabled
- /**
- * True if the forced app standby is currently enabled
- */
- @GuardedBy("mLock")
- boolean mForceAllAppsStandby;
-
- /**
- * True if the forced app standby for small battery devices feature is enabled in settings
- */
- @GuardedBy("mLock")
- boolean mForceAllAppStandbyForSmallBattery;
-
- /**
- * True if the forced app standby feature is enabled in settings
- */
- @GuardedBy("mLock")
- boolean mForcedAppStandbyEnabled;
-
- @VisibleForTesting
- class FeatureFlagsObserver extends ContentObserver {
- FeatureFlagsObserver() {
+ private class FeatureFlagObserver extends ContentObserver {
+ FeatureFlagObserver() {
super(null);
}
@@ -152,9 +128,6 @@
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.FORCED_APP_STANDBY_ENABLED),
false, this);
-
- mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED), false, this);
}
boolean isForcedAppStandbyEnabled() {
@@ -162,43 +135,20 @@
Settings.Global.FORCED_APP_STANDBY_ENABLED, 1) == 1;
}
- boolean isForcedAppStandbyForSmallBatteryEnabled() {
- return Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED, 0) == 1;
- }
-
@Override
- public void onChange(boolean selfChange, Uri uri) {
- if (Settings.Global.getUriFor(Settings.Global.FORCED_APP_STANDBY_ENABLED).equals(uri)) {
- final boolean enabled = isForcedAppStandbyEnabled();
- synchronized (mLock) {
- if (mForcedAppStandbyEnabled == enabled) {
- return;
- }
- mForcedAppStandbyEnabled = enabled;
- if (DEBUG) {
- Slog.d(TAG,
- "Forced app standby feature flag changed: " + mForcedAppStandbyEnabled);
- }
+ public void onChange(boolean selfChange) {
+ final boolean enabled = isForcedAppStandbyEnabled();
+ synchronized (mLock) {
+ if (mForcedAppStandbyEnabled == enabled) {
+ return;
}
- mHandler.notifyForcedAppStandbyFeatureFlagChanged();
- } else if (Settings.Global.getUriFor(
- Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED).equals(uri)) {
- final boolean enabled = isForcedAppStandbyForSmallBatteryEnabled();
- synchronized (mLock) {
- if (mForceAllAppStandbyForSmallBattery == enabled) {
- return;
- }
- mForceAllAppStandbyForSmallBattery = enabled;
- if (DEBUG) {
- Slog.d(TAG, "Forced app standby for small battery feature flag changed: "
- + mForceAllAppStandbyForSmallBattery);
- }
- updateForceAllAppStandbyState();
+ mForcedAppStandbyEnabled = enabled;
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Forced app standby feature flag changed: " + mForcedAppStandbyEnabled);
}
- } else {
- Slog.w(TAG, "Unexpected feature flag uri encountered: " + uri);
}
+ mHandler.notifyFeatureFlagChanged();
}
}
@@ -339,11 +289,9 @@
mAppOpsManager = Preconditions.checkNotNull(injectAppOpsManager());
mAppOpsService = Preconditions.checkNotNull(injectIAppOpsService());
mPowerManagerInternal = Preconditions.checkNotNull(injectPowerManagerInternal());
- mFlagsObserver = new FeatureFlagsObserver();
- mFlagsObserver.register();
- mForcedAppStandbyEnabled = mFlagsObserver.isForcedAppStandbyEnabled();
- mForceAllAppStandbyForSmallBattery =
- mFlagsObserver.isForcedAppStandbyForSmallBatteryEnabled();
+ final FeatureFlagObserver flagObserver = new FeatureFlagObserver();
+ flagObserver.register();
+ mForcedAppStandbyEnabled = flagObserver.isForcedAppStandbyEnabled();
try {
mIActivityManager.registerUidObserver(new UidObserver(),
@@ -358,24 +306,16 @@
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_REMOVED);
- filter.addAction(Intent.ACTION_BATTERY_CHANGED);
mContext.registerReceiver(new MyReceiver(), filter);
refreshForcedAppStandbyUidPackagesLocked();
mPowerManagerInternal.registerLowPowerModeObserver(
ServiceType.FORCE_ALL_APPS_STANDBY,
- (state) -> {
- synchronized (mLock) {
- mBatterySaverEnabled = state.batterySaverEnabled;
- updateForceAllAppStandbyState();
- }
- });
+ (state) -> updateForceAllAppsStandby(state.batterySaverEnabled));
- mBatterySaverEnabled = mPowerManagerInternal.getLowPowerState(
- ServiceType.FORCE_ALL_APPS_STANDBY).batterySaverEnabled;
-
- updateForceAllAppStandbyState();
+ updateForceAllAppsStandby(mPowerManagerInternal.getLowPowerState(
+ ServiceType.FORCE_ALL_APPS_STANDBY).batterySaverEnabled);
}
}
@@ -400,11 +340,6 @@
return LocalServices.getService(PowerManagerInternal.class);
}
- @VisibleForTesting
- boolean isSmallBatteryDevice() {
- return ActivityManager.isSmallBatteryDevice();
- }
-
/**
* Update {@link #mRunAnyRestrictedPackages} with the current app ops state.
*/
@@ -434,29 +369,18 @@
}
}
- private void updateForceAllAppStandbyState() {
- synchronized (mLock) {
- if (mIsCharging) {
- toggleForceAllAppsStandbyLocked(false);
- } else if (mForceAllAppStandbyForSmallBattery
- && isSmallBatteryDevice()) {
- toggleForceAllAppsStandbyLocked(true);
- } else {
- toggleForceAllAppsStandbyLocked(mBatterySaverEnabled);
- }
- }
- }
-
/**
* Update {@link #mForceAllAppsStandby} and notifies the listeners.
*/
- private void toggleForceAllAppsStandbyLocked(boolean enable) {
- if (enable == mForceAllAppsStandby) {
- return;
- }
- mForceAllAppsStandby = enable;
+ void updateForceAllAppsStandby(boolean enable) {
+ synchronized (mLock) {
+ if (enable == mForceAllAppsStandby) {
+ return;
+ }
+ mForceAllAppsStandby = enable;
- mHandler.notifyForceAllAppsStandbyChanged();
+ mHandler.notifyForceAllAppsStandbyChanged();
+ }
}
private int findForcedAppStandbyUidPackageIndexLocked(int uid, @NonNull String packageName) {
@@ -591,13 +515,6 @@
if (userId > 0) {
mHandler.doUserRemoved(userId);
}
- } else if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
- int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
- synchronized (mLock) {
- mIsCharging = (status == BatteryManager.BATTERY_STATUS_CHARGING
- || status == BatteryManager.BATTERY_STATUS_FULL);
- }
- updateForceAllAppStandbyState();
}
}
}
@@ -616,7 +533,7 @@
private static final int MSG_TEMP_WHITELIST_CHANGED = 5;
private static final int MSG_FORCE_ALL_CHANGED = 6;
private static final int MSG_USER_REMOVED = 7;
- private static final int MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 8;
+ private static final int MSG_FEATURE_FLAG_CHANGED = 8;
public MyHandler(Looper looper) {
super(looper);
@@ -646,8 +563,8 @@
obtainMessage(MSG_FORCE_ALL_CHANGED).sendToTarget();
}
- public void notifyForcedAppStandbyFeatureFlagChanged() {
- obtainMessage(MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED).sendToTarget();
+ public void notifyFeatureFlagChanged() {
+ obtainMessage(MSG_FEATURE_FLAG_CHANGED).sendToTarget();
}
public void doUserRemoved(int userId) {
@@ -701,7 +618,7 @@
l.onForceAllAppsStandbyChanged(sender);
}
return;
- case MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED:
+ case MSG_FEATURE_FLAG_CHANGED:
// Feature flag for forced app standby changed.
final boolean unblockAlarms;
synchronized (mLock) {
@@ -925,18 +842,6 @@
pw.println(isForceAllAppsStandbyEnabled());
pw.print(indent);
- pw.print("Small Battery Device: ");
- pw.println(isSmallBatteryDevice());
-
- pw.print(indent);
- pw.print("Force all apps standby for small battery device: ");
- pw.println(mForceAllAppStandbyForSmallBattery);
-
- pw.print(indent);
- pw.print("Charging: ");
- pw.println(mIsCharging);
-
- pw.print(indent);
pw.print("Foreground uids: [");
String sep = "";
@@ -975,11 +880,6 @@
final long token = proto.start(fieldId);
proto.write(ForceAppStandbyTrackerProto.FORCE_ALL_APPS_STANDBY, mForceAllAppsStandby);
- proto.write(ForceAppStandbyTrackerProto.IS_SMALL_BATTERY_DEVICE,
- isSmallBatteryDevice());
- proto.write(ForceAppStandbyTrackerProto.FORCE_ALL_APPS_STANDBY_FOR_SMALL_BATTERY,
- mForceAllAppStandbyForSmallBattery);
- proto.write(ForceAppStandbyTrackerProto.IS_CHARGING, mIsCharging);
for (int i = 0; i < mForegroundUids.size(); i++) {
if (mForegroundUids.valueAt(i)) {
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 55046959..21137ad 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -51,6 +51,7 @@
import org.xmlpull.v1.XmlSerializer;
import android.Manifest;
+import android.annotation.AnyThread;
import android.annotation.BinderThread;
import android.annotation.ColorInt;
import android.annotation.IntDef;
@@ -110,6 +111,7 @@
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -256,6 +258,44 @@
private static final String ACTION_SHOW_INPUT_METHOD_PICKER =
"com.android.server.InputMethodManagerService.SHOW_INPUT_METHOD_PICKER";
+ /**
+ * Debug flag for overriding runtime {@link SystemProperties}.
+ */
+ @AnyThread
+ private static final class DebugFlag {
+ private static final Object LOCK = new Object();
+ private final String mKey;
+ @GuardedBy("LOCK")
+ private boolean mValue;
+
+ public DebugFlag(String key) {
+ mKey = key;
+ refresh();
+ }
+
+ void refresh() {
+ synchronized (LOCK) {
+ mValue = SystemProperties.getBoolean(mKey, true);
+ }
+ }
+
+ boolean value() {
+ synchronized (LOCK) {
+ return mValue;
+ }
+ }
+ }
+
+ /**
+ * Debug flags that can be overridden using "adb shell setprop <key>"
+ * Note: These flags are cached. To refresh, run "adb shell ime refresh_debug_properties".
+ */
+ private static final class DebugFlags {
+ static final DebugFlag FLAG_OPTIMIZE_START_INPUT =
+ new DebugFlag("debug.optimize_startinput");
+ }
+
+
final Context mContext;
final Resources mRes;
final Handler mHandler;
@@ -2930,8 +2970,12 @@
}
if (!didStart && attribute != null) {
- res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
- controlFlags, startInputReason);
+ if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value()
+ || (controlFlags
+ & InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0) {
+ res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
+ controlFlags, startInputReason);
+ }
}
}
} finally {
@@ -4703,6 +4747,8 @@
return mService.handleShellCommandSetInputMethod(this);
case "reset":
return mService.handleShellCommandResetInputMethod(this);
+ case "refresh_debug_properties":
+ return refreshDebugProperties();
default:
getOutPrintWriter().println("Unknown command: " + imeCommand);
return ShellCommandResult.FAILURE;
@@ -4713,6 +4759,13 @@
}
@BinderThread
+ @ShellCommandResult
+ private int refreshDebugProperties() {
+ DebugFlags.FLAG_OPTIMIZE_START_INPUT.refresh();
+ return ShellCommandResult.SUCCESS;
+ }
+
+ @BinderThread
@Override
public void onHelp() {
try (PrintWriter pw = getOutPrintWriter()) {
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index ea748db..6c63f43 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -232,10 +232,9 @@
private final ArraySet<String> mBackgroundThrottlePackageWhitelist = new ArraySet<>();
- private final ArrayMap<IGnssMeasurementsListener, Identity> mGnssMeasurementsListeners =
- new ArrayMap<>();
+ private final ArrayMap<IBinder, Identity> mGnssMeasurementsListeners = new ArrayMap<>();
- private final ArrayMap<IGnssNavigationMessageListener, Identity>
+ private final ArrayMap<IBinder, Identity>
mGnssNavigationMessageListeners = new ArrayMap<>();
// current active user on the device - other users are denied location data
@@ -438,23 +437,23 @@
applyRequirementsLocked(provider);
}
- for (Entry<IGnssMeasurementsListener, Identity> entry
- : mGnssMeasurementsListeners.entrySet()) {
+ for (Entry<IBinder, Identity> entry : mGnssMeasurementsListeners.entrySet()) {
if (entry.getValue().mUid == uid) {
if (D) {
Log.d(TAG, "gnss measurements listener from uid " + uid
+ " is now " + (foreground ? "foreground" : "background)"));
}
if (foreground || isThrottlingExemptLocked(entry.getValue())) {
- mGnssMeasurementsProvider.addListener(entry.getKey());
+ mGnssMeasurementsProvider.addListener(
+ IGnssMeasurementsListener.Stub.asInterface(entry.getKey()));
} else {
- mGnssMeasurementsProvider.removeListener(entry.getKey());
+ mGnssMeasurementsProvider.removeListener(
+ IGnssMeasurementsListener.Stub.asInterface(entry.getKey()));
}
}
}
- for (Entry<IGnssNavigationMessageListener, Identity> entry
- : mGnssNavigationMessageListeners.entrySet()) {
+ for (Entry<IBinder, Identity> entry : mGnssNavigationMessageListeners.entrySet()) {
if (entry.getValue().mUid == uid) {
if (D) {
Log.d(TAG, "gnss navigation message listener from uid "
@@ -462,9 +461,11 @@
+ (foreground ? "foreground" : "background)"));
}
if (foreground || isThrottlingExemptLocked(entry.getValue())) {
- mGnssNavigationMessageProvider.addListener(entry.getKey());
+ mGnssNavigationMessageProvider.addListener(
+ IGnssNavigationMessageListener.Stub.asInterface(entry.getKey()));
} else {
- mGnssNavigationMessageProvider.removeListener(entry.getKey());
+ mGnssNavigationMessageProvider.removeListener(
+ IGnssNavigationMessageListener.Stub.asInterface(entry.getKey()));
}
}
}
@@ -2401,7 +2402,7 @@
synchronized (mLock) {
Identity callerIdentity
= new Identity(Binder.getCallingUid(), Binder.getCallingPid(), packageName);
- mGnssMeasurementsListeners.put(listener, callerIdentity);
+ mGnssMeasurementsListeners.put(listener.asBinder(), callerIdentity);
long identity = Binder.clearCallingIdentity();
try {
if (isThrottlingExemptLocked(callerIdentity)
@@ -2421,7 +2422,7 @@
public void removeGnssMeasurementsListener(IGnssMeasurementsListener listener) {
if (mGnssMeasurementsProvider != null) {
synchronized (mLock) {
- mGnssMeasurementsListeners.remove(listener);
+ mGnssMeasurementsListeners.remove(listener.asBinder());
mGnssMeasurementsProvider.removeListener(listener);
}
}
@@ -2438,7 +2439,7 @@
synchronized (mLock) {
Identity callerIdentity
= new Identity(Binder.getCallingUid(), Binder.getCallingPid(), packageName);
- mGnssNavigationMessageListeners.put(listener, callerIdentity);
+ mGnssNavigationMessageListeners.put(listener.asBinder(), callerIdentity);
long identity = Binder.clearCallingIdentity();
try {
if (isThrottlingExemptLocked(callerIdentity)
@@ -2458,7 +2459,7 @@
public void removeGnssNavigationMessageListener(IGnssNavigationMessageListener listener) {
if (mGnssNavigationMessageProvider != null) {
synchronized (mLock) {
- mGnssNavigationMessageListeners.remove(listener);
+ mGnssNavigationMessageListeners.remove(listener.asBinder());
mGnssNavigationMessageProvider.removeListener(listener);
}
}
@@ -3180,6 +3181,16 @@
pw.println(" " + record);
}
}
+ pw.println(" Active GnssMeasurement Listeners:");
+ for (Identity identity : mGnssMeasurementsListeners.values()) {
+ pw.println(" " + identity.mPid + " " + identity.mUid + " "
+ + identity.mPackageName + ": " + isThrottlingExemptLocked(identity));
+ }
+ pw.println(" Active GnssNavigationMessage Listeners:");
+ for (Identity identity : mGnssNavigationMessageListeners.values()) {
+ pw.println(" " + identity.mPid + " " + identity.mUid + " "
+ + identity.mPackageName + ": " + isThrottlingExemptLocked(identity));
+ }
pw.println(" Overlay Provider Packages:");
for (LocationProviderInterface provider : mProviders) {
if (provider instanceof LocationProviderProxy) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 65bebc6..1a47aa5 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -795,11 +795,13 @@
*/
private void stopGuestOrEphemeralUserIfBackground(int oldUserId) {
if (DEBUG_MU) Slog.i(TAG, "Stop guest or ephemeral user if background: " + oldUserId);
- UserState oldUss = mStartedUsers.get(oldUserId);
- if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId || oldUss == null
- || oldUss.state == UserState.STATE_STOPPING
- || oldUss.state == UserState.STATE_SHUTDOWN) {
- return;
+ synchronized(mLock) {
+ UserState oldUss = mStartedUsers.get(oldUserId);
+ if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId || oldUss == null
+ || oldUss.state == UserState.STATE_STOPPING
+ || oldUss.state == UserState.STATE_SHUTDOWN) {
+ return;
+ }
}
UserInfo userInfo = getUserInfo(oldUserId);
diff --git a/services/core/java/com/android/server/job/JobSchedulerInternal.java b/services/core/java/com/android/server/job/JobSchedulerInternal.java
index c97eeaf..945726d 100644
--- a/services/core/java/com/android/server/job/JobSchedulerInternal.java
+++ b/services/core/java/com/android/server/job/JobSchedulerInternal.java
@@ -16,6 +16,7 @@
package com.android.server.job;
+import android.annotation.UserIdInt;
import android.app.job.JobInfo;
import java.util.List;
@@ -39,6 +40,14 @@
long nextHeartbeatForBucket(int bucket);
/**
+ * Heartbeat ordinal for the given app. This is typically the heartbeat at which
+ * the app last ran jobs, so that a newly-scheduled job in an app that hasn't run
+ * jobs in a long time is immediately runnable even if the app is bucketed into
+ * an infrequent time allocation.
+ */
+ public long baseHeartbeatForApp(String packageName, @UserIdInt int userId, int appBucket);
+
+ /**
* Returns a list of pending jobs scheduled by the system service.
*/
List<JobInfo> getSystemScheduledPendingJobs();
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index bd1dbf9..91227c1 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -19,6 +19,7 @@
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
+import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AppGlobals;
@@ -38,6 +39,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.Intent.UriFlags;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
@@ -2020,6 +2022,29 @@
}
/**
+ * Heartbeat ordinal for the given app. This is typically the heartbeat at which
+ * the app last ran jobs, so that a newly-scheduled job in an app that hasn't run
+ * jobs in a long time is immediately runnable even if the app is bucketed into
+ * an infrequent time allocation.
+ */
+ public long baseHeartbeatForApp(String packageName, @UserIdInt int userId,
+ final int appStandbyBucket) {
+ if (appStandbyBucket == 0) {
+ // Active => everything can be run right away
+ return 0;
+ }
+ final long timeSinceLastJob = mStandbyTracker.getTimeSinceLastJobRun(
+ packageName, userId);
+ final long bucketLength = mConstants.STANDBY_BEATS[appStandbyBucket];
+ final long bucketsAgo = timeSinceLastJob / bucketLength;
+
+ // If we haven't run any jobs for more than the app's current bucket period, just
+ // consider anything new to be immediately runnable. Otherwise, base it on the
+ // bucket at which we last ran jobs.
+ return (bucketsAgo > bucketLength) ? 0 : (getCurrentHeartbeat() - bucketsAgo);
+ }
+
+ /**
* Returns a list of all pending jobs. A running job is not considered pending. Periodic
* jobs are always considered pending.
*/
@@ -2094,10 +2119,14 @@
mUsageStats = usageStats;
}
+ public long getTimeSinceLastJobRun(String packageName, final @UserIdInt int userId) {
+ return mUsageStats.getTimeSinceLastJobRun(packageName, userId);
+ }
+
// AppIdleStateChangeListener interface for live updates
@Override
- public void onAppIdleStateChanged(final String packageName, final int userId,
+ public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId,
boolean idle, int bucket) {
final int uid = mLocalPM.getPackageUid(packageName,
PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 8f68713..1add1ca 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -391,7 +391,9 @@
int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage,
sourceUserId, elapsedNow);
JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
- long currentHeartbeat = js != null ? js.currentHeartbeat() : 0;
+ long currentHeartbeat = js != null
+ ? js.baseHeartbeatForApp(jobPackage, sourceUserId, standbyBucket)
+ : 0;
return new JobStatus(job, callingUid, sourcePkg, sourceUserId,
standbyBucket, currentHeartbeat, tag, 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index a29e1be..1ba0d6e 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -228,6 +228,7 @@
counterId = generateAndStoreCounterId(recoveryAgentUid);
}
}
+
byte[] vaultParams = KeySyncUtils.packVaultParams(
publicKey,
counterId,
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
index 219f6e1..89e2deb 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
@@ -300,7 +300,7 @@
.put(SecureBox.encodePublicKey(thmPublicKey))
.putLong(counterId)
.putInt(maxAttempts)
- .put(new byte[VAULT_HANDLE_LENGTH_BYTES]) // TODO: replace with real vaultHandle
+ .put(vaultHandle)
.array();
}
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index de723c6..d84fbc5 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -185,6 +185,14 @@
ComponentName component = null;
synchronized (mLock) {
component = ((mCurrentVrService == null) ? null : mCurrentVrService.getComponent());
+
+ // If the VrCore main service was disconnected or the binding died we'll rebind
+ // automatically. Call focusedActivityChanged() once we rebind.
+ if (component != null && component.equals(event.component) &&
+ (event.event == LogEvent.EVENT_DISCONNECTED ||
+ event.event == LogEvent.EVENT_BINDING_DIED)) {
+ callFocusedActivityChangedLocked();
+ }
}
// If not on an AIO device and we permanently stopped trying to connect to the
@@ -980,16 +988,7 @@
oldVrServicePackage, oldUserId);
if (mCurrentVrService != null && sendUpdatedCaller) {
- final ComponentName c = mCurrentVrModeComponent;
- final boolean b = running2dInVr;
- final int pid = processId;
- mCurrentVrService.sendEvent(new PendingEvent() {
- @Override
- public void runEvent(IInterface service) throws RemoteException {
- IVrListener l = (IVrListener) service;
- l.focusedActivityChanged(c, b, pid);
- }
- });
+ callFocusedActivityChangedLocked();
}
if (!nothingChanged) {
@@ -1002,6 +1001,23 @@
}
}
+ private void callFocusedActivityChangedLocked() {
+ final ComponentName c = mCurrentVrModeComponent;
+ final boolean b = mRunning2dInVr;
+ final int pid = mVrAppProcessId;
+ mCurrentVrService.sendEvent(new PendingEvent() {
+ @Override
+ public void runEvent(IInterface service) throws RemoteException {
+ // Under specific (and unlikely) timing scenarios, when VrCore
+ // crashes and is rebound, focusedActivityChanged() may be
+ // called a 2nd time with the same arguments. IVrListeners
+ // should make sure to handle that scenario gracefully.
+ IVrListener l = (IVrListener) service;
+ l.focusedActivityChanged(c, b, pid);
+ }
+ });
+ }
+
private boolean isDefaultAllowed(String packageName) {
PackageManager pm = mContext.getPackageManager();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ef48661..fba404e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -134,6 +134,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
+import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.MutableBoolean;
import android.util.Slog;
@@ -330,6 +331,8 @@
final PinnedStackController mPinnedStackControllerLocked;
final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>();
+ /** A collection of windows that provide tap exclude regions inside of them. */
+ final ArraySet<WindowState> mTapExcludeProvidingWindows = new ArraySet<>();
private boolean mHaveBootMsg = false;
private boolean mHaveApp = false;
@@ -1866,10 +1869,14 @@
}
}
for (int i = mTapExcludedWindows.size() - 1; i >= 0; i--) {
- WindowState win = mTapExcludedWindows.get(i);
+ final WindowState win = mTapExcludedWindows.get(i);
win.getTouchableRegion(mTmpRegion);
mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION);
}
+ for (int i = mTapExcludeProvidingWindows.size() - 1; i >= 0; i--) {
+ final WindowState win = mTapExcludeProvidingWindows.valueAt(i);
+ win.amendTapExcludeRegion(mTouchExcludeRegion);
+ }
// TODO(multi-display): Support docked stacks on secondary displays.
if (mDisplayId == DEFAULT_DISPLAY && getSplitScreenPrimaryStack() != null) {
mDividerControllerLocked.getTouchRegion(mTmpRect);
@@ -3690,6 +3697,13 @@
.setParent(mOverlayLayer);
}
+ /**
+ * Reparents the given surface to mOverlayLayer.
+ */
+ void reparentToOverlay(Transaction transaction, SurfaceControl surface) {
+ transaction.reparent(surface, mOverlayLayer.getHandle());
+ }
+
void applyMagnificationSpec(MagnificationSpec spec) {
applyMagnificationSpec(getPendingTransaction(), spec);
getPendingTransaction().apply();
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 0171b56..d55a649 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -18,12 +18,10 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
import android.content.ClipData;
-import android.graphics.PixelFormat;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -32,8 +30,8 @@
import android.util.Slog;
import android.view.Display;
import android.view.IWindow;
-import android.view.Surface;
import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
import android.view.SurfaceSession;
import android.view.View;
@@ -50,10 +48,9 @@
private static final long DRAG_TIMEOUT_MS = 5000;
// Messages for Handler.
- private static final int MSG_DRAG_START_TIMEOUT = 0;
- static final int MSG_DRAG_END_TIMEOUT = 1;
- static final int MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT = 2;
- static final int MSG_ANIMATION_END = 3;
+ static final int MSG_DRAG_END_TIMEOUT = 0;
+ static final int MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT = 1;
+ static final int MSG_ANIMATION_END = 2;
/**
* Drag state per operation.
@@ -95,87 +92,35 @@
mDragState.sendDragStartedIfNeededLocked(window);
}
- IBinder prepareDrag(SurfaceSession session, int callerPid,
- int callerUid, IWindow window, int flags, int width, int height, Surface outSurface) {
+ IBinder performDrag(SurfaceSession session, int callerPid, int callerUid, IWindow window,
+ int flags, SurfaceControl surface, int touchSource, float touchX, float touchY,
+ float thumbCenterX, float thumbCenterY, ClipData data) {
if (DEBUG_DRAG) {
- Slog.d(TAG_WM, "prepare drag surface: w=" + width + " h=" + height
- + " flags=" + Integer.toHexString(flags) + " win=" + window
- + " asbinder=" + window.asBinder());
+ Slog.d(TAG_WM, "perform drag: win=" + window + " surface=" + surface + " flags=" +
+ Integer.toHexString(flags) + " data=" + data);
}
- if (width <= 0 || height <= 0) {
- Slog.w(TAG_WM, "width and height of drag shadow must be positive");
- return null;
- }
-
- synchronized (mService.mWindowMap) {
- if (dragDropActiveLocked()) {
- Slog.w(TAG_WM, "Drag already in progress");
- return null;
- }
-
- // TODO(multi-display): support other displays
- final DisplayContent displayContent =
- mService.getDefaultDisplayContentLocked();
- final Display display = displayContent.getDisplay();
-
- final SurfaceControl surface = new SurfaceControl.Builder(session)
- .setName("drag surface")
- .setSize(width, height)
- .setFormat(PixelFormat.TRANSLUCENT)
- .build();
- surface.setLayerStack(display.getLayerStack());
- float alpha = 1;
- if ((flags & View.DRAG_FLAG_OPAQUE) == 0) {
- alpha = DRAG_SHADOW_ALPHA_TRANSPARENT;
- }
- surface.setAlpha(alpha);
-
- if (SHOW_TRANSACTIONS)
- Slog.i(TAG_WM, " DRAG " + surface + ": CREATE");
- outSurface.copyFrom(surface);
- final IBinder winBinder = window.asBinder();
- IBinder token = new Binder();
- mDragState = new DragState(mService, this, token, surface, flags, winBinder);
- mDragState.mPid = callerPid;
- mDragState.mUid = callerUid;
- mDragState.mOriginalAlpha = alpha;
- token = mDragState.mToken = new Binder();
-
- // 5 second timeout for this window to actually begin the drag
- sendTimeoutMessage(MSG_DRAG_START_TIMEOUT, winBinder);
- return token;
- }
- }
-
- boolean performDrag(IWindow window, IBinder dragToken,
- int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
- ClipData data) {
- if (DEBUG_DRAG) {
- Slog.d(TAG_WM, "perform drag: win=" + window + " data=" + data);
- }
-
+ final IBinder dragToken = new Binder();
final boolean callbackResult = mCallback.get().prePerformDrag(window, dragToken,
touchSource, touchX, touchY, thumbCenterX, thumbCenterY, data);
try {
synchronized (mService.mWindowMap) {
- mHandler.removeMessages(MSG_DRAG_START_TIMEOUT, window.asBinder());
try {
if (!callbackResult) {
- return false;
+ Slog.w(TAG_WM, "IDragDropCallback rejects the performDrag request");
+ return null;
}
- Preconditions.checkState(
- mDragState != null, "performDrag() without prepareDrag()");
- Preconditions.checkState(
- mDragState.mToken == dragToken,
- "performDrag() does not match prepareDrag()");
+ if (dragDropActiveLocked()) {
+ Slog.w(TAG_WM, "Drag already in progress");
+ return null;
+ }
final WindowState callingWin = mService.windowForClientLocked(
null, window, false);
if (callingWin == null) {
Slog.w(TAG_WM, "Bad requesting window " + window);
- return false; // !!! TODO: throw here?
+ return null; // !!! TODO: throw here?
}
// !!! TODO: if input is not still focused on the initiating window, fail
@@ -188,18 +133,31 @@
// !!! FIXME: put all this heavy stuff onto the mHandler looper, as well as
// the actual drag event dispatch stuff in the dragstate
+ // !!! TODO(multi-display): support other displays
+
final DisplayContent displayContent = callingWin.getDisplayContent();
if (displayContent == null) {
Slog.w(TAG_WM, "display content is null");
- return false;
+ return null;
}
+ final float alpha = (flags & View.DRAG_FLAG_OPAQUE) == 0 ?
+ DRAG_SHADOW_ALPHA_TRANSPARENT : 1;
+ final IBinder winBinder = window.asBinder();
+ IBinder token = new Binder();
+ mDragState = new DragState(mService, this, token, surface, flags, winBinder);
+ surface = null;
+ mDragState.mPid = callerPid;
+ mDragState.mUid = callerUid;
+ mDragState.mOriginalAlpha = alpha;
+ mDragState.mToken = dragToken;
+
final Display display = displayContent.getDisplay();
mDragState.register(display);
if (!mService.mInputManager.transferTouchFocus(callingWin.mInputChannel,
mDragState.getInputChannel())) {
Slog.e(TAG_WM, "Unable to transfer touch focus");
- return false;
+ return null;
}
mDragState.mDisplayContent = displayContent;
@@ -213,28 +171,31 @@
// Make the surface visible at the proper location
final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag");
- mService.openSurfaceTransaction();
- try {
- surfaceControl.setPosition(touchX - thumbCenterX,
- touchY - thumbCenterY);
- surfaceControl.setLayer(mDragState.getDragLayerLocked());
- surfaceControl.setLayerStack(display.getLayerStack());
- surfaceControl.show();
- } finally {
- mService.closeSurfaceTransaction("performDrag");
- if (SHOW_LIGHT_TRANSACTIONS) {
- Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag");
- }
+
+ final SurfaceControl.Transaction transaction =
+ callingWin.getPendingTransaction();
+ transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha);
+ transaction.setPosition(
+ surfaceControl, touchX - thumbCenterX, touchY - thumbCenterY);
+ transaction.show(surfaceControl);
+ displayContent.reparentToOverlay(transaction, surfaceControl);
+ callingWin.scheduleAnimation();
+
+ if (SHOW_LIGHT_TRANSACTIONS) {
+ Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag");
}
mDragState.notifyLocationLocked(touchX, touchY);
} finally {
+ if (surface != null) {
+ surface.release();
+ }
if (mDragState != null && !mDragState.isInProgress()) {
mDragState.closeLocked();
}
}
}
- return true; // success!
+ return dragToken; // success!
} finally {
mCallback.get().postPerformDrag();
}
@@ -385,21 +346,6 @@
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_DRAG_START_TIMEOUT: {
- IBinder win = (IBinder) msg.obj;
- if (DEBUG_DRAG) {
- Slog.w(TAG_WM, "Timeout starting drag by win " + win);
- }
-
- synchronized (mService.mWindowMap) {
- // !!! TODO: ANR the app that has failed to start the drag in time
- if (mDragState != null) {
- mDragState.closeLocked();
- }
- }
- break;
- }
-
case MSG_DRAG_END_TIMEOUT: {
final IBinder win = (IBinder) msg.obj;
if (DEBUG_DRAG) {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 192d6c8..04ae38e 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -51,6 +51,7 @@
import android.view.IWindowSessionCallback;
import android.view.InputChannel;
import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
@@ -308,30 +309,22 @@
}
/* Drag/drop */
+
@Override
- public IBinder prepareDrag(IWindow window, int flags, int width, int height,
- Surface outSurface) {
+ public IBinder performDrag(IWindow window, int flags, SurfaceControl surface, int touchSource,
+ float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data) {
final int callerPid = Binder.getCallingPid();
final int callerUid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
- return mDragDropController.prepareDrag(
- mSurfaceSession, callerPid, callerUid, window, flags, width, height,
- outSurface);
+ return mDragDropController.performDrag(mSurfaceSession, callerPid, callerUid, window,
+ flags, surface, touchSource, touchX, touchY, thumbCenterX, thumbCenterY, data);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
- public boolean performDrag(IWindow window, IBinder dragToken,
- int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
- ClipData data) {
- return mDragDropController.performDrag(window, dragToken, touchSource,
- touchX, touchY, thumbCenterX, thumbCenterY, data);
- }
-
- @Override
public void reportDropResult(IWindow window, boolean consumed) {
final long ident = Binder.clearCallingIdentity();
try {
@@ -467,6 +460,17 @@
}
}
+ @Override
+ public void updateTapExcludeRegion(IWindow window, int regionId, int left, int top, int width,
+ int height) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mService.updateTapExcludeRegion(window, regionId, left, top, width, height);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
void windowAddedLocked(String packageName) {
mPackageName = packageName;
mRelayoutTag = "relayoutWindow: " + mPackageName;
diff --git a/services/core/java/com/android/server/wm/TapExcludeRegionHolder.java b/services/core/java/com/android/server/wm/TapExcludeRegionHolder.java
new file mode 100644
index 0000000..cbc936f
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TapExcludeRegionHolder.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.util.SparseArray;
+
+/**
+ * A holder that contains a collection of rectangular areas identified by int id. Each individual
+ * region can be updated separately.
+ */
+class TapExcludeRegionHolder {
+ private SparseArray<Rect> mTapExcludeRects = new SparseArray<>();
+
+ /** Update the specified region with provided position and size. */
+ void updateRegion(int regionId, int left, int top, int width, int height) {
+ if (width <= 0 || height <= 0) {
+ // A region became empty - remove it.
+ mTapExcludeRects.remove(regionId);
+ return;
+ }
+
+ Rect region = mTapExcludeRects.get(regionId);
+ if (region == null) {
+ region = new Rect();
+ }
+ region.set(left, top, left + width, top + height);
+ mTapExcludeRects.put(regionId, region);
+ }
+
+ /**
+ * Union the provided region with current region formed by this container.
+ */
+ void amendRegion(Region region, Rect boundingRegion) {
+ for (int i = mTapExcludeRects.size() - 1; i>= 0 ; --i) {
+ final Rect rect = mTapExcludeRects.valueAt(i);
+ rect.intersect(boundingRegion);
+ region.union(rect);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index fa7ea2f..26c87b7 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -59,6 +59,8 @@
private static final String TAG_LOCAL = "TaskPositioner";
private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
+ private static Factory sFactory;
+
// The margin the pointer position has to be within the side of the screen to be
// considered at the side of the screen.
static final int SIDE_MARGIN_DIP = 100;
@@ -214,6 +216,7 @@
}
}
+ /** Use {@link #create(WindowManagerService)} instead **/
TaskPositioner(WindowManagerService service) {
mService = service;
}
@@ -622,4 +625,22 @@
public String toShortString() {
return TAG;
}
+
+ static void setFactory(Factory factory) {
+ sFactory = factory;
+ }
+
+ static TaskPositioner create(WindowManagerService service) {
+ if (sFactory == null) {
+ sFactory = new Factory() {};
+ }
+
+ return sFactory.create(service);
+ }
+
+ interface Factory {
+ default TaskPositioner create(WindowManagerService service) {
+ return new TaskPositioner(service);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java
index 4dfe290..a3f4ee8 100644
--- a/services/core/java/com/android/server/wm/TaskPositioningController.java
+++ b/services/core/java/com/android/server/wm/TaskPositioningController.java
@@ -126,7 +126,7 @@
}
Display display = displayContent.getDisplay();
- mTaskPositioner = new TaskPositioner(mService);
+ mTaskPositioner = TaskPositioner.create(mService);
mTaskPositioner.register(displayContent);
mInputMonitor.updateInputWindowsLw(true /*force*/);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 10e7893..53086f7 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6922,6 +6922,23 @@
}
}
+ /**
+ * Update a tap exclude region with a rectangular area in the window identified by the provided
+ * id. Touches on this region will not switch focus to this window. Passing an empty rect will
+ * remove the area from the exclude region of this window.
+ */
+ void updateTapExcludeRegion(IWindow client, int regionId, int left, int top, int width,
+ int height) {
+ synchronized (mWindowMap) {
+ final WindowState callingWin = windowForClientLocked(null, client, false);
+ if (callingWin == null) {
+ Slog.w(TAG_WM, "Bad requesting window " + client);
+ return;
+ }
+ callingWin.updateTapExcludeRegion(regionId, left, top, width, height);
+ }
+ }
+
@Override
public void registerShortcutKey(long shortcutCode, IShortcutService shortcutKeyReceiver)
throws RemoteException {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index db30db0..477dd2b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -630,6 +630,11 @@
private final Point mSurfacePosition = new Point();
/**
+ * A region inside of this window to be excluded from touch-related focus switches.
+ */
+ private TapExcludeRegionHolder mTapExcludeRegionHolder;
+
+ /**
* Compares two window sub-layers and returns -1 if the first is lesser than the second in terms
* of z-order and 1 otherwise.
*/
@@ -1870,6 +1875,11 @@
if (WindowManagerService.excludeWindowTypeFromTapOutTask(type)) {
dc.mTapExcludedWindows.remove(this);
}
+ if (mTapExcludeRegionHolder != null) {
+ // If a tap exclude region container was initialized for this window, then it should've
+ // also been registered in display.
+ dc.mTapExcludeProvidingWindows.remove(this);
+ }
mPolicy.removeWindowLw(this);
disposeInputChannel();
@@ -4620,6 +4630,37 @@
}
}
+ /**
+ * Update a tap exclude region with a rectangular area identified by provided id. The requested
+ * area will be clipped to the window bounds.
+ */
+ void updateTapExcludeRegion(int regionId, int left, int top, int width, int height) {
+ final DisplayContent currentDisplay = getDisplayContent();
+ if (currentDisplay == null) {
+ throw new IllegalStateException("Trying to update window not attached to any display.");
+ }
+
+ if (mTapExcludeRegionHolder == null) {
+ mTapExcludeRegionHolder = new TapExcludeRegionHolder();
+
+ // Make sure that this window is registered as one that provides a tap exclude region
+ // for its containing display.
+ currentDisplay.mTapExcludeProvidingWindows.add(this);
+ }
+
+ mTapExcludeRegionHolder.updateRegion(regionId, left, top, width, height);
+ // Trigger touch exclude region update on current display.
+ final boolean isAppFocusedOnDisplay = mService.mFocusedApp != null
+ && mService.mFocusedApp.getDisplayContent() == currentDisplay;
+ currentDisplay.setTouchExcludeRegion(isAppFocusedOnDisplay ? mService.mFocusedApp.getTask()
+ : null);
+ }
+
+ /** Union the region with current tap exclude region that this window provides. */
+ void amendTapExcludeRegion(Region region) {
+ mTapExcludeRegionHolder.amendRegion(region, getBounds());
+ }
+
private final class MoveAnimationSpec implements AnimationSpec {
private final long mDuration;
diff --git a/services/robotests/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
index cf0bc23..6753d73 100644
--- a/services/robotests/src/com/android/server/backup/TransportManagerTest.java
+++ b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
@@ -19,9 +19,13 @@
import static com.android.server.backup.testing.TransportData.genericTransport;
import static com.android.server.backup.testing.TransportTestUtils.mockTransport;
import static com.android.server.backup.testing.TransportTestUtils.setUpTransportsForTransportManager;
-
import static com.google.common.truth.Truth.assertThat;
-
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+import static java.util.stream.Stream.concat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
@@ -31,23 +35,15 @@
import static org.robolectric.shadow.api.Shadow.extract;
import static org.testng.Assert.expectThrows;
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
-import static java.util.stream.Collectors.toList;
-import static java.util.stream.Collectors.toSet;
-import static java.util.stream.Stream.concat;
-
import android.annotation.Nullable;
+import android.app.backup.BackupManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.platform.test.annotations.Presubmit;
-
import com.android.server.backup.testing.ShadowContextImplForBackup;
-import com.android.server.testing.shadows.FrameworkShadowPackageManager;
import com.android.server.backup.testing.TransportData;
import com.android.server.backup.testing.TransportTestUtils.TransportMock;
import com.android.server.backup.transport.OnTransportRegisteredListener;
@@ -57,7 +53,12 @@
import com.android.server.testing.FrameworkRobolectricTestRunner;
import com.android.server.testing.SystemLoaderClasses;
import com.android.server.testing.shadows.FrameworkShadowContextImpl;
-
+import com.android.server.testing.shadows.FrameworkShadowPackageManager;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Stream;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -68,12 +69,6 @@
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowPackageManager;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.stream.Stream;
-
@RunWith(FrameworkRobolectricTestRunner.class)
@Config(
manifest = Config.NONE,
@@ -308,6 +303,43 @@
}
@Test
+ public void testRegisterAndSelectTransport_whenTransportRegistered() throws Exception {
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1);
+ TransportManager transportManager = createTransportManager(null, mTransportA1);
+ transportManager.registerTransports();
+ ComponentName transportComponent = mTransportA1.getTransportComponent();
+
+ int result = transportManager.registerAndSelectTransport(transportComponent);
+
+ assertThat(result).isEqualTo(BackupManager.SUCCESS);
+ assertThat(transportManager.getRegisteredTransportComponents())
+ .asList()
+ .contains(transportComponent);
+ assertThat(transportManager.getCurrentTransportName())
+ .isEqualTo(mTransportA1.transportName);
+ }
+
+ @Test
+ public void testRegisterAndSelectTransport_whenTransportNotRegistered() throws Exception {
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1);
+ TransportManager transportManager = createTransportManager(null, mTransportA1);
+ ComponentName transportComponent = mTransportA1.getTransportComponent();
+
+ int result = transportManager.registerAndSelectTransport(transportComponent);
+
+ assertThat(result).isEqualTo(BackupManager.SUCCESS);
+ assertThat(transportManager.getRegisteredTransportComponents())
+ .asList()
+ .contains(transportComponent);
+ assertThat(transportManager.getTransportDirName(mTransportA1.transportName))
+ .isEqualTo(mTransportA1.transportDirName);
+ assertThat(transportManager.getCurrentTransportName())
+ .isEqualTo(mTransportA1.transportName);
+ }
+
+ @Test
public void testGetCurrentTransportName_whenSelectTransportNotCalled_returnsDefaultTransport()
throws Exception {
setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
diff --git a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
index 6a21931..66d0da1 100644
--- a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
@@ -42,7 +42,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.os.BatteryManager;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager.ServiceType;
@@ -51,17 +50,13 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.provider.Settings;
-import android.provider.Settings.Global;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import android.test.mock.MockContentResolver;
import android.util.ArraySet;
import android.util.Pair;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
-import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.ForceAppStandbyTracker.Listener;
import org.junit.Before;
@@ -107,9 +102,6 @@
PowerManagerInternal injectPowerManagerInternal() {
return mMockPowerManagerInternal;
}
-
- @Override
- boolean isSmallBatteryDevice() { return mIsSmallBatteryDevice; };
}
private static final int UID_1 = Process.FIRST_APPLICATION_UID + 1;
@@ -145,11 +137,7 @@
private Consumer<PowerSaveState> mPowerSaveObserver;
private BroadcastReceiver mReceiver;
- private MockContentResolver mMockContentResolver;
- private FakeSettingsProvider mFakeSettingsProvider;
-
private boolean mPowerSaveMode;
- private boolean mIsSmallBatteryDevice;
private final ArraySet<Pair<Integer, String>> mRestrictedPackages = new ArraySet();
@@ -186,17 +174,13 @@
}
private void callStart(ForceAppStandbyTrackerTestable instance) throws RemoteException {
+
// Set up functions that start() calls.
when(mMockPowerManagerInternal.getLowPowerState(eq(ServiceType.FORCE_ALL_APPS_STANDBY)))
.thenAnswer(inv -> getPowerSaveState());
when(mMockAppOpsManager.getPackagesForOps(
any(int[].class)
- )).thenAnswer(inv -> new ArrayList<AppOpsManager.PackageOps>());
-
- mMockContentResolver = new MockContentResolver();
- mFakeSettingsProvider = new FakeSettingsProvider();
- when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
- mMockContentResolver.addProvider(Settings.AUTHORITY, mFakeSettingsProvider);
+ )).thenAnswer(inv -> new ArrayList<AppOpsManager.PackageOps>());
// Call start.
instance.start();
@@ -224,6 +208,7 @@
verify(mMockPowerManagerInternal).registerLowPowerModeObserver(
eq(ServiceType.FORCE_ALL_APPS_STANDBY),
powerSaveObserverCaptor.capture());
+
verify(mMockContext).registerReceiver(
receiverCaptor.capture(), any(IntentFilter.class));
@@ -236,7 +221,6 @@
assertNotNull(mAppOpsCallback);
assertNotNull(mPowerSaveObserver);
assertNotNull(mReceiver);
- assertNotNull(instance.mFlagsObserver);
}
private void setAppOps(int uid, String packageName, boolean restrict) throws RemoteException {
@@ -838,33 +822,6 @@
assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2));
}
- @Test
- public void testSmallBatteryAndCharging() throws Exception {
- // This is a small battery device
- mIsSmallBatteryDevice = true;
-
- final ForceAppStandbyTrackerTestable instance = newInstance();
- callStart(instance);
- assertFalse(instance.isForceAllAppsStandbyEnabled());
-
- // Setting/experiment for all app standby for small battery is enabled
- Global.putInt(mMockContentResolver, Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED, 1);
- instance.mFlagsObserver.onChange(true,
- Global.getUriFor(Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED));
- assertTrue(instance.isForceAllAppsStandbyEnabled());
-
- // When battery is charging, force app standby is disabled
- Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
- intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING);
- mReceiver.onReceive(mMockContext, intent);
- assertFalse(instance.isForceAllAppsStandbyEnabled());
-
- // When battery stops charging, force app standby is enabled
- intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_DISCHARGING);
- mReceiver.onReceive(mMockContext, intent);
- assertTrue(instance.isForceAllAppsStandbyEnabled());
- }
-
static int[] array(int... appIds) {
Arrays.sort(appIds);
return appIds;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index c6fc675..9f5f1e7 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -78,7 +78,7 @@
private static final int TEST_RECOVERY_AGENT_UID = 10009;
private static final int TEST_RECOVERY_AGENT_UID2 = 10010;
private static final byte[] TEST_VAULT_HANDLE =
- new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 14, 15, 16, 17};
+ new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17};
private static final String TEST_APP_KEY_ALIAS = "rcleaver";
private static final int TEST_GENERATION_ID = 2;
private static final int TEST_CREDENTIAL_TYPE = CREDENTIAL_TYPE_PASSWORD;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
index ba6b274..a251c9d 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
@@ -51,7 +51,7 @@
private static final int THM_KF_HASH_SIZE = 256;
private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
private static final byte[] TEST_VAULT_HANDLE =
- new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 14, 15, 16, 17};
+ new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17};
private static final String SHA_256_ALGORITHM = "SHA-256";
private static final String APPLICATION_KEY_ALGORITHM = "AES";
private static final byte[] LOCK_SCREEN_HASH_1 =
@@ -417,8 +417,7 @@
byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + Long.BYTES + Integer.BYTES);
byte[] vaultHandle = new byte[VAULT_HANDLE_LENGTH_BYTES];
byteBuffer.get(vaultHandle);
- // TODO: Fix this once we fix the code in the KeySyncUtils class
- assertArrayEquals(new byte[VAULT_HANDLE_LENGTH_BYTES], vaultHandle);
+ assertArrayEquals(TEST_VAULT_HANDLE, vaultHandle);
}
private static byte[] randomBytes(int n) {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index 402a37c..970bc33 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -330,7 +330,7 @@
TEST_VAULT_CHALLENGE,
ImmutableList.of());
fail("should have thrown");
- } catch (ServiceSpecificException e) {
+ } catch (UnsupportedOperationException e) {
assertThat(e.getMessage()).startsWith(
"Only a single KeychainProtectionParams is supported");
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java
index 0f95748..bb0474e 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java
@@ -125,7 +125,7 @@
storage.remove(TEST_USER_ID, TEST_SESSION_ID);
- assertNotNull(storage.get(TEST_USER_ID, TEST_SESSION_ID));
+ assertNotNull(storage.get(TEST_USER_ID, otherSessionId));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java
index ac29163..57da6a3 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java
@@ -20,7 +20,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
@@ -28,6 +27,7 @@
import static org.mockito.Mockito.when;
import android.content.ClipData;
+import android.graphics.PixelFormat;
import android.os.IBinder;
import android.os.Looper;
import android.os.UserHandle;
@@ -36,7 +36,7 @@
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.view.InputChannel;
-import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.View;
import com.android.internal.annotations.GuardedBy;
@@ -146,14 +146,6 @@
}
@Test
- public void testPrepareDrag_ZeroSizeSurface() throws Exception {
- final Surface surface = new Surface();
- mToken = mTarget.prepareDrag(
- new SurfaceSession(), 0, 0, mWindow.mClient, 0, 0, 0, surface);
- assertNull(mToken);
- }
-
- @Test
public void testPerformDrag_NullDataWithGrantUri() throws Exception {
dragFlow(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 0, 0);
}
@@ -169,16 +161,24 @@
}
private void dragFlow(int flag, ClipData data, float dropX, float dropY) {
- final Surface surface = new Surface();
- mToken = mTarget.prepareDrag(
- new SurfaceSession(), 0, 0, mWindow.mClient, flag, 100, 100, surface);
- assertNotNull(mToken);
+ final SurfaceSession appSession = new SurfaceSession();
+ try {
+ final SurfaceControl surface = new SurfaceControl.Builder(appSession)
+ .setName("drag surface")
+ .setSize(100, 100)
+ .setFormat(PixelFormat.TRANSLUCENT)
+ .build();
- assertTrue(sWm.mInputManager.transferTouchFocus(null, null));
- assertTrue(mTarget.performDrag(
- mWindow.mClient, mToken, 0, 0, 0, 0, 0, data));
+ assertTrue(sWm.mInputManager.transferTouchFocus(null, null));
+ mToken = mTarget.performDrag(
+ new SurfaceSession(), 0, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0,
+ data);
+ assertNotNull(mToken);
- mTarget.handleMotionEvent(false, dropX, dropY);
- mToken = mWindow.mClient.asBinder();
+ mTarget.handleMotionEvent(false, dropX, dropY);
+ mToken = mWindow.mClient.asBinder();
+ } finally {
+ appSession.kill();
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java
index 873a01b..7bf7dd7 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java
@@ -33,6 +33,7 @@
import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
@@ -57,6 +58,9 @@
@Before
public void setUp() throws Exception {
super.setUp();
+
+ TaskPositioner.setFactory(null);
+
final Display display = mDisplayContent.getDisplay();
final DisplayMetrics dm = new DisplayMetrics();
display.getMetrics(dm);
@@ -65,10 +69,26 @@
mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, dm);
mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, dm);
- mPositioner = new TaskPositioner(sWm);
+ mPositioner = TaskPositioner.create(sWm);
mPositioner.register(mDisplayContent);
}
+ @Test
+ public void testOverrideFactory() throws Exception {
+ final boolean[] created = new boolean[1];
+ created[0] = false;
+ TaskPositioner.setFactory(new TaskPositioner.Factory() {
+ @Override
+ public TaskPositioner create(WindowManagerService service) {
+ created[0] = true;
+ return null;
+ }
+ });
+
+ assertNull(TaskPositioner.create(sWm));
+ assertTrue(created[0]);
+ }
+
/**
* This tests that free resizing will allow to change the orientation as well
* as does some basic tests (e.g. dragging in Y only will keep X stable).
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 8edc8b1..de9e691 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2122,6 +2122,27 @@
* carrier restrictions.
*/
public static final int SIM_STATE_CARD_RESTRICTED = 9;
+ /**
+ * SIM card state: Loaded: SIM card applications have been loaded
+ * @hide
+ */
+ @SystemApi
+ public static final int SIM_STATE_LOADED = 10;
+ /**
+ * SIM card state: SIM Card is present
+ * @hide
+ */
+ @SystemApi
+ public static final int SIM_STATE_PRESENT = 11;
+
+ /**
+ * Extra included in {@link Intent.ACTION_SIM_CARD_STATE_CHANGED} and
+ * {@link Intent.ACTION_SIM_APPLICATION_STATE_CHANGED} to indicate the card/application state.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_SIM_STATE = "android.telephony.extra.SIM_STATE";
/**
* @return true if a ICC card is present
@@ -2168,6 +2189,14 @@
* @see #SIM_STATE_CARD_RESTRICTED
*/
public int getSimState() {
+ int simState = getSimStateIncludingLoaded();
+ if (simState == SIM_STATE_LOADED) {
+ simState = SIM_STATE_READY;
+ }
+ return simState;
+ }
+
+ private int getSimStateIncludingLoaded() {
int slotIndex = getSlotIndex();
// slotIndex may be invalid due to sim being absent. In that case query all slots to get
// sim state
@@ -2186,7 +2215,63 @@
"state as absent");
return SIM_STATE_ABSENT;
}
- return getSimState(slotIndex);
+ return SubscriptionManager.getSimStateForSlotIndex(slotIndex);
+ }
+
+ /**
+ * Returns a constant indicating the state of the default SIM card.
+ *
+ * @see #SIM_STATE_UNKNOWN
+ * @see #SIM_STATE_ABSENT
+ * @see #SIM_STATE_CARD_IO_ERROR
+ * @see #SIM_STATE_CARD_RESTRICTED
+ * @see #SIM_STATE_PRESENT
+ *
+ * @hide
+ */
+ @SystemApi
+ public int getSimCardState() {
+ int simCardState = getSimState();
+ switch (simCardState) {
+ case SIM_STATE_UNKNOWN:
+ case SIM_STATE_ABSENT:
+ case SIM_STATE_CARD_IO_ERROR:
+ case SIM_STATE_CARD_RESTRICTED:
+ return simCardState;
+ default:
+ return SIM_STATE_PRESENT;
+ }
+ }
+
+ /**
+ * Returns a constant indicating the state of the card applications on the default SIM card.
+ *
+ * @see #SIM_STATE_UNKNOWN
+ * @see #SIM_STATE_PIN_REQUIRED
+ * @see #SIM_STATE_PUK_REQUIRED
+ * @see #SIM_STATE_NETWORK_LOCKED
+ * @see #SIM_STATE_NOT_READY
+ * @see #SIM_STATE_PERM_DISABLED
+ * @see #SIM_STATE_LOADED
+ *
+ * @hide
+ */
+ @SystemApi
+ public int getSimApplicationState() {
+ int simApplicationState = getSimStateIncludingLoaded();
+ switch (simApplicationState) {
+ case SIM_STATE_UNKNOWN:
+ case SIM_STATE_ABSENT:
+ case SIM_STATE_CARD_IO_ERROR:
+ case SIM_STATE_CARD_RESTRICTED:
+ return SIM_STATE_UNKNOWN;
+ case SIM_STATE_READY:
+ // Ready is not a valid state anymore. The state that is broadcast goes from
+ // NOT_READY to either LOCKED or LOADED.
+ return SIM_STATE_NOT_READY;
+ default:
+ return simApplicationState;
+ }
}
/**
@@ -2207,6 +2292,9 @@
*/
public int getSimState(int slotIndex) {
int simState = SubscriptionManager.getSimStateForSlotIndex(slotIndex);
+ if (simState == SIM_STATE_LOADED) {
+ simState = SIM_STATE_READY;
+ }
return simState;
}
diff --git a/telephony/java/android/telephony/ims/feature/MMTelFeature.java b/telephony/java/android/telephony/ims/feature/MMTelFeature.java
index 4e095e3a..5197107 100644
--- a/telephony/java/android/telephony/ims/feature/MMTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MMTelFeature.java
@@ -19,6 +19,7 @@
import android.app.PendingIntent;
import android.os.Message;
import android.os.RemoteException;
+import android.telephony.ims.internal.stub.SmsImplBase;
import com.android.ims.ImsCallProfile;
import com.android.ims.internal.IImsCallSession;
@@ -28,6 +29,7 @@
import com.android.ims.internal.IImsMMTelFeature;
import com.android.ims.internal.IImsMultiEndpoint;
import com.android.ims.internal.IImsRegistrationListener;
+import com.android.ims.internal.IImsSmsListener;
import com.android.ims.internal.IImsUt;
import com.android.ims.internal.ImsCallSession;
@@ -171,6 +173,42 @@
return MMTelFeature.this.getMultiEndpointInterface();
}
}
+
+ @Override
+ public void setSmsListener(IImsSmsListener l) throws RemoteException {
+ synchronized (mLock) {
+ MMTelFeature.this.setSmsListener(l);
+ }
+ }
+
+ @Override
+ public void sendSms(int token, int messageRef, String format, String smsc, boolean retry,
+ byte[] pdu) {
+ synchronized (mLock) {
+ MMTelFeature.this.sendSms(token, messageRef, format, smsc, retry, pdu);
+ }
+ }
+
+ @Override
+ public void acknowledgeSms(int token, int messageRef, int result) {
+ synchronized (mLock) {
+ MMTelFeature.this.acknowledgeSms(token, messageRef, result);
+ }
+ }
+
+ @Override
+ public void acknowledgeSmsReport(int token, int messageRef, int result) {
+ synchronized (mLock) {
+ MMTelFeature.this.acknowledgeSmsReport(token, messageRef, result);
+ }
+ }
+
+ @Override
+ public String getSmsFormat() {
+ synchronized (mLock) {
+ return MMTelFeature.this.getSmsFormat();
+ }
+ }
};
/**
@@ -346,6 +384,39 @@
return null;
}
+ public void setSmsListener(IImsSmsListener listener) {
+ getSmsImplementation().registerSmsListener(listener);
+ }
+
+ public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
+ byte[] pdu) {
+ getSmsImplementation().sendSms(token, messageRef, format, smsc, isRetry, pdu);
+ }
+
+ public void acknowledgeSms(int token, int messageRef,
+ @SmsImplBase.DeliverStatusResult int result) {
+ getSmsImplementation().acknowledgeSms(token, messageRef, result);
+ }
+
+ public void acknowledgeSmsReport(int token, int messageRef,
+ @SmsImplBase.StatusReportResult int result) {
+ getSmsImplementation().acknowledgeSmsReport(token, messageRef, result);
+ }
+
+ /**
+ * Must be overridden by IMS Provider to be able to support SMS over IMS. Otherwise a default
+ * non-functional implementation is returned.
+ *
+ * @return an instance of {@link SmsImplBase} which should be implemented by the IMS Provider.
+ */
+ protected SmsImplBase getSmsImplementation() {
+ return new SmsImplBase();
+ }
+
+ public String getSmsFormat() {
+ return getSmsImplementation().getSmsFormat();
+ }
+
@Override
public void onFeatureReady() {
diff --git a/telephony/java/android/telephony/ims/internal/SmsImplBase.java b/telephony/java/android/telephony/ims/internal/SmsImplBase.java
index eb805a8..33b23d9 100644
--- a/telephony/java/android/telephony/ims/internal/SmsImplBase.java
+++ b/telephony/java/android/telephony/ims/internal/SmsImplBase.java
@@ -17,7 +17,6 @@
package android.telephony.ims.internal;
import android.annotation.IntDef;
-import android.annotation.SystemApi;
import android.os.RemoteException;
import android.telephony.SmsManager;
import android.telephony.SmsMessage;
@@ -37,6 +36,7 @@
public class SmsImplBase {
private static final String LOG_TAG = "SmsImplBase";
+ /** @hide */
@IntDef({
SEND_STATUS_OK,
SEND_STATUS_ERROR,
@@ -68,6 +68,7 @@
*/
public static final int SEND_STATUS_ERROR_FALLBACK = 4;
+ /** @hide */
@IntDef({
DELIVER_STATUS_OK,
DELIVER_STATUS_ERROR
@@ -84,6 +85,7 @@
*/
public static final int DELIVER_STATUS_ERROR = 2;
+ /** @hide */
@IntDef({
STATUS_REPORT_STATUS_OK,
STATUS_REPORT_STATUS_ERROR
@@ -114,9 +116,9 @@
* @hide
*/
public final void registerSmsListener(IImsSmsListener listener) {
- synchronized (mLock) {
- mListener = listener;
- }
+ synchronized (mLock) {
+ mListener = listener;
+ }
}
/**
@@ -128,8 +130,8 @@
* callbacks for this specific message.
* @param messageRef the message reference.
* @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
+ * {@link SmsMessage#FORMAT_3GPP2}.
* @param smsc the Short Message Service Center address.
- * {@link SmsMessage#FORMAT_3GPP2}.
* @param isRetry whether it is a retry of an already attempted message or not.
* @param pdu PDUs representing the contents of the message.
*/
@@ -154,7 +156,7 @@
* @param messageRef the message reference
*/
public void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) {
-
+ Log.e(LOG_TAG, "acknowledgeSms() not implemented.");
}
/**
@@ -168,7 +170,7 @@
* @param messageRef the message reference
*/
public void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) {
-
+ Log.e(LOG_TAG, "acknowledgeSmsReport() not implemented.");
}
/**
@@ -193,7 +195,6 @@
}
try {
mListener.onSmsReceived(token, format, pdu);
- acknowledgeSms(token, 0, DELIVER_STATUS_OK);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Can not deliver sms: " + e.getMessage());
acknowledgeSms(token, 0, DELIVER_STATUS_ERROR);
diff --git a/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java b/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java
new file mode 100644
index 0000000..113dad4
--- /dev/null
+++ b/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telephony.ims.internal.stub;
+
+import android.annotation.IntDef;
+import android.os.RemoteException;
+import android.telephony.SmsManager;
+import android.telephony.SmsMessage;
+import android.telephony.ims.internal.feature.MmTelFeature;
+import android.util.Log;
+
+import com.android.ims.internal.IImsSmsListener;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Base implementation for SMS over IMS.
+ *
+ * Any service wishing to provide SMS over IMS should extend this class and implement all methods
+ * that the service supports.
+ * @hide
+ */
+public class SmsImplBase {
+ private static final String LOG_TAG = "SmsImplBase";
+
+ @IntDef({
+ SEND_STATUS_OK,
+ SEND_STATUS_ERROR,
+ SEND_STATUS_ERROR_RETRY,
+ SEND_STATUS_ERROR_FALLBACK
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SendStatusResult {}
+ /**
+ * Message was sent successfully.
+ */
+ public static final int SEND_STATUS_OK = 1;
+
+ /**
+ * IMS provider failed to send the message and platform should not retry falling back to sending
+ * the message using the radio.
+ */
+ public static final int SEND_STATUS_ERROR = 2;
+
+ /**
+ * IMS provider failed to send the message and platform should retry again after setting TP-RD bit
+ * to high.
+ */
+ public static final int SEND_STATUS_ERROR_RETRY = 3;
+
+ /**
+ * IMS provider failed to send the message and platform should retry falling back to sending
+ * the message using the radio.
+ */
+ public static final int SEND_STATUS_ERROR_FALLBACK = 4;
+
+ @IntDef({
+ DELIVER_STATUS_OK,
+ DELIVER_STATUS_ERROR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DeliverStatusResult {}
+ /**
+ * Message was delivered successfully.
+ */
+ public static final int DELIVER_STATUS_OK = 1;
+
+ /**
+ * Message was not delivered.
+ */
+ public static final int DELIVER_STATUS_ERROR = 2;
+
+ @IntDef({
+ STATUS_REPORT_STATUS_OK,
+ STATUS_REPORT_STATUS_ERROR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StatusReportResult {}
+
+ /**
+ * Status Report was set successfully.
+ */
+ public static final int STATUS_REPORT_STATUS_OK = 1;
+
+ /**
+ * Error while setting status report.
+ */
+ public static final int STATUS_REPORT_STATUS_ERROR = 2;
+
+
+ // Lock for feature synchronization
+ private final Object mLock = new Object();
+ private IImsSmsListener mListener;
+
+ /**
+ * Registers a listener responsible for handling tasks like delivering messages.
+ *
+ * @param listener listener to register.
+ *
+ * @hide
+ */
+ public final void registerSmsListener(IImsSmsListener listener) {
+ synchronized (mLock) {
+ mListener = listener;
+ }
+ }
+
+ /**
+ * This method will be triggered by the platform when the user attempts to send an SMS. This
+ * method should be implemented by the IMS providers to provide implementation of sending an SMS
+ * over IMS.
+ *
+ * @param token unique token generated by the platform that should be used when triggering
+ * callbacks for this specific message.
+ * @param messageRef the message reference.
+ * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
+ * {@link SmsMessage#FORMAT_3GPP2}.
+ * @param smsc the Short Message Service Center address.
+ * @param isRetry whether it is a retry of an already attempted message or not.
+ * @param pdu PDUs representing the contents of the message.
+ */
+ public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
+ byte[] pdu) {
+ // Base implementation returns error. Should be overridden.
+ try {
+ onSendSmsResult(token, messageRef, SEND_STATUS_ERROR,
+ SmsManager.RESULT_ERROR_GENERIC_FAILURE);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Can not send sms: " + e.getMessage());
+ }
+ }
+
+ /**
+ * This method will be triggered by the platform after {@link #onSmsReceived(int, String, byte[])}
+ * has been called to deliver the result to the IMS provider.
+ *
+ * @param token token provided in {@link #onSmsReceived(int, String, byte[])}
+ * @param result result of delivering the message. Valid values are defined in
+ * {@link DeliverStatusResult}
+ * @param messageRef the message reference
+ */
+ public void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) {
+ Log.e(LOG_TAG, "acknowledgeSms() not implemented.");
+ }
+
+ /**
+ * This method will be triggered by the platform after
+ * {@link #onSmsStatusReportReceived(int, int, String, byte[])} has been called to provide the
+ * result to the IMS provider.
+ *
+ * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
+ * @param result result of delivering the message. Valid values are defined in
+ * {@link StatusReportResult}
+ * @param messageRef the message reference
+ */
+ public void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) {
+ Log.e(LOG_TAG, "acknowledgeSmsReport() not implemented.");
+ }
+
+ /**
+ * This method should be triggered by the IMS providers when there is an incoming message. The
+ * platform will deliver the message to the messages database and notify the IMS provider of the
+ * result by calling {@link #acknowledgeSms(int, int, int)}.
+ *
+ * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called.
+ *
+ * @param token unique token generated by IMS providers that the platform will use to trigger
+ * callbacks for this message.
+ * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
+ * {@link SmsMessage#FORMAT_3GPP2}.
+ * @param pdu PDUs representing the contents of the message.
+ * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
+ */
+ public final void onSmsReceived(int token, String format, byte[] pdu)
+ throws IllegalStateException {
+ synchronized (mLock) {
+ if (mListener == null) {
+ throw new IllegalStateException("Feature not ready.");
+ }
+ try {
+ mListener.onSmsReceived(token, format, pdu);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Can not deliver sms: " + e.getMessage());
+ acknowledgeSms(token, 0, DELIVER_STATUS_ERROR);
+ }
+ }
+ }
+
+ /**
+ * This method should be triggered by the IMS providers to pass the result of the sent message
+ * to the platform.
+ *
+ * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called.
+ *
+ * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
+ * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040
+ * @param status result of sending the SMS. Valid values are defined in {@link SendStatusResult}
+ * @param reason reason in case status is failure. Valid values are:
+ * {@link SmsManager#RESULT_ERROR_NONE},
+ * {@link SmsManager#RESULT_ERROR_GENERIC_FAILURE},
+ * {@link SmsManager#RESULT_ERROR_RADIO_OFF},
+ * {@link SmsManager#RESULT_ERROR_NULL_PDU},
+ * {@link SmsManager#RESULT_ERROR_NO_SERVICE},
+ * {@link SmsManager#RESULT_ERROR_LIMIT_EXCEEDED},
+ * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NOT_ALLOWED},
+ * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED}
+ * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
+ * @throws RemoteException if the connection to the framework is not available. If this happens
+ * attempting to send the SMS should be aborted.
+ */
+ public final void onSendSmsResult(int token, int messageRef, @SendStatusResult int status,
+ int reason) throws IllegalStateException, RemoteException {
+ synchronized (mLock) {
+ if (mListener == null) {
+ throw new IllegalStateException("Feature not ready.");
+ }
+ mListener.onSendSmsResult(token, messageRef, status, reason);
+ }
+ }
+
+ /**
+ * Sets the status report of the sent message.
+ *
+ * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
+ * @param messageRef the message reference.
+ * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
+ * {@link SmsMessage#FORMAT_3GPP2}.
+ * @param pdu PDUs representing the content of the status report.
+ * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
+ */
+ public final void onSmsStatusReportReceived(int token, int messageRef, String format,
+ byte[] pdu) {
+ synchronized (mLock) {
+ if (mListener == null) {
+ throw new IllegalStateException("Feature not ready.");
+ }
+ try {
+ mListener.onSmsStatusReportReceived(token, messageRef, format, pdu);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Can not process sms status report: " + e.getMessage());
+ acknowledgeSmsReport(token, messageRef, STATUS_REPORT_STATUS_ERROR);
+ }
+ }
+ }
+
+ /**
+ * Returns the SMS format. Default is {@link SmsMessage#FORMAT_3GPP} unless overridden by IMS
+ * Provider.
+ *
+ * @return the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
+ * {@link SmsMessage#FORMAT_3GPP2}.
+ */
+ public String getSmsFormat() {
+ return SmsMessage.FORMAT_3GPP;
+ }
+}
diff --git a/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl
index 52b3853..cce39f4 100644
--- a/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl
+++ b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl
@@ -25,6 +25,7 @@
import com.android.ims.internal.IImsEcbm;
import com.android.ims.internal.IImsMultiEndpoint;
import com.android.ims.internal.IImsRegistrationListener;
+import com.android.ims.internal.IImsSmsListener;
import com.android.ims.internal.IImsUt;
import android.os.Message;
@@ -53,4 +54,11 @@
IImsEcbm getEcbmInterface();
void setUiTTYMode(int uiTtyMode, in Message onComplete);
IImsMultiEndpoint getMultiEndpointInterface();
+ // SMS APIs
+ void setSmsListener(IImsSmsListener l);
+ oneway void sendSms(in int token, int messageRef, String format, String smsc, boolean retry,
+ in byte[] pdu);
+ oneway void acknowledgeSms(int token, int messageRef, int result);
+ oneway void acknowledgeSmsReport(int token, int messageRef, int result);
+ String getSmsFormat();
}
diff --git a/telephony/java/com/android/ims/internal/IImsSmsListener.aidl b/telephony/java/com/android/ims/internal/IImsSmsListener.aidl
new file mode 100644
index 0000000..5a4b7e4
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsSmsListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims.internal;
+
+/**
+ * See SmsImplBase for more information.
+ * {@hide}
+ */
+interface IImsSmsListener {
+ void onSendSmsResult(int token, int messageRef, int status, int reason);
+ void onSmsStatusReportReceived(int token, int messageRef, in String format,
+ in byte[] pdu);
+ void onSmsReceived(int token, in String format, in byte[] pdu);
+}
\ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/IccCardConstants.java b/telephony/java/com/android/internal/telephony/IccCardConstants.java
index f3d9335..d57f9af 100644
--- a/telephony/java/com/android/internal/telephony/IccCardConstants.java
+++ b/telephony/java/com/android/internal/telephony/IccCardConstants.java
@@ -30,16 +30,14 @@
public static final String INTENT_VALUE_ICC_NOT_READY = "NOT_READY";
/* ABSENT means ICC is missing */
public static final String INTENT_VALUE_ICC_ABSENT = "ABSENT";
+ /* PRESENT means ICC is present */
+ public static final String INTENT_VALUE_ICC_PRESENT = "PRESENT";
/* CARD_IO_ERROR means for three consecutive times there was SIM IO error */
static public final String INTENT_VALUE_ICC_CARD_IO_ERROR = "CARD_IO_ERROR";
/* CARD_RESTRICTED means card is present but not usable due to carrier restrictions */
static public final String INTENT_VALUE_ICC_CARD_RESTRICTED = "CARD_RESTRICTED";
/* LOCKED means ICC is locked by pin or by network */
public static final String INTENT_VALUE_ICC_LOCKED = "LOCKED";
- //TODO: we can remove this state in the future if Bug 18489776 analysis
- //#42's first race condition is resolved
- /* INTERNAL LOCKED means ICC is locked by pin or by network */
- public static final String INTENT_VALUE_ICC_INTERNAL_LOCKED = "INTERNAL_LOCKED";
/* READY means ICC is ready to access */
public static final String INTENT_VALUE_ICC_READY = "READY";
/* IMSI means ICC IMSI is ready in property */
@@ -77,7 +75,8 @@
NOT_READY, /** ordinal(6) == {@See TelephonyManager#SIM_STATE_NOT_READY} */
PERM_DISABLED, /** ordinal(7) == {@See TelephonyManager#SIM_STATE_PERM_DISABLED} */
CARD_IO_ERROR, /** ordinal(8) == {@See TelephonyManager#SIM_STATE_CARD_IO_ERROR} */
- CARD_RESTRICTED;/** ordinal(9) == {@See TelephonyManager#SIM_STATE_CARD_RESTRICTED} */
+ CARD_RESTRICTED,/** ordinal(9) == {@See TelephonyManager#SIM_STATE_CARD_RESTRICTED} */
+ LOADED; /** ordinal(9) == {@See TelephonyManager#SIM_STATE_LOADED} */
public boolean isPinLocked() {
return ((this == PIN_REQUIRED) || (this == PUK_REQUIRED));
@@ -85,9 +84,9 @@
public boolean iccCardExist() {
return ((this == PIN_REQUIRED) || (this == PUK_REQUIRED)
- || (this == NETWORK_LOCKED) || (this == READY)
+ || (this == NETWORK_LOCKED) || (this == READY) || (this == NOT_READY)
|| (this == PERM_DISABLED) || (this == CARD_IO_ERROR)
- || (this == CARD_RESTRICTED));
+ || (this == CARD_RESTRICTED) || (this == LOADED));
}
public static State intToState(int state) throws IllegalArgumentException {
@@ -102,6 +101,7 @@
case 7: return PERM_DISABLED;
case 8: return CARD_IO_ERROR;
case 9: return CARD_RESTRICTED;
+ case 10: return LOADED;
default:
throw new IllegalArgumentException();
}
diff --git a/tests/UiBench/src/com/android/test/uibench/leanback/BrowseFragment.java b/tests/UiBench/src/com/android/test/uibench/leanback/BrowseFragment.java
index 11ea361..4ab38a6 100644
--- a/tests/UiBench/src/com/android/test/uibench/leanback/BrowseFragment.java
+++ b/tests/UiBench/src/com/android/test/uibench/leanback/BrowseFragment.java
@@ -24,6 +24,7 @@
@Override
public void onCreate(Bundle savedInstanceState) {
+ TestHelper.initHeaderState(this);
super.onCreate(savedInstanceState);
BitmapLoader.clear();
TestHelper.initBackground(getActivity());
diff --git a/tests/UiBench/src/com/android/test/uibench/leanback/TestHelper.java b/tests/UiBench/src/com/android/test/uibench/leanback/TestHelper.java
index 2bf3885..bf408f7 100644
--- a/tests/UiBench/src/com/android/test/uibench/leanback/TestHelper.java
+++ b/tests/UiBench/src/com/android/test/uibench/leanback/TestHelper.java
@@ -40,6 +40,7 @@
public static final String EXTRA_CARD_ROUND_RECT = "extra_card_round_rect";
public static final String EXTRA_ENTRANCE_TRANSITION = "extra_entrance_transition";
public static final String EXTRA_BITMAP_UPLOAD = "extra_bitmap_upload";
+ public static final String EXTRA_SHOW_FAST_LANE = "extra_show_fast_lane";
/**
* Dont change the default values, they gave baseline for measuring the performance
@@ -53,6 +54,7 @@
static final boolean DEFAULT_CARD_SHADOW = true;
static final boolean DEFAULT_CARD_ROUND_RECT = true;
static final boolean DEFAULT_BITMAP_UPLOAD = true;
+ static final boolean DEFAULT_SHOW_FAST_LANE = true;
static long sCardIdSeed = 0;
static long sRowIdSeed = 0;
@@ -235,4 +237,11 @@
manager.setBitmap(bitmap);
}
}
+
+ public static void initHeaderState(BrowseFragment fragment) {
+ if (!fragment.getActivity().getIntent()
+ .getBooleanExtra(EXTRA_SHOW_FAST_LANE, DEFAULT_SHOW_FAST_LANE)) {
+ fragment.setHeadersState(BrowseFragment.HEADERS_HIDDEN);
+ }
+ }
}
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index 8552195..069360e 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -306,10 +306,25 @@
}
// A value that represents a primitive data type (float, int, boolean, etc.).
-// Corresponds to the fields (type/data) of the C struct android::Res_value.
message Primitive {
- uint32 type = 1;
- uint32 data = 2;
+ message NullType {
+ }
+ message EmptyType {
+ }
+ oneof oneof_value {
+ NullType null_value = 1;
+ EmptyType empty_value = 2;
+ float float_value = 3;
+ float dimension_value = 4;
+ float fraction_value = 5;
+ int32 int_decimal_value = 6;
+ uint32 int_hexidecimal_value = 7;
+ bool boolean_value = 8;
+ uint32 color_argb8_value = 9;
+ uint32 color_rgb8_value = 10;
+ uint32 color_argb4_value = 11;
+ uint32 color_rgb4_value = 12;
+ }
}
// A value that represents an XML attribute and what values it accepts.
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index d8635a9..064878b 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -23,6 +23,7 @@
#include "Locale.h"
#include "ResourceTable.h"
#include "ResourceUtils.h"
+#include "ResourceValues.h"
#include "ValueVisitor.h"
using ::android::ResStringPool;
@@ -761,8 +762,66 @@
case pb::Item::kPrim: {
const pb::Primitive& pb_prim = pb_item.prim();
- return util::make_unique<BinaryPrimitive>(static_cast<uint8_t>(pb_prim.type()),
- pb_prim.data());
+ android::Res_value val = {};
+ switch (pb_prim.oneof_value_case()) {
+ case pb::Primitive::kNullValue: {
+ val.dataType = android::Res_value::TYPE_NULL;
+ val.data = android::Res_value::DATA_NULL_UNDEFINED;
+ } break;
+ case pb::Primitive::kEmptyValue: {
+ val.dataType = android::Res_value::TYPE_NULL;
+ val.data = android::Res_value::DATA_NULL_EMPTY;
+ } break;
+ case pb::Primitive::kFloatValue: {
+ val.dataType = android::Res_value::TYPE_FLOAT;
+ float float_val = pb_prim.float_value();
+ val.data = *(uint32_t*)&float_val;
+ } break;
+ case pb::Primitive::kDimensionValue: {
+ val.dataType = android::Res_value::TYPE_DIMENSION;
+ float dimen_val = pb_prim.dimension_value();
+ val.data = *(uint32_t*)&dimen_val;
+ } break;
+ case pb::Primitive::kFractionValue: {
+ val.dataType = android::Res_value::TYPE_FRACTION;
+ float fraction_val = pb_prim.fraction_value();
+ val.data = *(uint32_t*)&fraction_val;
+ } break;
+ case pb::Primitive::kIntDecimalValue: {
+ val.dataType = android::Res_value::TYPE_INT_DEC;
+ val.data = static_cast<uint32_t>(pb_prim.int_decimal_value());
+ } break;
+ case pb::Primitive::kIntHexidecimalValue: {
+ val.dataType = android::Res_value::TYPE_INT_HEX;
+ val.data = pb_prim.int_hexidecimal_value();
+ } break;
+ case pb::Primitive::kBooleanValue: {
+ val.dataType = android::Res_value::TYPE_INT_BOOLEAN;
+ val.data = pb_prim.boolean_value() ? 0xFFFFFFFF : 0x0;
+ } break;
+ case pb::Primitive::kColorArgb8Value: {
+ val.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8;
+ val.data = pb_prim.color_argb8_value();
+ } break;
+ case pb::Primitive::kColorRgb8Value: {
+ val.dataType = android::Res_value::TYPE_INT_COLOR_RGB8;
+ val.data = pb_prim.color_rgb8_value();
+ } break;
+ case pb::Primitive::kColorArgb4Value: {
+ val.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4;
+ val.data = pb_prim.color_argb4_value();
+ } break;
+ case pb::Primitive::kColorRgb4Value: {
+ val.dataType = android::Res_value::TYPE_INT_COLOR_RGB4;
+ val.data = pb_prim.color_rgb4_value();
+ } break;
+ default: {
+ LOG(FATAL) << "Unexpected Primitive type: "
+ << static_cast<uint32_t>(pb_prim.oneof_value_case());
+ return {};
+ } break;
+ }
+ return util::make_unique<BinaryPrimitive>(val);
} break;
case pb::Item::kId: {
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index 78f1281..e9622f5 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -436,8 +436,51 @@
prim->Flatten(&val);
pb::Primitive* pb_prim = out_value_->mutable_item()->mutable_prim();
- pb_prim->set_type(val.dataType);
- pb_prim->set_data(val.data);
+
+ switch (val.dataType) {
+ case android::Res_value::TYPE_NULL: {
+ if (val.data == android::Res_value::DATA_NULL_UNDEFINED) {
+ pb_prim->set_allocated_null_value(new pb::Primitive_NullType());
+ } else if (val.data == android::Res_value::DATA_NULL_EMPTY) {
+ pb_prim->set_allocated_empty_value(new pb::Primitive_EmptyType());
+ } else {
+ LOG(FATAL) << "Unexpected data value for TYPE_NULL BinaryPrimitive: " << val.data;
+ }
+ } break;
+ case android::Res_value::TYPE_FLOAT: {
+ pb_prim->set_float_value(*(float*)&val.data);
+ } break;
+ case android::Res_value::TYPE_DIMENSION: {
+ pb_prim->set_dimension_value(*(float*)&val.data);
+ } break;
+ case android::Res_value::TYPE_FRACTION: {
+ pb_prim->set_fraction_value(*(float*)&val.data);
+ } break;
+ case android::Res_value::TYPE_INT_DEC: {
+ pb_prim->set_int_decimal_value(static_cast<int32_t>(val.data));
+ } break;
+ case android::Res_value::TYPE_INT_HEX: {
+ pb_prim->set_int_hexidecimal_value(val.data);
+ } break;
+ case android::Res_value::TYPE_INT_BOOLEAN: {
+ pb_prim->set_boolean_value(static_cast<bool>(val.data));
+ } break;
+ case android::Res_value::TYPE_INT_COLOR_ARGB8: {
+ pb_prim->set_color_argb8_value(val.data);
+ } break;
+ case android::Res_value::TYPE_INT_COLOR_RGB8: {
+ pb_prim->set_color_rgb8_value(val.data);
+ } break;
+ case android::Res_value::TYPE_INT_COLOR_ARGB4: {
+ pb_prim->set_color_argb4_value(val.data);
+ } break;
+ case android::Res_value::TYPE_INT_COLOR_RGB4: {
+ pb_prim->set_color_rgb4_value(val.data);
+ } break;
+ default:
+ LOG(FATAL) << "Unexpected BinaryPrimitive type: " << val.dataType;
+ break;
+ }
}
void Visit(const Attribute* attr) override {
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index ccba5c6..9081ab6 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -254,6 +254,111 @@
EXPECT_THAT(child_text->text, StrEq("woah there"));
}
+TEST(ProtoSerializeTest, SerializeAndDeserializePrimitives) {
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddValue("android:bool/boolean_true",
+ test::BuildPrimitive(android::Res_value::TYPE_INT_BOOLEAN, true))
+ .AddValue("android:bool/boolean_false",
+ test::BuildPrimitive(android::Res_value::TYPE_INT_BOOLEAN, false))
+ .AddValue("android:color/color_rgb8", ResourceUtils::TryParseColor("#AABBCC"))
+ .AddValue("android:color/color_argb8", ResourceUtils::TryParseColor("#11223344"))
+ .AddValue("android:color/color_rgb4", ResourceUtils::TryParseColor("#DEF"))
+ .AddValue("android:color/color_argb4", ResourceUtils::TryParseColor("#5678"))
+ .AddValue("android:integer/integer_444", ResourceUtils::TryParseInt("444"))
+ .AddValue("android:integer/integer_neg_333", ResourceUtils::TryParseInt("-333"))
+ .AddValue("android:integer/hex_int_abcd", ResourceUtils::TryParseInt("0xABCD"))
+ .AddValue("android:dimen/dimen_1.39mm", ResourceUtils::TryParseFloat("1.39mm"))
+ .AddValue("android:fraction/fraction_27", ResourceUtils::TryParseFloat("27%"))
+ .AddValue("android:integer/null", ResourceUtils::MakeEmpty())
+ .Build();
+
+ pb::ResourceTable pb_table;
+ SerializeTableToPb(*table, &pb_table);
+
+ test::TestFile file_a("res/layout/main.xml");
+ MockFileCollection files;
+ EXPECT_CALL(files, FindFile(Eq("res/layout/main.xml")))
+ .WillRepeatedly(::testing::Return(&file_a));
+
+ ResourceTable new_table;
+ std::string error;
+ ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error));
+ EXPECT_THAT(error, IsEmpty());
+
+ BinaryPrimitive* bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(
+ &new_table, "android:bool/boolean_true", ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_INT_BOOLEAN));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseBool("true")->value.data));
+
+ bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(&new_table, "android:bool/boolean_false",
+ ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_INT_BOOLEAN));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseBool("false")->value.data));
+
+ bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(&new_table, "android:color/color_rgb8",
+ ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_INT_COLOR_RGB8));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseColor("#AABBCC")->value.data));
+
+ bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(&new_table, "android:color/color_argb8",
+ ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_INT_COLOR_ARGB8));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseColor("#11223344")->value.data));
+
+ bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(&new_table, "android:color/color_rgb4",
+ ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_INT_COLOR_RGB4));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseColor("#DEF")->value.data));
+
+ bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(&new_table, "android:color/color_argb4",
+ ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_INT_COLOR_ARGB4));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseColor("#5678")->value.data));
+
+ bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(&new_table, "android:integer/integer_444",
+ ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_INT_DEC));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseInt("444")->value.data));
+
+ bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(
+ &new_table, "android:integer/integer_neg_333", ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_INT_DEC));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseInt("-333")->value.data));
+
+ bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(
+ &new_table, "android:integer/hex_int_abcd", ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_INT_HEX));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseInt("0xABCD")->value.data));
+
+ bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(&new_table, "android:dimen/dimen_1.39mm",
+ ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_DIMENSION));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseFloat("1.39mm")->value.data));
+
+ bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(
+ &new_table, "android:fraction/fraction_27", ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_FRACTION));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseFloat("27%")->value.data));
+
+ bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(&new_table, "android:integer/null",
+ ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_NULL));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::MakeEmpty()->value.data));
+}
+
static void ExpectConfigSerializes(const StringPiece& config_str) {
const ConfigDescription expected_config = test::ParseConfigOrDie(config_str);
pb::Configuration pb_config;
diff --git a/wifi/java/android/net/wifi/ISoftApCallback.aidl b/wifi/java/android/net/wifi/ISoftApCallback.aidl
new file mode 100644
index 0000000..b8d2971
--- /dev/null
+++ b/wifi/java/android/net/wifi/ISoftApCallback.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+/**
+ * Interface for Soft AP callback.
+ *
+ * @hide
+ */
+oneway interface ISoftApCallback
+{
+ /**
+ * Service to manager callback providing current soft AP state. The possible
+ * parameter values listed are defined in WifiManager.java
+ *
+ * @param state new AP state. One of WIFI_AP_STATE_DISABLED,
+ * WIFI_AP_STATE_DISABLING, WIFI_AP_STATE_ENABLED,
+ * WIFI_AP_STATE_ENABLING, WIFI_AP_STATE_FAILED
+ * @param failureReason reason when in failed state. One of
+ * SAP_START_FAILURE_GENERAL, SAP_START_FAILURE_NO_CHANNEL
+ */
+ void onStateChanged(int state, int failureReason);
+
+ /**
+ * Service to manager callback providing number of connected clients.
+ *
+ * @param numClients number of connected clients
+ */
+ void onNumClientsChanged(int numClients);
+}
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 70d6ce4..e9e61a5 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -23,15 +23,15 @@
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.net.wifi.hotspot2.IProvisioningCallback;
+import android.net.DhcpInfo;
+import android.net.Network;
+import android.net.wifi.ISoftApCallback;
+import android.net.wifi.PasspointManagementObjectDefinition;
+import android.net.wifi.ScanResult;
+import android.net.wifi.ScanSettings;
+import android.net.wifi.WifiActivityEnergyInfo;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
-import android.net.wifi.ScanSettings;
-import android.net.wifi.ScanResult;
-import android.net.wifi.PasspointManagementObjectDefinition;
-import android.net.wifi.WifiActivityEnergyInfo;
-import android.net.Network;
-
-import android.net.DhcpInfo;
import android.os.Messenger;
import android.os.ResultReceiver;
@@ -178,5 +178,9 @@
void restoreSupplicantBackupData(in byte[] supplicantData, in byte[] ipConfigData);
void startSubscriptionProvisioning(in OsuProvider provider, in IProvisioningCallback callback);
+
+ void registerSoftApCallback(in IBinder binder, in ISoftApCallback callback, int callbackIdentifier);
+
+ void unregisterSoftApCallback(int callbackIdentifier);
}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index aa75a07..99080d6 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -16,6 +16,7 @@
package android.net.wifi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -55,6 +56,8 @@
import dalvik.system.CloseGuard;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.net.InetAddress;
import java.util.Collections;
@@ -432,6 +435,17 @@
*/
public static final String EXTRA_WIFI_AP_MODE = "wifi_ap_mode";
+ /** @hide */
+ @IntDef(flag = false, prefix = { "WIFI_AP_STATE_" }, value = {
+ WIFI_AP_STATE_DISABLING,
+ WIFI_AP_STATE_DISABLED,
+ WIFI_AP_STATE_ENABLING,
+ WIFI_AP_STATE_ENABLED,
+ WIFI_AP_STATE_FAILED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface WifiApState {}
+
/**
* Wi-Fi AP is currently being disabled. The state will change to
* {@link #WIFI_AP_STATE_DISABLED} if it finishes successfully.
@@ -486,6 +500,14 @@
@SystemApi
public static final int WIFI_AP_STATE_FAILED = 14;
+ /** @hide */
+ @IntDef(flag = false, prefix = { "SAP_START_FAILURE_" }, value = {
+ SAP_START_FAILURE_GENERAL,
+ SAP_START_FAILURE_NO_CHANNEL,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SapStartFailure {}
+
/**
* If WIFI AP start failed, this reason code means there is no legal channel exists on
* user selected band by regulatory
@@ -2324,6 +2346,119 @@
}
/**
+ * Base class for soft AP callback. Should be extended by applications and set when calling
+ * {@link WifiManager#registerSoftApCallback(SoftApCallback, Handler)}.
+ *
+ * @hide
+ */
+ public interface SoftApCallback {
+ /**
+ * Called when soft AP state changes.
+ *
+ * @param state new new AP state. One of {@link #WIFI_AP_STATE_DISABLED},
+ * {@link #WIFI_AP_STATE_DISABLING}, {@link #WIFI_AP_STATE_ENABLED},
+ * {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED}
+ * @param failureReason reason when in failed state. One of
+ * {@link #SAP_START_FAILURE_GENERAL}, {@link #SAP_START_FAILURE_NO_CHANNEL}
+ */
+ public abstract void onStateChanged(@WifiApState int state,
+ @SapStartFailure int failureReason);
+
+ /**
+ * Called when number of connected clients to soft AP changes.
+ *
+ * @param numClients number of connected clients
+ */
+ public abstract void onNumClientsChanged(int numClients);
+ }
+
+ /**
+ * Callback proxy for SoftApCallback objects.
+ *
+ * @hide
+ */
+ private static class SoftApCallbackProxy extends ISoftApCallback.Stub {
+ private final Handler mHandler;
+ private final SoftApCallback mCallback;
+
+ SoftApCallbackProxy(Looper looper, SoftApCallback callback) {
+ mHandler = new Handler(looper);
+ mCallback = callback;
+ }
+
+ @Override
+ public void onStateChanged(int state, int failureReason) throws RemoteException {
+ Log.v(TAG, "SoftApCallbackProxy: onStateChanged: state=" + state + ", failureReason=" +
+ failureReason);
+ mHandler.post(() -> {
+ mCallback.onStateChanged(state, failureReason);
+ });
+ }
+
+ @Override
+ public void onNumClientsChanged(int numClients) throws RemoteException {
+ Log.v(TAG, "SoftApCallbackProxy: onNumClientsChanged: numClients=" + numClients);
+ mHandler.post(() -> {
+ mCallback.onNumClientsChanged(numClients);
+ });
+ }
+ }
+
+ /**
+ * Registers a callback for Soft AP. See {@link SoftApCallback}. Caller will receive the current
+ * soft AP state and number of connected devices immediately after a successful call to this API
+ * via callback. Note that receiving an immediate WIFI_AP_STATE_FAILED value for soft AP state
+ * indicates that the latest attempt to start soft AP has failed. Caller can unregister a
+ * previously registered callback using {@link unregisterSoftApCallback}
+ * <p>
+ * Applications should have the
+ * {@link android.Manifest.permission#NETWORK_SETTINGS NETWORK_SETTINGS} permission. Callers
+ * without the permission will trigger a {@link java.lang.SecurityException}.
+ * <p>
+ *
+ * @param callback Callback for soft AP events
+ * @param handler The Handler on whose thread to execute the callbacks of the {@code callback}
+ * object. If null, then the application's main thread will be used.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public void registerSoftApCallback(@NonNull SoftApCallback callback,
+ @Nullable Handler handler) {
+ if (callback == null) throw new IllegalArgumentException("callback cannot be null");
+ Log.v(TAG, "registerSoftApCallback: callback=" + callback + ", handler=" + handler);
+
+ Looper looper = (handler == null) ? mContext.getMainLooper() : handler.getLooper();
+ Binder binder = new Binder();
+ try {
+ mService.registerSoftApCallback(binder, new SoftApCallbackProxy(looper, callback),
+ callback.hashCode());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Allow callers to unregister a previously registered callback. After calling this method,
+ * applications will no longer receive soft AP events.
+ *
+ * @param callback Callback to unregister for soft AP events
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public void unregisterSoftApCallback(@NonNull SoftApCallback callback) {
+ if (callback == null) throw new IllegalArgumentException("callback cannot be null");
+ Log.v(TAG, "unregisterSoftApCallback: callback=" + callback);
+
+ try {
+ mService.unregisterSoftApCallback(callback.hashCode());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* LocalOnlyHotspotReservation that contains the {@link WifiConfiguration} for the active
* LocalOnlyHotspot request.
* <p>
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 0df5615..4b5f645 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -24,11 +24,19 @@
import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_NO_CHANNEL;
import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_TETHERING_DISALLOWED;
import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.REQUEST_REGISTERED;
+import static android.net.wifi.WifiManager.SAP_START_FAILURE_GENERAL;
+import static android.net.wifi.WifiManager.SAP_START_FAILURE_NO_CHANNEL;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.Mockito.*;
import android.content.Context;
@@ -37,6 +45,7 @@
import android.net.wifi.WifiManager.LocalOnlyHotspotObserver;
import android.net.wifi.WifiManager.LocalOnlyHotspotReservation;
import android.net.wifi.WifiManager.LocalOnlyHotspotSubscription;
+import android.net.wifi.WifiManager.SoftApCallback;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -66,6 +75,7 @@
@Mock ApplicationInfo mApplicationInfo;
@Mock WifiConfiguration mApConfig;
@Mock IBinder mAppBinder;
+ @Mock SoftApCallback mSoftApCallback;
private Handler mHandler;
private TestLooper mLooper;
@@ -632,6 +642,149 @@
}
/**
+ * Verify an IllegalArgumentException is thrown if callback is not provided.
+ */
+ @Test
+ public void registerSoftApCallbackThrowsIllegalArgumentExceptionOnNullArgumentForCallback() {
+ try {
+ mWifiManager.registerSoftApCallback(null, mHandler);
+ fail("expected IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ /**
+ * Verify an IllegalArgumentException is thrown if callback is not provided.
+ */
+ @Test
+ public void unregisterSoftApCallbackThrowsIllegalArgumentExceptionOnNullArgumentForCallback() {
+ try {
+ mWifiManager.unregisterSoftApCallback(null);
+ fail("expected IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ /**
+ * Verify main looper is used when handler is not provided.
+ */
+ @Test
+ public void registerSoftApCallbackUsesMainLooperOnNullArgumentForHandler() {
+ when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
+ mWifiManager.registerSoftApCallback(mSoftApCallback, null);
+ verify(mContext).getMainLooper();
+ }
+
+ /**
+ * Verify the call to registerSoftApCallback goes to WifiServiceImpl.
+ */
+ @Test
+ public void registerSoftApCallbackCallGoesToWifiServiceImpl() throws Exception {
+ mWifiManager.registerSoftApCallback(mSoftApCallback, mHandler);
+ verify(mWifiService).registerSoftApCallback(any(IBinder.class),
+ any(ISoftApCallback.Stub.class), anyInt());
+ }
+
+ /**
+ * Verify the call to unregisterSoftApCallback goes to WifiServiceImpl.
+ */
+ @Test
+ public void unregisterSoftApCallbackCallGoesToWifiServiceImpl() throws Exception {
+ ArgumentCaptor<Integer> callbackIdentifier = ArgumentCaptor.forClass(Integer.class);
+ mWifiManager.registerSoftApCallback(mSoftApCallback, mHandler);
+ verify(mWifiService).registerSoftApCallback(any(IBinder.class),
+ any(ISoftApCallback.Stub.class), callbackIdentifier.capture());
+
+ mWifiManager.unregisterSoftApCallback(mSoftApCallback);
+ verify(mWifiService).unregisterSoftApCallback(eq((int) callbackIdentifier.getValue()));
+ }
+
+ /*
+ * Verify client provided callback is being called through callback proxy
+ */
+ @Test
+ public void softApCallbackProxyCallsOnStateChanged() throws Exception {
+ ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
+ ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
+ mWifiManager.registerSoftApCallback(mSoftApCallback, mHandler);
+ verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(),
+ anyInt());
+
+ callbackCaptor.getValue().onStateChanged(WIFI_AP_STATE_ENABLED, 0);
+ mLooper.dispatchAll();
+ verify(mSoftApCallback).onStateChanged(WIFI_AP_STATE_ENABLED, 0);
+ }
+
+ /*
+ * Verify client provided callback is being called through callback proxy
+ */
+ @Test
+ public void softApCallbackProxyCallsOnNumClientsChanged() throws Exception {
+ ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
+ ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
+ mWifiManager.registerSoftApCallback(mSoftApCallback, mHandler);
+ verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(),
+ anyInt());
+
+ final int testNumClients = 3;
+ callbackCaptor.getValue().onNumClientsChanged(testNumClients);
+ mLooper.dispatchAll();
+ verify(mSoftApCallback).onNumClientsChanged(testNumClients);
+ }
+
+ /*
+ * Verify client provided callback is being called through callback proxy on multiple events
+ */
+ @Test
+ public void softApCallbackProxyCallsOnMultipleUpdates() throws Exception {
+ ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
+ ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
+ mWifiManager.registerSoftApCallback(mSoftApCallback, mHandler);
+ verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(),
+ anyInt());
+
+ final int testNumClients = 5;
+ callbackCaptor.getValue().onStateChanged(WIFI_AP_STATE_ENABLING, 0);
+ callbackCaptor.getValue().onNumClientsChanged(testNumClients);
+ callbackCaptor.getValue().onStateChanged(WIFI_AP_STATE_FAILED, SAP_START_FAILURE_GENERAL);
+
+ mLooper.dispatchAll();
+ verify(mSoftApCallback).onStateChanged(WIFI_AP_STATE_ENABLING, 0);
+ verify(mSoftApCallback).onNumClientsChanged(testNumClients);
+ verify(mSoftApCallback).onStateChanged(WIFI_AP_STATE_FAILED, SAP_START_FAILURE_GENERAL);
+ }
+
+ /*
+ * Verify client provided callback is being called on the correct thread
+ */
+ @Test
+ public void softApCallbackIsCalledOnCorrectThread() throws Exception {
+ ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
+ ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
+ TestLooper altLooper = new TestLooper();
+ Handler altHandler = new Handler(altLooper.getLooper());
+ mWifiManager.registerSoftApCallback(mSoftApCallback, altHandler);
+ verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(),
+ anyInt());
+
+ callbackCaptor.getValue().onStateChanged(WIFI_AP_STATE_ENABLED, 0);
+ altLooper.dispatchAll();
+ verify(mSoftApCallback).onStateChanged(WIFI_AP_STATE_ENABLED, 0);
+ }
+
+ /**
+ * Verify that the handler provided by the caller is used for registering soft AP callback.
+ */
+ @Test
+ public void testCorrectLooperIsUsedForSoftApCallbackHandler() throws Exception {
+ mWifiManager.registerSoftApCallback(mSoftApCallback, mHandler);
+ mLooper.dispatchAll();
+ verify(mWifiService).registerSoftApCallback(any(IBinder.class),
+ any(ISoftApCallback.Stub.class), anyInt());
+ verify(mContext, never()).getMainLooper();
+ }
+
+ /**
* Verify that the handler provided by the caller is used for the observer.
*/
@Test