diff options
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 + * —a full-match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}. + * + * <p>The exact score depends on the algorithm used to calculate it— the service must provide + * at least one default algorithm (which is used when the algorithm is not specified or is invalid), + * but it could provide more (in which case the algorithm name should be specifiied by the caller + * when calculating the scores). + * + * {@hide} + */ +@SystemApi +public abstract class AutofillFieldClassificationService extends Service { + + private static final String TAG = "AutofillFieldClassificationService"; + + private static final int MSG_GET_AVAILABLE_ALGORITHMS = 1; + private static final int MSG_GET_DEFAULT_ALGORITHM = 2; + private static final int MSG_GET_SCORES = 3; + + /** + * The {@link Intent} action that must be declared as handled by a service + * in its manifest for the system to recognize it as a quota providing service. + */ + public static final String SERVICE_INTERFACE = + "android.service.autofill.AutofillFieldClassificationService"; + + /** {@hide} **/ + public static final String EXTRA_SCORES = "scores"; + + private AutofillFieldClassificationServiceWrapper mWrapper; + + private final HandlerCaller.Callback mHandlerCallback = (msg) -> { + final int action = msg.what; + final Bundle data = new Bundle(); + final RemoteCallback callback; + switch (action) { + case MSG_GET_AVAILABLE_ALGORITHMS: + callback = (RemoteCallback) msg.obj; + final List<String> availableAlgorithms = onGetAvailableAlgorithms(); + String[] asArray = null; + if (availableAlgorithms != null) { + asArray = new String[availableAlgorithms.size()]; + availableAlgorithms.toArray(asArray); + } + data.putStringArray(EXTRA_AVAILABLE_ALGORITHMS, asArray); + break; + case MSG_GET_DEFAULT_ALGORITHM: + callback = (RemoteCallback) msg.obj; + final String defaultAlgorithm = onGetDefaultAlgorithm(); + data.putString(EXTRA_DEFAULT_ALGORITHM, defaultAlgorithm); + break; + case MSG_GET_SCORES: + final SomeArgs args = (SomeArgs) msg.obj; + callback = (RemoteCallback) args.arg1; + final String algorithmName = (String) args.arg2; + final Bundle algorithmArgs = (Bundle) args.arg3; + @SuppressWarnings("unchecked") + final List<AutofillValue> actualValues = ((List<AutofillValue>) args.arg4); + @SuppressWarnings("unchecked") + final String[] userDataValues = (String[]) args.arg5; + final Scores scores = onGetScores(algorithmName, algorithmArgs, actualValues, + Arrays.asList(userDataValues)); + data.putParcelable(EXTRA_SCORES, scores); + break; + default: + Log.w(TAG, "Handling unknown message: " + action); + return; + } + callback.sendResult(data); + }; + + private final HandlerCaller mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(), + mHandlerCallback, true); + + /** @hide */ + public AutofillFieldClassificationService() { + + } + + @Override + public void onCreate() { + super.onCreate(); + mWrapper = new AutofillFieldClassificationServiceWrapper(); + } + + @Override + public IBinder onBind(Intent intent) { + return mWrapper; + } + + /** + * Gets the name of all available algorithms. + * + * @throws UnsupportedOperationException if not implemented by service. + */ + // TODO(b/70939974): rename to onGetAvailableAlgorithms if not removed + @NonNull + public List<String> onGetAvailableAlgorithms() { + throw new UnsupportedOperationException("Must be implemented by external service"); + } + + /** + * Gets the default algorithm that's used when an algorithm is not specified or is invalid. + * + * @throws UnsupportedOperationException if not implemented by service. + */ + @NonNull + public String onGetDefaultAlgorithm() { + throw new UnsupportedOperationException("Must be implemented by external service"); + } + + /** + * Calculates field classification scores in a batch. + * + * <p>See {@link AutofillFieldClassificationService} for more info about field classification + * scores. + * + * @param algorithm name of the algorithm to be used to calculate the scores. If invalid, the + * default algorithm will be used instead. + * @param args optional arguments to be passed to the algorithm. + * @param actualValues values entered by the user. + * @param userDataValues values predicted from the user data. + * @return the calculated scores and the algorithm used. + * + * {@hide} + */ + @Nullable + @SystemApi + public Scores onGetScores(@Nullable String algorithm, + @Nullable Bundle args, @NonNull List<AutofillValue> actualValues, + @NonNull List<String> userDataValues) { + throw new UnsupportedOperationException("Must be implemented by external service"); + } + + private final class AutofillFieldClassificationServiceWrapper + extends IAutofillFieldClassificationService.Stub { + + @Override + public void getAvailableAlgorithms(RemoteCallback callback) throws RemoteException { + mHandlerCaller.obtainMessageO(MSG_GET_AVAILABLE_ALGORITHMS, callback).sendToTarget(); + } + + @Override + public void getDefaultAlgorithm(RemoteCallback callback) throws RemoteException { + mHandlerCaller.obtainMessageO(MSG_GET_DEFAULT_ALGORITHM, callback).sendToTarget(); + } + + @Override + public void getScores(RemoteCallback callback, String algorithmName, Bundle algorithmArgs, + List<AutofillValue> actualValues, String[] userDataValues) + throws RemoteException { + // TODO(b/70939974): refactor to use PooledLambda + mHandlerCaller.obtainMessageOOOOO(MSG_GET_SCORES, callback, algorithmName, + algorithmArgs, actualValues, userDataValues).sendToTarget(); + } + } + + + // TODO(b/70939974): it might be simpler to remove this class and return the float[][] directly, + // ignoring the request if the algorithm name is invalid. + /** + * Represents field classification scores used in a batch calculation. + * + * {@hide} + */ + @SystemApi + public static final class Scores implements Parcelable { + private final String mAlgorithmName; + private final float[][] mScores; + + /* @hide */ + public Scores(String algorithmName, int size1, int size2) { + mAlgorithmName = algorithmName; + mScores = new float[size1][size2]; + } + + public Scores(Parcel parcel) { + mAlgorithmName = parcel.readString(); + final int size1 = parcel.readInt(); + final int size2 = parcel.readInt(); + mScores = new float[size1][size2]; + for (int i = 0; i < size1; i++) { + for (int j = 0; j < size2; j++) { + mScores[i][j] = parcel.readFloat(); + } + } + } + + /** + * Gets the name of algorithm used to calculate the score. + */ + @NonNull + public String getAlgorithm() { + return mAlgorithmName; + } + + /** + * Gets the resulting scores, with the 1st dimension representing actual values and the 2nd + * dimension values from {@link UserData}. + */ + @NonNull + public float[][] getScores() { + return mScores; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeString(mAlgorithmName); + int size1 = mScores.length; + int size2 = mScores[0].length; + parcel.writeInt(size1); + parcel.writeInt(size2); + for (int i = 0; i < size1; i++) { + for (int j = 0; j < size2; j++) { + parcel.writeFloat(mScores[i][j]); + } + } + } + + public static final Creator<Scores> CREATOR = new Creator<Scores>() { + + @Override + public Scores createFromParcel(Parcel parcel) { + return new Scores(parcel); + } + + @Override + public Scores[] newArray(int size) { + return new Scores[size]; + } + + }; + } +} diff --git a/core/java/android/service/autofill/CharSequenceTransformation.java b/core/java/android/service/autofill/CharSequenceTransformation.java index 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 |