diff options
73 files changed, 1503 insertions, 2214 deletions
diff --git a/Android.mk b/Android.mk index e27aa307e1e6..ee7d47103f6a 100644 --- a/Android.mk +++ b/Android.mk @@ -320,8 +320,6 @@ LOCAL_SRC_FILES += \ core/java/android/service/wallpaper/IWallpaperService.aidl \ core/java/android/service/chooser/IChooserTargetService.aidl \ core/java/android/service/chooser/IChooserTargetResult.aidl \ - core/java/android/service/resolver/IResolverRankerService.aidl \ - core/java/android/service/resolver/IResolverRankerResult.aidl \ core/java/android/text/ITextClassificationService.aidl \ core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl\ core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl\ @@ -730,7 +728,6 @@ aidl_files := \ frameworks/base/core/java/android/service/notification/SnoozeCriterion.aidl \ frameworks/base/core/java/android/service/notification/StatusBarNotification.aidl \ frameworks/base/core/java/android/service/chooser/ChooserTarget.aidl \ - frameworks/base/core/java/android/service/resolver/ResolverTarget.aidl \ frameworks/base/core/java/android/speech/tts/Voice.aidl \ frameworks/base/core/java/android/app/usage/CacheQuotaHint.aidl \ frameworks/base/core/java/android/app/usage/ExternalStorageStats.aidl \ diff --git a/api/current.txt b/api/current.txt index 580cb247f760..c5f4816474dc 100644 --- a/api/current.txt +++ b/api/current.txt @@ -7044,6 +7044,7 @@ package android.appwidget { method public android.appwidget.AppWidgetProviderInfo getAppWidgetInfo(int); method public android.os.Bundle getAppWidgetOptions(int); method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProviders(); + method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForPackage(java.lang.String, android.os.UserHandle); method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfile(android.os.UserHandle); method public static android.appwidget.AppWidgetManager getInstance(android.content.Context); method public boolean isRequestPinAppWidgetSupported(); @@ -38750,6 +38751,7 @@ package android.telecom { method public java.lang.String getCallerDisplayName(); method public int getCallerDisplayNamePresentation(); method public final long getConnectTimeMillis(); + method public long getCreationTimeMillis(); method public android.telecom.DisconnectCause getDisconnectCause(); method public android.os.Bundle getExtras(); method public android.telecom.GatewayInfo getGatewayInfo(); diff --git a/api/system-current.txt b/api/system-current.txt index ed87cb339042..9b295bfe95d6 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -53,7 +53,6 @@ package android { field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE"; field public static final java.lang.String BIND_QUICK_SETTINGS_TILE = "android.permission.BIND_QUICK_SETTINGS_TILE"; field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS"; - field public static final java.lang.String BIND_RESOLVER_RANKER_SERVICE = "android.permission.BIND_RESOLVER_RANKER_SERVICE"; field public static final java.lang.String BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE = "android.permission.BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE"; field public static final java.lang.String BIND_SCREENING_SERVICE = "android.permission.BIND_SCREENING_SERVICE"; field public static final java.lang.String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE"; @@ -7504,6 +7503,7 @@ package android.appwidget { method public android.appwidget.AppWidgetProviderInfo getAppWidgetInfo(int); method public android.os.Bundle getAppWidgetOptions(int); method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProviders(); + method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForPackage(java.lang.String, android.os.UserHandle); method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfile(android.os.UserHandle); method public static android.appwidget.AppWidgetManager getInstance(android.content.Context); method public boolean isRequestPinAppWidgetSupported(); @@ -40620,36 +40620,6 @@ package android.service.quicksettings { } -package android.service.resolver { - - public abstract class ResolverRankerService extends android.app.Service { - ctor public ResolverRankerService(); - method public android.os.IBinder onBind(android.content.Intent); - method public void onPredictSharingProbabilities(java.util.List<android.service.resolver.ResolverTarget>); - method public void onTrainRankingModel(java.util.List<android.service.resolver.ResolverTarget>, int); - field public static final java.lang.String BIND_PERMISSION = "android.permission.BIND_RESOLVER_RANKER_SERVICE"; - field public static final java.lang.String SERVICE_INTERFACE = "android.service.resolver.ResolverRankerService"; - } - - public final class ResolverTarget implements android.os.Parcelable { - ctor public ResolverTarget(); - method public int describeContents(); - method public float getChooserScore(); - method public float getLaunchScore(); - method public float getRecencyScore(); - method public float getSelectProbability(); - method public float getTimeSpentScore(); - method public void setChooserScore(float); - method public void setLaunchScore(float); - method public void setRecencyScore(float); - method public void setSelectProbability(float); - method public void setTimeSpentScore(float); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.service.resolver.ResolverTarget> CREATOR; - } - -} - package android.service.restrictions { public abstract class RestrictionsReceiver extends android.content.BroadcastReceiver { @@ -41973,6 +41943,7 @@ package android.telecom { method public java.lang.String getCallerDisplayName(); method public int getCallerDisplayNamePresentation(); method public final long getConnectTimeMillis(); + method public long getCreationTimeMillis(); method public android.telecom.DisconnectCause getDisconnectCause(); method public android.os.Bundle getExtras(); method public android.telecom.GatewayInfo getGatewayInfo(); diff --git a/api/test-current.txt b/api/test-current.txt index c7f2a9f8df41..6db91be96ca6 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -7074,6 +7074,7 @@ package android.appwidget { method public android.appwidget.AppWidgetProviderInfo getAppWidgetInfo(int); method public android.os.Bundle getAppWidgetOptions(int); method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProviders(); + method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForPackage(java.lang.String, android.os.UserHandle); method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfile(android.os.UserHandle); method public static android.appwidget.AppWidgetManager getInstance(android.content.Context); method public boolean isRequestPinAppWidgetSupported(); @@ -38954,6 +38955,7 @@ package android.telecom { method public java.lang.String getCallerDisplayName(); method public int getCallerDisplayNamePresentation(); method public final long getConnectTimeMillis(); + method public long getCreationTimeMillis(); method public android.telecom.DisconnectCause getDisconnectCause(); method public android.os.Bundle getExtras(); method public android.telecom.GatewayInfo getGatewayInfo(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index d1d462c2fbe8..7299d6bd0ed7 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -38,6 +38,7 @@ import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ParceledListSlice; import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.content.res.AssetManager; @@ -869,16 +870,20 @@ public final class ActivityThread { sendMessage(H.UNBIND_SERVICE, s); } - public final void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId, - int flags ,Intent args) { - ServiceArgsData s = new ServiceArgsData(); - s.token = token; - s.taskRemoved = taskRemoved; - s.startId = startId; - s.flags = flags; - s.args = args; + public final void scheduleServiceArgs(IBinder token, ParceledListSlice args) { + List<ServiceStartArgs> list = args.getList(); + + for (int i = 0; i < list.size(); i++) { + ServiceStartArgs ssa = list.get(i); + ServiceArgsData s = new ServiceArgsData(); + s.token = token; + s.taskRemoved = ssa.taskRemoved; + s.startId = ssa.startId; + s.flags = ssa.flags; + s.args = ssa.args; - sendMessage(H.SERVICE_ARGS, s); + sendMessage(H.SERVICE_ARGS, s); + } } public final void scheduleStopService(IBinder token) { diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index f4d26fd7d9d8..079bbcdd4951 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -635,6 +635,11 @@ interface IActivityManager { */ int getLastResumedActivityUserId(); + /** + * Add a bare uid to the background restrictions whitelist. Only the system uid may call this. + */ + void backgroundWhitelistUid(int uid); + // WARNING: when these transactions are updated, check if they are any callers on the native // side. If so, make sure they are using the correct transaction ids and arguments. // If a transaction which will also be used on the native side is being inserted, add it diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index 6c43fe3beca1..1b3c00b61539 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -25,6 +25,7 @@ import android.content.IIntentReceiver; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.ParceledListSlice; import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.content.res.CompatibilityInfo; @@ -86,8 +87,7 @@ oneway interface IApplicationThread { in Bundle coreSettings, in String buildSerial); void scheduleExit(); void scheduleConfigurationChanged(in Configuration config); - void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId, - int flags, in Intent args); + void scheduleServiceArgs(IBinder token, in ParceledListSlice args); void updateTimeZone(); void processInBackground(); void scheduleBindService(IBinder token, diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 489a0f0975ae..d620a81e985b 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -945,7 +945,7 @@ public class ResourcesManager { final ResourcesKey key = mResourceImpls.keyAt(i); final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; - if (impl != null && key.mResDir.equals(assetPath)) { + if (impl != null && Objects.equals(key.mResDir, assetPath)) { if (!ArrayUtils.contains(key.mLibDirs, libAsset)) { final int newLibAssetCount = 1 + (key.mLibDirs != null ? key.mLibDirs.length : 0); diff --git a/core/java/android/service/resolver/ResolverTarget.aidl b/core/java/android/app/ServiceStartArgs.aidl index 6cab2d4df908..fd2cf0f93dc6 100644 --- a/core/java/android/service/resolver/ResolverTarget.aidl +++ b/core/java/android/app/ServiceStartArgs.aidl @@ -14,9 +14,7 @@ * limitations under the License. */ -package android.service.resolver; +package android.app; -/** - * @hide - */ -parcelable ResolverTarget; +/** @hide */ +parcelable ServiceStartArgs; diff --git a/core/java/android/app/ServiceStartArgs.java b/core/java/android/app/ServiceStartArgs.java new file mode 100644 index 000000000000..f030cbaaecaf --- /dev/null +++ b/core/java/android/app/ServiceStartArgs.java @@ -0,0 +1,82 @@ +/* + * 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.app; + +import android.content.Intent; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Describes a Service.onStartCommand() request from the system. + * @hide + */ +public class ServiceStartArgs implements Parcelable { + final public boolean taskRemoved; + final public int startId; + final public int flags; + final public Intent args; + + public ServiceStartArgs(boolean _taskRemoved, int _startId, int _flags, Intent _args) { + taskRemoved = _taskRemoved; + startId = _startId; + flags = _flags; + args = _args; + } + + public String toString() { + return "ServiceStartArgs{taskRemoved=" + taskRemoved + ", startId=" + startId + + ", flags=0x" + Integer.toHexString(flags) + ", args=" + args + "}"; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel out, int flags) { + out.writeInt(taskRemoved ? 1 : 0); + out.writeInt(startId); + out.writeInt(flags); + if (args != null) { + out.writeInt(1); + args.writeToParcel(out, 0); + } else { + out.writeInt(0); + } + } + + public static final Parcelable.Creator<ServiceStartArgs> CREATOR + = new Parcelable.Creator<ServiceStartArgs>() { + public ServiceStartArgs createFromParcel(Parcel in) { + return new ServiceStartArgs(in); + } + + public ServiceStartArgs[] newArray(int size) { + return new ServiceStartArgs[size]; + } + }; + + public ServiceStartArgs(Parcel in) { + taskRemoved = in.readInt() != 0; + startId = in.readInt(); + flags = in.readInt(); + if (in.readInt() != 0) { + args = Intent.CREATOR.createFromParcel(in); + } else { + args = null; + } + } +} diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index bb5d830820aa..82ad8252c5a9 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2768,9 +2768,11 @@ public class DevicePolicyManager { * or clears the lockscreen password. * <p> * <em>This token is highly sensitive and should be treated at the same level as user - * credentials. In particular, NEVER store this token on device in plaintext, especially in - * Device-Encrypted storage if the token will be used to reset password on FBE devices before - * user unlocks. + * credentials. In particular, NEVER store this token on device in plaintext. Do not store + * the plaintext token in device-encrypted storage if it will be needed to reset password on + * file-based encryption devices before user unlocks. Consider carefully how any password token + * will be stored on your server and who will need access to them. Tokens may be the subject of + * legal access requests. * </em> * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index 7d2db5c9f384..fe51633b50eb 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -27,9 +27,6 @@ import android.view.ViewStructure.HtmlInfo; import android.view.ViewStructure.HtmlInfo.Builder; import android.view.WindowManager; import android.view.WindowManagerGlobal; -import android.view.autofill.AutoFillId; -import android.view.autofill.AutoFillType; -import android.view.autofill.AutoFillValue; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; @@ -920,15 +917,6 @@ public class AssistStructure implements Parcelable { } /** - * @hide - * @deprecated TODO(b/35956626): remove once clients use getAutoFilltype - */ - @Deprecated - public AutoFillId getAutoFillId() { - return AutoFillId.forDaRealId(mAutofillId); - } - - /** * Gets the id that can be used to autofill the view contents. * * <p>It's only set when the {@link AssistStructure} is used for autofilling purposes, not @@ -939,26 +927,6 @@ public class AssistStructure implements Parcelable { } /** - * @hide - * @deprecated TODO(b/35956626): remove once clients use getAutoFilltype() - */ - @Deprecated - public AutoFillType getAutoFillType() { - switch (getAutofillType()) { - case View.AUTOFILL_TYPE_TEXT: - return AutoFillType.forText(); - case View.AUTOFILL_TYPE_TOGGLE: - return AutoFillType.forToggle(); - case View.AUTOFILL_TYPE_LIST: - return AutoFillType.forList(); - case View.AUTOFILL_TYPE_DATE: - return AutoFillType.forDate(); - default: - return null; - } - } - - /** * Gets the the type of value that can be used to autofill the view contents. * * <p>It's only set when the {@link AssistStructure} is used for autofilling purposes, not @@ -982,15 +950,6 @@ public class AssistStructure implements Parcelable { } /** - * @hide - * @deprecated TODO(b/35956626): remove once clients use getAutoFilltype - */ - @Deprecated - public AutoFillValue getAutoFillValue() { - return AutoFillValue.forDaRealValue(mAutofillValue); - } - - /** * Gets the the value of this view. * * <p>It's only set when the {@link AssistStructure} is used for autofilling purposes, not diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index 74a39e85e029..9f35e85b0665 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -710,9 +710,9 @@ public class AppWidgetManager { * user may have a corporate profile. In this case the parent user profile has a * child profile, the corporate one. * - * @param profile The profile for which to get providers. Passing null is equivaled - * to passing only the current user handle. - * @return The intalled providers. + * @param profile The profile for which to get providers. Passing null is equivalent + * to querying for only the calling user. + * @return The installed providers. * * @see android.os.Process#myUserHandle() * @see android.os.UserManager#getUserProfiles() @@ -722,7 +722,31 @@ public class AppWidgetManager { return Collections.emptyList(); } return getInstalledProvidersForProfile(AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN, - profile); + profile, null); + } + + /** + * Gets the AppWidget providers for the given package and user profile. User + * profile can only be the current user or a profile of the current user. For + * example, the current user may have a corporate profile. In this case the + * parent user profile has a child profile, the corporate one. + * + * @param packageName The package for which to get providers. If null, this method is + * equivalent to {@link #getInstalledProvidersForProfile(UserHandle)}. + * @param profile The profile for which to get providers. Passing null is equivalent + * to querying for only the calling user. + * @return The installed providers. + * + * @see android.os.Process#myUserHandle() + * @see android.os.UserManager#getUserProfiles() + */ + public List<AppWidgetProviderInfo> getInstalledProvidersForPackage(@Nullable String packageName, + @Nullable UserHandle profile) { + if (mService == null) { + return Collections.emptyList(); + } + return getInstalledProvidersForProfile(AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN, + profile, packageName); } /** @@ -733,7 +757,7 @@ public class AppWidgetManager { return Collections.emptyList(); } return getInstalledProvidersForProfile(AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN, - null); + null, null); } /** @@ -752,7 +776,7 @@ public class AppWidgetManager { if (mService == null) { return Collections.emptyList(); } - return getInstalledProvidersForProfile(categoryFilter, null); + return getInstalledProvidersForProfile(categoryFilter, null, null); } /** @@ -766,6 +790,7 @@ public class AppWidgetManager { * @param profile A profile of the current user which to be queried. The user * is itself also a profile. If null, the providers only for the current user * are returned. + * @param packageName If specified, will only return providers from the given package. * @return The intalled providers. * * @see android.os.Process#myUserHandle() @@ -774,7 +799,7 @@ public class AppWidgetManager { * @hide */ public List<AppWidgetProviderInfo> getInstalledProvidersForProfile(int categoryFilter, - UserHandle profile) { + @Nullable UserHandle profile, @Nullable String packageName) { if (mService == null) { return Collections.emptyList(); } @@ -785,7 +810,7 @@ public class AppWidgetManager { try { ParceledListSlice<AppWidgetProviderInfo> providers = mService.getInstalledProvidersForProfile( - categoryFilter, profile.getIdentifier()); + categoryFilter, profile.getIdentifier(), packageName); if (providers == null) { return Collections.emptyList(); } diff --git a/core/java/android/content/pm/BaseParceledListSlice.java b/core/java/android/content/pm/BaseParceledListSlice.java index c4e4e06be749..aaa5f19c3fca 100644 --- a/core/java/android/content/pm/BaseParceledListSlice.java +++ b/core/java/android/content/pm/BaseParceledListSlice.java @@ -50,6 +50,8 @@ abstract class BaseParceledListSlice<T> implements Parcelable { private final List<T> mList; + private int mInlineCountLimit = Integer.MAX_VALUE; + public BaseParceledListSlice(List<T> list) { mList = list; } @@ -135,6 +137,14 @@ abstract class BaseParceledListSlice<T> implements Parcelable { } /** + * Set a limit on the maximum number of entries in the array that will be included + * inline in the initial parcelling of this object. + */ + public void setInlineCountLimit(int maxCount) { + mInlineCountLimit = maxCount; + } + + /** * Write this to another Parcel. Note that this discards the internal Parcel * and should not be used anymore. This is so we can pass this to a Binder * where we won't have a chance to call recycle on this. @@ -149,7 +159,7 @@ abstract class BaseParceledListSlice<T> implements Parcelable { final Class<?> listElementClass = mList.get(0).getClass(); writeParcelableCreator(mList.get(0), dest); int i = 0; - while (i < N && dest.dataSize() < MAX_IPC_SIZE) { + while (i < N && i < mInlineCountLimit && dest.dataSize() < MAX_IPC_SIZE) { dest.writeInt(1); final T parcelable = mList.get(i); diff --git a/core/java/android/service/autofill/AutoFillService.java b/core/java/android/service/autofill/AutoFillService.java deleted file mode 100644 index c26f679cbda8..000000000000 --- a/core/java/android/service/autofill/AutoFillService.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.service.autofill; - -/** - * @hide - * @deprecated TODO(b/35956626): remove once clients use AutofillService - */ -@Deprecated -public abstract class AutoFillService extends AutofillService { -} diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java index 6f17d0e5ec77..9f8a6217c5f4 100644 --- a/core/java/android/service/autofill/AutofillService.java +++ b/core/java/android/service/autofill/AutofillService.java @@ -71,7 +71,7 @@ public abstract class AutofillService extends Service { * Name under which a AutoFillService component publishes information about itself. * This meta-data should reference an XML resource containing a * <code><{@link - * android.R.styleable#AutoFillService autofill-service}></code> tag. + * android.R.styleable#AutofillService autofill-service}></code> tag. * This is a a sample XML file configuring an AutoFillService: * <pre> <autofill-service * android:settingsActivity="foo.bar.SettingsActivity" diff --git a/core/java/android/service/autofill/AutofillServiceInfo.java b/core/java/android/service/autofill/AutofillServiceInfo.java index f6d40dbf3414..0f4824e47fa9 100644 --- a/core/java/android/service/autofill/AutofillServiceInfo.java +++ b/core/java/android/service/autofill/AutofillServiceInfo.java @@ -36,7 +36,6 @@ import com.android.internal.R; import java.io.IOException; -// TODO(b/33197203 , b/33802548): add CTS tests /** * {@link ServiceInfo} and meta-data about an {@link AutofillService}. * @@ -75,15 +74,8 @@ public final class AutofillServiceInfo { mServiceInfo = si; final TypedArray metaDataArray = getMetaDataArray(pm, si); if (metaDataArray != null) { - // TODO(b/35956626): inline newSettingsActivity once clients migrate - final String newSettingsActivity = - metaDataArray.getString(R.styleable.AutofillService_settingsActivity); - if (newSettingsActivity != null) { - mSettingsActivity = newSettingsActivity; - } else { - mSettingsActivity = - metaDataArray.getString(R.styleable.AutoFillService_settingsActivity); - } + mSettingsActivity = metaDataArray + .getString(R.styleable.AutofillService_settingsActivity); metaDataArray.recycle(); } else { mSettingsActivity = null; @@ -96,16 +88,11 @@ public final class AutofillServiceInfo { @Nullable private static TypedArray getMetaDataArray(PackageManager pm, ServiceInfo si) { // Check for permissions. - // TODO(b/35956626): remove check for BIND_AUTO_FILL once clients migrate - if (!Manifest.permission.BIND_AUTOFILL.equals(si.permission) - && !Manifest.permission.BIND_AUTO_FILL.equals(si.permission)) { + if (!Manifest.permission.BIND_AUTOFILL.equals(si.permission)) { Log.e(TAG, "Service does not require permission " + Manifest.permission.BIND_AUTOFILL); return null; } - // TODO(b/35956626): remove once clients migrate - final boolean oldStyle = !Manifest.permission.BIND_AUTOFILL.equals(si.permission); - // Get the AutoFill metadata, if declared. XmlResourceParser parser = si.loadXmlMetaData(pm, AutofillService.SERVICE_META_DATA); if (parser == null) { @@ -141,8 +128,7 @@ public final class AutofillServiceInfo { return null; } - return oldStyle ? res.obtainAttributes(attrs, R.styleable.AutoFillService) - : res.obtainAttributes(attrs, R.styleable.AutofillService); + return res.obtainAttributes(attrs, R.styleable.AutofillService); } finally { parser.close(); } diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index b7a04206a4c6..e77bd0d753ac 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -23,8 +23,6 @@ import android.annotation.Nullable; import android.content.IntentSender; import android.os.Parcel; import android.os.Parcelable; -import android.view.autofill.AutoFillId; -import android.view.autofill.AutoFillValue; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; import android.widget.RemoteViews; @@ -175,15 +173,6 @@ public final class Dataset implements Parcelable { } /** - * @hide - * @deprecated TODO(b/35956626): remove once clients use other setValue() - */ - @Deprecated - public @NonNull Builder setValue(@NonNull AutoFillId id, @NonNull AutoFillValue value) { - return setValue(id.getDaRealId(), value.getDaRealValue()); - } - - /** * Sets the value of a field. * * @param id id returned by {@link diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java index 6213d27bfc70..95608a54a72c 100644 --- a/core/java/android/service/autofill/SaveInfo.java +++ b/core/java/android/service/autofill/SaveInfo.java @@ -25,7 +25,6 @@ import android.content.IntentSender; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import android.view.autofill.AutoFillId; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; @@ -269,26 +268,6 @@ public final class SaveInfo implements Parcelable { return this; } - - /** - * @hide - */ - // TODO(b/33197203): temporary fix to runtime crash - public @NonNull Builder addSavableIds(@Nullable AutoFillId... ids) { - throwIfDestroyed(); - - if (ids == null || ids.length == 0) { - return this; - } - if (mRequiredIds == null) { - mRequiredIds = new AutofillId[ids.length]; - } - for (int i = 0; i < ids.length; i++) { - mRequiredIds[i] = ids[i].getDaRealId(); - } - return this; - } - /** * Sets an optional description to be shown in the UI when the user is asked to save. * diff --git a/core/java/android/service/resolver/IResolverRankerResult.aidl b/core/java/android/service/resolver/IResolverRankerResult.aidl deleted file mode 100644 index bda315420b7b..000000000000 --- a/core/java/android/service/resolver/IResolverRankerResult.aidl +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.service.resolver; - -import android.service.resolver.ResolverTarget; - -/** - * @hide - */ -oneway interface IResolverRankerResult -{ - void sendResult(in List<ResolverTarget> results); -}
\ No newline at end of file diff --git a/core/java/android/service/resolver/IResolverRankerService.aidl b/core/java/android/service/resolver/IResolverRankerService.aidl deleted file mode 100644 index f0d747d974a7..000000000000 --- a/core/java/android/service/resolver/IResolverRankerService.aidl +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.service.resolver; - -import android.service.resolver.IResolverRankerResult; -import android.service.resolver.ResolverTarget; - -/** - * @hide - */ -oneway interface IResolverRankerService -{ - void predict(in List<ResolverTarget> targets, IResolverRankerResult result); - void train(in List<ResolverTarget> targets, int selectedPosition); -}
\ No newline at end of file diff --git a/core/java/android/service/resolver/ResolverRankerService.java b/core/java/android/service/resolver/ResolverRankerService.java deleted file mode 100644 index 05067479bf45..000000000000 --- a/core/java/android/service/resolver/ResolverRankerService.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.service.resolver; - -import android.annotation.SdkConstant; -import android.annotation.SystemApi; -import android.app.Service; -import android.content.ComponentName; -import android.content.Intent; -import android.os.IBinder; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.RemoteException; -import android.service.resolver.ResolverTarget; -import android.util.Log; - -import java.util.List; -import java.util.Map; - -/** - * A service to rank apps according to usage stats of apps, when the system is resolving targets for - * an Intent. - * - * <p>To extend this class, you must declare the service in your manifest file with the - * {@link android.Manifest.permission#BIND_RESOLVER_RANKER_SERVICE} permission, and include an - * intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p> - * <pre> - * <service android:name=".MyResolverRankerService" - * android:exported="true" - * android:priority="100" - * android:permission="android.permission.BIND_RESOLVER_RANKER_SERVICE"> - * <intent-filter> - * <action android:name="android.service.resolver.ResolverRankerService" /> - * </intent-filter> - * </service> - * </pre> - * @hide - */ -@SystemApi -public abstract class ResolverRankerService extends Service { - - private static final String TAG = "ResolverRankerService"; - - private static final boolean DEBUG = false; - - /** - * The Intent action that a service must respond to. Add it to the intent filter of the service - * in its manifest. - */ - @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) - public static final String SERVICE_INTERFACE = "android.service.resolver.ResolverRankerService"; - - /** - * The permission that a service must require to ensure that only Android system can bind to it. - * If this permission is not enforced in the AndroidManifest of the service, the system will - * skip that service. - */ - public static final String BIND_PERMISSION = "android.permission.BIND_RESOLVER_RANKER_SERVICE"; - - private ResolverRankerServiceWrapper mWrapper = null; - - /** - * Called by the system to retrieve a list of probabilities to rank apps/options. To implement - * it, set selectProbability of each input {@link ResolverTarget}. The higher the - * selectProbability is, the more likely the {@link ResolverTarget} will be selected by the - * user. Override this function to provide prediction results. - * - * @param targets a list of {@link ResolverTarget}, for the list of apps to be ranked. - * - * @throws Exception when the prediction task fails. - */ - public void onPredictSharingProbabilities(final List<ResolverTarget> targets) {} - - /** - * Called by the system to train/update a ranking service, after the user makes a selection from - * the ranked list of apps. Override this function to enable model updates. - * - * @param targets a list of {@link ResolverTarget}, for the list of apps to be ranked. - * @param selectedPosition the position of the selected app in the list. - * - * @throws Exception when the training task fails. - */ - public void onTrainRankingModel( - final List<ResolverTarget> targets, final int selectedPosition) {} - - private static final String HANDLER_THREAD_NAME = "RESOLVER_RANKER_SERVICE"; - private volatile Handler mHandler; - private HandlerThread mHandlerThread; - - @Override - public IBinder onBind(Intent intent) { - if (DEBUG) Log.d(TAG, "onBind " + intent); - if (!SERVICE_INTERFACE.equals(intent.getAction())) { - if (DEBUG) Log.d(TAG, "bad intent action " + intent.getAction() + "; returning null"); - return null; - } - if (mHandlerThread == null) { - mHandlerThread = new HandlerThread(HANDLER_THREAD_NAME); - mHandlerThread.start(); - mHandler = new Handler(mHandlerThread.getLooper()); - } - if (mWrapper == null) { - mWrapper = new ResolverRankerServiceWrapper(); - } - return mWrapper; - } - - @Override - public void onDestroy() { - mHandler = null; - if (mHandlerThread != null) { - mHandlerThread.quitSafely(); - } - super.onDestroy(); - } - - private static void sendResult(List<ResolverTarget> targets, IResolverRankerResult result) { - try { - result.sendResult(targets); - } catch (Exception e) { - Log.e(TAG, "failed to send results: " + e); - } - } - - private class ResolverRankerServiceWrapper extends IResolverRankerService.Stub { - - @Override - public void predict(final List<ResolverTarget> targets, final IResolverRankerResult result) - throws RemoteException { - Runnable predictRunnable = new Runnable() { - @Override - public void run() { - try { - if (DEBUG) { - Log.d(TAG, "predict calls onPredictSharingProbabilities."); - } - onPredictSharingProbabilities(targets); - sendResult(targets, result); - } catch (Exception e) { - Log.e(TAG, "onPredictSharingProbabilities failed; send null results: " + e); - sendResult(null, result); - } - } - }; - final Handler h = mHandler; - if (h != null) { - h.post(predictRunnable); - } - } - - @Override - public void train(final List<ResolverTarget> targets, final int selectedPosition) - throws RemoteException { - Runnable trainRunnable = new Runnable() { - @Override - public void run() { - try { - if (DEBUG) { - Log.d(TAG, "train calls onTranRankingModel"); - } - onTrainRankingModel(targets, selectedPosition); - } catch (Exception e) { - Log.e(TAG, "onTrainRankingModel failed; skip train: " + e); - } - } - }; - final Handler h = mHandler; - if (h != null) { - h.post(trainRunnable); - } - } - } -} diff --git a/core/java/android/service/resolver/ResolverTarget.java b/core/java/android/service/resolver/ResolverTarget.java deleted file mode 100644 index fb3e2d738469..000000000000 --- a/core/java/android/service/resolver/ResolverTarget.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.service.resolver; - -import android.annotation.SystemApi; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.util.ArrayMap; - -import java.util.Map; - -/** - * A ResolverTarget contains features by which an app or option will be ranked, in - * {@link ResolverRankerService}. - * @hide - */ -@SystemApi -public final class ResolverTarget implements Parcelable { - private static final String TAG = "ResolverTarget"; - - /** - * a float score for recency of last use. - */ - private float mRecencyScore; - - /** - * a float score for total time spent. - */ - private float mTimeSpentScore; - - /** - * a float score for number of launches. - */ - private float mLaunchScore; - - /** - * a float score for number of selected. - */ - private float mChooserScore; - - /** - * a float score for the probability to be selected. - */ - private float mSelectProbability; - - // constructor for the class. - public ResolverTarget() {} - - ResolverTarget(Parcel in) { - mRecencyScore = in.readFloat(); - mTimeSpentScore = in.readFloat(); - mLaunchScore = in.readFloat(); - mChooserScore = in.readFloat(); - mSelectProbability = in.readFloat(); - } - - /** - * Gets the score for how recently the target was used in the foreground. - * - * @return a float score whose range is [0, 1]. The higher the score is, the more recently the - * target was used. - */ - public float getRecencyScore() { - return mRecencyScore; - } - - /** - * Sets the score for how recently the target was used in the foreground. - * - * @param recencyScore a float score whose range is [0, 1]. The higher the score is, the more - * recently the target was used. - */ - public void setRecencyScore(float recencyScore) { - this.mRecencyScore = recencyScore; - } - - /** - * Gets the score for how long the target has been used in the foreground. - * - * @return a float score whose range is [0, 1]. The higher the score is, the longer the target - * has been used for. - */ - public float getTimeSpentScore() { - return mTimeSpentScore; - } - - /** - * Sets the score for how long the target has been used in the foreground. - * - * @param timeSpentScore a float score whose range is [0, 1]. The higher the score is, the - * longer the target has been used for. - */ - public void setTimeSpentScore(float timeSpentScore) { - this.mTimeSpentScore = timeSpentScore; - } - - /** - * Gets the score for how many times the target has been launched to the foreground. - * - * @return a float score whose range is [0, 1]. The higher the score is, the more times the - * target has been launched. - */ - public float getLaunchScore() { - return mLaunchScore; - } - - /** - * Sets the score for how many times the target has been launched to the foreground. - * - * @param launchScore a float score whose range is [0, 1]. The higher the score is, the more - * times the target has been launched. - */ - public void setLaunchScore(float launchScore) { - this.mLaunchScore = launchScore; - } - - /** - * Gets the score for how many times the target has been selected by the user to share the same - * types of content. - * - * @return a float score whose range is [0, 1]. The higher the score is, the - * more times the target has been selected by the user to share the same types of content for. - */ - public float getChooserScore() { - return mChooserScore; - } - - /** - * Sets the score for how many times the target has been selected by the user to share the same - * types of content. - * - * @param chooserScore a float score whose range is [0, 1]. The higher the score is, the more - * times the target has been selected by the user to share the same types - * of content for. - */ - public void setChooserScore(float chooserScore) { - this.mChooserScore = chooserScore; - } - - /** - * Gets the probability of how likely this target will be selected by the user. - * - * @return a float score whose range is [0, 1]. The higher the score is, the more likely the - * user is going to select this target. - */ - public float getSelectProbability() { - return mSelectProbability; - } - - /** - * Sets the probability for how like this target will be selected by the user. - * - * @param selectProbability a float score whose range is [0, 1]. The higher the score is, the - * more likely tht user is going to select this target. - */ - public void setSelectProbability(float selectProbability) { - this.mSelectProbability = selectProbability; - } - - // serialize the class to a string. - @Override - public String toString() { - return "ResolverTarget{" - + mRecencyScore + ", " - + mTimeSpentScore + ", " - + mLaunchScore + ", " - + mChooserScore + ", " - + mSelectProbability + "}"; - } - - // describes the kinds of special objects contained in this Parcelable instance's marshaled - // representation. - @Override - public int describeContents() { - return 0; - } - - // flattens this object in to a Parcel. - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeFloat(mRecencyScore); - dest.writeFloat(mTimeSpentScore); - dest.writeFloat(mLaunchScore); - dest.writeFloat(mChooserScore); - dest.writeFloat(mSelectProbability); - } - - // creator definition for the class. - public static final Creator<ResolverTarget> CREATOR - = new Creator<ResolverTarget>() { - @Override - public ResolverTarget createFromParcel(Parcel source) { - return new ResolverTarget(source); - } - - @Override - public ResolverTarget[] newArray(int size) { - return new ResolverTarget[size]; - } - }; -} diff --git a/core/java/android/view/autofill/AutoFillId.aidl b/core/java/android/view/autofill/AutoFillId.aidl deleted file mode 100644 index fc57ce7acfc1..000000000000 --- a/core/java/android/view/autofill/AutoFillId.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) 2017, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.autofill; - -// @deprecated TODO(b/35956626): remove once clients use AutofillId -parcelable AutoFillId;
\ No newline at end of file diff --git a/core/java/android/view/autofill/AutoFillId.java b/core/java/android/view/autofill/AutoFillId.java deleted file mode 100644 index 081fb0289d76..000000000000 --- a/core/java/android/view/autofill/AutoFillId.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.view.autofill; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * @hide - * @deprecated TODO(b/35956626): remove once clients use getAutoFilltype - */ -@Deprecated -public final class AutoFillId implements Parcelable { - - private final AutofillId mRealId; - - /** @hide */ - public AutoFillId(AutofillId daRealId) { - this.mRealId = daRealId; - } - - @Override - public int hashCode() { - return mRealId.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - final AutoFillId other = (AutoFillId) obj; - return mRealId.equals(other.mRealId); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel parcel, int flags) { - parcel.writeParcelable(mRealId, 0); - } - - private AutoFillId(Parcel parcel) { - mRealId = parcel.readParcelable(null); - } - - /** @hide */ - public AutofillId getDaRealId() { - return mRealId; - } - - /** @hide */ - public static AutoFillId forDaRealId(AutofillId id) { - return id == null ? null : new AutoFillId(id); - } - - public static final Parcelable.Creator<AutoFillId> CREATOR = - new Parcelable.Creator<AutoFillId>() { - @Override - public AutoFillId createFromParcel(Parcel source) { - return new AutoFillId(source); - } - - @Override - public AutoFillId[] newArray(int size) { - return new AutoFillId[size]; - } - }; -} diff --git a/core/java/android/view/autofill/AutoFillType.java b/core/java/android/view/autofill/AutoFillType.java deleted file mode 100644 index c508ba4eb892..000000000000 --- a/core/java/android/view/autofill/AutoFillType.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.autofill; - -import static android.view.autofill.Helper.DEBUG; - -import android.os.Parcel; -import android.os.Parcelable; -import android.view.View; - -/** - * Defines the type of a object that can be used to autofill a {@link View} so the - * {@link android.service.autofill.AutofillService} can use the proper {@link AutofillValue} to - * fill it. - * - * @hide - * @deprecated TODO(b/35956626): remove once clients use getAutoFilltype - */ -@Deprecated -public final class AutoFillType implements Parcelable { - - // Cached instance for types that don't have subtype; it uses the "lazy initialization holder - // class idiom" (Effective Java, Item 71) to avoid memory utilization when autofill is not - // enabled. - private static class DefaultTypesHolder { - static final AutoFillType TEXT = new AutoFillType(TYPE_TEXT); - static final AutoFillType TOGGLE = new AutoFillType(TYPE_TOGGLE); - static final AutoFillType LIST = new AutoFillType(TYPE_LIST); - static final AutoFillType DATE = new AutoFillType(TYPE_DATE); - } - - private static final int TYPE_TEXT = 1; - private static final int TYPE_TOGGLE = 2; - private static final int TYPE_LIST = 3; - private static final int TYPE_DATE = 4; - - private final int mType; - - private AutoFillType(int type) { - mType = type; - } - - /** - * Checks if this is a type for a text field, which is filled by a {@link CharSequence}. - */ - public boolean isText() { - return mType == TYPE_TEXT; - } - - /** - * Checks if this is a a type for a togglable field, which is filled by a {@code boolean}. - */ - public boolean isToggle() { - return mType == TYPE_TOGGLE; - } - - /** - * Checks if this is a type for a selection list field, which is filled by a {@code integer} - * representing the element index inside the list (starting at {@code 0}. - */ - public boolean isList() { - return mType == TYPE_LIST; - } - - /** - * Checks if this is a type for a date and time, which is represented by a long representing - * the number of milliseconds since the standard base time known as "the epoch", namely - * January 1, 1970, 00:00:00 GMT (see {@link java.util.Date#getTime()}. - */ - public boolean isDate() { - return mType == TYPE_DATE; - } - - ///////////////////////////////////// - // Object "contract" methods. // - ///////////////////////////////////// - - @Override - public String toString() { - if (!DEBUG) return super.toString(); - - return "AutoFillType [type=" + mType + "]"; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + mType; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - final AutoFillType other = (AutoFillType) obj; - if (mType != other.mType) return false; - return true; - } - - ///////////////////////////////////// - // Parcelable "contract" methods. // - ///////////////////////////////////// - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel parcel, int flags) { - parcel.writeInt(mType); - } - - private AutoFillType(Parcel parcel) { - mType = parcel.readInt(); - } - - public static final Parcelable.Creator<AutoFillType> CREATOR = - new Parcelable.Creator<AutoFillType>() { - @Override - public AutoFillType createFromParcel(Parcel source) { - return new AutoFillType(source); - } - - @Override - public AutoFillType[] newArray(int size) { - return new AutoFillType[size]; - } - }; - - //////////////////// - // Factory methods // - //////////////////// - - /** - * Creates a text field type, which is filled by a {@link CharSequence}. - * - * <p>See {@link #isText()} for more info. - */ - public static AutoFillType forText() { - return DefaultTypesHolder.TEXT; - } - - /** - * Creates a type that can be toggled which is filled by a {@code boolean}. - * - * <p>See {@link #isToggle()} for more info. - */ - public static AutoFillType forToggle() { - return DefaultTypesHolder.TOGGLE; - } - - /** - * Creates a selection list, which is filled by a {@code integer} representing the element index - * inside the list (starting at {@code 0}. - * - * <p>See {@link #isList()} for more info. - */ - public static AutoFillType forList() { - return DefaultTypesHolder.LIST; - } - - /** - * Creates a type that represents a date. - * - * <p>See {@link #isDate()} for more info. - */ - public static AutoFillType forDate() { - return DefaultTypesHolder.DATE; - } -} diff --git a/core/java/android/view/autofill/AutoFillValue.java b/core/java/android/view/autofill/AutoFillValue.java deleted file mode 100644 index 4774d8f0a71a..000000000000 --- a/core/java/android/view/autofill/AutoFillValue.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.autofill; - -import static android.view.autofill.Helper.DEBUG; - -import android.annotation.Nullable; -import android.os.Parcel; -import android.os.Parcelable; -import android.view.View; - -/** - * @hide - * @deprecated TODO(b/35956626): remove once clients use AutofillValue - */ -@Deprecated -public final class AutoFillValue implements Parcelable { - private final AutofillValue mRealValue; - - private AutoFillValue(AutofillValue daRealValue) { - this.mRealValue = daRealValue; - } - - /** - * Gets the value to autofill a text field. - * - * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info. - */ - public CharSequence getTextValue() { - return mRealValue.getTextValue(); - } - - /** - * Gets the value to autofill a toggable field. - * - * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info. - */ - public boolean getToggleValue() { - return mRealValue.getToggleValue(); - } - - /** - * Gets the value to autofill a selection list field. - * - * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info. - */ - public int getListValue() { - return mRealValue.getListValue(); - } - - /** - * Gets the value to autofill a date field. - * - * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info. - */ - public long getDateValue() { - return mRealValue.getDateValue(); - } - - ///////////////////////////////////// - // Object "contract" methods. // - ///////////////////////////////////// - - @Override - public int hashCode() { - return mRealValue.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - final AutoFillValue other = (AutoFillValue) obj; - return mRealValue.equals(other.mRealValue); - } - - @Override - public String toString() { - if (!DEBUG) return super.toString(); - - return mRealValue.toString(); - } - - ///////////////////////////////////// - // Parcelable "contract" methods. // - ///////////////////////////////////// - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel parcel, int flags) { - parcel.writeParcelable(mRealValue, 0); - } - - private AutoFillValue(Parcel parcel) { - mRealValue = parcel.readParcelable(null); - } - - public static final Parcelable.Creator<AutoFillValue> CREATOR = - new Parcelable.Creator<AutoFillValue>() { - @Override - public AutoFillValue createFromParcel(Parcel source) { - return new AutoFillValue(source); - } - - @Override - public AutoFillValue[] newArray(int size) { - return new AutoFillValue[size]; - } - }; - - //////////////////// - // Factory methods // - //////////////////// - /** - * Creates a new {@link AutoFillValue} to autofill a {@link View} representing a text field. - * - * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info. - */ - @Nullable - public static AutoFillValue forText(@Nullable CharSequence value) { - return value == null ? null : new AutoFillValue(AutofillValue.forText(value)); - } - - /** - * Creates a new {@link AutoFillValue} to autofill a {@link View} representing a toggable - * field. - * - * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info. - */ - public static AutoFillValue forToggle(boolean value) { - return new AutoFillValue(AutofillValue.forToggle(value)); - } - - /** - * Creates a new {@link AutoFillValue} to autofill a {@link View} representing a selection - * list. - * - * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info. - */ - public static AutoFillValue forList(int value) { - return new AutoFillValue(AutofillValue.forList(value)); - } - - /** - * Creates a new {@link AutoFillValue} to autofill a {@link View} representing a date. - * - * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info. - */ - public static AutoFillValue forDate(long date) { - return new AutoFillValue(AutofillValue.forDate(date)); - } - - /** @hide */ - public static AutoFillValue forDaRealValue(AutofillValue daRealValue) { - return new AutoFillValue(daRealValue); - } - - /** @hide */ - public AutofillValue getDaRealValue() { - return mRealValue; - } -} diff --git a/core/java/com/android/internal/app/LRResolverRankerService.java b/core/java/com/android/internal/app/LRResolverRankerService.java deleted file mode 100644 index 1cad7c770b7c..000000000000 --- a/core/java/com/android/internal/app/LRResolverRankerService.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.app; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Environment; -import android.os.IBinder; -import android.os.storage.StorageManager; -import android.service.resolver.ResolverRankerService; -import android.service.resolver.ResolverTarget; -import android.util.ArrayMap; -import android.util.Log; - -import java.io.File; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -/** - * A Logistic Regression based {@link android.service.resolver.ResolverRankerService}, to be used - * in {@link ResolverComparator}. - */ -public final class LRResolverRankerService extends ResolverRankerService { - private static final String TAG = "LRResolverRankerService"; - - private static final boolean DEBUG = false; - - private static final String PARAM_SHARED_PREF_NAME = "resolver_ranker_params"; - private static final String BIAS_PREF_KEY = "bias"; - private static final String VERSION_PREF_KEY = "version"; - - private static final String LAUNCH_SCORE = "launch"; - private static final String TIME_SPENT_SCORE = "timeSpent"; - private static final String RECENCY_SCORE = "recency"; - private static final String CHOOSER_SCORE = "chooser"; - - // parameters for a pre-trained model, to initialize the app ranker. When updating the - // pre-trained model, please update these params, as well as initModel(). - private static final int CURRENT_VERSION = 1; - private static final float LEARNING_RATE = 0.0001f; - private static final float REGULARIZER_PARAM = 0.0001f; - - private SharedPreferences mParamSharedPref; - private ArrayMap<String, Float> mFeatureWeights; - private float mBias; - - @Override - public IBinder onBind(Intent intent) { - initModel(); - return super.onBind(intent); - } - - @Override - public void onPredictSharingProbabilities(List<ResolverTarget> targets) { - final int size = targets.size(); - for (int i = 0; i < size; ++i) { - ResolverTarget target = targets.get(i); - ArrayMap<String, Float> features = getFeatures(target); - target.setSelectProbability(predict(features)); - } - } - - @Override - public void onTrainRankingModel(List<ResolverTarget> targets, int selectedPosition) { - final int size = targets.size(); - if (selectedPosition < 0 || selectedPosition >= size) { - if (DEBUG) { - Log.d(TAG, "Invalid Position of Selected App " + selectedPosition); - } - return; - } - final ArrayMap<String, Float> positive = getFeatures(targets.get(selectedPosition)); - final float positiveProbability = targets.get(selectedPosition).getSelectProbability(); - final int targetSize = targets.size(); - for (int i = 0; i < targetSize; ++i) { - if (i == selectedPosition) { - continue; - } - final ArrayMap<String, Float> negative = getFeatures(targets.get(i)); - final float negativeProbability = targets.get(i).getSelectProbability(); - if (negativeProbability > positiveProbability) { - update(negative, negativeProbability, false); - update(positive, positiveProbability, true); - } - } - commitUpdate(); - } - - private void initModel() { - mParamSharedPref = getParamSharedPref(); - mFeatureWeights = new ArrayMap<>(4); - if (mParamSharedPref == null || - mParamSharedPref.getInt(VERSION_PREF_KEY, 0) < CURRENT_VERSION) { - // Initializing the app ranker to a pre-trained model. When updating the pre-trained - // model, please increment CURRENT_VERSION, and update LEARNING_RATE and - // REGULARIZER_PARAM. - mBias = -1.6568f; - mFeatureWeights.put(LAUNCH_SCORE, 2.5543f); - mFeatureWeights.put(TIME_SPENT_SCORE, 2.8412f); - mFeatureWeights.put(RECENCY_SCORE, 0.269f); - mFeatureWeights.put(CHOOSER_SCORE, 4.2222f); - } else { - mBias = mParamSharedPref.getFloat(BIAS_PREF_KEY, 0.0f); - mFeatureWeights.put(LAUNCH_SCORE, mParamSharedPref.getFloat(LAUNCH_SCORE, 0.0f)); - mFeatureWeights.put( - TIME_SPENT_SCORE, mParamSharedPref.getFloat(TIME_SPENT_SCORE, 0.0f)); - mFeatureWeights.put(RECENCY_SCORE, mParamSharedPref.getFloat(RECENCY_SCORE, 0.0f)); - mFeatureWeights.put(CHOOSER_SCORE, mParamSharedPref.getFloat(CHOOSER_SCORE, 0.0f)); - } - } - - private ArrayMap<String, Float> getFeatures(ResolverTarget target) { - ArrayMap<String, Float> features = new ArrayMap<>(4); - features.put(RECENCY_SCORE, target.getRecencyScore()); - features.put(TIME_SPENT_SCORE, target.getTimeSpentScore()); - features.put(LAUNCH_SCORE, target.getLaunchScore()); - features.put(CHOOSER_SCORE, target.getChooserScore()); - return features; - } - - private float predict(ArrayMap<String, Float> target) { - if (target == null) { - return 0.0f; - } - final int featureSize = target.size(); - float sum = 0.0f; - for (int i = 0; i < featureSize; i++) { - String featureName = target.keyAt(i); - float weight = mFeatureWeights.getOrDefault(featureName, 0.0f); - sum += weight * target.valueAt(i); - } - return (float) (1.0 / (1.0 + Math.exp(-mBias - sum))); - } - - private void update(ArrayMap<String, Float> target, float predict, boolean isSelected) { - if (target == null) { - return; - } - final int featureSize = target.size(); - float error = isSelected ? 1.0f - predict : -predict; - for (int i = 0; i < featureSize; i++) { - String featureName = target.keyAt(i); - float currentWeight = mFeatureWeights.getOrDefault(featureName, 0.0f); - mBias += LEARNING_RATE * error; - currentWeight = currentWeight - LEARNING_RATE * REGULARIZER_PARAM * currentWeight + - LEARNING_RATE * error * target.valueAt(i); - mFeatureWeights.put(featureName, currentWeight); - } - if (DEBUG) { - Log.d(TAG, "Weights: " + mFeatureWeights + " Bias: " + mBias); - } - } - - private void commitUpdate() { - try { - SharedPreferences.Editor editor = mParamSharedPref.edit(); - editor.putFloat(BIAS_PREF_KEY, mBias); - final int size = mFeatureWeights.size(); - for (int i = 0; i < size; i++) { - editor.putFloat(mFeatureWeights.keyAt(i), mFeatureWeights.valueAt(i)); - } - editor.putInt(VERSION_PREF_KEY, CURRENT_VERSION); - editor.apply(); - } catch (Exception e) { - Log.e(TAG, "Failed to commit update" + e); - } - } - - private SharedPreferences getParamSharedPref() { - // The package info in the context isn't initialized in the way it is for normal apps, - // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we - // build the path manually below using the same policy that appears in ContextImpl. - if (DEBUG) { - Log.d(TAG, "Context Package Name: " + getPackageName()); - } - final File prefsFile = new File(new File( - Environment.getDataUserCePackageDirectory( - StorageManager.UUID_PRIVATE_INTERNAL, getUserId(), getPackageName()), - "shared_prefs"), - PARAM_SHARED_PREF_NAME + ".xml"); - return getSharedPreferences(prefsFile, Context.MODE_PRIVATE); - } -}
\ No newline at end of file diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 622b70843cc2..3f1c9adb1b68 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -530,9 +530,6 @@ public class ResolverActivity extends Activity { getMainThreadHandler().removeCallbacks(mPostListReadyRunnable); mPostListReadyRunnable = null; } - if (mAdapter != null && mAdapter.mResolverListController != null) { - mAdapter.mResolverListController.destroy(); - } } @Override diff --git a/core/java/com/android/internal/app/ResolverComparator.java b/core/java/com/android/internal/app/ResolverComparator.java index 73b62a5fe60d..096fcb83e755 100644 --- a/core/java/com/android/internal/app/ResolverComparator.java +++ b/core/java/com/android/internal/app/ResolverComparator.java @@ -26,34 +26,20 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.ComponentInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.SharedPreferences; -import android.content.ServiceConnection; import android.os.Environment; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.RemoteException; import android.os.storage.StorageManager; import android.os.UserHandle; -import android.service.resolver.IResolverRankerService; -import android.service.resolver.IResolverRankerResult; -import android.service.resolver.ResolverRankerService; -import android.service.resolver.ResolverTarget; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import com.android.internal.app.ResolverActivity.ResolvedComponentInfo; import java.io.File; -import java.lang.InterruptedException; import java.text.Collator; import java.util.ArrayList; import java.util.Comparator; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -75,15 +61,11 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> { private static final float RECENCY_MULTIPLIER = 2.f; - // message types - private static final int RESOLVER_RANKER_SERVICE_RESULT = 0; - private static final int RESOLVER_RANKER_RESULT_TIMEOUT = 1; - - // timeout for establishing connections with a ResolverRankerService. - private static final int CONNECTION_COST_TIMEOUT_MILLIS = 200; - // timeout for establishing connections with a ResolverRankerService, collecting features and - // predicting ranking scores. - private static final int WATCHDOG_TIMEOUT_MILLIS = 500; + // feature names used in ranking. + private static final String LAUNCH_SCORE = "launch"; + private static final String TIME_SPENT_SCORE = "timeSpent"; + private static final String RECENCY_SCORE = "recency"; + private static final String CHOOSER_SCORE = "chooser"; private final Collator mCollator; private final boolean mHttp; @@ -92,74 +74,18 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> { private final Map<String, UsageStats> mStats; private final long mCurrentTime; private final long mSinceTime; - private final LinkedHashMap<ComponentName, ResolverTarget> mTargetsDict = new LinkedHashMap<>(); + private final LinkedHashMap<ComponentName, ScoredTarget> mScoredTargets = new LinkedHashMap<>(); private final String mReferrerPackage; - private final Object mLock = new Object(); - private ArrayList<ResolverTarget> mTargets; private String mContentType; private String[] mAnnotations; private String mAction; - private IResolverRankerService mRanker; - private ResolverRankerServiceConnection mConnection; - private AfterCompute mAfterCompute; - private Context mContext; - private CountDownLatch mConnectSignal; - - private final Handler mHandler = new Handler(Looper.getMainLooper()) { - public void handleMessage(Message msg) { - switch (msg.what) { - case RESOLVER_RANKER_SERVICE_RESULT: - if (DEBUG) { - Log.d(TAG, "RESOLVER_RANKER_SERVICE_RESULT"); - } - if (mHandler.hasMessages(RESOLVER_RANKER_RESULT_TIMEOUT)) { - if (msg.obj != null) { - final List<ResolverTarget> receivedTargets = - (List<ResolverTarget>) msg.obj; - if (receivedTargets != null && mTargets != null - && receivedTargets.size() == mTargets.size()) { - final int size = mTargets.size(); - for (int i = 0; i < size; ++i) { - mTargets.get(i).setSelectProbability( - receivedTargets.get(i).getSelectProbability()); - } - } else { - Log.e(TAG, "Sizes of sent and received ResolverTargets diff."); - } - } else { - Log.e(TAG, "Receiving null prediction results."); - } - mHandler.removeMessages(RESOLVER_RANKER_RESULT_TIMEOUT); - mAfterCompute.afterCompute(); - } - break; - - case RESOLVER_RANKER_RESULT_TIMEOUT: - if (DEBUG) { - Log.d(TAG, "RESOLVER_RANKER_RESULT_TIMEOUT; unbinding services"); - } - mHandler.removeMessages(RESOLVER_RANKER_SERVICE_RESULT); - mAfterCompute.afterCompute(); - break; + private LogisticRegressionAppRanker mRanker; - default: - super.handleMessage(msg); - } - } - }; - - public interface AfterCompute { - public void afterCompute (); - } - - public ResolverComparator(Context context, Intent intent, String referrerPackage, - AfterCompute afterCompute) { + public ResolverComparator(Context context, Intent intent, String referrerPackage) { mCollator = Collator.getInstance(context.getResources().getConfiguration().locale); String scheme = intent.getScheme(); mHttp = "http".equals(scheme) || "https".equals(scheme); mReferrerPackage = referrerPackage; - mAfterCompute = afterCompute; - mContext = context; mPm = context.getPackageManager(); mUsm = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE); @@ -170,9 +96,9 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> { mContentType = intent.getType(); getContentAnnotations(intent); mAction = intent.getAction(); + mRanker = new LogisticRegressionAppRanker(context); } - // get annotations of content from intent. public void getContentAnnotations(Intent intent) { ArrayList<String> annotations = intent.getStringArrayListExtra( Intent.EXTRA_CONTENT_ANNOTATIONS); @@ -188,24 +114,20 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> { } } - public void setCallBack(AfterCompute afterCompute) { - mAfterCompute = afterCompute; - } - - // compute features for each target according to usage stats of targets. public void compute(List<ResolvedComponentInfo> targets) { - reset(); + mScoredTargets.clear(); final long recentSinceTime = mCurrentTime - RECENCY_TIME_PERIOD; - float mostRecencyScore = 1.0f; - float mostTimeSpentScore = 1.0f; - float mostLaunchScore = 1.0f; - float mostChooserScore = 1.0f; + long mostRecentlyUsedTime = recentSinceTime + 1; + long mostTimeSpent = 1; + int mostLaunched = 1; + int mostSelected = 1; for (ResolvedComponentInfo target : targets) { - final ResolverTarget resolverTarget = new ResolverTarget(); - mTargetsDict.put(target.name, resolverTarget); + final ScoredTarget scoredTarget + = new ScoredTarget(target.getResolveInfoAt(0).activityInfo); + mScoredTargets.put(target.name, scoredTarget); final UsageStats pkStats = mStats.get(target.name.getPackageName()); if (pkStats != null) { // Only count recency for apps that weren't the caller @@ -213,33 +135,31 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> { // Persistent processes muck this up, so omit them too. if (!target.name.getPackageName().equals(mReferrerPackage) && !isPersistentProcess(target)) { - final float recencyScore = - (float) Math.max(pkStats.getLastTimeUsed() - recentSinceTime, 0); - resolverTarget.setRecencyScore(recencyScore); - if (recencyScore > mostRecencyScore) { - mostRecencyScore = recencyScore; + final long lastTimeUsed = pkStats.getLastTimeUsed(); + scoredTarget.lastTimeUsed = lastTimeUsed; + if (lastTimeUsed > mostRecentlyUsedTime) { + mostRecentlyUsedTime = lastTimeUsed; } } - final float timeSpentScore = (float) pkStats.getTotalTimeInForeground(); - resolverTarget.setTimeSpentScore(timeSpentScore); - if (timeSpentScore > mostTimeSpentScore) { - mostTimeSpentScore = timeSpentScore; + final long timeSpent = pkStats.getTotalTimeInForeground(); + scoredTarget.timeSpent = timeSpent; + if (timeSpent > mostTimeSpent) { + mostTimeSpent = timeSpent; } - final float launchScore = (float) pkStats.mLaunchCount; - resolverTarget.setLaunchScore(launchScore); - if (launchScore > mostLaunchScore) { - mostLaunchScore = launchScore; + final int launched = pkStats.mLaunchCount; + scoredTarget.launchCount = launched; + if (launched > mostLaunched) { + mostLaunched = launched; } - float chooserScore = 0.0f; + int selected = 0; if (pkStats.mChooserCounts != null && mAction != null && pkStats.mChooserCounts.get(mAction) != null) { - chooserScore = (float) pkStats.mChooserCounts.get(mAction) - .getOrDefault(mContentType, 0); + selected = pkStats.mChooserCounts.get(mAction).getOrDefault(mContentType, 0); if (mAnnotations != null) { final int size = mAnnotations.length; for (int i = 0; i < size; i++) { - chooserScore += (float) pkStats.mChooserCounts.get(mAction) + selected += pkStats.mChooserCounts.get(mAction) .getOrDefault(mAnnotations[i], 0); } } @@ -249,37 +169,44 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> { Log.d(TAG, "Action type is null"); } else { Log.d(TAG, "Chooser Count of " + mAction + ":" + - target.name.getPackageName() + " is " + - Float.toString(chooserScore)); + target.name.getPackageName() + " is " + Integer.toString(selected)); } } - resolverTarget.setChooserScore(chooserScore); - if (chooserScore > mostChooserScore) { - mostChooserScore = chooserScore; + scoredTarget.chooserCount = selected; + if (selected > mostSelected) { + mostSelected = selected; } } } + if (DEBUG) { - Log.d(TAG, "compute - mostRecencyScore: " + mostRecencyScore - + " mostTimeSpentScore: " + mostTimeSpentScore - + " mostLaunchScore: " + mostLaunchScore - + " mostChooserScore: " + mostChooserScore); + Log.d(TAG, "compute - mostRecentlyUsedTime: " + mostRecentlyUsedTime + + " mostTimeSpent: " + mostTimeSpent + + " recentSinceTime: " + recentSinceTime + + " mostLaunched: " + mostLaunched); } - mTargets = new ArrayList<>(mTargetsDict.values()); - for (ResolverTarget target : mTargets) { - final float recency = target.getRecencyScore() / mostRecencyScore; - setFeatures(target, recency * recency * RECENCY_MULTIPLIER, - target.getLaunchScore() / mostLaunchScore, - target.getTimeSpentScore() / mostTimeSpentScore, - target.getChooserScore() / mostChooserScore); - addDefaultSelectProbability(target); + for (ScoredTarget target : mScoredTargets.values()) { + final float recency = (float) Math.max(target.lastTimeUsed - recentSinceTime, 0) + / (mostRecentlyUsedTime - recentSinceTime); + target.setFeatures((float) target.launchCount / mostLaunched, + (float) target.timeSpent / mostTimeSpent, + recency * recency * RECENCY_MULTIPLIER, + (float) target.chooserCount / mostSelected); + target.selectProb = mRanker.predict(target.getFeatures()); if (DEBUG) { Log.d(TAG, "Scores: " + target); } } - predictSelectProbabilities(mTargets); + } + + static boolean isPersistentProcess(ResolvedComponentInfo rci) { + if (rci != null && rci.getCount() > 0) { + return (rci.getResolveInfoAt(0).activityInfo.applicationInfo.flags & + ApplicationInfo.FLAG_PERSISTENT) != 0; + } + return false; } @Override @@ -318,16 +245,16 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> { // Pinned items stay stable within a normal lexical sort and ignore scoring. if (!lPinned && !rPinned) { if (mStats != null) { - final ResolverTarget lhsTarget = mTargetsDict.get(new ComponentName( + final ScoredTarget lhsTarget = mScoredTargets.get(new ComponentName( lhs.activityInfo.packageName, lhs.activityInfo.name)); - final ResolverTarget rhsTarget = mTargetsDict.get(new ComponentName( + final ScoredTarget rhsTarget = mScoredTargets.get(new ComponentName( rhs.activityInfo.packageName, rhs.activityInfo.name)); - final int selectProbabilityDiff = Float.compare( - rhsTarget.getSelectProbability(), lhsTarget.getSelectProbability()); + final int selectProbDiff = Float.compare( + rhsTarget.selectProb, lhsTarget.selectProb); - if (selectProbabilityDiff != 0) { - return selectProbabilityDiff > 0 ? 1 : -1; + if (selectProbDiff != 0) { + return selectProbDiff > 0 ? 1 : -1; } } } @@ -341,234 +268,177 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> { } public float getScore(ComponentName name) { - final ResolverTarget target = mTargetsDict.get(name); + final ScoredTarget target = mScoredTargets.get(name); if (target != null) { - return target.getSelectProbability(); + return target.selectProb; } return 0; } - public void updateChooserCounts(String packageName, int userId, String action) { - if (mUsm != null) { - mUsm.reportChooserSelection(packageName, userId, mContentType, mAnnotations, action); + static class ScoredTarget { + public final ComponentInfo componentInfo; + public long lastTimeUsed; + public long timeSpent; + public long launchCount; + public long chooserCount; + public ArrayMap<String, Float> features; + public float selectProb; + + public ScoredTarget(ComponentInfo ci) { + componentInfo = ci; + features = new ArrayMap<>(5); } - } - // update ranking model when the connection to it is valid. - public void updateModel(ComponentName componentName) { - synchronized (mLock) { - if (mRanker != null) { - try { - int selectedPos = new ArrayList<ComponentName>(mTargetsDict.keySet()) - .indexOf(componentName); - if (selectedPos > 0) { - mRanker.train(mTargets, selectedPos); - } else { - if (DEBUG) { - Log.d(TAG, "Selected a unknown component: " + componentName); - } - } - } catch (RemoteException e) { - Log.e(TAG, "Error in Train: " + e); - } - } else { - if (DEBUG) { - Log.d(TAG, "Ranker is null; skip updateModel."); - } - } + @Override + public String toString() { + return "ScoredTarget{" + componentInfo + + " lastTimeUsed: " + lastTimeUsed + + " timeSpent: " + timeSpent + + " launchCount: " + launchCount + + " chooserCount: " + chooserCount + + " selectProb: " + selectProb + + "}"; } - } - // unbind the service and clear unhandled messges. - public void destroy() { - mHandler.removeMessages(RESOLVER_RANKER_SERVICE_RESULT); - mHandler.removeMessages(RESOLVER_RANKER_RESULT_TIMEOUT); - if (mConnection != null) { - mContext.unbindService(mConnection); - mConnection.destroy(); + public void setFeatures(float launchCountScore, float usageTimeScore, float recencyScore, + float chooserCountScore) { + features.put(LAUNCH_SCORE, launchCountScore); + features.put(TIME_SPENT_SCORE, usageTimeScore); + features.put(RECENCY_SCORE, recencyScore); + features.put(CHOOSER_SCORE, chooserCountScore); } - if (DEBUG) { - Log.d(TAG, "Unbinded Resolver Ranker."); + + public ArrayMap<String, Float> getFeatures() { + return features; } } - // connect to a ranking service. - private void initRanker(Context context) { - synchronized (mLock) { - if (mConnection != null && mRanker != null) { - if (DEBUG) { - Log.d(TAG, "Ranker still exists; reusing the existing one."); - } - return; - } - } - Intent intent = resolveRankerService(); - if (intent == null) { - return; + public void updateChooserCounts(String packageName, int userId, String action) { + if (mUsm != null) { + mUsm.reportChooserSelection(packageName, userId, mContentType, mAnnotations, action); } - mConnectSignal = new CountDownLatch(1); - mConnection = new ResolverRankerServiceConnection(mConnectSignal); - context.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE, UserHandle.SYSTEM); } - // resolve the service for ranking. - private Intent resolveRankerService() { - Intent intent = new Intent(ResolverRankerService.SERVICE_INTERFACE); - final List<ResolveInfo> resolveInfos = mPm.queryIntentServices(intent, 0); - for (ResolveInfo resolveInfo : resolveInfos) { - if (resolveInfo == null || resolveInfo.serviceInfo == null - || resolveInfo.serviceInfo.applicationInfo == null) { - if (DEBUG) { - Log.d(TAG, "Failed to retrieve a ranker: " + resolveInfo); - } - continue; - } - ComponentName componentName = new ComponentName( - resolveInfo.serviceInfo.applicationInfo.packageName, - resolveInfo.serviceInfo.name); - try { - final String perm = mPm.getServiceInfo(componentName, 0).permission; - if (!ResolverRankerService.BIND_PERMISSION.equals(perm)) { - Log.w(TAG, "ResolverRankerService " + componentName + " does not require" - + " permission " + ResolverRankerService.BIND_PERMISSION - + " - this service will not be queried for ResolverComparator." - + " add android:permission=\"" - + ResolverRankerService.BIND_PERMISSION + "\"" - + " to the <service> tag for " + componentName - + " in the manifest."); - continue; - } - } catch (NameNotFoundException e) { - Log.e(TAG, "Could not look up service " + componentName - + "; component name not found"); + public void updateModel(ComponentName componentName) { + if (mScoredTargets == null || componentName == null || + !mScoredTargets.containsKey(componentName)) { + return; + } + ScoredTarget selected = mScoredTargets.get(componentName); + for (ComponentName targetComponent : mScoredTargets.keySet()) { + if (targetComponent.equals(componentName)) { continue; } - if (DEBUG) { - Log.d(TAG, "Succeeded to retrieve a ranker: " + componentName); + ScoredTarget target = mScoredTargets.get(targetComponent); + // A potential point of optimization. Save updates or derive a closed form for the + // positive case, to avoid calculating them repeatedly. + if (target.selectProb >= selected.selectProb) { + mRanker.update(target.getFeatures(), target.selectProb, false); + mRanker.update(selected.getFeatures(), selected.selectProb, true); } - intent.setComponent(componentName); - return intent; } - return null; + mRanker.commitUpdate(); } - // set a watchdog, to avoid waiting for ranking service for too long. - private void startWatchDog(int timeOutLimit) { - if (DEBUG) Log.d(TAG, "Setting watchdog timer for " + timeOutLimit + "ms"); - if (mHandler == null) { - Log.d(TAG, "Error: Handler is Null; Needs to be initialized."); - } - mHandler.sendEmptyMessageDelayed(RESOLVER_RANKER_RESULT_TIMEOUT, timeOutLimit); - } + class LogisticRegressionAppRanker { + private static final String PARAM_SHARED_PREF_NAME = "resolver_ranker_params"; + private static final String BIAS_PREF_KEY = "bias"; + private static final String VERSION_PREF_KEY = "version"; - private class ResolverRankerServiceConnection implements ServiceConnection { - private final CountDownLatch mConnectSignal; + // parameters for a pre-trained model, to initialize the app ranker. When updating the + // pre-trained model, please update these params, as well as initModel(). + private static final int CURRENT_VERSION = 1; + private static final float LEARNING_RATE = 0.0001f; + private static final float REGULARIZER_PARAM = 0.0001f; - public ResolverRankerServiceConnection(CountDownLatch connectSignal) { - mConnectSignal = connectSignal; - } + private SharedPreferences mParamSharedPref; + private ArrayMap<String, Float> mFeatureWeights; + private float mBias; - public final IResolverRankerResult resolverRankerResult = - new IResolverRankerResult.Stub() { - @Override - public void sendResult(List<ResolverTarget> targets) throws RemoteException { - if (DEBUG) { - Log.d(TAG, "Sending Result back to Resolver: " + targets); - } - synchronized (mLock) { - final Message msg = Message.obtain(); - msg.what = RESOLVER_RANKER_SERVICE_RESULT; - msg.obj = targets; - mHandler.sendMessage(msg); - } - } - }; + public LogisticRegressionAppRanker(Context context) { + mParamSharedPref = getParamSharedPref(context); + initModel(); + } - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - if (DEBUG) { - Log.d(TAG, "onServiceConnected: " + name); + public float predict(ArrayMap<String, Float> target) { + if (target == null) { + return 0.0f; } - synchronized (mLock) { - mRanker = IResolverRankerService.Stub.asInterface(service); - mConnectSignal.countDown(); + final int featureSize = target.size(); + float sum = 0.0f; + for (int i = 0; i < featureSize; i++) { + String featureName = target.keyAt(i); + float weight = mFeatureWeights.getOrDefault(featureName, 0.0f); + sum += weight * target.valueAt(i); } + return (float) (1.0 / (1.0 + Math.exp(-mBias - sum))); } - @Override - public void onServiceDisconnected(ComponentName name) { - if (DEBUG) { - Log.d(TAG, "onServiceDisconnected: " + name); + public void update(ArrayMap<String, Float> target, float predict, boolean isSelected) { + if (target == null) { + return; } - synchronized (mLock) { - destroy(); + final int featureSize = target.size(); + float error = isSelected ? 1.0f - predict : -predict; + for (int i = 0; i < featureSize; i++) { + String featureName = target.keyAt(i); + float currentWeight = mFeatureWeights.getOrDefault(featureName, 0.0f); + mBias += LEARNING_RATE * error; + currentWeight = currentWeight - LEARNING_RATE * REGULARIZER_PARAM * currentWeight + + LEARNING_RATE * error * target.valueAt(i); + mFeatureWeights.put(featureName, currentWeight); + } + if (DEBUG) { + Log.d(TAG, "Weights: " + mFeatureWeights + " Bias: " + mBias); } } - public void destroy() { - synchronized (mLock) { - mRanker = null; + public void commitUpdate() { + SharedPreferences.Editor editor = mParamSharedPref.edit(); + editor.putFloat(BIAS_PREF_KEY, mBias); + final int size = mFeatureWeights.size(); + for (int i = 0; i < size; i++) { + editor.putFloat(mFeatureWeights.keyAt(i), mFeatureWeights.valueAt(i)); } + editor.putInt(VERSION_PREF_KEY, CURRENT_VERSION); + editor.apply(); } - } - private void reset() { - mTargetsDict.clear(); - mTargets = null; - startWatchDog(WATCHDOG_TIMEOUT_MILLIS); - initRanker(mContext); - } - - // predict select probabilities if ranking service is valid. - private void predictSelectProbabilities(List<ResolverTarget> targets) { - if (mConnection == null) { + private SharedPreferences getParamSharedPref(Context context) { + // The package info in the context isn't initialized in the way it is for normal apps, + // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we + // build the path manually below using the same policy that appears in ContextImpl. if (DEBUG) { - Log.d(TAG, "Has not found valid ResolverRankerService; Skip Prediction"); - } - return; - } else { - try { - mConnectSignal.await(CONNECTION_COST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); - synchronized (mLock) { - if (mRanker != null) { - mRanker.predict(targets, mConnection.resolverRankerResult); - return; - } else { - if (DEBUG) { - Log.d(TAG, "Ranker has not been initialized; skip predict."); - } - } - } - } catch (InterruptedException e) { - Log.e(TAG, "Error in Wait for Service Connection."); - } catch (RemoteException e) { - Log.e(TAG, "Error in Predict: " + e); + Log.d(TAG, "Context Package Name: " + context.getPackageName()); } + final File prefsFile = new File(new File( + Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL, + context.getUserId(), context.getPackageName()), + "shared_prefs"), + PARAM_SHARED_PREF_NAME + ".xml"); + return context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE); } - mAfterCompute.afterCompute(); - } - // adds select prob as the default values, according to a pre-trained Logistic Regression model. - private void addDefaultSelectProbability(ResolverTarget target) { - float sum = 2.5543f * target.getLaunchScore() + 2.8412f * target.getTimeSpentScore() + - 0.269f * target.getRecencyScore() + 4.2222f * target.getChooserScore(); - target.setSelectProbability((float) (1.0 / (1.0 + Math.exp(1.6568f - sum)))); - } - - // sets features for each target - private void setFeatures(ResolverTarget target, float recencyScore, float launchScore, - float timeSpentScore, float chooserScore) { - target.setRecencyScore(recencyScore); - target.setLaunchScore(launchScore); - target.setTimeSpentScore(timeSpentScore); - target.setChooserScore(chooserScore); - } - - static boolean isPersistentProcess(ResolvedComponentInfo rci) { - if (rci != null && rci.getCount() > 0) { - return (rci.getResolveInfoAt(0).activityInfo.applicationInfo.flags & - ApplicationInfo.FLAG_PERSISTENT) != 0; + private void initModel() { + mFeatureWeights = new ArrayMap<>(4); + if (mParamSharedPref == null || + mParamSharedPref.getInt(VERSION_PREF_KEY, 0) < CURRENT_VERSION) { + // Initializing the app ranker to a pre-trained model. When updating the pre-trained + // model, please increment CURRENT_VERSION, and update LEARNING_RATE and + // REGULARIZER_PARAM. + mBias = -1.6568f; + mFeatureWeights.put(LAUNCH_SCORE, 2.5543f); + mFeatureWeights.put(TIME_SPENT_SCORE, 2.8412f); + mFeatureWeights.put(RECENCY_SCORE, 0.269f); + mFeatureWeights.put(CHOOSER_SCORE, 4.2222f); + } else { + mBias = mParamSharedPref.getFloat(BIAS_PREF_KEY, 0.0f); + mFeatureWeights.put(LAUNCH_SCORE, mParamSharedPref.getFloat(LAUNCH_SCORE, 0.0f)); + mFeatureWeights.put( + TIME_SPENT_SCORE, mParamSharedPref.getFloat(TIME_SPENT_SCORE, 0.0f)); + mFeatureWeights.put(RECENCY_SCORE, mParamSharedPref.getFloat(RECENCY_SCORE, 0.0f)); + mFeatureWeights.put(CHOOSER_SCORE, mParamSharedPref.getFloat(CHOOSER_SCORE, 0.0f)); + } } - return false; } } diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java index e8bebb74bdd0..4071ff4ebd5a 100644 --- a/core/java/com/android/internal/app/ResolverListController.java +++ b/core/java/com/android/internal/app/ResolverListController.java @@ -32,10 +32,8 @@ import android.os.RemoteException; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import java.lang.InterruptedException; import java.util.ArrayList; import java.util.Collections; -import java.util.concurrent.CountDownLatch; import java.util.List; /** @@ -207,42 +205,14 @@ public class ResolverListController { return listToReturn; } - private class ComputeCallback implements ResolverComparator.AfterCompute { - - private CountDownLatch mFinishComputeSignal; - - public ComputeCallback(CountDownLatch finishComputeSignal) { - mFinishComputeSignal = finishComputeSignal; - } - - public void afterCompute () { - mFinishComputeSignal.countDown(); - } - } - @VisibleForTesting @WorkerThread public void sort(List<ResolverActivity.ResolvedComponentInfo> inputList) { - final CountDownLatch finishComputeSignal = new CountDownLatch(1); - ComputeCallback callback = new ComputeCallback(finishComputeSignal); if (mResolverComparator == null) { - mResolverComparator = - new ResolverComparator(mContext, mTargetIntent, mReferrerPackage, callback); - } else { - mResolverComparator.setCallBack(callback); - } - try { - long beforeRank = System.currentTimeMillis(); - mResolverComparator.compute(inputList); - finishComputeSignal.await(); - Collections.sort(inputList, mResolverComparator); - long afterRank = System.currentTimeMillis(); - if (DEBUG) { - Log.d(TAG, "Time Cost: " + Long.toString(afterRank - beforeRank)); - } - } catch (InterruptedException e) { - Log.e(TAG, "Compute & Sort was interrupted: " + e); + mResolverComparator = new ResolverComparator(mContext, mTargetIntent, mReferrerPackage); } + mResolverComparator.compute(inputList); + Collections.sort(inputList, mResolverComparator); } private static boolean isSameResolvedComponent(ResolveInfo a, @@ -263,7 +233,7 @@ public class ResolverListController { @VisibleForTesting public float getScore(ResolverActivity.DisplayResolveInfo target) { if (mResolverComparator == null) { - return 0.0f; + mResolverComparator = new ResolverComparator(mContext, mTargetIntent, mReferrerPackage); } return mResolverComparator.getScore(target.getResolvedComponentName()); } @@ -279,10 +249,4 @@ public class ResolverListController { mResolverComparator.updateChooserCounts(packageName, userId, action); } } - - public void destroy() { - if (mResolverComparator != null) { - mResolverComparator.destroy(); - } - } } diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl index f987a9f45e9f..caf35b39b324 100644 --- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl +++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl @@ -55,8 +55,8 @@ interface IAppWidgetService { in RemoteViews views); void updateAppWidgetProvider(in ComponentName provider, in RemoteViews views); void notifyAppWidgetViewDataChanged(String packageName, in int[] appWidgetIds, int viewId); - ParceledListSlice getInstalledProvidersForProfile(int categoryFilter, - int profileId); + ParceledListSlice getInstalledProvidersForProfile(int categoryFilter, int profileId, + String packageName); AppWidgetProviderInfo getAppWidgetInfo(String callingPackage, int appWidgetId); boolean hasBindAppWidgetPermission(in String packageName, int userId); void setBindAppWidgetPermission(in String packageName, int userId, in boolean permission); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index a73f54354062..792225083e8d 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3130,15 +3130,6 @@ <permission android:name="android.permission.BIND_CHOOSER_TARGET_SERVICE" android:protectionLevel="signature" /> - <!-- @SystemApi Must be required by services that extend - {@link android.service.resolver.ResolverRankerService}, to ensure that only the system can - bind to them. - <p>Protection level: signature - @hide - --> - <permission android:name="android.permission.BIND_RESOLVER_RANKER_SERVICE" - android:protectionLevel="signature" /> - <!-- Must be required by a {@link android.service.notification.ConditionProviderService}, to ensure that only the system can bind to it. @@ -3650,14 +3641,6 @@ android:permission="android.permission.BIND_JOB_SERVICE" > </service> - <service android:name="com.android.internal.app.LRResolverRankerService" - android:permission="android.permission.BIND_RESOLVER_RANKER_SERVICE" - android:exported="false" - android:priority="-1" > - <intent-filter> - <action android:name="android.service.resolver.ResolverRankerService" /> - </intent-filter> - </service> </application> </manifest> diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index a165621b9696..5a2bf4eb3f62 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -17,27 +17,24 @@ <NotificationHeaderView xmlns:android="http://schemas.android.com/apk/res/android" + android:theme="@style/Theme.Material.Notification" android:id="@+id/notification_header" android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="@dimen/notification_header_height" android:clipChildren="false" - android:paddingTop="@dimen/notification_header_padding_top" - android:paddingBottom="@dimen/notification_header_padding_bottom" - android:layout_marginBottom="5dp" - android:paddingStart="@dimen/notification_content_margin_start" - android:paddingEnd="16dp"> + style="?attr/notificationHeaderStyle"> <com.android.internal.widget.CachingIconView android:id="@+id/icon" - android:layout_width="@dimen/notification_header_icon_size" - android:layout_height="@dimen/notification_header_icon_size" + android:layout_width="?attr/notificationHeaderIconSize" + android:layout_height="?attr/notificationHeaderIconSize" android:layout_marginEnd="3dp" /> <TextView android:id="@+id/app_name_text" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textAppearance="@style/TextAppearance.Material.Notification.Info" + android:textAppearance="?attr/notificationHeaderTextAppearance" android:layout_marginStart="3dp" android:layout_marginEnd="2dp" android:singleLine="true" @@ -46,7 +43,7 @@ android:id="@+id/header_text_divider" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textAppearance="@style/TextAppearance.Material.Notification.Info" + android:textAppearance="?attr/notificationHeaderTextAppearance" android:layout_marginStart="2dp" android:layout_marginEnd="2dp" android:text="@string/notification_header_divider_symbol" @@ -55,7 +52,7 @@ android:id="@+id/header_text" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textAppearance="@style/TextAppearance.Material.Notification.Info" + android:textAppearance="?attr/notificationHeaderTextAppearance" android:layout_marginStart="2dp" android:layout_marginEnd="2dp" android:visibility="gone" @@ -64,7 +61,7 @@ android:id="@+id/time_divider" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textAppearance="@style/TextAppearance.Material.Notification.Info" + android:textAppearance="?attr/notificationHeaderTextAppearance" android:layout_marginStart="2dp" android:layout_marginEnd="2dp" android:text="@string/notification_header_divider_symbol" diff --git a/core/res/res/layout/notification_template_material_ambient.xml b/core/res/res/layout/notification_template_material_ambient.xml index f3aa0485bbd2..026bc6e5cb43 100644 --- a/core/res/res/layout/notification_template_material_ambient.xml +++ b/core/res/res/layout/notification_template_material_ambient.xml @@ -23,7 +23,8 @@ android:paddingStart="@dimen/notification_extra_margin_ambient" android:paddingEnd="@dimen/notification_extra_margin_ambient" > - <include layout="@layout/notification_template_header" /> + <include layout="@layout/notification_template_header" + android:theme="@style/Theme.Material.Notification.Ambient" /> <LinearLayout android:id="@+id/notification_action_list_margin_target" diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index ee73b6983888..d26d952224f1 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -7739,13 +7739,6 @@ <attr name="settingsActivity" /> </declare-styleable> - <!-- TODO(b/35956626): temporary until clients change to AutofillService --> - <declare-styleable name="AutoFillService"> - <!-- Fully qualified class name of an activity that allows the user to modify - the settings for this service. --> - <attr name="settingsActivity" /> - </declare-styleable> - <!-- =============================== --> <!-- Contacts meta-data attributes --> <!-- =============================== --> @@ -8635,5 +8628,12 @@ <attr name="stackFromEnd" format="boolean" /> </declare-styleable> + <!-- @hide --> + <declare-styleable name="NotificationTheme"> + <attr name="notificationHeaderStyle" format="reference" /> + <attr name="notificationHeaderTextAppearance" format="reference" /> + <attr name="notificationHeaderIconSize" format="dimension" /> + </declare-styleable> + <attr name="lockPatternStyle" format="reference" /> </resources> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index b82542af521f..c5316c6133cc 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -191,6 +191,9 @@ <!-- size (width and height) of the icon in the notification header --> <dimen name="notification_header_icon_size">18dp</dimen> + <!-- size (width and height) of the icon in the notification header --> + <dimen name="notification_header_icon_size_ambient">20dp</dimen> + <!-- Height of a small notification in the status bar --> <dimen name="notification_min_height">92dp</dimen> diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index 8f061a3862a3..ec1661176ba6 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -488,6 +488,10 @@ please see styles_device_defaults.xml. <style name="TextAppearance.Material.Notification.Time" parent="TextAppearance.Material.Notification.Info" /> + <style name="TextAppearance.Material.Notification.Info.Ambient"> + <item name="textSize">@dimen/notification_text_size</item> + </style> + <style name="TextAppearance.Material.Notification.Emphasis"> <item name="textColor">#66000000</item> </style> @@ -1283,4 +1287,12 @@ please see styles_device_defaults.xml. <style name="DialogWindowTitle.Material.Light" /> + <style name="Notification.Header" parent=""> + <item name="paddingTop">@dimen/notification_header_padding_top</item> + <item name="paddingBottom">@dimen/notification_header_padding_bottom</item> + <item name="layout_marginBottom">5dp</item> + <item name="paddingStart">@dimen/notification_content_margin_start</item> + <item name="paddingEnd">16dp</item> + </style> + </resources> diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml index 008c817dd73c..9dafa7a02849 100644 --- a/core/res/res/values/themes_material.xml +++ b/core/res/res/values/themes_material.xml @@ -1321,6 +1321,19 @@ please see themes_device_defaults.xml. <item name="windowNoTitle">true</item> </style> + <!-- Theme for inflating notifications --> + <style name="Theme.Material.Notification" parent=""> + <item name="notificationHeaderStyle">@style/Notification.Header</item> + <item name="notificationHeaderTextAppearance">@style/TextAppearance.Material.Notification.Info</item> + <item name="notificationHeaderIconSize">@dimen/notification_header_icon_size</item> + </style> + + <!-- Theme for inflating ambient notification --> + <style name="Theme.Material.Notification.Ambient"> + <item name="notificationHeaderTextAppearance">@style/TextAppearance.Material.Notification.Info.Ambient</item> + <item name="notificationHeaderIconSize">@dimen/notification_header_icon_size_ambient</item> + </style> + <!-- Default theme for Settings and activities launched from Settings. --> <style name="Theme.Material.Settings" parent="Theme.Material.Light.LightStatusBar"> <item name="colorPrimary">@color/primary_material_settings_light</item> diff --git a/packages/CarrierDefaultApp/res/values/strings.xml b/packages/CarrierDefaultApp/res/values/strings.xml index f9342b7c5899..fa5c3fff283f 100644 --- a/packages/CarrierDefaultApp/res/values/strings.xml +++ b/packages/CarrierDefaultApp/res/values/strings.xml @@ -6,6 +6,7 @@ <string name="no_data_notification_id">Your mobile data has been deactivated</string> <string name="portal_notification_detail">Tap to visit the %s website</string> <string name="no_data_notification_detail">Please contact your service provider %s</string> + <string name="mobile_data_status_notification_channel_name">Mobile data status</string> <string name="action_bar_label">Sign in to mobile network</string> <string name="ssl_error_warning">The network you’re trying to join has security issues.</string> <string name="ssl_error_example">For example, the login page may not belong to the organization shown.</string> diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java index 73ff3a9b5d1e..7fd16019267e 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java @@ -16,6 +16,7 @@ package com.android.carrierdefaultapp; import android.app.Notification; +import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; @@ -35,6 +36,7 @@ public class CarrierActionUtils { private static final String PORTAL_NOTIFICATION_TAG = "CarrierDefault.Portal.Notification"; private static final String NO_DATA_NOTIFICATION_TAG = "CarrierDefault.NoData.Notification"; + private static final String NOTIFICATION_CHANNEL_ID_MOBILE_DATA_STATUS = "mobile_data_status"; private static final int PORTAL_NOTIFICATION_ID = 0; private static final int NO_DATA_NOTIFICATION_ID = 1; private static boolean ENABLE = true; @@ -150,9 +152,18 @@ public class CarrierActionUtils { private static Notification getNotification(Context context, int titleId, int textId, PendingIntent pendingIntent) { final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class); + final NotificationManager notificationManager = context.getSystemService( + NotificationManager.class); final Resources resources = context.getResources(); final Bundle extras = Bundle.forPair(Notification.EXTRA_SUBSTITUTE_APP_NAME, resources.getString(R.string.android_system_label)); + /* Creates the notification channel and registers it with NotificationManager. If a channel + * with the same ID is already registered, NotificationManager will ignore this call. + */ + notificationManager.createNotificationChannel(new NotificationChannel( + NOTIFICATION_CHANNEL_ID_MOBILE_DATA_STATUS, + resources.getString(R.string.mobile_data_status_notification_channel_name), + NotificationManager.IMPORTANCE_DEFAULT)); Notification.Builder builder = new Notification.Builder(context) .setContentTitle(resources.getString(titleId)) .setContentText(String.format(resources.getString(textId), @@ -167,7 +178,8 @@ public class CarrierActionUtils { .setLocalOnly(true) .setWhen(System.currentTimeMillis()) .setShowWhen(false) - .setExtras(extras); + .setExtras(extras) + .setChannel(NOTIFICATION_CHANNEL_ID_MOBILE_DATA_STATUS); if (pendingIntent != null) { builder.setContentIntent(pendingIntent); diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/PrivateStorageInfo.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/PrivateStorageInfo.java index 40abb6c8c2c4..88f133ce57c2 100644 --- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/PrivateStorageInfo.java +++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/PrivateStorageInfo.java @@ -41,8 +41,8 @@ public class PrivateStorageInfo { long privateTotalBytes = 0; for (VolumeInfo info : sm.getVolumes()) { if (info.getType() == VolumeInfo.TYPE_PRIVATE && info.isMountedReadable()) { - privateTotalBytes += stats.getTotalBytes(info.getFsUuid()); - privateFreeBytes += stats.getFreeBytes(info.getFsUuid()); + privateTotalBytes += sm.getTotalBytes(stats, info); + privateFreeBytes += sm.getFreeBytes(stats, info); } } return new PrivateStorageInfo(privateFreeBytes, privateTotalBytes); diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageManagerVolumeProvider.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageManagerVolumeProvider.java index 320494c68faf..11060e6c1a05 100644 --- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageManagerVolumeProvider.java +++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageManagerVolumeProvider.java @@ -16,6 +16,7 @@ package com.android.settingslib.deviceinfo; +import android.app.usage.StorageStatsManager; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; @@ -46,4 +47,14 @@ public class StorageManagerVolumeProvider implements StorageVolumeProvider { public VolumeInfo findEmulatedForPrivate(VolumeInfo privateVolume) { return mStorageManager.findEmulatedForPrivate(privateVolume); } + + @Override + public long getTotalBytes(StorageStatsManager stats, VolumeInfo volume) { + return stats.getTotalBytes(volume.getFsUuid()); + } + + @Override + public long getFreeBytes(StorageStatsManager stats, VolumeInfo volume) { + return stats.getFreeBytes(volume.getFsUuid()); + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageVolumeProvider.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageVolumeProvider.java index 646c42f05a8e..e5d85d147bee 100644 --- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageVolumeProvider.java +++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageVolumeProvider.java @@ -16,6 +16,7 @@ package com.android.settingslib.deviceinfo; +import android.app.usage.StorageStatsManager; import android.os.storage.VolumeInfo; import java.util.List; @@ -39,4 +40,18 @@ public interface StorageVolumeProvider { * Returns the emulated volume for a given private volume. */ VolumeInfo findEmulatedForPrivate(VolumeInfo privateVolume); + + /** + * Returns the total bytes for a given storage volume. + * + * @pre The volume is a private volume and is readable. + */ + long getTotalBytes(StorageStatsManager stats, VolumeInfo volume); + + /** + * Returns the free bytes for a given storage volume. + * + * @pre The volume is a private volume and is readable. + */ + long getFreeBytes(StorageStatsManager stats, VolumeInfo volume); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index a0f2891493e4..8042321ef29f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -23,7 +23,6 @@ import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; -import com.android.internal.widget.CachingIconView; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.ViewInvertHelper; @@ -127,12 +126,8 @@ public class NotificationShelf extends ActivatableNotificationView implements super.setDark(dark, fade, delay); if (mDark == dark) return; mDark = dark; - if (fade) { - mViewInvertHelper.fade(dark, delay); - } else { - mViewInvertHelper.update(dark); - } - mShelfIcons.setAmbient(dark); + mShelfIcons.setDark(dark, fade, delay); + updateInteractiveness(); } @Override @@ -576,7 +571,8 @@ public class NotificationShelf extends ActivatableNotificationView implements } private void updateInteractiveness() { - mInteractive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf; + mInteractive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf + && !mDark; setClickable(mInteractive); setFocusable(mInteractive); setImportantForAccessibility(mInteractive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index b9c8a784e779..92bfae9e3f5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -27,6 +27,7 @@ import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -46,6 +47,7 @@ import android.view.animation.Interpolator; import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.statusbar.notification.NotificationIconDozeHelper; import com.android.systemui.statusbar.notification.NotificationUtils; import java.text.NumberFormat; @@ -99,7 +101,6 @@ public class StatusBarIconView extends AnimatedImageView { private int mDensity; private float mIconScale = 1.0f; private final Paint mDotPaint = new Paint(); - private boolean mDotVisible; private float mDotRadius; private int mStaticDotRadius; private int mVisibleState = STATE_ICON; @@ -110,6 +111,8 @@ public class StatusBarIconView extends AnimatedImageView { private OnVisibilityChangedListener mOnVisibilityChangedListener; private int mDrawableColor; private int mIconColor; + private int mDecorColor; + private float mDarkAmount; private ValueAnimator mColorAnimator; private int mCurrentSetColor = NO_COLOR; private int mAnimationStartColor = NO_COLOR; @@ -119,6 +122,7 @@ public class StatusBarIconView extends AnimatedImageView { animation.getAnimatedFraction()); setColorInternal(newColor); }; + private final NotificationIconDozeHelper mDozer; public StatusBarIconView(Context context, String slot, Notification notification) { this(context, slot, notification, false); @@ -127,6 +131,7 @@ public class StatusBarIconView extends AnimatedImageView { public StatusBarIconView(Context context, String slot, Notification notification, boolean blocked) { super(context); + mDozer = new NotificationIconDozeHelper(context); mBlocked = blocked; mSlot = slot; mNumberPain = new Paint(); @@ -190,6 +195,7 @@ public class StatusBarIconView extends AnimatedImageView { public StatusBarIconView(Context context, AttributeSet attrs) { super(context, attrs); + mDozer = new NotificationIconDozeHelper(context); mBlocked = false; mAlwaysScaleIcon = true; updateIconScale(); @@ -466,7 +472,19 @@ public class StatusBarIconView extends AnimatedImageView { * to the drawable. */ public void setDecorColor(int iconTint) { - mDotPaint.setColor(iconTint); + mDecorColor = iconTint; + updateDecorColor(); + } + + private void updateDecorColor() { + int color = NotificationUtils.interpolateColors(mDecorColor, Color.WHITE, mDarkAmount); + if (mDotPaint.getColor() != color) { + mDotPaint.setColor(color); + + if (mDotAppearAmount != 0) { + invalidate(); + } + } } /** @@ -477,6 +495,7 @@ public class StatusBarIconView extends AnimatedImageView { mDrawableColor = color; setColorInternal(color); mIconColor = color; + mDozer.setColor(color); } private void setColorInternal(int color) { @@ -649,6 +668,14 @@ public class StatusBarIconView extends AnimatedImageView { mOnVisibilityChangedListener = listener; } + public void setDark(boolean dark, boolean fade, long delay) { + mDozer.setImageDark(this, dark, fade, delay, mIconColor == NO_COLOR); + mDozer.setIntensityDark(f -> { + mDarkAmount = f; + updateDecorColor(); + }, dark, fade, delay); + } + public interface OnVisibilityChangedListener { void onVisibilityChanged(int newVisibility); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java index 3efa29f87450..bca4b43afc0c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; +import android.content.Context; import android.graphics.ColorMatrixColorFilter; import android.graphics.Paint; import android.view.View; @@ -38,8 +38,8 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper { private boolean mIsLegacy; private int mLegacyColor; - protected NotificationCustomViewWrapper(View view, ExpandableNotificationRow row) { - super(view, row); + protected NotificationCustomViewWrapper(Context ctx, View view, ExpandableNotificationRow row) { + super(ctx, view, row); mInvertHelper = new ViewInvertHelper(view, NotificationPanelView.DOZE_ANIMATION_DURATION); mLegacyColor = row.getContext().getColor(R.color.notification_legacy_background_color); } @@ -67,13 +67,11 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper { } protected void fadeGrayscale(final boolean dark, long delay) { - startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - updateGrayscaleMatrix((float) animation.getAnimatedValue()); - mGreyPaint.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix)); - mView.setLayerPaint(mGreyPaint); - } + getDozer().startIntensityAnimation(animation -> { + getDozer().updateGrayscaleMatrix((float) animation.getAnimatedValue()); + mGreyPaint.setColorFilter( + new ColorMatrixColorFilter(getDozer().getGrayscaleColorMatrix())); + mView.setLayerPaint(mGreyPaint); }, dark, delay, new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -86,9 +84,9 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper { protected void updateGrayscale(boolean dark) { if (dark) { - updateGrayscaleMatrix(1f); + getDozer().updateGrayscaleMatrix(1f); mGreyPaint.setColorFilter( - new ColorMatrixColorFilter(mGrayscaleColorMatrix)); + new ColorMatrixColorFilter(getDozer().getGrayscaleColorMatrix())); mView.setLayerPaint(mGreyPaint); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java new file mode 100644 index 000000000000..d592c5f5b7f3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.widget.ImageView; + +import com.android.systemui.Interpolators; +import com.android.systemui.statusbar.phone.NotificationPanelView; + +import java.util.function.Consumer; + +public class NotificationDozeHelper { + private final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix(); + + public void fadeGrayscale(final ImageView target, final boolean dark, long delay) { + startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + updateGrayscaleMatrix((float) animation.getAnimatedValue()); + target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix)); + } + }, dark, delay, new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (!dark) { + target.setColorFilter(null); + } + } + }); + } + + public void updateGrayscale(ImageView target, boolean dark) { + if (dark) { + updateGrayscaleMatrix(1f); + target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix)); + } else { + target.setColorFilter(null); + } + } + + public void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener, + boolean dark, long delay, Animator.AnimatorListener listener) { + float startIntensity = dark ? 0f : 1f; + float endIntensity = dark ? 1f : 0f; + ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity); + animator.addUpdateListener(updateListener); + animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION); + animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); + animator.setStartDelay(delay); + if (listener != null) { + animator.addListener(listener); + } + animator.start(); + } + + public void setIntensityDark(Consumer<Float> listener, boolean dark, + boolean animate, long delay) { + if (animate) { + startIntensityAnimation(a -> listener.accept((Float) a.getAnimatedValue()), dark, delay, + null /* listener */); + } else { + listener.accept(dark ? 1f : 0f); + } + } + + public void updateGrayscaleMatrix(float intensity) { + mGrayscaleColorMatrix.setSaturation(1 - intensity); + } + + public ColorMatrix getGrayscaleColorMatrix() { + return mGrayscaleColorMatrix; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java index 38e4ec1a4d7f..1ffc94480dab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java @@ -16,17 +16,10 @@ package com.android.systemui.statusbar.notification; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; import android.app.Notification; import android.content.Context; -import android.graphics.Color; import android.graphics.ColorFilter; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; -import android.graphics.drawable.Drawable; import android.util.ArraySet; import android.view.NotificationHeaderView; import android.view.View; @@ -37,7 +30,6 @@ import android.widget.ImageView; import android.widget.TextView; import com.android.systemui.Interpolators; -import com.android.systemui.R; import com.android.systemui.ViewInvertHelper; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.TransformableView; @@ -55,10 +47,6 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { private static final Interpolator LOW_PRIORITY_HEADER_CLOSE = new PathInterpolator(0.4f, 0f, 0.7f, 1f); - private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter( - 0, PorterDuff.Mode.SRC_ATOP); - private final int mIconDarkAlpha; - private final int mIconDarkColor = 0xffffffff; protected final ViewInvertHelper mInvertHelper; protected final ViewTransformationHelper mTransformationHelper; @@ -74,8 +62,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { private boolean mTransformLowPriorityTitle; protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) { - super(view, row); - mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha); + super(ctx, view, row); mInvertHelper = new ViewInvertHelper(ctx, NotificationPanelView.DOZE_ANIMATION_DURATION); mTransformationHelper = new ViewTransformationHelper(); @@ -108,6 +95,16 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { updateInvertHelper(); } + @Override + protected NotificationDozeHelper createDozer(Context ctx) { + return new NotificationIconDozeHelper(ctx); + } + + @Override + protected NotificationIconDozeHelper getDozer() { + return (NotificationIconDozeHelper) super.getDozer(); + } + protected void resolveHeaderViews() { mIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon); mHeaderText = (TextView) mView.findViewById(com.android.internal.R.id.header_text); @@ -116,6 +113,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { mColor = resolveColor(mExpandButton); mNotificationHeader = (NotificationHeaderView) mView.findViewById( com.android.internal.R.id.notification_header); + getDozer().setColor(mColor); } private int resolveColor(ImageView icon) { @@ -223,90 +221,8 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { // It also may lead to bugs where the icon isn't correctly greyed out. boolean hadColorFilter = mNotificationHeader.getOriginalIconColor() != NotificationHeaderView.NO_COLOR; - if (fade) { - if (hadColorFilter) { - fadeIconColorFilter(mIcon, dark, delay); - fadeIconAlpha(mIcon, dark, delay); - } else { - fadeGrayscale(mIcon, dark, delay); - } - } else { - if (hadColorFilter) { - updateIconColorFilter(mIcon, dark); - updateIconAlpha(mIcon, dark); - } else { - updateGrayscale(mIcon, dark); - } - } - } - } - - private void fadeIconColorFilter(final ImageView target, boolean dark, long delay) { - startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - updateIconColorFilter(target, (Float) animation.getAnimatedValue()); - } - }, dark, delay, null /* listener */); - } - - private void fadeIconAlpha(final ImageView target, boolean dark, long delay) { - startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float t = (float) animation.getAnimatedValue(); - target.setImageAlpha((int) (255 * (1f - t) + mIconDarkAlpha * t)); - } - }, dark, delay, null /* listener */); - } - - protected void fadeGrayscale(final ImageView target, final boolean dark, long delay) { - startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - updateGrayscaleMatrix((float) animation.getAnimatedValue()); - target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix)); - } - }, dark, delay, new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (!dark) { - target.setColorFilter(null); - } - } - }); - } - - private void updateIconColorFilter(ImageView target, boolean dark) { - updateIconColorFilter(target, dark ? 1f : 0f); - } - private void updateIconColorFilter(ImageView target, float intensity) { - int color = interpolateColor(mColor, mIconDarkColor, intensity); - mIconColorFilter.setColor(color); - Drawable iconDrawable = target.getDrawable(); - - // Also, the notification might have been modified during the animation, so background - // might be null here. - if (iconDrawable != null) { - Drawable d = iconDrawable.mutate(); - // DrawableContainer ignores the color filter if it's already set, so clear it first to - // get it set and invalidated properly. - d.setColorFilter(null); - d.setColorFilter(mIconColorFilter); - } - } - - private void updateIconAlpha(ImageView target, boolean dark) { - target.setImageAlpha(dark ? mIconDarkAlpha : 255); - } - - protected void updateGrayscale(ImageView target, boolean dark) { - if (dark) { - updateGrayscaleMatrix(1f); - target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix)); - } else { - target.setColorFilter(null); + getDozer().setImageDark(mIcon, dark, fade, delay, !hadColorFilter); } } @@ -316,22 +232,6 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { mNotificationHeader.setOnClickListener(expandable ? onClickListener : null); } - private static int interpolateColor(int source, int target, float t) { - int aSource = Color.alpha(source); - int rSource = Color.red(source); - int gSource = Color.green(source); - int bSource = Color.blue(source); - int aTarget = Color.alpha(target); - int rTarget = Color.red(target); - int gTarget = Color.green(target); - int bTarget = Color.blue(target); - return Color.argb( - (int) (aSource * (1f - t) + aTarget * t), - (int) (rSource * (1f - t) + rTarget * t), - (int) (gSource * (1f - t) + gTarget * t), - (int) (bSource * (1f - t) + bTarget * t)); - } - @Override public NotificationHeaderView getNotificationHeader() { return mNotificationHeader; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java new file mode 100644 index 000000000000..9f79ef2491d9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; +import android.widget.ImageView; + +import com.android.systemui.R; + +public class NotificationIconDozeHelper extends NotificationDozeHelper { + + private final int mImageDarkAlpha; + private final int mImageDarkColor = 0xffffffff; + private final PorterDuffColorFilter mImageColorFilter = new PorterDuffColorFilter( + 0, PorterDuff.Mode.SRC_ATOP); + + private int mColor = Color.BLACK; + + public NotificationIconDozeHelper(Context ctx) { + mImageDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha); + } + + public void setColor(int color) { + mColor = color; + } + + public void setImageDark(ImageView target, boolean dark, boolean fade, long delay, + boolean useGrayscale) { + if (fade) { + if (!useGrayscale) { + fadeImageColorFilter(target, dark, delay); + fadeImageAlpha(target, dark, delay); + } else { + fadeGrayscale(target, dark, delay); + } + } else { + if (!useGrayscale) { + updateImageColorFilter(target, dark); + updateImageAlpha(target, dark); + } else { + updateGrayscale(target, dark); + } + } + } + + private void fadeImageColorFilter(final ImageView target, boolean dark, long delay) { + startIntensityAnimation(animation -> { + updateImageColorFilter(target, (Float) animation.getAnimatedValue()); + }, dark, delay, null /* listener */); + } + + private void fadeImageAlpha(final ImageView target, boolean dark, long delay) { + startIntensityAnimation(animation -> { + float t = (float) animation.getAnimatedValue(); + target.setImageAlpha((int) (255 * (1f - t) + mImageDarkAlpha * t)); + }, dark, delay, null /* listener */); + } + + private void updateImageColorFilter(ImageView target, boolean dark) { + updateImageColorFilter(target, dark ? 1f : 0f); + } + + private void updateImageColorFilter(ImageView target, float intensity) { + int color = NotificationUtils.interpolateColors(mColor, mImageDarkColor, intensity); + mImageColorFilter.setColor(color); + Drawable imageDrawable = target.getDrawable(); + + // Also, the notification might have been modified during the animation, so background + // might be null here. + if (imageDrawable != null) { + Drawable d = imageDrawable.mutate(); + // DrawableContainer ignores the color filter if it's already set, so clear it first to + // get it set and invalidated properly. + d.setColorFilter(null); + d.setColorFilter(mImageColorFilter); + } + } + + private void updateImageAlpha(ImageView target, boolean dark) { + target.setImageAlpha(dark ? mImageDarkAlpha : 255); + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java index 846d03ac74dd..f0b6b2e89e9f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification; -import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Color; import android.service.notification.StatusBarNotification; @@ -46,7 +45,8 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp private int mContentHeight; private int mMinHeightHint; - protected NotificationTemplateViewWrapper(Context ctx, View view, ExpandableNotificationRow row) { + protected NotificationTemplateViewWrapper(Context ctx, View view, + ExpandableNotificationRow row) { super(ctx, view, row); mTransformationHelper.setCustomTransformation( new ViewTransformationHelper.CustomTransformation() { @@ -154,16 +154,20 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp // This also clears the existing types super.updateTransformedTypes(); if (mTitle != null) { - mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE, mTitle); + mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE, + mTitle); } if (mText != null) { - mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT, mText); + mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT, + mText); } if (mPicture != null) { - mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_IMAGE, mPicture); + mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_IMAGE, + mPicture); } if (mProgressBar != null) { - mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_PROGRESS, mProgressBar); + mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_PROGRESS, + mProgressBar); } } @@ -173,7 +177,7 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp return; } super.setDark(dark, fade, delay); - setPictureGrayscale(dark, fade, delay); + setPictureDark(dark, fade, delay); setProgressBarDark(dark, fade, delay); } @@ -188,12 +192,9 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp } private void fadeProgressDark(final ProgressBar target, final boolean dark, long delay) { - startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float t = (float) animation.getAnimatedValue(); - updateProgressDark(target, t); - } + getDozer().startIntensityAnimation(animation -> { + float t = (float) animation.getAnimatedValue(); + updateProgressDark(target, t); }, dark, delay, null /* listener */); } @@ -207,13 +208,9 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp updateProgressDark(target, dark ? 1f : 0f); } - protected void setPictureGrayscale(boolean grayscale, boolean fade, long delay) { + private void setPictureDark(boolean dark, boolean fade, long delay) { if (mPicture != null) { - if (fade) { - fadeGrayscale(mPicture, grayscale, delay); - } else { - updateGrayscale(mPicture, grayscale); - } + getDozer().setImageDark(mPicture, dark, fade, delay, true /* useGrayscale */); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java index c85e8d853b0d..f4db9a1977f3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java @@ -16,24 +16,17 @@ package com.android.systemui.statusbar.notification; -import android.animation.Animator; -import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Color; -import android.graphics.ColorMatrix; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; -import android.service.notification.StatusBarNotification; import android.support.v4.graphics.ColorUtils; import android.view.NotificationHeaderView; import android.view.View; -import com.android.systemui.Interpolators; -import com.android.systemui.R; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.TransformableView; -import com.android.systemui.statusbar.phone.NotificationPanelView; /** * Wraps the actual notification content view; used to implement behaviors which are different for @@ -41,14 +34,14 @@ import com.android.systemui.statusbar.phone.NotificationPanelView; */ public abstract class NotificationViewWrapper implements TransformableView { - protected final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix(); protected final View mView; protected final ExpandableNotificationRow mRow; + private final NotificationDozeHelper mDozer; + protected boolean mDark; private int mBackgroundColor = 0; protected boolean mShouldInvertDark; protected boolean mDarkInitialized = false; - private boolean mForcedInvisible; public static NotificationViewWrapper wrap(Context ctx, View v, ExpandableNotificationRow row) { if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) { @@ -65,13 +58,22 @@ public abstract class NotificationViewWrapper implements TransformableView { } else if (v instanceof NotificationHeaderView) { return new NotificationHeaderViewWrapper(ctx, v, row); } else { - return new NotificationCustomViewWrapper(v, row); + return new NotificationCustomViewWrapper(ctx, v, row); } } - protected NotificationViewWrapper(View view, ExpandableNotificationRow row) { + protected NotificationViewWrapper(Context ctx, View view, ExpandableNotificationRow row) { mView = view; mRow = row; + mDozer = createDozer(ctx); + } + + protected NotificationDozeHelper createDozer(Context ctx) { + return new NotificationDozeHelper(); + } + + protected NotificationDozeHelper getDozer() { + return mDozer; } /** @@ -112,26 +114,6 @@ public abstract class NotificationViewWrapper implements TransformableView { || ColorUtils.calculateLuminance(backgroundColor) > 0.5; } - - protected void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener, - boolean dark, long delay, Animator.AnimatorListener listener) { - float startIntensity = dark ? 0f : 1f; - float endIntensity = dark ? 1f : 0f; - ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity); - animator.addUpdateListener(updateListener); - animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION); - animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); - animator.setStartDelay(delay); - if (listener != null) { - animator.addListener(listener); - } - animator.start(); - } - - protected void updateGrayscaleMatrix(float intensity) { - mGrayscaleColorMatrix.setSaturation(1 - intensity); - } - /** * Update the appearance of the expand button. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index 3706dc8242b9..dee15d8163a6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -95,7 +95,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { private int mActualLayoutWidth = NO_VALUE; private float mActualPaddingEnd = NO_VALUE; private float mActualPaddingStart = NO_VALUE; - private boolean mCentered; + private boolean mDark; private boolean mChangingViewPositions; private int mAddAnimationStartIndex = -1; private int mCannedAnimationStartIndex = -1; @@ -183,6 +183,9 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex); } } + if (mDark && child instanceof StatusBarIconView) { + ((StatusBarIconView) child).setDark(mDark, false, 0); + } } @Override @@ -312,7 +315,8 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { numDots++; } } - if (mCentered && translationX < getLayoutEnd()) { + boolean center = mDark; + if (center && translationX < getLayoutEnd()) { float delta = (getLayoutEnd() - translationX) / 2; for (int i = 0; i < childCount; i++) { View view = getChildAt(i); @@ -390,9 +394,15 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mChangingViewPositions = changingViewPositions; } - public void setAmbient(boolean ambient) { - mCentered = ambient; + public void setDark(boolean dark, boolean fade, long delay) { + mDark = dark; mDisallowNextAnimation = true; + for (int i = 0; i < getChildCount(); i++) { + View view = getChildAt(i); + if (view instanceof StatusBarIconView) { + ((StatusBarIconView) view).setDark(dark, fade, delay); + } + } } public IconState getIconState(StatusBarIconView icon) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index ff4cbfc235b0..472af653af6e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -5010,23 +5010,25 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onPulseStarted() { callback.onPulseStarted(); - if (!mHeadsUpManager.getAllEntries().isEmpty()) { + Collection<HeadsUpManager.HeadsUpEntry> pulsingEntries = + mHeadsUpManager.getAllEntries(); + if (!pulsingEntries.isEmpty()) { // Only pulse the stack scroller if there's actually something to show. // Otherwise just show the always-on screen. - setPulsing(true); + setPulsing(pulsingEntries); } } @Override public void onPulseFinished() { callback.onPulseFinished(); - setPulsing(false); + setPulsing(null); } - private void setPulsing(boolean pulsing) { + private void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) { mStackScroller.setPulsing(pulsing); - mNotificationPanel.setPulsing(pulsing); - mVisualStabilityManager.setPulsing(pulsing); + mNotificationPanel.setPulsing(pulsing != null); + mVisualStabilityManager.setPulsing(pulsing != null); } }, reason); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 5bead730fcf1..15fcb38ccf6c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -72,6 +72,7 @@ import com.android.systemui.statusbar.DismissView; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; +import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.NotificationGuts; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StackScrollerDecorView; @@ -86,6 +87,7 @@ import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ScrollAdapter; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; @@ -331,7 +333,7 @@ public class NotificationStackScrollLayout extends ViewGroup } }; private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC); - private boolean mPulsing; + private Collection<HeadsUpManager.HeadsUpEntry> mPulsing; private boolean mDrawBackgroundAsSrc; private boolean mFadingOut; private boolean mParentNotFullyVisible; @@ -1917,15 +1919,19 @@ public class NotificationStackScrollLayout extends ViewGroup int numShownItems = 0; boolean finish = false; int maxDisplayedNotifications = mAmbientState.isDark() - ? (mPulsing ? 1 : 0) + ? (isPulsing() ? 1 : 0) : mMaxDisplayedNotifications; for (int i = 0; i < getChildCount(); i++) { ExpandableView expandableView = (ExpandableView) getChildAt(i); if (expandableView.getVisibility() != View.GONE && !expandableView.hasNoContentHeight()) { - if (maxDisplayedNotifications != -1 - && numShownItems >= maxDisplayedNotifications) { + boolean limitReached = maxDisplayedNotifications != -1 + && numShownItems >= maxDisplayedNotifications; + boolean notificationOnAmbientThatIsNotPulsing = isPulsing() + && expandableView instanceof ExpandableNotificationRow + && !isPulsing(((ExpandableNotificationRow) expandableView).getEntry()); + if (limitReached || notificationOnAmbientThatIsNotPulsing) { expandableView = mShelf; finish = true; } @@ -1971,6 +1977,19 @@ public class NotificationStackScrollLayout extends ViewGroup mAmbientState.setLayoutMaxHeight(mContentHeight); } + private boolean isPulsing(NotificationData.Entry entry) { + for (HeadsUpManager.HeadsUpEntry e : mPulsing) { + if (e.entry == entry) { + return true; + } + } + return false; + } + + private boolean isPulsing() { + return mPulsing != null; + } + private void updateScrollability() { boolean scrollable = getScrollRange() > 0; if (scrollable != mScrollable) { @@ -2784,7 +2803,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private void updateNotificationAnimationStates() { - boolean running = mAnimationsEnabled || mPulsing; + boolean running = mAnimationsEnabled || isPulsing(); mShelf.setAnimationsEnabled(running); int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { @@ -2795,7 +2814,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private void updateAnimationState(View child) { - updateAnimationState((mAnimationsEnabled || mPulsing) + updateAnimationState((mAnimationsEnabled || isPulsing()) && (mIsExpanded || isPinnedHeadsUp(child)), child); } @@ -4055,12 +4074,12 @@ public class NotificationStackScrollLayout extends ViewGroup return mIsExpanded; } - public void setPulsing(boolean pulsing) { - if (mPulsing == pulsing) { + public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) { + if (mPulsing == null && pulsing == null) { return; } mPulsing = pulsing; - mAmbientState.setPulsing(pulsing); + mAmbientState.setPulsing(isPulsing()); updateNotificationAnimationStates(); updateContentHeight(); notifyHeightChangeListener(mShelf); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationViewWrapperTest.java new file mode 100644 index 000000000000..a69de7a68291 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationViewWrapperTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.view.View; + +import com.android.systemui.statusbar.ExpandableNotificationRow; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class NotificationViewWrapperTest { + + private Context mContext; + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getTargetContext(); + } + + @Test + public void constructor_doesntUseViewContext() throws Exception { + new TestableNotificationViewWrapper(mContext, new View(null /* context */), null /* row */); + } + + static class TestableNotificationViewWrapper extends NotificationViewWrapper { + protected TestableNotificationViewWrapper(Context ctx, View view, + ExpandableNotificationRow row) { + super(ctx, view, row); + } + } +}
\ No newline at end of file diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 0482e734c234..ac81565fbf3c 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -1631,7 +1631,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku @Override public ParceledListSlice<AppWidgetProviderInfo> getInstalledProvidersForProfile(int categoryFilter, - int profileId) { + int profileId, String packageName) { final int userId = UserHandle.getCallingUserId(); if (DEBUG) { @@ -1653,8 +1653,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku Provider provider = mProviders.get(i); AppWidgetProviderInfo info = provider.info; - // Ignore an invalid provider or one not matching the filter. - if (provider.zombie || (info.widgetCategory & categoryFilter) == 0) { + // Ignore an invalid provider, one not matching the filter, + // or one that isn't in the given package, if any. + boolean inPackage = packageName == null + || provider.id.componentName.getPackageName().equals(packageName); + if (provider.zombie || (info.widgetCategory & categoryFilter) == 0 || !inPackage) { continue; } diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index e560d325e6dd..738365d5b36f 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -427,14 +427,13 @@ public class AccountManagerService public boolean addAccountExplicitlyWithVisibility(Account account, String password, Bundle extras, Map packageToVisibility) { Bundle.setDefusable(extras, true); - - final int callingUid = Binder.getCallingUid(); + int callingUid = Binder.getCallingUid(); + int userId = UserHandle.getCallingUserId(); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "addAccountExplicitly: " + account + ", caller's uid " + callingUid + ", pid " + Binder.getCallingPid()); } Preconditions.checkNotNull(account, "account cannot be null"); - int userId = UserHandle.getCallingUserId(); if (!isAccountManagedByCaller(account.type, callingUid, userId)) { String msg = String.format("uid %s cannot explicitly add accounts of type: %s", callingUid, account.type); @@ -461,9 +460,9 @@ public class AccountManagerService public Map<Account, Integer> getAccountsAndVisibilityForPackage(String packageName, String accountType) { int callingUid = Binder.getCallingUid(); + int userId = UserHandle.getCallingUserId(); boolean isSystemUid = UserHandle.isSameApp(callingUid, Process.SYSTEM_UID); - List<String> managedTypes = - getTypesForCaller(callingUid, UserHandle.getUserId(callingUid), isSystemUid); + List<String> managedTypes = getTypesForCaller(callingUid, userId, isSystemUid); if ((accountType != null && !managedTypes.contains(accountType)) || (accountType == null && !isSystemUid)) { @@ -478,8 +477,9 @@ public class AccountManagerService long identityToken = clearCallingIdentity(); try { + UserAccounts accounts = getUserAccounts(userId); return getAccountsAndVisibilityForPackage(packageName, managedTypes, callingUid, - getUserAccounts(UserHandle.getUserId(callingUid))); + accounts); } finally { restoreCallingIdentity(identityToken); } @@ -490,12 +490,8 @@ public class AccountManagerService */ private Map<Account, Integer> getAccountsAndVisibilityForPackage(String packageName, List<String> accountTypes, Integer callingUid, UserAccounts accounts) { - int uid = 0; - try { - uid = mPackageManager.getPackageUidAsUser(packageName, - UserHandle.getUserId(callingUid)); - } catch (NameNotFoundException e) { - Log.d(TAG, "Package not found " + e.getMessage()); + if (!packageExistsForUser(packageName, accounts.userId)) { + Log.d(TAG, "Package not found " + packageName); return new LinkedHashMap<>(); } @@ -520,19 +516,26 @@ public class AccountManagerService public Map<String, Integer> getPackagesAndVisibilityForAccount(Account account) { Preconditions.checkNotNull(account, "account cannot be null"); int callingUid = Binder.getCallingUid(); - int userId = UserHandle.getUserId(callingUid); - UserAccounts accounts = getUserAccounts(userId); + int userId = UserHandle.getCallingUserId(); if (!isAccountManagedByCaller(account.type, callingUid, userId) && !isSystemUid(callingUid)) { String msg = String.format("uid %s cannot get secrets for account %s", callingUid, account); throw new SecurityException(msg); } - synchronized (accounts.dbLock) { - synchronized (accounts.cacheLock) { - return getPackagesAndVisibilityForAccountLocked(account, accounts); + + long identityToken = clearCallingIdentity(); + try { + UserAccounts accounts = getUserAccounts(userId); + synchronized (accounts.dbLock) { + synchronized (accounts.cacheLock) { + return getPackagesAndVisibilityForAccountLocked(account, accounts); + } } + } finally { + restoreCallingIdentity(identityToken); } + } /** @@ -560,8 +563,8 @@ public class AccountManagerService Preconditions.checkNotNull(account, "account cannot be null"); Preconditions.checkNotNull(packageName, "packageName cannot be null"); int callingUid = Binder.getCallingUid(); - UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid)); - if (!isAccountManagedByCaller(account.type, callingUid, accounts.userId) + int userId = UserHandle.getCallingUserId(); + if (!isAccountManagedByCaller(account.type, callingUid, userId) && !isSystemUid(callingUid)) { String msg = String.format( "uid %s cannot get secrets for accounts of type: %s", @@ -569,7 +572,13 @@ public class AccountManagerService account.type); throw new SecurityException(msg); } - return resolveAccountVisibility(account, packageName, accounts); + long identityToken = clearCallingIdentity(); + try { + UserAccounts accounts = getUserAccounts(userId); + return resolveAccountVisibility(account, packageName, accounts); + } finally { + restoreCallingIdentity(identityToken); + } } /** @@ -708,8 +717,8 @@ public class AccountManagerService Preconditions.checkNotNull(account, "account cannot be null"); Preconditions.checkNotNull(packageName, "packageName cannot be null"); int callingUid = Binder.getCallingUid(); - UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid)); - if (!isAccountManagedByCaller(account.type, callingUid, accounts.userId) + int userId = UserHandle.getCallingUserId(); + if (!isAccountManagedByCaller(account.type, callingUid, userId) && !isSystemUid(callingUid)) { String msg = String.format( "uid %s cannot get secrets for accounts of type: %s", @@ -717,8 +726,14 @@ public class AccountManagerService account.type); throw new SecurityException(msg); } - return setAccountVisibility(account, packageName, newVisibility, true /* notify */, + long identityToken = clearCallingIdentity(); + try { + UserAccounts accounts = getUserAccounts(userId); + return setAccountVisibility(account, packageName, newVisibility, true /* notify */, accounts); + } finally { + restoreCallingIdentity(identityToken); + } } /** @@ -805,8 +820,15 @@ public class AccountManagerService public void registerAccountListener(String[] accountTypes, String opPackageName) { int callingUid = Binder.getCallingUid(); mAppOpsManager.checkPackage(callingUid, opPackageName); - registerAccountListener(accountTypes, opPackageName, - getUserAccounts(UserHandle.getUserId(callingUid))); + + int userId = UserHandle.getCallingUserId(); + long identityToken = clearCallingIdentity(); + try { + UserAccounts accounts = getUserAccounts(userId); + registerAccountListener(accountTypes, opPackageName, accounts); + } finally { + restoreCallingIdentity(identityToken); + } } private void registerAccountListener(String[] accountTypes, String opPackageName, @@ -832,7 +854,18 @@ public class AccountManagerService public void unregisterAccountListener(String[] accountTypes, String opPackageName) { int callingUid = Binder.getCallingUid(); mAppOpsManager.checkPackage(callingUid, opPackageName); - UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid)); + int userId = UserHandle.getCallingUserId(); + long identityToken = clearCallingIdentity(); + try { + UserAccounts accounts = getUserAccounts(userId); + unregisterAccountListener(accountTypes, opPackageName, accounts); + } finally { + restoreCallingIdentity(identityToken); + } + } + + private void unregisterAccountListener(String[] accountTypes, String opPackageName, + UserAccounts accounts) { synchronized (accounts.mReceiversForType) { if (accountTypes == null) { // null for any type @@ -903,7 +936,7 @@ public class AccountManagerService long identityToken = clearCallingIdentity(); try { mPackageManager.getPackageUidAsUser(packageName, userId); - return true; // package exist + return true; } finally { restoreCallingIdentity(identityToken); } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 4cbfb275fd32..7dd75df5252a 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -31,8 +31,10 @@ import java.util.Set; import android.app.ActivityThread; import android.app.AppOpsManager; +import android.app.ServiceStartArgs; import android.content.IIntentSender; import android.content.IntentSender; +import android.content.pm.ParceledListSlice; import android.os.Build; import android.os.Bundle; import android.os.DeadObjectException; @@ -369,7 +371,8 @@ public final class ActiveServices { } // This app knows it is in the new model where this operation is not // allowed, so tell it what has happened. - return new ComponentName("?", "app is in background"); + UidRecord uidRec = mAm.mActiveUids.get(r.appInfo.uid); + return new ComponentName("?", "app is in background uid " + uidRec); } } finally { Binder.restoreCallingIdentity(token); @@ -1956,78 +1959,86 @@ public final class ActiveServices { return; } + ArrayList<ServiceStartArgs> args = new ArrayList<>(); + while (r.pendingStarts.size() > 0) { - Exception caughtException = null; - ServiceRecord.StartItem si = null; - try { - si = r.pendingStarts.remove(0); - if (DEBUG_SERVICE) { - Slog.v(TAG_SERVICE, "Sending arguments to: " - + r + " " + r.intent + " args=" + si.intent); - } - if (si.intent == null && N > 1) { - // If somehow we got a dummy null intent in the middle, - // then skip it. DO NOT skip a null intent when it is - // the only one in the list -- this is to support the - // onStartCommand(null) case. - continue; - } - si.deliveredTime = SystemClock.uptimeMillis(); - r.deliveredStarts.add(si); - si.deliveryCount++; - if (si.neededGrants != null) { - mAm.grantUriPermissionUncheckedFromIntentLocked(si.neededGrants, - si.getUriPermissionsLocked()); - } - mAm.grantEphemeralAccessLocked(r.userId, si.intent, - r.appInfo.uid, UserHandle.getAppId(si.callingId)); - bumpServiceExecutingLocked(r, execInFg, "start"); - if (!oomAdjusted) { - oomAdjusted = true; - mAm.updateOomAdjLocked(r.app); - } - if (r.fgRequired && !r.fgWaiting) { - if (!r.isForeground) { - if (DEBUG_BACKGROUND_CHECK) { - Slog.i(TAG, "Launched service must call startForeground() within timeout: " + r); - } - scheduleServiceForegroundTransitionTimeoutLocked(r); - } else { - if (DEBUG_BACKGROUND_CHECK) { - Slog.i(TAG, "Service already foreground; no new timeout: " + r); - } - r.fgRequired = false; + ServiceRecord.StartItem si = r.pendingStarts.remove(0); + if (DEBUG_SERVICE) { + Slog.v(TAG_SERVICE, "Sending arguments to: " + + r + " " + r.intent + " args=" + si.intent); + } + if (si.intent == null && N > 1) { + // If somehow we got a dummy null intent in the middle, + // then skip it. DO NOT skip a null intent when it is + // the only one in the list -- this is to support the + // onStartCommand(null) case. + continue; + } + si.deliveredTime = SystemClock.uptimeMillis(); + r.deliveredStarts.add(si); + si.deliveryCount++; + if (si.neededGrants != null) { + mAm.grantUriPermissionUncheckedFromIntentLocked(si.neededGrants, + si.getUriPermissionsLocked()); + } + mAm.grantEphemeralAccessLocked(r.userId, si.intent, + r.appInfo.uid, UserHandle.getAppId(si.callingId)); + bumpServiceExecutingLocked(r, execInFg, "start"); + if (!oomAdjusted) { + oomAdjusted = true; + mAm.updateOomAdjLocked(r.app); + } + if (r.fgRequired && !r.fgWaiting) { + if (!r.isForeground) { + if (DEBUG_BACKGROUND_CHECK) { + Slog.i(TAG, "Launched service must call startForeground() within timeout: " + r); } + scheduleServiceForegroundTransitionTimeoutLocked(r); + } else { + if (DEBUG_BACKGROUND_CHECK) { + Slog.i(TAG, "Service already foreground; no new timeout: " + r); + } + r.fgRequired = false; } - int flags = 0; - if (si.deliveryCount > 1) { - flags |= Service.START_FLAG_RETRY; - } - if (si.doneExecutingCount > 0) { - flags |= Service.START_FLAG_REDELIVERY; - } - r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent); - } catch (TransactionTooLargeException e) { - if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Transaction too large: intent=" - + si.intent); - caughtException = e; - } catch (RemoteException e) { - // Remote process gone... we'll let the normal cleanup take care of this. - if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while sending args: " + r); - caughtException = e; - } catch (Exception e) { - Slog.w(TAG, "Unexpected exception", e); - caughtException = e; } + int flags = 0; + if (si.deliveryCount > 1) { + flags |= Service.START_FLAG_RETRY; + } + if (si.doneExecutingCount > 0) { + flags |= Service.START_FLAG_REDELIVERY; + } + args.add(new ServiceStartArgs(si.taskRemoved, si.id, flags, si.intent)); + } - if (caughtException != null) { - // Keep nesting count correct - final boolean inDestroying = mDestroyingServices.contains(r); + ParceledListSlice<ServiceStartArgs> slice = new ParceledListSlice<>(args); + slice.setInlineCountLimit(4); + Exception caughtException = null; + try { + r.app.thread.scheduleServiceArgs(r, slice); + } catch (TransactionTooLargeException e) { + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Transaction too large for " + args.size() + + " args, first: " + args.get(0).args); + Slog.w(TAG, "Failed delivering service starts", e); + caughtException = e; + } catch (RemoteException e) { + // Remote process gone... we'll let the normal cleanup take care of this. + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while sending args: " + r); + Slog.w(TAG, "Failed delivering service starts", e); + caughtException = e; + } catch (Exception e) { + Slog.w(TAG, "Unexpected exception", e); + caughtException = e; + } + + if (caughtException != null) { + // Keep nesting count correct + final boolean inDestroying = mDestroyingServices.contains(r); + for (int i = 0; i < args.size(); i++) { serviceDoneExecutingLocked(r, inDestroying, inDestroying); - if (caughtException instanceof TransactionTooLargeException) { - throw (TransactionTooLargeException)caughtException; - } - break; + } + if (caughtException instanceof TransactionTooLargeException) { + throw (TransactionTooLargeException)caughtException; } } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 19fc2b876c3e..ee2fdba71576 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -12143,6 +12143,24 @@ public class ActivityManagerService extends IActivityManager.Stub return false; } + @Override + public void backgroundWhitelistUid(final int uid) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Only the OS may call backgroundWhitelistUid()"); + } + + if (DEBUG_BACKGROUND_CHECK) { + Slog.i(TAG, "Adding uid " + uid + " to bg uid whitelist"); + } + synchronized (this) { + final int N = mBackgroundUidWhitelist.length; + int[] newList = new int[N+1]; + System.arraycopy(mBackgroundUidWhitelist, 0, newList, 0, N); + newList[N] = uid; + mBackgroundUidWhitelist = newList; + } + } + final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated, String abiOverride) { ProcessRecord app; diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index b72cd73e85cf..3e3fee54bdd0 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -745,7 +745,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } /** - * Returns a {@link TaskRecord} for the input id if available. Null otherwise. + * Returns a {@link TaskRecord} for the input id if available. {@code null} otherwise. * @param id Id of the task we would like returned. * @param matchMode The mode to match the given task id in. * @param stackId The stack to restore the task to (default launch stack will be used if @@ -765,7 +765,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { ActivityStack stack = stacks.get(stackNdx); - TaskRecord task = stack.taskForIdLocked(id); + final TaskRecord task = stack.taskForIdLocked(id); if (task != null) { return task; } @@ -780,11 +780,17 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // Otherwise, check the recent tasks and return if we find it there and we are not restoring // the task from recents if (DEBUG_RECENTS) Slog.v(TAG_RECENTS, "Looking for task id=" + id + " in recents"); - TaskRecord task = mRecentTasks.taskForIdLocked(id); - if (matchMode == MATCH_TASK_IN_STACKS_OR_RECENT_TASKS) { - if (DEBUG_RECENTS && task == null) { + final TaskRecord task = mRecentTasks.taskForIdLocked(id); + + if (task == null) { + if (DEBUG_RECENTS) { Slog.d(TAG_RECENTS, "\tDidn't find task id=" + id + " in recents"); } + + return null; + } + + if (matchMode == MATCH_TASK_IN_STACKS_OR_RECENT_TASKS) { return task; } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 3303081314a5..fe40efb7ef4a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -608,6 +608,10 @@ public class PackageManagerService extends IPackageManager.Stub { final boolean mIsPreNUpgrade; final boolean mIsPreNMR1Upgrade; + // Have we told the Activity Manager to whitelist the default container service by uid yet? + @GuardedBy("mPackages") + boolean mDefaultContainerWhitelisted = false; + @GuardedBy("mPackages") private boolean mDexOptDialogShown; @@ -3057,9 +3061,17 @@ public class PackageManagerService extends IPackageManager.Stub { | MATCH_DIRECT_BOOT_UNAWARE | (!Build.IS_DEBUGGABLE ? MATCH_SYSTEM_ONLY : 0); final Intent resolverIntent = new Intent(Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE); - final List<ResolveInfo> resolvers = queryIntentServicesInternal(resolverIntent, null, + List<ResolveInfo> resolvers = queryIntentServicesInternal(resolverIntent, null, resolveFlags, UserHandle.USER_SYSTEM, callingUid, false /*includeInstantApps*/); - + // temporarily look for the old action + if (resolvers.size() == 0) { + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "Ephemeral resolver not found with new action; try old one"); + } + resolverIntent.setAction(Intent.ACTION_RESOLVE_EPHEMERAL_PACKAGE); + resolvers = queryIntentServicesInternal(resolverIntent, null, + resolveFlags, UserHandle.USER_SYSTEM, callingUid, false /*includeInstantApps*/); + } final int N = resolvers.size(); if (N == 0) { if (DEBUG_EPHEMERAL) { @@ -3106,8 +3118,17 @@ public class PackageManagerService extends IPackageManager.Stub { MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | (!Build.IS_DEBUGGABLE ? MATCH_SYSTEM_ONLY : 0); - final List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE, + List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE, resolveFlags, UserHandle.USER_SYSTEM); + // temporarily look for the old action + if (matches.isEmpty()) { + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "Ephemeral installer not found with new action; try old one"); + } + intent.setAction(Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE); + matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE, + resolveFlags, UserHandle.USER_SYSTEM); + } Iterator<ResolveInfo> iter = matches.iterator(); while (iter.hasNext()) { final ResolveInfo rInfo = iter.next(); @@ -3136,8 +3157,17 @@ public class PackageManagerService extends IPackageManager.Stub { .addCategory(Intent.CATEGORY_DEFAULT) .setPackage(resolver.getPackageName()); final int resolveFlags = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE; - final List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, null, resolveFlags, + List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, null, resolveFlags, UserHandle.USER_SYSTEM); + // temporarily look for the old action + if (matches.isEmpty()) { + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "Ephemeral resolver settings not found with new action; try old one"); + } + intent.setAction(Intent.ACTION_EPHEMERAL_RESOLVER_SETTINGS); + matches = queryIntentActivitiesInternal(intent, null, resolveFlags, + UserHandle.USER_SYSTEM); + } if (matches.isEmpty()) { return null; } @@ -13024,7 +13054,18 @@ public class PackageManagerService extends IPackageManager.Stub { intent.setComponent(DEFAULT_CONTAINER_COMPONENT); IActivityManager am = ActivityManager.getService(); if (am != null) { + int dcsUid = -1; + synchronized (mPackages) { + if (!mDefaultContainerWhitelisted) { + mDefaultContainerWhitelisted = true; + PackageSetting ps = mSettings.mPackages.get(DEFAULT_CONTAINER_PACKAGE); + dcsUid = UserHandle.getUid(UserHandle.USER_SYSTEM, ps.appId); + } + } try { + if (dcsUid > 0) { + am.backgroundWhitelistUid(dcsUid); + } am.startService(null, intent, null, -1, null, false, mContext.getOpPackageName(), UserHandle.USER_SYSTEM); } catch (RemoteException e) { diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java index aa374073e4a5..5d0c23f81737 100644 --- a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java +++ b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java @@ -149,7 +149,7 @@ public class AccountsDbTest { // 2nd account Account account2 = new Account("name", "example2.com"); long accId2 = mAccountsDb.insertCeAccount(account2, "password"); - mAccountsDb.insertDeAccount(account2, accId); + mAccountsDb.insertDeAccount(account2, accId2); mAccountsDb.insertAuthToken(accId2, "type", "token"); mAccountsDb.deleteAuthTokensByAccountId(accId2); diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java index af3201c7aa97..54ecab3af542 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java @@ -33,8 +33,7 @@ import org.junit.Test; * bit FrameworksServicesTests:com.android.server.am.ActivityRecordTests */ @MediumTest -// TODO(b/36916522): Currently failing in CI. -// @Presubmit +@Presubmit @RunWith(AndroidJUnit4.class) public class ActivityRecordTests extends ActivityTestsBase { private final ComponentName testActivityComponent = diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java new file mode 100644 index 000000000000..b59c2bc93d73 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java @@ -0,0 +1,54 @@ +/* + * 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.server.am; + +import static org.junit.Assert.assertNull; + +import android.os.Debug; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.runner.RunWith; +import org.junit.Test; + +import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE; + +/** + * Tests for the {@link ActivityStackSupervisor} class. + * + * Build/Install/Run: + * bit FrameworksServicesTests:com.android.server.am.ActivityStackSupervisorTests + */ +@MediumTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class ActivityStackSupervisorTests extends ActivityTestsBase { + /** + * This test ensures that we do not try to restore a task based off an invalid task id. The + * stack supervisor is a test version so there will be no tasks present. We should expect + * {@code null} to be returned in this case. + */ + @Test + public void testRestoringInvalidTask() throws Exception { + Debug.waitForDebugger(); + final ActivityManagerService service = createActivityManagerService(); + TaskRecord task = service.mStackSupervisor.anyTaskForIdLocked(0 /*taskId*/, + MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, 0 /*stackId*/); + assertNull(task); + } +} diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java index c5cc2ff22abd..52405863e8fe 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java @@ -25,7 +25,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.graphics.Rect; -import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; import android.support.test.InstrumentationRegistry; import com.android.server.AttributeCache; @@ -34,6 +34,7 @@ import com.android.server.wm.StackWindowController; import com.android.server.wm.WindowManagerService; import com.android.server.wm.WindowTestUtils; +import org.junit.After; import org.junit.Before; import org.mockito.MockitoAnnotations; @@ -42,8 +43,7 @@ import org.mockito.MockitoAnnotations; */ public class ActivityTestsBase { private final Context mContext = InstrumentationRegistry.getContext(); - private static boolean sLooperPrepared; - private Handler mHandler; + private HandlerThread mHandlerThread; // Grabbing an instance of {@link WindowManagerService} creates it if not present so this must // be called at before any tests. @@ -52,11 +52,13 @@ public class ActivityTestsBase { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mHandlerThread = new HandlerThread("ActivityTestsBaseThread"); + mHandlerThread.start(); + } - if (!sLooperPrepared) { - sLooperPrepared = true; - Looper.prepare(); - } + @After + public void tearDown() { + mHandlerThread.quitSafely(); } protected ActivityManagerService createActivityManagerService() { @@ -126,7 +128,7 @@ public class ActivityTestsBase { @Override protected ActivityStackSupervisor createStackSupervisor() { - return new TestActivityStackSupervisor(this, new Handler().getLooper()); + return new TestActivityStackSupervisor(this, mHandlerThread.getLooper()); } } diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java index f3f68ff06e03..2663aaf35b3d 100644 --- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java @@ -50,6 +50,7 @@ import com.android.server.LocalServices; import org.mockito.ArgumentCaptor; +import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.concurrent.CountDownLatch; @@ -249,6 +250,25 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase { assertEquals(7, updates.size()); } + public void testGetInstalledProvidersForPackage() { + List<AppWidgetProviderInfo> allProviders = mManager.getInstalledProviders(); + assertTrue(!allProviders.isEmpty()); + String packageName = allProviders.get(0).provider.getPackageName(); + List<AppWidgetProviderInfo> providersForPackage = mManager.getInstalledProvidersForPackage( + packageName, null); + // Remove providers from allProviders that don't have the given package name. + Iterator<AppWidgetProviderInfo> iter = allProviders.iterator(); + while (iter.hasNext()) { + if (!iter.next().provider.getPackageName().equals(packageName)) { + iter.remove(); + } + } + assertEquals(allProviders.size(), providersForPackage.size()); + for (int i = 0; i < allProviders.size(); i++) { + assertEquals(allProviders.get(i).provider, providersForPackage.get(i).provider); + } + } + private int setupHostAndWidget() { List<PendingHostUpdate> updates = mService.startListening( mMockHost, mPkgName, HOST_ID, new int[0]).getList(); diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 92233b1fed39..f80ee7386f99 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -356,6 +356,7 @@ public final class Call { private final StatusHints mStatusHints; private final Bundle mExtras; private final Bundle mIntentExtras; + private final long mCreationTimeMillis; /** * Whether the supplied capabilities supports the specified capability. @@ -578,9 +579,12 @@ public final class Call { } /** - * @return The time the {@code Call} has been connected. This information is updated - * periodically, but user interfaces should not rely on this to display any "call time - * clock". + * Returns the time the {@link Call} connected (i.e. became active). This information is + * updated periodically, but user interfaces should not rely on this to display the "call + * time clock". For the time when the call was first added to Telecom, see + * {@link #getCreationTimeMillis()}. + * + * @return The time the {@link Call} connected in milliseconds since the epoch. */ public final long getConnectTimeMillis() { return mConnectTimeMillis; @@ -622,6 +626,18 @@ public final class Call { return mIntentExtras; } + /** + * Returns the time when the call was first created and added to Telecom. This is the same + * time that is logged as the start time in the Call Log (see + * {@link android.provider.CallLog.Calls#DATE}). To determine when the call was connected + * (became active), see {@link #getConnectTimeMillis()}. + * + * @return The creation time of the call, in millis since the epoch. + */ + public long getCreationTimeMillis() { + return mCreationTimeMillis; + } + @Override public boolean equals(Object o) { if (o instanceof Details) { @@ -641,28 +657,29 @@ public final class Call { Objects.equals(mVideoState, d.mVideoState) && Objects.equals(mStatusHints, d.mStatusHints) && areBundlesEqual(mExtras, d.mExtras) && - areBundlesEqual(mIntentExtras, d.mIntentExtras); + areBundlesEqual(mIntentExtras, d.mIntentExtras) && + Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis); } return false; } @Override public int hashCode() { - return - Objects.hashCode(mHandle) + - Objects.hashCode(mHandlePresentation) + - Objects.hashCode(mCallerDisplayName) + - Objects.hashCode(mCallerDisplayNamePresentation) + - Objects.hashCode(mAccountHandle) + - Objects.hashCode(mCallCapabilities) + - Objects.hashCode(mCallProperties) + - Objects.hashCode(mDisconnectCause) + - Objects.hashCode(mConnectTimeMillis) + - Objects.hashCode(mGatewayInfo) + - Objects.hashCode(mVideoState) + - Objects.hashCode(mStatusHints) + - Objects.hashCode(mExtras) + - Objects.hashCode(mIntentExtras); + return Objects.hash(mHandle, + mHandlePresentation, + mCallerDisplayName, + mCallerDisplayNamePresentation, + mAccountHandle, + mCallCapabilities, + mCallProperties, + mDisconnectCause, + mConnectTimeMillis, + mGatewayInfo, + mVideoState, + mStatusHints, + mExtras, + mIntentExtras, + mCreationTimeMillis); } /** {@hide} */ @@ -681,7 +698,8 @@ public final class Call { int videoState, StatusHints statusHints, Bundle extras, - Bundle intentExtras) { + Bundle intentExtras, + long creationTimeMillis) { mTelecomCallId = telecomCallId; mHandle = handle; mHandlePresentation = handlePresentation; @@ -697,6 +715,7 @@ public final class Call { mStatusHints = statusHints; mExtras = extras; mIntentExtras = intentExtras; + mCreationTimeMillis = creationTimeMillis; } /** {@hide} */ @@ -716,7 +735,8 @@ public final class Call { parcelableCall.getVideoState(), parcelableCall.getStatusHints(), parcelableCall.getExtras(), - parcelableCall.getIntentExtras()); + parcelableCall.getIntentExtras(), + parcelableCall.getCreationTimeMillis()); } @Override diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java index 85a92d1a135a..6212a77feba8 100644 --- a/telecomm/java/android/telecom/ParcelableCall.java +++ b/telecomm/java/android/telecom/ParcelableCall.java @@ -59,6 +59,7 @@ public final class ParcelableCall implements Parcelable { private final List<String> mConferenceableCallIds; private final Bundle mIntentExtras; private final Bundle mExtras; + private final long mCreationTimeMillis; public ParcelableCall( String id, @@ -85,7 +86,8 @@ public final class ParcelableCall implements Parcelable { int videoState, List<String> conferenceableCallIds, Bundle intentExtras, - Bundle extras) { + Bundle extras, + long creationTimeMillis) { mId = id; mState = state; mDisconnectCause = disconnectCause; @@ -111,6 +113,7 @@ public final class ParcelableCall implements Parcelable { mConferenceableCallIds = Collections.unmodifiableList(conferenceableCallIds); mIntentExtras = intentExtras; mExtras = extras; + mCreationTimeMillis = creationTimeMillis; } /** The unique ID of the call. */ @@ -289,6 +292,13 @@ public final class ParcelableCall implements Parcelable { return mIsVideoCallProviderChanged; } + /** + * @return The time the call was created, in milliseconds since the epoch. + */ + public long getCreationTimeMillis() { + return mCreationTimeMillis; + } + /** Responsible for creating ParcelableCall objects for deserialized Parcels. */ public static final Parcelable.Creator<ParcelableCall> CREATOR = new Parcelable.Creator<ParcelableCall> () { @@ -324,6 +334,7 @@ public final class ParcelableCall implements Parcelable { int supportedAudioRoutes = source.readInt(); boolean isRttCallChanged = source.readByte() == 1; ParcelableRttCall rttCall = source.readParcelable(classLoader); + long creationTimeMillis = source.readLong(); return new ParcelableCall( id, state, @@ -349,7 +360,8 @@ public final class ParcelableCall implements Parcelable { videoState, conferenceableCallIds, intentExtras, - extras); + extras, + creationTimeMillis); } @Override @@ -393,6 +405,7 @@ public final class ParcelableCall implements Parcelable { destination.writeInt(mSupportedAudioRoutes); destination.writeByte((byte) (mIsRttCallChanged ? 1 : 0)); destination.writeParcelable(mRttCall, 0); + destination.writeLong(mCreationTimeMillis); } @Override diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 8461905d8034..90f713b67985 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -155,7 +155,10 @@ bool ResourceParser::FlattenXmlSubtree( xml::XmlPullParser* parser, std::string* out_raw_string, StyleString* out_style_string, std::vector<UntranslatableSection>* out_untranslatable_sections) { // Keeps track of formatting tags (<b>, <i>) and the range of characters for which they apply. - std::vector<Span> span_stack; + // The stack elements refer to the indices in out_style_string->spans. + // By first adding to the out_style_string->spans vector, and then using the stack to refer + // to this vector, the original order of tags is preserved in cases such as <b><i>hello</b></i>. + std::vector<size_t> span_stack; // Clear the output variables. out_raw_string->clear(); @@ -192,7 +195,9 @@ bool ResourceParser::FlattenXmlSubtree( return false; } - span_stack.push_back(Span{std::move(span_name), static_cast<uint32_t>(builder.Utf16Len())}); + out_style_string->spans.push_back( + Span{std::move(span_name), static_cast<uint32_t>(builder.Utf16Len())}); + span_stack.push_back(out_style_string->spans.size() - 1); } else if (parser->element_namespace() == sXliffNamespaceUri) { if (parser->element_name() == "g") { if (untranslatable_start_depth) { @@ -233,9 +238,8 @@ bool ResourceParser::FlattenXmlSubtree( if (parser->element_namespace().empty()) { // This is an HTML tag which we encode as a span. Update the span // stack and pop the top entry. - Span& top_span = span_stack.back(); + Span& top_span = out_style_string->spans[span_stack.back()]; top_span.last_char = builder.Utf16Len() - 1; - out_style_string->spans.push_back(std::move(top_span)); span_stack.pop_back(); } else if (untranslatable_start_depth == make_value(depth)) { // This is the end of an untranslatable section. Use UTF8 indices/lengths. diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index eefa320a4418..8062c2e6afea 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -101,20 +101,24 @@ TEST_F(ResourceParserTest, ParseStyledString) { // Use a surrogate pair unicode point so that we can verify that the span // indices use UTF-16 length and not UTF-8 length. std::string input = - "<string name=\"foo\">This is my aunt\u2019s <b>string</b></string>"; + "<string name=\"foo\">This is my aunt\u2019s <b>fickle <small>string</small></b></string>"; ASSERT_TRUE(TestParse(input)); StyledString* str = test::GetValue<StyledString>(&table_, "string/foo"); ASSERT_NE(nullptr, str); - const std::string expected_str = "This is my aunt\u2019s string"; + const std::string expected_str = "This is my aunt\u2019s fickle string"; EXPECT_EQ(expected_str, *str->value->str); - EXPECT_EQ(1u, str->value->spans.size()); + EXPECT_EQ(2u, str->value->spans.size()); EXPECT_TRUE(str->untranslatable_sections.empty()); EXPECT_EQ(std::string("b"), *str->value->spans[0].name); EXPECT_EQ(17u, str->value->spans[0].first_char); - EXPECT_EQ(23u, str->value->spans[0].last_char); + EXPECT_EQ(30u, str->value->spans[0].last_char); + + EXPECT_EQ(std::string("small"), *str->value->spans[1].name); + EXPECT_EQ(24u, str->value->spans[1].first_char); + EXPECT_EQ(30u, str->value->spans[1].last_char); } TEST_F(ResourceParserTest, ParseStringWithWhitespace) { diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp index fad9edd04e4c..a031ea4c31ec 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator.cpp +++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp @@ -22,136 +22,194 @@ #include "ResourceValues.h" #include "ValueVisitor.h" #include "compile/Pseudolocalizer.h" +#include "util/Util.h" using android::StringPiece; +using android::StringPiece16; namespace aapt { -std::unique_ptr<StyledString> PseudolocalizeStyledString( - StyledString* string, Pseudolocalizer::Method method, StringPool* pool) { - Pseudolocalizer localizer(method); +// The struct that represents both Span objects and UntranslatableSections. +struct UnifiedSpan { + // Only present for Span objects. If not present, this was an UntranslatableSection. + Maybe<std::string> tag; - const StringPiece original_text = *string->value->str; + // The UTF-16 index into the string where this span starts. + uint32_t first_char; - StyleString localized; + // The UTF-16 index into the string where this span ends, inclusive. + uint32_t last_char; +}; - // Copy the spans. We will update their offsets when we localize. - localized.spans.reserve(string->value->spans.size()); - for (const StringPool::Span& span : string->value->spans) { - localized.spans.push_back( - Span{*span.name, span.first_char, span.last_char}); +inline static bool operator<(const UnifiedSpan& left, const UnifiedSpan& right) { + if (left.first_char < right.first_char) { + return true; + } else if (left.first_char > right.first_char) { + return false; + } else if (left.last_char < right.last_char) { + return true; } + return false; +} - // The ranges are all represented with a single value. This is the start of - // one range and end of another. - struct Range { - size_t start; - - // If set to true, toggles the state of translatability. - bool toggle_translatability; - - // Once the new string is localized, these are the pointers to the spans to adjust. - // Since this struct represents the start of one range and end of another, - // we have the two pointers respectively. - uint32_t* update_start; - uint32_t* update_end; - }; - - auto cmp = [](const Range& r, size_t index) -> bool { - return r.start < index; - }; - - // Construct the ranges. The ranges are represented like so: [0, 2, 5, 7] - // The ranges are the spaces in between. In this example, with a total string - // length of 9, the vector represents: (0,1], (2,4], (5,6], (7,9] - // - std::vector<Range> ranges; - ranges.push_back(Range{0, false, nullptr, nullptr}); - ranges.push_back(Range{original_text.size() - 1, false, nullptr, nullptr}); - for (size_t i = 0; i < string->value->spans.size(); i++) { - const StringPool::Span& span = string->value->spans[i]; - - // Insert or update the Range marker for the start of this span. - auto iter = - std::lower_bound(ranges.begin(), ranges.end(), span.first_char, cmp); - if (iter != ranges.end() && iter->start == span.first_char) { - iter->update_start = &localized.spans[i].first_char; - } else { - ranges.insert(iter, Range{span.first_char, false, &localized.spans[i].first_char, nullptr}); - } +inline static UnifiedSpan SpanToUnifiedSpan(const StringPool::Span& span) { + return UnifiedSpan{*span.name, span.first_char, span.last_char}; +} + +inline static UnifiedSpan UntranslatableSectionToUnifiedSpan(const UntranslatableSection& section) { + return UnifiedSpan{ + {}, static_cast<uint32_t>(section.start), static_cast<uint32_t>(section.end) - 1}; +} - // Insert or update the Range marker for the end of this span. - iter = std::lower_bound(ranges.begin(), ranges.end(), span.last_char, cmp); - if (iter != ranges.end() && iter->start == span.last_char) { - iter->update_end = &localized.spans[i].last_char; +// Merges the Span and UntranslatableSections of this StyledString into a single vector of +// UnifiedSpans. This will first check that the Spans are sorted in ascending order. +static std::vector<UnifiedSpan> MergeSpans(const StyledString& string) { + // Ensure the Spans are sorted and converted. + std::vector<UnifiedSpan> sorted_spans; + sorted_spans.reserve(string.value->spans.size()); + std::transform(string.value->spans.begin(), string.value->spans.end(), + std::back_inserter(sorted_spans), SpanToUnifiedSpan); + + // Stable sort to ensure tag sequences like "<b><i>" are preserved. + std::stable_sort(sorted_spans.begin(), sorted_spans.end()); + + // Ensure the UntranslatableSections are sorted and converted. + std::vector<UnifiedSpan> sorted_untranslatable_sections; + sorted_untranslatable_sections.reserve(string.untranslatable_sections.size()); + std::transform(string.untranslatable_sections.begin(), string.untranslatable_sections.end(), + std::back_inserter(sorted_untranslatable_sections), + UntranslatableSectionToUnifiedSpan); + std::sort(sorted_untranslatable_sections.begin(), sorted_untranslatable_sections.end()); + + std::vector<UnifiedSpan> merged_spans; + merged_spans.reserve(sorted_spans.size() + sorted_untranslatable_sections.size()); + auto span_iter = sorted_spans.begin(); + auto untranslatable_iter = sorted_untranslatable_sections.begin(); + while (span_iter != sorted_spans.end() && + untranslatable_iter != sorted_untranslatable_sections.end()) { + if (*span_iter < *untranslatable_iter) { + merged_spans.push_back(std::move(*span_iter)); + ++span_iter; } else { - ranges.insert(iter, Range{span.last_char, false, nullptr, &localized.spans[i].last_char}); + merged_spans.push_back(std::move(*untranslatable_iter)); + ++untranslatable_iter; } } - // Parts of the string may be untranslatable. Merge those ranges - // in as well, so that we have continuous sections of text to - // feed into the pseudolocalizer. - // We do this by marking the beginning of a range as either toggling - // the translatability state or not. - for (const UntranslatableSection& section : string->untranslatable_sections) { - auto iter = std::lower_bound(ranges.begin(), ranges.end(), section.start, cmp); - if (iter != ranges.end() && iter->start == section.start) { - // An existing span starts (or ends) here. We just need to mark that - // the translatability should toggle here. If translatability was - // already being toggled, then that means we have two adjacent ranges of untranslatable - // text, so remove the toggle and only toggle at the end of this range, - // effectively merging these ranges. - iter->toggle_translatability = !iter->toggle_translatability; - } else { - // Insert a new range that specifies to toggle the translatability. - iter = ranges.insert(iter, Range{section.start, true, nullptr, nullptr}); - } + while (span_iter != sorted_spans.end()) { + merged_spans.push_back(std::move(*span_iter)); + ++span_iter; + } - // Update/create an end to the untranslatable section. - iter = std::lower_bound(iter, ranges.end(), section.end, cmp); - if (iter != ranges.end() && iter->start == section.end) { - iter->toggle_translatability = true; - } else { - iter = ranges.insert(iter, Range{section.end, true, nullptr, nullptr}); - } + while (untranslatable_iter != sorted_untranslatable_sections.end()) { + merged_spans.push_back(std::move(*untranslatable_iter)); + ++untranslatable_iter; } + return merged_spans; +} - localized.str += localizer.Start(); +std::unique_ptr<StyledString> PseudolocalizeStyledString(StyledString* string, + Pseudolocalizer::Method method, + StringPool* pool) { + Pseudolocalizer localizer(method); - // Iterate over the ranges and localize each section. - // The text starts as translatable, and each time a range has toggle_translatability - // set to true, we toggle whether to translate or not. - // This assumes no untranslatable ranges overlap. - bool translatable = true; - for (size_t i = 0; i < ranges.size(); i++) { - const size_t start = ranges[i].start; - size_t len = original_text.size() - start; - if (i + 1 < ranges.size()) { - len = ranges[i + 1].start - start; - } + // Collect the spans and untranslatable sections into one set of spans, sorted by first_char. + // This will effectively subdivide the string into multiple sections that can be individually + // pseudolocalized, while keeping the span indices synchronized. + std::vector<UnifiedSpan> merged_spans = MergeSpans(*string); - if (ranges[i].update_start) { - *ranges[i].update_start = localized.str.size(); - } + // All Span indices are UTF-16 based, according to the resources.arsc format expected by the + // runtime. So we will do all our processing in UTF-16, then convert back. + const std::u16string text16 = util::Utf8ToUtf16(*string->value->str); - if (ranges[i].update_end) { - *ranges[i].update_end = localized.str.size(); - } + // Convenient wrapper around the text that allows us to work with StringPieces. + const StringPiece16 text(text16); + + // The new string. + std::string new_string = localizer.Start(); + + // The stack that keeps track of what nested Span we're in. + std::vector<size_t> span_stack; + + // The current position in the original text. + uint32_t cursor = 0u; + + // The current position in the new text. + uint32_t new_cursor = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(new_string.data()), + new_string.size(), false); - if (ranges[i].toggle_translatability) { - translatable = !translatable; + // We assume no nesting of untranslatable sections, since XLIFF doesn't allow it. + bool translatable = true; + size_t span_idx = 0u; + while (span_idx < merged_spans.size() || !span_stack.empty()) { + UnifiedSpan* span = span_idx >= merged_spans.size() ? nullptr : &merged_spans[span_idx]; + UnifiedSpan* parent_span = span_stack.empty() ? nullptr : &merged_spans[span_stack.back()]; + + if (span != nullptr) { + if (parent_span == nullptr || parent_span->last_char > span->first_char) { + // There is no parent, or this span is the child of the parent. + // Pseudolocalize all the text until this span. + const StringPiece16 substr = text.substr(cursor, span->first_char - cursor); + cursor += substr.size(); + + // Pseudolocalize the substring. + std::string new_substr = util::Utf16ToUtf8(substr); + if (translatable) { + new_substr = localizer.Text(new_substr); + } + new_cursor += utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(new_substr.data()), + new_substr.size(), false); + new_string += new_substr; + + // Rewrite the first_char. + span->first_char = new_cursor; + if (!span->tag) { + // An untranslatable section has begun! + translatable = false; + } + span_stack.push_back(span_idx); + ++span_idx; + continue; + } } - if (translatable) { - localized.str += localizer.Text(original_text.substr(start, len)); - } else { - localized.str += original_text.substr(start, len); + if (parent_span != nullptr) { + // There is a parent, and either this span is not a child of it, or there are no more spans. + // Pop this off the stack. + const StringPiece16 substr = text.substr(cursor, parent_span->last_char - cursor + 1); + cursor += substr.size(); + + // Pseudolocalize the substring. + std::string new_substr = util::Utf16ToUtf8(substr); + if (translatable) { + new_substr = localizer.Text(new_substr); + } + new_cursor += utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(new_substr.data()), + new_substr.size(), false); + new_string += new_substr; + + parent_span->last_char = new_cursor - 1; + if (parent_span->tag) { + // An end to an untranslatable section. + translatable = true; + } + span_stack.pop_back(); } } - localized.str += localizer.End(); + // Finish the pseudolocalization at the end of the string. + new_string += localizer.Text(util::Utf16ToUtf8(text.substr(cursor, text.size() - cursor))); + new_string += localizer.End(); + + StyleString localized; + localized.str = std::move(new_string); + // Convert the UnifiedSpans into regular Spans, skipping the UntranslatableSections. + for (UnifiedSpan& span : merged_spans) { + if (span.tag) { + localized.spans.push_back(Span{std::move(span.tag.value()), span.first_char, span.last_char}); + } + } return util::make_unique<StyledString>(pool->MakeRef(localized)); } @@ -175,8 +233,7 @@ class Visitor : public RawValueVisitor { if (sub_visitor.value) { localized->values[i] = std::move(sub_visitor.item); } else { - localized->values[i] = - std::unique_ptr<Item>(plural->values[i]->Clone(pool_)); + localized->values[i] = std::unique_ptr<Item>(plural->values[i]->Clone(pool_)); } } } @@ -210,8 +267,7 @@ class Visitor : public RawValueVisitor { } result += localizer_.End(); - std::unique_ptr<String> localized = - util::make_unique<String>(pool_->MakeRef(result)); + std::unique_ptr<String> localized = util::make_unique<String>(pool_->MakeRef(result)); localized->SetSource(string->GetSource()); localized->SetWeak(true); item = std::move(localized); @@ -282,14 +338,10 @@ void PseudolocalizeIfNeeded(const Pseudolocalizer::Method method, } } -/** - * A value is pseudolocalizable if it does not define a locale (or is the - * default locale) - * and is translatable. - */ +// A value is pseudolocalizable if it does not define a locale (or is the default locale) and is +// translatable. static bool IsPseudolocalizable(ResourceConfigValue* config_value) { - const int diff = - config_value->config.diff(ConfigDescription::DefaultConfig()); + const int diff = config_value->config.diff(ConfigDescription::DefaultConfig()); if (diff & ConfigDescription::CONFIG_LOCALE) { return false; } @@ -298,19 +350,16 @@ static bool IsPseudolocalizable(ResourceConfigValue* config_value) { } // namespace -bool PseudolocaleGenerator::Consume(IAaptContext* context, - ResourceTable* table) { +bool PseudolocaleGenerator::Consume(IAaptContext* context, ResourceTable* table) { for (auto& package : table->packages) { for (auto& type : package->types) { for (auto& entry : type->entries) { - std::vector<ResourceConfigValue*> values = - entry->FindValuesIf(IsPseudolocalizable); - + std::vector<ResourceConfigValue*> values = entry->FindValuesIf(IsPseudolocalizable); for (ResourceConfigValue* value : values) { - PseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value, - &table->string_pool, entry.get()); - PseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value, - &table->string_pool, entry.get()); + PseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value, &table->string_pool, + entry.get()); + PseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value, &table->string_pool, + entry.get()); } } } diff --git a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp index 4db37db55eb7..b08e1dab35a9 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp +++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp @@ -25,7 +25,7 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeStyledString) { StringPool pool; StyleString original_style; original_style.str = "Hello world!"; - original_style.spans = {Span{"b", 2, 3}, Span{"b", 6, 7}, Span{"i", 1, 10}}; + original_style.spans = {Span{"i", 1, 10}, Span{"b", 2, 3}, Span{"b", 6, 7}}; std::unique_ptr<StyledString> new_string = PseudolocalizeStyledString( util::make_unique<StyledString>(pool.MakeRef(original_style)).get(), @@ -34,22 +34,19 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeStyledString) { EXPECT_EQ(original_style.str, *new_string->value->str); ASSERT_EQ(original_style.spans.size(), new_string->value->spans.size()); - EXPECT_EQ(std::string("He").size(), new_string->value->spans[0].first_char); - EXPECT_EQ(std::string("Hel").size(), new_string->value->spans[0].last_char); - EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name); + EXPECT_EQ(std::string("i"), *new_string->value->spans[0].name); + EXPECT_EQ(std::u16string(u"H").size(), new_string->value->spans[0].first_char); + EXPECT_EQ(std::u16string(u"Hello worl").size(), new_string->value->spans[0].last_char); - EXPECT_EQ(std::string("Hello ").size(), - new_string->value->spans[1].first_char); - EXPECT_EQ(std::string("Hello w").size(), - new_string->value->spans[1].last_char); EXPECT_EQ(std::string("b"), *new_string->value->spans[1].name); + EXPECT_EQ(std::u16string(u"He").size(), new_string->value->spans[1].first_char); + EXPECT_EQ(std::u16string(u"Hel").size(), new_string->value->spans[1].last_char); - EXPECT_EQ(std::string("H").size(), new_string->value->spans[2].first_char); - EXPECT_EQ(std::string("Hello worl").size(), - new_string->value->spans[2].last_char); - EXPECT_EQ(std::string("i"), *new_string->value->spans[2].name); + EXPECT_EQ(std::string("b"), *new_string->value->spans[2].name); + EXPECT_EQ(std::u16string(u"Hello ").size(), new_string->value->spans[2].first_char); + EXPECT_EQ(std::u16string(u"Hello w").size(), new_string->value->spans[2].last_char); - original_style.spans.push_back(Span{"em", 0, 11u}); + original_style.spans.insert(original_style.spans.begin(), Span{"em", 0, 11u}); new_string = PseudolocalizeStyledString( util::make_unique<StyledString>(pool.MakeRef(original_style)).get(), @@ -58,23 +55,128 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeStyledString) { EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð¡ one two]"), *new_string->value->str); ASSERT_EQ(original_style.spans.size(), new_string->value->spans.size()); - EXPECT_EQ(std::string("[Ĥé").size(), new_string->value->spans[0].first_char); - EXPECT_EQ(std::string("[Ĥéļ").size(), new_string->value->spans[0].last_char); + EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[0].first_char); + EXPECT_EQ(std::u16string(u"[Ĥéļļö ŵöŕļð").size(), new_string->value->spans[0].last_char); + + EXPECT_EQ(std::u16string(u"[Ĥ").size(), new_string->value->spans[1].first_char); + EXPECT_EQ(std::u16string(u"[Ĥéļļö ŵöŕļ").size(), new_string->value->spans[1].last_char); + + EXPECT_EQ(std::u16string(u"[Ĥé").size(), new_string->value->spans[2].first_char); + EXPECT_EQ(std::u16string(u"[Ĥéļ").size(), new_string->value->spans[2].last_char); + + EXPECT_EQ(std::u16string(u"[Ĥéļļö ").size(), new_string->value->spans[3].first_char); + EXPECT_EQ(std::u16string(u"[Ĥéļļö ŵ").size(), new_string->value->spans[3].last_char); +} + +TEST(PseudolocaleGeneratorTest, PseudolocalizeAdjacentNestedTags) { + StringPool pool; + StyleString original_style; + original_style.str = "bold"; + original_style.spans = {Span{"b", 0, 3}, Span{"i", 0, 3}}; + + std::unique_ptr<StyledString> new_string = PseudolocalizeStyledString( + util::make_unique<StyledString>(pool.MakeRef(original_style)).get(), + Pseudolocalizer::Method::kAccent, &pool); + ASSERT_NE(nullptr, new_string); + ASSERT_EQ(2u, new_string->value->spans.size()); + EXPECT_EQ(std::string("[ɓöļð one]"), *new_string->value->str); + + EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name); + EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[0].first_char); + EXPECT_EQ(std::u16string(u"[ɓöļ").size(), new_string->value->spans[0].last_char); + + EXPECT_EQ(std::string("i"), *new_string->value->spans[1].name); + EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[1].first_char); + EXPECT_EQ(std::u16string(u"[ɓöļ").size(), new_string->value->spans[1].last_char); +} + +TEST(PseudolocaleGeneratorTest, PseudolocalizeAdjacentTagsUnsorted) { + StringPool pool; + StyleString original_style; + original_style.str = "bold"; + original_style.spans = {Span{"i", 2, 3}, Span{"b", 0, 1}}; + + std::unique_ptr<StyledString> new_string = PseudolocalizeStyledString( + util::make_unique<StyledString>(pool.MakeRef(original_style)).get(), + Pseudolocalizer::Method::kAccent, &pool); + ASSERT_NE(nullptr, new_string); + ASSERT_EQ(2u, new_string->value->spans.size()); + EXPECT_EQ(std::string("[ɓöļð one]"), *new_string->value->str); + + EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name); + EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[0].first_char); + EXPECT_EQ(std::u16string(u"[ɓ").size(), new_string->value->spans[0].last_char); + + EXPECT_EQ(std::string("i"), *new_string->value->spans[1].name); + EXPECT_EQ(std::u16string(u"[ɓö").size(), new_string->value->spans[1].first_char); + EXPECT_EQ(std::u16string(u"[ɓöļ").size(), new_string->value->spans[1].last_char); +} + +TEST(PseudolocaleGeneratorTest, PseudolocalizeNestedAndAdjacentTags) { + StringPool pool; + StyleString original_style; + original_style.str = "This sentence is not what you think it is at all."; + original_style.spans = {Span{"b", 16u, 19u}, Span{"em", 29u, 47u}, Span{"i", 38u, 40u}, + Span{"b", 44u, 47u}}; + + std::unique_ptr<StyledString> new_string = PseudolocalizeStyledString( + util::make_unique<StyledString>(pool.MakeRef(original_style)).get(), + Pseudolocalizer::Method::kAccent, &pool); + ASSERT_NE(nullptr, new_string); + ASSERT_EQ(4u, new_string->value->spans.size()); + EXPECT_EQ(std::string( + "[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ îš åţ åļļ. one two three four five six]"), + *new_string->value->str); + + EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name); + EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš").size(), new_string->value->spans[0].first_char); + EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñö").size(), new_string->value->spans[0].last_char); - EXPECT_EQ(std::string("[Ĥéļļö ").size(), + EXPECT_EQ(std::string("em"), *new_string->value->spans[1].name); + EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû").size(), new_string->value->spans[1].first_char); - EXPECT_EQ(std::string("[Ĥéļļö ŵ").size(), + EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ îš åţ åļ").size(), new_string->value->spans[1].last_char); - EXPECT_EQ(std::string("[Ĥ").size(), new_string->value->spans[2].first_char); - EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļ").size(), + EXPECT_EQ(std::string("i"), *new_string->value->spans[2].name); + EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ").size(), + new_string->value->spans[2].first_char); + EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ î").size(), new_string->value->spans[2].last_char); - EXPECT_EQ(std::string("[").size(), new_string->value->spans[3].first_char); - EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð").size(), + EXPECT_EQ(std::string("b"), *new_string->value->spans[3].name); + EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ îš åţ").size(), + new_string->value->spans[3].first_char); + EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ îš åţ åļ").size(), new_string->value->spans[3].last_char); } +TEST(PseudolocaleGeneratorTest, PseudolocalizePartsOfString) { + StringPool pool; + StyleString original_style; + original_style.str = "This should NOT be pseudolocalized."; + original_style.spans = {Span{"em", 4u, 14u}, Span{"i", 18u, 33u}}; + std::unique_ptr<StyledString> original_string = + util::make_unique<StyledString>(pool.MakeRef(original_style)); + original_string->untranslatable_sections = {UntranslatableSection{11u, 15u}}; + + std::unique_ptr<StyledString> new_string = + PseudolocalizeStyledString(original_string.get(), Pseudolocalizer::Method::kAccent, &pool); + ASSERT_NE(nullptr, new_string); + ASSERT_EQ(2u, new_string->value->spans.size()); + EXPECT_EQ(std::string("[Ţĥîš šĥöûļð NOT ɓé þšéûðöļöçåļîžéð. one two three four]"), + *new_string->value->str); + + EXPECT_EQ(std::string("em"), *new_string->value->spans[0].name); + EXPECT_EQ(std::u16string(u"[Ţĥîš").size(), new_string->value->spans[0].first_char); + EXPECT_EQ(std::u16string(u"[Ţĥîš šĥöûļð NO").size(), new_string->value->spans[0].last_char); + + EXPECT_EQ(std::string("i"), *new_string->value->spans[1].name); + EXPECT_EQ(std::u16string(u"[Ţĥîš šĥöûļð NOT ɓé").size(), new_string->value->spans[1].first_char); + EXPECT_EQ(std::u16string(u"[Ţĥîš šĥöûļð NOT ɓé þšéûðöļöçåļîžé").size(), + new_string->value->spans[1].last_char); +} + TEST(PseudolocaleGeneratorTest, PseudolocalizeOnlyDefaultConfigs) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() @@ -138,7 +240,7 @@ TEST(PseudolocaleGeneratorTest, RespectUntranslateableSections) { { StyleString original_style; original_style.str = "Hello world!"; - original_style.spans = {Span{"b", 2, 3}, Span{"b", 6, 7}, Span{"i", 1, 10}}; + original_style.spans = {Span{"i", 1, 10}, Span{"b", 2, 3}, Span{"b", 6, 7}}; auto styled_string = util::make_unique<StyledString>(table->string_pool.MakeRef(original_style)); |