summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp5
-rw-r--r--api/current.txt18
-rw-r--r--api/system-current.txt22
-rw-r--r--api/test-current.txt8
-rw-r--r--cmds/statsd/Android.mk2
-rw-r--r--cmds/statsd/src/stats_log.proto16
-rw-r--r--core/java/android/app/ActivityView.java37
-rw-r--r--core/java/android/app/SystemServiceRegistry.java4
-rw-r--r--core/java/android/content/Context.java2
-rw-r--r--core/java/android/content/pm/CrossProfileApps.java (renamed from core/java/android/content/pm/crossprofile/CrossProfileApps.java)15
-rw-r--r--core/java/android/content/pm/ICrossProfileApps.aidl (renamed from core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl)2
-rw-r--r--core/java/android/provider/Settings.java9
-rw-r--r--core/java/android/service/autofill/AutofillFieldClassificationService.java284
-rw-r--r--core/java/android/service/autofill/CharSequenceTransformation.java28
-rw-r--r--core/java/android/service/autofill/IAutofillFieldClassificationService.aidl34
-rw-r--r--core/java/android/view/Choreographer.java3
-rw-r--r--core/java/android/view/IWindowSession.aidl8
-rw-r--r--core/java/android/view/View.java9
-rw-r--r--core/java/android/view/autofill/AutofillManager.java91
-rw-r--r--core/java/android/view/autofill/IAutoFillManager.aidl5
-rw-r--r--core/java/android/widget/EditText.java4
-rw-r--r--core/jni/android/graphics/ImageDecoder.cpp2
-rw-r--r--core/proto/android/server/forceappstandbytracker.proto9
-rw-r--r--core/res/AndroidManifest.xml9
-rw-r--r--core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java (renamed from core/tests/coretests/src/android/content/pm/crossprofile/CrossProfileAppsTest.java)10
-rw-r--r--core/tests/coretests/src/android/provider/SettingsBackupTest.java1
-rw-r--r--packages/ExtServices/AndroidManifest.xml7
-rw-r--r--packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java81
-rw-r--r--packages/ExtServices/src/android/ext/services/autofill/EditDistanceScorer.java (renamed from core/java/android/service/autofill/EditDistanceScorer.java)12
-rw-r--r--packages/ExtServices/tests/src/android/ext/services/autofill/EditDistanceScorerTest.java77
-rw-r--r--packages/SettingsLib/res/values/strings.xml4
-rw-r--r--packages/SystemUI/res/values/strings.xml9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java49
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillManagerService.java75
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java165
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java56
-rw-r--r--services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java279
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java76
-rw-r--r--services/backup/java/com/android/server/backup/TransportManager.java89
-rw-r--r--services/core/java/com/android/server/ForceAppStandbyTracker.java164
-rw-r--r--services/core/java/com/android/server/InputMethodManagerService.java57
-rw-r--r--services/core/java/com/android/server/LocationManagerService.java41
-rw-r--r--services/core/java/com/android/server/pm/CrossProfileAppsService.java (renamed from services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsService.java)2
-rw-r--r--services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java (renamed from services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java)6
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java9
-rw-r--r--services/core/java/com/android/server/wm/Session.java11
-rw-r--r--services/core/java/com/android/server/wm/TapExcludeRegionHolder.java56
-rw-r--r--services/core/java/com/android/server/wm/TaskPositioner.java21
-rw-r--r--services/core/java/com/android/server/wm/TaskPositioningController.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java17
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java41
-rw-r--r--services/java/com/android/server/SystemServer.java10
-rw-r--r--services/robotests/src/com/android/server/backup/TransportManagerTest.java68
-rw-r--r--services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java49
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java (renamed from services/tests/servicestests/src/com/android/server/pm/crossprofile/CrossProfileAppsServiceImplTest.java)7
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java22
-rw-r--r--telephony/java/android/telephony/ims/feature/MMTelFeature.java71
-rw-r--r--telephony/java/android/telephony/ims/internal/SmsImplBase.java17
-rw-r--r--telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java271
-rw-r--r--telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl8
-rw-r--r--telephony/java/com/android/ims/internal/IImsSmsListener.aidl28
-rw-r--r--wifi/java/android/net/wifi/ISoftApCallback.aidl44
-rw-r--r--wifi/java/android/net/wifi/IWifiManager.aidl18
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java135
-rw-r--r--wifi/tests/src/android/net/wifi/WifiManagerTest.java153
67 files changed, 2359 insertions, 590 deletions
diff --git a/Android.bp b/Android.bp
index ae4f97411fd4..19c0580e21d2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -117,7 +117,7 @@ java_library {
"core/java/android/content/ISyncServiceAdapter.aidl",
"core/java/android/content/ISyncStatusObserver.aidl",
"core/java/android/content/om/IOverlayManager.aidl",
- "core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl",
+ "core/java/android/content/pm/ICrossProfileApps.aidl",
"core/java/android/content/pm/IDexModuleRegisterCallback.aidl",
"core/java/android/content/pm/ILauncherApps.aidl",
"core/java/android/content/pm/IOnAppsChangedListener.aidl",
@@ -242,6 +242,7 @@ java_library {
"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 @@ java_library {
"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 @@ java_library {
"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 5e8d14411413..06f3b2ac8f1c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -10687,6 +10687,13 @@ package android.content.pm {
field public int reqTouchScreen;
}
+ public class CrossProfileApps {
+ method public android.graphics.drawable.Drawable getProfileSwitchingIconDrawable(android.os.UserHandle);
+ method public java.lang.CharSequence getProfileSwitchingLabel(android.os.UserHandle);
+ method public java.util.List<android.os.UserHandle> getTargetUserProfiles();
+ method public void startMainActivity(android.content.ComponentName, android.os.UserHandle);
+ }
+
public final class FeatureGroupInfo implements android.os.Parcelable {
ctor public FeatureGroupInfo();
ctor public FeatureGroupInfo(android.content.pm.FeatureGroupInfo);
@@ -11481,17 +11488,6 @@ package android.content.pm {
}
-package android.content.pm.crossprofile {
-
- public class CrossProfileApps {
- method public android.graphics.drawable.Drawable getProfileSwitchingIcon(android.os.UserHandle);
- method public java.lang.CharSequence getProfileSwitchingLabel(android.os.UserHandle);
- method public java.util.List<android.os.UserHandle> getTargetUserProfiles();
- method public void startMainActivity(android.content.ComponentName, android.os.UserHandle);
- }
-
-}
-
package android.content.res {
public class AssetFileDescriptor implements java.io.Closeable android.os.Parcelable {
diff --git a/api/system-current.txt b/api/system-current.txt
index a29d3a09b60c..6539d1ded395 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3856,6 +3856,28 @@ package android.security.keystore {
}
+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 {
diff --git a/api/test-current.txt b/api/test-current.txt
index 6369bb4e1674..acc819e602d1 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -563,11 +563,6 @@ package android.service.autofill {
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 @@ package android.view {
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 9bddbcbe405b..5eff54887be1 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -134,7 +134,7 @@ LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) \
LOCAL_MODULE_CLASS := EXECUTABLES
-LOCAL_INIT_RC := statsd.rc
+#LOCAL_INIT_RC := statsd.rc
include $(BUILD_EXECUTABLE)
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index 393f795a97fe..bb2193fb442b 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -58,9 +58,9 @@ message CountBucketInfo {
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 DurationBucketInfo {
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 ValueBucketInfo {
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 GaugeBucketInfo {
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/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index 9f1e98399dce..ac6cba138eed 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -34,6 +34,7 @@ import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import dalvik.system.CloseGuard;
@@ -58,6 +59,8 @@ public class ActivityView extends ViewGroup {
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 @@ public class ActivityView extends ViewGroup {
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 @@ public class ActivityView extends ViewGroup {
} else {
mVirtualDisplay.setSurface(surfaceHolder.getSurface());
}
+ updateLocation();
}
@Override
@@ -248,6 +271,7 @@ public class ActivityView extends ViewGroup {
if (mVirtualDisplay != null) {
mVirtualDisplay.resize(width, height, getBaseDisplayDensity());
}
+ updateLocation();
}
@Override
@@ -257,6 +281,7 @@ public class ActivityView extends ViewGroup {
if (mVirtualDisplay != null) {
mVirtualDisplay.setSurface(null);
}
+ cleanTapExcludeRegion();
}
}
@@ -290,6 +315,7 @@ public class ActivityView extends ViewGroup {
if (mInputForwarder != null) {
mInputForwarder = null;
}
+ cleanTapExcludeRegion();
final boolean displayReleased;
if (mVirtualDisplay != null) {
@@ -313,6 +339,17 @@ public class ActivityView extends ViewGroup {
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/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 6eafcc437447..33277eae0520 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -38,12 +38,12 @@ import android.content.ClipboardManager;
import android.content.Context;
import android.content.IRestrictionsManager;
import android.content.RestrictionsManager;
+import android.content.pm.CrossProfileApps;
+import android.content.pm.ICrossProfileApps;
import android.content.pm.IShortcutService;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutManager;
-import android.content.pm.crossprofile.CrossProfileApps;
-import android.content.pm.crossprofile.ICrossProfileApps;
import android.content.res.Resources;
import android.hardware.ConsumerIrManager;
import android.hardware.ISerialManager;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index f69e76445aa7..265f7c7425db 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4146,7 +4146,7 @@ public abstract class Context {
/**
* Use with {@link #getSystemService(String)} to retrieve a
- * {@link android.content.pm.crossprofile.CrossProfileApps} for cross profile operations.
+ * {@link android.content.pm.CrossProfileApps} for cross profile operations.
*
* @see #getSystemService(String)
*/
diff --git a/core/java/android/content/pm/crossprofile/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index 414c13894f80..7d5d6090788c 100644
--- a/core/java/android/content/pm/crossprofile/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.content.pm.crossprofile;
+package android.content.pm;
import android.annotation.NonNull;
import android.content.ComponentName;
@@ -57,13 +57,14 @@ public class CrossProfileApps {
* action {@link android.content.Intent#ACTION_MAIN}, category
* {@link android.content.Intent#CATEGORY_LAUNCHER}. Otherwise, SecurityException will
* be thrown.
- * @param user The UserHandle of the profile, must be one of the users returned by
+ * @param targetUser The UserHandle of the profile, must be one of the users returned by
* {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will
* be thrown.
*/
- public void startMainActivity(@NonNull ComponentName component, @NonNull UserHandle user) {
+ public void startMainActivity(@NonNull ComponentName component,
+ @NonNull UserHandle targetUser) {
try {
- mService.startActivityAsUser(mContext.getPackageName(), component, user);
+ mService.startActivityAsUser(mContext.getPackageName(), component, targetUser);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
@@ -114,7 +115,7 @@ public class CrossProfileApps {
}
/**
- * Return an icon that calling app can show to user for the semantic of profile switching --
+ * Return a drawable that calling app can show to user for the semantic of profile switching --
* launching its own activity in specified user profile. For example, it may return a briefcase
* icon if the given user handle is the managed profile one.
*
@@ -124,9 +125,9 @@ public class CrossProfileApps {
* @return an icon that calling app can show user for the semantic of launching its own
* activity in specified user profile.
*
- * @see #startMainActivity(ComponentName, UserHandle, Rect, Bundle)
+ * @see #startMainActivity(ComponentName, UserHandle)
*/
- public @NonNull Drawable getProfileSwitchingIcon(@NonNull UserHandle userHandle) {
+ public @NonNull Drawable getProfileSwitchingIconDrawable(@NonNull UserHandle userHandle) {
verifyCanAccessUser(userHandle);
final boolean isManagedProfile =
diff --git a/core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl b/core/java/android/content/pm/ICrossProfileApps.aidl
index 227f91f5d3e1..e79deb96838b 100644
--- a/core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl
+++ b/core/java/android/content/pm/ICrossProfileApps.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.content.pm.crossprofile;
+package android.content.pm;
import android.content.ComponentName;
import android.content.Intent;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 396c897c7047..956e8f624b17 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9861,15 +9861,6 @@ public final class Settings {
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 000000000000..18f6dab9fc59
--- /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
+ * &mdash;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&mdash; 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 2413e97ba837..f52ac8505289 100644
--- a/core/java/android/service/autofill/CharSequenceTransformation.java
+++ b/core/java/android/service/autofill/CharSequenceTransformation.java
@@ -22,7 +22,6 @@ import android.annotation.NonNull;
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 android.widget.TextView;
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 @@ import java.util.regex.Pattern;
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 @@ public final class CharSequenceTransformation extends InternalTransformation imp
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 @@ public final class CharSequenceTransformation extends InternalTransformation imp
* 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 @@ public final class CharSequenceTransformation extends InternalTransformation imp
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 000000000000..d8e829d8f67c
--- /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 33fbf73b4371..1caea5773160 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -373,6 +373,7 @@ public final class Choreographer {
* @see #removeCallbacks
* @hide
*/
+ @TestApi
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
@@ -391,6 +392,7 @@ public final class Choreographer {
* @see #removeCallback
* @hide
*/
+ @TestApi
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
if (action == null) {
@@ -440,6 +442,7 @@ public final class Choreographer {
* @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 ed167c812be1..49f14442868b 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -236,4 +236,12 @@ interface IWindowSession {
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/View.java b/core/java/android/view/View.java
index f461e45372d3..3242ff1a0b90 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -17886,6 +17886,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * 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) {
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index de9b0d7dfa4a..f54561bfb423 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -33,6 +33,7 @@ import android.metrics.LogMaker;
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.Retention;
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 final class AutofillManager {
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 final class AutofillManager {
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 @@ public final class AutofillManager {
* 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 @@ public final class AutofillManager {
* <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 @@ public final class AutofillManager {
}
}
}
+
+ 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 1afa35e80a26..41672e7aeb9b 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -20,6 +20,7 @@ import android.content.ComponentName;
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 @@ interface IAutoFillManager {
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/EditText.java b/core/java/android/widget/EditText.java
index 336c20cdcdc0..728824c2f0dc 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -106,6 +106,10 @@ public class EditText extends TextView {
@Override
public Editable getText() {
CharSequence text = super.getText();
+ // This can only happen during construction.
+ if (text == null) {
+ return null;
+ }
if (text instanceof Editable) {
return (Editable) super.getText();
}
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
index ed9d0e9e86c3..1c2528ffecd4 100644
--- a/core/jni/android/graphics/ImageDecoder.cpp
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -393,7 +393,7 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
canvas.drawBitmap(bm, 0.0f, 0.0f, &paint);
bm.swap(scaledBm);
- nativeBitmap = scaledPixelRef;
+ nativeBitmap = std::move(scaledPixelRef);
}
if (jpostProcess) {
diff --git a/core/proto/android/server/forceappstandbytracker.proto b/core/proto/android/server/forceappstandbytracker.proto
index c9f7d52ae83f..8753bf768321 100644
--- a/core/proto/android/server/forceappstandbytracker.proto
+++ b/core/proto/android/server/forceappstandbytracker.proto
@@ -41,13 +41,4 @@ message ForceAppStandbyTrackerProto {
// 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 d2a22d0794b6..547e83c144a4 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/tests/coretests/src/android/content/pm/crossprofile/CrossProfileAppsTest.java b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java
index 80e4c0214ad4..0cfcd8f85784 100644
--- a/core/tests/coretests/src/android/content/pm/crossprofile/CrossProfileAppsTest.java
+++ b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.content.pm.crossprofile;
+package android.content.pm;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.nullable;
@@ -42,7 +42,7 @@ import java.util.List;
/**
* Build/Install/Run:
- * bit FrameworksCoreTests:android.content.pm.crossprofile.CrossProfileAppsTest
+ * atest frameworks/base/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java
*/
@Presubmit
@RunWith(MockitoJUnitRunner.class)
@@ -118,7 +118,7 @@ public class CrossProfileAppsTest {
public void getProfileSwitchingIcon_managedProfile() {
setValidTargetProfile(MANAGED_PROFILE);
- mCrossProfileApps.getProfileSwitchingIcon(MANAGED_PROFILE);
+ mCrossProfileApps.getProfileSwitchingIconDrawable(MANAGED_PROFILE);
verify(mResources).getDrawable(R.drawable.ic_corp_badge, null);
}
@@ -126,13 +126,13 @@ public class CrossProfileAppsTest {
public void getProfileSwitchingIcon_personalProfile() {
setValidTargetProfile(PERSONAL_PROFILE);
- mCrossProfileApps.getProfileSwitchingIcon(PERSONAL_PROFILE);
+ mCrossProfileApps.getProfileSwitchingIconDrawable(PERSONAL_PROFILE);
verify(mResources).getDrawable(R.drawable.ic_account_circle, null);
}
@Test(expected = SecurityException.class)
public void getProfileSwitchingIcon_securityException() {
- mCrossProfileApps.getProfileSwitchingIcon(PERSONAL_PROFILE);
+ mCrossProfileApps.getProfileSwitchingIconDrawable(PERSONAL_PROFILE);
}
private void setValidTargetProfile(UserHandle userHandle) {
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index c2ae7760c80e..8da7cedd7ea1 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -213,7 +213,6 @@ public class SettingsBackupTest {
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/packages/ExtServices/AndroidManifest.xml b/packages/ExtServices/AndroidManifest.xml
index 291009ef7005..63d3623c468a 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 000000000000..ea516a1db8b8
--- /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
index 97a386866665..d2e804af1b43 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 @@ import android.view.autofill.AutofillValue;
* 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 @@ public final class EditDistanceScorer {
* <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 000000000000..cc1571920e86
--- /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/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index d130a61f62b3..b13de2ec5ce1 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -498,8 +498,6 @@
<string name="wifi_display_certification">Wireless display certification</string>
<!-- Setting Checkbox title whether to enable WiFi Verbose Logging. [CHAR LIMIT=40] -->
<string name="wifi_verbose_logging">Enable Wi\u2011Fi Verbose Logging</string>
- <!-- Setting Checkbox title whether to enable WiFi Scanning in the presence of traffic. [CHAR LIMIT=80] -->
- <string name="wifi_allow_scan_with_traffic">Always allow Wi\u2011Fi Roam Scans</string>
<!-- Setting Checkbox title whether to always keep mobile data active. [CHAR LIMIT=80] -->
<string name="mobile_data_always_on">Mobile data always active</string>
<!-- Setting Checkbox title whether to enable hardware acceleration for tethering. [CHAR LIMIT=80] -->
@@ -554,8 +552,6 @@
<string name="wifi_display_certification_summary">Show options for wireless display certification</string>
<!-- Setting Checkbox summary whether to enable Wifi verbose Logging [CHAR LIMIT=80] -->
<string name="wifi_verbose_logging_summary">Increase Wi\u2011Fi logging level, show per SSID RSSI in Wi\u2011Fi Picker</string>
- <!-- Setting Checkbox summary whether to always allow WiFi Roam Scans [CHAR LIMIT=130] -->
- <string name="wifi_allow_scan_with_traffic_summary">Allow/Disallow Wi\u2011Fi Roam Scans based on the amount of data traffic present at the interface</string>
<!-- UI debug setting: limit size of Android logger buffers -->
<string name="select_logd_size_title">Logger buffer sizes</string>
<!-- UI debug setting: limit size of Android logger buffers [CHAR LIMIT=59] -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index dde4dcfb23fe..6437903bd12a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -775,6 +775,15 @@
<string name="quick_settings_work_mode_label">Work mode</string>
<!-- QuickSettings: Label for the toggle to activate Night display (renamed "Night Light" with title caps). [CHAR LIMIT=20] -->
<string name="quick_settings_night_display_label">Night Light</string>
+ <!-- QuickSettings: Secondary text for when the Night Light will be enabled at sunset. [CHAR LIMIT=20] -->
+ <string name="quick_settings_night_secondary_label_on_at_sunset">On at sunset</string>
+ <!-- QuickSettings: Secondary text for when the Night Light will be on until sunrise. [CHAR LIMIT=20] -->
+ <string name="quick_settings_night_secondary_label_until_sunrise">Until sunrise</string>
+ <!-- QuickSettings: Secondary text for when the Night Light will be enabled at some user-selected time. [CHAR LIMIT=20] -->
+ <string name="quick_settings_night_secondary_label_on_at">On at <xliff:g id="time" example="10 pm">%s</xliff:g></string>
+ <!-- QuickSettings: Secondary text for when the Night Light will be on until some user-selected time. [CHAR LIMIT=20] -->
+ <string name="quick_settings_night_secondary_label_until">Until <xliff:g id="time" example="7 am">%s</xliff:g></string>
+
<!-- QuickSettings: NFC tile [CHAR LIMIT=NONE] -->
<string name="quick_settings_nfc_label">NFC</string>
<!-- QuickSettings: NFC (off) [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 653e5000f72c..8501519d26aa 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 @@ public class KeyguardViewMediator extends SystemUI {
&& !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/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
index a226f3cac83a..9eb9906ba3b5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
@@ -29,7 +29,6 @@ import android.widget.TextView;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
-import com.android.systemui.R.id;
import com.android.systemui.plugins.qs.QSIconView;
import com.android.systemui.plugins.qs.QSTile;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index 763ffc677bc2..99a9be38065d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Intent;
import android.provider.Settings;
@@ -29,9 +30,17 @@ import com.android.systemui.qs.QSHost;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import java.time.format.DateTimeFormatter;
+
public class NightDisplayTile extends QSTileImpl<BooleanState>
implements ColorDisplayController.Callback {
+ /**
+ * Pattern for {@link java.time.format.DateTimeFormatter} used to approximate the time to the
+ * nearest hour and add on the AM/PM indicator.
+ */
+ private static final String APPROXIMATE_HOUR_DATE_TIME_PATTERN = "H a";
+
private ColorDisplayController mController;
private boolean mIsListening;
@@ -74,13 +83,49 @@ public class NightDisplayTile extends QSTileImpl<BooleanState>
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
- final boolean isActivated = mController.isActivated();
- state.value = isActivated;
+ state.value = mController.isActivated();
state.label = state.contentDescription =
mContext.getString(R.string.quick_settings_night_display_label);
state.icon = ResourceIcon.get(R.drawable.ic_qs_night_display_on);
state.expandedAccessibilityClassName = Switch.class.getName();
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
+ state.secondaryLabel = getSecondaryLabel(state.value);
+ }
+
+ /**
+ * Returns a {@link String} for the secondary label that reflects when the light will be turned
+ * on or off based on the current auto mode and night light activated status.
+ */
+ @Nullable
+ private String getSecondaryLabel(boolean isNightLightActivated) {
+ switch(mController.getAutoMode()) {
+ case ColorDisplayController.AUTO_MODE_TWILIGHT:
+ // Auto mode related to sunrise & sunset. If the light is on, it's guaranteed to be
+ // turned off at sunrise. If it's off, it's guaranteed to be turned on at sunset.
+ return isNightLightActivated
+ ? mContext.getString(
+ R.string.quick_settings_night_secondary_label_until_sunrise)
+ : mContext.getString(
+ R.string.quick_settings_night_secondary_label_on_at_sunset);
+
+ case ColorDisplayController.AUTO_MODE_CUSTOM:
+ // User-specified time, approximated to the nearest hour.
+ return isNightLightActivated
+ ? mContext.getString(
+ R.string.quick_settings_night_secondary_label_until,
+ mController.getCustomEndTime().format(
+ DateTimeFormatter.ofPattern(
+ APPROXIMATE_HOUR_DATE_TIME_PATTERN)))
+ : mContext.getString(
+ R.string.quick_settings_night_secondary_label_on_at,
+ mController.getCustomStartTime().format(
+ DateTimeFormatter.ofPattern(
+ APPROXIMATE_HOUR_DATE_TIME_PATTERN)));
+
+ default:
+ // No secondary label when auto mode is disabled.
+ return null;
+ }
}
@Override
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index cac7fedd0b00..6d845f9a9d3a 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.Build;
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.UserHandle;
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 com.android.server.autofill.ui.AutoFillUI;
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 @@ public final class AutofillManagerService extends SystemService {
}
}
+ // 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 @@ public final class AutofillManagerService extends SystemService {
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 @@ public final class AutofillManagerService extends SystemService {
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 @@ public final class AutofillManagerService extends SystemService {
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 @@ public final class AutofillManagerService extends SystemService {
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 @@ public final class AutofillManagerService extends SystemService {
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 @@ public final class AutofillManagerService extends SystemService {
}
@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 @@ public final class AutofillManagerService extends SystemService {
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 @@ public final class AutofillManagerService extends SystemService {
@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 @@ public final class AutofillManagerService extends SystemService {
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 @@ public final class AutofillManagerService extends SystemService {
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 @@ public final class AutofillManagerService extends SystemService {
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 @@ public final class AutofillManagerService extends SystemService {
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 @@ public final class AutofillManagerService extends SystemService {
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 final class AutofillManagerService extends SystemService {
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 da74dba31416..a5bd59a9e77d 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.Binder;
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.ArrayMap;
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 com.android.server.autofill.ui.AutoFillUI;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.Random;
@@ -124,137 +116,7 @@ final class AutofillManagerServiceImpl {
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 @@ final class AutofillManagerServiceImpl {
mUiLatencyHistory = uiLatencyHistory;
mUserId = userId;
mUi = ui;
+ mFieldClassificationStrategy = new FieldClassificationStrategy(context, userId);
updateLocked(disabled);
}
@@ -1089,10 +952,8 @@ final class AutofillManagerServiceImpl {
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 @@ final class AutofillManagerServiceImpl {
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 f3de557ec540..44560879f028 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 @@ public final class AutofillManagerServiceShellCommand extends ShellCommand {
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 @@ public final class AutofillManagerServiceShellCommand extends ShellCommand {
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 @@ public final class AutofillManagerServiceShellCommand extends ShellCommand {
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 @@ public final class AutofillManagerServiceShellCommand extends ShellCommand {
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 000000000000..7228f1d2a8ea
--- /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 f5d1336a0f6e..a0e23a152224 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 @@ package com.android.server.autofill;
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_VALUE_CHANGED;
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.Parcelable;
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.MetricsLogger;
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.Collections;
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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
// 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
* {@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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mComponentName.getPackageName());
return;
}
- final FieldClassificationScores matrix = result.getParcelable(EXTRA_SCORES);
-
- // Then use the results.
- for (int i = 0; i < viewsSize; i++) {
- final AutofillId fieldId = fieldIds[i];
+ 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();
- 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);
+ int i = 0, j = 0;
+ try {
+ for (i = 0; i < viewsSize; i++) {
+ final AutofillId fieldId = fieldIds[i];
+
+ 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
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 30fd25a92484..5b901ee2b3da 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 @@ public class TransportManager {
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 @@ public class TransportManager {
@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 @@ public class TransportManager {
* 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 @@ public class TransportManager {
*/
@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) {
+ // 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) {
- // Shouldn't happen because we are holding the lock
- Slog.wtf(TAG, "Transport unexpectedly not registered");
+ Slog.wtf(TAG, "Transport got unregistered");
return BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
}
}
@@ -512,13 +531,11 @@ public class TransportManager {
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 @@ public class TransportManager {
/**
* 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 @@ public class TransportManager {
/** 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/ForceAppStandbyTracker.java b/services/core/java/com/android/server/ForceAppStandbyTracker.java
index 45516115b629..a75a3675f7f9 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.Context;
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 @@ public class ForceAppStandbyTracker {
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 @@ public class ForceAppStandbyTracker {
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 @@ public class ForceAppStandbyTracker {
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 @@ public class ForceAppStandbyTracker {
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 @@ public class ForceAppStandbyTracker {
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 @@ public class ForceAppStandbyTracker {
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 @@ public class ForceAppStandbyTracker {
return LocalServices.getService(PowerManagerInternal.class);
}
- @VisibleForTesting
- boolean isSmallBatteryDevice() {
- return ActivityManager.isSmallBatteryDevice();
- }
-
/**
* Update {@link #mRunAnyRestrictedPackages} with the current app ops state.
*/
@@ -434,29 +369,18 @@ public class ForceAppStandbyTracker {
}
}
- 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 @@ public class ForceAppStandbyTracker {
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 @@ public class ForceAppStandbyTracker {
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 @@ public class ForceAppStandbyTracker {
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 @@ public class ForceAppStandbyTracker {
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 @@ public class ForceAppStandbyTracker {
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 @@ public class ForceAppStandbyTracker {
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 55046959e4dd..21137adce69a 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.XmlPullParserException;
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.ServiceManager;
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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
@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 ea748db159e1..6c63f43234ab 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -232,10 +232,9 @@ public class LocationManagerService extends ILocationManager.Stub {
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 @@ public class LocationManagerService extends ILocationManager.Stub {
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 @@ public class LocationManagerService extends ILocationManager.Stub {
+ (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 @@ public class LocationManagerService extends ILocationManager.Stub {
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 class LocationManagerService extends ILocationManager.Stub {
public void removeGnssMeasurementsListener(IGnssMeasurementsListener listener) {
if (mGnssMeasurementsProvider != null) {
synchronized (mLock) {
- mGnssMeasurementsListeners.remove(listener);
+ mGnssMeasurementsListeners.remove(listener.asBinder());
mGnssMeasurementsProvider.removeListener(listener);
}
}
@@ -2438,7 +2439,7 @@ public class LocationManagerService extends ILocationManager.Stub {
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 class LocationManagerService extends ILocationManager.Stub {
public void removeGnssNavigationMessageListener(IGnssNavigationMessageListener listener) {
if (mGnssNavigationMessageProvider != null) {
synchronized (mLock) {
- mGnssNavigationMessageListeners.remove(listener);
+ mGnssNavigationMessageListeners.remove(listener.asBinder());
mGnssNavigationMessageProvider.removeListener(listener);
}
}
@@ -3180,6 +3181,16 @@ public class LocationManagerService extends ILocationManager.Stub {
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/pm/crossprofile/CrossProfileAppsService.java b/services/core/java/com/android/server/pm/CrossProfileAppsService.java
index 0913269f35e1..027a302a325e 100644
--- a/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsService.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsService.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.server.pm.crossprofile;
+package com.android.server.pm;
import android.content.Context;
diff --git a/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index a517d6d1a99e..2007a0e43aa1 100644
--- a/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.server.pm.crossprofile;
+package com.android.server.pm;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
@@ -25,14 +25,12 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.pm.ICrossProfileApps;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
-import android.content.pm.crossprofile.ICrossProfileApps;
-import android.graphics.Rect;
import android.os.Binder;
-import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ef486614fd2a..3997c56eec07 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.IBinder;
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 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
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 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
}
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);
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 192d6c84e190..334be336e199 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -467,6 +467,17 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
}
}
+ @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 000000000000..cbc936f2f1d7
--- /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 fa7ea2ffdd17..26c87b738f8c 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 @@ class TaskPositioner {
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 @@ class TaskPositioner {
}
}
+ /** Use {@link #create(WindowManagerService)} instead **/
TaskPositioner(WindowManagerService service) {
mService = service;
}
@@ -622,4 +625,22 @@ class TaskPositioner {
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 4dfe290c4f7d..a3f4ee80dfcb 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 @@ class TaskPositioningController {
}
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 10e789345232..53086f714a79 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 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ /**
+ * 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 db30db074838..477dd2bb9633 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
+ /**
+ * 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/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3199bfa49455..e660c50fcbc1 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -46,10 +46,10 @@ import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.os.storage.IStorageManager;
-import android.util.TimingsTraceLog;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Slog;
+import android.util.TimingsTraceLog;
import android.view.WindowManager;
import com.android.internal.R;
@@ -57,20 +57,21 @@ import com.android.internal.app.ColorDisplayController;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BinderInternal;
-import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.util.ConcurrentUtils;
+import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.widget.ILockSettings;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.am.ActivityManagerService;
import com.android.server.audio.AudioService;
+import com.android.server.broadcastradio.BroadcastRadioService;
import com.android.server.camera.CameraServiceProxy;
import com.android.server.car.CarServiceHelperService;
import com.android.server.clipboard.ClipboardService;
import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.coverage.CoverageService;
import com.android.server.devicepolicy.DevicePolicyManagerService;
-import com.android.server.display.DisplayManagerService;
import com.android.server.display.ColorDisplayService;
+import com.android.server.display.DisplayManagerService;
import com.android.server.dreams.DreamManagerService;
import com.android.server.emergency.EmergencyAffordanceService;
import com.android.server.fingerprint.FingerprintService;
@@ -92,17 +93,16 @@ import com.android.server.om.OverlayManagerService;
import com.android.server.os.DeviceIdentifiersPolicyService;
import com.android.server.os.SchedulingPolicyService;
import com.android.server.pm.BackgroundDexOptService;
+import com.android.server.pm.CrossProfileAppsService;
import com.android.server.pm.Installer;
import com.android.server.pm.LauncherAppsService;
import com.android.server.pm.OtaDexoptService;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.ShortcutService;
import com.android.server.pm.UserManagerService;
-import com.android.server.pm.crossprofile.CrossProfileAppsService;
import com.android.server.policy.PhoneWindowManager;
import com.android.server.power.PowerManagerService;
import com.android.server.power.ShutdownThread;
-import com.android.server.broadcastradio.BroadcastRadioService;
import com.android.server.restrictions.RestrictionsManagerService;
import com.android.server.security.KeyAttestationApplicationIdProviderService;
import com.android.server.security.KeyChainSystemService;
diff --git a/services/robotests/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
index cf0bc235dc10..6753d73590d3 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 @@ package com.android.server.backup;
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.mockito.Mockito.when;
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.backup.transport.TransportNotRegisteredException;
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.RuntimeEnvironment;
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 @@ public class TransportManagerTest {
}
@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 6a21931e0418..66d0da13fff1 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.BroadcastReceiver;
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.PowerSaveState;
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 @@ public class ForceAppStandbyTrackerTest {
PowerManagerInternal injectPowerManagerInternal() {
return mMockPowerManagerInternal;
}
-
- @Override
- boolean isSmallBatteryDevice() { return mIsSmallBatteryDevice; };
}
private static final int UID_1 = Process.FIRST_APPLICATION_UID + 1;
@@ -145,11 +137,7 @@ public class ForceAppStandbyTrackerTest {
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 @@ public class ForceAppStandbyTrackerTest {
}
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 @@ public class ForceAppStandbyTrackerTest {
verify(mMockPowerManagerInternal).registerLowPowerModeObserver(
eq(ServiceType.FORCE_ALL_APPS_STANDBY),
powerSaveObserverCaptor.capture());
+
verify(mMockContext).registerReceiver(
receiverCaptor.capture(), any(IntentFilter.class));
@@ -236,7 +221,6 @@ public class ForceAppStandbyTrackerTest {
assertNotNull(mAppOpsCallback);
assertNotNull(mPowerSaveObserver);
assertNotNull(mReceiver);
- assertNotNull(instance.mFlagsObserver);
}
private void setAppOps(int uid, String packageName, boolean restrict) throws RemoteException {
@@ -838,33 +822,6 @@ public class ForceAppStandbyTrackerTest {
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/pm/crossprofile/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
index ff55a2ba120b..c69437dc798e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/crossprofile/CrossProfileAppsServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
@@ -1,9 +1,7 @@
-package com.android.server.pm.crossprofile;
+package com.android.server.pm;
import static com.google.common.truth.Truth.assertThat;
-import static junit.framework.Assert.assertEquals;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -34,7 +32,6 @@ import android.util.SparseArray;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@@ -44,7 +41,7 @@ import java.util.List;
/**
* Build/Install/Run:
- * bit FrameworksServicesTests:com.android.server.pm.crossprofile.CrossProfileAppsServiceImplTest
+ * atest FrameworksServicesTests:com.android.server.pm.CrossProfileAppsServiceImplTest
*/
@Presubmit
@RunWith(MockitoJUnitRunner.class)
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 873a01bd8116..7bf7dd78711c 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.WindowManagerService.dipToPixel;
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 @@ public class TaskPositionerTests extends WindowTestsBase {
@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 @@ public class TaskPositionerTests extends WindowTestsBase {
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/ims/feature/MMTelFeature.java b/telephony/java/android/telephony/ims/feature/MMTelFeature.java
index 4e095e3a7003..519710728403 100644
--- a/telephony/java/android/telephony/ims/feature/MMTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MMTelFeature.java
@@ -19,6 +19,7 @@ package android.telephony.ims.feature;
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.IImsEcbm;
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 @@ public class MMTelFeature extends ImsFeature {
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 @@ public class MMTelFeature extends ImsFeature {
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 eb805a88da8e..33b23d94ad34 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 @@ import java.lang.annotation.RetentionPolicy;
public class SmsImplBase {
private static final String LOG_TAG = "SmsImplBase";
+ /** @hide */
@IntDef({
SEND_STATUS_OK,
SEND_STATUS_ERROR,
@@ -68,6 +68,7 @@ public class SmsImplBase {
*/
public static final int SEND_STATUS_ERROR_FALLBACK = 4;
+ /** @hide */
@IntDef({
DELIVER_STATUS_OK,
DELIVER_STATUS_ERROR
@@ -84,6 +85,7 @@ public class SmsImplBase {
*/
public static final int DELIVER_STATUS_ERROR = 2;
+ /** @hide */
@IntDef({
STATUS_REPORT_STATUS_OK,
STATUS_REPORT_STATUS_ERROR
@@ -114,9 +116,9 @@ public class SmsImplBase {
* @hide
*/
public final void registerSmsListener(IImsSmsListener listener) {
- synchronized (mLock) {
- mListener = listener;
- }
+ synchronized (mLock) {
+ mListener = listener;
+ }
}
/**
@@ -128,8 +130,8 @@ public class SmsImplBase {
* 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 @@ public class SmsImplBase {
* @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 @@ public class SmsImplBase {
* @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 @@ public class SmsImplBase {
}
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 000000000000..113dad4696ef
--- /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 52b3853ec5b6..cce39f4daf2a 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.IImsConfig;
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 @@ interface IImsMMTelFeature {
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 000000000000..5a4b7e489025
--- /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/wifi/java/android/net/wifi/ISoftApCallback.aidl b/wifi/java/android/net/wifi/ISoftApCallback.aidl
new file mode 100644
index 000000000000..b8d2971e74bb
--- /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 70d6ce46bcf0..e9e61a546e14 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.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.net.wifi.hotspot2.IProvisioningCallback;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.ScanSettings;
-import android.net.wifi.ScanResult;
+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.Network;
-
-import android.net.DhcpInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
import android.os.Messenger;
import android.os.ResultReceiver;
@@ -178,5 +178,9 @@ interface IWifiManager
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 aa75a0703a84..99080d6cc2c0 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 com.android.server.net.NetworkPinner;
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 class WifiManager {
*/
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 @@ public class WifiManager {
@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 @@ public class WifiManager {
}
/**
+ * 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 0df5615385fe..4b5f6452d392 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_INCOMP
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.LocalOnlyHotspotCallback;
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 @@ public class WifiManagerTest {
@Mock ApplicationInfo mApplicationInfo;
@Mock WifiConfiguration mApConfig;
@Mock IBinder mAppBinder;
+ @Mock SoftApCallback mSoftApCallback;
private Handler mHandler;
private TestLooper mLooper;
@@ -632,6 +642,149 @@ public class WifiManagerTest {
}
/**
+ * 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