Merge "Camera: Initial support for vendor tag caches" into oc-dev
diff --git a/Android.mk b/Android.mk
index e27aa30..01fb73e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -224,6 +224,7 @@
core/java/android/net/IEthernetManager.aidl \
core/java/android/net/IEthernetServiceListener.aidl \
core/java/android/net/INetdEventCallback.aidl \
+ core/java/android/net/IIpSecService.aidl \
core/java/android/net/INetworkManagementEventObserver.aidl \
core/java/android/net/INetworkPolicyListener.aidl \
core/java/android/net/INetworkPolicyManager.aidl \
@@ -320,8 +321,6 @@
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 +729,6 @@
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 3c90844..c154fc9 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1609,6 +1609,7 @@
field public static final int alert_light_frame = 17301505; // 0x1080001
field public static final int arrow_down_float = 17301506; // 0x1080002
field public static final int arrow_up_float = 17301507; // 0x1080003
+ field public static final int autofilled_highlight = 17301684; // 0x10800b4
field public static final int bottom_bar = 17301658; // 0x108009a
field public static final int btn_default = 17301508; // 0x1080004
field public static final int btn_default_small = 17301509; // 0x1080005
@@ -3578,7 +3579,7 @@
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public void enterPictureInPictureMode();
method public boolean enterPictureInPictureMode(android.app.PictureInPictureArgs);
- method public android.view.View findViewById(int);
+ method public <T extends android.view.View> T findViewById(int);
method public void finish();
method public void finishActivity(int);
method public void finishActivityFromChild(android.app.Activity, int);
@@ -4358,7 +4359,7 @@
method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public boolean dispatchTouchEvent(android.view.MotionEvent);
method public boolean dispatchTrackballEvent(android.view.MotionEvent);
- method public android.view.View findViewById(int);
+ method public <T extends android.view.View> T findViewById(int);
method public android.app.ActionBar getActionBar();
method public final android.content.Context getContext();
method public android.view.View getCurrentFocus();
@@ -4771,7 +4772,7 @@
method public abstract android.app.FragmentManager.BackStackEntry getBackStackEntryAt(int);
method public abstract int getBackStackEntryCount();
method public abstract android.app.Fragment getFragment(android.os.Bundle, java.lang.String);
- method public abstract java.util.Collection<android.app.Fragment> getFragments();
+ method public abstract java.util.List<android.app.Fragment> getFragments();
method public abstract android.app.Fragment getPrimaryNavigationFragment();
method public void invalidateOptionsMenu();
method public abstract boolean isDestroyed();
@@ -7044,6 +7045,7 @@
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();
@@ -25556,7 +25558,7 @@
method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
method public void removeTransportModeTransform(java.net.Socket, android.net.IpSecTransform);
method public void removeTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform);
- method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+ method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
}
@@ -25590,7 +25592,6 @@
method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm);
method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm);
method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
- method public android.net.IpSecTransform.Builder setSpi(int, int);
method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex);
}
@@ -37051,7 +37052,7 @@
ctor public FillResponse.Builder();
method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset);
method public android.service.autofill.FillResponse build();
- method public android.service.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender, android.widget.RemoteViews);
+ method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews);
method public android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle);
method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo);
}
@@ -37067,8 +37068,10 @@
field public static final android.os.Parcelable.Creator<android.service.autofill.SaveInfo> CREATOR;
field public static final int SAVE_DATA_TYPE_ADDRESS = 2; // 0x2
field public static final int SAVE_DATA_TYPE_CREDIT_CARD = 3; // 0x3
+ field public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 5; // 0x5
field public static final int SAVE_DATA_TYPE_GENERIC = 0; // 0x0
field public static final int SAVE_DATA_TYPE_PASSWORD = 1; // 0x1
+ field public static final int SAVE_DATA_TYPE_USERNAME = 4; // 0x4
}
public static final class SaveInfo.Builder {
@@ -38750,6 +38753,7 @@
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();
@@ -46605,7 +46609,7 @@
method public void clearFlags(int);
method public abstract void closeAllPanels();
method public abstract void closePanel(int);
- method public android.view.View findViewById(int);
+ method public <T extends android.view.View> T findViewById(int);
method public boolean getAllowEnterTransitionOverlap();
method public boolean getAllowReturnTransitionOverlap();
method public final android.view.WindowManager.LayoutParams getAttributes();
diff --git a/api/system-current.txt b/api/system-current.txt
index 6a14b15..6c34d40 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -53,7 +53,6 @@
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";
@@ -1728,6 +1727,7 @@
field public static final int alert_light_frame = 17301505; // 0x1080001
field public static final int arrow_down_float = 17301506; // 0x1080002
field public static final int arrow_up_float = 17301507; // 0x1080003
+ field public static final int autofilled_highlight = 17301684; // 0x10800b4
field public static final int bottom_bar = 17301658; // 0x108009a
field public static final int btn_default = 17301508; // 0x1080004
field public static final int btn_default_small = 17301509; // 0x1080005
@@ -3703,7 +3703,7 @@
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public void enterPictureInPictureMode();
method public boolean enterPictureInPictureMode(android.app.PictureInPictureArgs);
- method public android.view.View findViewById(int);
+ method public <T extends android.view.View> T findViewById(int);
method public void finish();
method public void finishActivity(int);
method public void finishActivityFromChild(android.app.Activity, int);
@@ -4510,7 +4510,7 @@
method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public boolean dispatchTouchEvent(android.view.MotionEvent);
method public boolean dispatchTrackballEvent(android.view.MotionEvent);
- method public android.view.View findViewById(int);
+ method public <T extends android.view.View> T findViewById(int);
method public android.app.ActionBar getActionBar();
method public final android.content.Context getContext();
method public android.view.View getCurrentFocus();
@@ -4932,7 +4932,7 @@
method public abstract android.app.FragmentManager.BackStackEntry getBackStackEntryAt(int);
method public abstract int getBackStackEntryCount();
method public abstract android.app.Fragment getFragment(android.os.Bundle, java.lang.String);
- method public abstract java.util.Collection<android.app.Fragment> getFragments();
+ method public abstract java.util.List<android.app.Fragment> getFragments();
method public abstract android.app.Fragment getPrimaryNavigationFragment();
method public void invalidateOptionsMenu();
method public abstract boolean isDestroyed();
@@ -7504,6 +7504,7 @@
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();
@@ -27718,13 +27719,11 @@
public final class IpSecManager {
method public void applyTransportModeTransform(java.net.Socket, android.net.IpSecTransform) throws java.io.IOException;
method public void applyTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform) throws java.io.IOException;
- method public void applyTunnelModeTransform(android.net.Network, android.net.IpSecTransform);
method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
method public void removeTransportModeTransform(java.net.Socket, android.net.IpSecTransform);
method public void removeTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform);
- method public void removeTunnelModeTransform(android.net.Network, android.net.IpSecTransform);
- method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+ method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
}
@@ -27755,12 +27754,10 @@
public static class IpSecTransform.Builder {
ctor public IpSecTransform.Builder(android.content.Context);
method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
- method public android.net.IpSecTransform buildTunnelModeTransform(java.net.InetAddress, java.net.InetAddress);
method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm);
method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm);
method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
method public android.net.IpSecTransform.Builder setNattKeepalive(int);
- method public android.net.IpSecTransform.Builder setSpi(int, int);
method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex);
method public android.net.IpSecTransform.Builder setUnderlyingNetwork(android.net.Network);
}
@@ -40117,7 +40114,7 @@
ctor public FillResponse.Builder();
method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset);
method public android.service.autofill.FillResponse build();
- method public android.service.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender, android.widget.RemoteViews);
+ method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews);
method public android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle);
method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo);
}
@@ -40133,8 +40130,10 @@
field public static final android.os.Parcelable.Creator<android.service.autofill.SaveInfo> CREATOR;
field public static final int SAVE_DATA_TYPE_ADDRESS = 2; // 0x2
field public static final int SAVE_DATA_TYPE_CREDIT_CARD = 3; // 0x3
+ field public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 5; // 0x5
field public static final int SAVE_DATA_TYPE_GENERIC = 0; // 0x0
field public static final int SAVE_DATA_TYPE_PASSWORD = 1; // 0x1
+ field public static final int SAVE_DATA_TYPE_USERNAME = 4; // 0x4
}
public static final class SaveInfo.Builder {
@@ -40620,36 +40619,6 @@
}
-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 +41942,7 @@
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();
@@ -50129,7 +50099,7 @@
method public void clearFlags(int);
method public abstract void closeAllPanels();
method public abstract void closePanel(int);
- method public android.view.View findViewById(int);
+ method public <T extends android.view.View> T findViewById(int);
method public boolean getAllowEnterTransitionOverlap();
method public boolean getAllowReturnTransitionOverlap();
method public final android.view.WindowManager.LayoutParams getAttributes();
diff --git a/api/test-current.txt b/api/test-current.txt
index c5dc584..e41fde3 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1609,6 +1609,7 @@
field public static final int alert_light_frame = 17301505; // 0x1080001
field public static final int arrow_down_float = 17301506; // 0x1080002
field public static final int arrow_up_float = 17301507; // 0x1080003
+ field public static final int autofilled_highlight = 17301684; // 0x10800b4
field public static final int bottom_bar = 17301658; // 0x108009a
field public static final int btn_default = 17301508; // 0x1080004
field public static final int btn_default_small = 17301509; // 0x1080005
@@ -3580,7 +3581,7 @@
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public void enterPictureInPictureMode();
method public boolean enterPictureInPictureMode(android.app.PictureInPictureArgs);
- method public android.view.View findViewById(int);
+ method public <T extends android.view.View> T findViewById(int);
method public void finish();
method public void finishActivity(int);
method public void finishActivityFromChild(android.app.Activity, int);
@@ -4370,7 +4371,7 @@
method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public boolean dispatchTouchEvent(android.view.MotionEvent);
method public boolean dispatchTrackballEvent(android.view.MotionEvent);
- method public android.view.View findViewById(int);
+ method public <T extends android.view.View> T findViewById(int);
method public android.app.ActionBar getActionBar();
method public final android.content.Context getContext();
method public android.view.View getCurrentFocus();
@@ -4783,7 +4784,7 @@
method public abstract android.app.FragmentManager.BackStackEntry getBackStackEntryAt(int);
method public abstract int getBackStackEntryCount();
method public abstract android.app.Fragment getFragment(android.os.Bundle, java.lang.String);
- method public abstract java.util.Collection<android.app.Fragment> getFragments();
+ method public abstract java.util.List<android.app.Fragment> getFragments();
method public abstract android.app.Fragment getPrimaryNavigationFragment();
method public void invalidateOptionsMenu();
method public abstract boolean isDestroyed();
@@ -7074,6 +7075,7 @@
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();
@@ -25669,7 +25671,7 @@
method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
method public void removeTransportModeTransform(java.net.Socket, android.net.IpSecTransform);
method public void removeTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform);
- method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+ method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
}
@@ -25703,7 +25705,6 @@
method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm);
method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm);
method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
- method public android.net.IpSecTransform.Builder setSpi(int, int);
method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex);
}
@@ -37209,7 +37210,7 @@
ctor public FillResponse.Builder();
method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset);
method public android.service.autofill.FillResponse build();
- method public android.service.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender, android.widget.RemoteViews);
+ method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews);
method public android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle);
method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo);
}
@@ -37225,8 +37226,10 @@
field public static final android.os.Parcelable.Creator<android.service.autofill.SaveInfo> CREATOR;
field public static final int SAVE_DATA_TYPE_ADDRESS = 2; // 0x2
field public static final int SAVE_DATA_TYPE_CREDIT_CARD = 3; // 0x3
+ field public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 5; // 0x5
field public static final int SAVE_DATA_TYPE_GENERIC = 0; // 0x0
field public static final int SAVE_DATA_TYPE_PASSWORD = 1; // 0x1
+ field public static final int SAVE_DATA_TYPE_USERNAME = 4; // 0x4
}
public static final class SaveInfo.Builder {
@@ -38954,6 +38957,7 @@
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();
@@ -46036,6 +46040,7 @@
method public void setAnimation(android.view.animation.Animation);
method public void setAutofillHints(java.lang.String...);
method public void setAutofillMode(int);
+ method public void setAutofilled(boolean);
method public void setBackground(android.graphics.drawable.Drawable);
method public void setBackgroundColor(int);
method public deprecated void setBackgroundDrawable(android.graphics.drawable.Drawable);
@@ -46987,7 +46992,7 @@
method public void clearFlags(int);
method public abstract void closeAllPanels();
method public abstract void closePanel(int);
- method public android.view.View findViewById(int);
+ method public <T extends android.view.View> T findViewById(int);
method public boolean getAllowEnterTransitionOverlap();
method public boolean getAllowReturnTransitionOverlap();
method public final android.view.WindowManager.LayoutParams getAttributes();
diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
index 35011b5..8442585 100644
--- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java
+++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
@@ -40,8 +40,8 @@
import java.io.IOException;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
@@ -98,11 +98,10 @@
"alwaysPromptForAccount";
/**
- * If set then this string willb e used as the description rather than
+ * If set then this string will be used as the description rather than
* the default.
*/
- public static final String EXTRA_DESCRIPTION_TEXT_OVERRIDE =
- "descriptionTextOverride";
+ public static final String EXTRA_DESCRIPTION_TEXT_OVERRIDE = "descriptionTextOverride";
public static final int REQUEST_NULL = 0;
public static final int REQUEST_CHOOSE_TYPE = 1;
@@ -112,7 +111,8 @@
private static final String KEY_INSTANCE_STATE_EXISTING_ACCOUNTS = "existingAccounts";
private static final String KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME = "selectedAccountName";
private static final String KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT = "selectedAddAccount";
- private static final String KEY_INSTANCE_STATE_ACCOUNT_LIST = "accountAndVisibilityList";
+ private static final String KEY_INSTANCE_STATE_ACCOUNTS_LIST = "accountsList";
+ private static final String KEY_INSTANCE_STATE_VISIBILITY_LIST = "visibilityList";
private static final int SELECTED_ITEM_NONE = -1;
@@ -122,7 +122,7 @@
private boolean mSelectedAddNewAccount = false;
private String mDescriptionOverride;
- private Map<Account, Integer> mAccounts;
+ private LinkedHashMap<Account, Integer> mAccounts;
// TODO Redesign flow to show NOT_VISIBLE accounts
// and display a warning if they are selected.
// Currently NOT_VISBILE accounts are not shown at all.
@@ -164,6 +164,10 @@
// save some items we use frequently
final Intent intent = getIntent();
+ mSetOfAllowableAccounts = getAllowableAccountSet(intent);
+ mSetOfRelevantAccountTypes = getReleventAccountTypes(intent);
+ mDescriptionOverride = intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE);
+
if (savedInstanceState != null) {
mPendingRequest = savedInstanceState.getInt(KEY_INSTANCE_STATE_PENDING_REQUEST);
mExistingAccounts =
@@ -174,8 +178,15 @@
savedInstanceState.getString(KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME);
mSelectedAddNewAccount =
savedInstanceState.getBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false);
- mAccounts = (Map<Account, Integer>) savedInstanceState
- .getSerializable(KEY_INSTANCE_STATE_ACCOUNT_LIST);
+ // restore mAccounts
+ Parcelable[] accounts =
+ savedInstanceState.getParcelableArray(KEY_INSTANCE_STATE_ACCOUNTS_LIST);
+ ArrayList<Integer> visibility =
+ savedInstanceState.getIntegerArrayList(KEY_INSTANCE_STATE_VISIBILITY_LIST);
+ mAccounts = new LinkedHashMap<>();
+ for (int i = 0; i < accounts.length; i++) {
+ mAccounts.put((Account) accounts[i], visibility.get(i));
+ }
} else {
mPendingRequest = REQUEST_NULL;
mExistingAccounts = null;
@@ -185,20 +196,21 @@
if (selectedAccount != null) {
mSelectedAccountName = selectedAccount.name;
}
+ mAccounts = getAcceptableAccountChoices(AccountManager.get(this));
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "selected account name is " + mSelectedAccountName);
}
+ mPossiblyVisibleAccounts = new ArrayList<>(mAccounts.size());
+ for (Map.Entry<Account, Integer> entry : mAccounts.entrySet()) {
+ if (AccountManager.VISIBILITY_NOT_VISIBLE != entry.getValue()) {
+ mPossiblyVisibleAccounts.add(entry.getKey());
+ }
+ }
- mSetOfAllowableAccounts = getAllowableAccountSet(intent);
- mSetOfRelevantAccountTypes = getReleventAccountTypes(intent);
- mDescriptionOverride = intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE);
-
- mAccounts = getAcceptableAccountChoices(AccountManager.get(this));
- if (mAccounts.isEmpty()
- && mDisallowAddAccounts) {
+ if (mPossiblyVisibleAccounts.isEmpty() && mDisallowAddAccounts) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.app_not_authorized);
mDontShowPicker = true;
@@ -216,7 +228,7 @@
if (mPendingRequest == REQUEST_NULL) {
// If there are no relevant accounts and only one relevant account type go directly to
// add account. Otherwise let the user choose.
- if (mAccounts.isEmpty()) {
+ if (mPossiblyVisibleAccounts.isEmpty()) {
setNonLabelThemeAndCallSuperCreate(savedInstanceState);
if (mSetOfRelevantAccountTypes.size() == 1) {
runAddAccountForAuthenticator(mSetOfRelevantAccountTypes.iterator().next());
@@ -226,12 +238,6 @@
}
}
- mPossiblyVisibleAccounts = new ArrayList<>(mAccounts.size());
- for (Map.Entry<Account, Integer> entry : mAccounts.entrySet()) {
- if (AccountManager.VISIBILITY_NOT_VISIBLE != entry.getValue()) {
- mPossiblyVisibleAccounts.add(entry.getKey());
- }
- }
String[] listItems = getListOfDisplayableOptions(mPossiblyVisibleAccounts);
mSelectedItemIndex = getItemIndexToSelect(mPossiblyVisibleAccounts, mSelectedAccountName,
mSelectedAddNewAccount);
@@ -270,10 +276,16 @@
mPossiblyVisibleAccounts.get(mSelectedItemIndex).name);
}
}
- // should be HashMap by default.
- HashMap<Account, Integer> accountsHashMap = (mAccounts instanceof HashMap)
- ? (HashMap) mAccounts : new HashMap<Account, Integer>(mAccounts);
- outState.putSerializable(KEY_INSTANCE_STATE_ACCOUNT_LIST, accountsHashMap);
+ // save mAccounts
+ Parcelable[] accounts = new Parcelable[mAccounts.size()];
+ ArrayList<Integer> visibility = new ArrayList<>(mAccounts.size());
+ int i = 0;
+ for (Map.Entry<Account, Integer> e : mAccounts.entrySet()) {
+ accounts[i++] = e.getKey();
+ visibility.add(e.getValue());
+ }
+ outState.putParcelableArray(KEY_INSTANCE_STATE_ACCOUNTS_LIST, accounts);
+ outState.putIntegerArrayList(KEY_INSTANCE_STATE_VISIBILITY_LIST, visibility);
}
public void onCancelButtonClicked(View view) {
@@ -308,7 +320,7 @@
if (resultCode == RESULT_CANCELED) {
// if canceling out of addAccount and the original state caused us to skip this,
// finish this activity
- if (mAccounts.isEmpty()) {
+ if (mPossiblyVisibleAccounts.isEmpty()) {
setResult(Activity.RESULT_CANCELED);
finish();
}
@@ -428,18 +440,20 @@
private void setResultAndFinish(final String accountName, final String accountType) {
// Mark account as visible since user chose it.
Account account = new Account(accountName, accountType);
- Integer oldVisibility = mAccounts.get(account);
- // oldVisibility is null if new account was added
- if (oldVisibility == null) {
- Map<Account, Integer> accountsAndVisibility = AccountManager.get(this)
- .getAccountsAndVisibilityForPackage(mCallingPackage, null /* type */);
- oldVisibility = accountsAndVisibility.get(account);
- }
+ Integer oldVisibility =
+ AccountManager.get(this).getAccountVisibility(account, mCallingPackage);
if (oldVisibility != null
&& oldVisibility == AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE) {
AccountManager.get(this).setAccountVisibility(account, mCallingPackage,
AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
}
+
+ if (oldVisibility != null && oldVisibility == AccountManager.VISIBILITY_NOT_VISIBLE) {
+ // Added account is not visible to caller.
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ return;
+ }
Bundle bundle = new Bundle();
bundle.putString(AccountManager.KEY_ACCOUNT_NAME, accountName);
bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType);
@@ -448,6 +462,7 @@
Log.v(TAG, "ChooseTypeAndAccountActivity.setResultAndFinish: selected account "
+ accountName + ", " + accountType);
}
+
finish();
}
@@ -509,22 +524,24 @@
* that don't match the allowable types, if provided, or that don't match the allowable
* accounts, if provided.
*/
- private Map<Account, Integer> getAcceptableAccountChoices(AccountManager accountManager) {
- Map<Account, Integer> accountsAndVisibility =
- accountManager.getAccountsAndVisibilityForPackage(mCallingPackage, null /* type */);
-
- Map<Account, Integer> accountsToPopulate =
- new HashMap<Account, Integer>(accountsAndVisibility.size());
- for (Map.Entry<Account, Integer> entry : accountsAndVisibility.entrySet()) {
+ private LinkedHashMap<Account, Integer> getAcceptableAccountChoices(AccountManager accountManager) {
+ Map<Account, Integer> accountsAndVisibilityForCaller =
+ accountManager.getAccountsAndVisibilityForPackage(mCallingPackage, null);
+ Account[] allAccounts = accountManager.getAccounts();
+ LinkedHashMap<Account, Integer> accountsToPopulate =
+ new LinkedHashMap<>(accountsAndVisibilityForCaller.size());
+ for (Account account : allAccounts) {
if (mSetOfAllowableAccounts != null
- && !mSetOfAllowableAccounts.contains(entry.getKey())) {
+ && !mSetOfAllowableAccounts.contains(account)) {
continue;
}
if (mSetOfRelevantAccountTypes != null
- && !mSetOfRelevantAccountTypes.contains(entry.getKey().type)) {
+ && !mSetOfRelevantAccountTypes.contains(account.type)) {
continue;
}
- accountsToPopulate.put(entry.getKey(), entry.getValue());
+ if (accountsAndVisibilityForCaller.get(account) != null) {
+ accountsToPopulate.put(account, accountsAndVisibilityForCaller.get(account));
+ }
}
return accountsToPopulate;
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index d432160..2376a95 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -724,6 +724,7 @@
public static final int FINISH_TASK_WITH_ACTIVITY = 2;
static final String FRAGMENTS_TAG = "android:fragments";
+ static final String AUTOFILL_RESET_NEEDED_TAG = "android:autofillResetNeeded";
private static final String WINDOW_HIERARCHY_TAG = "android:viewHierarchyState";
private static final String SAVED_DIALOG_IDS_KEY = "android:savedDialogIds";
@@ -1057,6 +1058,12 @@
* @see #onSaveInstanceState
*/
protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ mAutoFillResetNeeded = savedInstanceState.getBoolean(AUTOFILL_RESET_NEEDED_TAG, false);
+
+ if (mAutoFillResetNeeded) {
+ getSystemService(AutofillManager.class).onRestoreInstanceState(savedInstanceState);
+ }
+
if (mWindow != null) {
Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
if (windowState != null) {
@@ -1502,6 +1509,10 @@
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
+ if (mAutoFillResetNeeded) {
+ outState.putBoolean(AUTOFILL_RESET_NEEDED_TAG, mAutoFillResetNeeded);
+ getSystemService(AutofillManager.class).onSaveInstanceState(outState);
+ }
getApplication().dispatchActivitySaveInstanceState(this, outState);
}
@@ -2443,13 +2454,20 @@
}
/**
- * Finds a view that was identified by the id attribute from the XML that
- * was processed in {@link #onCreate}.
+ * Finds a view that was identified by the {@code android:id} XML attribute
+ * that was processed in {@link #onCreate}.
+ * <p>
+ * <strong>Note:</strong> In most cases -- depending on compiler support --
+ * the resulting view is automatically cast to the target class type. If
+ * the target class type is unconstrained, an explicit cast may be
+ * necessary.
*
- * @return The view if found or null otherwise.
+ * @param id the ID to search for
+ * @return a view with given ID if found, or {@code null} otherwise
+ * @see View#findViewById(int)
*/
@Nullable
- public View findViewById(@IdRes int id) {
+ public <T extends View> T findViewById(@IdRes int id) {
return getWindow().findViewById(id);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d1d462c..7299d6b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -38,6 +38,7 @@
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 @@
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();
- sendMessage(H.SERVICE_ARGS, s);
+ 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);
+ }
}
public final void scheduleStopService(IBinder token) {
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 72ccf72..943c572 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -498,14 +498,22 @@
}
/**
- * Finds a child view with the given identifier. Returns null if the
- * specified child view does not exist or the dialog has not yet been fully
- * created (for example, via {@link #show()} or {@link #create()}).
+ * Finds the first descendant view with the given ID or {@code null} if the
+ * ID is invalid (< 0), there is no matching view in the hierarchy, or the
+ * dialog has not yet been fully created (for example, via {@link #show()}
+ * or {@link #create()}).
+ * <p>
+ * <strong>Note:</strong> In most cases -- depending on compiler support --
+ * the resulting view is automatically cast to the target class type. If
+ * the target class type is unconstrained, an explicit cast may be
+ * necessary.
*
- * @param id the identifier of the view to find
- * @return The view with the given id or null.
+ * @param id the ID to search for
+ * @return a view with given ID if found, or {@code null} otherwise
+ * @see View#findViewById(int)
*/
- public @Nullable View findViewById(@IdRes int id) {
+ @Nullable
+ public <T extends View> T findViewById(@IdRes int id) {
return mWindow.findViewById(id);
}
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 3102a93..a3c123f 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -2608,6 +2608,12 @@
}
}
+ void noteStateNotSaved() {
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.noteStateNotSaved();
+ }
+ }
+
@Deprecated
void performMultiWindowModeChanged(boolean isInMultiWindowMode) {
onMultiWindowModeChanged(isInMultiWindowMode);
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 399987f..75d6295 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -314,14 +314,17 @@
public abstract Fragment getFragment(Bundle bundle, String key);
/**
- * Get a collection of all fragments that are currently added to the FragmentManager.
+ * Get a list of all fragments that are currently added to the FragmentManager.
* This may include those that are hidden as well as those that are shown.
* This will not include any fragments only in the back stack, or fragments that
* are detached or removed.
+ * <p>
+ * The order of the fragments in the list is the order in which they were
+ * added or attached.
*
- * @return A collection of all fragments that are added to the FragmentManager.
+ * @return A list of all fragments that are added to the FragmentManager.
*/
- public abstract Collection<Fragment> getFragments();
+ public abstract List<Fragment> getFragments();
/**
* Save the current instance state of the given Fragment. This can be
@@ -695,6 +698,9 @@
// This is dangerous, but we want to keep from breaking old applications.
boolean mAllowOldReentrantBehavior;
+ // Saved FragmentManagerNonConfig during saveAllState() and cleared in noteStateNotSaved()
+ FragmentManagerNonConfig mSavedNonConfig;
+
Runnable mExecCommit = new Runnable() {
@Override
public void run() {
@@ -907,12 +913,12 @@
}
@Override
- public Collection<Fragment> getFragments() {
+ public List<Fragment> getFragments() {
if (mAdded == null) {
return Collections.EMPTY_LIST;
}
synchronized (mAdded) {
- return (Collection<Fragment>) mAdded.clone();
+ return (List<Fragment>) mAdded.clone();
}
}
@@ -2518,6 +2524,35 @@
}
FragmentManagerNonConfig retainNonConfig() {
+ setRetaining(mSavedNonConfig);
+ return mSavedNonConfig;
+ }
+
+ /**
+ * Recurse the FragmentManagerNonConfig fragments and set the mRetaining to true. This
+ * was previously done while saving the non-config state, but that has been moved to
+ * {@link #saveNonConfig()} called from {@link #saveAllState()}. If mRetaining is set too
+ * early, the fragment won't be destroyed when the FragmentManager is destroyed.
+ */
+ private static void setRetaining(FragmentManagerNonConfig nonConfig) {
+ if (nonConfig == null) {
+ return;
+ }
+ List<Fragment> fragments = nonConfig.getFragments();
+ if (fragments != null) {
+ for (Fragment fragment : fragments) {
+ fragment.mRetaining = true;
+ }
+ }
+ List<FragmentManagerNonConfig> children = nonConfig.getChildNonConfigs();
+ if (children != null) {
+ for (FragmentManagerNonConfig child : children) {
+ setRetaining(child);
+ }
+ }
+ }
+
+ void saveNonConfig() {
ArrayList<Fragment> fragments = null;
ArrayList<FragmentManagerNonConfig> childFragments = null;
if (mActive != null) {
@@ -2529,13 +2564,13 @@
fragments = new ArrayList<>();
}
fragments.add(f);
- f.mRetaining = true;
f.mTargetIndex = f.mTarget != null ? f.mTarget.mIndex : -1;
if (DEBUG) Log.v(TAG, "retainNonConfig: keeping retained " + f);
}
boolean addedChild = false;
if (f.mChildFragmentManager != null) {
- FragmentManagerNonConfig child = f.mChildFragmentManager.retainNonConfig();
+ f.mChildFragmentManager.saveNonConfig();
+ FragmentManagerNonConfig child = f.mChildFragmentManager.mSavedNonConfig;
if (child != null) {
if (childFragments == null) {
childFragments = new ArrayList<>();
@@ -2554,9 +2589,10 @@
}
}
if (fragments == null && childFragments == null) {
- return null;
+ mSavedNonConfig = null;
+ } else {
+ mSavedNonConfig = new FragmentManagerNonConfig(fragments, childFragments);
}
- return new FragmentManagerNonConfig(fragments, childFragments);
}
void saveFragmentViewState(Fragment f) {
@@ -2617,6 +2653,7 @@
execPendingActions();
mStateSaved = true;
+ mSavedNonConfig = null;
if (mActive == null || mActive.size() <= 0) {
return null;
@@ -2717,6 +2754,7 @@
if (mPrimaryNav != null) {
fms.mPrimaryNavActiveIndex = mPrimaryNav.mIndex;
}
+ saveNonConfig();
return fms;
}
@@ -2892,9 +2930,17 @@
}
public void noteStateNotSaved() {
+ mSavedNonConfig = null;
mStateSaved = false;
+ final int addedCount = mAdded == null ? 0 : mAdded.size();
+ for (int i = 0; i < addedCount; i++) {
+ Fragment fragment = mAdded.get(i);
+ if (fragment != null) {
+ fragment.noteStateNotSaved();
+ }
+ }
}
-
+
public void dispatchCreate() {
mStateSaved = false;
dispatchMoveToState(Fragment.CREATED);
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index f4d26fd..079bbcd 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -635,6 +635,11 @@
*/
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 6c43fe3..1b3c00b 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -25,6 +25,7 @@
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 @@
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 489a0f0..d620a81 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -945,7 +945,7 @@
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
similarity index 88%
rename from core/java/android/service/resolver/ResolverTarget.aidl
rename to core/java/android/app/ServiceStartArgs.aidl
index 6cab2d4..fd2cf0f 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 0000000..f030cba
--- /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/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 6b05bdf..4572578 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -74,7 +74,9 @@
import android.net.EthernetManager;
import android.net.IConnectivityManager;
import android.net.IEthernetManager;
+import android.net.IIpSecService;
import android.net.INetworkPolicyManager;
+import android.net.IpSecManager;
import android.net.NetworkPolicyManager;
import android.net.NetworkScoreManager;
import android.net.nsd.INsdManager;
@@ -261,6 +263,15 @@
return new ConnectivityManager(context, service);
}});
+ registerService(Context.IPSEC_SERVICE, IpSecManager.class,
+ new StaticServiceFetcher<IpSecManager>() {
+ @Override
+ public IpSecManager createService() {
+ IBinder b = ServiceManager.getService(Context.IPSEC_SERVICE);
+ IIpSecService service = IIpSecService.Stub.asInterface(b);
+ return new IpSecManager(service);
+ }});
+
registerService(Context.COUNTRY_DETECTOR, CountryDetector.class,
new StaticServiceFetcher<CountryDetector>() {
@Override
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index bb5d830..82ad825 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2768,9 +2768,11 @@
* 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 7d2db5c..545aef5 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.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;
@@ -602,6 +599,10 @@
boolean mSanitized;
HtmlInfo mHtmlInfo;
+ // POJO used to override some autofill-related values when the node is parcelized.
+ // Not written to parcel.
+ AutofillOverlay mAutofillOverlay;
+
int mX;
int mY;
int mScrollX;
@@ -759,6 +760,7 @@
boolean writeSensitive = true;
int flags = mFlags & ~FLAGS_ALL_CONTROL;
+
if (mId != View.NO_ID) {
flags |= FLAGS_HAS_ID;
}
@@ -813,6 +815,13 @@
// Remove 'checked' from sanitized autofill request.
writtenFlags = flags & ~FLAGS_CHECKED;
}
+ if (mAutofillOverlay != null) {
+ if (mAutofillOverlay.focused) {
+ writtenFlags |= ViewNode.FLAGS_FOCUSED;
+ } else {
+ writtenFlags &= ~ViewNode.FLAGS_FOCUSED;
+ }
+ }
out.writeInt(writtenFlags);
if ((flags&FLAGS_HAS_ID) != 0) {
@@ -832,7 +841,14 @@
out.writeParcelable(mAutofillId, 0);
out.writeInt(mAutofillType);
out.writeStringArray(mAutofillHints);
- final AutofillValue sanitizedValue = writeSensitive ? mAutofillValue : null;
+ final AutofillValue sanitizedValue;
+ if (mAutofillOverlay != null && mAutofillOverlay.value != null) {
+ sanitizedValue = mAutofillOverlay.value;
+ } else if (writeSensitive) {
+ sanitizedValue = mAutofillValue;
+ } else {
+ sanitizedValue = null;
+ }
out.writeParcelable(sanitizedValue, 0);
out.writeStringArray(mAutofillOptions);
if (mHtmlInfo instanceof Parcelable) {
@@ -920,15 +936,6 @@
}
/**
- * @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 +946,6 @@
}
/**
- * @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 +969,6 @@
}
/**
- * @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
@@ -1000,6 +978,11 @@
return mAutofillValue;
}
+ /** @hide **/
+ public void setAutofillOverlay(AutofillOverlay overlay) {
+ mAutofillOverlay = overlay;
+ }
+
/**
* Gets the options that can be used to autofill this structure.
*
@@ -1381,6 +1364,16 @@
}
}
+ /**
+ * POJO used to override some autofill-related values when the node is parcelized.
+ *
+ * @hide
+ */
+ static public class AutofillOverlay {
+ public boolean focused;
+ public AutofillValue value;
+ }
+
static class ViewNodeBuilder extends ViewStructure {
final AssistStructure mAssist;
final ViewNode mNode;
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 74a39e8..9f35e85 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -710,9 +710,9 @@
* 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 @@
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 @@
return Collections.emptyList();
}
return getInstalledProvidersForProfile(AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN,
- null);
+ null, null);
}
/**
@@ -752,7 +776,7 @@
if (mService == null) {
return Collections.emptyList();
}
- return getInstalledProvidersForProfile(categoryFilter, null);
+ return getInstalledProvidersForProfile(categoryFilter, null, null);
}
/**
@@ -766,6 +790,7 @@
* @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 @@
* @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 @@
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 c4e4e06..aaa5f19 100644
--- a/core/java/android/content/pm/BaseParceledListSlice.java
+++ b/core/java/android/content/pm/BaseParceledListSlice.java
@@ -50,6 +50,8 @@
private final List<T> mList;
+ private int mInlineCountLimit = Integer.MAX_VALUE;
+
public BaseParceledListSlice(List<T> list) {
mList = list;
}
@@ -135,6 +137,14 @@
}
/**
+ * 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 @@
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/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 9457d15..2dfb45f 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -6172,6 +6172,7 @@
cpuAbiOverride = dest.readString();
use32bitAbi = (dest.readInt() == 1);
restrictUpdateHash = dest.createByteArray();
+ visibleToInstantApps = dest.readInt() == 1;
}
private static void internStringArrayList(List<String> list) {
@@ -6286,6 +6287,7 @@
dest.writeString(cpuAbiOverride);
dest.writeInt(use32bitAbi ? 1 : 0);
dest.writeByteArray(restrictUpdateHash);
+ dest.writeInt(visibleToInstantApps ? 1 : 0);
}
diff --git a/core/java/android/hardware/SensorAdditionalInfo.java b/core/java/android/hardware/SensorAdditionalInfo.java
index 572a287..ea1d01b 100644
--- a/core/java/android/hardware/SensorAdditionalInfo.java
+++ b/core/java/android/hardware/SensorAdditionalInfo.java
@@ -131,6 +131,64 @@
*/
public static final int TYPE_SAMPLING = 0x10004;
+ /**
+ * Local geo-magnetic Field.
+ *
+ * Additional into to sensor hardware. Local geomagnetic field information based on
+ * device geo location. This type is primarily for for magnetic field calibration and rotation
+ * vector sensor fusion.
+ *
+ * float[3]: strength (uT), declination and inclination angle (rad).
+ * @hide
+ */
+ public static final int TYPE_LOCAL_GEOMAGNETIC_FIELD = 0x30000;
+
+ /**
+ * Local gravity acceleration strength.
+ *
+ * Additional info to sensor hardware for accelerometer calibration.
+ *
+ * float: gravitational acceleration norm in m/s^2.
+ * @hide
+ */
+ public static final int TYPE_LOCAL_GRAVITY = 0x30001;
+
+ /**
+ * Device dock state.
+ *
+ * Additional info to sensor hardware indicating dock states of device.
+ *
+ * int32_t: dock state following definition of {@link android.content.Intent#EXTRA_DOCK_STATE}.
+ * Undefined values are ignored.
+ * @hide
+ */
+ public static final int TYPE_DOCK_STATE = 0x30002;
+
+ /**
+ * High performance mode.
+ *
+ * Additional info to sensor hardware. Device is able to use up more power and take more
+ * resources to improve throughput and latency in high performance mode. One possible use case
+ * is virtual reality, when sensor latency need to be carefully controlled.
+ *
+ * int32_t: 1 or 0, denoting device is in or out of high performance mode, respectively.
+ * Other values are ignored.
+ * @hide
+ */
+ public static final int TYPE_HIGH_PERFORMANCE_MODE = 0x30003;
+
+ /**
+ * Magnetic field calibration hint.
+ *
+ * Additional info to sensor hardware. Device is notified when manually triggered magnetic field
+ * calibration procedure is started or stopped. The calibration procedure is assumed timed out
+ * after 1 minute from start, even if an explicit stop is not received.
+ *
+ * int32_t: 1 for calibration start, 0 for stop, other values are ignored.
+ * @hide
+ */
+ public static final int TYPE_MAGNETIC_FIELD_CALIBRATION = 0x30004;
+
SensorAdditionalInfo(
Sensor aSensor, int aType, int aSerial, int [] aIntValues, float [] aFloatValues) {
sensor = aSensor;
@@ -139,4 +197,18 @@
intValues = aIntValues;
floatValues = aFloatValues;
}
+
+ /** @hide */
+ public static SensorAdditionalInfo createLocalGeomagneticField(
+ float strength, float declination, float inclination) {
+ if (strength < 10 || strength > 100 // much beyond extreme values on earth
+ || declination < 0 || declination > Math.PI
+ || inclination < -Math.PI / 2 || inclination > Math.PI / 2) {
+ throw new IllegalArgumentException("Geomagnetic field info out of range");
+ }
+
+ return new SensorAdditionalInfo(
+ null, TYPE_LOCAL_GEOMAGNETIC_FIELD, 0,
+ null, new float[] { strength, declination, inclination});
+ }
}
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index a6930b0..1dc6478 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -1927,4 +1927,12 @@
}
return delay;
}
+
+ /** @hide */
+ public boolean setOperationParameter(SensorAdditionalInfo parameter) {
+ return setOperationParameterImpl(parameter);
+ }
+
+ /** @hide */
+ protected abstract boolean setOperationParameterImpl(SensorAdditionalInfo parameter);
}
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 7029847..0677179 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -67,6 +67,9 @@
private static native int nativeConfigDirectChannel(
long nativeInstance, int channelHandle, int sensorHandle, int rate);
+ private static native int nativeSetOperationParameter(
+ long nativeInstance, int type, float[] floatValues, int[] intValues);
+
private static final Object sLock = new Object();
@GuardedBy("sLock")
private static boolean sNativeClassInited = false;
@@ -928,4 +931,9 @@
}
}
+
+ protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) {
+ return nativeSetOperationParameter(
+ mNativeInstance, parameter.type, parameter.floatValues, parameter.intValues) == 0;
+ }
}
diff --git a/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl
new file mode 100644
index 0000000..0aa3ce6
--- /dev/null
+++ b/core/java/android/net/IIpSecService.aidl
@@ -0,0 +1,46 @@
+/*
+** Copyright 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.net;
+
+import android.net.Network;
+import android.net.IpSecConfig;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * @hide
+ */
+interface IIpSecService
+{
+ Bundle reserveSecurityParameterIndex(
+ int direction, in String remoteAddress, int requestedSpi, in IBinder binder);
+
+ void releaseSecurityParameterIndex(int resourceId);
+
+ Bundle openUdpEncapsulationSocket(int port, in IBinder binder);
+
+ void closeUdpEncapsulationSocket(in ParcelFileDescriptor socket);
+
+ Bundle createTransportModeTransform(in IpSecConfig c, in IBinder binder);
+
+ void deleteTransportModeTransform(int transformId);
+
+ void applyTransportModeTransform(in ParcelFileDescriptor socket, int transformId);
+
+ void removeTransportModeTransform(in ParcelFileDescriptor socket, int transformId);
+}
diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java
index da5cb37..7fea4a2 100644
--- a/core/java/android/net/IpSecAlgorithm.java
+++ b/core/java/android/net/IpSecAlgorithm.java
@@ -164,6 +164,8 @@
private static boolean isTruncationLengthValid(String algo, int truncLenBits) {
switch (algo) {
+ case ALGO_CRYPT_AES_CBC:
+ return (truncLenBits == 128 || truncLenBits == 192 || truncLenBits == 256);
case ALGO_AUTH_HMAC_MD5:
return (truncLenBits >= 96 && truncLenBits <= 128);
case ALGO_AUTH_HMAC_SHA1:
diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java
index b58bf42..13dc19f 100644
--- a/core/java/android/net/IpSecConfig.java
+++ b/core/java/android/net/IpSecConfig.java
@@ -23,7 +23,7 @@
/** @hide */
public final class IpSecConfig implements Parcelable {
- private static final String TAG = IpSecConfig.class.getSimpleName();
+ private static final String TAG = "IpSecConfig";
//MODE_TRANSPORT or MODE_TUNNEL
int mode;
@@ -43,13 +43,13 @@
int spi;
// Encryption Algorithm
- IpSecAlgorithm encryptionAlgo;
+ IpSecAlgorithm encryption;
// Authentication Algorithm
- IpSecAlgorithm authenticationAlgo;
+ IpSecAlgorithm authentication;
}
- Flow[] flow = new Flow[2];
+ Flow[] flow = new Flow[] {new Flow(), new Flow()};
// For tunnel mode IPv4 UDP Encapsulation
// IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE
@@ -57,17 +57,15 @@
int encapLocalPort;
int encapRemotePort;
- // An optional protocol to match with the selector
- int selectorProto;
-
- // A bitmask of FEATURE_* indicating which of the fields
- // of this class are valid.
- long features;
-
// An interval, in seconds between the NattKeepalive packets
int nattKeepaliveInterval;
- public InetAddress getLocalIp() {
+ // Transport or Tunnel
+ public int getMode() {
+ return mode;
+ }
+
+ public InetAddress getLocalAddress() {
return localAddress;
}
@@ -75,19 +73,19 @@
return flow[direction].spi;
}
- public InetAddress getRemoteIp() {
+ public InetAddress getRemoteAddress() {
return remoteAddress;
}
- public IpSecAlgorithm getEncryptionAlgo(int direction) {
- return flow[direction].encryptionAlgo;
+ public IpSecAlgorithm getEncryption(int direction) {
+ return flow[direction].encryption;
}
- public IpSecAlgorithm getAuthenticationAlgo(int direction) {
- return flow[direction].authenticationAlgo;
+ public IpSecAlgorithm getAuthentication(int direction) {
+ return flow[direction].authentication;
}
- Network getNetwork() {
+ public Network getNetwork() {
return network;
}
@@ -103,18 +101,10 @@
return encapRemotePort;
}
- public int getSelectorProto() {
- return selectorProto;
- }
-
- int getNattKeepaliveInterval() {
+ public int getNattKeepaliveInterval() {
return nattKeepaliveInterval;
}
- public boolean hasProperty(int featureBits) {
- return (features & featureBits) == featureBits;
- }
-
// Parcelable Methods
@Override
@@ -124,31 +114,25 @@
@Override
public void writeToParcel(Parcel out, int flags) {
- out.writeLong(features);
// TODO: Use a byte array or other better method for storing IPs that can also include scope
out.writeString((localAddress != null) ? localAddress.getHostAddress() : null);
// TODO: Use a byte array or other better method for storing IPs that can also include scope
out.writeString((remoteAddress != null) ? remoteAddress.getHostAddress() : null);
out.writeParcelable(network, flags);
out.writeInt(flow[IpSecTransform.DIRECTION_IN].spi);
- out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].encryptionAlgo, flags);
- out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].authenticationAlgo, flags);
+ out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].encryption, flags);
+ out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].authentication, flags);
out.writeInt(flow[IpSecTransform.DIRECTION_OUT].spi);
- out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].encryptionAlgo, flags);
- out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].authenticationAlgo, flags);
+ out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].encryption, flags);
+ out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].authentication, flags);
out.writeInt(encapType);
out.writeInt(encapLocalPort);
out.writeInt(encapRemotePort);
- out.writeInt(selectorProto);
}
// Package Private: Used by the IpSecTransform.Builder;
// there should be no public constructor for this object
- IpSecConfig() {
- flow[IpSecTransform.DIRECTION_IN].spi = 0;
- flow[IpSecTransform.DIRECTION_OUT].spi = 0;
- nattKeepaliveInterval = 0; //FIXME constant
- }
+ IpSecConfig() {}
private static InetAddress readInetAddressFromParcel(Parcel in) {
String addrString = in.readString();
@@ -164,24 +148,22 @@
}
private IpSecConfig(Parcel in) {
- features = in.readLong();
localAddress = readInetAddressFromParcel(in);
remoteAddress = readInetAddressFromParcel(in);
network = (Network) in.readParcelable(Network.class.getClassLoader());
flow[IpSecTransform.DIRECTION_IN].spi = in.readInt();
- flow[IpSecTransform.DIRECTION_IN].encryptionAlgo =
+ flow[IpSecTransform.DIRECTION_IN].encryption =
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
- flow[IpSecTransform.DIRECTION_IN].authenticationAlgo =
+ flow[IpSecTransform.DIRECTION_IN].authentication =
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
flow[IpSecTransform.DIRECTION_OUT].spi = in.readInt();
- flow[IpSecTransform.DIRECTION_OUT].encryptionAlgo =
+ flow[IpSecTransform.DIRECTION_OUT].encryption =
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
- flow[IpSecTransform.DIRECTION_OUT].authenticationAlgo =
+ flow[IpSecTransform.DIRECTION_OUT].authentication =
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
encapType = in.readInt();
encapLocalPort = in.readInt();
encapRemotePort = in.readInt();
- selectorProto = in.readInt();
}
public static final Parcelable.Creator<IpSecConfig> CREATOR =
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 2c544e9..6852beb 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -17,10 +17,11 @@
import static com.android.internal.util.Preconditions.checkNotNull;
-import android.annotation.SystemApi;
-import android.content.Context;
-import android.os.INetworkManagementService;
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.Bundle;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
import android.util.AndroidException;
import dalvik.system.CloseGuard;
import java.io.FileDescriptor;
@@ -41,6 +42,29 @@
private static final String TAG = "IpSecManager";
/**
+ * The Security Parameter Index, SPI, 0 indicates an unknown or invalid index.
+ *
+ * <p>No IPsec packet may contain an SPI of 0.
+ */
+ public static final int INVALID_SECURITY_PARAMETER_INDEX = 0;
+
+ /** @hide */
+ public interface Status {
+ public static final int OK = 0;
+ public static final int RESOURCE_UNAVAILABLE = 1;
+ public static final int SPI_UNAVAILABLE = 2;
+ }
+
+ /** @hide */
+ public static final String KEY_STATUS = "status";
+ /** @hide */
+ public static final String KEY_RESOURCE_ID = "resourceId";
+ /** @hide */
+ public static final String KEY_SPI = "spi";
+ /** @hide */
+ public static final int INVALID_RESOURCE_ID = 0;
+
+ /**
* Indicates that the combination of remote InetAddress and SPI was non-unique for a given
* request. If encountered, selection of a new SPI is required before a transform may be
* created. Note, this should happen very rarely if the SPI is chosen to be sufficiently random
@@ -79,42 +103,30 @@
}
}
- private final Context mContext;
- private final INetworkManagementService mService;
+ private final IIpSecService mService;
public static final class SecurityParameterIndex implements AutoCloseable {
- private final Context mContext;
- private final InetAddress mDestinationAddress;
+ private final IIpSecService mService;
+ private final InetAddress mRemoteAddress;
private final CloseGuard mCloseGuard = CloseGuard.get();
- private int mSpi;
+ private int mSpi = INVALID_SECURITY_PARAMETER_INDEX;
+ private int mResourceId;
/** Return the underlying SPI held by this object */
public int getSpi() {
return mSpi;
}
- private SecurityParameterIndex(Context context, InetAddress destinationAddress, int spi)
- throws ResourceUnavailableException, SpiUnavailableException {
- mContext = context;
- mDestinationAddress = destinationAddress;
- mSpi = spi;
- mCloseGuard.open("open");
- }
-
/**
* Release an SPI that was previously reserved.
*
- * <p>Release an SPI for use by other users in the system. This will fail if the SPI is
- * currently in use by an IpSecTransform.
- *
- * @param destinationAddress SPIs must be unique for each combination of SPI and destination
- * address. Thus, the destinationAddress to which the SPI will communicate must be
- * supplied.
- * @param spi the previously reserved SPI to be freed.
+ * <p>Release an SPI for use by other users in the system. If a SecurityParameterIndex is
+ * applied to an IpSecTransform, it will become unusable for future transforms but should
+ * still be closed to ensure system resources are released.
*/
@Override
public void close() {
- mSpi = INVALID_SECURITY_PARAMETER_INDEX; // TODO: Invalid SPI
+ mSpi = INVALID_SECURITY_PARAMETER_INDEX;
mCloseGuard.close();
}
@@ -126,23 +138,61 @@
close();
}
+
+ private SecurityParameterIndex(
+ @NonNull IIpSecService service, int direction, InetAddress remoteAddress, int spi)
+ throws ResourceUnavailableException, SpiUnavailableException {
+ mService = service;
+ mRemoteAddress = remoteAddress;
+ try {
+ Bundle result =
+ mService.reserveSecurityParameterIndex(
+ direction, remoteAddress.getHostAddress(), spi, new Binder());
+
+ if (result == null) {
+ throw new NullPointerException("Received null response from IpSecService");
+ }
+
+ int status = result.getInt(KEY_STATUS);
+ switch (status) {
+ case Status.OK:
+ break;
+ case Status.RESOURCE_UNAVAILABLE:
+ throw new ResourceUnavailableException(
+ "No more SPIs may be allocated by this requester.");
+ case Status.SPI_UNAVAILABLE:
+ throw new SpiUnavailableException("Requested SPI is unavailable", spi);
+ default:
+ throw new RuntimeException(
+ "Unknown status returned by IpSecService: " + status);
+ }
+ mSpi = result.getInt(KEY_SPI);
+ mResourceId = result.getInt(KEY_RESOURCE_ID);
+
+ if (mSpi == INVALID_SECURITY_PARAMETER_INDEX) {
+ throw new RuntimeException("Invalid SPI returned by IpSecService: " + status);
+ }
+
+ if (mResourceId == INVALID_RESOURCE_ID) {
+ throw new RuntimeException(
+ "Invalid Resource ID returned by IpSecService: " + status);
+ }
+
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mCloseGuard.open("open");
+ }
}
/**
- * The Security Parameter Index, SPI, 0 indicates an unknown or invalid index.
- *
- * <p>No IPsec packet may contain an SPI of 0.
- */
- public static final int INVALID_SECURITY_PARAMETER_INDEX = 0;
-
- /**
- * Reserve an SPI for traffic bound towards the specified destination address.
+ * Reserve an SPI for traffic bound towards the specified remote address.
*
* <p>If successful, this SPI is guaranteed available until released by a call to {@link
* SecurityParameterIndex#close()}.
*
- * @param destinationAddress SPIs must be unique for each combination of SPI and destination
- * address.
+ * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT}
+ * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress.
* @param requestedSpi the requested SPI, or '0' to allocate a random SPI.
* @return the reserved SecurityParameterIndex
* @throws ResourceUnavailableException indicating that too many SPIs are currently allocated
@@ -150,9 +200,9 @@
* @throws SpiUnavailableException indicating that a particular SPI cannot be reserved
*/
public SecurityParameterIndex reserveSecurityParameterIndex(
- InetAddress destinationAddress, int requestedSpi)
+ int direction, InetAddress remoteAddress, int requestedSpi)
throws SpiUnavailableException, ResourceUnavailableException {
- return new SecurityParameterIndex(mContext, destinationAddress, requestedSpi);
+ return new SecurityParameterIndex(mService, direction, remoteAddress, requestedSpi);
}
/**
@@ -190,7 +240,13 @@
}
/* Call down to activate a transform */
- private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {}
+ private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {
+ try {
+ mService.applyTransportModeTransform(pfd, transform.getResourceId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
/**
* Apply an active Tunnel Mode IPsec Transform to a network, which will tunnel all traffic to
@@ -203,7 +259,6 @@
* @param transform an {@link IpSecTransform}, which must be an active Tunnel Mode transform.
* @hide
*/
- @SystemApi
public void applyTunnelModeTransform(Network net, IpSecTransform transform) {}
/**
@@ -235,7 +290,13 @@
}
/* Call down to activate a transform */
- private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {}
+ private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {
+ try {
+ mService.removeTransportModeTransform(pfd, transform.getResourceId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
/**
* Remove a Tunnel Mode IPsec Transform from a {@link Network}. This must be used as part of
@@ -248,7 +309,6 @@
* network
* @hide
*/
- @SystemApi
public void removeTunnelModeTransform(Network net, IpSecTransform transform) {}
/**
@@ -260,19 +320,19 @@
*/
public static final class UdpEncapsulationSocket implements AutoCloseable {
private final FileDescriptor mFd;
- private final Context mContext;
+ private final IIpSecService mService;
private final CloseGuard mCloseGuard = CloseGuard.get();
- private UdpEncapsulationSocket(Context context, int port)
+ private UdpEncapsulationSocket(@NonNull IIpSecService service, int port)
throws ResourceUnavailableException {
- mContext = context;
+ mService = service;
mCloseGuard.open("constructor");
// TODO: go down to the kernel and get a socket on the specified
mFd = new FileDescriptor();
}
- private UdpEncapsulationSocket(Context context) throws ResourceUnavailableException {
- mContext = context;
+ private UdpEncapsulationSocket(IIpSecService service) throws ResourceUnavailableException {
+ mService = service;
mCloseGuard.open("constructor");
// TODO: go get a random socket on a random port
mFd = new FileDescriptor();
@@ -339,7 +399,7 @@
public UdpEncapsulationSocket openUdpEncapsulationSocket(int port)
throws IOException, ResourceUnavailableException {
// Temporary code
- return new UdpEncapsulationSocket(mContext, port);
+ return new UdpEncapsulationSocket(mService, port);
}
/**
@@ -363,7 +423,7 @@
public UdpEncapsulationSocket openUdpEncapsulationSocket()
throws IOException, ResourceUnavailableException {
// Temporary code
- return new UdpEncapsulationSocket(mContext);
+ return new UdpEncapsulationSocket(mService);
}
/**
@@ -372,8 +432,7 @@
* @param context the application context for this manager
* @hide
*/
- public IpSecManager(Context context, INetworkManagementService service) {
- mContext = checkNotNull(context, "missing context");
+ public IpSecManager(IIpSecService service) {
mService = checkNotNull(service, "missing service");
}
}
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index d6dd28b..801e98c 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -15,11 +15,21 @@
*/
package android.net;
+import static android.net.IpSecManager.INVALID_RESOURCE_ID;
+import static android.net.IpSecManager.KEY_RESOURCE_ID;
+import static android.net.IpSecManager.KEY_STATUS;
+
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.content.Context;
-import android.system.ErrnoException;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.Log;
+import com.android.internal.util.Preconditions;
import dalvik.system.CloseGuard;
import java.io.IOException;
import java.lang.annotation.Retention;
@@ -86,39 +96,64 @@
@Retention(RetentionPolicy.SOURCE)
public @interface EncapType {}
- /**
- * Sentinel for an invalid transform (means that this transform is inactive).
- *
- * @hide
- */
- public static final int INVALID_TRANSFORM_ID = -1;
-
private IpSecTransform(Context context, IpSecConfig config) {
mContext = context;
mConfig = config;
- mTransformId = INVALID_TRANSFORM_ID;
+ mResourceId = INVALID_RESOURCE_ID;
+ }
+
+ private IIpSecService getIpSecService() {
+ IBinder b = ServiceManager.getService(android.content.Context.IPSEC_SERVICE);
+ if (b == null) {
+ throw new RemoteException("Failed to connect to IpSecService")
+ .rethrowAsRuntimeException();
+ }
+
+ return IIpSecService.Stub.asInterface(b);
+ }
+
+ private void checkResultStatusAndThrow(int status)
+ throws IOException, IpSecManager.ResourceUnavailableException,
+ IpSecManager.SpiUnavailableException {
+ switch (status) {
+ case IpSecManager.Status.OK:
+ return;
+ // TODO: Pass Error string back from bundle so that errors can be more specific
+ case IpSecManager.Status.RESOURCE_UNAVAILABLE:
+ throw new IpSecManager.ResourceUnavailableException(
+ "Failed to allocate a new IpSecTransform");
+ case IpSecManager.Status.SPI_UNAVAILABLE:
+ Log.wtf(TAG, "Attempting to use an SPI that was somehow not reserved");
+ // Fall through
+ default:
+ throw new IllegalStateException(
+ "Failed to Create a Transform with status code " + status);
+ }
}
private IpSecTransform activate()
throws IOException, IpSecManager.ResourceUnavailableException,
IpSecManager.SpiUnavailableException {
- int transformId;
synchronized (this) {
- //try {
- transformId = INVALID_TRANSFORM_ID;
- //} catch (RemoteException e) {
- // throw e.rethrowFromSystemServer();
- //}
+ try {
+ IIpSecService svc = getIpSecService();
+ Bundle result = svc.createTransportModeTransform(mConfig, new Binder());
+ int status = result.getInt(KEY_STATUS);
+ checkResultStatusAndThrow(status);
+ mResourceId = result.getInt(KEY_RESOURCE_ID, INVALID_RESOURCE_ID);
- if (transformId < 0) {
- throw new ErrnoException("addTransform", -transformId).rethrowAsIOException();
+ /* Keepalive will silently fail if not needed by the config; but, if needed and
+ * it fails to start, we need to bail because a transform will not be reliable
+ * to use if keepalive is expected to offload and fails.
+ */
+ // FIXME: if keepalive fails, we need to fail spectacularly
+ startKeepalive(mContext);
+ Log.d(TAG, "Added Transform with Id " + mResourceId);
+ mCloseGuard.open("build");
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
-
- startKeepalive(mContext); // Will silently fail if not required
- mTransformId = transformId;
- Log.d(TAG, "Added Transform with Id " + transformId);
}
- mCloseGuard.open("build");
return this;
}
@@ -133,21 +168,27 @@
* transform is no longer needed.
*/
public void close() {
- Log.d(TAG, "Removing Transform with Id " + mTransformId);
+ Log.d(TAG, "Removing Transform with Id " + mResourceId);
// Always safe to attempt cleanup
- if (mTransformId == INVALID_TRANSFORM_ID) {
+ if (mResourceId == INVALID_RESOURCE_ID) {
+ mCloseGuard.close();
return;
}
- //try {
- stopKeepalive();
- //} catch (RemoteException e) {
- // transform.setTransformId(transformId);
- // throw e.rethrowFromSystemServer();
- //} finally {
- mTransformId = INVALID_TRANSFORM_ID;
- //}
- mCloseGuard.close();
+ try {
+ /* Order matters here because the keepalive is best-effort but could fail in some
+ * horrible way to be removed if the wifi (or cell) subsystem has crashed, and we
+ * still want to clear out the transform.
+ */
+ IIpSecService svc = getIpSecService();
+ svc.deleteTransportModeTransform(mResourceId);
+ stopKeepalive();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ mResourceId = INVALID_RESOURCE_ID;
+ mCloseGuard.close();
+ }
}
@Override
@@ -164,7 +205,7 @@
}
private final IpSecConfig mConfig;
- private int mTransformId;
+ private int mResourceId;
private final Context mContext;
private final CloseGuard mCloseGuard = CloseGuard.get();
private ConnectivityManager.PacketKeepalive mKeepalive;
@@ -200,6 +241,7 @@
/* Package */
void startKeepalive(Context c) {
+ // FIXME: NO_KEEPALIVE needs to be a constant
if (mConfig.getNattKeepaliveInterval() == 0) {
return;
}
@@ -208,7 +250,7 @@
(ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
if (mKeepalive != null) {
- Log.e(TAG, "Keepalive already started for this IpSecTransform.");
+ Log.wtf(TAG, "Keepalive already started for this IpSecTransform.");
return;
}
@@ -218,10 +260,11 @@
mConfig.getNetwork(),
mConfig.getNattKeepaliveInterval(),
mKeepaliveCallback,
- mConfig.getLocalIp(),
+ mConfig.getLocalAddress(),
mConfig.getEncapLocalPort(),
- mConfig.getRemoteIp());
+ mConfig.getRemoteAddress());
try {
+ // FIXME: this is still a horrible way to fudge the synchronous callback
mKeepaliveSyncLock.wait(2000);
} catch (InterruptedException e) {
}
@@ -232,6 +275,11 @@
}
/* Package */
+ int getResourceId() {
+ return mResourceId;
+ }
+
+ /* Package */
void stopKeepalive() {
if (mKeepalive == null) {
return;
@@ -247,16 +295,6 @@
}
}
- /* Package */
- void setTransformId(int transformId) {
- mTransformId = transformId;
- }
-
- /* Package */
- int getTransformId() {
- return mTransformId;
- }
-
/**
* Builder object to facilitate the creation of IpSecTransform objects.
*
@@ -280,7 +318,7 @@
*/
public IpSecTransform.Builder setEncryption(
@TransformDirection int direction, IpSecAlgorithm algo) {
- mConfig.flow[direction].encryptionAlgo = algo;
+ mConfig.flow[direction].encryption = algo;
return this;
}
@@ -295,7 +333,7 @@
*/
public IpSecTransform.Builder setAuthentication(
@TransformDirection int direction, IpSecAlgorithm algo) {
- mConfig.flow[direction].authenticationAlgo = algo;
+ mConfig.flow[direction].authentication = algo;
return this;
}
@@ -305,32 +343,9 @@
* given destination address.
*
* <p>Care should be chosen when selecting an SPI to ensure that is is as unique as
- * possible. Random number generation is a reasonable approach to selecting an SPI. For
- * outbound SPIs, they must be reserved by calling {@link
- * IpSecManager#reserveSecurityParameterIndex(InetAddress, int)}. Otherwise, Transforms will
- * fail to build.
- *
- * <p>Unless an SPI is set for a given direction, traffic in that direction will be
- * sent/received without any IPsec applied.
- *
- * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
- * @param spi a unique 32-bit integer to identify transformed traffic
- */
- public IpSecTransform.Builder setSpi(@TransformDirection int direction, int spi) {
- mConfig.flow[direction].spi = spi;
- return this;
- }
-
- /**
- * Set the SPI, which uniquely identifies a particular IPsec session from others. Because
- * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a
- * given destination address.
- *
- * <p>Care should be chosen when selecting an SPI to ensure that is is as unique as
- * possible. Random number generation is a reasonable approach to selecting an SPI. For
- * outbound SPIs, they must be reserved by calling {@link
- * IpSecManager#reserveSecurityParameterIndex(InetAddress, int)}. Otherwise, Transforms will
- * fail to activate.
+ * possible. To reserve a value call {@link IpSecManager#reserveSecurityParameterIndex(int,
+ * InetAddress, int)}. Otherwise, SPI collisions would prevent a transform from being
+ * activated. IpSecManager#reserveSecurityParameterIndex(int, InetAddres$s, int)}.
*
* <p>Unless an SPI is set for a given direction, traffic in that direction will be
* sent/received without any IPsec applied.
@@ -341,6 +356,8 @@
*/
public IpSecTransform.Builder setSpi(
@TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) {
+ // TODO: convert to using the resource Id of the SPI. Then build() can validate
+ // the owner in the IpSecService
mConfig.flow[direction].spi = spi.getSpi();
return this;
}
@@ -447,7 +464,6 @@
* properties is invalid.
* @hide
*/
- @SystemApi
public IpSecTransform buildTunnelModeTransform(
InetAddress localAddress, InetAddress remoteAddress) {
//FIXME: argument validation here
@@ -463,7 +479,8 @@
*
* @param context current Context
*/
- public Builder(Context context) {
+ public Builder(@NonNull Context context) {
+ Preconditions.checkNotNull(context);
mContext = context;
mConfig = new IpSecConfig();
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index dd11f68..4c6d22a 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -182,7 +182,7 @@
* New in version 19:
* - Wakelock data (wl) gets current and max times.
* New in version 20:
- * - Sensor, BluetoothScan, WifiScan get background timers and counter.
+ * - Background timers and counters for: Sensor, BluetoothScan, WifiScan, Jobs.
*/
static final String CHECKIN_VERSION = "20";
@@ -392,6 +392,16 @@
}
/**
+ * Returns the secondary Timer held by the Timer, if one exists. This secondary timer may be
+ * used, for example, for tracking background usage. Secondary timers are never pooled.
+ *
+ * Not all Timer subclasses have a secondary timer; those that don't return null.
+ */
+ public Timer getSubTimer() {
+ return null;
+ }
+
+ /**
* Returns whether the timer is currently running. Some types of timers
* (e.g. BatchTimers) don't know whether the event is currently active,
* and report false.
@@ -3303,16 +3313,17 @@
final int wifiScanCount = u.getWifiScanCount(which);
final int wifiScanCountBg = u.getWifiScanBackgroundCount(which);
// Note that 'ActualTime' are unpooled and always since reset (regardless of 'which')
- final long wifiScanActualTime = u.getWifiScanActualTime(rawRealtime);
- final long wifiScanActualTimeBg = u.getWifiScanBackgroundTime(rawRealtime);
+ final long wifiScanActualTimeMs = (u.getWifiScanActualTime(rawRealtime) + 500) / 1000;
+ final long wifiScanActualTimeMsBg = (u.getWifiScanBackgroundTime(rawRealtime) + 500)
+ / 1000;
final long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which);
if (fullWifiLockOnTime != 0 || wifiScanTime != 0 || wifiScanCount != 0
- || wifiScanCountBg != 0 || wifiScanActualTime != 0 || wifiScanActualTimeBg != 0
- || uidWifiRunningTime != 0) {
+ || wifiScanCountBg != 0 || wifiScanActualTimeMs != 0
+ || wifiScanActualTimeMsBg != 0 || uidWifiRunningTime != 0) {
dumpLine(pw, uid, category, WIFI_DATA, fullWifiLockOnTime, wifiScanTime,
uidWifiRunningTime, wifiScanCount,
/* legacy fields follow, keep at 0 */ 0, 0, 0,
- wifiScanCountBg, wifiScanActualTime, wifiScanActualTimeBg);
+ wifiScanCountBg, wifiScanActualTimeMs, wifiScanActualTimeMsBg);
}
dumpControllerActivityLine(pw, uid, category, WIFI_CONTROLLER_DATA,
@@ -3393,9 +3404,13 @@
// Convert from microseconds to milliseconds with rounding
final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
final int count = timer.getCountLocked(which);
+ final Timer bgTimer = timer.getSubTimer();
+ final long bgTime = bgTimer != null ?
+ (bgTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000 : -1;
+ final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : -1;
if (totalTime != 0) {
dumpLine(pw, uid, category, JOB_DATA, "\"" + jobs.keyAt(ij) + "\"",
- totalTime, count);
+ totalTime, count, bgTime, bgCount);
}
}
@@ -4616,6 +4631,10 @@
// Convert from microseconds to milliseconds with rounding
final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
final int count = timer.getCountLocked(which);
+ final Timer bgTimer = timer.getSubTimer();
+ final long bgTime = bgTimer != null ?
+ (bgTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000 : -1;
+ final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : -1;
sb.setLength(0);
sb.append(prefix);
sb.append(" Job ");
@@ -4626,6 +4645,13 @@
sb.append("realtime (");
sb.append(count);
sb.append(" times)");
+ if (bgTime > 0) {
+ sb.append(", ");
+ formatTimeMs(sb, bgTime);
+ sb.append("background (");
+ sb.append(bgCount);
+ sb.append(" times)");
+ }
} else {
sb.append("(not used)");
}
diff --git a/core/java/android/service/autofill/AutoFillService.java b/core/java/android/service/autofill/AutoFillService.java
deleted file mode 100644
index c26f679..0000000
--- 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 6f17d0e..ef14095 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -71,7 +71,7 @@
* 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"
@@ -204,11 +204,12 @@
* to notify the result of the request.
*
* @param structure {@link Activity}'s view structure.
- * @param data bundle containing data passed by the service on previous calls to fill.
- * This bundle allows your service to keep state between fill and save requests
- * as well as when filling different sections of the UI as the system will try to
- * aggressively unbind from the service to conserve resources. See {@link
- * FillResponse} Javadoc for examples of multiple-sections requests.
+ * @param data bundle containing data passed by the service in a last call to
+ * {@link FillResponse.Builder#setExtras(Bundle)}, if any. This bundle allows your
+ * service to keep state between fill and save requests as well as when filling different
+ * sections of the UI as the system will try to aggressively unbind from the service to
+ * conserve resources.
+ * See {@link FillResponse} for examples of multiple-sections requests.
* @param flags either {@code 0} or {@link AutofillManager#FLAG_MANUAL_REQUEST}.
* @param cancellationSignal signal for observing cancellation requests. The system will use
* this to notify you that the fill result is no longer needed and you should stop
@@ -242,11 +243,12 @@
* to notify the result of the request.
*
* @param structure {@link Activity}'s view structure.
- * @param data bundle containing data passed by the service on previous calls to fill.
- * This bundle allows your service to keep state between fill and save requests
- * as well as when filling different sections of the UI as the system will try to
- * aggressively unbind from the service to conserve resources. See {@link
- * FillResponse} Javadoc for examples of multiple-sections requests.
+ * @param data bundle containing data passed by the service in a last call to
+ * {@link FillResponse.Builder#setExtras(Bundle)}, if any. This bundle allows your
+ * service to keep state between fill and save requests as well as when filling different
+ * sections of the UI as the system will try to aggressively unbind from the service to
+ * conserve resources.
+ * See {@link FillResponse} for examples of multiple-sections requests.
* @param callback object used to notify the result of the request.
*/
public abstract void onSaveRequest(@NonNull AssistStructure structure, @Nullable Bundle data,
diff --git a/core/java/android/service/autofill/AutofillServiceInfo.java b/core/java/android/service/autofill/AutofillServiceInfo.java
index f6d40db..0f4824e 100644
--- a/core/java/android/service/autofill/AutofillServiceInfo.java
+++ b/core/java/android/service/autofill/AutofillServiceInfo.java
@@ -36,7 +36,6 @@
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 @@
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 @@
@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 @@
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 b7a0420..e77bd0d 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -23,8 +23,6 @@
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 @@
}
/**
- * @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/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index c43019d..3117f98 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package android.service.autofill;
import static android.view.autofill.Helper.DEBUG;
@@ -23,6 +24,7 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.widget.RemoteViews;
@@ -112,7 +114,7 @@
*
* <p>The service could require user authentication at the {@link FillResponse} or the
* {@link Dataset} level, prior to autofilling an activity - see
- * {@link FillResponse.Builder#setAuthentication(IntentSender, RemoteViews)} and
+ * {@link FillResponse.Builder#setAuthentication(AutofillId[], IntentSender, RemoteViews)} and
* {@link Dataset.Builder#setAuthentication(IntentSender)}.
*
* <p>It is recommended that you encrypt only the sensitive data but leave the labels unencrypted
@@ -126,7 +128,7 @@
* possible options) which will start your auth flow and after successfully authenticating
* the user will be presented with the Home and Work options to pick one. Hence, you have
* flexibility how to implement your auth while storing labels non-encrypted and data
- * encrypted provides a better user experience.</p>
+ * encrypted provides a better user experience.
*/
public final class FillResponse implements Parcelable {
@@ -135,6 +137,7 @@
private final Bundle mExtras;
private final RemoteViews mPresentation;
private final IntentSender mAuthentication;
+ private AutofillId[] mAuthenticationIds;
private FillResponse(@NonNull Builder builder) {
mDatasets = builder.mDatasets;
@@ -142,6 +145,7 @@
mExtras = builder.mExtras;
mPresentation = builder.mPresentation;
mAuthentication = builder.mAuthentication;
+ mAuthenticationIds = builder.mAuthenticationIds;
}
/** @hide */
@@ -169,6 +173,11 @@
return mAuthentication;
}
+ /** @hide */
+ public @Nullable AutofillId[] getAuthenticationIds() {
+ return mAuthenticationIds;
+ }
+
/**
* Builder for {@link FillResponse} objects. You must to provide at least
* one dataset or set an authentication intent with a presentation view.
@@ -179,6 +188,7 @@
private Bundle mExtras;
private RemoteViews mPresentation;
private IntentSender mAuthentication;
+ private AutofillId[] mAuthenticationIds;
private boolean mDestroyed;
/**
@@ -193,7 +203,7 @@
* be encrypted. The provided {@link android.app.PendingIntent intent} must be an
* activity which implements your authentication flow. Also if you provide an auth
* intent you also need to specify the presentation view to be shown in the fill UI
- * for the user to trigger your authentication flow.</p>
+ * for the user to trigger your authentication flow.
*
* <p>When a user triggers autofill, the system launches the provided intent
* whose extras will have the {@link AutofillManager#EXTRA_ASSIST_STRUCTURE screen
@@ -205,40 +215,54 @@
* user's data was locked and marked that the response needs an authentication then
* in the response returned if authentication succeeds you need to provide all
* available data sets some of which may need to be further authenticated, for
- * example a credit card whose CVV needs to be entered.</p>
+ * example a credit card whose CVV needs to be entered.
*
* <p>If you provide an authentication intent you must also provide a presentation
* which is used to visualize visualize the response for triggering the authentication
- * flow.</p>
+ * flow.
*
* <p></><strong>Note:</strong> Do not make the provided pending intent
* immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
- * platform needs to fill in the authentication arguments.</p>
+ * platform needs to fill in the authentication arguments.
*
* @param authentication Intent to an activity with your authentication flow.
* @param presentation The presentation to visualize the response.
- * @return This builder.
+ * @param ids id of Views that when focused will display the authentication UI affordance.
*
+ * @return This builder.
* @see android.app.PendingIntent#getIntentSender()
*/
- public @NonNull Builder setAuthentication(@Nullable IntentSender authentication,
- @Nullable RemoteViews presentation) {
+ public @NonNull Builder setAuthentication(@NonNull AutofillId[] ids,
+ @Nullable IntentSender authentication, @Nullable RemoteViews presentation) {
throwIfDestroyed();
+ // TODO(b/33197203): assert ids is not null nor empty once old version is removed
if (authentication == null ^ presentation == null) {
throw new IllegalArgumentException("authentication and presentation"
+ " must be both non-null or null");
}
mAuthentication = authentication;
mPresentation = presentation;
+ mAuthenticationIds = ids;
return this;
}
/**
+ * TODO(b/33197203): will be removed once clients use the version that takes ids
+ * @hide
+ * @deprecated
+ */
+ @Deprecated
+ public @NonNull Builder setAuthentication(@Nullable IntentSender authentication,
+ @Nullable RemoteViews presentation) {
+ return setAuthentication(null, authentication, presentation);
+ }
+
+ /**
* Adds a new {@link Dataset} to this response.
*
* @return This builder.
*/
- public@NonNull Builder addDataset(@Nullable Dataset dataset) {
+ public @NonNull Builder addDataset(@Nullable Dataset dataset) {
throwIfDestroyed();
if (dataset == null) {
return this;
@@ -273,6 +297,9 @@
* android.os.CancellationSignal, FillCallback)} and {@link AutofillService#onSaveRequest(
* android.app.assist.AssistStructure, Bundle, SaveCallback)}.
*
+ * <p>If this method is called on multiple {@link FillResponse} objects for the same
+ * activity, just the latest bundle is passed back to the service.
+ *
* @param extras The response extras.
* @return This builder.
*/
@@ -282,6 +309,7 @@
return this;
}
+
/**
* Builds a new {@link FillResponse} instance. You must provide at least
* one dataset or some savable ids or an authentication with a presentation
@@ -308,7 +336,7 @@
}
/////////////////////////////////////
- // Object "contract" methods. //
+ // Object "contract" methods. //
/////////////////////////////////////
@Override
public String toString() {
@@ -320,11 +348,13 @@
.append(", hasExtras=").append(mExtras != null)
.append(", hasPresentation=").append(mPresentation != null)
.append(", hasAuthentication=").append(mAuthentication != null)
+ .append(", authenticationSize=").append(mAuthenticationIds != null
+ ? mAuthenticationIds.length : "N/A")
.toString();
}
/////////////////////////////////////
- // Parcelable "contract" methods. //
+ // Parcelable "contract" methods. //
/////////////////////////////////////
@Override
@@ -337,6 +367,7 @@
parcel.writeTypedArrayList(mDatasets, flags);
parcel.writeParcelable(mSaveInfo, flags);
parcel.writeParcelable(mExtras, flags);
+ parcel.writeParcelableArray(mAuthenticationIds, flags);
parcel.writeParcelable(mAuthentication, flags);
parcel.writeParcelable(mPresentation, flags);
}
@@ -356,8 +387,8 @@
}
builder.setSaveInfo(parcel.readParcelable(null));
builder.setExtras(parcel.readParcelable(null));
- builder.setAuthentication(parcel.readParcelable(null),
- parcel.readParcelable(null));
+ builder.setAuthentication(parcel.readParcelableArray(null, AutofillId.class),
+ parcel.readParcelable(null), parcel.readParcelable(null));
return builder.build();
}
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index 6213d27..f75b7af 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -25,7 +25,6 @@
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;
@@ -130,6 +129,16 @@
*/
public static final int SAVE_DATA_TYPE_CREDIT_CARD = 3;
+ /**
+ * Type used when the {@link FillResponse} represents just an username, without a password.
+ */
+ public static final int SAVE_DATA_TYPE_USERNAME = 4;
+
+ /**
+ * Type used when the {@link FillResponse} represents just an email address, without a password.
+ */
+ public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 5;
+
private final @SaveDataType int mType;
private final CharSequence mNegativeActionTitle;
private final IntentSender mNegativeActionListener;
@@ -223,6 +232,8 @@
case SAVE_DATA_TYPE_PASSWORD:
case SAVE_DATA_TYPE_ADDRESS:
case SAVE_DATA_TYPE_CREDIT_CARD:
+ case SAVE_DATA_TYPE_USERNAME:
+ case SAVE_DATA_TYPE_EMAIL_ADDRESS:
mType = type;
break;
default:
@@ -269,26 +280,6 @@
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 bda3154..0000000
--- 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 f0d747d..0000000
--- 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 0506747..0000000
--- 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 fb3e2d7..0000000
--- 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/text/method/DateKeyListener.java b/core/java/android/text/method/DateKeyListener.java
index e14cd2c..0accbf6 100644
--- a/core/java/android/text/method/DateKeyListener.java
+++ b/core/java/android/text/method/DateKeyListener.java
@@ -22,6 +22,7 @@
import android.view.KeyEvent;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
import java.util.HashMap;
import java.util.LinkedHashSet;
@@ -37,8 +38,11 @@
public class DateKeyListener extends NumberKeyListener
{
public int getInputType() {
- return InputType.TYPE_CLASS_DATETIME
- | InputType.TYPE_DATETIME_VARIATION_DATE;
+ if (mNeedsAdvancedInput) {
+ return InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
+ } else {
+ return InputType.TYPE_CLASS_DATETIME | InputType.TYPE_DATETIME_VARIATION_DATE;
+ }
}
@Override
@@ -65,7 +69,13 @@
final boolean success = NumberKeyListener.addDigits(chars, locale)
&& NumberKeyListener.addFormatCharsFromSkeletons(
chars, locale, SKELETONS, SYMBOLS_TO_IGNORE);
- mCharacters = success ? NumberKeyListener.collectionToArray(chars) : CHARACTERS;
+ if (success) {
+ mCharacters = NumberKeyListener.collectionToArray(chars);
+ mNeedsAdvancedInput = !ArrayUtils.containsAll(CHARACTERS, mCharacters);
+ } else {
+ mCharacters = CHARACTERS;
+ mNeedsAdvancedInput = false;
+ }
}
/**
@@ -110,6 +120,7 @@
};
private final char[] mCharacters;
+ private final boolean mNeedsAdvancedInput;
private static final Object sLock = new Object();
@GuardedBy("sLock")
diff --git a/core/java/android/text/method/DateTimeKeyListener.java b/core/java/android/text/method/DateTimeKeyListener.java
index 62e3ade..551db55 100644
--- a/core/java/android/text/method/DateTimeKeyListener.java
+++ b/core/java/android/text/method/DateTimeKeyListener.java
@@ -22,6 +22,7 @@
import android.view.KeyEvent;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
import java.util.HashMap;
import java.util.LinkedHashSet;
@@ -37,10 +38,13 @@
public class DateTimeKeyListener extends NumberKeyListener
{
public int getInputType() {
- return InputType.TYPE_CLASS_DATETIME
- | InputType.TYPE_DATETIME_VARIATION_NORMAL;
+ if (mNeedsAdvancedInput) {
+ return InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
+ } else {
+ return InputType.TYPE_CLASS_DATETIME | InputType.TYPE_DATETIME_VARIATION_NORMAL;
+ }
}
-
+
@Override
@NonNull
protected char[] getAcceptedChars()
@@ -70,7 +74,13 @@
chars, locale, SKELETON_12HOUR, SYMBOLS_TO_IGNORE)
&& NumberKeyListener.addFormatCharsFromSkeleton(
chars, locale, SKELETON_24HOUR, SYMBOLS_TO_IGNORE);
- mCharacters = success ? NumberKeyListener.collectionToArray(chars) : CHARACTERS;
+ if (success) {
+ mCharacters = NumberKeyListener.collectionToArray(chars);
+ mNeedsAdvancedInput = !ArrayUtils.containsAll(CHARACTERS, mCharacters);
+ } else {
+ mCharacters = CHARACTERS;
+ mNeedsAdvancedInput = false;
+ }
}
/**
@@ -114,6 +124,7 @@
};
private final char[] mCharacters;
+ private final boolean mNeedsAdvancedInput;
private static final Object sLock = new Object();
@GuardedBy("sLock")
diff --git a/core/java/android/text/method/DigitsKeyListener.java b/core/java/android/text/method/DigitsKeyListener.java
index 26c69ab..d9f2dcf 100644
--- a/core/java/android/text/method/DigitsKeyListener.java
+++ b/core/java/android/text/method/DigitsKeyListener.java
@@ -27,6 +27,7 @@
import android.view.KeyEvent;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
import java.util.HashMap;
import java.util.LinkedHashSet;
@@ -42,8 +43,12 @@
public class DigitsKeyListener extends NumberKeyListener
{
private char[] mAccepted;
+ private boolean mNeedsAdvancedInput;
private final boolean mSign;
private final boolean mDecimal;
+ private final boolean mStringMode;
+ @Nullable
+ private final Locale mLocale;
private static final String DEFAULT_DECIMAL_POINT_CHARS = ".";
private static final String DEFAULT_SIGN_CHARS = "-+";
@@ -112,11 +117,17 @@
this(locale, false, false);
}
- private void setToCompat(boolean sign, boolean decimal) {
+ private void setToCompat() {
mDecimalPointChars = DEFAULT_DECIMAL_POINT_CHARS;
mSignChars = DEFAULT_SIGN_CHARS;
- final int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0);
+ final int kind = (mSign ? SIGN : 0) | (mDecimal ? DECIMAL : 0);
mAccepted = COMPATIBILITY_CHARACTERS[kind];
+ mNeedsAdvancedInput = false;
+ }
+
+ private void calculateNeedForAdvancedInput() {
+ final int kind = (mSign ? SIGN : 0) | (mDecimal ? DECIMAL : 0);
+ mNeedsAdvancedInput = !ArrayUtils.containsAll(COMPATIBILITY_CHARACTERS[kind], mAccepted);
}
// Takes a sign string and strips off its bidi controls, if any.
@@ -144,14 +155,16 @@
public DigitsKeyListener(@Nullable Locale locale, boolean sign, boolean decimal) {
mSign = sign;
mDecimal = decimal;
+ mStringMode = false;
+ mLocale = locale;
if (locale == null) {
- setToCompat(sign, decimal);
+ setToCompat();
return;
}
LinkedHashSet<Character> chars = new LinkedHashSet<>();
final boolean success = NumberKeyListener.addDigits(chars, locale);
if (!success) {
- setToCompat(sign, decimal);
+ setToCompat();
return;
}
if (sign || decimal) {
@@ -161,7 +174,7 @@
final String plusString = stripBidiControls(symbols.getPlusSignString());
if (minusString.length() > 1 || plusString.length() > 1) {
// non-BMP and multi-character signs are not supported.
- setToCompat(sign, decimal);
+ setToCompat();
return;
}
final char minus = minusString.charAt(0);
@@ -181,7 +194,7 @@
final String separatorString = symbols.getDecimalSeparatorString();
if (separatorString.length() > 1) {
// non-BMP and multi-character decimal separators are not supported.
- setToCompat(sign, decimal);
+ setToCompat();
return;
}
final Character separatorChar = Character.valueOf(separatorString.charAt(0));
@@ -190,13 +203,19 @@
}
}
mAccepted = NumberKeyListener.collectionToArray(chars);
+ calculateNeedForAdvancedInput();
}
private DigitsKeyListener(@NonNull final String accepted) {
mSign = false;
mDecimal = false;
+ mStringMode = true;
+ mLocale = null;
mAccepted = new char[accepted.length()];
accepted.getChars(0, accepted.length(), mAccepted, 0);
+ // Theoretically we may need advanced input, but for backward compatibility, we don't change
+ // the input type.
+ mNeedsAdvancedInput = false;
}
/**
@@ -280,13 +299,38 @@
return result;
}
- public int getInputType() {
- int contentType = InputType.TYPE_CLASS_NUMBER;
- if (mSign) {
- contentType |= InputType.TYPE_NUMBER_FLAG_SIGNED;
+ /**
+ * Returns a DigitsKeyListener based on an the settings of a existing DigitsKeyListener, with
+ * the locale modified.
+ *
+ * @hide
+ */
+ @NonNull
+ public static DigitsKeyListener getInstance(
+ @Nullable Locale locale,
+ @NonNull DigitsKeyListener listener) {
+ if (listener.mStringMode) {
+ return listener; // string-mode DigitsKeyListeners have no locale.
+ } else {
+ return getInstance(locale, listener.mSign, listener.mDecimal);
}
- if (mDecimal) {
- contentType |= InputType.TYPE_NUMBER_FLAG_DECIMAL;
+ }
+
+ /**
+ * Returns the input type for the listener.
+ */
+ public int getInputType() {
+ int contentType;
+ if (mNeedsAdvancedInput) {
+ contentType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
+ } else {
+ contentType = InputType.TYPE_CLASS_NUMBER;
+ if (mSign) {
+ contentType |= InputType.TYPE_NUMBER_FLAG_SIGNED;
+ }
+ if (mDecimal) {
+ contentType |= InputType.TYPE_NUMBER_FLAG_DECIMAL;
+ }
}
return contentType;
}
diff --git a/core/java/android/text/method/TimeKeyListener.java b/core/java/android/text/method/TimeKeyListener.java
index c9f9f9f..5b1db11 100644
--- a/core/java/android/text/method/TimeKeyListener.java
+++ b/core/java/android/text/method/TimeKeyListener.java
@@ -22,6 +22,7 @@
import android.view.KeyEvent;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
import java.util.HashMap;
import java.util.LinkedHashSet;
@@ -37,8 +38,11 @@
public class TimeKeyListener extends NumberKeyListener
{
public int getInputType() {
- return InputType.TYPE_CLASS_DATETIME
- | InputType.TYPE_DATETIME_VARIATION_TIME;
+ if (mNeedsAdvancedInput) {
+ return InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
+ } else {
+ return InputType.TYPE_CLASS_DATETIME | InputType.TYPE_DATETIME_VARIATION_TIME;
+ }
}
@Override
@@ -70,7 +74,13 @@
chars, locale, SKELETON_12HOUR, SYMBOLS_TO_IGNORE)
&& NumberKeyListener.addFormatCharsFromSkeleton(
chars, locale, SKELETON_24HOUR, SYMBOLS_TO_IGNORE);
- mCharacters = success ? NumberKeyListener.collectionToArray(chars) : CHARACTERS;
+ if (success) {
+ mCharacters = NumberKeyListener.collectionToArray(chars);
+ mNeedsAdvancedInput = !ArrayUtils.containsAll(CHARACTERS, mCharacters);
+ } else {
+ mCharacters = CHARACTERS;
+ mNeedsAdvancedInput = false;
+ }
}
/**
@@ -114,6 +124,7 @@
};
private final char[] mCharacters;
+ private final boolean mNeedsAdvancedInput;
private static final Object sLock = new Object();
@GuardedBy("sLock")
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 0221040..a376c83 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2750,7 +2750,7 @@
* 1 PFLAG3_SCROLL_INDICATOR_END
* 1 PFLAG3_ASSIST_BLOCKED
* 1 PFLAG3_CLUSTER
- * x * NO LONGER NEEDED, SHOULD BE REUSED *
+ * 1 PFLAG3_IS_AUTOFILLED
* 1 PFLAG3_FINGER_DOWN
* 1 PFLAG3_FOCUSED_BY_DEFAULT
* 11 PFLAG3_AUTO_FILL_MODE_MASK
@@ -2961,6 +2961,14 @@
private static final int PFLAG3_CLUSTER = 0x8000;
/**
+ * Flag indicating that the view is autofilled
+ *
+ * @see #isAutofilled()
+ * @see #setAutofilled(boolean)
+ */
+ private static final int PFLAG3_IS_AUTOFILLED = 0x10000;
+
+ /**
* Indicates that the user is currently touching the screen.
* Currently used for the tooltip positioning only.
*/
@@ -7440,10 +7448,9 @@
* <ol>
* <li>Calling the proper getter method on {@link AutofillValue} to fetch the actual value.
* <li>Passing the actual value to the equivalent setter in the view.
- * <ol>
+ * </ol>
*
* <p>For example, a text-field view would call:
- *
* <pre class="prettyprint">
* CharSequence text = value.getTextValue();
* if (text != null) {
@@ -7451,6 +7458,10 @@
* }
* </pre>
*
+ * <p>If the value is updated asyncronously the next call to
+ * {@link AutofillManager#notifyValueChanged(View)} must happen <u>after</u> the value was
+ * changed to the autofilled value. If not, the view will not be considered autofilled.
+ *
* @param value value to be autofilled.
*/
public void autofill(@SuppressWarnings("unused") AutofillValue value) {
@@ -7461,6 +7472,9 @@
*
* <p>See {@link #autofill(AutofillValue)} and
* {@link #onProvideAutofillVirtualStructure(ViewStructure, int)} for more info.
+ * <p>To indicate that a virtual view was autofilled
+ * <code>@android:drawable/autofilled_highlight</code> should be drawn over it until the data
+ * changes.
*
* @param values map of values to be autofilled, keyed by virtual child id.
*/
@@ -7491,6 +7505,13 @@
}
/**
+ * @hide
+ */
+ public boolean isAutofilled() {
+ return (mPrivateFlags3 & PFLAG3_IS_AUTOFILLED) != 0;
+ }
+
+ /**
* Gets the {@link View}'s current autofill value.
*
* <p>By default returns {@code null}, but views should override it (and
@@ -9131,6 +9152,24 @@
}
/**
+ * @hide
+ */
+ @TestApi
+ public void setAutofilled(boolean isAutofilled) {
+ boolean wasChanged = isAutofilled != isAutofilled();
+
+ if (wasChanged) {
+ if (isAutofilled) {
+ mPrivateFlags3 |= PFLAG3_IS_AUTOFILLED;
+ } else {
+ mPrivateFlags3 &= ~PFLAG3_IS_AUTOFILLED;
+ }
+
+ invalidate();
+ }
+ }
+
+ /**
* Set whether this view should have sound effects enabled for events such as
* clicking and touching.
*
@@ -17117,9 +17156,19 @@
@CallSuper
protected Parcelable onSaveInstanceState() {
mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
- if (mStartActivityRequestWho != null) {
+ if (mStartActivityRequestWho != null || isAutofilled()) {
BaseSavedState state = new BaseSavedState(AbsSavedState.EMPTY_STATE);
+
+ if (mStartActivityRequestWho != null) {
+ state.mSavedData |= BaseSavedState.START_ACTIVITY_REQUESTED_WHO_SAVED;
+ }
+
+ if (isAutofilled()) {
+ state.mSavedData |= BaseSavedState.IS_AUTOFILLED;
+ }
+
state.mStartActivityRequestWhoSaved = mStartActivityRequestWho;
+ state.mIsAutofilled = isAutofilled();
return state;
}
return BaseSavedState.EMPTY_STATE;
@@ -17189,7 +17238,14 @@
+ "other views do not use the same id.");
}
if (state != null && state instanceof BaseSavedState) {
- mStartActivityRequestWho = ((BaseSavedState) state).mStartActivityRequestWhoSaved;
+ BaseSavedState baseState = (BaseSavedState) state;
+
+ if ((baseState.mSavedData & BaseSavedState.START_ACTIVITY_REQUESTED_WHO_SAVED) != 0) {
+ mStartActivityRequestWho = baseState.mStartActivityRequestWhoSaved;
+ }
+ if ((baseState.mSavedData & BaseSavedState.IS_AUTOFILLED) != 0) {
+ setAutofilled(baseState.mIsAutofilled);
+ }
}
}
@@ -17570,6 +17626,7 @@
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);
+ drawAutofilledHighlight(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
@@ -17870,6 +17927,7 @@
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
+ drawAutofilledHighlight(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
@@ -17946,6 +18004,7 @@
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);
+ drawAutofilledHighlight(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
@@ -18630,6 +18689,8 @@
// Step 4, draw the children
dispatchDraw(canvas);
+ drawAutofilledHighlight(canvas);
+
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
@@ -18783,6 +18844,8 @@
canvas.restoreToCount(saveCount);
+ drawAutofilledHighlight(canvas);
+
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
@@ -20141,6 +20204,41 @@
}
/**
+ * Get the drawable to be overlayed when a view is autofilled
+ *
+ * @return The drawable
+ *
+ * @throws IllegalStateException if the drawable could not be found.
+ */
+ @NonNull private Drawable getAutofilledDrawable() {
+ // Lazily load the isAutofilled drawable.
+ if (mAttachInfo.mAutofilledDrawable == null) {
+ mAttachInfo.mAutofilledDrawable = mContext.getDrawable(R.drawable.autofilled_highlight);
+
+ if (mAttachInfo.mAutofilledDrawable == null) {
+ throw new IllegalStateException(
+ "Could not find android:drawable/autofilled_highlight");
+ }
+ }
+
+ return mAttachInfo.mAutofilledDrawable;
+ }
+
+ /**
+ * Draw {@link View#isAutofilled()} highlight over view if the view is autofilled.
+ *
+ * @param canvas The canvas to draw on
+ */
+ private void drawAutofilledHighlight(@NonNull Canvas canvas) {
+ if (isAutofilled()) {
+ Drawable autofilledHighlight = getAutofilledDrawable();
+
+ autofilledHighlight.setBounds(0, 0, getWidth(), getHeight());
+ autofilledHighlight.draw(canvas);
+ }
+ }
+
+ /**
* Draw any foreground content for this view.
*
* <p>Foreground content may consist of scroll bars, a {@link #setForeground foreground}
@@ -20808,11 +20906,18 @@
}
/**
- * Look for a child view with the given id. If this view has the given
- * id, return this view.
+ * Finds the first descendant view with the given ID, the view itself if
+ * the ID matches {@link #getId()}, or {@code null} if the ID is invalid
+ * (< 0) or there is no matching view in the hierarchy.
+ * <p>
+ * <strong>Note:</strong> In most cases -- depending on compiler support --
+ * the resulting view is automatically cast to the target class type. If
+ * the target class type is unconstrained, an explicit cast may be
+ * necessary.
*
- * @param id The id to search for.
- * @return The view that has the given id in the hierarchy or null
+ * @param id the ID to search for
+ * @return a view with given ID if found, or {@code null} otherwise
+ * @see View#findViewById(int)
*/
@Nullable
public final <T extends View> T findViewById(@IdRes int id) {
@@ -24298,7 +24403,13 @@
* state in {@link android.view.View#onSaveInstanceState()}.
*/
public static class BaseSavedState extends AbsSavedState {
+ static final int START_ACTIVITY_REQUESTED_WHO_SAVED = 0b1;
+ static final int IS_AUTOFILLED = 0b10;
+
+ // Flags that describe what data in this state is valid
+ int mSavedData;
String mStartActivityRequestWhoSaved;
+ boolean mIsAutofilled;
/**
* Constructor used when reading from a parcel. Reads the state of the superclass.
@@ -24318,7 +24429,9 @@
*/
public BaseSavedState(Parcel source, ClassLoader loader) {
super(source, loader);
+ mSavedData = source.readInt();
mStartActivityRequestWhoSaved = source.readString();
+ mIsAutofilled = source.readBoolean();
}
/**
@@ -24333,7 +24446,10 @@
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
+
+ out.writeInt(mSavedData);
out.writeString(mStartActivityRequestWhoSaved);
+ out.writeBoolean(mIsAutofilled);
}
public static final Parcelable.Creator<BaseSavedState> CREATOR
@@ -24734,6 +24850,13 @@
Drawable mAccessibilityFocusDrawable;
/**
+ * The drawable for highlighting autofilled views.
+ *
+ * @see #isAutofilled()
+ */
+ Drawable mAutofilledDrawable;
+
+ /**
* Show where the margins, bounds and layout bounds are for each view.
*/
boolean mDebugLayout = SystemProperties.getBoolean(DEBUG_LAYOUT_PROPERTY, false);
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 0053caa..6dd8ecf 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1308,15 +1308,22 @@
}
/**
- * Finds a view that was identified by the id attribute from the XML that
- * was processed in {@link android.app.Activity#onCreate}. This will
- * implicitly call {@link #getDecorView} for you, with all of the
- * associated side-effects.
+ * Finds a view that was identified by the {@code android:id} XML attribute
+ * that was processed in {@link android.app.Activity#onCreate}. This will
+ * implicitly call {@link #getDecorView} with all of the associated
+ * side-effects.
+ * <p>
+ * <strong>Note:</strong> In most cases -- depending on compiler support --
+ * the resulting view is automatically cast to the target class type. If
+ * the target class type is unconstrained, an explicit cast may be
+ * necessary.
*
- * @return The view if found or null otherwise.
+ * @param id the ID to search for
+ * @return a view with given ID if found, or {@code null} otherwise
+ * @see View#findViewById(int)
*/
@Nullable
- public View findViewById(@IdRes int id) {
+ public <T extends View> T findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 35276cc..5e6ace7 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -41,6 +41,7 @@
import android.view.IWindow;
import android.view.View;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IntPair;
import java.util.ArrayList;
@@ -126,6 +127,8 @@
final Handler mHandler;
+ final Handler.Callback mCallback;
+
boolean mIsEnabled;
int mRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK;
@@ -217,12 +220,12 @@
// is now off an exception will be thrown. We want to have the exception
// enforcement to guard against apps that fire unnecessary accessibility
// events when accessibility is off.
- mHandler.obtainMessage(MyHandler.MSG_SET_STATE, state, 0).sendToTarget();
+ mHandler.obtainMessage(MyCallback.MSG_SET_STATE, state, 0).sendToTarget();
}
@Override
public void notifyServicesStateChanged() {
- mHandler.obtainMessage(MyHandler.MSG_NOTIFY_SERVICES_STATE_CHANGED).sendToTarget();
+ mHandler.obtainMessage(MyCallback.MSG_NOTIFY_SERVICES_STATE_CHANGED).sendToTarget();
}
@Override
@@ -271,7 +274,8 @@
public AccessibilityManager(Context context, IAccessibilityManager service, int userId) {
// Constructor can't be chained because we can't create an instance of an inner class
// before calling another constructor.
- mHandler = new MyHandler(context.getMainLooper());
+ mCallback = new MyCallback();
+ mHandler = new Handler(context.getMainLooper(), mCallback);
mUserId = userId;
synchronized (mLock) {
tryConnectToServiceLocked(service);
@@ -288,6 +292,7 @@
* @hide
*/
public AccessibilityManager(Handler handler, IAccessibilityManager service, int userId) {
+ mCallback = new MyCallback();
mHandler = handler;
mUserId = userId;
synchronized (mLock) {
@@ -303,6 +308,14 @@
}
/**
+ * @hide
+ */
+ @VisibleForTesting
+ public Handler.Callback getCallback() {
+ return mCallback;
+ }
+
+ /**
* Returns if the accessibility in the system is enabled.
*
* @return True if accessibility is enabled, false otherwise.
@@ -711,15 +724,15 @@
mIsHighTextContrastEnabled = highTextContrastEnabled;
if (wasEnabled != enabled) {
- mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED);
+ mHandler.sendEmptyMessage(MyCallback.MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED);
}
if (wasTouchExplorationEnabled != touchExplorationEnabled) {
- mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_EXPLORATION_STATE_CHANGED);
+ mHandler.sendEmptyMessage(MyCallback.MSG_NOTIFY_EXPLORATION_STATE_CHANGED);
}
if (wasHighTextContrastEnabled != highTextContrastEnabled) {
- mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED);
+ mHandler.sendEmptyMessage(MyCallback.MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED);
}
}
@@ -960,19 +973,15 @@
}
}
- private final class MyHandler extends Handler {
+ private final class MyCallback implements Handler.Callback {
public static final int MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED = 1;
public static final int MSG_NOTIFY_EXPLORATION_STATE_CHANGED = 2;
public static final int MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED = 3;
public static final int MSG_SET_STATE = 4;
public static final int MSG_NOTIFY_SERVICES_STATE_CHANGED = 5;
- public MyHandler(Looper looper) {
- super(looper, null, false);
- }
-
@Override
- public void handleMessage(Message message) {
+ public boolean handleMessage(Message message) {
switch (message.what) {
case MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED: {
handleNotifyAccessibilityStateChanged();
@@ -998,6 +1007,7 @@
}
} break;
}
+ return true;
}
}
}
diff --git a/core/java/android/view/autofill/AutoFillId.aidl b/core/java/android/view/autofill/AutoFillId.aidl
deleted file mode 100644
index fc57ce7..0000000
--- 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 081fb02..0000000
--- 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 c508ba4..0000000
--- 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 4774d8f..0000000
--- 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/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 19980fb..07fad60 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -44,6 +44,7 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.List;
+import java.util.Objects;
/**
* App entry point to the AutoFill Framework.
@@ -91,6 +92,8 @@
*/
public static final String EXTRA_DATA_EXTRAS = "android.view.autofill.extra.DATA_EXTRAS";
+ static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData";
+
// Public flags start from the lowest bit
/**
* Indicates autofill was explicitly requested by the user.
@@ -115,6 +118,9 @@
private boolean mHasSession;
private boolean mEnabled;
+ /** If a view changes to this mapping the autofill operation was successful */
+ @Nullable private ParcelableMap mLastAutofilledData;
+
/** @hide */
public interface AutofillClient {
/**
@@ -160,7 +166,31 @@
}
/**
- * Checkes whether autofill is enabled for the current user.
+ * Restore state after activity lifecycle
+ *
+ * @param savedInstanceState The state to be restored
+ *
+ * {@hide}
+ */
+ public void onRestoreInstanceState(Bundle savedInstanceState) {
+ mLastAutofilledData = savedInstanceState.getParcelable(LAST_AUTOFILLED_DATA_TAG);
+ }
+
+ /**
+ * Save state before activity lifecycle
+ *
+ * @param outState Place to store the state
+ *
+ * {@hide}
+ */
+ public void onSaveInstanceState(Bundle outState) {
+ if (mLastAutofilledData != null) {
+ outState.putParcelable(LAST_AUTOFILLED_DATA_TAG, mLastAutofilledData);
+ }
+ }
+
+ /**
+ * Checks whether autofill is enabled for the current user.
*
* <p>Typically used to determine whether the option to explicitly request autofill should
* be offered - see {@link #requestAutofill(View)}.
@@ -311,12 +341,43 @@
* @param view view whose value changed.
*/
public void notifyValueChanged(View view) {
+ AutofillId id = null;
+ boolean valueWasRead = false;
+ AutofillValue value = null;
+
+ // If the session is gone some fields might still be highlighted, hence we have to remove
+ // the isAutofilled property even if no sessions are active.
+ if (mLastAutofilledData == null) {
+ view.setAutofilled(false);
+ } else {
+ id = getAutofillId(view);
+ if (mLastAutofilledData.containsKey(id)) {
+ value = view.getAutofillValue();
+ valueWasRead = true;
+
+ if (Objects.equals(mLastAutofilledData.get(id), value)) {
+ view.setAutofilled(true);
+ } else {
+ view.setAutofilled(false);
+ mLastAutofilledData.remove(id);
+ }
+ } else {
+ view.setAutofilled(false);
+ }
+ }
+
if (!mEnabled || !mHasSession) {
return;
}
- final AutofillId id = getAutofillId(view);
- final AutofillValue value = view.getAutofillValue();
+ if (id == null) {
+ id = getAutofillId(view);
+ }
+
+ if (!valueWasRead) {
+ value = view.getAutofillValue();
+ }
+
updateSession(id, null, value, FLAG_VALUE_CHANGED);
}
@@ -535,6 +596,23 @@
}
}
+ /**
+ * Sets a view as autofilled if the current value is the {code targetValue}.
+ *
+ * @param view The view that is to be autofilled
+ * @param targetValue The value we want to fill into view
+ */
+ private void setAutofilledIfValuesIs(@NonNull View view, @Nullable AutofillValue targetValue) {
+ AutofillValue currentValue = view.getAutofillValue();
+ if (Objects.equals(currentValue, targetValue)) {
+ if (mLastAutofilledData == null) {
+ mLastAutofilledData = new ParcelableMap(1);
+ }
+ mLastAutofilledData.put(getAutofillId(view), targetValue);
+ view.setAutofilled(true);
+ }
+ }
+
private void handleAutofill(IBinder windowToken, List<AutofillId> ids,
List<AutofillValue> values) {
final View root = WindowManagerGlobal.getInstance().getWindowView(windowToken);
@@ -568,7 +646,19 @@
}
valuesByParent.put(id.getVirtualChildId(), value);
} else {
+ // Mark the view as to be autofilled with 'value'
+ if (mLastAutofilledData == null) {
+ mLastAutofilledData = new ParcelableMap(itemCount - i);
+ }
+ mLastAutofilledData.put(id, value);
+
view.autofill(value);
+
+ // Set as autofilled if the values match now, e.g. when the value was updated
+ // synchronously.
+ // If autofill happens async, the view is set to autofilled in notifyValueChanged.
+ setAutofilledIfValuesIs(view, value);
+
numApplied++;
}
}
diff --git a/core/java/android/view/autofill/ParcelableMap.java b/core/java/android/view/autofill/ParcelableMap.java
new file mode 100644
index 0000000..f97b1a0
--- /dev/null
+++ b/core/java/android/view/autofill/ParcelableMap.java
@@ -0,0 +1,73 @@
+/*
+ * 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 android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A parcelable HashMap for {@link AutofillId} and {@link AutofillValue}
+ *
+ * {@hide}
+ */
+class ParcelableMap extends HashMap<AutofillId, AutofillValue> implements Parcelable {
+ ParcelableMap(int size) {
+ super(size);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(size());
+
+ for (Map.Entry<AutofillId, AutofillValue> entry : entrySet()) {
+ dest.writeParcelable(entry.getKey(), 0);
+ dest.writeParcelable(entry.getValue(), 0);
+ }
+ }
+
+ public static final Parcelable.Creator<ParcelableMap> CREATOR =
+ new Parcelable.Creator<ParcelableMap>() {
+ @Override
+ public ParcelableMap createFromParcel(Parcel source) {
+ int size = source.readInt();
+
+ ParcelableMap map = new ParcelableMap(size);
+
+ for (int i = 0; i < size; i++) {
+ AutofillId key = source.readParcelable(null);
+ AutofillValue value = source.readParcelable(null);
+
+ map.put(key, value);
+ }
+
+ return map;
+ }
+
+ @Override
+ public ParcelableMap[] newArray(int size) {
+ return new ParcelableMap[size];
+ }
+ };
+}
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
index ab4cce4..2e8faee 100644
--- a/core/java/android/widget/ListPopupWindow.java
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -76,6 +76,7 @@
private boolean mDropDownVerticalOffsetSet;
private boolean mIsAnimatedFromAnchor = true;
private boolean mOverlapAnchor;
+ private boolean mOverlapAnchorSet;
private int mDropDownGravity = Gravity.NO_GRAVITY;
@@ -681,7 +682,9 @@
mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
mPopup.setTouchInterceptor(mTouchInterceptor);
mPopup.setEpicenterBounds(mEpicenterBounds);
- mPopup.setOverlapAnchor(mOverlapAnchor);
+ if (mOverlapAnchorSet) {
+ mPopup.setOverlapAnchor(mOverlapAnchor);
+ }
mPopup.showAsDropDown(getAnchorView(), mDropDownHorizontalOffset,
mDropDownVerticalOffset, mDropDownGravity);
mDropDownList.setSelection(ListView.INVALID_POSITION);
@@ -1259,6 +1262,7 @@
* @hide
*/
public void setOverlapAnchor(boolean overlap) {
+ mOverlapAnchorSet = true;
mOverlapAnchor = overlap;
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1b60ebc..7d9253b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -598,6 +598,11 @@
private Layout mLayout;
private boolean mLocalesChanged = false;
+ // True if setKeyListener() has been explicitly called
+ private boolean mListenerChanged = false;
+ // True if internationalized input should be used for numbers and date and time.
+ private final boolean mUseInternationalizedInput;
+
@ViewDebug.ExportedProperty(category = "text")
private int mGravity = Gravity.TOP | Gravity.START;
private boolean mHorizontallyScrolling;
@@ -1356,6 +1361,9 @@
final boolean numberPasswordInputType = variation
== (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
+ mUseInternationalizedInput =
+ context.getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O;
+
if (inputMethod != null) {
Class<?> c;
@@ -1398,15 +1406,11 @@
mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
} else if (numeric != 0) {
createEditorIfNeeded();
- mEditor.mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
- (numeric & DECIMAL) != 0);
- inputType = EditorInfo.TYPE_CLASS_NUMBER;
- if ((numeric & SIGNED) != 0) {
- inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
- }
- if ((numeric & DECIMAL) != 0) {
- inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
- }
+ mEditor.mKeyListener = DigitsKeyListener.getInstance(
+ mUseInternationalizedInput ? getTextLocale() : null,
+ (numeric & SIGNED) != 0,
+ (numeric & DECIMAL) != 0);
+ inputType = mEditor.mKeyListener.getInputType();
mEditor.mInputType = inputType;
} else if (autotext || autocap != -1) {
TextKeyListener.Capitalize cap;
@@ -2308,19 +2312,13 @@
* @attr ref android.R.styleable#TextView_autoText
*/
public void setKeyListener(KeyListener input) {
+ mListenerChanged = true;
setKeyListenerOnly(input);
fixFocusableAndClickableSettings();
if (input != null) {
createEditorIfNeeded();
- try {
- mEditor.mInputType = mEditor.mKeyListener.getInputType();
- } catch (IncompatibleClassChangeError e) {
- mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
- }
- // Change inputType, without affecting transformation.
- // No need to applySingleLine since mSingleLine is unchanged.
- setInputTypeSingleLine(mSingleLine);
+ setInputTypeFromEditor();
} else {
if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
}
@@ -2329,6 +2327,17 @@
if (imm != null) imm.restartInput(this);
}
+ private void setInputTypeFromEditor() {
+ try {
+ mEditor.mInputType = mEditor.mKeyListener.getInputType();
+ } catch (IncompatibleClassChangeError e) {
+ mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
+ }
+ // Change inputType, without affecting transformation.
+ // No need to applySingleLine since mSingleLine is unchanged.
+ setInputTypeSingleLine(mSingleLine);
+ }
+
private void setKeyListenerOnly(KeyListener input) {
if (mEditor == null && input == null) return; // null is the default value
@@ -3390,6 +3399,29 @@
return mTextPaint.getTextLocales();
}
+ private void changeListenerLocaleTo(@NonNull Locale locale) {
+ if (mListenerChanged) {
+ // If a listener has been explicitly set, don't change it. We may break something.
+ return;
+ }
+ if (mEditor != null) {
+ KeyListener listener = mEditor.mKeyListener;
+ if (listener instanceof DigitsKeyListener) {
+ listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener);
+ } else if (listener instanceof DateKeyListener) {
+ listener = DateKeyListener.getInstance(locale);
+ } else if (listener instanceof TimeKeyListener) {
+ listener = TimeKeyListener.getInstance(locale);
+ } else if (listener instanceof DateTimeKeyListener) {
+ listener = DateTimeKeyListener.getInstance(locale);
+ } else {
+ return;
+ }
+ setKeyListenerOnly(listener);
+ setInputTypeFromEditor();
+ }
+ }
+
/**
* Set the default {@link LocaleList} of the text in this TextView to a one-member list
* containing just the given value.
@@ -3401,6 +3433,7 @@
public void setTextLocale(@NonNull Locale locale) {
mLocalesChanged = true;
mTextPaint.setTextLocale(locale);
+ changeListenerLocaleTo(locale);
if (mLayout != null) {
nullLayouts();
requestLayout();
@@ -3422,6 +3455,7 @@
public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) {
mLocalesChanged = true;
mTextPaint.setTextLocales(locales);
+ changeListenerLocaleTo(locales.get(0));
if (mLayout != null) {
nullLayouts();
requestLayout();
@@ -3433,7 +3467,9 @@
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (!mLocalesChanged) {
- mTextPaint.setTextLocales(LocaleList.getDefault());
+ final LocaleList locales = LocaleList.getDefault();
+ mTextPaint.setTextLocales(locales);
+ changeListenerLocaleTo(locales.get(0));
if (mLayout != null) {
nullLayouts();
requestLayout();
@@ -5567,26 +5603,35 @@
input = TextKeyListener.getInstance(autotext, cap);
} else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
input = DigitsKeyListener.getInstance(
+ mUseInternationalizedInput ? getTextLocale() : null,
(type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
(type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
+ if (mUseInternationalizedInput) {
+ type = input.getInputType(); // Override type, if necessary for i18n.
+ }
} else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
+ final Locale locale = mUseInternationalizedInput ? getTextLocale() : null;
switch (type & EditorInfo.TYPE_MASK_VARIATION) {
case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
- input = DateKeyListener.getInstance();
+ input = DateKeyListener.getInstance(locale);
break;
case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
- input = TimeKeyListener.getInstance();
+ input = TimeKeyListener.getInstance(locale);
break;
default:
- input = DateTimeKeyListener.getInstance();
+ input = DateTimeKeyListener.getInstance(locale);
break;
}
+ if (mUseInternationalizedInput) {
+ type = input.getInputType(); // Override type, if necessary for i18n.
+ }
} else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
input = DialerKeyListener.getInstance();
} else {
input = TextKeyListener.getInstance();
}
setRawInputType(type);
+ mListenerChanged = false;
if (direct) {
createEditorIfNeeded();
mEditor.mKeyListener = input;
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 1cad7c7..0000000
--- 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 622b708..3f1c9ad 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -530,9 +530,6 @@
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 73b62a5..096fcb8 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.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 @@
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 @@
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 LogisticRegressionAppRanker mRanker;
- 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;
-
- 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 @@
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 @@
}
}
- 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 @@
// 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 @@
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 @@
// 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 @@
}
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;
}
+ 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);
+ }
+
+ @Override
+ public String toString() {
+ return "ScoredTarget{" + componentInfo
+ + " lastTimeUsed: " + lastTimeUsed
+ + " timeSpent: " + timeSpent
+ + " launchCount: " + launchCount
+ + " chooserCount: " + chooserCount
+ + " selectProb: " + selectProb
+ + "}";
+ }
+
+ 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);
+ }
+
+ public ArrayMap<String, Float> getFeatures() {
+ return features;
+ }
+ }
+
public void updateChooserCounts(String packageName, int userId, String action) {
if (mUsm != null) {
mUsm.reportChooserSelection(packageName, userId, mContentType, mAnnotations, action);
}
}
- // 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.");
- }
+ if (mScoredTargets == null || componentName == null ||
+ !mScoredTargets.containsKey(componentName)) {
+ return;
+ }
+ ScoredTarget selected = mScoredTargets.get(componentName);
+ for (ComponentName targetComponent : mScoredTargets.keySet()) {
+ if (targetComponent.equals(componentName)) {
+ continue;
+ }
+ 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);
}
}
+ mRanker.commitUpdate();
}
- // 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();
- }
- if (DEBUG) {
- Log.d(TAG, "Unbinded Resolver Ranker.");
- }
- }
+ 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";
- // 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.");
- }
+ // 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;
+
+ public LogisticRegressionAppRanker(Context context) {
+ mParamSharedPref = getParamSharedPref(context);
+ initModel();
+ }
+
+ public 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)));
+ }
+
+ public void update(ArrayMap<String, Float> target, float predict, boolean isSelected) {
+ if (target == null) {
return;
}
- }
- Intent intent = resolveRankerService();
- if (intent == null) {
- return;
- }
- 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");
- continue;
+ 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, "Succeeded to retrieve a ranker: " + componentName);
+ Log.d(TAG, "Weights: " + mFeatureWeights + " Bias: " + mBias);
}
- intent.setComponent(componentName);
- return intent;
- }
- return null;
- }
-
- // 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);
- }
-
- private class ResolverRankerServiceConnection implements ServiceConnection {
- private final CountDownLatch mConnectSignal;
-
- public ResolverRankerServiceConnection(CountDownLatch connectSignal) {
- mConnectSignal = connectSignal;
}
- 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 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();
+ }
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
+ 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, "onServiceConnected: " + name);
+ Log.d(TAG, "Context Package Name: " + context.getPackageName());
}
- synchronized (mLock) {
- mRanker = IResolverRankerService.Stub.asInterface(service);
- mConnectSignal.countDown();
- }
+ 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);
}
- @Override
- public void onServiceDisconnected(ComponentName name) {
- if (DEBUG) {
- Log.d(TAG, "onServiceDisconnected: " + name);
- }
- synchronized (mLock) {
- destroy();
+ 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));
}
}
-
- public void destroy() {
- synchronized (mLock) {
- mRanker = null;
- }
- }
- }
-
- 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) {
- 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);
- }
- }
- 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;
- }
- return false;
}
}
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index e8bebb7..4071ff4 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.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 @@
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);
+ mResolverComparator = new ResolverComparator(mContext, mTargetIntent, mReferrerPackage);
}
- 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.compute(inputList);
+ Collections.sort(inputList, mResolverComparator);
}
private static boolean isSameResolvedComponent(ResolveInfo a,
@@ -263,7 +233,7 @@
@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 @@
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 f987a9f..caf35b3 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -55,8 +55,8 @@
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/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index b263657..d19ffad 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -114,7 +114,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 152 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 153 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS = 2000;
@@ -2006,107 +2006,92 @@
* State for keeping track of two DurationTimers with different TimeBases, presumably where one
* TimeBase is effectively a subset of the other.
*/
- public static class DualTimer {
- // mMainTimer typically tracks the total time. May be pooled (but since it's a durationTimer,
- // it also has the unpooled getTotalDurationMsLocked() for STATS_SINCE_CHARGED).
- private final DurationTimer mMainTimer;
+ public static class DualTimer extends DurationTimer {
+ // This class both is a DurationTimer and also holds a second DurationTimer.
+ // The main timer (this) typically tracks the total time. It may be pooled (but since it's a
+ // durationTimer, it also has the unpooled getTotalDurationMsLocked() for
+ // STATS_SINCE_CHARGED).
// mSubTimer typically tracks only part of the total time, such as background time, as
// determined by a subTimeBase. It is NOT pooled.
private final DurationTimer mSubTimer;
/**
- * Creates a DualTimer to hold a mMainTimer and a mSubTimer.
- * The mMainTimer is based on the given timeBase and timerPool.
+ * Creates a DualTimer to hold a main timer (this) and a mSubTimer.
+ * The main timer (this) is based on the given timeBase and timerPool.
* The mSubTimer is based on the given subTimeBase. The mSubTimer is not pooled, even if
- * the mMainTimer is.
+ * the main timer is.
*/
public DualTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
TimeBase timeBase, TimeBase subTimeBase, Parcel in) {
- mMainTimer = new DurationTimer(clocks, uid, type, timerPool, timeBase, in);
+ super(clocks, uid, type, timerPool, timeBase, in);
mSubTimer = new DurationTimer(clocks, uid, type, null, subTimeBase, in);
}
/**
- * Creates a DualTimer to hold a mMainTimer and a mSubTimer.
- * The mMainTimer is based on the given timeBase and timerPool.
+ * Creates a DualTimer to hold a main timer (this) and a mSubTimer.
+ * The main timer (this) is based on the given timeBase and timerPool.
* The mSubTimer is based on the given subTimeBase. The mSubTimer is not pooled, even if
- * the mMainTimer is.
+ * the main timer is.
*/
public DualTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
TimeBase timeBase, TimeBase subTimeBase) {
- mMainTimer = new DurationTimer(clocks, uid, type, timerPool, timeBase);
+ super(clocks, uid, type, timerPool, timeBase);
mSubTimer = new DurationTimer(clocks, uid, type, null, subTimeBase);
}
- /** Get the main timer. */
- public DurationTimer getMainTimer() {
- return mMainTimer;
- }
-
/** Get the secondary timer. */
+ @Override
public DurationTimer getSubTimer() {
return mSubTimer;
}
+ @Override
public void startRunningLocked(long elapsedRealtimeMs) {
- mMainTimer.startRunningLocked(elapsedRealtimeMs);
+ super.startRunningLocked(elapsedRealtimeMs);
mSubTimer.startRunningLocked(elapsedRealtimeMs);
}
+ @Override
public void stopRunningLocked(long elapsedRealtimeMs) {
- mMainTimer.stopRunningLocked(elapsedRealtimeMs);
+ super.stopRunningLocked(elapsedRealtimeMs);
mSubTimer.stopRunningLocked(elapsedRealtimeMs);
}
+ @Override
public void stopAllRunningLocked(long elapsedRealtimeMs) {
- mMainTimer.stopAllRunningLocked(elapsedRealtimeMs);
+ super.stopAllRunningLocked(elapsedRealtimeMs);
mSubTimer.stopAllRunningLocked(elapsedRealtimeMs);
}
- public void setMark(long elapsedRealtimeMs) {
- mMainTimer.setMark(elapsedRealtimeMs);
- mSubTimer.setMark(elapsedRealtimeMs);
- }
-
+ @Override
public boolean reset(boolean detachIfReset) {
boolean active = false;
- active |= !mMainTimer.reset(detachIfReset);
+ active |= !super.reset(detachIfReset);
active |= !mSubTimer.reset(detachIfReset);
return !active;
}
+ @Override
public void detach() {
- mMainTimer.detach();
+ super.detach();
mSubTimer.detach();
}
- /**
- * Writes a possibly null DualTimer to a Parcel.
- *
- * @param out the Parcel to which to write.
- * @param t a DualTimer, or null.
- */
- public static void writeDualTimerToParcel(Parcel out, DualTimer t, long elapsedRealtimeUs) {
- if (t != null) {
- out.writeInt(1);
- t.writeToParcel(out, elapsedRealtimeUs);
- } else {
- out.writeInt(0);
- }
- }
-
+ @Override
public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
- mMainTimer.writeToParcel(out, elapsedRealtimeUs);
+ super.writeToParcel(out, elapsedRealtimeUs);
mSubTimer.writeToParcel(out, elapsedRealtimeUs);
}
+ @Override
public void writeSummaryFromParcelLocked(Parcel out, long elapsedRealtimeUs) {
- mMainTimer.writeSummaryFromParcelLocked(out, elapsedRealtimeUs);
+ super.writeSummaryFromParcelLocked(out, elapsedRealtimeUs);
mSubTimer.writeSummaryFromParcelLocked(out, elapsedRealtimeUs);
}
+ @Override
public void readSummaryFromParcelLocked(Parcel in) {
- mMainTimer.readSummaryFromParcelLocked(in);
+ super.readSummaryFromParcelLocked(in);
mSubTimer.readSummaryFromParcelLocked(in);
}
}
@@ -5488,7 +5473,7 @@
/**
* The statistics we have collected for this uid's jobs.
*/
- final OverflowArrayMap<StopwatchTimer> mJobStats;
+ final OverflowArrayMap<DualTimer> mJobStats;
/**
* The statistics we have collected for this uid's sensor activations.
@@ -5533,10 +5518,10 @@
mBsi.mOnBatteryTimeBase);
}
};
- mJobStats = mBsi.new OverflowArrayMap<StopwatchTimer>(uid) {
- @Override public StopwatchTimer instantiateObject() {
- return new StopwatchTimer(mBsi.mClocks, Uid.this, JOB, null,
- mBsi.mOnBatteryTimeBase);
+ mJobStats = mBsi.new OverflowArrayMap<DualTimer>(uid) {
+ @Override public DualTimer instantiateObject() {
+ return new DualTimer(mBsi.mClocks, Uid.this, JOB, null,
+ mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase);
}
};
@@ -5918,7 +5903,7 @@
if (mWifiScanTimer == null) {
return 0;
}
- return mWifiScanTimer.getMainTimer().getTotalTimeLocked(elapsedRealtimeUs, which);
+ return mWifiScanTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@Override
@@ -5926,12 +5911,12 @@
if (mWifiScanTimer == null) {
return 0;
}
- return mWifiScanTimer.getMainTimer().getCountLocked(which);
+ return mWifiScanTimer.getCountLocked(which);
}
@Override
public int getWifiScanBackgroundCount(int which) {
- if (mWifiScanTimer == null) {
+ if (mWifiScanTimer == null || mWifiScanTimer.getSubTimer() == null) {
return 0;
}
return mWifiScanTimer.getSubTimer().getCountLocked(which);
@@ -5943,12 +5928,12 @@
return 0;
}
final long elapsedRealtimeMs = (elapsedRealtimeUs + 500) / 1000;
- return mWifiScanTimer.getMainTimer().getTotalDurationMsLocked(elapsedRealtimeMs) * 1000;
+ return mWifiScanTimer.getTotalDurationMsLocked(elapsedRealtimeMs) * 1000;
}
@Override
public long getWifiScanBackgroundTime(final long elapsedRealtimeUs) {
- if (mWifiScanTimer == null) {
+ if (mWifiScanTimer == null || mWifiScanTimer.getSubTimer() == null) {
return 0;
}
final long elapsedRealtimeMs = (elapsedRealtimeUs + 500) / 1000;
@@ -6008,10 +5993,7 @@
@Override
public Timer getBluetoothScanTimer() {
- if (mBluetoothScanTimer == null) {
- return null;
- }
- return mBluetoothScanTimer.getMainTimer();
+ return mBluetoothScanTimer;
}
@Override
@@ -6361,9 +6343,9 @@
}
}
mSyncStats.cleanup();
- final ArrayMap<String, StopwatchTimer> jobStats = mJobStats.getMap();
+ final ArrayMap<String, DualTimer> jobStats = mJobStats.getMap();
for (int ij=jobStats.size()-1; ij>=0; ij--) {
- StopwatchTimer timer = jobStats.valueAt(ij);
+ DualTimer timer = jobStats.valueAt(ij);
if (timer.reset(false)) {
jobStats.removeAt(ij);
timer.detach();
@@ -6531,12 +6513,12 @@
Timer.writeTimerToParcel(out, timer, elapsedRealtimeUs);
}
- final ArrayMap<String, StopwatchTimer> jobStats = mJobStats.getMap();
+ final ArrayMap<String, DualTimer> jobStats = mJobStats.getMap();
int NJ = jobStats.size();
out.writeInt(NJ);
for (int ij=0; ij<NJ; ij++) {
out.writeString(jobStats.keyAt(ij));
- StopwatchTimer timer = jobStats.valueAt(ij);
+ DualTimer timer = jobStats.valueAt(ij);
Timer.writeTimerToParcel(out, timer, elapsedRealtimeUs);
}
@@ -6756,8 +6738,8 @@
for (int j = 0; j < numJobs; j++) {
String jobName = in.readString();
if (in.readInt() != 0) {
- mJobStats.add(jobName, new StopwatchTimer(mBsi.mClocks, Uid.this, JOB, null,
- timeBase, in));
+ mJobStats.add(jobName, new DualTimer(mBsi.mClocks, Uid.this, JOB, null,
+ mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase, in));
}
}
@@ -7196,15 +7178,12 @@
}
void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) {
- DualTimer.writeDualTimerToParcel(out, mTimer, elapsedRealtimeUs);
+ Timer.writeTimerToParcel(out, mTimer, elapsedRealtimeUs);
}
@Override
public Timer getSensorTime() {
- if (mTimer == null) {
- return null;
- }
- return mTimer.getMainTimer();
+ return mTimer;
}
@Override
@@ -8023,7 +8002,7 @@
}
public void readJobSummaryFromParcelLocked(String name, Parcel in) {
- StopwatchTimer timer = mJobStats.instantiateObject();
+ DualTimer timer = mJobStats.instantiateObject();
timer.readSummaryFromParcelLocked(in);
mJobStats.add(name, timer);
}
@@ -8084,14 +8063,14 @@
}
public void noteStartJobLocked(String name, long elapsedRealtimeMs) {
- StopwatchTimer t = mJobStats.startObject(name);
+ DualTimer t = mJobStats.startObject(name);
if (t != null) {
t.startRunningLocked(elapsedRealtimeMs);
}
}
public void noteStopJobLocked(String name, long elapsedRealtimeMs) {
- StopwatchTimer t = mJobStats.stopObject(name);
+ DualTimer t = mJobStats.stopObject(name);
if (t != null) {
t.stopRunningLocked(elapsedRealtimeMs);
}
@@ -9149,7 +9128,7 @@
final Uid uid = mUidStats.valueAt(i);
// Sum the total scan power for all apps.
- totalScanTimeMs += uid.mWifiScanTimer.getMainTimer().getTimeSinceMarkLocked(
+ totalScanTimeMs += uid.mWifiScanTimer.getTimeSinceMarkLocked(
elapsedRealtimeMs * 1000) / 1000;
// Sum the total time holding wifi lock for all apps.
@@ -9170,7 +9149,7 @@
for (int i = 0; i < uidStatsSize; i++) {
final Uid uid = mUidStats.valueAt(i);
- long scanTimeSinceMarkMs = uid.mWifiScanTimer.getMainTimer().getTimeSinceMarkLocked(
+ long scanTimeSinceMarkMs = uid.mWifiScanTimer.getTimeSinceMarkLocked(
elapsedRealtimeMs * 1000) / 1000;
if (scanTimeSinceMarkMs > 0) {
// Set the new mark so that next time we get new data since this point.
@@ -9444,7 +9423,7 @@
continue;
}
- totalScanTimeMs += u.mBluetoothScanTimer.getMainTimer().getTimeSinceMarkLocked(
+ totalScanTimeMs += u.mBluetoothScanTimer.getTimeSinceMarkLocked(
elapsedRealtimeMs * 1000) / 1000;
}
@@ -9465,7 +9444,7 @@
continue;
}
- long scanTimeSinceMarkMs = u.mBluetoothScanTimer.getMainTimer().getTimeSinceMarkLocked(
+ long scanTimeSinceMarkMs = u.mBluetoothScanTimer.getTimeSinceMarkLocked(
elapsedRealtimeMs * 1000) / 1000;
if (scanTimeSinceMarkMs > 0) {
// Set the new mark so that next time we get new data since this point.
@@ -11526,7 +11505,7 @@
syncStats.valueAt(is).writeSummaryFromParcelLocked(out, NOWREAL_SYS);
}
- final ArrayMap<String, StopwatchTimer> jobStats = u.mJobStats.getMap();
+ final ArrayMap<String, DualTimer> jobStats = u.mJobStats.getMap();
int NJ = jobStats.size();
out.writeInt(NJ);
for (int ij=0; ij<NJ; ij++) {
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index f4dd5a6..2c8e4e0 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -236,6 +236,29 @@
return false;
}
+ public static boolean contains(@Nullable char[] array, char value) {
+ if (array == null) return false;
+ for (char element : array) {
+ if (element == value) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Test if all {@code check} items are contained in {@code array}.
+ */
+ public static <T> boolean containsAll(@Nullable char[] array, char[] check) {
+ if (check == null) return true;
+ for (char checkItem : check) {
+ if (!contains(array, checkItem)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
public static long total(@Nullable long[] array) {
long total = 0;
if (array != null) {
diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp
index 1a35330..214d97c 100644
--- a/core/jni/android/graphics/Shader.cpp
+++ b/core/jni/android/graphics/Shader.cpp
@@ -68,11 +68,18 @@
}
sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
- sk_sp<SkShader> shader = image->makeShader(
- (SkShader::TileMode)tileModeX, (SkShader::TileMode)tileModeY, matrix);
+ sk_sp<SkShader> baseShader = image->makeShader(
+ (SkShader::TileMode)tileModeX, (SkShader::TileMode)tileModeY);
- ThrowIAE_IfNull(env, shader.get());
- return reinterpret_cast<jlong>(shader.release());
+ SkShader* shader;
+ if (matrix) {
+ shader = baseShader->makeWithLocalMatrix(*matrix).release();
+ } else {
+ shader = baseShader.release();
+ }
+
+ ThrowIAE_IfNull(env, shader);
+ return reinterpret_cast<jlong>(shader);
}
///////////////////////////////////////////////////////////////////////////////////////////////
@@ -95,9 +102,16 @@
#error Need to convert float array to SkScalar array before calling the following function.
#endif
- SkShader* shader = SkGradientShader::MakeLinear(pts,
+ sk_sp<SkShader> baseShader(SkGradientShader::MakeLinear(pts,
reinterpret_cast<const SkColor*>(colorValues), pos, count,
- static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, matrix).release();
+ static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, NULL));
+
+ SkShader* shader;
+ if (matrix) {
+ shader = baseShader->makeWithLocalMatrix(*matrix).release();
+ } else {
+ shader = baseShader.release();
+ }
env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues), JNI_ABORT);
ThrowIAE_IfNull(env, shader);
@@ -116,8 +130,15 @@
colors[0] = color0;
colors[1] = color1;
- SkShader* s = SkGradientShader::MakeLinear(pts, colors, NULL, 2,
- static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, matrix).release();
+ sk_sp<SkShader> baseShader(SkGradientShader::MakeLinear(pts, colors, NULL, 2,
+ static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, NULL));
+
+ SkShader* s;
+ if (matrix) {
+ s = baseShader->makeWithLocalMatrix(*matrix).release();
+ } else {
+ s = baseShader.release();
+ }
ThrowIAE_IfNull(env, s);
return reinterpret_cast<jlong>(s);
@@ -141,9 +162,17 @@
#error Need to convert float array to SkScalar array before calling the following function.
#endif
- SkShader* shader = SkGradientShader::MakeRadial(center, radius,
+ sk_sp<SkShader> baseShader = SkGradientShader::MakeRadial(center, radius,
reinterpret_cast<const SkColor*>(colorValues), pos, count,
- static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, matrix).release();
+ static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, NULL);
+
+ SkShader* shader;
+ if (matrix) {
+ shader = baseShader->makeWithLocalMatrix(*matrix).release();
+ } else {
+ shader = baseShader.release();
+ }
+
env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues),
JNI_ABORT);
@@ -161,10 +190,17 @@
colors[0] = color0;
colors[1] = color1;
- SkShader* s = SkGradientShader::MakeRadial(center, radius, colors, NULL, 2,
- static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, matrix).release();
- ThrowIAE_IfNull(env, s);
- return reinterpret_cast<jlong>(s);
+ sk_sp<SkShader> baseShader = SkGradientShader::MakeRadial(center, radius, colors, NULL, 2,
+ static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, NULL);
+
+ SkShader* shader;
+ if (matrix) {
+ shader = baseShader->makeWithLocalMatrix(*matrix).release();
+ } else {
+ shader = baseShader.release();
+ }
+ ThrowIAE_IfNull(env, shader);
+ return reinterpret_cast<jlong>(shader);
}
///////////////////////////////////////////////////////////////////////////////
@@ -182,8 +218,17 @@
#error Need to convert float array to SkScalar array before calling the following function.
#endif
- SkShader* shader = SkGradientShader::MakeSweep(x, y, reinterpret_cast<const SkColor*>(colors),
- pos, count, sGradientShaderFlags, matrix).release();
+ sk_sp<SkShader> baseShader = SkGradientShader::MakeSweep(x, y,
+ reinterpret_cast<const SkColor*>(colors), pos, count,
+ sGradientShaderFlags, NULL);
+
+ SkShader* shader;
+ if (matrix) {
+ shader = baseShader->makeWithLocalMatrix(*matrix).release();
+ } else {
+ shader = baseShader.release();
+ }
+
env->ReleaseIntArrayElements(jcolors, const_cast<jint*>(colors),
JNI_ABORT);
ThrowIAE_IfNull(env, shader);
@@ -196,10 +241,18 @@
SkColor colors[2];
colors[0] = color0;
colors[1] = color1;
- SkShader* s = SkGradientShader::MakeSweep(x, y, colors, NULL, 2,
- sGradientShaderFlags, matrix).release();
- ThrowIAE_IfNull(env, s);
- return reinterpret_cast<jlong>(s);
+
+ sk_sp<SkShader> baseShader = SkGradientShader::MakeSweep(x, y, colors,
+ NULL, 2, sGradientShaderFlags, NULL);
+
+ SkShader* shader;
+ if (matrix) {
+ shader = baseShader->makeWithLocalMatrix(*matrix).release();
+ } else {
+ shader = baseShader.release();
+ }
+ ThrowIAE_IfNull(env, shader);
+ return reinterpret_cast<jlong>(shader);
}
///////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index dae4310..520302e 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -282,6 +282,25 @@
return mgr->configureDirectChannel(channelHandle, sensorHandle, rate);
}
+static jint nativeSetOperationParameter(JNIEnv *_env, jclass _this, jlong sensorManager,
+ jint type, jfloatArray floats, jintArray ints) {
+ SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
+ Vector<float> floatVector;
+ Vector<int32_t> int32Vector;
+
+ if (floats != nullptr) {
+ floatVector.resize(_env->GetArrayLength(floats));
+ _env->GetFloatArrayRegion(floats, 0, _env->GetArrayLength(floats), floatVector.editArray());
+ }
+
+ if (ints != nullptr) {
+ int32Vector.resize(_env->GetArrayLength(ints));
+ _env->GetIntArrayRegion(ints, 0, _env->GetArrayLength(ints), int32Vector.editArray());
+ }
+
+ return mgr->setOperationParameter(type, floatVector, int32Vector);
+}
+
//----------------------------------------------------------------------------
class Receiver : public LooperCallback {
@@ -499,6 +518,10 @@
{"nativeConfigDirectChannel",
"(JIII)I",
(void*)nativeConfigDirectChannel },
+
+ {"nativeSetOperationParameter",
+ "(JI[F[I)I",
+ (void*)nativeSetOperationParameter },
};
static const JNINativeMethod gBaseEventQueueMethods[] = {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a73f543..7922250 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/drawable/autofilled_highlight.xml b/core/res/res/drawable/autofilled_highlight.xml
new file mode 100644
index 0000000..c7aacb9
--- /dev/null
+++ b/core/res/res/drawable/autofilled_highlight.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetLeft="4dp"
+ android:insetRight="4dp"
+ android:insetBottom="4dp"
+ android:insetTop="4dp">
+ <shape>
+ <solid android:color="@color/autofilled_highlight" />
+ </shape>
+</inset>
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index a165621..5a2bf4e 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 f3aa048..026bc6e 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 ee73b69..d26d952 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/colors.xml b/core/res/res/values/colors.xml
index f9fd57c..937fc6f 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -150,6 +150,7 @@
<color name="keyguard_avatar_frame_pressed_color">#ff35b5e5</color>
<color name="accessibility_focus_highlight">#bf39b500</color>
+ <color name="autofilled_highlight">#4dffeb3b</color>
<color name="system_notification_accent_color">#ff607D8B</color>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index b82542a..c5316c6 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/public.xml b/core/res/res/values/public.xml
index 624eb59..1d1fd5e 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2825,6 +2825,10 @@
<public name="autofill" />
</public-group>
+ <public-group type="drawable" first-id="0x010800b4">
+ <public name="autofilled_highlight" />
+ </public-group>
+
<!-- ===============================================================
DO NOT ADD UN-GROUPED ITEMS HERE
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 831cf89..459b48f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -20,8 +20,10 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Suffix added to a number to signify size in bytes. -->
<string name="byteShort">B</string>
- <!-- Suffix added to a number to signify size in kilobytes (1000 bytes). -->
- <string name="kilobyteShort">KB</string>
+ <!-- Suffix added to a number to signify size in kilobytes (1000 bytes).
+ If you retain the Latin script for the localization, please use the lowercase
+ 'k', as it signifies 1000 bytes as opposed to 1024 bytes. -->
+ <string name="kilobyteShort">kB</string>
<!-- Suffix added to a number to signify size in megabytes. -->
<string name="megabyteShort">MB</string>
<!-- Suffix added to a number to signify size in gigabytes. -->
@@ -3172,7 +3174,7 @@
<string name="alert_windows_notification_channel_name"><xliff:g id="name" example="Google Maps">%s</xliff:g> displaying over other apps</string>
<!-- Notification title when an application is displaying ui on-top of other apps
[CHAR LIMIT=30] -->
- <string name="alert_windows_notification_title"><xliff:g id="name" example="Google Maps">%s</xliff:g> is displaying over other apps.</string>
+ <string name="alert_windows_notification_title"><xliff:g id="name" example="Google Maps">%s</xliff:g> is displaying over other apps</string>
<!-- Notification body when an application is displaying ui on-top of other apps
[CHAR LIMIT=NONE] -->
<string name="alert_windows_notification_message">If you don’t want <xliff:g id="name" example="Google Maps">%s</xliff:g> to use this feature, tap to open settings and turn it off.</string>
@@ -4600,6 +4602,10 @@
<string name="autofill_save_type_address">address</string>
<!-- Label for the type of data being saved for autofill when it represents a credit card [CHAR LIMIT=NONE] -->
<string name="autofill_save_type_credit_card">credit card</string>
+ <!-- Label for the type of data being saved for autofill when it represents an username [CHAR LIMIT=NONE] -->
+ <string name="autofill_save_type_username">username</string>
+ <!-- Label for the type of data being saved for autofill when it represents an email address [CHAR LIMIT=NONE] -->
+ <string name="autofill_save_type_email_address">email address</string>
<!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for earthquake -->
<string name="etws_primary_default_message_earthquake">Stay calm and seek shelter nearby.</string>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 8f061a3..ec16611 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -488,6 +488,10 @@
<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 @@
<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/symbols.xml b/core/res/res/values/symbols.xml
index c12116a..428db87 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1333,6 +1333,7 @@
<java-symbol type="drawable" name="ic_sim_card_multi_48px_clr" />
<java-symbol type="drawable" name="stat_notify_mmcc_indication_icn" />
+ <java-symbol type="drawable" name="autofilled_highlight"/>
<java-symbol type="drawable" name="ic_account_circle" />
<java-symbol type="color" name="user_icon_1" />
@@ -2883,6 +2884,8 @@
<java-symbol type="string" name="autofill_save_type_password" />
<java-symbol type="string" name="autofill_save_type_address" />
<java-symbol type="string" name="autofill_save_type_credit_card" />
+ <java-symbol type="string" name="autofill_save_type_username" />
+ <java-symbol type="string" name="autofill_save_type_email_address" />
<!-- Accessibility fingerprint gestures -->
<java-symbol type="string" name="capability_title_canCaptureFingerprintGestures" />
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index 008c817..9dafa7a 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -1321,6 +1321,19 @@
<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/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java
index 6b52b98..8283335 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java
@@ -21,6 +21,7 @@
import android.os.BatteryStats;
import android.os.WorkSource;
import android.support.test.filters.SmallTest;
+import android.util.ArrayMap;
import junit.framework.TestCase;
@@ -187,4 +188,65 @@
assertEquals((305 - 202) * 1000, actualTime);
assertEquals((305 - 254) * 1000, bgTime);
}
+
+ @SmallTest
+ public void testJob() throws Exception {
+ final MockClocks clocks = new MockClocks();
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ final String jobName = "job_name";
+ long curr = 0; // realtime in us
+
+ // On battery
+ curr = 1000 * (clocks.realtime = clocks.uptime = 100);
+ bi.updateTimeBasesLocked(true, false, curr, curr); // on battery
+ // App in foreground
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+
+ // Start timer
+ curr = 1000 * (clocks.realtime = clocks.uptime = 151);
+ bi.noteJobStartLocked(jobName, UID);
+
+ // Stop timer
+ curr = 1000 * (clocks.realtime = clocks.uptime = 161);
+ bi.noteJobFinishLocked(jobName, UID);
+
+ // Start timer
+ curr = 1000 * (clocks.realtime = clocks.uptime = 202);
+ bi.noteJobStartLocked(jobName, UID);
+
+ // Move to background
+ curr = 1000 * (clocks.realtime = clocks.uptime = 254);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
+ // Off battery
+ curr = 1000 * (clocks.realtime = clocks.uptime = 305);
+ bi.updateTimeBasesLocked(false, false, curr, curr); // off battery
+
+ // Stop timer
+ curr = 1000 * (clocks.realtime = clocks.uptime = 409);
+ bi.noteJobFinishLocked(jobName, UID);
+
+ // Test
+ curr = 1000 * (clocks.realtime = clocks.uptime = 657);
+ final ArrayMap<String, ? extends BatteryStats.Timer> jobs =
+ bi.getUidStats().get(UID).getJobStats();
+ assertEquals(1, jobs.size());
+ BatteryStats.Timer timer = jobs.valueAt(0);
+ BatteryStats.Timer bgTimer = timer.getSubTimer();
+ long time = timer.getTotalTimeLocked(curr, STATS_SINCE_CHARGED);
+ int count = timer.getCountLocked(STATS_SINCE_CHARGED);
+ int bgCount = bgTimer.getCountLocked(STATS_SINCE_CHARGED);
+ long bgTime = bgTimer.getTotalTimeLocked(curr, STATS_SINCE_CHARGED);
+ assertEquals((161 - 151 + 305 - 202) * 1000, time);
+ assertEquals(2, count);
+ assertEquals(1, bgCount);
+ assertEquals((305 - 254) * 1000, bgTime);
+
+ // Test that a second job is separate.
+ curr = 1000 * (clocks.realtime = clocks.uptime = 3000);
+ final String jobName2 = "second_job";
+ bi.noteJobStartLocked(jobName2, UID);
+ assertEquals(2, bi.getUidStats().get(UID).getJobStats().size());
+ bi.noteJobFinishLocked(jobName2, UID);
+ }
}
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 2723826..97dd933 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -753,9 +753,12 @@
* @param init container-specific data, its meaning is interpreted based on the
* mime type provided in the mimeType parameter. It could contain, for example,
* the content ID, key ID or other data obtained from the content metadata that is
- * required in generating the key request. init may be null when keyType is
- * KEY_TYPE_RELEASE.
- * @param mimeType identifies the mime type of the content
+ * required in generating the key request. May be null when keyType is
+ * KEY_TYPE_RELEASE or if the request is a renewal, i.e. not the first key
+ * request for the session.
+ * @param mimeType identifies the mime type of the content. May be null if the
+ * keyType is KEY_TYPE_RELEASE or if the request is a renewal, i.e. not the
+ * first key request for the session.
* @param keyType specifes the type of the request. The request may be to acquire
* keys for streaming or offline content, or to release previously acquired
* keys, which are identified by a keySetId.
@@ -779,13 +782,17 @@
* response is for an offline key request, a keySetId is returned that can be
* used to later restore the keys to a new session with the method
* {@link #restoreKeys}.
- * When the response is for a streaming or release request, null is returned.
+ * When the response is for a streaming or release request, an empty byte array
+ * is returned.
*
* @param scope may be a sessionId or keySetId depending on the type of the
* response. Scope should be set to the sessionId when the response is for either
* streaming or offline key requests. Scope should be set to the keySetId when
* the response is for a release request.
* @param response the byte array response from the server
+ * @return If the response is for an offline request, the keySetId for the offline
+ * keys will be returned. If the response is for a streaming or release request
+ * an empty byte array will be returned.
*
* @throws NotProvisionedException if the response indicates that
* reprovisioning is required
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index 23bf3d6..431d5d8 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -34,6 +34,7 @@
libutils \
libbinder \
libmedia \
+ libmediametrics \
libmediadrm \
libmidi \
libskia \
diff --git a/packages/CarrierDefaultApp/res/values/strings.xml b/packages/CarrierDefaultApp/res/values/strings.xml
index f9342b7..fa5c3ff 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 73ff3a9..7fd1601 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 @@
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 @@
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 @@
.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/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index 2e6ed69b..63850ae 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -149,6 +149,12 @@
<!-- Description of printer info icon. [CHAR LIMIT=50] -->
<string name="printer_info_desc">More information about this printer</string>
+ <!-- Label for the notification channel that contains print jobs without problems. [CHAR LIMIT=40] -->
+ <string name="notification_channel_progress">Running print jobs</string>
+
+ <!-- Label for the notification channel that contains print jobs with problems. [CHAR LIMIT=40] -->
+ <string name="notification_channel_failure">Failed print jobs</string>
+
<!-- Notification that we could not create a file name for the printed PDF. [CHAR LIMIT=50] -->
<string name="could_not_create_file">Could not create file</string>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
index cd1d540..9d737e0 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
@@ -20,13 +20,12 @@
import android.annotation.Nullable;
import android.app.Notification;
import android.app.Notification.Action;
-import android.app.Notification.InboxStyle;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.AsyncTask;
@@ -57,14 +56,14 @@
public static final String LOG_TAG = "NotificationController";
+ private static final String NOTIFICATION_CHANNEL_PROGRESS = "PRINT_PROGRESS";
+ private static final String NOTIFICATION_CHANNEL_FAILURES = "PRINT_FAILURES";
+
private static final String INTENT_ACTION_CANCEL_PRINTJOB = "INTENT_ACTION_CANCEL_PRINTJOB";
private static final String INTENT_ACTION_RESTART_PRINTJOB = "INTENT_ACTION_RESTART_PRINTJOB";
private static final String EXTRA_PRINT_JOB_ID = "EXTRA_PRINT_JOB_ID";
- private static final String PRINT_JOB_NOTIFICATION_GROUP_KEY = "PRINT_JOB_NOTIFICATIONS";
- private static final String PRINT_JOB_NOTIFICATION_SUMMARY = "PRINT_JOB_NOTIFICATIONS_SUMMARY";
-
private final Context mContext;
private final NotificationManager mNotificationManager;
@@ -78,6 +77,15 @@
mNotificationManager = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mNotifications = new ArraySet<>(0);
+
+ mNotificationManager.createNotificationChannel(
+ new NotificationChannel(NOTIFICATION_CHANNEL_PROGRESS,
+ context.getString(R.string.notification_channel_progress),
+ NotificationManager.IMPORTANCE_LOW));
+ mNotificationManager.createNotificationChannel(
+ new NotificationChannel(NOTIFICATION_CHANNEL_FAILURES,
+ context.getString(R.string.notification_channel_failure),
+ NotificationManager.IMPORTANCE_DEFAULT));
}
public void onUpdateNotifications(List<PrintJobInfo> printJobs) {
@@ -104,13 +112,6 @@
final int numPrintJobs = printJobs.size();
- // Create summary notification
- if (numPrintJobs > 1) {
- createStackedNotification(printJobs);
- } else {
- mNotificationManager.cancel(PRINT_JOB_NOTIFICATION_SUMMARY, 0);
- }
-
// Create per print job notification
for (int i = 0; i < numPrintJobs; i++) {
PrintJobInfo printJob = printJobs.get(i);
@@ -178,16 +179,16 @@
*/
private void createNotification(@NonNull PrintJobInfo printJob, @Nullable Action firstAction,
@Nullable Action secondAction) {
- Notification.Builder builder = new Notification.Builder(mContext)
+ Notification.Builder builder = new Notification.Builder(mContext, computeChannel(printJob))
.setContentIntent(createContentIntent(printJob.getId()))
.setSmallIcon(computeNotificationIcon(printJob))
.setContentTitle(computeNotificationTitle(printJob))
.setWhen(System.currentTimeMillis())
.setOngoing(true)
.setShowWhen(true)
+ .setOnlyAlertOnce(true)
.setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color))
- .setGroup(PRINT_JOB_NOTIFICATION_GROUP_KEY);
+ com.android.internal.R.color.system_notification_accent_color));
if (firstAction != null) {
builder.addAction(firstAction);
@@ -238,43 +239,6 @@
createNotification(printJob, null, null);
}
- private void createStackedNotification(List<PrintJobInfo> printJobs) {
- Notification.Builder builder = new Notification.Builder(mContext)
- .setContentIntent(createContentIntent(null))
- .setWhen(System.currentTimeMillis())
- .setOngoing(true)
- .setShowWhen(true)
- .setGroup(PRINT_JOB_NOTIFICATION_GROUP_KEY)
- .setGroupSummary(true);
-
- final int printJobCount = printJobs.size();
-
- InboxStyle inboxStyle = new InboxStyle();
-
- int icon = com.android.internal.R.drawable.ic_print;
- for (int i = printJobCount - 1; i>= 0; i--) {
- PrintJobInfo printJob = printJobs.get(i);
-
- inboxStyle.addLine(computeNotificationTitle(printJob));
-
- // if any print job is in an error state show an error icon for the summary
- if (printJob.getState() == PrintJobInfo.STATE_FAILED
- || printJob.getState() == PrintJobInfo.STATE_BLOCKED) {
- icon = com.android.internal.R.drawable.ic_print_error;
- }
- }
-
- builder.setSmallIcon(icon);
- builder.setLargeIcon(
- ((BitmapDrawable) mContext.getResources().getDrawable(icon, null)).getBitmap());
- builder.setNumber(printJobCount);
- builder.setStyle(inboxStyle);
- builder.setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color));
-
- mNotificationManager.notify(PRINT_JOB_NOTIFICATION_SUMMARY, 0, builder.build());
- }
-
private String computeNotificationTitle(PrintJobInfo printJob) {
switch (printJob.getState()) {
case PrintJobInfo.STATE_FAILED: {
@@ -359,6 +323,22 @@
}
}
+ private static String computeChannel(PrintJobInfo printJob) {
+ if (printJob.isCancelling()) {
+ return NOTIFICATION_CHANNEL_PROGRESS;
+ }
+
+ switch (printJob.getState()) {
+ case PrintJobInfo.STATE_FAILED:
+ case PrintJobInfo.STATE_BLOCKED: {
+ return NOTIFICATION_CHANNEL_FAILURES;
+ }
+ default: {
+ return NOTIFICATION_CHANNEL_PROGRESS;
+ }
+ }
+ }
+
public static final class NotificationBroadcastReceiver extends BroadcastReceiver {
@SuppressWarnings("hiding")
private static final String LOG_TAG = "NotificationBroadcastReceiver";
diff --git a/packages/SettingsLib/Android.mk b/packages/SettingsLib/Android.mk
index 67ef40a..1ad4fea 100644
--- a/packages/SettingsLib/Android.mk
+++ b/packages/SettingsLib/Android.mk
@@ -6,6 +6,7 @@
LOCAL_MODULE := SettingsLib
LOCAL_SHARED_ANDROID_LIBRARIES := \
+ android-support-annotations \
android-support-v4 \
android-support-v7-recyclerview \
android-support-v7-preference \
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index 4bfca9b..474de90 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -211,7 +211,16 @@
if (preferLongName) {
displayName = getZoneLongName(timeZoneNames, tz, now);
} else {
- displayName = timeZoneNames.getExemplarLocationName(tz.getID());
+ // Canonicalize the zone ID for ICU. It will only return valid strings for zone IDs
+ // that match ICUs zone IDs (which are similar but not guaranteed the same as those
+ // in timezones.xml). timezones.xml and related files uses the IANA IDs. ICU IDs are
+ // stable and IANA IDs have changed over time so they have drifted.
+ // See http://bugs.icu-project.org/trac/ticket/13070 / http://b/36469833.
+ String canonicalZoneId = android.icu.util.TimeZone.getCanonicalID(tz.getID());
+ if (canonicalZoneId == null) {
+ canonicalZoneId = tz.getID();
+ }
+ displayName = timeZoneNames.getExemplarLocationName(canonicalZoneId);
if (displayName == null || displayName.isEmpty()) {
// getZoneExemplarLocation can return null. Fall back to the long name.
displayName = getZoneLongName(timeZoneNames, tz, now);
@@ -325,4 +334,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/PrivateStorageInfo.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/PrivateStorageInfo.java
index 40abb6c..88f133c 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 @@
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 320494c..11060e6 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 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 646c42f..e5d85d1 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 @@
* 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/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 1ea4183..901848a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -149,6 +149,7 @@
private int mRankingScore = Integer.MIN_VALUE;
private int mBadge = NetworkBadging.BADGING_NONE;
+ private boolean mIsScoredNetworkMetered = false;
// used to co-relate internal vs returned accesspoint.
int mId;
@@ -248,6 +249,7 @@
this.mScanResultCache.putAll(that.mScanResultCache);
this.mId = that.mId;
this.mBadge = that.mBadge;
+ this.mIsScoredNetworkMetered = that.mIsScoredNetworkMetered;
this.mRankingScore = that.mRankingScore;
}
@@ -336,16 +338,32 @@
builder.append(",level=").append(getLevel());
builder.append(",rankingScore=").append(mRankingScore);
builder.append(",badge=").append(mBadge);
+ builder.append(",metered=").append(isMetered());
return builder.append(')').toString();
}
/**
+ * Updates the AccessPoint rankingScore, metering, and badge, returning true if the data has
+ * changed.
+ *
+ * @param scoreCache The score cache to use to retrieve scores.
+ * @param scoringUiEnabled Whether to show scoring and badging UI.
+ */
+ boolean update(WifiNetworkScoreCache scoreCache, boolean scoringUiEnabled) {
+ boolean scoreChanged = false;
+ if (scoringUiEnabled) {
+ scoreChanged = updateScores(scoreCache);
+ }
+ return updateMetered(scoreCache) || scoreChanged;
+ }
+
+ /**
* Updates the AccessPoint rankingScore and badge, returning true if the data has changed.
*
* @param scoreCache The score cache to use to retrieve scores.
*/
- boolean updateScores(WifiNetworkScoreCache scoreCache) {
+ private boolean updateScores(WifiNetworkScoreCache scoreCache) {
int oldBadge = mBadge;
int oldRankingScore = mRankingScore;
mBadge = NetworkBadging.BADGING_NONE;
@@ -366,6 +384,23 @@
return (oldBadge != mBadge || oldRankingScore != mRankingScore);
}
+ /**
+ * Updates the AccessPoint's metering based on {@link ScoredNetwork#meteredHint}, returning
+ * true if the metering changed.
+ */
+ private boolean updateMetered(WifiNetworkScoreCache scoreCache) {
+ boolean oldMetering = mIsScoredNetworkMetered;
+ mIsScoredNetworkMetered = false;
+ for (ScanResult result : mScanResultCache.values()) {
+ ScoredNetwork score = scoreCache.getScoredNetwork(result);
+ if (score == null) {
+ continue;
+ }
+ mIsScoredNetworkMetered |= score.meteredHint;
+ }
+ return oldMetering == mIsScoredNetworkMetered;
+ }
+
private void evictOldScanResults() {
long nowMs = SystemClock.elapsedRealtime();
for (Iterator<ScanResult> iter = mScanResultCache.values().iterator(); iter.hasNext(); ) {
@@ -474,6 +509,17 @@
mSeen = seen;
}
+ /**
+ * Returns if the network is marked metered. Metering can be marked through its config in
+ * {@link WifiConfiguration}, after connection in {@link WifiInfo}, or from a score config in
+ * {@link ScoredNetwork}.
+ */
+ public boolean isMetered() {
+ return mIsScoredNetworkMetered
+ || (mConfig != null && mConfig.meteredHint)
+ || (mInfo != null && mInfo.getMeteredHint());
+ }
+
public NetworkInfo getNetworkInfo() {
return mNetworkInfo;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index 8f8167e..e82bf81 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -183,7 +183,7 @@
}
if (mAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
mFrictionSld.setState(STATE_SECURED);
- } else if (mAccessPoint.getConfig() != null && mAccessPoint.getConfig().meteredHint) {
+ } else if (mAccessPoint.isMetered()) {
mFrictionSld.setState(STATE_METERED);
}
Drawable drawable = mFrictionSld.getCurrent();
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 50f294c..fc8c42c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -543,11 +543,9 @@
}
}
- if (mNetworkScoringUiEnabled) {
- requestScoresForNetworkKeys(scoresToRequest);
- for (AccessPoint ap : accessPoints) {
- ap.updateScores(mScoreCache);
- }
+ requestScoresForNetworkKeys(scoresToRequest);
+ for (AccessPoint ap : accessPoints) {
+ ap.update(mScoreCache, mNetworkScoringUiEnabled);
}
// Pre-sort accessPoints to speed preference insertion
@@ -648,7 +646,7 @@
if (ap.update(connectionConfig, mLastInfo, mLastNetworkInfo)) {
reorder = true;
}
- if (mNetworkScoringUiEnabled && ap.updateScores(mScoreCache)) {
+ if (ap.update(mScoreCache, mNetworkScoringUiEnabled)) {
reorder = true;
}
}
@@ -659,15 +657,11 @@
}
/**
- * Update all the internal access points rankingScores and badge.
+ * Update all the internal access points rankingScores, badge and metering.
*
* <p>Will trigger a resort and notify listeners of changes if applicable.
*/
private void updateNetworkScores() {
- if (!mNetworkScoringUiEnabled) {
- return;
- }
-
// Lock required to prevent accidental copying of AccessPoint states while the modification
// is in progress. see #copyAndNotifyListeners
long before = System.currentTimeMillis();
@@ -679,7 +673,7 @@
boolean reorder = false;
for (int i = 0; i < mInternalAccessPoints.size(); i++) {
- if (mInternalAccessPoints.get(i).updateScores(mScoreCache)) {
+ if (mInternalAccessPoints.get(i).update(mScoreCache, mNetworkScoringUiEnabled)) {
reorder = true;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java
index d3bdeb7..ab7c6d2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java
@@ -17,6 +17,7 @@
import android.content.Context;
import android.os.Looper;
+import android.support.annotation.Keep;
/**
* Factory method used to inject WifiTracker instances.
@@ -26,14 +27,7 @@
private static WifiTracker sTestingWifiTracker;
- public static void enableTestingMode() {
- sTestingMode = true;
- }
-
- public static void disableTestingMode() {
- sTestingMode = false;
- }
-
+ @Keep
public static void setTestingWifiTracker(WifiTracker tracker) {
sTestingWifiTracker = tracker;
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index 762d9f8..3e01b34 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -17,15 +17,19 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
-
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.when;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
+import android.net.NetworkKey;
+import android.net.ScoredNetwork;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiNetworkScoreCache;
import android.net.wifi.WifiSsid;
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.net.wifi.hotspot2.pps.HomeSp;
@@ -36,10 +40,11 @@
import android.support.test.runner.AndroidJUnit4;
import android.text.SpannableString;
import android.text.style.TtsSpan;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Collections;
@@ -50,9 +55,11 @@
private static final String TEST_SSID = "test_ssid";
private Context mContext;
+ @Mock private WifiNetworkScoreCache mWifiNetworkScoreCache;
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
mContext = InstrumentationRegistry.getTargetContext();
}
@@ -74,6 +81,7 @@
@Test
public void testCopyAccessPoint_dataShouldMatch() {
WifiConfiguration configuration = createWifiConfiguration();
+ configuration.meteredHint = true;
NetworkInfo networkInfo =
new NetworkInfo(ConnectivityManager.TYPE_WIFI, 2, "WIFI", "WIFI_SUBTYPE");
@@ -88,6 +96,7 @@
assertThat(originalAccessPoint.getBssid()).isEqualTo(copy.getBssid());
assertThat(originalAccessPoint.getConfig()).isEqualTo(copy.getConfig());
assertThat(originalAccessPoint.getSecurity()).isEqualTo(copy.getSecurity());
+ assertThat(originalAccessPoint.isMetered()).isEqualTo(copy.isMetered());
assertThat(originalAccessPoint.compareTo(copy) == 0).isTrue();
}
@@ -230,6 +239,55 @@
assertTrue(ap.isPasspointConfig());
}
+ @Test
+ public void testIsMetered_returnTrueWhenWifiConfigurationIsMetered() {
+ WifiConfiguration configuration = createWifiConfiguration();
+ configuration.meteredHint = true;
+
+ NetworkInfo networkInfo =
+ new NetworkInfo(ConnectivityManager.TYPE_WIFI, 2, "WIFI", "WIFI_SUBTYPE");
+ AccessPoint accessPoint = new AccessPoint(mContext, configuration);
+ WifiInfo wifiInfo = new WifiInfo();
+ wifiInfo.setSSID(WifiSsid.createFromAsciiEncoded(configuration.SSID));
+ wifiInfo.setBSSID(configuration.BSSID);
+ wifiInfo.setNetworkId(configuration.networkId);
+ accessPoint.update(configuration, wifiInfo, networkInfo);
+
+ assertTrue(accessPoint.isMetered());
+ };
+
+ @Test
+ public void testIsMetered_returnTrueWhenWifiInfoIsMetered() {
+ WifiConfiguration configuration = createWifiConfiguration();
+
+ NetworkInfo networkInfo =
+ new NetworkInfo(ConnectivityManager.TYPE_WIFI, 2, "WIFI", "WIFI_SUBTYPE");
+ AccessPoint accessPoint = new AccessPoint(mContext, configuration);
+ WifiInfo wifiInfo = new WifiInfo();
+ wifiInfo.setSSID(WifiSsid.createFromAsciiEncoded(configuration.SSID));
+ wifiInfo.setBSSID(configuration.BSSID);
+ wifiInfo.setNetworkId(configuration.networkId);
+ wifiInfo.setMeteredHint(true);
+ accessPoint.update(configuration, wifiInfo, networkInfo);
+
+ assertTrue(accessPoint.isMetered());
+ };
+
+ @Test
+ public void testIsMetered_returnTrueWhenScoredNetworkIsMetered() {
+ AccessPoint ap = createAccessPointWithScanResultCache();
+
+ when(mWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
+ .thenReturn(
+ new ScoredNetwork(
+ null /* NetworkKey */,
+ null /* rssiCurve */,
+ true /* metered */));
+ ap.update(mWifiNetworkScoreCache, false /* scoringUiEnabled */);
+
+ assertTrue(ap.isMetered());
+ };
+
private AccessPoint createAccessPointWithScanResultCache() {
Bundle bundle = new Bundle();
ArrayList<ScanResult> scanResults = new ArrayList<>();
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
index 02deb44..b71915f 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
@@ -302,7 +303,7 @@
new ScoredNetwork(
NETWORK_KEY_2,
mockCurve2,
- false /* meteredHint */,
+ true /* meteredHint */,
attr2);
WifiNetworkScoreCache scoreCache = mScoreCacheCaptor.getValue();
@@ -515,6 +516,23 @@
}
@Test
+ public void scoreCacheUpdateMeteredShouldUpdateAccessPointMetering()
+ throws InterruptedException {
+ WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
+ updateScoresAndWaitForAccessPointsChangedCallback();
+
+ List<AccessPoint> aps = tracker.getAccessPoints();
+
+ for (AccessPoint ap : aps) {
+ if (ap.getSsidStr().equals(SSID_1)) {
+ assertFalse(ap.isMetered());
+ } else if (ap.getSsidStr().equals(SSID_2)) {
+ assertTrue(ap.isMetered());
+ }
+ }
+ }
+
+ @Test
public void noBadgesShouldBeInsertedIntoAccessPointWhenScoringUiDisabled()
throws InterruptedException {
Settings.Global.putInt(
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 1f559e4..169a034 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -994,6 +994,15 @@
return false;
}
+ private PackageInfo getCallingPackageInfo(int userId) {
+ try {
+ return mPackageManager.getPackageInfo(getCallingPackage(),
+ PackageManager.GET_SIGNATURES, userId);
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Package " + getCallingPackage() + " doesn't exist");
+ }
+ }
+
private Cursor getAllSecureSettings(int userId, String[] projection) {
if (DEBUG) {
Slog.v(LOG_TAG, "getAllSecureSettings(" + userId + ")");
@@ -1002,6 +1011,13 @@
// Resolve the userId on whose behalf the call is made.
final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(userId);
+ // The relevant "calling package" userId will be the owning userId for some
+ // profiles, and we can't do the lookup inside our [lock held] loop, so work out
+ // up front who the effective "new SSAID" user ID for that settings name will be.
+ final int ssaidUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId,
+ Settings.Secure.ANDROID_ID);
+ final PackageInfo ssaidCallingPkg = getCallingPackageInfo(ssaidUserId);
+
synchronized (mLock) {
List<String> names = getSettingsNamesLocked(SETTINGS_TYPE_SECURE, callingUserId);
@@ -1026,7 +1042,7 @@
// SETTINGS_FILE_SSAID, unless accessed by a system process.
final Setting setting;
if (isNewSsaidSetting(name)) {
- setting = getSsaidSettingLocked(owningUserId);
+ setting = getSsaidSettingLocked(ssaidCallingPkg, owningUserId);
} else {
setting = mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_SECURE, owningUserId,
name);
@@ -1060,14 +1076,17 @@
return settings != null ? settings.getNullSetting() : null;
}
- // Get the value.
- synchronized (mLock) {
- // As of Android O, the SSAID is read from an app-specific entry in table
- // SETTINGS_FILE_SSAID, unless accessed by a system process.
- if (isNewSsaidSetting(name)) {
- return getSsaidSettingLocked(owningUserId);
+ // As of Android O, the SSAID is read from an app-specific entry in table
+ // SETTINGS_FILE_SSAID, unless accessed by a system process.
+ if (isNewSsaidSetting(name)) {
+ PackageInfo callingPkg = getCallingPackageInfo(owningUserId);
+ synchronized (mLock) {
+ return getSsaidSettingLocked(callingPkg, owningUserId);
}
+ }
+ // Not the SSAID; do a straight lookup
+ synchronized (mLock) {
return mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_SECURE,
owningUserId, name);
}
@@ -1078,7 +1097,7 @@
&& UserHandle.getAppId(Binder.getCallingUid()) >= Process.FIRST_APPLICATION_UID;
}
- private Setting getSsaidSettingLocked(int owningUserId) {
+ private Setting getSsaidSettingLocked(PackageInfo callingPkg, int owningUserId) {
// Get uid of caller (key) used to store ssaid value
String name = Integer.toString(
UserHandle.getUid(owningUserId, UserHandle.getAppId(Binder.getCallingUid())));
@@ -1093,7 +1112,7 @@
// Lazy initialize ssaid if not yet present in ssaid table.
if (ssaid == null || ssaid.isNull() || ssaid.getValue() == null) {
- return mSettingsRegistry.generateSsaidLocked(getCallingPackage(), owningUserId);
+ return mSettingsRegistry.generateSsaidLocked(callingPkg, owningUserId);
}
return ssaid;
@@ -2070,15 +2089,7 @@
return ByteBuffer.allocate(4).putInt(data.length).array();
}
- public Setting generateSsaidLocked(String packageName, int userId) {
- final PackageInfo packageInfo;
- try {
- packageInfo = mPackageManager.getPackageInfo(packageName,
- PackageManager.GET_SIGNATURES, userId);
- } catch (RemoteException e) {
- throw new IllegalStateException("Package info doesn't exist");
- }
-
+ public Setting generateSsaidLocked(PackageInfo callingPkg, int userId) {
// Read the user's key from the ssaid table.
Setting userKeySetting = getSettingLocked(SETTINGS_TYPE_SSAID, userId, SSAID_USER_KEY);
if (userKeySetting == null || userKeySetting.isNull()
@@ -2113,11 +2124,12 @@
}
// Mac the package name and each of the signatures.
- byte[] packageNameBytes = packageInfo.packageName.getBytes(StandardCharsets.UTF_8);
+ final String packageName = callingPkg.packageName;
+ byte[] packageNameBytes = packageName.getBytes(StandardCharsets.UTF_8);
m.update(getLengthPrefix(packageNameBytes), 0, 4);
m.update(packageNameBytes);
- for (int i = 0; i < packageInfo.signatures.length; i++) {
- byte[] sig = packageInfo.signatures[i].toByteArray();
+ for (int i = 0; i < callingPkg.signatures.length; i++) {
+ byte[] sig = callingPkg.signatures[i].toByteArray();
m.update(getLengthPrefix(sig), 0, 4);
m.update(sig);
}
@@ -2127,7 +2139,7 @@
.toLowerCase(Locale.US);
// Save the ssaid in the ssaid table.
- final String uid = Integer.toString(packageInfo.applicationInfo.uid);
+ final String uid = Integer.toString(callingPkg.applicationInfo.uid);
final SettingsState ssaidSettings = getSettingsLocked(SETTINGS_TYPE_SSAID, userId);
final boolean success = ssaidSettings.insertSettingLocked(uid, ssaid, null, true,
packageName);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index fbd9f0c..4e7cf72 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -87,7 +87,9 @@
private boolean mMenuVisible;
private boolean mAllowMenuTimeout = true;
+
private final List<RemoteAction> mActions = new ArrayList<>();
+
private View mViewRoot;
private Drawable mBackgroundDrawable;
private View mMenuContainer;
@@ -266,7 +268,6 @@
}
notifyMenuVisibility(true);
updateExpandButtonFromBounds(stackBounds, movementBounds);
- setDecorViewVisibility(true);
mMenuContainerAnimator = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
mMenuContainer.getAlpha(), 1f);
mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
@@ -311,11 +312,15 @@
if (animationFinishedRunnable != null) {
animationFinishedRunnable.run();
}
- setDecorViewVisibility(false);
+
+ finish();
}
});
mMenuContainerAnimator.addUpdateListener(mMenuBgUpdateListener);
mMenuContainerAnimator.start();
+ } else {
+ // If the menu is not visible, just finish now
+ finish();
}
}
@@ -431,7 +436,6 @@
alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255);
}
mBackgroundDrawable.setAlpha(alpha);
- setDecorViewVisibility(alpha > 0);
}
private void notifyRegisterInputConsumer() {
@@ -508,16 +512,4 @@
v.removeCallbacks(mFinishRunnable);
v.postDelayed(mFinishRunnable, delay);
}
-
- /**
- * Sets the visibility of the root view of the window to disable drawing and touches for the
- * activity. This differs from {@link Activity#setVisible(boolean)} in that it does not set
- * the internal mVisibleFromClient state.
- */
- private void setDecorViewVisibility(boolean visible) {
- final View decorView = getWindow().getDecorView();
- if (decorView != null) {
- decorView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index bcaa395..875fb14 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -50,6 +50,7 @@
public class PipMenuActivityController {
private static final String TAG = "PipMenuActController";
+ private static final boolean DEBUG = false;
public static final String EXTRA_CONTROLLER_MESSENGER = "messenger";
public static final String EXTRA_ACTIONS = "actions";
@@ -195,6 +196,10 @@
* Updates the appearance of the menu and scrim on top of the PiP while dismissing.
*/
public void setDismissFraction(float fraction) {
+ if (DEBUG) {
+ Log.d(TAG, "setDismissFraction() hasActivity=" + (mToActivityMessenger != null)
+ + " fraction=" + fraction);
+ }
if (mToActivityMessenger != null) {
mTmpDismissFractionData.clear();
mTmpDismissFractionData.putFloat(EXTRA_DISMISS_FRACTION, fraction);
@@ -216,6 +221,9 @@
* Shows the menu activity.
*/
public void showMenu(Rect stackBounds, Rect movementBounds, boolean allowMenuTimeout) {
+ if (DEBUG) {
+ Log.d(TAG, "showMenu() hasActivity=" + (mToActivityMessenger != null));
+ }
if (mToActivityMessenger != null) {
Bundle data = new Bundle();
data.putParcelable(EXTRA_STACK_BOUNDS, stackBounds);
@@ -238,6 +246,9 @@
* Pokes the menu, indicating that the user is interacting with it.
*/
public void pokeMenu() {
+ if (DEBUG) {
+ Log.d(TAG, "pokeMenu() hasActivity=" + (mToActivityMessenger != null));
+ }
if (mToActivityMessenger != null) {
Message m = Message.obtain();
m.what = PipMenuActivity.MESSAGE_POKE_MENU;
@@ -253,6 +264,9 @@
* Hides the menu activity.
*/
public void hideMenu() {
+ if (DEBUG) {
+ Log.d(TAG, "hideMenu() hasActivity=" + (mToActivityMessenger != null));
+ }
if (mToActivityMessenger != null) {
Message m = Message.obtain();
m.what = PipMenuActivity.MESSAGE_HIDE_MENU;
@@ -365,6 +379,10 @@
* Handles changes in menu visibility.
*/
private void onMenuVisibilityChanged(boolean visible, boolean resize) {
+ if (DEBUG) {
+ Log.d(TAG, "onMenuVisibilityChanged() mMenuVisible=" + mMenuVisible
+ + " menuVisible=" + visible + " resize=" + resize);
+ }
if (visible) {
mInputConsumerController.unregisterInputConsumer();
} else {
@@ -389,6 +407,7 @@
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
pw.println(innerPrefix + "mMenuVisible=" + mMenuVisible);
+ pw.println(innerPrefix + "mToActivityMessenger=" + mToActivityMessenger);
pw.println(innerPrefix + "mListeners=" + mListeners.size());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index a14a712..fb8574d 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -23,6 +23,7 @@
import static com.android.systemui.Interpolators.LINEAR_OUT_SLOW_IN;
import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
@@ -253,7 +254,7 @@
* Flings the PiP to the closest snap target.
*/
Rect flingToSnapTarget(float velocity, float velocityX, float velocityY, Rect movementBounds,
- AnimatorUpdateListener listener) {
+ AnimatorUpdateListener updateListener, AnimatorListener listener) {
cancelAnimations();
Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds,
velocityX, velocityY);
@@ -263,8 +264,11 @@
mFlingAnimationUtils.apply(mBoundsAnimator, 0,
distanceBetweenRectOffsets(mBounds, toBounds),
velocity);
- if (listener != null) {
- mBoundsAnimator.addUpdateListener(listener);
+ if (updateListener != null) {
+ mBoundsAnimator.addUpdateListener(updateListener);
+ }
+ if (listener != null){
+ mBoundsAnimator.addListener(listener);
}
mBoundsAnimator.start();
}
@@ -274,14 +278,18 @@
/**
* Animates the PiP to the closest snap target.
*/
- Rect animateToClosestSnapTarget(Rect movementBounds, AnimatorUpdateListener listener) {
+ Rect animateToClosestSnapTarget(Rect movementBounds, AnimatorUpdateListener updateListener,
+ AnimatorListener listener) {
cancelAnimations();
Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds);
if (!mBounds.equals(toBounds)) {
mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, SNAP_STACK_DURATION,
FAST_OUT_SLOW_IN, mUpdateBoundsListener);
- if (listener != null) {
- mBoundsAnimator.addUpdateListener(listener);
+ if (updateListener != null) {
+ mBoundsAnimator.addUpdateListener(updateListener);
+ }
+ if (listener != null){
+ mBoundsAnimator.addListener(listener);
}
mBoundsAnimator.start();
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index d68836c..161bdac 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -16,6 +16,8 @@
package com.android.systemui.pip.phone;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.app.IActivityManager;
@@ -391,7 +393,10 @@
final float distance = bounds.bottom - target;
fraction = Math.min(distance / bounds.height(), 1f);
}
- mMenuController.setDismissFraction(fraction);
+ if (Float.compare(fraction, 0f) != 0 || mMenuController.isMenuVisible()) {
+ // Update if the fraction > 0, or if fraction == 0 and the menu was already visible
+ mMenuController.setDismissFraction(fraction);
+ }
}
}
@@ -611,22 +616,34 @@
setMinimizedStateInternal(false);
}
- // If the menu is still visible, and we aren't minimized, then just poke the menu
- // so that it will timeout after the user stops touching it
+ AnimatorListenerAdapter postAnimationCallback = null;
if (mMenuController.isMenuVisible()) {
+ // If the menu is still visible, and we aren't minimized, then just poke the
+ // menu so that it will timeout after the user stops touching it
mMenuController.showMenu(mMotionHelper.getBounds(), mMovementBounds,
true /* allowMenuTimeout */);
+ } else {
+ // If the menu is not visible, then we can still be showing the activity for the
+ // dismiss overlay, so just finish it after the animation completes
+ postAnimationCallback = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mMenuController.hideMenu();
+ }
+ };
}
if (isFling) {
mMotionHelper.flingToSnapTarget(velocity, vel.x, vel.y, mMovementBounds,
- mUpdateScrimListener);
+ mUpdateScrimListener, postAnimationCallback);
} else {
- mMotionHelper.animateToClosestSnapTarget(mMovementBounds, mUpdateScrimListener);
+ mMotionHelper.animateToClosestSnapTarget(mMovementBounds, mUpdateScrimListener,
+ postAnimationCallback);
}
} else if (mIsMinimized) {
// This was a tap, so no longer minimized
- mMotionHelper.animateToClosestSnapTarget(mMovementBounds, null /* listener */);
+ mMotionHelper.animateToClosestSnapTarget(mMovementBounds, null /* updateListener */,
+ null /* animatorListener */);
setMinimizedStateInternal(false);
} else if (!mIsMenuVisible) {
mMenuController.showMenu(mMotionHelper.getBounds(), mMovementBounds,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index 9efe224..d2a2919 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -242,7 +242,7 @@
mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
mDetailSettingsButton.setOnClickListener(v -> {
Dependency.get(MetricsLogger.class).action(ACTION_QS_MORE_SETTINGS,
- mDetailAdapter.getMetricsCategory());
+ adapter.getMetricsCategory());
Dependency.get(ActivityStarter.class)
.postStartActivityDismissingKeyguard(settingsIntent, 0);
});
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index dc666e9..311f8ff 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -524,7 +524,7 @@
* changes.
*/
public void onTaskDataLoaded() {
- if (mTask.icon != null) {
+ if (mTask != null && mTask.icon != null) {
mIconView.setImageDrawable(mTask.icon);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
index 21a0dc9..8298f35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -228,6 +228,7 @@
// Settings button.
final TextView settingsButton = (TextView) findViewById(R.id.more_settings);
if (mAppUid >= 0 && onSettingsClick != null) {
+ settingsButton.setVisibility(View.VISIBLE);
final int appUidF = mAppUid;
settingsButton.setOnClickListener(
(View view) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
index 5055dda..bb82b60 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
@@ -120,6 +120,9 @@
@Override
public void createMenu(ViewGroup parent) {
mParent = (ExpandableNotificationRow) parent;
+ if (mMenuContainer != null) {
+ mMenuContainer.removeAllViews();
+ }
mMenuContainer = new FrameLayout(mContext);
for (int i = 0; i < mMenuItems.size(); i++) {
addMenuView(mMenuItems.get(i), mMenuContainer);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index a0f2891..715dc82 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.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 @@
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
@@ -439,7 +434,8 @@
iconState.scaleY = 1.0f;
iconState.hidden = false;
}
- if (row.isAboveShelf() || (!row.isInShelf() && isLastChild && row.areGutsExposed())) {
+ if (row.isAboveShelf() || (!row.isInShelf() && (isLastChild && row.areGutsExposed()
+ || row.getTranslationZ() > mAmbientState.getBaseZHeight()))) {
iconState.hidden = true;
}
int shelfColor = icon.getStaticDrawableColor();
@@ -576,7 +572,8 @@
}
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 b9c8a78..92bfae9 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.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 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 @@
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 @@
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 @@
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 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 StatusBarIconView(Context context, AttributeSet attrs) {
super(context, attrs);
+ mDozer = new NotificationIconDozeHelper(context);
mBlocked = false;
mAlwaysScaleIcon = true;
updateIconScale();
@@ -466,7 +472,19 @@
* 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 @@
mDrawableColor = color;
setColorInternal(color);
mIconColor = color;
+ mDozer.setColor(color);
}
private void setColorInternal(int color) {
@@ -649,6 +668,14 @@
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 3efa29f..bca4b43 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 @@
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 @@
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 @@
}
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 @@
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 0000000..d592c5f
--- /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 38e4ec1..1ffc944 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.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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 0000000..9f79ef2
--- /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 846d03a..f0b6b2e 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 @@
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 @@
// 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 @@
return;
}
super.setDark(dark, fade, delay);
- setPictureGrayscale(dark, fade, delay);
+ setPictureDark(dark, fade, delay);
setProgressBarDark(dark, fade, delay);
}
@@ -188,12 +192,9 @@
}
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 @@
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 c85e8d8..f4db9a1 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 @@
*/
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 @@
} 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 @@
|| 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 3706dc8..dee15d8 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 @@
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 @@
mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex);
}
}
+ if (mDark && child instanceof StatusBarIconView) {
+ ((StatusBarIconView) child).setDark(mDark, false, 0);
+ }
}
@Override
@@ -312,7 +315,8 @@
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 @@
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 9dc0ceb..472af65 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 @@
@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);
}
@@ -5769,12 +5771,16 @@
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
final String pkg = sbn.getPackageName();
NotificationInfo info = (NotificationInfo) gutsView;
- final NotificationInfo.OnSettingsClickListener onSettingsClick = (View v,
- NotificationChannel channel, int appUid) -> {
- mMetricsLogger.action(MetricsEvent.ACTION_NOTE_INFO);
- guts.resetFalsingCheck();
- startAppNotificationSettingsActivity(pkg, appUid, channel);
- };
+ // Settings link is only valid for notifications that specify a user, unless this is the
+ // system user.
+ NotificationInfo.OnSettingsClickListener onSettingsClick = null;
+ if (!userHandle.equals(UserHandle.ALL) || mCurrentUserId == UserHandle.USER_SYSTEM) {
+ onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
+ mMetricsLogger.action(MetricsEvent.ACTION_NOTE_INFO);
+ guts.resetFalsingCheck();
+ startAppNotificationSettingsActivity(pkg, appUid, channel);
+ };
+ }
final View.OnClickListener onDoneClick = (View v) -> {
saveAndCloseNotificationMenu(info, row, guts, v);
};
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 5bead73..15fcb38 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.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.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 @@
}
};
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 @@
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 @@
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 @@
}
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 @@
}
private void updateAnimationState(View child) {
- updateAnimationState((mAnimationsEnabled || mPulsing)
+ updateAnimationState((mAnimationsEnabled || isPulsing())
&& (mIsExpanded || isPinnedHeadsUp(child)), child);
}
@@ -4055,12 +4074,12 @@
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/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
index c8659fb..5b594be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
@@ -190,7 +190,9 @@
view.setScaleY(scaleY);
}
- boolean becomesInvisible = this.alpha == 0.0f || (this.hidden && !isAnimating(view));
+ int oldVisibility = view.getVisibility();
+ boolean becomesInvisible = this.alpha == 0.0f
+ || (this.hidden && (!isAnimating(view) || oldVisibility != View.VISIBLE));
boolean animatingAlpha = isAnimating(view, TAG_ANIMATOR_ALPHA);
if (animatingAlpha) {
updateAlphaAnimation(view);
@@ -212,7 +214,6 @@
}
// apply visibility
- int oldVisibility = view.getVisibility();
int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
if (newVisibility != oldVisibility) {
if (!(view instanceof ExpandableView) || !((ExpandableView) view).willBeGone()) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
index c67cccc..8609eeb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
@@ -100,4 +100,10 @@
ViewUtils.detachView(mQsDetail);
mTestableLooper.processAllMessages();
}
+
+ @Test
+ public void testNullAdapterClick() {
+ mQsDetail.setupDetailFooter(mock(DetailAdapter.class));
+ mQsDetail.findViewById(android.R.id.button2).performClick();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
index 21930a3..0621f4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
@@ -206,6 +206,29 @@
}
@Test
+ public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() throws Exception {
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ null, null, null, null);
+ final TextView settingsButton =
+ (TextView) mNotificationInfo.findViewById(R.id.more_settings);
+ assertTrue(settingsButton.getVisibility() != View.VISIBLE);
+ }
+
+ @Test
+ public void testBindNotification_SettingsButtonReappersAfterSecondBind() throws Exception {
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ null, null, null, null);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ (View v, NotificationChannel c, int appUid) -> {}, null, null, null);
+ final TextView settingsButton =
+ (TextView) mNotificationInfo.findViewById(R.id.more_settings);
+ assertEquals(View.VISIBLE, settingsButton.getVisibility());
+ }
+
+ @Test
public void testOnClickListenerPassesNullChannelForBundle() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java
index 31b9bae..efb9fea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java
@@ -44,4 +44,11 @@
ViewUtils.detachView(row.getMenuView());
TestableLooper.get(this).processAllMessages();
}
+
+ @Test
+ public void testRecreateMenu() {
+ NotificationMenuRowPlugin row = new NotificationMenuRow(mContext);
+ row.createMenu(null);
+ row.createMenu(null);
+ }
}
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 0000000..a69de7a
--- /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 0482e73..ac81565 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 @@
@Override
public ParceledListSlice<AppWidgetProviderInfo> getInstalledProvidersForProfile(int categoryFilter,
- int profileId) {
+ int profileId, String packageName) {
final int userId = UserHandle.getCallingUserId();
if (DEBUG) {
@@ -1653,8 +1653,11 @@
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/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 15ec98f..c8a5780 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -148,7 +148,7 @@
// TODO(b/33197203): since service is fetching the data (to use for save later),
// we should optimize what's sent (for example, remove layout containers,
// color / font info, etc...)
- session.mStructure = structure;
+ session.setStructureLocked(structure);
}
@@ -194,13 +194,17 @@
}
}
+ private String getComponentNameFromSettings() {
+ return Settings.Secure.getStringForUser(
+ mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId);
+ }
+
void updateLocked(boolean disabled) {
final boolean wasEnabled = isEnabled();
mDisabled = disabled;
ComponentName serviceComponent = null;
ServiceInfo serviceInfo = null;
- final String componentName = Settings.Secure.getStringForUser(
- mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId);
+ final String componentName = getComponentNameFromSettings();
if (!TextUtils.isEmpty(componentName)) {
try {
serviceComponent = ComponentName.unflattenFromString(componentName);
@@ -413,8 +417,7 @@
void disableSelf() {
final long identity = Binder.clearCallingIdentity();
try {
- final String autoFillService = Settings.Secure.getStringForUser(
- mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId);
+ final String autoFillService = getComponentNameFromSettings();
if (mInfo.getServiceInfo().getComponentName().equals(
ComponentName.unflattenFromString(autoFillService))) {
Settings.Secure.putStringForUser(mContext.getContentResolver(),
@@ -432,12 +435,14 @@
void dumpLocked(String prefix, PrintWriter pw) {
final String prefix2 = prefix + " ";
- pw.print(prefix); pw.print("User :"); pw.println(mUserId);
- pw.print(prefix); pw.print("Component:"); pw.println(mInfo != null
+ pw.print(prefix); pw.print("User: "); pw.println(mUserId);
+ pw.print(prefix); pw.print("Component: "); pw.println(mInfo != null
? mInfo.getServiceInfo().getComponentName() : null);
+ pw.print(prefix); pw.print("Component from settings: ");
+ pw.println(getComponentNameFromSettings());
pw.print(prefix); pw.print("Default component: ");
pw.println(mContext.getString(R.string.config_defaultAutofillService));
- pw.print(prefix); pw.print("Disabled:"); pw.println(mDisabled);
+ pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
if (VERBOSE && mInfo != null) {
// ServiceInfo dump is too noisy and redundant (it can be obtained through other dumps)
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 801769c..a3da50e 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -29,6 +29,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.AutofillOverlay;
import android.app.assist.AssistStructure.ViewNode;
import android.app.assist.AssistStructure.WindowNode;
import android.content.ComponentName;
@@ -100,7 +101,7 @@
@NonNull private final String mPackageName;
@GuardedBy("mLock")
- private final Map<AutofillId, ViewState> mViewStates = new ArrayMap<>();
+ private final ArrayMap<AutofillId, ViewState> mViewStates = new ArrayMap<>();
/**
* Id of the View currently being displayed.
@@ -132,7 +133,8 @@
* Assist structure sent by the app; it will be updated (sanitized, change values for save)
* before sent to {@link AutofillService}.
*/
- @GuardedBy("mLock") AssistStructure mStructure;
+ @GuardedBy("mLock")
+ private AssistStructure mStructure;
/**
* Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}.
@@ -140,6 +142,13 @@
private boolean mHasCallback;
/**
+ * Extras sent by service on {@code onFillRequest()} calls; the first non-null extra is saved
+ * and used on subsequent {@code onFillRequest()} and {@code onSaveRequest()} calls.
+ */
+ @GuardedBy("mLock")
+ private Bundle mExtras;
+
+ /**
* Flags used to start the session.
*/
int mFlags;
@@ -352,6 +361,10 @@
mHasCallback = hasIt;
}
+ public void setStructureLocked(AssistStructure structure) {
+ mStructure = structure;
+ }
+
/**
* Shows the save UI, when session can be saved.
*
@@ -474,9 +487,6 @@
Slog.d(TAG, "callSaveLocked(): mViewStates=" + mViewStates);
}
- // TODO(b/33197203 , b/35707731): decide how to handle bundle in multiple partitions
- final Bundle extras = mResponses != null ? mResponses.get(0).getExtras() : null;
-
for (Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) {
final AutofillValue value = entry.getValue().getCurrentValue();
if (value == null) {
@@ -506,7 +516,7 @@
mStructure.dump();
}
- mRemoteFillService.onSaveRequest(mStructure, extras);
+ mRemoteFillService.onSaveRequest(mStructure, mExtras);
}
void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int flags) {
@@ -517,10 +527,10 @@
if (DEBUG) {
Slog.d(TAG, "Creating viewState for " + id + " on " + getFlagAsString(flags));
}
- viewState = new ViewState(this, id, this, ViewState.STATE_INITIAL);
+ viewState = new ViewState(this, id, value, this, ViewState.STATE_INITIAL);
mViewStates.put(id, viewState);
} else if ((flags & FLAG_VIEW_ENTERED) != 0) {
- viewState = startPartitionLocked(id);
+ viewState = startPartitionLocked(id, value);
} else {
if (VERBOSE) Slog.v(TAG, "Ignored " + getFlagAsString(flags) + " for " + id);
return;
@@ -584,25 +594,38 @@
Slog.w(TAG, "updateLocked(): unknown flags " + flags + ": " + getFlagAsString(flags));
}
- private ViewState startPartitionLocked(AutofillId id) {
+ private ViewState startPartitionLocked(AutofillId id, AutofillValue value) {
if (DEBUG) {
Slog.d(TAG, "Starting partition for view id " + id);
}
- final ViewState viewState =
- new ViewState(this, id, this,ViewState.STATE_STARTED_PARTITION);
- mViewStates.put(id, viewState);
+ final ViewState newViewState =
+ new ViewState(this, id, value, this,ViewState.STATE_STARTED_PARTITION);
+ mViewStates.put(id, newViewState);
- /*
- * TODO(b/33197203 , b/35707731): when start a new partition, it should
- *
- * - add autofilled fields as sanitized
- * - set focus on ViewStructure that triggered it
- * - pass the first onFillRequest() bundle
- * - optional: perhaps add a new flag onFilLRequest() to indicate it's a new partition?
- */
- mRemoteFillService.onFillRequest(mStructure, null, 0);
+ // Must update value of nodes so:
+ // - proper node is focused
+ // - autofillValue is sent back to service when it was previously autofilled
+ for (int i = 0; i < mViewStates.size(); i++) {
+ final ViewState viewState = mViewStates.valueAt(i);
- return viewState;
+ final ViewNode node = findViewNodeByIdLocked(viewState.id);
+ if (node == null) {
+ Slog.w(TAG, "startPartitionLocked(): no node for " + viewState.id);
+ continue;
+ }
+
+ final AutofillValue initialValue = viewState.getInitialValue();
+ final AutofillValue filledValue = viewState.getAutofilledValue();
+ final AutofillOverlay overlay = new AutofillOverlay();
+ if (filledValue != null && !filledValue.equals(initialValue)) {
+ overlay.value = filledValue;
+ }
+ overlay.focused = id.equals(viewState.id);
+ node.setAutofillOverlay(overlay);
+ }
+ mRemoteFillService.onFillRequest(mStructure, mExtras, 0);
+
+ return newViewState;
}
@Override
@@ -650,6 +673,9 @@
mResponses = new ArrayList<>(4);
}
mResponses.add(response);
+ if (response != null) {
+ mExtras = response.getExtras();
+ }
setViewStatesLocked(response, ViewState.STATE_FILLABLE);
@@ -695,7 +721,7 @@
if (viewState != null) {
viewState.setState(state);
} else {
- viewState = new ViewState(this, id, this, state);
+ viewState = new ViewState(this, id, null, this, state);
if (DEBUG) { // TODO(b/33197203): change to VERBOSE once stable
Slog.d(TAG, "Adding autofillable view with id " + id + " and state " + state);
}
@@ -791,6 +817,7 @@
}
}
pw.print(prefix); pw.print("mHasCallback: "); pw.println(mHasCallback);
+ pw.print(prefix); pw.print("mExtras: "); pw.println(Helper.bundleToString(mExtras));
mRemoteFillService.dump(prefix, pw);
}
diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java
index 549f231..f8919ee 100644
--- a/services/autofill/java/com/android/server/autofill/ViewState.java
+++ b/services/autofill/java/com/android/server/autofill/ViewState.java
@@ -67,15 +67,17 @@
private final Session mSession;
private FillResponse mResponse;
+ private AutofillValue mInitialValue;
private AutofillValue mCurrentValue;
private AutofillValue mAutofilledValue;
private Rect mVirtualBounds;
private int mState;
- ViewState(Session session, AutofillId id, Listener listener, int state) {
+ ViewState(Session session, AutofillId id, AutofillValue value, Listener listener, int state) {
mSession = session;
this.id = id;
+ mInitialValue = value;
mListener = listener;
mState = state;
}
@@ -110,6 +112,11 @@
}
@Nullable
+ AutofillValue getInitialValue() {
+ return mInitialValue;
+ }
+
+ @Nullable
FillResponse getResponse() {
return mResponse;
}
@@ -184,14 +191,16 @@
@Override
public String toString() {
- return "ViewState: [id=" + id + ", currentValue=" + mCurrentValue
- + ", bounds=" + mVirtualBounds + ", state=" + getStateAsString() +"]";
+ return "ViewState: [id=" + id + ", initialValue=" + mInitialValue
+ + ", currentValue=" + mCurrentValue + ", autofilledValue=" + mAutofilledValue
+ + ", bounds=" + mVirtualBounds + ", state=" + getStateAsString() + "]";
}
void dump(String prefix, PrintWriter pw) {
pw.print(prefix); pw.print("id:" ); pw.println(this.id);
pw.print(prefix); pw.print("state:" ); pw.println(getStateAsString());
pw.print(prefix); pw.print("has response:" ); pw.println(mResponse != null);
+ pw.print(prefix); pw.print("initialValue:" ); pw.println(mInitialValue);
pw.print(prefix); pw.print("currentValue:" ); pw.println(mCurrentValue);
pw.print(prefix); pw.print("autofilledValue:" ); pw.println(mAutofilledValue);
pw.print(prefix); pw.print("virtualBounds:" ); pw.println(mVirtualBounds);
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 509351b..2c08afa 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -117,6 +117,12 @@
case SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD:
type = context.getString(R.string.autofill_save_type_credit_card);
break;
+ case SaveInfo.SAVE_DATA_TYPE_USERNAME:
+ type = context.getString(R.string.autofill_save_type_username);
+ break;
+ case SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS:
+ type = context.getString(R.string.autofill_save_type_email_address);
+ break;
default:
type = null;
}
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
new file mode 100644
index 0000000..a7ce95b
--- /dev/null
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -0,0 +1,494 @@
+/*
+ * 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;
+
+import static android.Manifest.permission.DUMP;
+import static android.net.IpSecManager.INVALID_RESOURCE_ID;
+import static android.net.IpSecManager.KEY_RESOURCE_ID;
+import static android.net.IpSecManager.KEY_SPI;
+import static android.net.IpSecManager.KEY_STATUS;
+
+import android.content.Context;
+import android.net.IIpSecService;
+import android.net.INetd;
+import android.net.IpSecAlgorithm;
+import android.net.IpSecConfig;
+import android.net.IpSecManager;
+import android.net.IpSecTransform;
+import android.net.util.NetdService;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import com.android.internal.annotations.GuardedBy;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/** @hide */
+public class IpSecService extends IIpSecService.Stub {
+ private static final String TAG = "IpSecService";
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final String NETD_SERVICE_NAME = "netd";
+ private static final int[] DIRECTIONS =
+ new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN};
+
+ /** Binder context for this service */
+ private final Context mContext;
+
+ private Object mLock = new Object();
+
+ private static final int NETD_FETCH_TIMEOUT = 5000; //ms
+
+ private AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0);
+
+ private abstract class ManagedResource implements IBinder.DeathRecipient {
+ final int pid;
+ final int uid;
+ private IBinder mBinder;
+
+ ManagedResource(IBinder binder) {
+ super();
+ mBinder = binder;
+ pid = Binder.getCallingPid();
+ uid = Binder.getCallingUid();
+
+ try {
+ mBinder.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ binderDied();
+ }
+ }
+
+ /**
+ * When this record is no longer needed for managing system resources this function should
+ * unlink all references held by the record to allow efficient garbage collection.
+ */
+ public final void release() {
+ //Release all the underlying system resources first
+ releaseResources();
+
+ if (mBinder != null) {
+ mBinder.unlinkToDeath(this, 0);
+ }
+ mBinder = null;
+
+ //remove this record so that it can be cleaned up
+ nullifyRecord();
+ }
+
+ /**
+ * If the Binder object dies, this function is called to free the system resources that are
+ * being managed by this record and to subsequently release this record for garbage
+ * collection
+ */
+ public final void binderDied() {
+ release();
+ }
+
+ /**
+ * Implement this method to release all object references contained in the subclass to allow
+ * efficient garbage collection of the record. This should remove any references to the
+ * record from all other locations that hold a reference as the record is no longer valid.
+ */
+ protected abstract void nullifyRecord();
+
+ /**
+ * Implement this method to release all system resources that are being protected by this
+ * record. Once the resources are released, the record should be invalidated and no longer
+ * used by calling releaseRecord()
+ */
+ protected abstract void releaseResources();
+ };
+
+ private final class TransformRecord extends ManagedResource {
+ private IpSecConfig mConfig;
+ private int mResourceId;
+
+ TransformRecord(IpSecConfig config, int resourceId, IBinder binder) {
+ super(binder);
+ mConfig = config;
+ mResourceId = resourceId;
+ }
+
+ public IpSecConfig getConfig() {
+ return mConfig;
+ }
+
+ @Override
+ protected void releaseResources() {
+ for (int direction : DIRECTIONS) {
+ try {
+ getNetdInstance()
+ .ipSecDeleteSecurityAssociation(
+ mResourceId,
+ direction,
+ (mConfig.getLocalAddress() != null)
+ ? mConfig.getLocalAddress().getHostAddress()
+ : "",
+ (mConfig.getRemoteAddress() != null)
+ ? mConfig.getRemoteAddress().getHostAddress()
+ : "",
+ mConfig.getSpi(direction));
+ } catch (ServiceSpecificException e) {
+ // FIXME: get the error code and throw is at an IOException from Errno Exception
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to delete SA with ID: " + mResourceId);
+ }
+ }
+ }
+
+ @Override
+ protected void nullifyRecord() {
+ mConfig = null;
+ mResourceId = INVALID_RESOURCE_ID;
+ }
+ }
+
+ private final class SpiRecord extends ManagedResource {
+ private final int mDirection;
+ private final String mLocalAddress;
+ private final String mRemoteAddress;
+ private final IBinder mBinder;
+ private int mSpi;
+ private int mResourceId;
+
+ SpiRecord(
+ int resourceId,
+ int direction,
+ String localAddress,
+ String remoteAddress,
+ int spi,
+ IBinder binder) {
+ super(binder);
+ mResourceId = resourceId;
+ mDirection = direction;
+ mLocalAddress = localAddress;
+ mRemoteAddress = remoteAddress;
+ mSpi = spi;
+ mBinder = binder;
+ }
+
+ protected void releaseResources() {
+ try {
+ getNetdInstance()
+ .ipSecDeleteSecurityAssociation(
+ mResourceId, mDirection, mLocalAddress, mRemoteAddress, mSpi);
+ } catch (ServiceSpecificException e) {
+ // FIXME: get the error code and throw is at an IOException from Errno Exception
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId);
+ }
+ }
+
+ protected void nullifyRecord() {
+ mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
+ mResourceId = INVALID_RESOURCE_ID;
+ }
+ }
+
+ @GuardedBy("mSpiRecords")
+ private final SparseArray<SpiRecord> mSpiRecords = new SparseArray<>();
+
+ @GuardedBy("mTransformRecords")
+ private final SparseArray<TransformRecord> mTransformRecords = new SparseArray<>();
+
+ /**
+ * Constructs a new IpSecService instance
+ *
+ * @param context Binder context for this service
+ */
+ private IpSecService(Context context) {
+ mContext = context;
+ }
+
+ static IpSecService create(Context context) throws InterruptedException {
+ final IpSecService service = new IpSecService(context);
+ service.connectNativeNetdService();
+ return service;
+ }
+
+ public void systemReady() {
+ if (isNetdAlive()) {
+ Slog.d(TAG, "IpSecService is ready");
+ } else {
+ Slog.wtf(TAG, "IpSecService not ready: failed to connect to NetD Native Service!");
+ }
+ }
+
+ private void connectNativeNetdService() {
+ // Avoid blocking the system server to do this
+ Thread t =
+ new Thread(
+ new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ NetdService.get(NETD_FETCH_TIMEOUT);
+ }
+ }
+ });
+ t.run();
+ }
+
+ INetd getNetdInstance() throws RemoteException {
+ final INetd netd = NetdService.getInstance();
+ if (netd == null) {
+ throw new RemoteException("Failed to Get Netd Instance");
+ }
+ return netd;
+ }
+
+ boolean isNetdAlive() {
+ synchronized (mLock) {
+ try {
+ final INetd netd = getNetdInstance();
+ if (netd == null) {
+ return false;
+ }
+ return netd.isAlive();
+ } catch (RemoteException re) {
+ return false;
+ }
+ }
+ }
+
+ @Override
+ /** Get a new SPI and maintain the reservation in the system server */
+ public Bundle reserveSecurityParameterIndex(
+ int direction, String remoteAddress, int requestedSpi, IBinder binder)
+ throws RemoteException {
+ int resourceId = mNextResourceId.getAndIncrement();
+
+ int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
+ String localAddress = "";
+ Bundle retBundle = new Bundle(3);
+ try {
+ spi =
+ getNetdInstance()
+ .ipSecAllocateSpi(
+ resourceId,
+ direction,
+ localAddress,
+ remoteAddress,
+ requestedSpi);
+ Log.d(TAG, "Allocated SPI " + spi);
+ retBundle.putInt(KEY_STATUS, IpSecManager.Status.OK);
+ retBundle.putInt(KEY_RESOURCE_ID, resourceId);
+ retBundle.putInt(KEY_SPI, spi);
+ synchronized (mSpiRecords) {
+ mSpiRecords.put(
+ resourceId,
+ new SpiRecord(
+ resourceId, direction, localAddress, remoteAddress, spi, binder));
+ }
+ } catch (ServiceSpecificException e) {
+ // TODO: Add appropriate checks when other ServiceSpecificException types are supported
+ retBundle.putInt(KEY_STATUS, IpSecManager.Status.SPI_UNAVAILABLE);
+ retBundle.putInt(KEY_RESOURCE_ID, resourceId);
+ retBundle.putInt(KEY_SPI, spi);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return retBundle;
+ }
+
+ /** Release a previously allocated SPI that has been registered with the system server */
+ @Override
+ public void releaseSecurityParameterIndex(int resourceId) throws RemoteException {}
+
+ /**
+ * Open a socket via the system server and bind it to the specified port (random if port=0).
+ * This will return a PFD to the user that represent a bound UDP socket. The system server will
+ * cache the socket and a record of its owner so that it can and must be freed when no longer
+ * needed.
+ */
+ @Override
+ public Bundle openUdpEncapsulationSocket(int port, IBinder binder) throws RemoteException {
+ return null;
+ }
+
+ /** close a socket that has been been allocated by and registered with the system server */
+ @Override
+ public void closeUdpEncapsulationSocket(ParcelFileDescriptor socket) {}
+
+ /**
+ * Create a transport mode transform, which represent two security associations (one in each
+ * direction) in the kernel. The transform will be cached by the system server and must be freed
+ * when no longer needed. It is possible to free one, deleting the SA from underneath sockets
+ * that are using it, which will result in all of those sockets becoming unable to send or
+ * receive data.
+ */
+ @Override
+ public Bundle createTransportModeTransform(IpSecConfig c, IBinder binder)
+ throws RemoteException {
+ // TODO: Basic input validation here since it's coming over the Binder
+ int resourceId = mNextResourceId.getAndIncrement();
+ for (int direction : DIRECTIONS) {
+ IpSecAlgorithm auth = c.getAuthentication(direction);
+ IpSecAlgorithm crypt = c.getEncryption(direction);
+ try {
+ int result =
+ getNetdInstance()
+ .ipSecAddSecurityAssociation(
+ resourceId,
+ c.getMode(),
+ direction,
+ (c.getLocalAddress() != null)
+ ? c.getLocalAddress().getHostAddress()
+ : "",
+ (c.getRemoteAddress() != null)
+ ? c.getRemoteAddress().getHostAddress()
+ : "",
+ (c.getNetwork() != null)
+ ? c.getNetwork().getNetworkHandle()
+ : 0,
+ c.getSpi(direction),
+ (auth != null) ? auth.getName() : "",
+ (auth != null) ? auth.getKey() : null,
+ (auth != null) ? auth.getTruncationLengthBits() : 0,
+ (crypt != null) ? crypt.getName() : "",
+ (crypt != null) ? crypt.getKey() : null,
+ (crypt != null) ? crypt.getTruncationLengthBits() : 0,
+ c.getEncapType(),
+ c.getEncapLocalPort(),
+ c.getEncapRemotePort());
+ if (result != c.getSpi(direction)) {
+ // TODO: cleanup the first SA if creation of second SA fails
+ Bundle retBundle = new Bundle(2);
+ retBundle.putInt(KEY_STATUS, IpSecManager.Status.SPI_UNAVAILABLE);
+ retBundle.putInt(KEY_RESOURCE_ID, INVALID_RESOURCE_ID);
+ return retBundle;
+ }
+ } catch (ServiceSpecificException e) {
+ // FIXME: get the error code and throw is at an IOException from Errno Exception
+ }
+ }
+ synchronized (mTransformRecords) {
+ mTransformRecords.put(resourceId, new TransformRecord(c, resourceId, binder));
+ }
+
+ Bundle retBundle = new Bundle(2);
+ retBundle.putInt(KEY_STATUS, IpSecManager.Status.OK);
+ retBundle.putInt(KEY_RESOURCE_ID, resourceId);
+ return retBundle;
+ }
+
+ /**
+ * Delete a transport mode transform that was previously allocated by + registered with the
+ * system server. If this is called on an inactive (or non-existent) transform, it will not
+ * return an error. It's safe to de-allocate transforms that may have already been deleted for
+ * other reasons.
+ */
+ @Override
+ public void deleteTransportModeTransform(int resourceId) throws RemoteException {
+ synchronized (mTransformRecords) {
+ TransformRecord record;
+ // We want to non-destructively get so that we can check credentials before removing
+ // this from the records.
+ record = mTransformRecords.get(resourceId);
+
+ if (record == null) {
+ throw new IllegalArgumentException(
+ "Transform " + resourceId + " is not available to be deleted");
+ }
+
+ if (record.pid != Binder.getCallingPid() || record.uid != Binder.getCallingUid()) {
+ throw new SecurityException("Only the owner of an IpSec Transform may delete it!");
+ }
+
+ // TODO: if releaseResources() throws RemoteException, we can try again to clean up on
+ // binder death. Need to make sure that path is actually functional.
+ record.releaseResources();
+ mTransformRecords.remove(resourceId);
+ record.nullifyRecord();
+ }
+ }
+
+ /**
+ * Apply an active transport mode transform to a socket, which will apply the IPsec security
+ * association as a correspondent policy to the provided socket
+ */
+ @Override
+ public void applyTransportModeTransform(ParcelFileDescriptor socket, int resourceId)
+ throws RemoteException {
+
+ synchronized (mTransformRecords) {
+ TransformRecord info;
+ // FIXME: this code should be factored out into a security check + getter
+ info = mTransformRecords.get(resourceId);
+
+ if (info == null) {
+ throw new IllegalArgumentException("Transform " + resourceId + " is not active");
+ }
+
+ // TODO: make this a function.
+ if (info.pid != getCallingPid() || info.uid != getCallingUid()) {
+ throw new SecurityException("Only the owner of an IpSec Transform may apply it!");
+ }
+
+ IpSecConfig c = info.getConfig();
+ try {
+ for (int direction : DIRECTIONS) {
+ getNetdInstance()
+ .ipSecApplyTransportModeTransform(
+ socket.getFileDescriptor(),
+ resourceId,
+ direction,
+ (c.getLocalAddress() != null)
+ ? c.getLocalAddress().getHostAddress()
+ : "",
+ (c.getRemoteAddress() != null)
+ ? c.getRemoteAddress().getHostAddress()
+ : "",
+ c.getSpi(direction));
+ }
+ } catch (ServiceSpecificException e) {
+ // FIXME: get the error code and throw is at an IOException from Errno Exception
+ }
+ }
+ }
+ /**
+ * Remove a transport mode transform from a socket, applying the default (empty) policy. This
+ * will ensure that NO IPsec policy is applied to the socket (would be the equivalent of
+ * applying a policy that performs no IPsec). Today the resourceId parameter is passed but not
+ * used: reserved for future improved input validation.
+ */
+ @Override
+ public void removeTransportModeTransform(ParcelFileDescriptor socket, int resourceId)
+ throws RemoteException {
+ try {
+ getNetdInstance().ipSecRemoveTransportModeTransform(socket.getFileDescriptor());
+ } catch (ServiceSpecificException e) {
+ // FIXME: get the error code and throw is at an IOException from Errno Exception
+ }
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mContext.enforceCallingOrSelfPermission(DUMP, TAG);
+
+ pw.println("IpSecService Log:");
+ pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead"));
+ pw.println();
+ }
+}
diff --git a/services/core/java/com/android/server/NetworkScorerAppManager.java b/services/core/java/com/android/server/NetworkScorerAppManager.java
index e127eb9..8404025 100644
--- a/services/core/java/com/android/server/NetworkScorerAppManager.java
+++ b/services/core/java/com/android/server/NetworkScorerAppManager.java
@@ -42,6 +42,7 @@
*
* @hide
*/
+@VisibleForTesting
public class NetworkScorerAppManager {
private static final String TAG = "NetworkScorerAppManager";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -63,7 +64,8 @@
* Returns the list of available scorer apps. The list will be empty if there are
* no valid scorers.
*/
- List<NetworkScorerAppData> getAllValidScorers() {
+ @VisibleForTesting
+ public List<NetworkScorerAppData> getAllValidScorers() {
if (VERBOSE) Log.v(TAG, "getAllValidScorers()");
final PackageManager pm = mContext.getPackageManager();
final Intent serviceIntent = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS);
@@ -168,7 +170,8 @@
* it was disabled or uninstalled).
*/
@Nullable
- NetworkScorerAppData getActiveScorer() {
+ @VisibleForTesting
+ public NetworkScorerAppData getActiveScorer() {
final int enabledSetting = getNetworkRecommendationsEnabledSetting();
if (enabledSetting == NetworkScoreManager.RECOMMENDATIONS_ENABLED_FORCED_OFF) {
return null;
@@ -211,7 +214,8 @@
* @return true if the scorer was changed, or false if the package is not a valid scorer or
* a valid network recommendation provider exists.
*/
- boolean setActiveScorer(String packageName) {
+ @VisibleForTesting
+ public boolean setActiveScorer(String packageName) {
final String oldPackageName = getNetworkRecommendationsPackage();
if (TextUtils.equals(oldPackageName, packageName)) {
@@ -246,7 +250,8 @@
* is no longer valid then {@link Settings.Global#NETWORK_RECOMMENDATIONS_ENABLED} will be set
* to <code>0</code> (disabled).
*/
- void updateState() {
+ @VisibleForTesting
+ public void updateState() {
final int enabledSetting = getNetworkRecommendationsEnabledSetting();
if (enabledSetting == NetworkScoreManager.RECOMMENDATIONS_ENABLED_FORCED_OFF) {
// Don't change anything if it's forced off.
@@ -284,7 +289,8 @@
/**
* Migrates the NETWORK_SCORER_APP Setting to the USE_OPEN_WIFI_PACKAGE Setting.
*/
- void migrateNetworkScorerAppSettingIfNeeded() {
+ @VisibleForTesting
+ public void migrateNetworkScorerAppSettingIfNeeded() {
final String scorerAppPkgNameSetting =
mSettingsFacade.getString(mContext, Settings.Global.NETWORK_SCORER_APP);
if (TextUtils.isEmpty(scorerAppPkgNameSetting)) {
diff --git a/services/core/java/com/android/server/SensorNotificationService.java b/services/core/java/com/android/server/SensorNotificationService.java
index 0610464..7f5befa 100644
--- a/services/core/java/com/android/server/SensorNotificationService.java
+++ b/services/core/java/com/android/server/SensorNotificationService.java
@@ -20,25 +20,46 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.hardware.GeomagneticField;
import android.hardware.Sensor;
+import android.hardware.SensorAdditionalInfo;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Bundle;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
-public class SensorNotificationService extends SystemService implements SensorEventListener {
- //TODO: set DBG to false or remove Slog before release
- private static final boolean DBG = true;
+public class SensorNotificationService extends SystemService
+ implements SensorEventListener, LocationListener {
+ private static final boolean DBG = false;
private static final String TAG = "SensorNotificationService";
- private Context mContext;
+ private static final long MINUTE_IN_MS = 60 * 1000;
+ private static final long KM_IN_M = 1000;
+
+ private static final long LOCATION_MIN_TIME = 30 * MINUTE_IN_MS;
+ private static final long LOCATION_MIN_DISTANCE = 100 * KM_IN_M;
+
+ private static final String PROPERTY_USE_MOCKED_LOCATION =
+ "sensor.notification.use_mocked"; // max key length is 32
+
+ private static final long MILLIS_2010_1_1 = 1262358000000l;
+
+ private Context mContext;
private SensorManager mSensorManager;
+ private LocationManager mLocationManager;
private Sensor mMetaSensor;
+ // for rate limiting
+ private long mLocalGeomagneticFieldUpdateTime = -LOCATION_MIN_TIME;
+
public SensorNotificationService(Context context) {
super(context);
mContext = context;
@@ -50,7 +71,6 @@
public void onBootPhase(int phase) {
if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
- // start
mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
mMetaSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_DYNAMIC_SENSOR_META);
if (mMetaSensor == null) {
@@ -60,13 +80,28 @@
SensorManager.SENSOR_DELAY_FASTEST);
}
}
+
+ if (phase == PHASE_BOOT_COMPLETED) {
+ // LocationManagerService is initialized after PHASE_THIRD_PARTY_APPS_CAN_START
+ mLocationManager =
+ (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+ if (mLocationManager == null) {
+ if (DBG) Slog.d(TAG, "Cannot obtain location service.");
+ } else {
+ mLocationManager.requestLocationUpdates(
+ LocationManager.PASSIVE_PROVIDER,
+ LOCATION_MIN_TIME,
+ LOCATION_MIN_DISTANCE,
+ this);
+ }
+ }
}
private void broadcastDynamicSensorChanged() {
Intent i = new Intent(Intent.ACTION_DYNAMIC_SENSOR_CHANGED);
i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); // avoid waking up manifest receivers
mContext.sendBroadcastAsUser(i, UserHandle.ALL);
- if (DBG) Slog.d(TAG, "DYNS sent dynamic sensor broadcast");
+ if (DBG) Slog.d(TAG, "dynamic sensor broadcast sent");
}
@Override
@@ -77,8 +112,62 @@
}
@Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ public void onLocationChanged(Location location) {
+ if (DBG) Slog.d(TAG, String.format(
+ "Location is (%f, %f), h %f, acc %f, mocked %b",
+ location.getLatitude(), location.getLongitude(),
+ location.getAltitude(), location.getAccuracy(),
+ location.isFromMockProvider()));
+ // lat long == 0 usually means invalid location
+ if (location.getLatitude() == 0 && location.getLongitude() == 0) {
+ return;
+ }
+
+ // update too often, ignore
+ if (SystemClock.elapsedRealtime() - mLocalGeomagneticFieldUpdateTime < 10 * MINUTE_IN_MS) {
+ return;
+ }
+
+ long time = System.currentTimeMillis();
+ // Mocked location should not be used. Except in test, only use mocked location
+ // Wrong system clock also gives bad values so ignore as well.
+ if (useMockedLocation() == location.isFromMockProvider() || time < MILLIS_2010_1_1) {
+ return;
+ }
+
+ GeomagneticField field = new GeomagneticField(
+ (float) location.getLatitude(), (float) location.getLongitude(),
+ (float) location.getAltitude(), time);
+ if (DBG) Slog.d(TAG, String.format(
+ "Nominal mag field, norm %fuT, decline %f deg, incline %f deg",
+ field.getFieldStrength() / 1000, field.getDeclination(), field.getInclination()));
+
+ try {
+ SensorAdditionalInfo info = SensorAdditionalInfo.createLocalGeomagneticField(
+ field.getFieldStrength() / 1000, // convert from nT to uT
+ (float)(field.getDeclination() * Math.PI / 180), // from degree to rad
+ (float)(field.getInclination() * Math.PI / 180)); // from degree to rad
+ if (info != null) {
+ mSensorManager.setOperationParameter(info);
+ mLocalGeomagneticFieldUpdateTime = SystemClock.elapsedRealtime();
+ }
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Invalid local geomagnetic field, ignore.");
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {}
+ @Override
+ public void onStatusChanged(String provider, int status, Bundle extras) {}
+ @Override
+ public void onProviderEnabled(String provider) {}
+ @Override
+ public void onProviderDisabled(String provider) {}
+
+ private boolean useMockedLocation() {
+ return "false".equals(System.getProperty(PROPERTY_USE_MOCKED_LOCATION, "false"));
}
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 7c1a609..dd4d906 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -32,37 +32,39 @@
import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.telephony.CellLocation;
-import android.telephony.Rlog;
-import android.telephony.TelephonyManager;
-import android.telephony.SubscriptionManager;
-import android.telephony.PhoneStateListener;
-import android.telephony.ServiceState;
-import android.telephony.SignalStrength;
import android.telephony.CellInfo;
-import android.telephony.VoLteServiceState;
+import android.telephony.CellLocation;
import android.telephony.DisconnectCause;
+import android.telephony.PhoneStateListener;
import android.telephony.PreciseCallState;
import android.telephony.PreciseDataConnectionState;
import android.telephony.PreciseDisconnectCause;
+import android.telephony.Rlog;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.VoLteServiceState;
import android.text.TextUtils;
-import android.text.format.Time;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
+import android.util.LocalLog;
import com.android.internal.app.IBatteryStats;
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
-import com.android.internal.telephony.ITelephonyRegistry;
import com.android.internal.telephony.IPhoneStateListener;
+import com.android.internal.telephony.ITelephonyRegistry;
import com.android.internal.telephony.PhoneConstantConversions;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.am.BatteryStatsService;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
/**
* Since phone process can be restarted, this class provides a centralized place
* that applications can register and be called back from.
@@ -159,8 +161,6 @@
private String[] mDataConnectionReason;
- private String[] mDataConnectionApn;
-
private ArrayList<String>[] mConnectedApns;
private LinkProperties[] mDataConnectionLinkProperties;
@@ -191,6 +191,8 @@
private boolean mCarrierNetworkChangeState = false;
+ private final LocalLog mLocalLog = new LocalLog(100);
+
private PreciseDataConnectionState mPreciseDataConnectionState =
new PreciseDataConnectionState();
@@ -310,7 +312,6 @@
mMessageWaiting = new boolean[numPhones];
mDataConnectionPossible = new boolean[numPhones];
mDataConnectionReason = new String[numPhones];
- mDataConnectionApn = new String[numPhones];
mCallForwarding = new boolean[numPhones];
mCellLocation = new Bundle[numPhones];
mDataConnectionLinkProperties = new LinkProperties[numPhones];
@@ -329,7 +330,6 @@
mCallForwarding[i] = false;
mDataConnectionPossible[i] = false;
mDataConnectionReason[i] = "";
- mDataConnectionApn[i] = "";
mCellLocation[i] = new Bundle();
mCellInfo.add(i, null);
mConnectedApns[i] = new ArrayList<String>();
@@ -536,7 +536,6 @@
if (DBG) {
log("listen: Register r=" + r + " r.subId=" + r.subId + " phoneId=" + phoneId);
}
- if (VDBG) toStringLogSSC("listen");
if (notifyNow && validatePhoneId(phoneId)) {
if ((events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) {
try {
@@ -780,14 +779,14 @@
}
synchronized (mRecords) {
+ String str = "notifyServiceStateForSubscriber: subId=" + subId + " phoneId=" + phoneId
+ + " state=" + state;
if (VDBG) {
- log("notifyServiceStateForSubscriber: subId=" + subId + " phoneId=" + phoneId
- + " state=" + state);
+ log(str);
}
+ mLocalLog.log(str);
if (validatePhoneId(phoneId)) {
mServiceState[phoneId] = state;
- logServiceStateChanged("notifyServiceStateForSubscriber", subId, phoneId, state);
- if (VDBG) toStringLogSSC("notifyServiceStateForSubscriber");
for (Record r : mRecords) {
if (VDBG) {
@@ -885,7 +884,6 @@
if (VDBG) {
log("notifySignalStrengthForPhoneId: subId=" + subId
+" phoneId=" + phoneId + " signalStrength=" + signalStrength);
- toStringLogSSC("notifySignalStrengthForPhoneId");
}
synchronized (mRecords) {
@@ -1137,18 +1135,20 @@
modified = true;
}
if (modified) {
- if (DBG) {
- log("onDataConnectionStateChanged(" + mDataConnectionState[phoneId]
- + ", " + mDataConnectionNetworkType[phoneId] + ")");
- }
+ String str = "onDataConnectionStateChanged(" + mDataConnectionState[phoneId]
+ + ", " + mDataConnectionNetworkType[phoneId] + ")";
+ log(str);
+ mLocalLog.log(str);
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) &&
idMatch(r.subId, subId, phoneId)) {
try {
- log("Notify data connection state changed on sub: " +
- subId);
- r.callback.onDataConnectionStateChanged(mDataConnectionState[phoneId],
+ if (DBG) {
+ log("Notify data connection state changed on sub: " + subId);
+ }
+ r.callback.onDataConnectionStateChanged(
+ mDataConnectionState[phoneId],
mDataConnectionNetworkType[phoneId]);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
@@ -1163,7 +1163,8 @@
if (r.matchPhoneStateListenerEvent(
PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)) {
try {
- r.callback.onPreciseDataConnectionStateChanged(mPreciseDataConnectionState);
+ r.callback.onPreciseDataConnectionStateChanged(
+ mPreciseDataConnectionState);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -1391,36 +1392,58 @@
}
@Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
+
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+
synchronized (mRecords) {
final int recordCount = mRecords.size();
pw.println("last known state:");
+ pw.increaseIndent();
for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
- pw.println(" Phone Id=" + i);
- pw.println(" mCallState=" + mCallState[i]);
- pw.println(" mCallIncomingNumber=" + mCallIncomingNumber[i]);
- pw.println(" mServiceState=" + mServiceState[i]);
- pw.println(" mVoiceActivationState= " + mVoiceActivationState[i]);
- pw.println(" mDataActivationState= " + mDataActivationState[i]);
- pw.println(" mSignalStrength=" + mSignalStrength[i]);
- pw.println(" mMessageWaiting=" + mMessageWaiting[i]);
- pw.println(" mCallForwarding=" + mCallForwarding[i]);
- pw.println(" mDataActivity=" + mDataActivity[i]);
- pw.println(" mDataConnectionState=" + mDataConnectionState[i]);
- pw.println(" mDataConnectionPossible=" + mDataConnectionPossible[i]);
- pw.println(" mDataConnectionReason=" + mDataConnectionReason[i]);
- pw.println(" mDataConnectionApn=" + mDataConnectionApn[i]);
- pw.println(" mDataConnectionLinkProperties=" + mDataConnectionLinkProperties[i]);
- pw.println(" mDataConnectionNetworkCapabilities=" +
+ pw.println("Phone Id=" + i);
+ pw.increaseIndent();
+ pw.println("mCallState=" + mCallState[i]);
+ pw.println("mCallIncomingNumber=" + mCallIncomingNumber[i]);
+ pw.println("mServiceState=" + mServiceState[i]);
+ pw.println("mVoiceActivationState= " + mVoiceActivationState[i]);
+ pw.println("mDataActivationState= " + mDataActivationState[i]);
+ pw.println("mSignalStrength=" + mSignalStrength[i]);
+ pw.println("mMessageWaiting=" + mMessageWaiting[i]);
+ pw.println("mCallForwarding=" + mCallForwarding[i]);
+ pw.println("mDataActivity=" + mDataActivity[i]);
+ pw.println("mDataConnectionState=" + mDataConnectionState[i]);
+ pw.println("mDataConnectionPossible=" + mDataConnectionPossible[i]);
+ pw.println("mDataConnectionReason=" + mDataConnectionReason[i]);
+ pw.println("mDataConnectionLinkProperties=" + mDataConnectionLinkProperties[i]);
+ pw.println("mDataConnectionNetworkCapabilities=" +
mDataConnectionNetworkCapabilities[i]);
- pw.println(" mCellLocation=" + mCellLocation[i]);
- pw.println(" mCellInfo=" + mCellInfo.get(i));
+ pw.println("mCellLocation=" + mCellLocation[i]);
+ pw.println("mCellInfo=" + mCellInfo.get(i));
+ pw.decreaseIndent();
}
+ pw.println("mConnectedApns=" + Arrays.toString(mConnectedApns));
+ pw.println("mPreciseDataConnectionState=" + mPreciseDataConnectionState);
+ pw.println("mPreciseCallState=" + mPreciseCallState);
+ pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState);
+ pw.println("mRingingCallState=" + mRingingCallState);
+ pw.println("mForegroundCallState=" + mForegroundCallState);
+ pw.println("mBackgroundCallState=" + mBackgroundCallState);
+ pw.println("mVoLteServiceState=" + mVoLteServiceState);
+
+ pw.decreaseIndent();
+
+ pw.println("local logs:");
+ pw.increaseIndent();
+ mLocalLog.dump(fd, pw, args);
+ pw.decreaseIndent();
pw.println("registrations: count=" + recordCount);
+ pw.increaseIndent();
for (Record r : mRecords) {
- pw.println(" " + r);
+ pw.println(r);
}
+ pw.decreaseIndent();
}
}
@@ -1705,63 +1728,6 @@
Rlog.d(TAG, s);
}
- private static class LogSSC {
- private Time mTime;
- private String mS;
- private int mSubId;
- private int mPhoneId;
- private ServiceState mState;
-
- public void set(Time t, String s, int subId, int phoneId, ServiceState state) {
- mTime = t; mS = s; mSubId = subId; mPhoneId = phoneId; mState = state;
- }
-
- @Override
- public String toString() {
- return mS + " Time " + mTime.toString() + " mSubId " + mSubId + " mPhoneId "
- + mPhoneId + " mState " + mState;
- }
- }
-
- private LogSSC logSSC [] = new LogSSC[10];
- private int next = 0;
-
- private void logServiceStateChanged(String s, int subId, int phoneId, ServiceState state) {
- if (logSSC == null || logSSC.length == 0) {
- return;
- }
- if (logSSC[next] == null) {
- logSSC[next] = new LogSSC();
- }
- Time t = new Time();
- t.setToNow();
- logSSC[next].set(t, s, subId, phoneId, state);
- if (++next >= logSSC.length) {
- next = 0;
- }
- }
-
- private void toStringLogSSC(String prompt) {
- if (logSSC == null || logSSC.length == 0 || (next == 0 && logSSC[next] == null)) {
- log(prompt + ": logSSC is empty");
- } else {
- // There is at least one element
- log(prompt + ": logSSC.length=" + logSSC.length + " next=" + next);
- int i = next;
- if (logSSC[i] == null) {
- // logSSC is not full so back to the beginning
- i = 0;
- }
- do {
- log(logSSC[i].toString());
- if (++i >= logSSC.length) {
- i = 0;
- }
- } while (i != next);
- log(prompt + ": ----------------");
- }
- }
-
boolean idMatch(int rSubId, int subId, int phoneId) {
if(subId < 0) {
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index e560d32..738365d 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 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 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 @@
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 @@
*/
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 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 @@
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 @@
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 @@
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 @@
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 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 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 @@
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 4cbfb27..7dd75df 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 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 @@
}
// 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 @@
return;
}
- 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;
- }
- }
- 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;
- }
+ ArrayList<ServiceStartArgs> args = new ArrayList<>();
- if (caughtException != null) {
- // Keep nesting count correct
- final boolean inDestroying = mDestroyingServices.contains(r);
- serviceDoneExecutingLocked(r, inDestroying, inDestroying);
- if (caughtException instanceof TransactionTooLargeException) {
- throw (TransactionTooLargeException)caughtException;
+ while (r.pendingStarts.size() > 0) {
+ 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;
}
- break;
+ }
+ 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));
+ }
+
+ 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;
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 19fc2b8..ee2fdba 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 @@
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 b72cd73..3e3fee5 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 @@
}
/**
- * 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 @@
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 @@
// 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/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index e5b2eca..983c975 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1021,32 +1021,24 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- Slog.d(TAG, "begin setBatteryStateLocked");
- try {
- synchronized (mStats) {
- final boolean onBattery = plugType == BatteryStatsImpl.BATTERY_PLUGGED_NONE;
- if (mStats.isOnBattery() == onBattery) {
- // The battery state has not changed, so we don't need to sync external
- // stats immediately.
- mStats.setBatteryStateLocked(status, health, plugType, level, temp,
- volt,
- chargeUAh, chargeFullUAh);
- return;
- }
+ synchronized (mStats) {
+ final boolean onBattery = plugType == BatteryStatsImpl.BATTERY_PLUGGED_NONE;
+ if (mStats.isOnBattery() == onBattery) {
+ // The battery state has not changed, so we don't need to sync external
+ // stats immediately.
+ mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt,
+ chargeUAh, chargeFullUAh);
+ return;
}
- } finally {
- Slog.d(TAG, "end setBatteryStateLocked");
}
// Sync external stats first as the battery has changed states. If we don't sync
// immediately here, we may not collect the relevant data later.
updateExternalStatsSync("battery-state", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
- Slog.d(TAG, "begin setBatteryStateLocked");
synchronized (mStats) {
mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt,
chargeUAh, chargeFullUAh);
}
- Slog.d(TAG, "end setBatteryStateLocked");
}
});
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 728476a..3b5e5bc 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -670,7 +670,7 @@
}
mInjector.systemServiceManagerCleanupUser(userId);
synchronized (mLock) {
- mInjector.stackSupervisorRemoveUserLocked(userId);
+ mInjector.getActivityStackSupervisor().removeUserLocked(userId);
}
// Remove the user if it is ephemeral.
if (getUserInfo(userId).isEphemeral()) {
@@ -823,8 +823,10 @@
return true;
}
- mInjector.stackSupervisorSetLockTaskModeLocked(null,
- ActivityManager.LOCK_TASK_MODE_NONE, "startUser", false);
+ if (foreground) {
+ mInjector.getActivityStackSupervisor().setLockTaskModeLocked(
+ null, ActivityManager.LOCK_TASK_MODE_NONE, "startUser", false);
+ }
final UserInfo userInfo = getUserInfo(userId);
if (userInfo == null) {
@@ -1202,11 +1204,12 @@
}
void moveUserToForegroundLocked(UserState uss, int oldUserId, int newUserId) {
- boolean homeInFront = mInjector.stackSupervisorSwitchUserLocked(newUserId, uss);
+ boolean homeInFront =
+ mInjector.getActivityStackSupervisor().switchUserLocked(newUserId, uss);
if (homeInFront) {
mInjector.startHomeActivityLocked(newUserId, "moveUserToForeground");
} else {
- mInjector.stackSupervisorResumeFocusedStackTopActivityLocked();
+ mInjector.getActivityStackSupervisor().resumeFocusedStackTopActivityLocked();
}
EventLogTags.writeAmSwitchUser(newUserId);
sendUserSwitchBroadcastsLocked(oldUserId, newUserId);
@@ -1680,10 +1683,6 @@
mService.mSystemServiceManager.cleanupUser(userId);
}
- void stackSupervisorRemoveUserLocked(int userId) {
- mService.mStackSupervisor.removeUserLocked(userId);
- }
-
protected UserManagerService getUserManager() {
if (mUserManager == null) {
IBinder b = ServiceManager.getService(Context.USER_SERVICE);
@@ -1743,24 +1742,10 @@
return mService.checkComponentPermission(permission, pid, uid, owningUid, exported);
}
- boolean stackSupervisorSwitchUserLocked(int userId, UserState uss) {
- return mService.mStackSupervisor.switchUserLocked(userId, uss);
- }
-
void startHomeActivityLocked(int userId, String reason) {
mService.startHomeActivityLocked(userId, reason);
}
- void stackSupervisorResumeFocusedStackTopActivityLocked() {
- mService.mStackSupervisor.resumeFocusedStackTopActivityLocked();
- }
-
- void stackSupervisorSetLockTaskModeLocked(TaskRecord task, int lockTaskModeState,
- String reason, boolean andResume) {
- mService.mStackSupervisor.setLockTaskModeLocked(task, lockTaskModeState, reason,
- andResume);
- }
-
void updateUserConfigurationLocked() {
mService.updateUserConfigurationLocked();
}
@@ -1778,5 +1763,9 @@
true /* above system */);
d.show();
}
+
+ ActivityStackSupervisor getActivityStackSupervisor() {
+ return mService.mStackSupervisor;
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java
index 59f8a2d..6f593b0 100644
--- a/services/core/java/com/android/server/pm/InstantAppResolver.java
+++ b/services/core/java/com/android/server/pm/InstantAppResolver.java
@@ -86,22 +86,18 @@
final List<InstantAppResolveInfo> instantAppResolveInfoList =
connection.getInstantAppResolveInfoList(shaPrefix, token);
- final AuxiliaryResolveInfo resolveInfo;
if (instantAppResolveInfoList == null || instantAppResolveInfoList.size() == 0) {
// No hash prefix match; there are no instant apps for this domain.
if (DEBUG_EPHEMERAL) {
Log.d(TAG, "No results returned");
}
- resolveInfo = null;
- } else {
- resolveInfo = InstantAppResolver.filterInstantAppIntent(instantAppResolveInfoList,
- intent, requestObj.resolvedType, requestObj.userId,
- intent.getPackage(), digest, token);
+ return null;
}
-
+ final AuxiliaryResolveInfo resolveInfo = InstantAppResolver.filterInstantAppIntent(
+ instantAppResolveInfoList, intent, requestObj.resolvedType, requestObj.userId,
+ intent.getPackage(), digest, token);
logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_ONE, startTime, token,
- resolveInfo != null ? RESOLUTION_SUCCESS : RESOLUTION_FAILURE);
-
+ RESOLUTION_SUCCESS);
return resolveInfo;
}
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index d9ea728..acbd446 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -151,6 +151,7 @@
// TODO(calin,jeffhao): shared library paths should be adjusted to include previous code
// paths (b/34169257).
final String sharedLibrariesPath = getSharedLibrariesPath(sharedLibraries);
+ // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags.
final int dexoptFlags = getDexFlags(pkg, compilerFilter);
int result = DEX_OPT_SKIPPED;
@@ -254,6 +255,8 @@
@GuardedBy("mInstallLock")
private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path, Set<String> isas,
String compilerFilter, boolean isUsedByOtherApps) {
+ compilerFilter = getRealCompilerFilter(info, compilerFilter, isUsedByOtherApps);
+ // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags.
int dexoptFlags = getDexFlags(info, compilerFilter) | DEXOPT_SECONDARY_DEX;
// Check the app storage and add the appropriate flags.
if (info.dataDir.equals(info.deviceProtectedDataDir)) {
@@ -264,7 +267,6 @@
Slog.e(TAG, "Could not infer CE/DE storage for package " + info.packageName);
return DEX_OPT_FAILED;
}
- compilerFilter = getRealCompilerFilter(info, compilerFilter, isUsedByOtherApps);
Log.d(TAG, "Running dexopt on: " + path
+ " pkg=" + info.packageName + " isa=" + isas
+ " dexoptFlags=" + printDexoptFlags(dexoptFlags)
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 3303081..f62f115 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 @@
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 @@
| 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 @@
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 @@
.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;
}
@@ -5723,20 +5753,23 @@
synchronized (mPackages) {
final int count = (resolvedActivities == null ? 0 : resolvedActivities.size());
for (int n = 0; n < count; n++) {
- ResolveInfo info = resolvedActivities.get(n);
- String packageName = info.activityInfo.packageName;
- PackageSetting ps = mSettings.mPackages.get(packageName);
+ final ResolveInfo info = resolvedActivities.get(n);
+ final String packageName = info.activityInfo.packageName;
+ final PackageSetting ps = mSettings.mPackages.get(packageName);
if (ps != null) {
- // Try to get the status from User settings first
- long packedStatus = getDomainVerificationStatusLPr(ps, userId);
- int status = (int) (packedStatus >> 32);
- if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS
+ // only check domain verification status if the app is not a browser
+ if (!info.handleAllWebDataURI) {
+ // Try to get the status from User settings first
+ final long packedStatus = getDomainVerificationStatusLPr(ps, userId);
+ final int status = (int) (packedStatus >> 32);
+ if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS
|| status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {
- if (DEBUG_EPHEMERAL) {
- Slog.v(TAG, "DENY ephemeral apps;"
- + " pkg: " + packageName + ", status: " + status);
+ if (DEBUG_EPHEMERAL) {
+ Slog.v(TAG, "DENY instant app;"
+ + " pkg: " + packageName + ", status: " + status);
+ }
+ return false;
}
- return false;
}
if (ps.getInstantApp(userId)) {
if (DEBUG_EPHEMERAL) {
@@ -13024,7 +13057,18 @@
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/core/java/com/android/server/vr/CompatibilityDisplay.java b/services/core/java/com/android/server/vr/CompatibilityDisplay.java
index ae1d50f..772fc26 100644
--- a/services/core/java/com/android/server/vr/CompatibilityDisplay.java
+++ b/services/core/java/com/android/server/vr/CompatibilityDisplay.java
@@ -95,20 +95,15 @@
* Creates and Destroys the virtual display depending on the current state of VrMode.
*/
private void updateVirtualDisplay() {
- boolean createVirtualDisplay = "true".equals(SystemProperties.get("vr_virtualdisplay"));
if (DEBUG) {
- Log.i(TAG, "isVrMode: " + mIsVrModeEnabled + ", createVD: " + createVirtualDisplay +
- ", override: " + mIsVrModeOverrideEnabled);
+ Log.i(TAG, "isVrMode: " + mIsVrModeEnabled + ", override: " + mIsVrModeOverrideEnabled);
}
- if (mIsVrModeEnabled || (createVirtualDisplay && mIsVrModeOverrideEnabled)) {
+ if (mIsVrModeEnabled || mIsVrModeOverrideEnabled) {
// TODO: Consider not creating the display until ActivityManager needs one on
// which to display a 2D application.
- // TODO: STOPSHIP Remove createVirtualDisplay conditional before launching.
- if (createVirtualDisplay) {
- startVirtualDisplay();
- startImageReader();
- }
+ startVirtualDisplay();
+ startImageReader();
} else {
// Stop virtual display to test exit condition
stopVirtualDisplay();
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 1decf4e..a8664a5 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -505,6 +505,13 @@
getController().removeStartingWindow();
}
+ // If this window was animating, then we need to ensure that the app transition notifies
+ // that animations have completed in WMS.handleAnimatingStoppedAndTransitionLocked(), so
+ // add to that list now
+ if (mAppAnimator.animating) {
+ mService.mNoAnimationNotifyOnTransitionFinished.add(token);
+ }
+
final TaskStack stack = getTask().mStack;
if (delayed && !isEmpty()) {
// set the token aside because it has an active animation to be finished
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index 9f0ed21..7b8057ca 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -61,17 +61,21 @@
extends WindowManagerInternal.AppTransitionListener implements Runnable {
public void onAppTransitionCancelledLocked() {
+ if (DEBUG) Slog.d(TAG, "onAppTransitionCancelledLocked:"
+ + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition);
animationFinished();
}
public void onAppTransitionFinishedLocked(IBinder token) {
+ if (DEBUG) Slog.d(TAG, "onAppTransitionFinishedLocked:"
+ + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition);
animationFinished();
}
private void animationFinished() {
if (mFinishAnimationAfterTransition) {
mHandler.removeCallbacks(this);
- // This might end up calling into activity manager which will be bad since we have the
- // window manager lock held at this point. Post a message to take care of the processing
- // so we don't deadlock.
+ // This might end up calling into activity manager which will be bad since we have
+ // the window manager lock held at this point. Post a message to take care of the
+ // processing so we don't deadlock.
mHandler.post(this);
}
}
@@ -195,6 +199,7 @@
if (!mTarget.setPinnedStackSize(mTmpRect, mTmpTaskBounds)) {
// Whoops, the target doesn't feel like animating anymore. Let's immediately finish
// any further animation.
+ if (DEBUG) Slog.d(TAG, "animateUpdate: cancelled");
animation.cancel();
}
}
@@ -203,7 +208,9 @@
public void onAnimationEnd(Animator animation) {
if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget
+ " mMoveToFullScreen=" + mMoveToFullScreen
- + " mSkipAnimationEnd=" + mSkipAnimationEnd);
+ + " mSkipAnimationEnd=" + mSkipAnimationEnd
+ + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition
+ + " mAppTransitionIsRunning=" + mAppTransition.isRunning());
// There could be another animation running. For example in the
// move to fullscreen case, recents will also be closing while the
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 3cb96a1..ee2d5de 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -441,6 +441,8 @@
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now closing app " + wtoken);
appAnimator.clearThumbnail();
appAnimator.setNullAnimation();
+ // TODO: Do we need to add to mNoAnimationNotifyOnTransitionFinished like above if not
+ // animating?
wtoken.setVisibility(animLp, false, transit, false, voiceInteraction);
wtoken.updateReportedVisibilityLocked();
// Force the allDrawn flag, because we want to start
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index a5eac46..63af2da 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -668,6 +668,7 @@
VibratorService vibrator = null;
IStorageManager storageManager = null;
NetworkManagementService networkManagement = null;
+ IpSecService ipSecService = null;
NetworkStatsService networkStats = null;
NetworkPolicyManagerService networkPolicy = null;
ConnectivityService connectivity = null;
@@ -1015,6 +1016,15 @@
reportWtf("starting NetworkManagement Service", e);
}
traceEnd();
+
+ traceBeginAndSlog("StartIpSecService");
+ try {
+ ipSecService = IpSecService.create(context);
+ ServiceManager.addService(Context.IPSEC_SERVICE, ipSecService);
+ } catch (Throwable e) {
+ reportWtf("starting IpSec Service", e);
+ }
+ traceEnd();
}
if (!disableNonCoreServices && !disableTextServices) {
@@ -1628,6 +1638,7 @@
final TelephonyRegistry telephonyRegistryF = telephonyRegistry;
final MediaRouterService mediaRouterF = mediaRouter;
final MmsServiceBroker mmsServiceF = mmsService;
+ final IpSecService ipSecServiceF = ipSecService;
// We now tell the activity manager it is okay to run third party
// code. It will call back into us once it has gotten to the state
@@ -1691,6 +1702,13 @@
.networkScoreAndNetworkManagementServiceReady();
}
traceEnd();
+ traceBeginAndSlog("MakeIpSecServiceReady");
+ try {
+ if (ipSecServiceF != null) ipSecServiceF.systemReady();
+ } catch (Throwable e) {
+ reportWtf("making IpSec Service ready", e);
+ }
+ traceEnd();
traceBeginAndSlog("MakeNetworkStatsServiceReady");
try {
if (networkStatsF != null) networkStatsF.systemReady();
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index 590bce1..61a9294 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -23,7 +23,6 @@
import android.net.apf.ApfCapabilities;
import android.net.apf.ApfFilter;
import android.net.DhcpResults;
-import android.net.INetd;
import android.net.InterfaceConfiguration;
import android.net.LinkAddress;
import android.net.LinkProperties;
@@ -35,12 +34,10 @@
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.IpManagerEvent;
import android.net.util.MultinetworkPolicyTracker;
-import android.net.util.NetdService;
import android.os.INetworkManagementService;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.ServiceSpecificException;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.LocalLog;
@@ -1030,16 +1027,14 @@
private boolean startIPv6() {
// Set privacy extensions.
- final String PREFER_TEMPADDRS = "2";
try {
- NetdService.run((INetd netd) -> {
- netd.setProcSysNet(
- INetd.IPV6, INetd.CONF, mInterfaceName, "use_tempaddr",
- PREFER_TEMPADDRS);
- });
+ mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true);
mNwService.enableIpv6(mInterfaceName);
- } catch (IllegalStateException|RemoteException|ServiceSpecificException e) {
- logError("Unable to change interface settings: %s", e);
+ } catch (RemoteException re) {
+ logError("Unable to change interface settings: %s", re);
+ return false;
+ } catch (IllegalStateException ie) {
+ logError("Unable to change interface settings: %s", ie);
return false;
}
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 6c7f146..205c8de 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -52,20 +52,6 @@
<application>
<uses-library android:name="android.test.runner" />
- <service android:name="com.android.server.AccessibilityManagerServiceTest$MyFirstMockAccessibilityService"
- android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
- <intent-filter>
- <action android:name="android.accessibilityservice.AccessibilityService"/>
- </intent-filter>
- </service>
-
- <service android:name="com.android.server.AccessibilityManagerServiceTest$MySecondMockAccessibilityService"
- android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
- <intent-filter>
- <action android:name="android.accessibilityservice.AccessibilityService"/>
- </intent-filter>
- </service>
-
<service android:name="com.android.server.accounts.TestAccountType1AuthenticatorService"
android:exported="false">
<intent-filter>
diff --git a/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java
deleted file mode 100644
index 340c624..0000000
--- a/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java
+++ /dev/null
@@ -1,762 +0,0 @@
-/*
- * Copyright (C) 2010 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;
-
-import android.accessibilityservice.AccessibilityService;
-import android.accessibilityservice.AccessibilityServiceInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ServiceInfo;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.IAccessibilityManager;
-import android.view.accessibility.IAccessibilityManagerClient;
-
-import com.android.internal.util.IntPair;
-
-/**
- * This test exercises the
- * {@link com.android.server.accessibility.AccessibilityManagerService} by mocking the
- * {@link android.view.accessibility.AccessibilityManager} which talks to to the
- * service. The service itself is interacting with the platform. Note: Testing
- * the service in full isolation would require significant amount of work for
- * mocking all system interactions. It would also require a lot of mocking code.
- */
-public class AccessibilityManagerServiceTest extends AndroidTestCase {
-
- /**
- * Timeout required for pending Binder calls or event processing to
- * complete.
- */
- private static final long TIMEOUT_BINDER_CALL = 100;
-
- /**
- * Timeout in which we are waiting for the system to start the mock
- * accessibility services.
- */
- private static final long TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES = 1000;
-
- /**
- * Timeout used for testing that a service is notified only upon a
- * notification timeout.
- */
- private static final long TIMEOUT_TEST_NOTIFICATION_TIMEOUT = 300;
-
- /**
- * The interface used to talk to the tested service.
- */
- private IAccessibilityManager mManagerService;
-
- @Override
- protected void setUp() throws Exception {
- // Reset the state.
- ensureOnlyMockServicesEnabled(getContext(), false, false);
- }
-
- @Override
- public void setContext(Context context) {
- super.setContext(context);
- if (MyFirstMockAccessibilityService.sComponentName == null) {
- MyFirstMockAccessibilityService.sComponentName = new ComponentName(
- context.getPackageName(), MyFirstMockAccessibilityService.class.getName())
- .flattenToShortString();
- }
- if (MySecondMockAccessibilityService.sComponentName == null) {
- MySecondMockAccessibilityService.sComponentName = new ComponentName(
- context.getPackageName(), MySecondMockAccessibilityService.class.getName())
- .flattenToShortString();
- }
- }
-
- /**
- * Creates a new instance.
- */
- public AccessibilityManagerServiceTest() {
- IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
- mManagerService = IAccessibilityManager.Stub.asInterface(iBinder);
- }
-
- @LargeTest
- public void testAddClient_AccessibilityDisabledThenEnabled() throws Exception {
- // at least some service must be enabled, otherwise accessibility will always be disabled.
- ensureOnlyMockServicesEnabled(mContext, true, false);
-
- // make sure accessibility is disabled
- ensureAccessibilityEnabled(mContext, false);
-
- // create a client mock instance
- MyMockAccessibilityManagerClient mockClient = new MyMockAccessibilityManagerClient();
-
- // invoke the method under test
- final int stateFlagsDisabled =
- IntPair.first(mManagerService.addClient(mockClient, UserHandle.USER_CURRENT));
- boolean enabledAccessibilityDisabled =
- (stateFlagsDisabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
-
- // check expected result
- assertFalse("The client must be disabled since accessibility is disabled.",
- enabledAccessibilityDisabled);
-
- // enable accessibility
- ensureAccessibilityEnabled(mContext, true);
-
- // invoke the method under test
- final int stateFlagsEnabled =
- IntPair.first(mManagerService.addClient(mockClient, UserHandle.USER_CURRENT));
- boolean enabledAccessibilityEnabled =
- (stateFlagsEnabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
-
- // check expected result
- assertTrue("The client must be enabled since accessibility is enabled.",
- enabledAccessibilityEnabled);
- }
-
- @LargeTest
- public void testAddClient_AccessibilityEnabledThenDisabled() throws Exception {
- // at least some service must be enabled, otherwise accessibility will always be disabled.
- ensureOnlyMockServicesEnabled(mContext, true, false);
-
- // enable accessibility before registering the client
- ensureAccessibilityEnabled(mContext, true);
-
- // create a client mock instance
- MyMockAccessibilityManagerClient mockClient = new MyMockAccessibilityManagerClient();
-
- // invoke the method under test
- final int stateFlagsEnabled =
- IntPair.first(mManagerService.addClient(mockClient, UserHandle.USER_CURRENT));
- boolean enabledAccessibilityEnabled =
- (stateFlagsEnabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
-
- // check expected result
- assertTrue("The client must be enabled since accessibility is enabled.",
- enabledAccessibilityEnabled);
-
- // disable accessibility
- ensureAccessibilityEnabled(mContext, false);
-
- // invoke the method under test
- final int stateFlagsDisabled =
- IntPair.first(mManagerService.addClient(mockClient, UserHandle.USER_CURRENT));
- boolean enabledAccessibilityDisabled =
- (stateFlagsDisabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
-
- // check expected result
- assertFalse("The client must be disabled since accessibility is disabled.",
- enabledAccessibilityDisabled);
- }
-
- @LargeTest
- public void testGetAccessibilityServicesList() throws Exception {
- boolean firstMockServiceInstalled = false;
- boolean secondMockServiceInstalled = false;
-
- String packageName = getContext().getPackageName();
- String firstMockServiceClassName = MyFirstMockAccessibilityService.class.getName();
- String secondMockServiceClassName = MySecondMockAccessibilityService.class.getName();
-
- // look for the two mock services
- for (AccessibilityServiceInfo info : mManagerService.getInstalledAccessibilityServiceList(
- UserHandle.USER_CURRENT)) {
- ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo;
- if (packageName.equals(serviceInfo.packageName)) {
- if (firstMockServiceClassName.equals(serviceInfo.name)) {
- firstMockServiceInstalled = true;
- } else if (secondMockServiceClassName.equals(serviceInfo.name)) {
- secondMockServiceInstalled = true;
- }
- }
- }
-
- // check expected result
- assertTrue("First mock service must be installed", firstMockServiceInstalled);
- assertTrue("Second mock service must be installed", secondMockServiceInstalled);
- }
-
- @LargeTest
- public void testSendAccessibilityEvent_OneService_MatchingPackageAndEventType()
- throws Exception {
- // enable the mock accessibility service
- ensureOnlyMockServicesEnabled(mContext, true, false);
-
- // set the accessibility setting value
- ensureAccessibilityEnabled(mContext, true);
-
- // configure the mock service
- MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance;
- service.setServiceInfo(MockAccessibilityService.createDefaultInfo());
-
- // wait for the binder call to #setService to complete
- Thread.sleep(TIMEOUT_BINDER_CALL);
-
- // create and populate an event to be sent
- AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
- fullyPopulateDefaultAccessibilityEvent(sentEvent);
-
- // set expectations
- service.expectEvent(sentEvent);
- service.replay();
-
- // send the event
- mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_CURRENT);
-
- // verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(service);
- }
-
- @LargeTest
- public void testSendAccessibilityEvent_OneService_NotMatchingPackage() throws Exception {
- // enable the mock accessibility service
- ensureOnlyMockServicesEnabled(mContext, true, false);
-
- // set the accessibility setting value
- ensureAccessibilityEnabled(mContext, true);
-
- // configure the mock service
- MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance;
- service.setServiceInfo(MockAccessibilityService.createDefaultInfo());
-
- // wait for the binder call to #setService to complete
- Thread.sleep(TIMEOUT_BINDER_CALL);
-
- // create and populate an event to be sent
- AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
- fullyPopulateDefaultAccessibilityEvent(sentEvent);
- sentEvent.setPackageName("no.service.registered.for.this.package");
-
- // set expectations
- service.replay();
-
- // send the event
- mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_CURRENT);
-
- // verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(service);
- }
-
- @LargeTest
- public void testSendAccessibilityEvent_OneService_NotMatchingEventType() throws Exception {
- // enable the mock accessibility service
- ensureOnlyMockServicesEnabled(mContext, true, false);
-
- // set the accessibility setting value
- ensureAccessibilityEnabled(mContext, true);
-
- // configure the mock service
- MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance;
- service.setServiceInfo(MockAccessibilityService.createDefaultInfo());
-
- // wait for the binder call to #setService to complete
- Thread.sleep(TIMEOUT_BINDER_CALL);
-
- // create and populate an event to be sent
- AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
- fullyPopulateDefaultAccessibilityEvent(sentEvent);
- sentEvent.setEventType(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
-
- // set expectations
- service.replay();
-
- // send the event
- mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_CURRENT);
-
- // verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(service);
- }
-
- @LargeTest
- public void testSendAccessibilityEvent_OneService_NotificationAfterTimeout() throws Exception {
- // enable the mock accessibility service
- ensureOnlyMockServicesEnabled(mContext, true, false);
-
- // set the accessibility setting value
- ensureAccessibilityEnabled(mContext, true);
-
- // configure the mock service
- MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance;
- AccessibilityServiceInfo info = MockAccessibilityService.createDefaultInfo();
- info.notificationTimeout = TIMEOUT_TEST_NOTIFICATION_TIMEOUT;
- service.setServiceInfo(info);
-
- // wait for the binder call to #setService to complete
- Thread.sleep(TIMEOUT_BINDER_CALL);
-
- // create and populate the first event to be sent
- AccessibilityEvent firstEvent = AccessibilityEvent.obtain();
- fullyPopulateDefaultAccessibilityEvent(firstEvent);
-
- // create and populate the second event to be sent
- AccessibilityEvent secondEvent = AccessibilityEvent.obtain();
- fullyPopulateDefaultAccessibilityEvent(secondEvent);
-
- // set expectations
- service.expectEvent(secondEvent);
- service.replay();
-
- // send the events
- mManagerService.sendAccessibilityEvent(firstEvent, UserHandle.USER_CURRENT);
- mManagerService.sendAccessibilityEvent(secondEvent, UserHandle.USER_CURRENT);
-
- // wait for #sendAccessibilityEvent to reach the backing service
- Thread.sleep(TIMEOUT_BINDER_CALL);
-
- try {
- service.verify();
- fail("No events must be dispatched before the expiration of the notification timeout.");
- } catch (IllegalStateException ise) {
- /* expected */
- }
-
- // wait for the configured notification timeout to expire
- Thread.sleep(TIMEOUT_TEST_NOTIFICATION_TIMEOUT);
-
- // verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(service);
- }
-
- @LargeTest
- public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_DiffFeedback()
- throws Exception {
- // enable the mock accessibility services
- ensureOnlyMockServicesEnabled(mContext, true, true);
-
- // set the accessibility setting value
- ensureAccessibilityEnabled(mContext, true);
-
- // configure the first mock service
- MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance;
- AccessibilityServiceInfo firstInfo = MockAccessibilityService.createDefaultInfo();
- firstInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_AUDIBLE;
- firstService.setServiceInfo(firstInfo);
-
- // configure the second mock service
- MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance;
- AccessibilityServiceInfo secondInfo = MockAccessibilityService.createDefaultInfo();
- secondInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_HAPTIC;
- secondService.setServiceInfo(secondInfo);
-
- // wait for the binder calls to #setService to complete
- Thread.sleep(TIMEOUT_BINDER_CALL);
-
- // create and populate an event to be sent
- AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
- fullyPopulateDefaultAccessibilityEvent(sentEvent);
-
- // set expectations for the first mock service
- firstService.expectEvent(sentEvent);
- firstService.replay();
-
- // set expectations for the second mock service
- secondService.expectEvent(sentEvent);
- secondService.replay();
-
- // send the event
- mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_CURRENT);
-
- // verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(firstService);
- assertMockServiceVerifiedWithinTimeout(secondService);
- }
-
- @LargeTest
- public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType()
- throws Exception {
- // enable the mock accessibility services
- ensureOnlyMockServicesEnabled(mContext, true, true);
-
- // set the accessibility setting value
- ensureAccessibilityEnabled(mContext, true);
-
- // configure the first mock service
- MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance;
- firstService.setServiceInfo(MockAccessibilityService.createDefaultInfo());
-
- // configure the second mock service
- MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance;
- secondService.setServiceInfo(MockAccessibilityService.createDefaultInfo());
-
- // wait for the binder calls to #setService to complete
- Thread.sleep(TIMEOUT_BINDER_CALL);
-
- // create and populate an event to be sent
- AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
- fullyPopulateDefaultAccessibilityEvent(sentEvent);
-
- // set expectations for the first mock service
- firstService.expectEvent(sentEvent);
- firstService.replay();
-
- // set expectations for the second mock service
- secondService.replay();
-
- // send the event
- mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_CURRENT);
-
- // verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(firstService);
- assertMockServiceVerifiedWithinTimeout(secondService);
- }
-
- @LargeTest
- public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_OneDefault()
- throws Exception {
- // enable the mock accessibility services
- ensureOnlyMockServicesEnabled(mContext, true, true);
-
- // set the accessibility setting value
- ensureAccessibilityEnabled(mContext, true);
-
- // configure the first mock service
- MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance;
- AccessibilityServiceInfo firstInfo = MyFirstMockAccessibilityService.createDefaultInfo();
- firstInfo.flags = AccessibilityServiceInfo.DEFAULT;
- firstService.setServiceInfo(firstInfo);
-
- // configure the second mock service
- MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance;
- secondService.setServiceInfo(MySecondMockAccessibilityService.createDefaultInfo());
-
- // wait for the binder calls to #setService to complete
- Thread.sleep(TIMEOUT_BINDER_CALL);
-
- // create and populate an event to be sent
- AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
- fullyPopulateDefaultAccessibilityEvent(sentEvent);
-
- // set expectations for the first mock service
- firstService.replay();
-
- // set expectations for the second mock service
- secondService.expectEvent(sentEvent);
- secondService.replay();
-
- // send the event
- mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_CURRENT);
-
- // verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(firstService);
- assertMockServiceVerifiedWithinTimeout(secondService);
- }
-
- @LargeTest
- public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_TwoDefault()
- throws Exception {
- // enable the mock accessibility services
- ensureOnlyMockServicesEnabled(mContext, true, true);
-
- // set the accessibility setting value
- ensureAccessibilityEnabled(mContext, true);
-
- // configure the first mock service
- MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance;
- AccessibilityServiceInfo firstInfo = MyFirstMockAccessibilityService.createDefaultInfo();
- firstInfo.flags = AccessibilityServiceInfo.DEFAULT;
- firstService.setServiceInfo(firstInfo);
-
- // configure the second mock service
- MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance;
- AccessibilityServiceInfo secondInfo = MyFirstMockAccessibilityService.createDefaultInfo();
- secondInfo.flags = AccessibilityServiceInfo.DEFAULT;
- secondService.setServiceInfo(firstInfo);
-
- // wait for the binder calls to #setService to complete
- Thread.sleep(TIMEOUT_BINDER_CALL);
-
- // create and populate an event to be sent
- AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
- fullyPopulateDefaultAccessibilityEvent(sentEvent);
-
- // set expectations for the first mock service
- firstService.expectEvent(sentEvent);
- firstService.replay();
-
- // set expectations for the second mock service
- secondService.replay();
-
- // send the event
- mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_CURRENT);
-
- // verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(firstService);
- assertMockServiceVerifiedWithinTimeout(secondService);
- }
-
- @LargeTest
- public void testInterrupt() throws Exception {
- // enable the mock accessibility services
- ensureOnlyMockServicesEnabled(mContext, true, true);
-
- // set the accessibility setting value
- ensureAccessibilityEnabled(mContext, true);
-
- // configure the first mock service
- MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance;
- firstService.setServiceInfo(MockAccessibilityService.createDefaultInfo());
-
- // configure the second mock service
- MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance;
- secondService.setServiceInfo(MockAccessibilityService.createDefaultInfo());
-
- // wait for the binder calls to #setService to complete
- Thread.sleep(TIMEOUT_BINDER_CALL);
-
- // set expectations for the first mock service
- firstService.expectInterrupt();
- firstService.replay();
-
- // set expectations for the second mock service
- secondService.expectInterrupt();
- secondService.replay();
-
- // call the method under test
- mManagerService.interrupt(UserHandle.USER_CURRENT);
-
- // verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(firstService);
- assertMockServiceVerifiedWithinTimeout(secondService);
- }
-
- /**
- * Fully populates the {@link AccessibilityEvent} to marshal.
- *
- * @param sentEvent The event to populate.
- */
- private void fullyPopulateDefaultAccessibilityEvent(AccessibilityEvent sentEvent) {
- sentEvent.setAddedCount(1);
- sentEvent.setBeforeText("BeforeText");
- sentEvent.setChecked(true);
- sentEvent.setClassName("foo.bar.baz.Class");
- sentEvent.setContentDescription("ContentDescription");
- sentEvent.setCurrentItemIndex(1);
- sentEvent.setEnabled(true);
- sentEvent.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT);
- sentEvent.setEventTime(1000);
- sentEvent.setFromIndex(1);
- sentEvent.setFullScreen(true);
- sentEvent.setItemCount(1);
- sentEvent.setPackageName("foo.bar.baz");
- sentEvent.setParcelableData(Message.obtain(null, 1, null));
- sentEvent.setPassword(true);
- sentEvent.setRemovedCount(1);
- }
-
- /**
- * This class is a mock {@link IAccessibilityManagerClient}.
- */
- public class MyMockAccessibilityManagerClient extends IAccessibilityManagerClient.Stub {
- int mState;
-
- public void setState(int state) {
- mState = state;
- }
-
- public void notifyServicesStateChanged() {}
-
- public void setRelevantEventTypes(int eventTypes) {}
-
- public void setTouchExplorationEnabled(boolean enabled) {}
- }
-
- /**
- * Ensures accessibility is in a given state by writing the state to the
- * settings and waiting until the accessibility manager service pick it up.
- *
- * @param context A context handle to access the settings.
- * @param enabled The accessibility state to write to the settings.
- * @throws Exception If any error occurs.
- */
- private void ensureAccessibilityEnabled(Context context, boolean enabled) throws Exception {
- boolean isEnabled = Settings.Secure.getInt(context.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
-
- if (isEnabled == enabled) {
- return;
- }
-
- Settings.Secure.putInt(context.getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED,
- enabled ? 1 : 0);
-
- // wait the accessibility manager service to pick the change up
- Thread.sleep(TIMEOUT_BINDER_CALL);
- }
-
- /**
- * Ensures the only {@link MockAccessibilityService}s with given component
- * names are enabled by writing to the system settings and waiting until the
- * accessibility manager service picks that up or the
- * {@link #TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES} is exceeded.
- *
- * @param context A context handle to access the settings.
- * @param firstMockServiceEnabled If the first mock accessibility service is enabled.
- * @param secondMockServiceEnabled If the second mock accessibility service is enabled.
- * @throws IllegalStateException If some of the requested for enabling mock services
- * is not properly started.
- * @throws Exception Exception If any error occurs.
- */
- private void ensureOnlyMockServicesEnabled(Context context, boolean firstMockServiceEnabled,
- boolean secondMockServiceEnabled) throws Exception {
- String enabledServices = Settings.Secure.getString(context.getContentResolver(),
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
-
- StringBuilder servicesToEnable = new StringBuilder();
- if (firstMockServiceEnabled) {
- servicesToEnable.append(MyFirstMockAccessibilityService.sComponentName).append(":");
- }
- if (secondMockServiceEnabled) {
- servicesToEnable.append(MySecondMockAccessibilityService.sComponentName).append(":");
- }
-
- Settings.Secure.putString(context.getContentResolver(),
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, servicesToEnable.toString());
-
- // Optimization. If things will not change, we don't have to do anything.
- if (servicesToEnable.equals(enabledServices)) {
- return;
- }
-
- // we have enabled the services of interest and need to wait until they
- // are instantiated and started (if needed) and the system binds to them
- boolean firstMockServiceOK = false;
- boolean secondMockServiceOK = false;
- long start = SystemClock.uptimeMillis();
- long pollingInterval = TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES / 6;
-
- while (SystemClock.uptimeMillis() - start < TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES) {
- firstMockServiceOK = !firstMockServiceEnabled
- || (MyFirstMockAccessibilityService.sInstance != null
- && MyFirstMockAccessibilityService.sInstance.isSystemBoundAsClient());
-
- secondMockServiceOK = !secondMockServiceEnabled
- || (MySecondMockAccessibilityService.sInstance != null
- && MySecondMockAccessibilityService.sInstance.isSystemBoundAsClient());
-
- if (firstMockServiceOK && secondMockServiceOK) {
- return;
- }
-
- Thread.sleep(pollingInterval);
- }
-
- StringBuilder message = new StringBuilder();
- message.append("Mock accessibility services not started or system not bound as a client: ");
- if (!firstMockServiceOK) {
- message.append(MyFirstMockAccessibilityService.sComponentName);
- message.append(" ");
- }
- if (!secondMockServiceOK) {
- message.append(MySecondMockAccessibilityService.sComponentName);
- }
- throw new IllegalStateException(message.toString());
- }
-
- /**
- * Asserts the the mock accessibility service has been successfully verified
- * (which is it has received the expected method calls with expected
- * arguments) within the {@link #TIMEOUT_BINDER_CALL}. The verified state is
- * checked by polling upon small intervals.
- *
- * @param service The service to verify.
- * @throws Exception If the verification has failed with exception after the
- * {@link #TIMEOUT_BINDER_CALL}.
- */
- private void assertMockServiceVerifiedWithinTimeout(MockAccessibilityService service)
- throws Exception {
- Exception lastVerifyException = null;
- long beginTime = SystemClock.uptimeMillis();
- long pollTimeout = TIMEOUT_BINDER_CALL / 5;
-
- // poll until the timeout has elapsed
- while (SystemClock.uptimeMillis() - beginTime < TIMEOUT_BINDER_CALL) {
- // sleep first since immediate call will always fail
- try {
- Thread.sleep(pollTimeout);
- } catch (InterruptedException ie) {
- /* ignore */
- }
- // poll for verification and if this fails save the exception and
- // keep polling
- try {
- service.verify();
- // reset so it does not accept more events
- service.reset();
- return;
- } catch (Exception e) {
- lastVerifyException = e;
- }
- }
-
- // reset, we have already failed
- service.reset();
-
- // always not null
- throw lastVerifyException;
- }
-
- /**
- * This class is the first mock {@link AccessibilityService}.
- */
- public static class MyFirstMockAccessibilityService extends MockAccessibilityService {
-
- /**
- * The service {@link ComponentName} flattened as a string.
- */
- static String sComponentName;
-
- /**
- * Handle to the service instance.
- */
- static MyFirstMockAccessibilityService sInstance;
-
- /**
- * Creates a new instance.
- */
- public MyFirstMockAccessibilityService() {
- sInstance = this;
- }
- }
-
- /**
- * This class is the first mock {@link AccessibilityService}.
- */
- public static class MySecondMockAccessibilityService extends MockAccessibilityService {
-
- /**
- * The service {@link ComponentName} flattened as a string.
- */
- static String sComponentName;
-
- /**
- * Handle to the service instance.
- */
- static MySecondMockAccessibilityService sInstance;
-
- /**
- * Creates a new instance.
- */
- public MySecondMockAccessibilityService() {
- sInstance = this;
- }
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java b/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java
deleted file mode 100644
index 9261771..0000000
--- a/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2010 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;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.accessibilityservice.AccessibilityServiceInfo;
-import android.os.UserHandle;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.IAccessibilityManager;
-import android.view.accessibility.IAccessibilityManagerClient;
-
-import com.android.internal.util.IntPair;
-
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Tests for the AccessibilityManager which mocking the backing service.
- */
-public class AccessibilityManagerTest extends AndroidTestCase {
-
- /**
- * Timeout required for pending Binder calls or event processing to
- * complete.
- */
- public static final long TIMEOUT_BINDER_CALL = 50;
-
- @Mock
- private IAccessibilityManager mMockService;
-
- @Override
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- }
-
- private AccessibilityManager createManager(boolean enabled) throws Exception {
- if (enabled) {
- when(mMockService.addClient(any(IAccessibilityManagerClient.class), anyInt()))
- .thenReturn(
- IntPair.of(AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED,
- AccessibilityEvent.TYPES_ALL_MASK));
- } else {
- when(mMockService.addClient(any(IAccessibilityManagerClient.class), anyInt()))
- .thenReturn(IntPair.of(0, AccessibilityEvent.TYPES_ALL_MASK));
- }
-
- AccessibilityManager manager =
- new AccessibilityManager(mContext, mMockService, UserHandle.USER_CURRENT);
-
- verify(mMockService).addClient(any(IAccessibilityManagerClient.class), anyInt());
-
- return manager;
- }
-
- @MediumTest
- public void testGetAccessibilityServiceList() throws Exception {
- // create a list of installed accessibility services the mock service returns
- List<AccessibilityServiceInfo> expectedServices = new ArrayList<>();
- AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo();
- accessibilityServiceInfo.packageNames = new String[] { "foo.bar" };
- expectedServices.add(accessibilityServiceInfo);
-
- // configure the mock service behavior
- when(mMockService.getInstalledAccessibilityServiceList(anyInt()))
- .thenReturn(expectedServices);
-
- // invoke the method under test
- AccessibilityManager manager = createManager(true);
- List<AccessibilityServiceInfo> receivedServices =
- manager.getInstalledAccessibilityServiceList();
-
- verify(mMockService).getInstalledAccessibilityServiceList(UserHandle.USER_CURRENT);
- // check expected result (list equals() compares it contents as well)
- assertEquals("All expected services must be returned", expectedServices, receivedServices);
- }
-
- @MediumTest
- public void testInterrupt() throws Exception {
- AccessibilityManager manager = createManager(true);
- manager.interrupt();
-
- verify(mMockService).interrupt(UserHandle.USER_CURRENT);
- }
-
- @LargeTest
- public void testIsEnabled() throws Exception {
- // invoke the method under test
- AccessibilityManager manager = createManager(true);
- boolean isEnabledServiceEnabled = manager.isEnabled();
-
- // check expected result
- assertTrue("Must be enabled since the mock service is enabled", isEnabledServiceEnabled);
-
- // disable accessibility
- manager.getClient().setState(0);
-
- // wait for the asynchronous IBinder call to complete
- Thread.sleep(TIMEOUT_BINDER_CALL);
-
- // invoke the method under test
- boolean isEnabledServcieDisabled = manager.isEnabled();
-
- // check expected result
- assertFalse("Must be disabled since the mock service is disabled",
- isEnabledServcieDisabled);
- }
-
- @MediumTest
- public void testSendAccessibilityEvent_AccessibilityEnabled() throws Exception {
- AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
-
- AccessibilityManager manager = createManager(true);
- manager.sendAccessibilityEvent(sentEvent);
-
- assertSame("The event should be recycled.", sentEvent, AccessibilityEvent.obtain());
- }
-
- @MediumTest
- public void testSendAccessibilityEvent_AccessibilityDisabled() throws Exception {
- AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
-
- AccessibilityManager manager = createManager(false /* disabled */);
-
- try {
- manager.sendAccessibilityEvent(sentEvent);
- fail("No accessibility events are sent if accessibility is disabled");
- } catch (IllegalStateException ise) {
- // check expected result
- assertEquals("Accessibility off. Did you forget to check that?", ise.getMessage());
- }
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java b/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java
deleted file mode 100644
index e1c5cee..0000000
--- a/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Copyright (C) 2010 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;
-
-import android.accessibilityservice.AccessibilityService;
-import android.accessibilityservice.AccessibilityServiceInfo;
-import android.content.Intent;
-import android.os.Message;
-import android.view.accessibility.AccessibilityEvent;
-
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Queue;
-
-import junit.framework.TestCase;
-
-/**
- * This is the base class for mock {@link AccessibilityService}s.
- */
-public abstract class MockAccessibilityService extends AccessibilityService {
-
- /**
- * The event this service expects to receive.
- */
- private final Queue<AccessibilityEvent> mExpectedEvents = new LinkedList<AccessibilityEvent>();
-
- /**
- * Interruption call this service expects to receive.
- */
- private boolean mExpectedInterrupt;
-
- /**
- * Flag if the mock is currently replaying.
- */
- private boolean mReplaying;
-
- /**
- * Flag if the system is bound as a client to this service.
- */
- private boolean mIsSystemBoundAsClient;
-
- /**
- * Creates an {@link AccessibilityServiceInfo} populated with default
- * values.
- *
- * @return The default info.
- */
- public static AccessibilityServiceInfo createDefaultInfo() {
- AccessibilityServiceInfo defaultInfo = new AccessibilityServiceInfo();
- defaultInfo.eventTypes = AccessibilityEvent.TYPE_ANNOUNCEMENT;
- defaultInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_AUDIBLE;
- defaultInfo.flags = 0;
- defaultInfo.notificationTimeout = 0;
- defaultInfo.packageNames = new String[] {
- "foo.bar.baz"
- };
-
- return defaultInfo;
- }
-
- /**
- * Starts replaying the mock.
- */
- public void replay() {
- mReplaying = true;
- }
-
- /**
- * Verifies if all expected service methods have been called.
- */
- public void verify() {
- if (!mReplaying) {
- throw new IllegalStateException("Did you forget to call replay()");
- }
-
- if (mExpectedInterrupt) {
- throw new IllegalStateException("Expected call to #interrupt() not received");
- }
- if (!mExpectedEvents.isEmpty()) {
- throw new IllegalStateException("Expected a call to onAccessibilityEvent() for "
- + "events \"" + mExpectedEvents + "\" not received");
- }
- }
-
- /**
- * Resets this instance so it can be reused.
- */
- public void reset() {
- mExpectedEvents.clear();
- mExpectedInterrupt = false;
- mReplaying = false;
- }
-
- /**
- * Sets an expected call to
- * {@link #onAccessibilityEvent(AccessibilityEvent)} with given event as
- * argument.
- *
- * @param expectedEvent The expected event argument.
- */
- public void expectEvent(AccessibilityEvent expectedEvent) {
- mExpectedEvents.add(expectedEvent);
- }
-
- /**
- * Sets an expected call of {@link #onInterrupt()}.
- */
- public void expectInterrupt() {
- mExpectedInterrupt = true;
- }
-
- @Override
- public void onAccessibilityEvent(AccessibilityEvent receivedEvent) {
- if (!mReplaying) {
- return;
- }
-
- if (mExpectedEvents.isEmpty()) {
- throw new IllegalStateException("Unexpected event: " + receivedEvent);
- }
-
- AccessibilityEvent expectedEvent = mExpectedEvents.poll();
- assertEqualsAccessiblityEvent(expectedEvent, receivedEvent);
- }
-
- @Override
- public void onInterrupt() {
- if (!mReplaying) {
- return;
- }
-
- if (!mExpectedInterrupt) {
- throw new IllegalStateException("Unexpected call to onInterrupt()");
- }
-
- mExpectedInterrupt = false;
- }
-
- @Override
- protected void onServiceConnected() {
- mIsSystemBoundAsClient = true;
- }
-
- @Override
- public boolean onUnbind(Intent intent) {
- mIsSystemBoundAsClient = false;
- return false;
- }
-
- /**
- * Returns if the system is bound as client to this service.
- *
- * @return True if the system is bound, false otherwise.
- */
- public boolean isSystemBoundAsClient() {
- return mIsSystemBoundAsClient;
- }
-
- /**
- * Compares all properties of the <code>expectedEvent</code> and the
- * <code>receviedEvent</code> to verify that the received event is the one
- * that is expected.
- */
- private void assertEqualsAccessiblityEvent(AccessibilityEvent expectedEvent,
- AccessibilityEvent receivedEvent) {
- TestCase.assertEquals("addedCount has incorrect value", expectedEvent.getAddedCount(),
- receivedEvent.getAddedCount());
- TestCase.assertEquals("beforeText has incorrect value", expectedEvent.getBeforeText(),
- receivedEvent.getBeforeText());
- TestCase.assertEquals("checked has incorrect value", expectedEvent.isChecked(),
- receivedEvent.isChecked());
- TestCase.assertEquals("className has incorrect value", expectedEvent.getClassName(),
- receivedEvent.getClassName());
- TestCase.assertEquals("contentDescription has incorrect value", expectedEvent
- .getContentDescription(), receivedEvent.getContentDescription());
- TestCase.assertEquals("currentItemIndex has incorrect value", expectedEvent
- .getCurrentItemIndex(), receivedEvent.getCurrentItemIndex());
- TestCase.assertEquals("enabled has incorrect value", expectedEvent.isEnabled(),
- receivedEvent.isEnabled());
- TestCase.assertEquals("eventType has incorrect value", expectedEvent.getEventType(),
- receivedEvent.getEventType());
- TestCase.assertEquals("fromIndex has incorrect value", expectedEvent.getFromIndex(),
- receivedEvent.getFromIndex());
- TestCase.assertEquals("fullScreen has incorrect value", expectedEvent.isFullScreen(),
- receivedEvent.isFullScreen());
- TestCase.assertEquals("itemCount has incorrect value", expectedEvent.getItemCount(),
- receivedEvent.getItemCount());
- assertEqualsNotificationAsParcelableData(expectedEvent, receivedEvent);
- TestCase.assertEquals("password has incorrect value", expectedEvent.isPassword(),
- receivedEvent.isPassword());
- TestCase.assertEquals("removedCount has incorrect value", expectedEvent.getRemovedCount(),
- receivedEvent.getRemovedCount());
- assertEqualsText(expectedEvent, receivedEvent);
- }
-
- /**
- * Compares the {@link android.os.Parcelable} data of the
- * <code>expectedEvent</code> and <code>receivedEvent</code> to verify that
- * the received event is the one that is expected.
- */
- private void assertEqualsNotificationAsParcelableData(AccessibilityEvent expectedEvent,
- AccessibilityEvent receivedEvent) {
- String message = "parcelableData has incorrect value";
- Message expectedMessage = (Message) expectedEvent.getParcelableData();
- Message receivedMessage = (Message) receivedEvent.getParcelableData();
-
- if (expectedMessage == null) {
- if (receivedMessage == null) {
- return;
- }
- }
-
- TestCase.assertNotNull(message, receivedMessage);
-
- // we do a very simple sanity check since we do not test Message
- TestCase.assertEquals(message, expectedMessage.what, receivedMessage.what);
- }
-
- /**
- * Compares the text of the <code>expectedEvent</code> and
- * <code>receivedEvent</code> by comparing the string representation of the
- * corresponding {@link CharSequence}s.
- */
- private void assertEqualsText(AccessibilityEvent expectedEvent,
- AccessibilityEvent receivedEvent) {
- String message = "text has incorrect value";
- List<CharSequence> expectedText = expectedEvent.getText();
- List<CharSequence> receivedText = receivedEvent.getText();
-
- TestCase.assertEquals(message, expectedText.size(), receivedText.size());
-
- Iterator<CharSequence> expectedTextIterator = expectedText.iterator();
- Iterator<CharSequence> receivedTextIterator = receivedText.iterator();
-
- for (int i = 0; i < expectedText.size(); i++) {
- // compare the string representation
- TestCase.assertEquals(message, expectedTextIterator.next().toString(),
- receivedTextIterator.next().toString());
- }
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index e0ac393..353199a 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -645,20 +645,6 @@
}
@Test
- public void testDump_noDumpPermission() {
- doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
- eq(permission.DUMP), anyString());
-
- try {
- mNetworkScoreService.dump(
- new FileDescriptor(), new PrintWriter(new StringWriter()), new String[0]);
- fail("SecurityException expected");
- } catch (SecurityException e) {
- // expected
- }
- }
-
- @Test
public void testDump_doesNotCrash() {
when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER);
StringWriter stringWriter = new StringWriter();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java
new file mode 100644
index 0000000..5d09e31
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2010 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.accessibility;
+
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertSame;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.Instrumentation;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityManager;
+import android.view.accessibility.IAccessibilityManagerClient;
+
+import com.android.internal.util.IntPair;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for the AccessibilityManager by mocking the backing service.
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityManagerTest {
+ private static final boolean WITH_A11Y_ENABLED = true;
+ private static final boolean WITH_A11Y_DISABLED = false;
+
+ @Mock private IAccessibilityManager mMockService;
+ private MessageCapturingHandler mHandler;
+ private Instrumentation mInstrumentation;
+
+ @BeforeClass
+ public static void oneTimeInitialization() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mHandler = new MessageCapturingHandler(null);
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ }
+
+ private AccessibilityManager createManager(boolean enabled) throws Exception {
+ long serviceReturnValue = IntPair.of(
+ (enabled) ? AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED : 0,
+ AccessibilityEvent.TYPES_ALL_MASK);
+ when(mMockService.addClient(any(IAccessibilityManagerClient.class), anyInt()))
+ .thenReturn(serviceReturnValue);
+
+ AccessibilityManager manager =
+ new AccessibilityManager(mHandler, mMockService, UserHandle.USER_CURRENT);
+
+ verify(mMockService).addClient(any(IAccessibilityManagerClient.class), anyInt());
+ mHandler.setCallback(manager.getCallback());
+ mHandler.sendAllMessages();
+ return manager;
+ }
+
+ @Test
+ public void testGetAccessibilityServiceList() throws Exception {
+ // create a list of installed accessibility services the mock service returns
+ List<AccessibilityServiceInfo> expectedServices = new ArrayList<>();
+ AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo();
+ accessibilityServiceInfo.packageNames = new String[] { "foo.bar" };
+ expectedServices.add(accessibilityServiceInfo);
+
+ // configure the mock service behavior
+ when(mMockService.getInstalledAccessibilityServiceList(anyInt()))
+ .thenReturn(expectedServices);
+
+ // invoke the method under test
+ AccessibilityManager manager = createManager(true);
+ List<AccessibilityServiceInfo> receivedServices =
+ manager.getInstalledAccessibilityServiceList();
+
+ verify(mMockService).getInstalledAccessibilityServiceList(UserHandle.USER_CURRENT);
+ // check expected result (list equals() compares it contents as well)
+ assertEquals("All expected services must be returned", expectedServices, receivedServices);
+ }
+
+ @Test
+ public void testInterrupt() throws Exception {
+ AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+ manager.interrupt();
+
+ verify(mMockService).interrupt(UserHandle.USER_CURRENT);
+ }
+
+ @Test
+ public void testIsEnabled() throws Exception {
+ // Create manager with a11y enabled
+ AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+ assertTrue("Must be enabled since the mock service is enabled", manager.isEnabled());
+
+ // Disable accessibility
+ manager.getClient().setState(0);
+ mHandler.sendAllMessages();
+ assertFalse("Must be disabled since the mock service is disabled", manager.isEnabled());
+ }
+
+ @Test
+ public void testSendAccessibilityEvent_AccessibilityEnabled() throws Exception {
+ AccessibilityEvent sentEvent = AccessibilityEvent.obtain(
+ AccessibilityEvent.TYPE_ANNOUNCEMENT);
+
+ AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+ manager.sendAccessibilityEvent(sentEvent);
+
+ assertSame("The event should be recycled.", sentEvent, AccessibilityEvent.obtain());
+ }
+
+ @Test
+ public void testSendAccessibilityEvent_AccessibilityDisabled() throws Exception {
+ AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
+
+ AccessibilityManager manager = createManager(WITH_A11Y_DISABLED);
+ mInstrumentation.runOnMainSync(() -> {
+ try {
+ manager.sendAccessibilityEvent(sentEvent);
+ fail("No accessibility events are sent if accessibility is disabled");
+ } catch (IllegalStateException ise) {
+ // check expected result
+ assertEquals("Accessibility off. Did you forget to check that?", ise.getMessage());
+ }
+ });
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
index d44c1ca..5887215 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
@@ -20,11 +20,12 @@
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
-import static org.mockito.Matchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -48,6 +49,7 @@
import android.view.WindowManagerInternal.MagnificationCallbacks;
import com.android.internal.R;
+
import org.hamcrest.CoreMatchers;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
@@ -471,9 +473,10 @@
public void testResetIfNeeded_doesWhatItSays() {
mMagnificationController.register();
zoomIn2xToMiddle();
+ reset(mMockAms);
assertTrue(mMagnificationController.resetIfNeeded(false));
verify(mMockAms).notifyMagnificationChanged(
- eq(INITIAL_MAGNIFICATION_REGION), eq(1.0f), anyInt(), anyInt());
+ eq(INITIAL_MAGNIFICATION_REGION), eq(1.0f), anyFloat(), anyFloat());
assertFalse(mMagnificationController.isMagnifying());
assertFalse(mMagnificationController.resetIfNeeded(false));
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java b/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java
index 003f7ab..0dba35f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java
@@ -42,6 +42,10 @@
return super.sendMessageAtTime(message, uptimeMillis);
}
+ public void setCallback(Handler.Callback callback) {
+ mCallback = callback;
+ }
+
public void sendOneMessage() {
Message message = timedMessages.remove(0).first;
removeMessages(message.what, message.obj);
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 aa37407..5d0c23f 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 @@
// 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 af3201c..54ecab3 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 @@
* 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 0000000..8423aff
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
@@ -0,0 +1,52 @@
+/*
+ * 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.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 {
+ 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 c5cc2ff..5240586 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.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.WindowManagerService;
import com.android.server.wm.WindowTestUtils;
+import org.junit.After;
import org.junit.Before;
import org.mockito.MockitoAnnotations;
@@ -42,8 +43,7 @@
*/
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 @@
@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 @@
@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/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 0f1b81e..7a4746a 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import android.app.ActivityManager;
import android.app.IUserSwitchObserver;
import android.content.Context;
import android.content.IIntentReceiver;
@@ -49,16 +50,20 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static com.android.server.am.ActivityManagerService.CONTINUE_USER_SWITCH_MSG;
+import static com.android.server.am.ActivityManagerService.REPORT_LOCKED_BOOT_COMPLETE_MSG;
import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_COMPLETE_MSG;
import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_MSG;
import static com.android.server.am.ActivityManagerService.SYSTEM_USER_CURRENT_MSG;
import static com.android.server.am.ActivityManagerService.SYSTEM_USER_START_MSG;
import static com.android.server.am.ActivityManagerService.USER_SWITCH_TIMEOUT_MSG;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -71,9 +76,29 @@
private UserController mUserController;
private TestInjector mInjector;
+ private static final List<String> START_FOREGROUND_USER_ACTIONS =
+ Arrays.asList(
+ Intent.ACTION_USER_STARTED,
+ Intent.ACTION_USER_SWITCHED,
+ Intent.ACTION_USER_STARTING);
+
+ private static final List<String> START_BACKGROUND_USER_ACTIONS =
+ Arrays.asList(
+ Intent.ACTION_USER_STARTED,
+ Intent.ACTION_LOCKED_BOOT_COMPLETED,
+ Intent.ACTION_USER_STARTING);
+
+ private static final Set<Integer> START_FOREGROUND_USER_MESSAGE_CODES =
+ new HashSet<>(Arrays.asList(REPORT_USER_SWITCH_MSG, USER_SWITCH_TIMEOUT_MSG,
+ SYSTEM_USER_START_MSG, SYSTEM_USER_CURRENT_MSG));
+
+ private static final Set<Integer> START_BACKGROUND_USER_MESSAGE_CODES =
+ new HashSet<>(Arrays.asList(SYSTEM_USER_START_MSG, REPORT_LOCKED_BOOT_COMPLETE_MSG));
+
@Override
public void setUp() throws Exception {
super.setUp();
+ System.setProperty("dexmaker.share_classloader", "true");
mInjector = new TestInjector(getContext());
mUserController = new UserController(mInjector);
setUpUser(TEST_USER_ID, 0);
@@ -83,39 +108,62 @@
protected void tearDown() throws Exception {
super.tearDown();
mInjector.handlerThread.quit();
-
}
@SmallTest
- public void testStartUser() throws RemoteException {
- mUserController.startUser(TEST_USER_ID, true);
+ public void testStartUser_foreground() throws RemoteException {
+ mUserController.startUser(TEST_USER_ID, true /* foreground */);
Mockito.verify(mInjector.getWindowManager()).startFreezingScreen(anyInt(), anyInt());
Mockito.verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
Mockito.verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean());
Mockito.verify(mInjector.getWindowManager()).setSwitchingUser(true);
- startUserAssertions();
+ Mockito.verify(mInjector.getActivityStackSupervisor()).setLockTaskModeLocked(
+ nullable(TaskRecord.class),
+ eq(ActivityManager.LOCK_TASK_MODE_NONE),
+ anyString(),
+ anyBoolean());
+ startForegroundUserAssertions();
+ }
+
+ @SmallTest
+ public void testStartUser_background() throws RemoteException {
+ mUserController.startUser(TEST_USER_ID, false /* foreground */);
+ Mockito.verify(
+ mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
+ Mockito.verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
+ Mockito.verify(mInjector.getActivityStackSupervisor(), never()).setLockTaskModeLocked(
+ nullable(TaskRecord.class),
+ eq(ActivityManager.LOCK_TASK_MODE_NONE),
+ anyString(),
+ anyBoolean());
+ startBackgroundUserAssertions();
}
@SmallTest
public void testStartUserUIDisabled() throws RemoteException {
mUserController.mUserSwitchUiEnabled = false;
- mUserController.startUser(TEST_USER_ID, true);
+ mUserController.startUser(TEST_USER_ID, true /* foreground */);
Mockito.verify(mInjector.getWindowManager(), never())
.startFreezingScreen(anyInt(), anyInt());
Mockito.verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
Mockito.verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
- startUserAssertions();
+ startForegroundUserAssertions();
}
- private void startUserAssertions() throws RemoteException {
- List<String> expectedActions = Arrays.asList(Intent.ACTION_USER_STARTED,
- Intent.ACTION_USER_SWITCHED, Intent.ACTION_USER_STARTING);
+ private void startUserAssertions(
+ List<String> expectedActions, Set<Integer> expectedMessageCodes)
+ throws RemoteException {
assertEquals(expectedActions, getActions(mInjector.sentIntents));
- Set<Integer> expectedCodes = new HashSet<>(
- Arrays.asList(REPORT_USER_SWITCH_MSG, USER_SWITCH_TIMEOUT_MSG,
- SYSTEM_USER_START_MSG, SYSTEM_USER_CURRENT_MSG));
Set<Integer> actualCodes = mInjector.handler.getMessageCodes();
- assertEquals("Unexpected message sent", expectedCodes, actualCodes);
+ assertEquals("Unexpected message sent", expectedMessageCodes, actualCodes);
+ }
+
+ private void startBackgroundUserAssertions() throws RemoteException {
+ startUserAssertions(START_BACKGROUND_USER_ACTIONS, START_BACKGROUND_USER_MESSAGE_CODES);
+ }
+
+ private void startForegroundUserAssertions() throws RemoteException {
+ startUserAssertions(START_FOREGROUND_USER_ACTIONS, START_FOREGROUND_USER_MESSAGE_CODES);
Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG);
assertNotNull(reportMsg);
UserState userState = (UserState) reportMsg.obj;
@@ -275,6 +323,7 @@
UserManagerService userManagerMock;
UserManagerInternal userManagerInternalMock;
WindowManagerService windowManagerMock;
+ ActivityStackSupervisor activityStackSupervisor;
private Context mCtx;
List<Intent> sentIntents = new ArrayList<>();
@@ -287,6 +336,7 @@
userManagerMock = mock(UserManagerService.class);
userManagerInternalMock = mock(UserManagerInternal.class);
windowManagerMock = mock(WindowManagerService.class);
+ activityStackSupervisor = mock(ActivityStackSupervisor.class);
}
@Override
@@ -321,12 +371,6 @@
}
@Override
- void stackSupervisorSetLockTaskModeLocked(TaskRecord task, int lockTaskModeState,
- String reason, boolean andResume) {
- Log.i(TAG, "stackSupervisorSetLockTaskModeLocked");
- }
-
- @Override
WindowManagerService getWindowManager() {
return windowManagerMock;
}
@@ -347,16 +391,15 @@
}
@Override
- boolean stackSupervisorSwitchUserLocked(int userId, UserState uss) {
- Log.i(TAG, "stackSupervisorSwitchUserLocked " + userId);
- return true;
- }
-
- @Override
void startHomeActivityLocked(int userId, String reason) {
Log.i(TAG, "startHomeActivityLocked " + userId);
}
- }
+
+ @Override
+ ActivityStackSupervisor getActivityStackSupervisor() {
+ return activityStackSupervisor;
+ }
+ }
private static class TestHandler extends Handler {
private final List<Message> mMessages = new ArrayList<>();
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 f3f68ff..2663aaf 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 org.mockito.ArgumentCaptor;
+import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
@@ -249,6 +250,25 @@
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 92233b1..f80ee73 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -356,6 +356,7 @@
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 @@
}
/**
- * @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 @@
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 @@
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 @@
int videoState,
StatusHints statusHints,
Bundle extras,
- Bundle intentExtras) {
+ Bundle intentExtras,
+ long creationTimeMillis) {
mTelecomCallId = telecomCallId;
mHandle = handle;
mHandlePresentation = handlePresentation;
@@ -697,6 +715,7 @@
mStatusHints = statusHints;
mExtras = extras;
mIntentExtras = intentExtras;
+ mCreationTimeMillis = creationTimeMillis;
}
/** {@hide} */
@@ -716,7 +735,8 @@
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 85a92d1..6212a77 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -59,6 +59,7 @@
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 @@
int videoState,
List<String> conferenceableCallIds,
Bundle intentExtras,
- Bundle extras) {
+ Bundle extras,
+ long creationTimeMillis) {
mId = id;
mState = state;
mDisconnectCause = disconnectCause;
@@ -111,6 +113,7 @@
mConferenceableCallIds = Collections.unmodifiableList(conferenceableCallIds);
mIntentExtras = intentExtras;
mExtras = extras;
+ mCreationTimeMillis = creationTimeMillis;
}
/** The unique ID of the call. */
@@ -289,6 +292,13 @@
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 @@
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 @@
videoState,
conferenceableCallIds,
intentExtras,
- extras);
+ extras,
+ creationTimeMillis);
}
@Override
@@ -393,6 +405,7 @@
destination.writeInt(mSupportedAudioRoutes);
destination.writeByte((byte) (mIsRttCallChanged ? 1 : 0));
destination.writeParcelable(mRttCall, 0);
+ destination.writeLong(mCreationTimeMillis);
}
@Override
diff --git a/tests/UiBench/Android.mk b/tests/UiBench/Android.mk
index 36ebc90..71067ae 100644
--- a/tests/UiBench/Android.mk
+++ b/tests/UiBench/Android.mk
@@ -2,7 +2,7 @@
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := 24
+LOCAL_SDK_VERSION := current
LOCAL_MIN_SDK_VERSION := 21
# omit gradle 'build' dir
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 8461905..90f713b 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -155,7 +155,10 @@
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 @@
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 @@
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 eefa320..8062c2e6 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -101,20 +101,24 @@
// 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 fad9edd..a031ea4 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) {
+// 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;
+
+ // The UTF-16 index into the string where this span starts.
+ uint32_t first_char;
+
+ // The UTF-16 index into the string where this span ends, inclusive.
+ uint32_t 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;
+}
+
+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};
+}
+
+// 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 {
+ merged_spans.push_back(std::move(*untranslatable_iter));
+ ++untranslatable_iter;
+ }
+ }
+
+ while (span_iter != sorted_spans.end()) {
+ merged_spans.push_back(std::move(*span_iter));
+ ++span_iter;
+ }
+
+ while (untranslatable_iter != sorted_untranslatable_sections.end()) {
+ merged_spans.push_back(std::move(*untranslatable_iter));
+ ++untranslatable_iter;
+ }
+ return merged_spans;
+}
+
+std::unique_ptr<StyledString> PseudolocalizeStyledString(StyledString* string,
+ Pseudolocalizer::Method method,
+ StringPool* pool) {
Pseudolocalizer localizer(method);
- const StringPiece original_text = *string->value->str;
+ // 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);
+
+ // 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);
+
+ // 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);
+
+ // 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 (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();
+ }
+ }
+
+ // 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);
- // 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});
- }
-
- // 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});
- }
-
- // 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;
- } else {
- ranges.insert(iter, Range{span.last_char, false, nullptr, &localized.spans[i].last_char});
+ // 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});
}
}
-
- // 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});
- }
-
- // 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});
- }
- }
-
- localized.str += localizer.Start();
-
- // 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;
- }
-
- if (ranges[i].update_start) {
- *ranges[i].update_start = localized.str.size();
- }
-
- if (ranges[i].update_end) {
- *ranges[i].update_end = localized.str.size();
- }
-
- if (ranges[i].toggle_translatability) {
- translatable = !translatable;
- }
-
- if (translatable) {
- localized.str += localizer.Text(original_text.substr(start, len));
- } else {
- localized.str += original_text.substr(start, len);
- }
- }
-
- localized.str += localizer.End();
-
return util::make_unique<StyledString>(pool->MakeRef(localized));
}
@@ -175,8 +233,7 @@
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 @@
}
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 @@
}
}
-/**
- * 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 @@
} // 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 4db37db..b08e1da 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
@@ -25,7 +25,7 @@
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 @@
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 @@
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::string("[Ĥéļļö ").size(),
+ 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("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 @@
{
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));